【维生素C语言】第十六章 - 文件操作(上)(二)

简介: 本章为文件操作教学上篇,由浅入深的引入问题,然后逐一介绍知识。将详细讲解文件的打开和关闭、文件的顺序读写并精讲函数部分,初步学习“流”的概念!

三、文件的顺序读写


0x00 什么是顺序读写

首先要了解什么是读写:我们写的程序是在内存中,而数据是要放到文件中的,文件又是在硬盘上的。当我们把文件里的数据读到内存中去时,这个动作我们称之为输入/读取。反过来,如果把程序中的东西放到硬盘上,这个动作我们称之为输出/写入。


📚 顺序读写,顾名思义就是按照顺序在文件中读和写。

3faa07eb9a11d5061756dfb96cae8cb7_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

0x01 顺序读写函数一览表

e0948e40219bbd2cd61e650fde7aedb7_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x02 字符输出函数 fputc

ca599dca467d31d36cb2963626a47600_20210818213032241.png


📚 介绍:将参数 char 指定的字符写入到指定的流 stream 中,并把位置标识符向前移动 (字符必须为一个无符号字符)。适用于所有输出流。


💬 代码演示:创建一个 test.txt,随后使用 fputc 函数分别写入 "abc" 到文件中


#include <stdio.h>
int main(void) {
    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;
}

🚩(代码正常运行)


📂 此时打开工程文件夹可以成功看到 test.txt 被创建了(大小为1kb可以看出写入成功了):

78e673c3fb3e5fa8324ef508d541fac6_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


💬 我们正好测试下 w 的覆盖效果,我们把写的内容注释掉:


#include <stdio.h>
int main(void) {
    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;
}

🚩 此时再次运行,我们发现那个文件里的内容不见了(大小也变为0kb):

d43852fda685555ecf25c4e2149ba115_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


❗  值得注意的是,文件的写入是有顺序的。abc,先是a,然后是b,最后是c:


fputc('a', pf);
fputc('b', pf);
fputc('c', pf);

e9d62f417cb5117884b5da4b3f37c802_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

0x03  字符输入函数 fgetc

da5ecb303ef4a37ac1493d33fc99784b_20210819085954812.png

📚 介绍:从指定的流 stream 获取下一个字符,并把位置标识符向前移动(字符必须为一个无符号字符)。如果读取成功会返回相应的ASCII码值,如果读取失败它会返回一个EOF。适用于所有输入流。


💬 代码演示:在工程文件夹里打开 test.txt ,随便写入一些数据,随后使用 fgetc 函数读取:

02f898dfe9228a810d512126607dab11_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

#include <stdio.h>
// 使用fgetc从文件里读
int main(void) {
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 读文件
    int ret = fgetc(pf);
    printf("%c\n", ret);
    ret = fgetc(pf);
    printf("%c\n", ret);
    ret = fgetc(pf);
    printf("%c\n", ret);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}


🚩 运行结果如下:

c6430319b273a81974ca7c7918a4d076_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

❓ 如果读完了会发生什么?


🐞 我们来调试一下看看:

359610391ecced202b33adae215fd0a2_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x04 文本行输出函数 fputs

a536723b46f67e9145ad00041d241158_20210819093221650.png

📚 介绍:将字符串写入到指定的流 stream 中(不包括空字符)。适用于所有输出流。


💬 代码演示:利用 fputs 在 test2.txt 中随便写入几行数据:


#include <stdio.h>
int main(void) {
    FILE* pf = fopen("test2.txt", "w");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 写文件 - 按照行来写
    fputs("abcdef", pf);
    fputs("123456", pf);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}


🚩  (代码成功运行)

f495564ab17fca8b886a41a5cb035bbd_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

❓ 如果想换行呢?


🔑 换行需要在代码里自行加 \n :


fputs("abcdef\n", pf);
fputs("123456", pf);

41a6e7f523f80864be86601b445eb299_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

(这时候打开文件,就是换行的了)


0x05 文本行输入函数 fgets

7d4af124777218f03a73567994c0ebfd_20210819101943887.png

📚 介绍:从指定的流 stream 读取一行,并把它存储在 string 所指向的字符串中,当读取(n-1)个字符时,或者读取到换行符、到达文件末尾时,它会停止,具体视情况而定。适用于所有输入流。


📌 注意事项:假如 n 是100,读取到的就是99个字符(n-1),因为要留一个字符给斜杠0。


💬 代码演示:利用 fgets 读取 test2.txt 中的内容:

41a6e7f523f80864be86601b445eb299_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

#include <stdio.h>
int main(void) {
    char arr[10] = "xxxxxx"; // 存放处
    FILE* pf = fopen("test2.txt", "r");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 读文件 - 按照行来读
    fgets(arr, 4, pf);
    printf("%s\n", arr);
    fgets(arr, 4, pf);
    printf("%s\n", arr);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

🚩 代码运行结果如下:

a55ccad19e50407155266912dc79bbcb_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

🐞 调试一下看看:

9de5afa4bc1d2296869f77ee44701697_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png



0x06 格式化输出函数 fprintf

29d3bf50c3a74c1313af9ed17add0486_2021081911114696.png


📚 介绍:fprintf 用于对格式化的数据进行写文件,发送格式化输出到流 stream 中。适用于所有输出流。


💬 代码演示:将结构体的三个数据利用 fprintf 写到 test3.txt 中:

#include 
struct Player {
    char name[10];
    int dpi;
    float sens;
};
int main(void) {
    struct Player p1 = { "carpe", 900, 3.12f };
    // 对格式化的数据进行写文件
    FILE* pf = fopen("test3.txt", "w");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 写文件
    fprintf(pf, "%s %d %f", p1.name, p1.dpi, p1.sens);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

🚩  (代码成功运行)

f21fefc82f8cd686f67a01d08abe6d58_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x07 格式化输入函数 fscanf

9ed127a890d14b0aec977644ba87e55d_20210819112328419.png


📚 介绍:fscanf 用于对格式化的数据进行读取,从流 stream 读取格式化输入。适用于所有输入流。


💬 代码演示:利用 fscanf 读取 test4.txt 中的内容,并打印:


#include 
struct Player {
    char name[10];
    int dpi;
    float sens;
};
int main(void) {
    struct Player p1 = { 0 }; // 存放处
    // 对格式化的数据进行写文件
    FILE* pf = fopen("test3.txt", "r");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 读文件
    fscanf(
        pf, "%s %d %f",
        p1.name, &(p1.dpi), &(p1.sens) 
    ); // 👆 p1.name本身就是地址(不用&)
    // 将读到的数据打印
    printf("%s %d %f\n", p1.name, p1.dpi, p1.sens);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

🚩 运行结果如下:

84f53c3393e789b5cac27d30817dfe3d_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x08 二进制输出函数 fwrite

0313dd44bfe18abd3504ac8d2bec8b2b_20210819143832291.png


📚 介绍:写一个数据到流中去,把 buffer 所指向的数组中的数据写入到给定流 stream 中。


💬 创建一个 test5.txt,用 fwrite 写入一个数据到 text5.txt 中去:


#include 
// 二进制的形式写
struct S {
    char arr[10];
    int num;
    float score;
};
int main(void) {
    struct S s = { "abcde", 10, 5.5f };
    FILE* pf = fopen("test5.txt", "w");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 写文件
    fwrite(&s, sizeof(struct S), 1, pf);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

🚩  (代码成功运行)

66635b867f5d8c004240e1abce81f1da_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


我们发现他烫起来了2333(划掉)


💡 打开文件后我们发现只有abcde看得懂,后面是什么我们看不懂。


我们试着用 nodepad++ 打开:


c2e0d4b949008d84a050645b31c68752_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


❓ 为什么还是乱码?为什么 abcde 不是乱码?


🔑 解答:


① 我们刚才用的都是文本编译器,文本编译器打开二进制形式的文件完全是两种状态。


② 因为字符串以文本形式写进去和以二进制形式写进去是一样的,但是对于整数、浮点数等来说就不一样了,文本形式写入和二进制形式写入完全是两个概念。


(那么该怎么读呢,我们来看下面的 fread 函数)


0x09 二进制输入函数 fread

965adbbc82c64964702613c272f019ab_20210819143812411.png


📚 介绍:从流中读取,从给定流 stream 读取数据到 buffer 所指向的数组中。


💬 用 fread 读取 text5.txt 中的二进制数据:


#include 
// 二进制的形式读
struct S {
    char arr[10];
    int num;
    float score;
};
int main(void) {
    struct S s = { 0 }; // 存放处
    FILE* pf = fopen("test5.txt", "r");
    if (pf == NULL) {
        perror("fopen");
        return 1;
    }
    // 读文件
    fread(&s, sizeof(struct S), 1, pf);
    // 将读到的数据打印
    printf("%s %d %f", s.arr, s.num, s.score);
    // 关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}


🚩  (代码正常运行)

c48e6ff1c08d746bbd14e038e3230357_580a337cd1d445faa658ab19b68de591.png

🔺 总结: fwrite 和 fread 是一对,fwrire 写进去用 fread 读。


0xA0 流的概念(stream)

在这里,我们补充一下流的概念。

1cb3a6fdd3e0e4b672a50f5183d1917a_65af73223be84320b4a9d811380c5b87.png


📚 观察刚才的表格我们可以发现有的函数是适用于所有xx流的(比如 fputc 函数)。fputc 就适用于所有输出流,也就是说它不仅仅可以给文件里写。我们来读一下MSDN的介绍:

1de349d1d576e8241a499598697d8174_20210818220502796.png

我们发现它还可以写到 stdout 上。


❓ 那么 stdout 是什么呢?


💡 stdout 就是标准输出流,在这里,我们要来讲一下流的概念。

ad1dc23dcd3cafa36da29a373ec48884_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


📚 C语言默认打开的3个流:


     ① stdin   - 标准输入流 - 键盘

     ② stdout - 标准输出流 - 屏幕

     ③ stderr  - 标准输出流 - 屏幕


💬 我们用流向屏幕上输出信息 - stdout:

#include 
int main(void) {
    fputc('a', stdout);
    fputc('b', stdout);
    fputc('c', stdout);
    return 0;
}

🚩  a b c


💬 fgetc 从标准输入流读取 - stdin


#include 
// 使用fgetc从标准输入流中读
int main(void) {
    int ret = fgetc(stdin);
    printf("%c\n", ret);
    ret = fgetc(stdin);
    printf("%c\n", ret);
    ret = fgetc(stdin);
    printf("%c\n", ret);
    return 0;
}

🚩 运行:

adde74df2515867459541403433237cf_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png



相关文章
|
8天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
30天前
|
C语言
【C语言篇】文件操作(下篇)
因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂ 件。如果不做,可能导致读写⽂件的问题。
|
26天前
|
存储 API 数据处理
C语言中的文件操作
C语言中的文件操作
66 0
|
29天前
|
C语言 索引
【C语言】文件操作全解速览
【C语言】文件操作全解速览
27 0
|
30天前
|
存储 程序员 C语言
【C语言篇】文件操作(上篇)
在程序设计中,我们⼀般谈的⽂件有两种:程序⽂件、数据⽂件(从⽂件功能的⻆度来分类的)。
|
1月前
|
存储 编译器 程序员
【C语言】文件操作讲解
【C语言】文件操作讲解
|
3月前
|
C语言
C语言——文件操作
C语言——文件操作
44 2
C语言——文件操作
|
3月前
|
存储 程序员 编译器
文件操作(C语言)
文件操作(C语言)
|
3月前
|
存储 C语言
【c语言】详解文件操作(二)
【c语言】详解文件操作(二)
25 0
|
3月前
|
存储 程序员 编译器
【c语言】详解文件操作(一)
【c语言】详解文件操作(一)
21 0