C++(week3):C语言文件操作

(十二) 文件

1.流

在这里插入图片描述


(1)流模型

data sink:数据接收端、数据汇

在这里插入图片描述

优点:
①程序员读写文件时,不需要关心文件的位置
②数据源(data source) 和 数据汇(data sink) 是解耦的


(2)程序员视角的文件

存放的是一个一个字节。
EOF(end of file)指向文件末尾后一个位置,EOF是一个宏,值为-1

在这里插入图片描述


(3)缓冲区类型:满缓冲、行缓冲、无缓冲

①满缓冲 / 全缓冲 (Fully Buffered):数据积累到缓冲区满时才进行I/O操作,适用于大多数文件的读写操作。 [缓冲区空才从输入流读数据;缓冲区满向输出流中写入数据。]
行缓冲 (Line Buffered):当读取或写入一行数据时触发I/O操作,常用于交互式设备,如终端。 [以行为单位进行读和写,如stdin、stdout]
无缓冲 (Unbuffered):每次I/O操作都直接进行系统调用,不经过缓冲区,通常用于错误日志或重要数据的记录。 [没有缓冲区,立即输入输出,例如:标准错误流 stderr]

在这里插入图片描述

在这里插入图片描述

刷新输出缓冲区 (fflush),将输出缓冲区的内容输出到屏幕上


(4)标准流

①stdin:标准输入
②stdout:标准输出
③stderr:标准错误

这三个标准流,不需要程序员手动声明、创建、关闭

在这里插入图片描述


(5)二进制文件 与 文本文件

1.区别
(1)二进制文件:byte。(二进制文件以字节为单位,人类不可读,但体积小)

(2)文本文件:字符 + 编码。(文本文件以字符为单位,一个字符占几个字节)

在这里插入图片描述


2.优缺点:
文本文件:人类可读,数据量大
二进制文件:人类不可读,数据量小

在这里插入图片描述


(6)文件流的接口(API)

1.打开文件流 :fopen

2.读/写文件:
统计、转换、加密解密

2.5 移动文件位置

3.关闭文件流:fclose



2.打开/关闭文件

(1)fopen

1.函数原型

FILE* fopen(const char* filename, const char* mode);

(1)参数
①filename是文件的路径
②mode是打开模式

(2)返回值:
①打开文件成功,返回文件指针
②打开文件失败,返回NULL

(3)举例:

FILE* file = fopen("example.txt", "r");  //以只读模式打开文件

在这里插入图片描述


2.文件路径
(1)绝对路径
从根目录 (或者盘符) 开始,一直到文件所在的位置,比如:“c:/project/test.dat”。
(2)相对路径
另一种是相对路径:从当前工作目录开始,一直到文件所在的位置,比如:“in.dat”。

相对路径用的多,因为一个app各文件的相对路径一般不变,但是绝对路径,当安装到不同电脑上时一般不同。


3.打开模式(mode):文件的类型、对文件的操作(r,w)
(1)以文本文件方式打开
①“r”,读(read):要求文件存在。若不存在则返回NULL
②“w”,写(write):若文件存在,清空文件内容;若文件不存在,创建文件。
③“a”,追加(append):若文件存在,不修改原内容,每次都在文件末尾追加写入;若文件不存在,创建文件。

权限文件存在文件不存在
r只读打开失败,返回NULL
w清空文件内容创建文件
a追加创建文件
r+读写打开失败,返回NULL
w+读写清空文件内容创建文件
a+追加创建文件

在这里插入图片描述

注意:如果以w+方式打开,写入后要读取写入的内容,需要先使用fseek将文件指针移动到要读取的位置。

在这里插入图片描述


(2)以二进制文件打开

在这里插入图片描述


(2)fclose

fclose 可以关闭程序不再使用的文件。

int fclose(FILE* stream);

如果成功关闭, fclose 返回零;否则返回 EOF


(3)示例代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	//1.打开文件
	FILE* stream = fopen("a.txt", "w");
	if (stream == NULL) {
		fprintf(stderr, "file open failed.\n");
		exit(1);
	}

	//2.读写文件 (统计,转换,加密,解密)


	//3.关闭文件
	fclose(stream);

	return 0;
}



3.读/写文件

在这里插入图片描述

(1)fgetc / fputc:读写文本文件,逐字符

fgetc 用于从文件中读取字符,返回读取的字符或 EOF。
fputc 用于向文件中写入字符,返回写入的字符或 EOF。

①fgetc

1.作用:fgetc 从指定的文件流中读取下一个字符

2.函数原型

#include <stdio.h>

int fgetc(FILE* stream);

(1)参数
stream 指向要从中读取字符的文件流指针

(2)返回值
①成功,返回读取的字符,转换为 unsigned char 后 再转换为 int,即对应的ASCII码值
②失败,返回 EOF

3.惯用法:一个字符一个字符地读取,直到文件末尾

FILE* src = fopen("src.txt", "r");
int c;
while((c = fgetc(src)) != EOF){
	//操作
}

②fputc

1.作用:fputc 向指定的文件流中写入一个字符。适用于处理字符级别的文件操作。

2.函数原型

#include <stdio.h>

int fputc(int c, FILE* stream);

(1)参数
①c是要写入的字符
②stream指向要写入字符的文件流的指针

(2)返回值
①成功:返回要写入字符,转换为 unsigned char 后 再转换为 int
②失败:返回EOF


3.示例代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>

int main(void) {
	FILE* src = fopen("src.txt", "r");
	if (src == NULL) {
		perror("fopen");
		return -1;
	}    

	FILE* dst = fopen("dst.txt", "w");
	if (dst == NULL) {
		perror("fopen");
		fclose(src);
		return -1;
	}    

	int c;
	while ((c = fgetc(src)) != EOF) {
		//fputc(c, dst);		   //实现文件复制,一个字符一个字符的复制
		//fputc(tolower(c), dst);  //全文替换为小写
		fputc(toupper(c), dst);	   //全文替换为大写
	}

	fclose(src);
	fclose(dst);

	return 0;
}

(2)fgets / fputs:读写文本文件,逐行

fgetc的c是character,fgets的s是string

①fgets

1.功能:
fgets 从指定的文件流中读取一行,并存储在字符串中。
读取会下列三种情况下停止:
①读取到换行符 \n
②读取到文件末尾EOF
③读取到指定的最大字符数 n-1
并且fgets会在字符串末尾自动添加一个空字符 \0

2.函数原型

#include <stdio.h>

char* fgets(char* str, int n, FILE* stream);

(1)参数:
①str:指向一个字符数组,用于存储读取的字符串
②n:要读取的最大字符数,包括空字符 \0 (通常是str指向字符数组的长度)
③stream:指向要从中读取数据的文件流的指针

(2)返回值:
①成功,返回指向存储字符串的指针 str
②失败或到达文件末尾时,返回 NULL


3.惯用法:

while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    printf("%s", buffer);
}
while(fgets(sendline, MAXLINE, stdin) != NULL){ //一行一行地读取输入

}

4.fgets的特点
①fgets会读\n
②gets(str) 等价于 fgets(str, ∞, stdin) ,即gets不会检查数组越界


②fputs

1.作用:
fputs 将一个字符串写入到指定的文件流中,不会在字符串末尾添加换行符。
适用于对整行数据进行处理。

2.函数原型

int fputs(const char* str, FILE* stream);

(1)参数:
①str:指向要写入的字符串 (以’\0’结尾的字符串)
②stream:指向要写入数据的文件流的指针

(2)返回值:
①成功,返回一个非负值
②失败,返回EOF,并设置errno


3.fputs的特点
①fputs原样输出字符串,puts在字符串后多输出一个换行符’\n’
②puts(str) 等价于 fputs(str, stdout)

在这里插入图片描述


4.示例代码
(1)例1:实现文件的复制
①核心代码:
fgets不会清空buffer,但fgets会在字符串末尾自动添加一个 \0。

char buffer[100];
while (fgets(buffer, sizeof(buffer), src) != NULL) {
	//printf("%s", buffer);	//输出文件的内容
	fputs(buffer, dst);		//逐行读取,实现文件的原样复制
}

②完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>

int main(void) {
	FILE* src = fopen("src.txt", "r");
	if (src == NULL) {
		perror("fopen");
		return -1;
	}

	FILE* dst = fopen("dst.txt", "w");
	if (dst == NULL) {
		perror("fopen");
		fclose(src);
		return -1;
	}

	char buffer[100];
	while (fgets(buffer, sizeof(buffer), src) != NULL) {
		//printf("%s", buffer);	//输出文件的内容
		fputs(buffer, dst);		//逐行读取,实现文件的原样复制
	}
	
	fclose(src);
	fclose(dst);

	return 0;
}

(2)示例2:实现文件的复制,并在每行的行首添加行号
①核心代码:

char buffer[100];
int line_num = 1;
char line[100];

while (fgets(buffer, sizeof(buffer), src) != NULL) {
	//printf("%s", buffer);	//输出文件的内容
	sprintf(line, "%d %s", line_num, buffer);
	fputs(line, dst);		
	line_num++;
}

②完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	FILE* src = fopen("src.txt", "r");
	if (src == NULL) {
		perror("fopen");
		return -1;
	}

	FILE* dst = fopen("dst.txt", "w");
	if (dst == NULL) {
		perror("fopen");
		fclose(src);
		return -1;
	}

	char buffer[100];
	int line_num = 1;
	char line[100];

	while (fgets(buffer, sizeof(buffer), src) != NULL) {
		//printf("%s", buffer);	//输出文件的内容
		sprintf(line, "%d %s", line_num, buffer);
		fputs(line, dst);		
		line_num++;
	}
	
	fclose(src);
	fclose(dst);

	return 0;
}

(3)fscanf / fprintf:格式化地读写
①fscanf

1.作用
fscanf函数用于从文件中读取格式化的数据。它的功能类似于scanf,但是输入源是文件而不是标准输入stdin(通常是键盘)

2.函数原型

int fscanf(FILE* stream, const char* format, ...);

(1)参数
①FILE *stream: 文件指针,指向要读取的文件。
②const char *format: 格式控制字符串,定义了如何解析文件中的数据。
③…: 可变参数列表,指向将存储读取数据的变量。

(2)返回值
①成功,读取并格式化的项数
②读取错误或达到文件末尾,返回EOF

在这里插入图片描述


②fprintf

1.作用
fprintf函数用于将格式化的数据写入文件。它的功能类似于printf,但是输出目标是文件而不是标准输出stdout(通常是屏幕)
适用于生成结构化的日志文件、配置文件、数据文件等。

2.函数原型

int fprintf(FILE* stream, const char* format, ...);

(1)参数
①FILE *stream:文件指针,指向要写入的文件。
②const char *format: 格式控制字符串,定义了如何格式化后续的参数。
③… :可变参数列表,要格式化并写入文件的数据。

(2)返回值
①成功时,返回写入文件的字符数
②失败时,返回负值

在这里插入图片描述


3.示例代码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#define MAXLINE 128

typedef struct Student {
	int id;
	char name[25];
	char gender;
	int chinese;
	int math;
	int english;
} Student;

int main(int argc, char* argv[]) {
	//xxx.exe src dst
	//0.参数校验
	if (argc != 3) {
		fprintf(stderr, "Usage: %s src dst\n", argv[0]);
		exit(1);
	}
	//1.打开文件
	FILE* src = fopen(argv[1], "r");
	if (!src) {
		fprintf(stderr, "Open %s failed.\n", argv[1]);
		exit(1);
	}

	FILE* dst = fopen(argv[2], "w");
	if (!dst) {
		fprintf(stderr, "Open %s failed.\n", argv[2]);
		fclose(src);
		exit(1);
	}

	//2.读写文件 (统计,转换,加密,解密)
	//(3)格式化地读写
	Student students[5];	//定义 学生结构体数组
	for (int i = 0; i < 5; i++) {
		fscanf(src, "%d%s %c%d%d%d",
			&students[i].id,
			students[i].name,
			&students[i].gender,
			&students[i].chinese,
			&students[i].math,
			&students[i].english);
	}

	//修改成绩
	for (int i = 0; i < 5; i++) {
		students[i].chinese *= 10;
		students[i].math *= 10;
		students[i].english *= 10;
	}

	for (int i = 0; i < 5; i++) {
		fprintf(dst, "%d %s %c %d %d %d\n",
			students[i].id,
			students[i].name,
			students[i].gender,
			students[i].chinese,
			students[i].math,
			students[i].english);
	}

	printf("成绩已修改。\n");

	//3.关闭文件
	fclose(src);
	fclose(dst);

	return 0;
}
//a.txt
1 Edward M 100 100 100
2 Amber  F  99  99  99
3 Sam    M  98  98  98 
4 Windy  F  97  97  97
5 Chole  F  96  96  96

(4)fread / fwrite:读写二进制文件,逐块
①fread

1.函数原型

#include <stdio.h>

size_t fread(void* buffer, size_t size, size_t count, FILE* stream);

(1)参数:
①buffer:指向存放数据的数组
②size:每个元素的大小,以字节为单位
③count:最多可以读取的元素个数
④stream:文件指针

(2)返回值:
①返回成功读取的元素数量
②若返回值小于count,则读到文件末尾或发送错误。 [可通过feof 和 ferror 函数判断是哪种情况]

(3)举例:

fread(png_file_buff, 1, filesize, png_file);

②fwrite

1.函数原型

#include <stdio.h>

size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);

(1)参数
①buffer:指向存放数据的数组
②size:每个元素的大小,以字节为单位
③count:要写入元素的个数
④stream:文件指针

(2)返回值
返回成功写入的元素的个数。若返回值<count,则可能发生了写入错误。

(3)举例

fwrite(buffer, 1, BUFSIZE, dst);

③代码

1.核心代码:

//读写二进制文件 (复制)
char buffer[BUFSIZE];
int bytes;
while ((bytes = fread(buffer, 1, BUFSIZE, src)) > 0) {
	fwrite(buffer, 1, BUFSIZE, dst);
}

2.完整代码:复制二进制文件

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

#define BUFSIZE 4096

int main(int argc, char* argv[]) {
	//xxx.exe src
	//0.参数校验
	if (argc != 3) {
		fprintf(stderr, "Usage: %s src dst\n", argv[0]);
		exit(1);
	}
	//1.打开文件
	FILE* src = fopen(argv[1], "rb");
	if (!src) {
		fprintf(stderr, "Open %s failed.\n", argv[1]);
		exit(1);
	}

	FILE* dst = fopen(argv[2], "wb");
	if (!dst) {
		fprintf(stderr, "Open %s failed.\n", argv[2]);
		fclose(src);
		exit(1);
	}

	//2.读写文件 
	//(4)读写二进制文件 (复制)
	char buffer[BUFSIZE];
	int bytes;
	while ((bytes = fread(buffer, 1, BUFSIZE, src)) > 0) {
		fwrite(buffer, 1, BUFSIZE, dst);
	}

	//3.关闭文件
	fclose(src);
	fclose(dst);

	return 0;
}



4.文件定位、移动文件位置

(1)fseek:移动文件位置
int fseek(FILE* stream, long int offset, int whence);

whence的三个宏:
SEEK_SET:文件的起始位置,0
SEEK_CUR:文件的当前位置,pos
SEEK_END:文件的末尾位置,EOF

在这里插入图片描述

在这里插入图片描述


(2)ftell:返回当前文件位置
long int ftell(FILE* stream);

在这里插入图片描述


(3)rewind:将文件位置移回开头
void rewind(FILE* fp);

rewind会将文件位置设置为起始位置,等价于调用:
举例:

//获取文件大小:获取图片长度
fseek(png_file, 0, SEEK_END);
long filesize = ftell(png_file);
rewind(png_file);
fseek(fp, 0, SEEK_SET);

(4)代码实战

在这里插入图片描述

难点:
①如何确定文件的大小

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

char* readFile(const char* path) {
	//1.打开文件
	FILE* stream = fopen(path, "rb");
	if (stream == NULL) {  //!stream
		fprintf(stderr, "Open %s failed\n", path);
		exit(1);
	}

	//2.确定文件大小
	fseek(stream, 0, SEEK_END);
	long n = ftell(stream);
	char* content = malloc((n + 1) * sizeof(char));  // 1 for '\0'

	//3.读取文件
	rewind(stream);  //回到文件开头
	int bytes = fread(content, 1, n, stream);
	content[bytes] = '\0';

	//4.关闭文件
	fclose(stream);

	return content;
}

int main(int argc, char* argv[]) {
	//0.参数校验
	if (argc != 2) {
		fprintf(stderr, "Usage: %s filename\n", argv[0]);
		exit(1);
	}

	char* content = readFile(argv[1]);

	//操作
	printf("%s\n", content);

	free(content);

	return 0;
}



5.两种文件指针:FILE* 与 fd 的区别

1.FILE* 文件流指针
FILE* 是一个文件流指针,定义在标准 I/O 库 <stdio.h> 中。它提供了高级别的文件操作函数,如 fopen、fclose、fread、fwrite、fprintf、fscanf 等。这些函数通常更易于使用,并且提供了缓冲机制,多次调用直至缓冲区满了才进行一次系统调用,提高了 I/O 操作的效率。


2.fd 文件描述符
fd 是文件描述符(file descriptor),通常是一个整数,定义在 POSIX 标准中。文件描述符是一个低级别的文件指针,定义在 <unistd.h> 中。文件描述符的操作函数包括 open、close、read、write、lseek 等。这些函数更接近系统调用,提供了更细粒度的控制,但没有缓冲机制,每次调用会直接进行系统调用


3.区别
(1)缓冲机制:
FILE* 默认是全缓冲,系统自动管理缓冲区,多次调用直至缓冲区满才进行一次系统调用,减少了系统调用的次数,提高了I/O效率。[使用fopen打开文件时,标准库会自动为文件流动态分配一个缓冲区,称为 FILE *的自动缓冲]
fd无缓冲,会直接进行系统调用,需要用户手动管理缓冲区。fd频繁的系统调用,导致效率较低。

(2)级别不同:
①FILE* 是高级文件指针,带有缓冲机制,适用于一般文件读写操作;
②fd 是低级文件描述符,适用于需要精细控制的场景,例如设备文件、管道、套接字等。

(3)跨平台性:
①FILE* 更加跨平台,其内部实现适配各种操作系统
②fd 是 POSIX 标准,主要用于 UNIX 和类 UNIX 系统。

(4)灵活性/复杂性:
①FILE* 封装了复杂的缓冲操作,使得编程更加简洁。
②fd 提供了更灵活的文件操作,但代码相对复杂,需要手动管理缓冲区;


4.使用场景
①FILE*:适用于大多数文件的读写操作,如文本文件读写、日志记录等。提供更高的抽象层次和缓冲机制,简化编程并提高效率。
②fd:适用于需要精细控制的场景,如多线程环境下的文件操作、系统编程、网络编程、设备驱动开发等。



6.错误处理

1.如何检测错误:①②
2.如何打印错误信息:③④⑤

(1)返回值

用int ret 接收函数的返回值,判断是否执行成功


(2)errno

全局变量errno,定义在 <errno.h> 头文件中 (每个线程都有自己的errno,线程特定变量)
errno为0表示没有错误,非0表示有错误

#include <errno.h>

FILE* fp = fopen("not_exist.txt", "r");
printf("%d\n", errno); 

(3)strerror (errno)

strerror将errno从数字转化为对应的字符串错误,定义在 <string.h> 头文件中

#include <error.h>
#include <string.h>

FILE* fp = fopen("not_exist.txt", "r");
printf("%s\n", strerror(errno));  //将errno对应的错误信息,转化为人类可读的字符串
#define ARGS_CHECK(argc, num){           \                                                                                                                               
    if(argc != num){                     \
        fprintf(stderr, "ARGS ERROR!\n");\
        return -1;                       \
    }                                    \
}                                        \

(4)perror (“前缀信息”)

1.函数原型

#include <stdio.h>

void perror(const char *s);

2.功能
打印: s、冒号、空格、换行符。

perror("前缀信息"),错误的前缀信息,后面自动添加了 : \n

相当于调用了

fprintf(stderr, "%s: %s\n", "prefix", strerror(errno));

3.举例

#include <error.h>
#include <string.h>

FILE* fp = fopen("not_exist.txt", "r");
fprintf(stderr, "%s: %s\n", "prefix", strerror(errno));
perror("prefix");  //添加错误的前缀信息
exit(1);

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(void) {
	//errno==0 代表 没有错误
	printf("errno = %d\n", errno);

	FILE* fp = fopen("a.txt","r+"); //实际a.txt不存在
	//errno
	printf("errno = %d\n", errno);
	
	//strerror(errno)
	printf("strerror(errno) = %s\n",strerror(errno));
	
	//perror
	fprintf(stderr, "%s: %s\n", "prefix", strerror(errno));
	perror("prefix");

	return 0;
}

在这里插入图片描述


(5)error()

仅linux可用,自动添加‘\n’

#include <error.h>
#include <errno.h>

error(0, error, "prefix");   //不退出程序,设置errno
error(1, 0, "prefix");      //相当于exit(1),不设置errno
error(1, errno, "prefix");  //相当于exit(1),设置errno

输出:

./可执行程序名: prefix   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值