概念
文件
- 通常就是磁盘上一段命名的存储区
- 我们更关心的是如何在 C 程序中处理文件
读写文件和 printf、scanf 关联
- scanf
- 标准输入 (键盘)
- printf
- 标准输出 (屏幕)
- perror
- 标准错误 (屏幕)
系统文件
应用程序启动时,自动被打开;程序执行结束时,自动被关闭 (隐式回收)。
- 标准输入
- stdin -> 0
- 标准输出
- stdout -> 1
- 标准错误
- stderr -> 2
文件分类
- 设备文件
- 屏幕、键盘、磁盘、网卡、声卡、扬声器 …
- 磁盘文件
- 文本文件
- ASCII 组成
- 二进制文件
- 二进制编码组成
- 文本文件
流 (stream)
流是一个动态的概念,I/O 操作可以简单的看作是从程序移进或移出字节,这种搬运的过程便称为流。
- 文本流
- 以文本模式读取文件
- 换行符
- Windows:\r\n
- Linux:\n
- 二进制流
- 适用于非文本数据
- 和文本文件的打开方式的区别,主要是换行符的处理
- 文本模式打开,需要进行 \r\n 和 \n 的转换
- 二进制模式打开,不需要做转换
文件指针
操作系统将我们要打开的文件信息保存起来,并返回一个 FILE 类型指针指向文件的信息。通过文件指针可以对文件进行各种操作。
- 借助文件操作函数改变 fp 为空、野指针的状况
FILE *fp = fopen(); //相当于 fp = malloc();
- 操作文件,使用文件读写函数完成
- fputc、fgetc、fputs、fgets、fread、fwrite
- 结构体 iobuf
struct _iobuf { char* _ptr; // 文件输入的下一个位置 int _cnt; // 剩余多少字符未被读取 char* _base; // 基础位置 (文件的起始位置) int _flag; // 文件标志,判断是否读到了文件尾 int _file; // 文件的有效性验证 int _charbuf; // 检查缓冲区状况,如果无缓冲区则不读取 int _bufsiz; // 文件的大小 int* _tmpfname; // 临时文件名 };
文件缓冲区
- 分为
- 输入缓冲区
- 输出缓冲区
- 优势
- 提高硬件寿命和读写效率
文件路径
- 绝对路径
- 从系统磁盘的根目录开始,找到待访问的文件路径
-
Windows 书写方法
H:\\学习文档\\C++\\项目\\day10\\1.txt H:/学习文档/C++/项目/day10/1.txt # 也适用于 Linux
- 相对路径
- 如果在 VS 环境下编译执行,相对路径是相对于 .vcxproj 文件的位置
- 如果双击 .exe 文件执行,文件的相对路径是相对于 .exe 文件
文件操作步骤
- 打开文件 -> 读写文件 -> 关闭文件
// 1.打开文件 FILE *fp = fopen(); // 2.读写文件 fputc、fgetc、fputs、fgets、fread、fwrite // 3.关闭文件 fclose()、close()、pclose()
- 注意事项
- 对打开的文件进行操作时,若文件缓冲区的空间未被写入的内容填满,这些内容不会写入到打开的文件中;只有对打开的文件进行关闭时,停留在文件缓冲区的内容才能写入到该文件中
- 文件关闭时,也意味着释放了该文件的缓冲区
文件读写
fopen 函数
- 函数描述
- 打开文件
- 函数原型
FILE *fopen(const char *filename, const char * mode);
- 参数
- filename
- 待打开文件的文件名 (访问路径)
- mode
- 文件打开权限
- mode 的选项
- filename
- 返回值
- 成功,返回打开文件的指针;失败,返回 NULL
- 参数
fclose 函数
- 函数描述
- 关闭文件
- 函数原型
int fclose(FILE * stream);
- 参数
- stream
- 打开文件的 fp (fopen 的返回值)
- stream
- 返回值
- 成功,返回 0;失败,返回 -1
- 参数
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include int main(void) { FILE* fp = NULL; fp = fopen("H:\\学习文档\\C++\\项目\\day10\\1.txt", "r"); if (fp == NULL) { printf("fopen error"); return -1; } fclose(fp); system("pause"); return 0; }
按字符读写
- fputc 函数
- 函数描述
- 按字符写
- 函数原型
int fputc(int ch, FILE * stream);
- 参数
- ch
- 待写入的字符
- stream
- 打开文件的 fp (fopen 返回值)
- ch
- 返回值
- 成功,则写入文件中字符对应的 ASCII 码;失败,返回 -1
- 参数
- 函数描述
- fgetc 函数
- 函数描述
- 按字符读
- 函数原型
int fgetc(FILE * stream);
- 参数
- stream
- 待读取的文件 fp (fopen 的返回值)
- stream
- 返回值
- 成功,则读到文件中字符对应的 ASCII 码;失败,返回 -1
- 参数
- 函数描述
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include void write_file(char *filename) { FILE* fp = NULL; fp = fopen(filename, "w"); if (fp == NULL) { printf("fopen error"); return -1; } fputc('a', fp); fputc('b', fp); fputc('c', fp); fputc('d', fp); fputc('e', fp); fclose(fp); } void read_file(char* filename) { FILE* fp = NULL; fp = fopen(filename, "r"); if (fp == NULL) { printf("fopen error"); return -1; } while (1) { char ch = fgetc(fp); if (ch == -1) { break; } printf("%c ", ch); } fclose(fp); } int main(void) { write_file("3.txt"); read_file("3.txt"); system("pause"); return 0; }
- 不需要挪动光标
feof 函数
- 函数描述
- 判断是否到达文件结尾 (可以判断文本文件,或二进制文件)
-
文本文件的结束标记 (EOF -> -1)
#define EOF (-1)
- 函数原型
int feof(FILE * stream);
- 参数
- stream
- fopen 的返回值
- stream
- 返回值
- 到达文件结尾,返回非 0;否则,返回 0
- 参数
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include void write_file(char *filename) { FILE* fp = NULL; fp = fopen(filename, "w"); if (fp == NULL) { printf("fopen error"); return -1; } fputc('a', fp); fputc('b', fp); fputc('c', fp); fputc('d', fp); fputc('e', fp); fclose(fp); } void read_file(char* filename) { FILE* fp = NULL; fp = fopen(filename, "r"); if (fp == NULL) { printf("fopen error"); return -1; } while (1) { char ch = fgetc(fp); //if (ch == -1) if(feof(fp)) // 判断文件的结束标记 { break; } printf("%c ", ch); } fclose(fp); } int main(void) { write_file("3.txt"); read_file("3.txt"); system("pause"); return 0; }
按行读写
- fgets 函数
- 函数描述
- 获取一个字符串 (按行读),以 ‘\n’ 作为结束标记
- 自动添加 ‘\0’;如果空间足够大,会读 ‘\n’;空间不足,会舍弃 ‘\n’
- 函数原型
char * fgets(char * str, int size, FILE * stream);
- 函数描述
- fputs 函数
- 函数描述
- 按行写
- 写一个字符串,如果字符串中没有 ‘\n’,不会写 ‘\n’
- 函数原型
int fputs(const char * str, FILE * stream);
- 返回值
- 成功,返回 0;失败,返回 -1
- 返回值
- 函数描述
二进制文件的读取和写入 (按块读写)
- fwrite 函数
既可以处理文本文件,也可以处理二进制文件。
- 函数描述
- 按块写 (二进制文件)
- 函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- 参数
- ptr
- 待写出的数据的地址
- size
- 待写出的数据大小
- nmemb
- 每次写出的个数 (size * nmemb = 写出数据的总大小)
- 通常将 size 传 1,nmemb 传实际写出的字节数
- stream
- 文件
- ptr
- 返回值
- 成功,返回 nmemb 的值;失败,返回 0
- 参数
- 函数描述
- fread 函数
- 函数描述
- 按块读 (二进制文件)
- 函数原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 参数
- 返回值
- 成功,返回 nmemb 的值;失败,返回 0
- 和 feof(fp),都可以用来判断是否到达文件结尾
- 函数描述
- 示例
- fwrite 和 fread
#define _CRT_SECURE_NO_WARNINGS #include typedef struct Person { char name[12]; int age; }person; int fwrite_struct(const char * filename) { FILE* fp = fopen(filename, "w+"); if (fp == NULL) { perror("fopen error"); return -1; } person p = { "aaa", 10 }; int ret = fwrite(&p, 1, 1 * sizeof(person), fp); if (ret == 0) { perror("fwite error"); return -1; } fclose(fp); return 0; } int fread_struct(const char* filename) { FILE* fp = fopen(filename, "r"); if (fp == NULL) { perror("fopen error"); return -1; } person p; int ret = fread(&p, 1, 1 * sizeof(person), fp); if (ret == 0) { perror("fread error"); return -1; } printf("name = %s, age = %d\n", p.name, p.age); } int main() { fwrite_struct("./fwrite.txt"); fread_struct("./fwrite.txt"); return 0; }
- 大文件拷贝 (需要按块读写)
#define _CRT_SECURE_NO_WARNINGS #include int file_cpy(const char *src_file, char* dest_file) { FILE* rfp = fopen(src_file, "rb"); if (!rfp) { perror("src_file fopen error"); return -1; } FILE* wfp = fopen(dest_file, "wb"); if (!wfp) { perror("dest_file fopen error"); return -1; } char buf[4096] = {0}; // 这里定义成 char *buf = NULL 会报错,数组是变量,指针是常量 int ret = 0; while (1) { memset(buf, 0x00, sizeof(buf)); // buf 使用前缓冲区清零 ret = fread(buf, 1, 1 * sizeof(buf), rfp); if (ret == 0) { perror("fread error or file read over"); break; } fwrite(buf, 1, ret, wfp); } fclose(rfp); fclose(wfp); return 0; } int main() { file_cpy("C:\\Users\\WangHaiying_GamePC\\Desktop\\webserver_epoll.jpg", "C:\\Users\\WangHaiying_GamePC\\Desktop\\webserver_epoll_2.jpg"); return 0; }
- 已知一个任意类型的文件,对该文件复制,产生一个相同的新文件
- 在 Windows 下,打开二进制文件 (mp3、mp4、avi、jpg …) 时,需要使用 “b”,例如:rb、wb
- fwrite 和 fread
按格式化方式读写
- fprintf 函数
- 函数描述
- 格式化写
- 函数原型
int fprintf(FILE * stream, const char * format, ...);
- 函数描述
- fscanf 函数
- 函数描述
- 格式化读
- 函数原型
int fscanf(FILE * stream, const char * format, ...);
- 注意事项
- 边界溢出
- 场景复现
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 输出到文件 void file_write(char *filename) { FILE* fp = NULL; fp = fopen(filename, "w"); if (!fp) { perror("fopen error"); return -1; } fprintf(fp, "%d + %d = %d\n", 10, 20, 10 + 20); fclose(fp); } // 从文件输入 void file_read(char* filename) { int a, b, c; FILE* fp = NULL; fp = fopen(filename, "r"); if (!fp) { perror("fopen error"); return -1; } //fscanf(fp, "%d + %d = %d\n", &a, &b, &c); //printf("%d + %d = %d\n", a, b, c); fscanf(fp, "%d\n", &a); printf("%d\n", a); fscanf(fp, "%d\n", &a); printf("%d\n", a); fscanf(fp, "%d\n", &a); printf("%d\n", a); fscanf(fp, "%d\n", &a); printf("%d\n", a); // 最终输出 1 2 3 3 fclose(fp); } int main(void) { file_write("06.txt"); file_read("066.txt"); system("pause"); return 0; }
- getchar() 可以暂停一下,用于观察和调试
- 故意输出 4 次,最后一次输出与上次相同
- 存储读取的数据空间应该在使用前清空 (memset )
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 输出到文件 void file_write(char *filename) { FILE* fp = NULL; fp = fopen(filename, "w"); if (!fp) { perror("fopen error"); return -1; } fprintf(fp, "%d + %d = %d\n", 10, 20, 10 + 20); fclose(fp); } // 从文件输入 void file_read(char* filename) { int a, b, c; FILE* fp = NULL; fp = fopen(filename, "r"); if (!fp) { perror("fopen error"); return -1; } //fscanf(fp, "%d + %d = %d\n", &a, &b, &c); //printf("%d + %d = %d\n", a, b, c); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); // 最终输出 1 2 3 3 //while (1) // 上面的输入使用循环方式书写 //{ // fscanf(fp, "%d\n", &a); // if (feof(fp)) // { // break; // } // printf("%d\n", a); // 输出 1 2,当 a = 3 时,会将 fp 置为 0 //} //while (1) // 上面的输入使用循环方式书写 //{ // fscanf(fp, "%d\n", &a); // printf("%d\n", a); // 输出 1 2,当 a = 3 时,会将 fp 置为 0 // // if (feof(fp)) // { // break; // } //} char buf[1024]; while (1) { memset(buf, 0, 1024); // 清空缓冲区 fgets(buf, 1024, fp); printf("%s", buf); // 输出 1 2 3 if (feof(fp)) { break; } } fclose(fp); } int main(void) { file_write("06.txt"); file_read("066.txt"); system("pause"); return 0; }
- 场景复现
- fscanf() 每次调用时都会判断下一次调用是否匹配参数 2,如果不匹配,提前结束文件读操作
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 输出到文件 void file_write(char *filename) { FILE* fp = NULL; fp = fopen(filename, "w"); if (!fp) { perror("fopen error"); return -1; } fprintf(fp, "%d + %d = %d\n", 10, 20, 10 + 20); fclose(fp); } // 从文件输入 void file_read(char* filename) { int a, b, c; FILE* fp = NULL; fp = fopen(filename, "r"); if (!fp) { perror("fopen error"); return -1; } //fscanf(fp, "%d + %d = %d\n", &a, &b, &c); //printf("%d + %d = %d\n", a, b, c); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); //fscanf(fp, "%d\n", &a); //printf("%d\n", a); // 最终输出 1 2 3 3 //while (1) // 上面的输入使用循环方式书写 //{ // fscanf(fp, "%d\n", &a); // if (feof(fp)) // { // break; // } // printf("%d\n", a); // 输出 1 2,当 a = 3 时,会将 fp 置为 0 //} while (1) // 上面的输入使用循环方式书写 { fscanf(fp, "%d\n", &a); printf("%d\n", a); // 输出 1 2,当 a = 3 时,会将 fp 置为 0 if (feof(fp)) { break; } } fclose(fp); } int main(void) { file_write("06.txt"); file_read("066.txt"); system("pause"); return 0; }
- 边界溢出
- 函数描述
- 对比
- printf -> sprintf -> fprintf
- 示例
// printf printf("hello"); printf("%s", "hello"); printf("%d + %d = %d", a, b, a+b); // sprintf char buf[1024]; sprintf(buf, "%d + %d = %d", a, b, a+b); // fprintf FILE * fp = fopen(); fprintf(fp, "%d + %d = %d", a, b, a+b);
- 示例
- scanf -> sscanf -> fscanf
- 举例
// scanf scanf("%d", &m); // 键盘 -> m // sscanf char str[] = "98"; sscanf(str, "%d", &m); // str -> m // fscanf FILE * fp = fopen(); fscanf(fp,"%d", &m); // fp 指向的文件中 -> m
- 举例
- printf -> sprintf -> fprintf
- 变参函数
- 参数形参中,有 “…”,最后一个固参通常是格式描述串 (包含格式匹配符),函数的参数个数、类型、顺序,由这个固定参数决定
- 示例
- 文件版排序:生成随机数,写入文件,排序后再重新写入
“`c
<h1>define _CRT_SECURE_NO_WARNINGS</h1>
<h1>include</h1>
<h1>include</h1>
<h1>include</h1>
// 冒泡排序
void bubble_sort(int* str, int n)
{
int temp;<pre><code>for (int i = 0; i < n; i++)
{
for (int j = 0; j str[j + 1])
{
temp = str[j];
str[j] = str[j + 1];
str[j + 1] = temp;
}
}
}
</code></pre>}
// 生成随机数并写入文件
int f_rand(char* filename)
{
int a;
srand(time(NULL)); // 生成随机数种子<pre><code>FILE* fp = NULL;
fp = fopen(filename, "w"); // 创建空文件
if (!fp)
{
perror("fopen error"); // 判断是否创建文件成功
return -1;
}for(size_t i = 0; i< 10; i++) // 写入 10 次
{
a = rand() % 100; // 随机数范围是 0-99
fprintf(fp, "%d\n", a);
}fclose(fp);
return 0;
</code></pre>}
// 读取文件中的随机数
int f_read(char *filename)
{
int i = 0;
int buf[10];<pre><code>FILE* fp = NULL;
fp = fopen(filename, "r");
if (!fp)
{
perror("fopen error");
return -1;
}while (1)
{
fscanf(fp, "%d\n", &buf[i]);
printf("%d\n", buf[i]); // 验证i++;
if (feof(fp))
{
break;
}
}fclose(fp);
// 排序
bubble_sort(buf, i);// 将排序后的数字重新写入文件
fp = fopen(filename, "w"); // 不需要重复定义 fpif (!fp)
{
perror("fopen error");
return -1;
}for (size_t k = 0; k < 10; k++) // 验证
{
fprintf(fp, "%d\n", buf[k]); // 写入文件
}fclose(fp);
return 0;
</code></pre>}
int main(void)
{
f_rand("07.txt");
f_read("07.txt");<pre><code>// 验证冒泡排序
/*
int str[5] = { 1, 2, 5, 4, 6 };bubble_sort(str, 5);
for (size_t i = 0; i 向后,- -> 向前
– whence
– 指针位置
– 选项– SEEK_SET
– 自定义文件位置
– SEEK_CUR
– 当前位置
– SEEK_END
– 文件结尾位置
</code></pre><ul>
<li>返回值<ul>
<li>成功,返回 0;失败,返回 -1</li>
</ul></li>
</ul></li>
</ul></li>
<li>ftell 函数<ul>
<li>函数描述<ul>
<li>获取文件读写指针位置</li>
</ul></li>
<li>函数原型<pre><code class="language-c line-numbers">long ftell(FILE *stream);
</code></pre><ul>
<li>返回值<ul>
<li>从文件当前读写位置到起始位置的偏移量 (字节数)</li>
</ul></li>
</ul></li>
</ul></li>
<li>rewind 函数将读写指针移动到起始位置。
<ul>
<li>函数描述<ul>
<li>回卷文件读写指针</li>
</ul></li>
<li>函数原型<pre><code class="language-c line-numbers">void rewind(FILE * stream);
</code></pre></li>
</ul></li>
<li>获取文件大小<pre><code class="language-c line-numbers">fseek(SEEK_END) + ftell()
</code></pre></li>
<li>示例<pre><code class="language-c line-numbers">#define _CRT_SECURE_NO_WARNINGS
#includetypedef struct Person
{
char name[16];
int age;
}person;int read_random(const char* filename)
{
person p[3] = { "aa", 10, "bb", 20, "cc", 30 };
int ret = 0;
person newp;FILE* fp = fopen(filename, "w+");
if (!fp)
{
perror("fopen error");
return -1;
}// 结构体写入文件
ret = fwrite(&p, 1, 1 * sizeof(person), fp);
if (ret == 0)
{
perror("fwrite error");
return -1;
}// 偏移文件指针
fseek(fp, sizeof(person) * 2, SEEK_SET);// 读取文件
ret = fread(&newp, 1, sizeof(person), fp);
printf("name: %s, age: %d\n", newp.name, newp.age); // 乱码,因为上面只写入了 p[0],而想要读取 p[2]// 获取当前文件指针位置
int len = ftell(fp);
printf("offset = %d\n", len); // offset = 40// 回卷指针
rewind(fp);// 读取文件
ret = fread(&newp, 1, sizeof(person), fp);
printf("name: %s, age: %d\n", newp.name, newp.age); // name: aa, age: 10return 0;
}int main()
{
read_random("./read_random.txt");return 0;
}
</code></pre></li>
</ul><h3>error 宏</h3>
“`c++
FILE* f_write = fopen(“./02.txt”, “w”);
if (f_write == NULL)
{
perror(“fopen error”); // error 宏
return;
}
“`- 会额外打印 error 宏的错误信息
示例
- 获取用户键盘输入,写入文件
#define _CRT_SECURE_NO_WARNINGS #include #include #include int main(void) { FILE* fp = NULL; fp = fopen("04.txt", "w"); if (fp == NULL) { printf("fopen error"); return -1; } char buf[4096] = {0}; while (1) { fgets(buf, 4096, stdin); if (strcmp(buf, ":wq\n") == 0) { break; } fputs(buf, fp); } fclose(fp); system("pause"); return 0; }
- 当用户输入 “:wp” 时,中止用户输入,并将之前的数据保存为文件
- 实际的终止符是 “:wq\n”
- 文件版四则运算
#define _CRT_SECURE_NO_WARNINGS #include #include > #include // 创建文件,内容由键盘输入 void writefile(char* filename) { FILE* fp = NULL; fp = fopen(filename, "w"); if (fp == NULL) { printf("fopen error"); return -1; } char buf[4096] = { 0 }; while (1) { fgets(buf, 4096, stdin); if (strcmp(buf, ":wq\n") == 0) { break; } fputs(buf, fp); } fclose(fp); } // 读取文件 void readfile(char* filename1, char *filename2) // filename1: 原文件; filename2: 最终写入的文件 { FILE* fp = NULL; fp = fopen(filename1, "r"); if (fp == NULL) { printf("fopen error"); return -1; } char buf1[4096] = { 0 }; int n = sizeof(buf1); int a, b, c=0; char ch; char result[1024] = { 0 }; char buf2[4096] = { 0 }; while (1) { fgets(buf1, n, fp); if (feof(fp)) { break; } sscanf(buf1, "%d%c%d=\n", &a, &ch, &b); switch (ch) { case '+': c = a + b; break; case '-': c = a - b; break; case '*': c = a * b; break; case '/': c = a / b; break; default: printf("ch is %c, error!", ch); break; } //printf("%d%c%d=%d\n", a, ch, b, c); // 用于验证 // 字符串拼接 sprintf(result, "%d%c%d=%d\n", a, ch, b, c); strcat(buf2, result); } fclose(fp); printf("%s\n", buf2); // 重新打开文件 (清空文件,重新写入) FILE* fp2 = NULL; fp2 = fopen(filename2, "w"); fputs(buf2, fp2); fclose(fp2); } int main(void) { //writefile("05.txt"); readfile("05.txt", "055.txt"); system("pause"); return 0; }
文件读写的注意事项
- 不要用 feof() 按照字符的方式读取文件
- 滞后性,多读出一个 EOF 字符
- 如果属性开辟到了堆区,不要存指针到文件中,要将指针指向的内容存放到文件
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 注意事项1:不要用 feof() 按照字符的方式读取文件 void test01() { FILE* fp = fopen("./03.txt", "w"); if (fp == NULL) { perror("fopen error"); return; } fprintf(fp, "%s\n%s", "第一行数据", "第二行数据"); fclose(fp); fp = fopen("./03.txt", "r"); char ch; #if 0 while (!feof(fp)) { ch = fgetc(fp); printf("%c", ch); // aaaa||||| 后面多读了一个 EOF 字符 } printf("###"); /* 第一行数据 第二行数据### */ #else while ((ch = fgetc(fp)) != EOF) { printf("%c", ch); } printf("###"); /* 第一行数据 第二行数据### */ #endif // 自定义条件编译,0 表示不跑这段代码 fclose(fp); } // 注意事项2 struct Hero { char* name; //如果属性开辟到堆区,不要存指针到文件中,要将指针指向的内容存放到文件中 int age; }; int main(void) { test01(); system("pause"); return 0; }
其它文件操作
Linux 和 Windows 文件的区别
- 对于二进制文件操作
- Windows 下
- 使用 “b”
- Linux 下
- 二进制和文本没区别
- Windows 下
- 回车和换行
- Windows 下
- 回车 ‘\r’,换行 ‘\n’,所以每行结尾是 “\r\n”
- Linux 下
- 回车、换行都是 ‘\n’
- Windows 下
- 示例
#define _CRT_SECURE_NO_WARNINGS #include typedef struct Person { char name[16]; int age; }person; int read_random(const char* filename) { person p[3] = { "aa", 10, "bb", 20, "cc", 30 }; int ret = 0; person newp; FILE* fp = fopen(filename, "a+"); // 追加,不清空文件 if (!fp) { perror("fopen error"); return -1; } // 偏移文件指针 //fseek(fp, 0, SEEK_CUR); // 读取文件 ret = fread(&newp, 1, sizeof(person), fp); printf("name: %s, age: %d\n", newp.name, newp.age); // // 结构体写入文件 ret = fwrite(&p[1], 1, 1 * sizeof(person), fp); if (ret == 0) { perror("fwrite error"); return -1; } // 回卷指针 rewind(fp); // 读取文件 ret = fread(&newp, 1, sizeof(person), fp); printf("name: %s, age: %d\n", newp.name, newp.age); // name: aa, age: 10 return 0; } int main() { read_random("./read_random.txt"); return 0; }
stat 函数
打开文件,对系统资源的消耗较大。
- 函数描述
- 获取文件属性
- 函数原型
#include #include #include int stat(const char *path, struct stat * buf);
- 参数
- path
- 访问文件的路径
- buf
- 文件属性结构体 stat
struct stat{ dev 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; // 最后一次属性改变的时间 };
- 文件属性结构体 stat
- path
- 返回值
- 成功,返回 0;失败,返回 -1
- 参数
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include void get_file_stat(const char* filename) { struct stat s; stat(filename, &s); printf("st_uid: %d, st_gid: %d, st_size: %d\n", s.st_uid, s.st_gid, s.st_size); } int main() { get_file_stat("C:\\Users\\WangHaiying_GamePC\\Desktop\\webserver_epoll.jpg"); return 0; }
- 示意图:对应文件属性
- 示意图:对应文件属性
remove 函数
- 函数描述
- 删除文件
- 函数原型
int remove(const char *pathname);
rename 函数
- 函数描述
- 重命名文件
- 函数原型
int rename(const char *oldpath, const char *newpath);
fflush 函数
- 函数描述
- 手动刷新缓冲区
- 函数原型
int fflush(FILE *stream);
- 返回值
- 成功,返回 0;失败,返回 -1
- 返回值
- 示例
#define _CRT_SECURE_NO_WARNINGS #include int flush_buf(const char* filename) { FILE* fp = fopen(filename, "w+"); if (!fp) { perror("fopen error"); return -1; } char str[64] = { 0 }; //char* str = NULL; // error! while (1) { // 从屏幕获取字符串 scanf("%s", str); printf("str = [%s]\n", str); if (strcmp(str, ":wq") == 0) { break; } // 写入文件 fwrite(str, 1, sizeof(str), fp); // 刷新缓冲区 fflush(fp); } fclose(fp); return 0; } int main() { flush_buf("./flush_buf.txt"); return 0; }
- 扩展
- 缓冲区
- 标准输入 (stdin) -> 标准输入缓冲区
从键盘读取的数据,直接读到缓冲区中,由缓冲区给程序提供数据。
-
标准输出 (stdout) -> 标准输出缓冲区
写给屏幕的数据,先存到缓冲区中,由缓冲区一次性刷新到物理设备 (屏幕)。
-
预读入,缓输出
- 行缓存 (printf())
- 遇到 ‘\n’ 就会将缓冲区中的数据刷新到物理设备上
- 全缓存 (文件)
- 缓冲区存满,数据刷新到物理设备上
- 无缓存 (perror)
- 缓冲区中只要有数据,就立即刷新到物理设备
- 行缓存 (printf())
- 标准输入 (stdin) -> 标准输入缓冲区
- 自动刷新缓冲区
- 文件关闭时,缓冲区会被自动刷新
- 隐式回收时:关闭文件、刷新缓冲区、释放 malloc
- 缓冲区
配置文件读写案例
需求
- 文件中按照键值对的方式存放了有效的信息,需要解析出来
配置文件
- 内容
#英雄id heroid:1 # 英雄姓名 heroName:aaaa # 攻击力 heroAtk:9999 # 防御力 heroDef:1000 # 英雄简介 heroInfo:无敌 aaaaaaaaaaa bbbbbbbb ccccccccc dsfasdfaf ewrqerqwcxvasf // 根据 key 值访问对应的 value 值
- 编码
- 编码要求使用 936 (ANSI/OEM-简体中文)
- 使用 UTF-8 编码,在打印时中文会显示乱码
实现
- config.h
定义结构体、函数声明。
- getFileLines
- 获取文件的有效行数
- isValidLines
- 检测当前行是否是有效信息
- 有冒号就是真的
- parseFile
- 解析配置文件,将文件的键值信息存放到结构体中
- getInfoByKey
- 根据 key 值查找对应的 value 值
- freeConfigInof
- 释放内存
- getFileLines
- config.c
- 函数定义
- main.c
- 主函数中测试
代码
- config.h
#define _CRT_SECURE_NO_WARNINGS #pragma once #include #include #include // 配置信息 结构体 struct ConfigInfo { char key[64]; char value[64]; }; // 获取有效行数 int getFileLines(char* filePath); // 检测当前行是否是有效信息 int isValidLines(char* str); // 解析文件 void parseFile(char* filePath, int lines, struct ConfigInfo** configinfo); // 根据 key 获取对应 value char* getInfoByKey(char* key, struct ConfigInfo* configinfo, int len); // 释放内存 void freeConfigInfo(struct ConfigInfo* configinfo);
- config.c
#include "config.h" // 获取有效行数 int getFileLines(char* filePath) { FILE* file = fopen(filePath, "r"); if (file == NULL) { return -1; } char buf[1024] = { 0 }; int lines = 0; while (fgets(buf, 1024, file) != NULL) { if (isValidLines(buf)) { lines++; } memset(buf, 0, 1024); } fclose(file); return lines; } // 检测当前行是否是有效信息 int isValidLines(char* str) { if (strchr(str, ':') == NULL) { return 0; //返回假 代表无效行 } return 1; } // 解析文件 void parseFile(char* filePath, int lines, struct ConfigInfo** configinfo) { struct ConfigInfo* info = malloc(sizeof(struct ConfigInfo) * lines); if (info == NULL) { return; } FILE* file = fopen(filePath, "r"); if (file == NULL) { return; } char buf[1024] = { 0 }; int index = 0; // 有效信息的行数 while (fgets(buf, 1024, file) != NULL) { // 有效信息才去解析,例如 heroName:aaaa\n if (isValidLines(buf)) { memset(info[index].key, 0, 64); // 清空 key 和 value 数组 memset(info[index].value, 0, 64); char* pos = strchr(buf, ':'); strncpy(info[index].key, buf, pos - buf); // 截取 key strncpy(info[index].value, pos + 1, strlen(pos + 1) - 1); // 截取 value index++; } memset(buf, 0, 1024); } *configinfo = info; } // 根据 key 获取对应 value char* getInfoByKey(char* key, struct ConfigInfo* configinfo, int len) { for (int i = 0; i < len; i++) { if (strcmp(key, configinfo[i].key) == 0) { return configinfo[i].value; } } return NULL; } // 释放内存 void freeConfigInfo(struct ConfigInfo* configinfo) { if (configinfo != NULL) { free(configinfo); configinfo = NULL; } }
- main.c
#define _CRT_SECURE_NO_WARNINGS #include #include #include #include "config.h" int main() { char* filePath = "./config.txt"; int len = getFileLines(filePath); printf("文件的有效行数为:%d\n", len); // struct ConfigInfo* configInfo = NULL; parseFile(filePath, len, &configInfo); // 根据 key 获取 value printf("heroid = %s\n", getInfoByKey("heroid", configInfo, len)); printf("heroName = %s\n", getInfoByKey("heroName", configInfo, len)); printf("heroAtk = %s\n", getInfoByKey("heroAtk", configInfo, len)); printf("heroDef = %s\n", getInfoByKey("heroDef", configInfo, len)); printf("heroInfo = %s\n", getInfoByKey("heroInfo", configInfo, len)); // 释放内存 freeConfigInfo(configInfo); system("pause"); return 0; }
- config.txt
# 英雄id heroid:1 # 英雄姓名 heroName:aaaa # 攻击力 heroAtk:9999 # 防御力 heroDef:1000 # 英雄简介 heroInfo:无敌 aaaaaaaaaaa bbbbbbbb ccccccccc dsfasdfaf ewrqerqwcxvasf // 根据 key 值访问对应的 value 值
文件加密
- 文件版排序:生成随机数,写入文件,排序后再重新写入