C语言--文件操作

时间:2024-04-04 13:26:05
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为⽂件指针。
C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。

2.文件的打开和关闭

#include<stdio.h>
int main()
{
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

这个是所有文件,无论进行任何操作都会经历的文件的打开和关闭,中间可能我们会进行文件的读和文件的写,以及对于文件的修改等等操作。

3.二进制文件和文本文件

数据在内存里面以二进制的形式存储,如果不进行转换输出到文件里面就是二进制文件;

如果转换成ASCII形式,以ASCII形式存储的文件就是文本文件;

对于二进制的文件,我们无法看懂,如果是在VS上面,可以使用二进制编辑器进行转换,这样的话,文件里面的内容就会以16进制的形式显示出来,我们这个时候就可以正常阅读文件了;

4.文件的读和写

我们首先要清楚什么是文件的读和写?

文件的读就是从文件里面获取数据,文件的写就是从程序向文件里面写入数据;

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

我们这段代码是进行了读文件的操作,那么这个文件必须是存在的,只有这样我们才能够进行相应的操作,但是我们路径下面是没有这个文件的,所以perror就会显示对应的错误;如果已经新建了这个文件,那么程序运行就不会显示任何的错误;

如果我们是一些的方式,目录就会自己新建生成test.txt文件的;

5.文件的顺序读写函数

(1)fputc函数

这个函数具有2个参数,我们可见就是把第一个参数(字符)写到第二个参数(文件)里面去;

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

上面的代码是一次写入一个字符,我们也可以利用for循环语句,一次写入多个字符,下面的这段代码就是写入26个字符;

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	char ch = 0;
	for (ch = 'a'; ch < 'z'; ch++)
	{
		fputc(ch, pf);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

(2)fgetc函数

这个函数的返回值是int类型的,如果成功读取返回就是对应字符的ASCII值,如果是遇到文件的末尾或者读取失败,就会返回EOF(数值是-1);我们可以利用while循环读取文件里面的字符;

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int i = 0;
	int ch = 0;
	while((ch=fgetc(pf))!=EOF)
	{
		printf("%c", ch);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

(3)fputs函数

上面的两个函数一次只能够读一个字符或者是写一个字符,我们写入字符串(也就是一次性写一堆字符)就可以使用fputs函数;

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("hello world", pf);
	fputs("hello china", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

我们可以多次进行写入,但是这个函数是没有换行的功能的,需要我们手动进行换行,也就是添加斜杠n进行换行;

(4)fgets函数

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	char arr[10] = { 0 };
	fgets(arr, 10, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

这个函数有3个参数,第一个是指针数组(我们读取的数据放进去),第二个参数是读取的个数,第三个参数是文件流(就是从哪个文件里面读取);

实际上,我们这里写的是10个字符,他只会读取9个字符,最后一个留给了斜杠0;

如果我们读取10个字符,文件只有5个字符,这个时候就会读取文件末尾的斜杠0读取进来(这个过程我们通过调试可以观察到),下面是调试的截图,读者可以自行尝试:

(5)fprintf函数

这个函数和我们熟知的printf函数参数很相似,就是多了一个文件流参数:对于初学者,我们可先按照printf函数的形式进行写代码,最后加上我们的文件流就可以了:

struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "章山",18,99.3f };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fprintf(pf,"%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

这个里面我们是创建了一个结构体类型的变量,最后打印姓名,年龄和成绩,由此可见,写法和printf相似,就是在前面加上文件流pf,这样就可以写入文件里面去了;

(6)fscanf函数

我们把写进去的数据读出来,最后我们使用printf打印到我们的屏幕上;

因为fscanf要地址,name这个数组已经是地址,不需要加上取地址符号,其他的两个要加上取地址符号;


上面我们介绍的六个函数,适用于所有的流,既可以是文件流pf(我们上面的案例使用的全部是pf文件流),也可以是标准流,例如我们把pf改为stdout这样就可以把内容打印在屏幕上面(使用pf直接写到文件里面去了);


(7)fwrite函数

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int sz = sizeof(arr) / sizeof(arr[0]);
	fwrite(arr, sizeof(arr[0]), sz, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

这个函数有4个参数,第一个是地址,第二合适单个元素的大小,第三个是个数(写的个数),第四个是文件流,这个是以二进制写入的(wb)我们看不懂,

我们可以使用下面的函数fread同样以二进制的方式读出来;


(8)fread函数

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int sz = sizeof(arr) / sizeof(arr[0]);
	fread(arr, sizeof(arr[0]), sz, pf);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

上面的读出前提是我们已经知道数组大小,如果不知道,我们可以利用循环读取:

我们之所以能够这样做是因为fread的返回值是读取成功的字符的个数,我们利用fread的返回值,读取到最后就可以自动退出循环,这样即使不知道数组的大小,我们也可以进行打印;


78两个函数是以二进制的方式进行读和写的。


6.一组函数的对比

int main()
{
	char arr[300] = { 0 };
	struct S s = { "章山",18,99.3f };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	sprintf(arr, "%s %d %f", s.name, s.age, s.score);
	printf("%s\n", arr);
	struct S t = { 0 };
	sscanf(arr, "%s %d %f", t.name, &(t.age), &(t.score));
	printf("%s %d %f", t.name, t.age, t.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

sprintf和sscanf函数,第一个是把格式化的数据转化为字符串,第二个是把字符串转换为格式化的数据;

7.文件的随机读写函数

(1)fseek函数

根据⽂件指针的位置和偏移量来定位⽂件指针(⽂件内容的光标)。
第三个参数有以下的多种形式可供我们选择;
第一次打印a,偏移之后在读取,就会打印f了;
如果第三个参数写的是SEEK_END相当于指向了文件的末尾,如果我们还想打印f就需要设置第二
个参数是-7,相当于从末尾偏移7个长度
(2)ftell函数
返回⽂件指针相对于起始位置的偏移量
还是上面的例子,我们计算的就是f相对于起始位置的偏移量,也就是5,打印的结果就是5;
我们可以使用fseek(pf,0,SEEK_END),这个时候使用ftell(pf)就是计算的字符串的长度;
(3)rewind函数
让⽂件指针的位置回到⽂件的起始位置;
这个里面我们本来已经偏移4个单位长度,rewind直接回到起点,打印的还是字符a;

8.文件读取结束

牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束
fgetc读取过程返回EOF,就说明读取结束;
fgets读取过程返回NULL,就说明读取结束;
如果是读取到末尾结束了,feof返回的是非0数字,我们使用if语句进行判断;
由此可见,feof是判断文件是否因为读取到末尾才结束;
否则使用ferror判断读取错误(如果是读取出错结束,ferror返回非0数字),perror打印错误原因。