文件输入/输出
一、概述
1.1 概念
文件: 通常是在磁盘或固态硬盘上的一段已命名的存储区。
文件模式: C提供了两种文件模式,文本模式
、二进制模式
。
文本模式: 如果文件使用二进制编码的字符(Unicode)表示文本,该文件就是文本文件。
二进制模式: 如果文件中的二进制代表机器语言或图片或音视频编码,该文件就是二进制文件。
1.2 指向标准文件的指针
标准文件 | 文件指针 | 通常使用的设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 显示器 |
标准错误 | stderr | 显示器 |
二、标准 I/O 中常见的函数
标准库 <stdio.h>
链接:https://www.runoob.com/cprogramming/c-standard-library-stdio-h.html
2.1 fopen() 与 fclose() 与 fflush() 函数
函数声明 | 作用 |
---|---|
FILE *fopen(const char *filename, const char *mode) | 使用给定的 mode (文件访问模式) 打开 filename 所指向的文件; |
int fclose(FILE *stream) | 关闭流 stream,刷新所有的缓冲区。如果流成功关闭,则该方法返回0。如果失败,则返回 EOF。 |
int fflush(FILE *stream) | 刷新缓冲区,将所有输出缓冲区的数据发送到输出的文件中。 成功返回0。失败返回 EOF,且设置错误标识符(即 feof)。 |
打开文件的 mode (模式) 如下:
模式 | 描述 |
---|---|
“r” | 以读模式打开文件; 该文件必须存在。 |
“w” | 以写模式打开文件; 文件如果存在,则清空原有的内容; 文件不存在则创建文件; |
“a” | 以写模式打开文件; 写操作向文件末尾追加数据; 文件不存在则创建文件; |
“r+” | 读写模式打开文件; 内容写入文件末尾; 该文件必须存在; |
“w+” | 读写模式打开文件; 文件如果存在,则清空原有的内容; 文件不存在则创建文件; |
“a+” | 读写模式打开文件; 内容写入文件末尾; 文件不存在则创建文件; |
示例
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = NULL; // 将指针声明为 NULL 是一个好习惯,NULL 是定义在 stdlib.h 的宏
fp = fopen("elson.txt", "w"); // 以一写模式打开一个文件
if (fp == NULL) { // 文件打开失败
exit(EXIT_FAILURE); //EXIT_FAILURE 是定义在 stdlib.h 的宏
}
fprintf(fp, "%s", "我是 Elson."); // 将文本写入指定的文件
fclose(fp); // 关闭文件流
return(0);
}
2.2 getchar() 与 putchar() 、getc() 与 putc() 、fgetc() 与 fputc() 函数
函数声明 | 作用 |
---|---|
int getchar(void) | 从标准输入 stdin 中获取一个(无符号)字符。 |
int putchar(int char) | 把参数 char 指定的(无符号)字符写入到标准输出 stdout 中。 |
int getc(FILE *stream) | 从指定的 stream 中获取下一个(无符号)字符,并把位置标识符往前移动。 |
int putc(int char, FILE *stream) | 把参数 char 指定的(无符号)字符写入到指定的流 stream 中,并把位置标识符往前移动。 |
int fgetc(FILE *stream) | 从指定的流 stream 获取下一个(无符号)字符,并把位置标识符往前移动。 |
int fputc(int char, FILE *stream) | 把参数 char 指定的(无符号)字符写入到指定的流 stream 中,并把位置标识符往前移动。 |
小结:
- getchar() 函数从
stdin
获取数据;putchar() 函数将数据输出到stdout
中;- getc() 函数从指定文件中获取数据;putc() 函数将数据写入到指定的文件中;
- getchar() 与 getc() 的区别在于获取的数据来源不一致,我们可以使用 getc(stdin) 来实现 getchar() 同样的效果;通过查看 stdio.h 文件,我们可以知道 stdin 是一个指向
__stdinp
的文件指针;- getc() 与 fgetc() 函数,他们的实现效果是一样的,不同点在于 getc() 是宏定义,而 fgetc() 是函数;
2.3 gets() 与 puts() 、fgets() 与 fputs() 函数
函数声明 | 作用 |
---|---|
char *gets(char *str) | 1. 从 stdin 读取一行 并存入 str 所指向的字符串中。2. 当读取到换行符时,或者到达文件末尾时,该方法会返回。 3. 当读取到数据时,返回str;当读取到EOF时(即读取到文件末尾),返回NULL。 4. 函数读取到换行符时,会丢弃换行符,返回其余的字符。 |
int puts(const char *str) | 1. 将 str 所指向的字符串输出到 stdout,直到遇到空字符(空字符不输出)。 2. 在输出字符串时,会自动在其末尾添加一个换行符。 |
char *fgets(char *str, int n, FILE *stream) | 1. 从指定的流 stream 读取一行,并存入 str 所指向的字符串内。 2. 当读取 (n-1) 个字符时 (留一个字符用于添加空字符) ,或者读取到换行符时,或者到达文件末尾时,该方法会返回。3. 当读取到数据时,返回str;当读取到EOF时(即读取到文件末尾),返回NULL。 4. 函数读取到换行符时,不会丢弃换行符。 |
int fputs(const char *str, FILE *stream) | 1. 把字符串写入到指定的流 stream 中,但不包括空字符。 2. 不会在输出末尾添加换行符。 |
小结:
- gets() 与 fgets() 函数的区别:fget() 函数可以从流中读取指定长度的字符,而 get() 函数得读取一整行数据或读取到文件结尾EOF才会返回。
- gets() 参数 str 所指向的字符串不一定能装下一行的数据,存在缓冲区溢出的风险(即多余的字符超出了指定的目标空间)。
换行符的问题
- gets() 与 puts() 函数:gets() 会丢弃输入中的换行符,puts() 会在输出的末尾添加换行符。 (
读删写补
)- fgets() 与 fputs() 函数:fgets() 会保留输入中的换行符,fputs() 不会在输出的末尾添加换行符。 (
不删不补
)
空字符的问题 (空字符是为了让程序直到字符串到哪里截止)
- gets() 函数读取整行的输入直到遇到换行符,然后会丢弃换行符,保留其余字符,并在字符末尾添加一个空字符,使之成为一个C字符串。
- fgets() 函数在读取输入直到第1个换行符后面,或读到文件结尾,或读取到 (n-1) 个字符,然后在末尾添加一个空字符,使之成为一个C字符串。
fgets() 函数传入 n,为什么只能读取 (n-1) 个字符?
- 为了在最后一位添加一个空字符,使之成为一个C字符串。保证在输出时知道字符串的截止为止。
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
char line[81];
while (gets(line)) { //gets()返回为NULL时,在条件中求值为0,因此不满足条件。
puts(line);
}
}
2.4 fseek() 与 ftell() 函数
函数声明 | 作用 |
---|---|
int fseek(FILE *stream, long int offset, int whence) | 设置流 stream 的文件位置为给定的偏移 offset; 参数 offset 意味着从给定的 whence 位置查找的字节数。 |
long int ftell(FILE *stream) | 返回给定流 stream 的当前文件位置。 |
fseek() 函数的 whence 参数几个常用常量:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
示例:
fseek(fp, 0L, SEEK_SET); //定位到文件开始位置
fseek(fp, 10L, SEEK_SET); //定位到文件的第10个字节
fseek(fp, 2L, SEEK_CUR); //从文件当前位置前移2个字节
fseek(fp, 0L, SEEK_END); //定位到文件结尾
fseek(fp, -10L, SEEK_END); //从文件结尾回退10个字节
last = ftell(fp); //返回参数指向文件的当前位置到文件开始处的字节数。
2.5 文件结尾
在读取一个文件数据时,如何判断是否读取到文件结尾呢?
如果通过 getc() 函数读取文件时,如果读取到了文件结尾,该函数会返回一个
EOF
,该值定义在<stdio.h>
文件中。
示例
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char * argv[]) {
// insert code here...
int ch;
FILE * fp = NULL; //将指针指向一个NULL是个好习惯
fp = fopen("Elson.txt", "r");
if (fp == NULL) { //校验文件是否打开成功
exit(EXIT_FAILURE);
}
ch = getc(fp);
while (ch != EOF) {
putchar(ch);
ch = getc(fp);
}
fclose(fp);
return 0;
}
三、小结:文本压缩程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LEN 40
int main(int argc, const char * argv[]) {
FILE *fpin = NULL;
FILE *fpout = NULL;
int ch;
char name[LEN];
int count = 0;
if (argc < 2) {
fprintf(stderr, "Usage: %s filename\n", argc[0]);
exit(EXIT_FAILURE);
}
// 打开文件
if ((fpin = fopen(argc[1], "r") == NULL)) {
fprintf(stderr, "I couldn't open the file \"%s\"\n", argc[1]);
exit(EXIT_FAILURE);
}
strncpy(name, argc[1], LEN - 5); //拷贝文件名
name[LEN - 5] = '\0';
strcat(name, ".red"); //给文件添加后缀
if ((fpout = fopen(name, "w") == NULL)) {
fprintf(stderr, "Can't create output file.\n", argc[1]);
exit(3);
}
// 拷贝数据
while ((ch = getc(fpin)) != EOF) {
if (count++ % 3 == 0) { //每3个字符取1个字符。
putc(ch, out);
}
}
// 关闭文件流
if (fclose(fpin) != 0 || fclose(fpout) != 0) {
fprintf(stderr, "Error in closeing files\n");
}
return 0;
}