文章目录
- (十二) 文件
- 1.流
- (1)流模型
- (2)程序员视角的文件
- (3)缓冲区类型
- (4)标准流
- (5)二进制文件 与 文本文件
- (6)文件流的接口(API)
- 2.打开/关闭文件
- (1)fopen
- (2)fclose
- (3)示例代码
- 3.读/写文件
- (1)fgetc / fputc:一个字符一个字符地读写
- (2)fgets / fputs:一行一行地读写
- ①fgets
- ②fputs
- ③代码
- (3)fscanf / fprintf:格式化地读写
- ①fscanf
- ②fprintf
- ③代码
- (4)fread / fwrite:读写二进制文件
- ①fread
- ②fwrite
- ③代码
- 4.文件定位、移动文件位置
- (1)fseek
- (2)ftell
- (3)rewind
- (4)课堂小练习
- 5.错误处理:perror
(十二) 文件
1.流
(1)流模型
data sink:数据接收端、数据汇
优点:
①程序员读写文件时,不需要关心文件的位置
②数据源(data source) 和 数据汇(data sink) 是解耦的
(2)程序员视角的文件
存放的是一个一个字节。
EOF(end of file)指向文件末尾后一个位置,EOF是一个宏,值为-1
(3)缓冲区类型
①满缓冲:缓冲区空才从输入流读数据;缓冲区满向输出流中写入数据。
②行缓冲:以行为单位进行读和写
③无缓冲:没有缓冲区,立即输入输出,例如:标准错误流 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.fopen的参数
①filename是文件的路径
②mode是打开模式
FILE* fopen(const char* filename, const char* mode);
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 | 追加 | 创建文件 |
(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:一个字符一个字符地读写
1.函数参数
int fgetc(FILE* stream);
int fputc(int c, FILE* stream);
2.惯用法:一个字符一个字符地读取,直到文件末尾
int c;
while((c = fgetc(src)) != EOF){
//操作
}
3.完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
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.读写文件 (统计,转换,加密,解密)
//(1)一个字符一个字符地读写:fgetc, fputc
//把大写字母转换为小写字母
int c;
while ((c = fgetc(src)) != EOF) {
fputc(tolower(c), dst);
}
printf("大写字母已全部转换为小写字母\n");
//3.关闭文件
fclose(src);
fclose(dst);
return 0;
}
(2)fgets / fputs:一行一行地读写
①fgets
1.函数参数
char* fgets(char* str, int count, FILE* stream);
参数:
①str:指向第一个字符数组
②count:能够写入的最大字符数量 (通常是str指向字符数组的长度)
③stream:输入流
④返回值:成功返回str,失败返回NULL
2.fgets的特点
①fgets会读\n
②gets(str) 等价于 fgets(str, ∞, stdin) ,即gets不会检查数组越界
②fputs
1.函数参数
int fputs(const char* str, FILE* stream);
参数:
①str:要写的字符串(以’\0’结尾的字符串)
②stream:输出流
③返回值:成功返回一个非负值;失败返回EOF,并设置errno
2.fputs的特点
①fputs原样输出字符串,puts在字符串后多输出一个换行符’\n’
②puts(str) 等价于 fputs(str, stdout)
③代码
1.核心代码:
#define MAXLINE 128
//2.读写文件:
//(2)一行一行地读写
//每行加序号
char line[MAXLINE];
char buffer[MAXLINE];
//fgets(line, MAXLINE, src);
//fgets(line, MAXLINE, src);
int line_num = 1;
while ((fgets(buffer, MAXLINE, src)) != NULL) {
sprintf(line, "%d.%s", line_num, buffer);
fputs(line, dst);
line_num++;
}
2.完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#define MAXLINE 128
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.读写文件:
//(2)一行一行地读写
//每行加序号
char line[MAXLINE];
char buffer[MAXLINE];
//fgets(line, MAXLINE, src);
//fgets(line, MAXLINE, src);
int line_num = 1;
while ((fgets(buffer, MAXLINE, src)) != NULL) {
sprintf(line, "%d.%s", line_num, buffer);
fputs(line, dst);
line_num++;
}
printf("每行内容前面已加上序号。\n");
//3.关闭文件
fclose(src);
fclose(dst);
return 0;
}
(3)fscanf / fprintf:格式化地读写
①fscanf
int fscanf(FILE* stream, const char* format, ...);
②fprintf
int fprintf(FILE* stream, const char* format, ...);
③代码
#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
②fwrite
③代码
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* stream);
(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.错误处理:perror
1.如何检测错误
2.如何打印错误信息
①返回值
②errno
,定义在 <errno.h>
头文件中
(每个线程都有自己的errno,线程特定变量)
③strerror(errno)
,定义在 <string.h>
头文件中
④perror
,错误的前缀信息,后面自带了
相当于调用了fprintf(stderr,"%s: ",%s)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
printf("%d\n", errno); // 0:没有错误
FILE* fp = fopen("not_exist.txt", "r");
printf("%d\n", errno);
printf("%s\n", strerror(errno)); //将errno对应的错误信息,转化为人类可读的字符串
perror("prefix"); //添加错误的前缀信息
return 0;
}