网站如何连接微信支付宝吗,大庆门户网站,网站建设生存期模型,个人域名做邮箱网站文章目录 文件1. 流1.1 文件缓冲1.2 标准流1.3 文本文件和二进制文件 2. 打开/关闭文件2.1 fopen2.2 fclose 3. 读写文件3.1 fgetc fputc3.2 fgets futs3.3 fscanf fprintf3.4 fread fwrite 4. 文件定位5. 错误处理5.1 errno 文件
1. 流
在 C 语言中… 文章目录 文件1. 流1.1 文件缓冲1.2 标准流1.3 文本文件和二进制文件 2. 打开/关闭文件2.1 fopen2.2 fclose 3. 读写文件3.1 fgetc fputc3.2 fgets futs3.3 fscanf fprintf3.4 fread fwrite 4. 文件定位5. 错误处理5.1 errno 文件
1. 流
在 C 语言中流 (stream) 表示任意输入的源或任意输出的目的地。流是一个抽象的概念它既可以表示存储硬盘上的文件也可以表示网络端口或者打印设备。流这个概念可以很好地屏蔽硬件设备之间的差异使得 C 语言可以像读写文件一样读写任意的设备。
Linux哲学一切皆文件。
1.1 文件缓冲
仅仅了解抽象的概念是不够的有时候我们还需要了解事物运行的机理。由于内存和硬件设备之间存在读写性能上的鸿沟所以操作系统会在内存上为流设置缓冲区。 缓冲区是以先进先出的方式管理数据的。缓冲区分为三种类型
满缓冲。当缓冲区空时从输入流中读取数据当缓冲区满时向输出流中写入数据。行缓冲。每次从输入流中读取一行数据每次向输出流中写入一行数据stdin、stdout。无缓冲。顾名思义就是没有缓冲区stderr。
1.2 标准流
C 语言对流的访问是通过文件指针实现的它的类型为 FILE* 。并且在stdio.h头文件中提供了 3 个标准流。这 3 个标准流可以直接使用——我们不需要对其进行声明也不用打开或者关闭它们。
文件指针流默认含义stdin标准输入键盘stdout标准输出屏幕stderr标准错误屏幕
1.3 文本文件和二进制文件
C 语言支持两种类型的文件文本文件和二进制文件。文本文件中存储的是字符数据人类是可以看懂的二进制文件中的数据人类是看不懂的。 [!TIP] 二进制文件的存储的基本单位是字节 文本文件的基本单位是字符字符 字节 编码比如GBK UTF-8 文本文件具有两个独特的性质 文本文件有行的概念。文本文件被划分为若干行并且每一行的结尾都以特殊字符进行标记。在 Windows 系统中是以回车符和换行符 (\r\n) 进行标记的在 Unix 和 Macintosh 系统中是以换行符 (\n) 标记的。 文本文件可能包含一个特殊的“文本末尾”标记。一些操作系统允许在文本文件的末尾使用一个特殊的字节作为标记。在 Windows 系统中这个标记为 ‘\x1a’ (CtrlZ)。CtrlZ不是必需的但如果存在它就标志着文件的结束其后的所有字节都会被忽略。大多数其他操作系统 (包括 UNIX) 是没有文件末尾字符。 使用 CtrlZ 的这一习惯继承自 DOS而 DOS 中的这一习惯又是从 CP/M (早期用于个人电脑的一种操作系统) 来的。
在写入数据时我们需要考虑是以文本形式存储还是以二进制的形式存储。比如存储整数 32767一种选择是写入字符 ‘3’, ‘2’, ‘7’, ‘6’, ‘7’需要 5 个字节。 另一个选择是以二进制形式存储这个数这种方法只需要两个字节。 文本形式可以方便人类阅读和编辑二进制形式可以节省空间并且转换效率高。
2. 打开/关闭文件
2.1 fopen
读写文件之前我们需要使用 fopen 函数打开文件。
FILE* fopen(const char* filename, const char* mode);第一个参数是文件的路径用来定位文件的第二个参数表示是以何种模式打开文件的。如果无法打开文件 fopen 返回空指针。
文件路径
文件路径分为两种一种是绝对路径从根目录 (或者盘符) 开始一直到文件所在的位置比如“c:/project/test.dat”。另一种是相对路径从当前工作目录开始一直到文件所在的位置比如“in.dat”。
在实际工作中我们一般使用相对路径 (Why? 简单高效)。
模式
模式的选择不仅依赖于后续对文件的操作还依赖于文件是文本形式还是二进制形式。打开一个文本文件可以使用下面一些模式
模式字符串含义“r”打开文件用于读“w”打开文件用于写文件不存在则创建“a”打开文件用于追加文件不存在则创建“r”打开文件用于读和写从文件头开始“w”打开文件用于读和写文件不存在则创建“a”打开文件用于读和写文件不存在则创建
当使用 fopen 打开二进制文件时需要在模式字符串中包含字母 b。
模式字符串含义“rb”打开文件用于读“wb”打开文件用于写文件不存在则创建“ab”打开文件用于追加文件不存在则创建“rb或rb”打开文件用于读和写从文件头开始“wb或wb”打开文件用于读和写文件不存在则创建“ab或ab”打开文件用于读和写文件不存在则创建
写模式和追加模式是不一样的。如果文件存在写模式会清空原有的数据而追加模式会在原有数据的后面写入新的内容。
2.2 fclose
fclose 可以关闭程序不再使用的文件。
int fclose(FILE* stream);如果成功关闭fclose返回零否则返回 EOF。 [!TIP] 注意当不再使用某个文件时一定要及时关闭该文件。 下面给出了一个程序框架展示了在实际工作中是如何打开和关闭文件的
FILE* fp fopen(filename, mode);
if (fp NULL) {// error handling
}
...
fclose(fp);3. 读写文件
前面介绍了如何打开和关闭文件接下来我们来学习下如何读写文件。
其中 fgetc/fputc , fgets/fputs 和 fscanf/fprintf 是用来读写文本文件的
fread/fwrite 是用来读写二进制文件的。
3.1 fgetc fputc
fgetc
fgetc 可以从输入流中读取一个字符如果读取成功返回读取的字符如果读到文件末尾或者读取失败返回 EOF。
int fgetc(FILE* stream);fgetc 和 getchar 类似。不同的是 getchar 只能从标准输入流(stdin)中读取字 符而 fgetc 可以从任意一个输入流中读取字符。
fputc
fputc 可以向输出流中写入一个字符如果写入成功返回写入的字符如果写入失败返回EOF。
int fputc(int c, FILE* stream);fputc 和putchar 类似。不同的是 putchar 只能向标准输出流(stdout)中写入字符而fputc 可以向任意一个流中写入字符。
示例
#include stdio.h
#include cstdlibint main(int argc, char** argv) {if (argc ! 3) {printf(Error: invalid arguments\n);exit(EXIT_FAILURE);}// 打开文件流FILE* source_fp fopen(argv[1], r);if (source_fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}FILE* dest_fp fopen(argv[2], w);if (dest_fp NULL) {printf(Can not open %s\n, argv[2]);fclose(source_fp);exit(EXIT_FAILURE);}// 复制文件int c 0;while ((c fgetc(source_fp)) ! EOF) {fputc(c, dest_fp);}// 关闭文件流fclose(source_fp);fclose(dest_fp);return 0;
}3.2 fgets futs
一个字符一个字符地读写文本文件效率太慢了。C 语言提供了一次性可以读写一行的函数 fgets 和 fputs 。
fgets
从输入流 stream 中最多读取 count - 1 个字符并把读取的字符存入 str 指向的字符数组中。 fgets 遇到换行符’\n’或者文件的末尾就会终止(也就是说读取的字符数可能不足 count - 1 个)并且会存储换行符’\n’。 fgets 会在最后添加空字符’\0’。
char* fgets(char* str, int count, FILE* stream);
参数:str: 指向一个字符数组count: 能够写入的最大字符数量(通常是str指向字符数组的长度)stream: 输入流
返回值:成功返回str失败NULLfgets 是 gets 的通用版本它可以从任意输入流中读取数据而 gets 只能从 stdin 中读取数据。
fgets 也比 gets 更为安全因为它限制了读取字符的最大数目 (count - 1)。此外如果 fgets 是因为读取了换行符而终止那么它会存储换行符’\n’而 gets 函数从来不会存储换行符。
fputs
将 str 指向的字符串写入输出流 stream 中。
int fputs(const char* str, FILE* stream);
参数str: 要写的字符串(以\0结尾的字符串)stream: 输出流
返回值成功返回一个非负值。失败返回EOF并设置errno。fputs 是 puts 的通用版本它可以将字符串写入到任意的输出流中而 puts 只能 写入到 stdout 中。此外 fputs 是原样输出字符串而 puts 会在字符串后面而外 输出一个换行符’\n’。
示例
#include stdio.h
#include cstdlibint main(int argc, char** argv) {if (argc ! 3) {printf(Error: invalid arguments\n);exit(EXIT_FAILURE);}// 打开文件流FILE* source_fp fopen(argv[1], r);if (source_fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}FILE* dest_fp fopen(argv[2], w);if (dest_fp NULL) {printf(Can not open %s\n, argv[2]);fclose(source_fp);exit(EXIT_FAILURE);}// 复制文件fgets fputschar str[1024];while (fgets(str, sizeof(str), source_fp) ! NULL) {fputs(str, dest_fp);}// 关闭文件流fclose(source_fp);fclose(dest_fp);return 0;
}3.3 fscanf fprintf
fscanf
fscanf 和 scanf 类似是用来进行格式化输入的。
int fscanf(FILE* stream, const char* format, ...);不同的是 scanf 是从标准输入(stdin)中读取数据而 fscanf可以从任何一个流中读取数据。也就是说当 fscanf 的第一个参数为 stdin 时它的效果等价于scanf 。
顺便提一下sscanf 可以从字符串中读取数据。
fprintf
fprintf 和 printf 类似是用来进行格式化输出的。
int fprintf(FILE* stream, const char* format, ...);不同的是 printf 始终是向标准输出(stdout)写入内容的而 fprintf 可以向任何一个输出流中写入内容。也就是说当 fprintf 的第一个参数为 stdout 时它的效果等价于 printf 。
顺便提一下 sprintf 可以将内容写入到一个字符数组中。
格式化输入输出可以用于序列化和反序列化过程中。所谓序列化就是将程序中的对象转换成一种可以保存的格式(二进制或文本)从而方便存储(存储到文件或数据库中)或传输(通过网络传输给另一台机器)。反序列化则是序列化的逆过程它将按一定格式存储的数据转换成程序中的对象。
示例
#include stdio.h
#include cstdlibtypedef struct {char name[25];int age;char gender;
} Student_t;int main(int argc, char** argv) {if (argc ! 2) {printf(Error: invalid arguments\n);exit(EXIT_FAILURE);}Student_t stu1 {tom, 18, f};FILE* fp fopen(argv[1], w);if (fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}// 序列化fprintf(fp, %s %d %c, stu1.name, stu1.age, stu1.gender);fclose(fp);fp fopen(argv[1], r);if (fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}// 反序列化Student_t stu2 {jack, 19, m};fscanf(fp, %s %d %c, stu2.name, stu2.age, stu2.gender);fclose(fp);printf(Stu2.name: %s Stu2.age: %d Stu2.gender: %c\n, stu2.name, stu2.age, stu2.gender);return 0;
}3.4 fread fwrite
fread 和 fwrite 主要是用来处理二进制文件的。 fread 可以每次读取一大块数据 fwrite 可以每次写入一大块数据。
fread 从输入流 stream 中最多读取 count 个元素并把它们依次存放到 buffer 指向的数组中。
size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
参数buffer: 指向存放数据的数组size: 每个元素的大小(以字节为单位)count: 最多可以读取的元素个数stream: 输入流
返回值成功读取元素的个数。当读到文件末尾或者发生错误时返回值可能小于count。我们可以通过feof和ferror函数来判断到底是读到了文件末尾还是发生了错误。fwrite 将存放在 buffer 指向的数组中的 count 个元素写入到输出流 stream 中。
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
参数buffer: 指向存放数据的数组。size: 每个元素的大小(以字节为单位)count: 要写入元素的个数stream: 输出流
返回值成功写入元素的个数。当发生错误时这个值可能小于count。fread/fwrite 不仅可以用于读写二进制文件还可以用于序列化和反序列化过程中。
示例1复制二进制文件
int main(int argc, char** argv) {if (argc ! 3) {printf(Error: invalid arguments\n);exit(EXIT_FAILURE);}// 打开文件流FILE* source_fp fopen(argv[1], r);if (source_fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}FILE* dest_fp fopen(argv[2], w);if (dest_fp NULL) {printf(Can not open %s\n, argv[2]);fclose(source_fp);exit(EXIT_FAILURE);}// 复制文件fread fwritechar str[1024];int n 0;while ((n fread(str, 1, sizeof(str), source_fp)) ! 0) {fwrite(str, 1, n, dest_fp);}// 关闭文件流fclose(source_fp);fclose(dest_fp);return 0;
}示例2序列化与反序列化
#include stdio.h
#include cstdlibtypedef struct {char name[25];int age;char gender;
} Student_t;int main(int argc, char** argv) {if (argc ! 2) {printf(Error: invalid arguments\n);exit(EXIT_FAILURE);}Student_t stu1 {jerry, 20, m};FILE* fp fopen(argv[1], w);if (fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}/* ********** 使用fscanf fprintf ********** */// 序列化fwrite(stu1, sizeof(Student_t), 1, fp);fclose(fp);fp fopen(argv[1], r);if (fp NULL) {printf(Can not open %s\n, argv[1]);exit(EXIT_FAILURE);}Student_t stu2 {jack, 19, m};// 反序列化fread(stu2, sizeof(Student_t), 1, fp);fclose(fp);printf(Stu2.name: %s Stu2.age: %d Stu2.gender: %c\n, stu2.name, stu2.age, stu2.gender);return 0;
}4. 文件定位
每个流都有相关联的文件位置。在执行读写操作时文件位置会自动推进并按照顺序访问文件。顺序访问是很好的但是有时候我们可能需要跳跃地访问文件。为此 提供了几个函数来支持这种能力
int fseek(FILE* stream, long int offset, int whence);
long int ftell(FILE* stream);
void rewind(FILE* stream);fseek
fseek 可以改变与 stream 相关联的文件位置。其中 whence 表示参照点参照点有 3 个选择
SEEK_SET文件的起始位置SEEK_CUR文件的当前位置SEEK_END文件的末尾位置
offset 表示偏移量 (可能为负)它是以字节进行计数的。比如移动到文件的起始位置可以这样写
fseek(fp, 0L, SEEK_SET);移动到文件的末尾可以这样写
fseek(fp, 0L, SEEK_END);往回移动10个字节可以这样写
fseek(fp, -10L, SEEK_CUR);通常情况下 fseek 会返回 0如果发生错误 (比如请求的位置不存在)那么 fseek 会返回非 0 值。
ftell
ftell 以长整数形式返回当前文件位置如果发生错误ftell返回-1L。ftell 一般的用法是记录当前位置方便以后返回。
long int filePos ftell(fp);
...
fseek(fp, filePos, SEEK_SET);rewind
rewind 会将文件位置设置为起始位置类似于调用
fseek(fp, 0L, SEEK_SET);练习
用户输入文件名将整个文件的内容读入到字符数组中并在后面添加空字符’\0’。
char* readFile(const char* path);char* readFile(const char* path) {// 打开文件FILE* fp fopen(path, rb);if (fp NULL) {printf(Can not open %s\n, path);exit(EXIT_FAILURE);}// 文件结尾位置fseek(fp, 0L, SEEK_END);long int file_len ftell(fp);// 将文件内容复制到buffer中rewind(fp); // 回到文件开头因为要从开头开始复制char* buffer (char*)malloc(file_len 1);fread(buffer, 1, file_len, fp);// 在数组末尾添加空字符buffer[file_len] \0;fclose(fp);return buffer;
}int main(int argc, char* argv[]) {if (argc ! 2) {printf(Error: invalid arguments\n);exit(EXIT_FAILURE);}char* buffer readFile(argv[1]);printf(buffer content: \n%s\n, buffer);free(buffer);return 0;
}5. 错误处理
错误的检测和处理并不是 C 语言的强项C 语言没有其它高级语言 (C, Java, C#等) 所具有的异常处理机制。
C 语言往往是通过函数的返回值或者是测试 errno 变量来检测错误的并且需要程序员自己编写代码来处理错误。
5.1 errno
errno 是一个 int 类型的全局变量 (C11 修改为线程本地变量即每个线程都有一个独有的 errno 变量)它定义在 errno.h 头文件中。
标准库中有些函数 (比如与文件相关的一些函数)如果在调用过程中发生了错误它会设置 errno 的值以表明发生 了何种类型的错误。
程序启动时会将 errno 的值设为 0表示没有错误发生。其它非 0 值都表示发生了某种类型的错误。
我们可以通过 perror 和 strerror 来显示错误信息。其中 perror 定义在 stdio.h 头文件 中 strerror 定义在 string.h 头文件中。
示例
int main(void) {printf(%d\n, errno); // errno 0FILE* fp fopen(not_exist.txt, r);printf(%d\n, errno); // errno 2printf(%s\n, strerror(errno)); // No such file or directoryperror(not_exist.txt); // not_exist.txt: No such file or directoryreturn 0;
}