C语言程序把输入和输出看作是字节流,对于面向文本的程序,每个字节代表一个字符。文件类型type FILE
对象包含控制流的信息1:指向缓存的指针、位置指示器(缓存中当前字符位置)和状态指示器(是否正在读或写文件、是否出现错误或EOF
等等)。包括头文件
或
后,自动创建三个文件类型FILE
的对象,相应指针FILE *
分别是stdin
(标准输入流standard input stream)、stdout
(标准输出流standard output stream)和stderr
(标准错误流standard error stream),都是常量constant,无法直接赋值。通常,stdin
与键盘相连,stdout
和stderr
与屏幕相连,但是也可以把它们重定向到文件或者管道(见下文重定向标准输入和输出流)。
FILE * fopen (const char * filename, const char * mode);
FILE * freopen (const char * filename, const char * mode, FILE * stream);
int fclose (FILE * stream);
除了程序开始时自动创建的三个文件类型对象,也可以使用头文件
中的fopen
函数创建需要的文件类型对象。根据fopen
函数原型,其第一个参数是文件名,第二个参数是文件访问模式,返回值是一个指向文件类型对象的指针(空指针NULL
代表打开失败)。
C string | 模式 |
---|---|
“r” | 读 |
“w” | 写 |
“a” | 附加 |
“r+” | 读和更新 |
“w+” | 写和更新 |
“a+” | 附加和更新 |
fopen
函数按照文件访问模式打开文件,并将该文件与一个流联系起来。在未来的操作中,该流可以通过返回的文件类型指针来辨认。流充当着程序端与文件端之间的媒介,连接二者。文件端可以是文件,也可以是装置,比如键盘和屏幕。相应地,可以通过fclose
函数关闭与传入文件类型指针相关联的文件,并将流与文件和所有相关联的缓存分离,清空所有缓存。分离后,该流FILE *
还可以通过freopen
函数重新被使用去打开(其他)文件或者改变文件访问模式。代码示例如下:
#include
int main(int argc, char const *argv[]) {
FILE * pfile = fopen("1.txt", "r"); // open file 1.txt
if (pfile != NULL) {
printf("fopen succeeded.\n");
} else {
printf("fopen failed.\n");
}
int n = 13;
char a[n];
fgets(a, n, pfile); // read n-1 characters from pfile stream
// 1.txt only contains one line: hello, world\n
printf("%s\n", a);
fclose(pfile); // close file 1.txt
freopen("1.txt", "a", stdout); // redirect stdout to file 1.txt
// and in append mode
printf("This sentence is redirected to a file.\n");
// append to 1.txt, instead of display
fclose(stdout);
return 0;
}
Shell命令和输出
user@Laptop C I/O % clang freopen.c
user@Laptop C I/O % cat 1.txt
hello, world
user@Laptop C I/O % ./a.out
fopen succeeded.
hello, world
user@Laptop C I/O % cat 1.txt
hello, world
This sentence is redirected to a file.
字符的输入和输出有如下(代码片段示例)三种2,其中getchar
和putchar
默认使用stdin
和stdout
,具体实现方式可能是函数也可能是宏指令(macro);其余两种增加了一个参数FILE * stream
,可以传入具体想利用的输入输出流,二者基本等价,除了getc
和putc
也可能像getchar
和putchar
一样是宏指令。如果出现错误,返回EOF
(end of file)。EOF
是一个宏定义的int
类型的负整常数(符号常量),一般为-1
,除了真实的文件末尾,还可以通过键盘模拟,比如DOS和Windows使用Ctrl+Z
和UNIX使用Ctrl+D
。
#include // in C
#include // in C++
// use stdin and stdout
int getchar(void);
int putchar(int character);
// pass in stream to read from and write to
int getc(FILE * stream);
int putc(int character, FILE * stream);
int fgetc(FILE * stream);
int fputc(int character, FILE * stream);
// use stdin and stdout
char * gets(char * str);
int puts(const char * str);
// specify stream to read from and write to
char * fgets(char * str, int num, FILE * stream);
int fputs(const char * str, FILE * stream);
字符串的输入和输出有以上两种:
char *
指针,返回NULL
表示出现错误;int
型整数,非负代表成功,EOF
表示出错;FILE *
;fgets
还比gets
多一个整型int
参数,表示最多读取num-1
个字符,末尾再添加无效字符'\0'
;gets
没有指明字符串大小,可能导致缓存溢出,是不安全的;gets
遇到换行符(newline character)或者EOF
停止,无论哪个先发生;fgets
遇到换行符'\n'
(newline character)、EOF
或者已经读了num-1
个字符停止,无论哪个先发生;gets
不包括末尾换行符,puts
添加一个换行符,而fgets
和fputs
相反。#include
#include
int main(int argc, char const *argv[]) {
int n = 16;
char a[n];
gets(a); // keyboard input: hello, world\n
printf("strlen(a): %lu\n", strlen(a)); // without ending '\n'
puts(a); // add '\n' at end
printf("%s", a); // without '\n' at end
printf("newline\n");
return 0;
}
Shell命令和输出:C警告gets
不安全
user@Laptop C I/O % clang -std=c11 -Wall ctest.c
user@Laptop C I/O % ./a.out
warning: this program uses gets(), which is unsafe.
hello, world // keyboard input
strlen(a): 12
hello, world
hello, worldnewline
#include
#include
int main(int argc, char const *argv[]) {
FILE * pfileI = fopen("1.txt", "r");
// 1.txt contains only one line: hello, world\n
FILE * pfileO = fopen("2.txt", "w");
int n = 16;
char a[n];
fgets(a, n, pfileI);
printf("strlen(a): %lu\n", strlen(a)); // with ending '\n'
fputs(a, pfileO);
printf("%s", a); // with ending '\n'
printf("newline\n");
return 0;
}
输出(gets
和puts
)
strlen(a): 13
hello, world
newline
三种格式化输入和输出对应三种输入源和输出目的地:
FILE *
#include // in C
#include // in C++
// read from stdin and write to stdout
int scanf(const char * format, arg1, arg2, ...);
int printf(const char * format, arg1, arg2, ...);
// read from str and write to str
int sscanf(const char * str, const char * format, arg1, arg2, ...);
int sprintf(char * str, const char * format, arg1, arg2, ...);
// read from stream and write to stream
int fscanf(FILE * stream, const char * format, arg1, arg2, ...);
int fprintf(FILE * stream, const char * format, arg1, arg2, ...);
格式化输入函数按照format
的所指示的规格解读从输入源读取的字符,并将其写入剩余参数arg1, arg2, ...
(都是指针,即地址),返回成功匹配并赋值的项目个数。格式化输出函数在format
的控制下转化、格式化并向输出目的地输出剩余参数,返回打印的字符个数。
格式化输入函数的格式format
字符串可以分为三种字符:
%
开头。与格式化输入函数的格式参数相比,格式化输出函数的格式字符串只分为两种字符:
%
开头。#include
int main(int argc, char const *argv[]) {
/* code */
freopen("1.txt", "r", stdin);
int a = 0;
int b = 0;
int c = 0;
scanf("%d%d %d", &a, &b, &c);
printf("%d\n", a - b - c);
int year = 0;
int month = 0;
int day = 0;
scanf("%d%d-%d", &year, &month, &day);
printf("%d:%d:%d\n", year, month, day);
return 0;
}
文本文件"1.txt"
1
2 // preceding tabs
3 // preceding spaces
2021-02-15
输出
-4
2021:-2:15
当输入格式说明符为%s
(输入字符串)时,遇到第一个空白字符停止(无论字符数组的大小),并在末尾添加无效字符'\0'
。(代码示例见5. 命令行重定向输入输出流)
在Shell命令行,使用./a.out < input.txt > output.txt
,意思是将input.txt作为输入源,将output.txt作为输出目的地。
#include
#include
int main(int argc, char const *argv[]) {
/* code */
int n = 5;
char a[n];
scanf("%s", a);
printf("%s\n", a);
printf("strlen(a): %lu\n", strlen(a));
return 0;
}
Shell命令
user@Laptop C I/O % clang -std=c11 -Wall clt_redirection.c
user@Laptop C I/O % ./a.out < 1.txt > 2.txt
文本文件"1.txt"
hello, world
文本文件"2.txt"(初始为空)
hello,
strlen(a): 6
字符数组大小是5个字符,但是实际读取了6个字符strlen(a): 6
,实际需要字符数组大小为7(加上末尾的无效字符)。
www.cplusplus.com ↩︎
The C Programming Language 2nd Edition by K & R (ANSI C) ↩︎