C语言基础 Day11 文件(下)

1. fprintf和fscanf

在说fprintf函数和fscanf函数之前,首先说说什么是变参函数。变参函数是形参中带有...,最后一个固定参数通常是格式描述串,包含了格式匹配符,函数的参数个数、类型、顺序由这个固定参数决定。比如举例最简单的printf函数:

printf("hello world\n");
printf("%s\n", "hello world");
printf("%s %s\n", "hello", "world");

在这三个函数调用语句中,第一个固定参数由于没有格式匹配字符,所以就一个参数。而第二个中固定参数有一个格式匹配字符,所以就两个参数,依次类推。

对于fprintf函数和fscanf函数,在上一节的表中就已经写明了其用法。接下来看看关于这两个函数的使用例子。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

// 使用fprintf向文件中写数据
void write_file(char *filename)
{
	srand((unsigned int)time(NULL));

	FILE *fp = fopen(filename, "w");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < 10; i++)
	{
		fprintf(fp, "%d\n", rand() % 10);
	}

	fclose(fp);
}

// 使用fscanf从文件中读数据
void read_file1(char *filename)
{
	FILE *fp = fopen(filename, "r");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	int number = 0;

	// 读次数大于数字个数
	for (int i = 0; i < 12; i++)
	{
		fscanf(fp, "%d\n", &number);
		printf("%d\n", number);
		// 每次读完打印后重置为0
		number = 0;
	}

	fclose(fp);
}


// 使用fscanf循环读文件
void read_file2(char *filename)
{
	FILE *fp = fopen(filename, "r");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	int number = 0;
	while (1)
	{
		fscanf(fp, "%d\n", &number);
		printf("%d\n", number);

		if (feof(fp))
			break;
	}

	fclose(fp);
}

// fgets循环读文件
void read_file3(char *filename)
{
	FILE *fp = fopen(filename, "r");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	char buff[1024];
	while (1)
	{
		fgets(buff, 1024, fp);
		if (feof(fp))
			break;
		printf("%s", buff);
	}

	fclose(fp);
}

int main(int argc, char* argv[])
{
	char *filename = "test01-1.txt";

	write_file(filename);
	read_file1(filename);
	printf("==============================\n");
	read_file2(filename);
	printf("==============================\n");
	read_file3(filename);

	system("pause");
	return 0;
}

#endif

在使用fscanf函数的时候,特别需要注意一点的是fscanf函数每次在调用的时候都会判断下一次的调用是否匹配参数format,如果匹配就会提前结束文件,也就是feof为真。因此对于fscanf函数,我们都是先获取数据之后再进行判断。

下面给一个关于文件版排序的例子。利用计算机生成随机数并写入到文件中,再将文件中的随机数进行从小到大排序写入到文件。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

#define N 15

void write_file(char *filename)
{
	srand((unsigned int)time(NULL));  // 随机数种子

	FILE *fp = fopen(filename, "w");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < N; i++)
	{
		fprintf(fp, "%d\n", rand() % 100); // 将生成的随机数写入文件
	}

	fclose(fp);
}

void bubble_sort(int a[], int size)
{
	for (int i = 0; i < size - 1; i++)
	{
		for (int j = 0; j < size - i - 1; j++)
		{
			if (a[j] > a[j + 1])
			{
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
}


void read_file(char *filename)
{
	FILE *fp = fopen(filename, "r");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	int *p = (int *)malloc(sizeof(int)* 100);
	int i = 0;

	while (1)
	{
		fscanf(fp, "%d\n", &p[i ++]);  // 从文件中读取一个随机数,存入数组p
		if (feof(fp))	// 先存储后判断,防止最后一个元素丢失
		{
			break;
		}
	}


	for (int j = 0; j < i; j++)
	{
		printf("%d ", p[j]);
	}
	putchar('\n');

	fclose(fp);

	bubble_sort(p, i);  // 对读取到的乱序数组进行排序

	fp = fopen(filename, "w");	// 用w的方式打开文件,清空原来的内容
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int j = 0; j < i; j++)
	{
		printf("%d ", p[j]);
		fprintf(fp, "%d\n", p[j]);    // 写排序好的数组到文件
	}
	putchar('\n');

	fclose(fp);
}


int main(int argc, char* argv[])
{
	char *filename = "test02-1.txt";

	//write_file(filename);
	read_file(filename);

	system("pause");
	return 0;
}

#endif

2. fwrite与fread

这两个函数主要二进制的文件输入和输出。由于是二进制操作,所以对于文本文件和二进制文件都可以进行操作,因为文本文件在进行存储的时候底层也是用二进制进行存储的。对于这两个函数在也在上面一节的表中说明了用法。对于这两个函数的返回值,成功永远都是nmemb的值,读写的总大小是size*nmemb

关于两个函数的示例如下。示例主要是给了一些列的学生的姓名、年龄和电话,并按照年龄从小到大的顺序进行了冒泡排序。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

#define MAX_SIZE 100
#define STUDENT_CNT 15
#define N STUDENT_CNT 


typedef struct Student
{
	char *name;
	unsigned int age;
	char *tel;
} Student;


// 将数据写入文件中
void write_file(char *filename)
{
	// 以写的方式打开文件判断是否打开成功
	FILE *fp = fopen(filename, "w");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	srand((unsigned int)time(NULL));

	Student *std_list;
	std_list = (Student *)malloc(sizeof(Student)* MAX_SIZE);
	char buff[15] = {0};

	// 生成学生的基本信息
	for (int i = 0; i < N; i++)
	{

		// 在堆上开辟学生的基本信息存储空间
		std_list[i].name = (char *)malloc(sizeof(char)* 15);
		std_list[i].tel = (char *)malloc(sizeof(char)* 15);
		std_list[i].age = rand() % 30 + 20;
		
		memset(std_list[i].name, 0, 15);
		memset(std_list[i].tel, 0, 15);
		
		// 初始化信息
		int name_len = rand() % 6 + 3;
		for (int i = 0; i < name_len; i++)
		{
			buff[i] = rand() % 26 + 'a';
		}
		strcpy(std_list[i].name, buff);
		sprintf(std_list[i].tel, "%d", rand() % 100000 + 100000000);
		
		// 将结构体的信息写入文件中
		int ret = 0;

		// 学生姓名
		ret = fwrite(std_list[i].name, 1, 15, fp);
		if (ret == 0)
		{
			perror("fwrite error");
			return;
		}

		// 学生年龄
		ret = fwrite(&std_list[i].age, 1, sizeof(unsigned int), fp); 
		if (ret == 0)
		{
			perror("fwrite error");
			return;
		}

		// 学生电话
		ret = fwrite(std_list[i].tel, 1, 15, fp); 
		if (ret == 0)
		{
			perror("fwrite error");
			return;
		}

	}


	fclose(fp);

}

// 对堆上的元素进行冒泡排序
void bubble_sort(Student *p, int size)
{
	for (int i = 0; i < size - 1; i++)
	{
		for (int j = 0; j < size - 1 - i; j++)
		{
			if (p[j].age > p[j + 1].age)
			{
				Student s = p[j];
				p[j] = p[j + 1];
				p[j + 1] = s;
			}
		}
	}
}

// 从文件中使用fread读取数据
void read_file(char *filename)
{
	// 打开文件
	FILE *fp = fopen(filename, "r");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	// 在堆上开辟学生数组
	Student *stu = (Student *)malloc(sizeof(Student)* MAX_SIZE);
	
	int i = 0;

	// 读取数据
	while (1)
	{
		// 开辟姓名和电话的存储空间
		stu[i].name = (char *)malloc(sizeof(char)* 15);
		stu[i].tel = (char *)malloc(sizeof(char)* 15);
		fread(stu[i].name, 1, 15, fp);
		
		// 判断是否文件末尾
		if (feof(fp))
		{
			break;
		}
		fread(&stu[i].age, 1, sizeof(unsigned int), fp);
		fread(stu[i].tel, 1, 15, fp);
		printf("学生姓名: %s   年龄: %u    电话: %s\n", stu[i].name, stu[i].age, stu[i].tel);
		i++;
	}

	bubble_sort(stu, i);

	printf("按照年龄从小到大排序后:\n");

	for (int j = 0; j < i; j++)
	{
		printf("学生姓名: %s   年龄: %u    电话: %s\n", stu[j].name, stu[j].age, stu[j].tel);
	}

	fclose(fp);
}

int main(int argc, char* argv[])
{
	char *filename = "test03-1.txt";
	write_file(filename);

	read_file(filename);

	system("pause");
	return 0;
}

#endif

会使用这两个函数之后,我们可以进行一些其它文件的操作。比如文件的复制。下面给出一个关于文件复制的代码。在文件复制中需要注意的是fwrite中第三个参数传的数据应该是fread的返回值,代码如下。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int file_copy(char *filename1, char *filename2)
{
	FILE *rfp = fopen(filename1, "rb");
	if (rfp == NULL)
	{
		perror("file1 fopen error");
		return -1;
	}

	FILE *wfp = fopen(filename2, "wb");
	if (wfp == NULL)
	{
		perror("file2 fopen error");
		return -1;
	}

	char buff[256];
	
	int ret = 0;
	while (1)
	{
		ret = fread(buff, 1, sizeof(buff), rfp);
		printf("ret = %d\n", ret);
		if (feof(rfp))
		{
			break;
		}
		fwrite(buff, 1, ret, wfp);
	}

	fclose(wfp);
	fclose(rfp);

	return 0;
}

int main(int argc, char* argv[])
{

	if (argc != 3) return -1;

	printf("%s\n%s\n", argv[1], argv[2]);
	file_copy(argv[1], argv[2]);

	system("pause");
	return 0;
}

#endif

3. 随机读写

对于文件的随机读写,我们主要控制的是读写光标的位置,读写光标也就是我们说的文件读写指针。在程序中,读写光标我们定义了三个关键的宏代表了起始、当前、结束。

宏名含义
SEEK_SET文件起始位置
SEEK_CUR读写指针当前位置
SEEK_END文件结束位置

对于文件位置的操作,我们有三个函数:

函数名原型返回值参数作用tip
fseekint fseek(FILE *stream, long offset, int whence);成功返回0,失败返回-1stream: 已经打开的文件指针
offset: 根据whence移动的偏移量,可以是正数,也可以是负数。正数往后移动,负数往前移动
whence: 取值为上述的三个宏
移动文件流的读写位置-
ftelllong ftell(FILE *stream);成功返回当前文件流的读写位置,失败返回-1stream: 已经打开的文件指针获取文件流的读写位置-
rewindvoid rewind(FILE *stream);-stream: 已经打开的文件指针把文件流的读写位置移动到文件开头-

下面给出关于随机读写文件的示例代码。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


typedef struct Student
{
	char name[12];
	unsigned int age;
	unsigned int sno;
} Student;


void test1()
{
	Student s_list[4] = {
		"张三", 16, 26123,
		"李四", 20, 26433,
		"王麻子", 26, 24733,
		"老六", 18, 62356
	};

	Student stu;

	FILE *fp = fopen("test05-1.txt", "wb+");	
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	int ret = fwrite(s_list, 1, sizeof(s_list), fp);	// 以二进制的方式写入
	printf("ret = %d\n", ret);

	fseek(fp, sizeof(stu) * 2, SEEK_SET);
	ret = ftell(fp);	// 获取读写指针到文件起始位置的偏移量
	printf("ret = %d\n", ret);

	ret = fread(&stu, 1, sizeof(stu), fp);
	printf("ret = %d\n", ret);
	printf("姓名: %s, 年龄: %u, 编号: %u\n", stu.name, stu.age, stu.sno);

	fseek(fp, 0, SEEK_END);		// 将读写指针放在文件结尾
	ret = ftell(fp);
	printf("文件大小为: %d\n", ret);

	rewind(fp);		// 回卷读写指针
	ret = fread(&stu, 1, sizeof(stu), fp);
	printf("ret = %d\n", ret);
	printf("姓名: %s, 年龄: %u, 编号: %u\n", stu.name, stu.age, stu.sno);

	fclose(fp);
}

void test2()
{
	FILE *fp = fopen("test05-2.txt", "w+");

	int ret = fputs("11111", fp);
	printf("ret1 = %d\n", ret);

	ret = fputs("22222", fp);
	printf("ret2 = %d\n", ret);

	ret = fputs("33333", fp);
	printf("ret3 = %d\n", ret);

	char buff[1024] = {0};

	/*char *ptr = fgets(buff, sizeof(buff), fp);
	if (feof(fp))
	{
		printf("error\n");
	}
	if (ptr == NULL)
		printf("ptr = NULL\n", ptr);
	else
		printf("ptr = %s\n", ptr);
	memset(buff, 0, sizeof(buff));*/
	fseek(fp, -10, SEEK_CUR);
	
	char *ptr = fgets(buff, sizeof(buff), fp);
	if (ptr == NULL)
		printf("ptr = NULL\n", ptr);
	else
		printf("ptr = %s\n", ptr);
	memset(buff, 0, sizeof(buff));


	rewind(fp);
	ptr = fgets(buff, sizeof(buff), fp);
	if (ptr == NULL)
		printf("ptr = NULL\n", ptr);
	else
		printf("ptr = %s\n", ptr);

	fclose(fp);

}

void test3()
{
	FILE *fp = fopen("test05-3.txt", "r+");
	if (fp == NULL)
	{
		perror("fopen error");
		return;
	}

	char buff[6] = { 0 };
	char *ptr = fgets(buff, 6, fp);

	printf("ptr = %s, buff = %s\n", ptr, buff);

	fseek(fp, 0, SEEK_CUR);
	int ret = fputs("12345", fp);
	printf("ret = %d\n", ret);

	fclose(fp);

}

int main(int argc, char* argv[])
{
	//test1();
	//test2();
	test3();

	system("pause");
	return 0;
}

#endif

在test1中我们使用了fseek(fp, 0, SEEK_END) 与 ftell(fp)联合使用获取到了文件的大小。在test2中我们需要知道文件以w+打开时候到文件末尾要进行读操作会发生乱码的错误,此时应该先移动文件读写的指针再进行读操作就不会出错。在test3中我们使用了r+的方式打开了文件,此时会发现进行写操作的时候在Windows中无法写入,在Linux中不受影响。因此在Windows中如果需要写需要在写操作前添加fseek(fp, 0, SEEK_CUR);,虽然看着是废话,但是是必须写的。

4. Linux和Windows文件的区别与文件的其它操作

Linux和window文件的区别主要在于以下三点:

  • 对于二进制文件的操作,Windows下必须使用“b”,而Linux下二进制文件和文本文件没有任何区别。
  • Windows下回车用\r,换行用\n,而在Linux中回车换行都是\n
  • 对文件指针来说,先写后读的操作在Windows和Linux中的效果是一样的。但是如果是先读后写,Linux中无需修改,而在Windows中必须在写操作前加上fseek(fp, 0, SEEK_CUR);来获取文件读写指针,使之生效。

在前面,我们使用了fseek函数与ftell函数计算了文件的大小。但是这种算法需要打开文件进行操作。打开文件对于系统而言,系统资源消耗巨大。所以此处我们引入了一个新的操作,即获取文件的状态。使用该函数和结构体的使用在sys/stat.hsys/types.h头文件中。函数原型为int stat(const char *path, struct stat *buf);,其中返回值表示是否成功获取到了文件的状态,0表示成功,-1表示失败。其中形式参数中path表示的是文件路径,buf是一个关于stat的结构体,该结构体的定义如下:

struct stat {
	dev_t         st_dev;         //文件的设备编号
	ino_t         st_ino;          //节点
	mode_t        st_mode;   //文件的类型和存取的权限
	nlink_t       st_nlink;     //连到该文件的硬连接数目,刚建立的文件值为1
	uid_t         st_uid;         //用户ID
	gid_t         st_gid;         //组ID
	dev_t         st_rdev;      //(设备类型)若此文件为设备文件,则为其设备编号
	off_t         st_size;        //文件字节数(文件大小)
	unsigned long st_blksize;   //块大小(文件系统的I/O 缓冲区大小)
	unsigned long st_blocks;    //块数
	time_t        st_atime;     //最后一次访问时间
	time_t        st_mtime;    //最后一次修改时间
	time_t        st_ctime;     //最后一次改变时间(指属性)
};

通过成员变量就可以获取到文件相应的一些信息。下面给出一个关于获取文件大小与最后一次修改时间的例子。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char* argv[])
{
	struct stat buf;

	int ret = stat("06-获取文件属性.c", &buf);

	printf("文件大小: %d\n", buf.st_size);

	time_t tm = buf.st_mtime;
	struct tm *ptm = localtime(&tm);

	
	printf("文件的最后一次修改时间: %04d/%02d/%02d  %02d:%02d:%02d\n", 1900 + ptm->tm_year, 1 + ptm->tm_mon, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);

	system("pause");
	return 0;
}

#endif

除了关于文件状态信息的获取,有时候我们也需要对文件进行删除或者重命名。下面给出这两个函数的原型:

int remove(const char *pathname);  // 删除文件。
int rename(const char *oldpath, const char *newpath);  // 重名文件

最后谈谈关于缓冲区刷新的问题。标准输出对应着标准输出缓冲区,写给屏幕都数据,都是先存在缓冲区中,由缓冲区一次性刷新到物理设备上。标准输入对应着标准输入缓冲区,从键盘上读取的数据,直接读到缓冲区中,由缓冲区提供给程序数据。预读入,缓输出。缓冲分为行缓冲、全缓冲和无缓冲。

  • 行缓冲:printf对应的就是行缓冲,遇到\n就会将缓冲区的数据刷新到物理设备上。
  • 全缓冲:文件就是全缓冲。缓冲区存满,数据才会刷新到物理设备上。
  • 无缓冲:perror就是无缓冲。只要缓冲区有数据就会立即刷新到物理设备。

由于文件时全缓冲,所以缓冲区没有满的时候就只有关闭的时候才能让缓冲区自动刷新。如果想要手动刷新,则需要用到fflush()函数。fflush的函数原型为int fflush(FILE *stream);,其中返回值为0表示成功,-1表示失败。下面给出一个关于文件缓冲区刷新的例子。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

int main0701(void)
{
	FILE *fp = fopen("test07.txt", "w+");
	if (!fp)
	{
		perror("fopen error");
		return -1;
	}
	char m = 0;

	while (1) 
	{
		scanf("%c", &m);
		if (m == ':')
		{
			break;
		}
		fputc(m, fp);
		fflush(fp);  // 手动刷新文件缓冲到物理磁盘。
	}
	// 当文件关闭时,会自动刷新缓冲区。
	fclose(fp);

	system("pause");
	return EXIT_SUCCESS;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值