Linux C语言 20-文件I/O
本节关键字:Linux、C语言、标准输入(stdin)、标准输出(stdout)、文件读写
相关C库函数:getchar、putchar、putc、getc、gets、puts、printf、scanf、fopen、fclose、fputc、fgetc、fputs、fgets、fprintf、fscanf、fwrite、fread
什么叫做IO
I:input,表示输入
O:output,表示输出
在编写程序时,我们经常会用到交互,怎么样做到交互呢。很简单,就是在程序需要用户输入一些数据时,就从标准输入或者文件读取,执行任务后再从标准输出或文件将执行结果告知用户。
标准文件
C语言中把所有的设备都当作文件,而Linux内核也是使用C语言进行编写的,因此在Linux中也有一句名言叫做“一切皆文件”。在C语言程序执行过程中,会自动打开三个文件:
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 屏幕 |
这三个文件指针定义在头文件 stdio.h 中:
#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
C语言的输入和输出
在C语言中,默认输入设备对应键盘,默认输出设备对应屏幕。
相关库函数
#include <stdio.h>
// 标准输出
int putc(int c, FILE *stream);
int putchar(int c);
int puts(const char *s);
int printf(const char *format, ...);
// 标准输入
int getc(FILE *stream);
int getchar(void);
char *gets(char *s); // 不建议使用
int scanf(const cjar *format, ...);
C语言中的I/O(输入/输出)一般使用printf()和scanf(),scanf()函数从标准输入(键盘)读取内容并格式化,printf()函数发送格式化输出到标准输出(屏幕)。
格式化参数
参数格式 | 描述 |
---|---|
%a | 浮点数、十六进制数和p-计数法(c99) |
%A | 浮点数、十六进制数和p-记法(c99) |
%c | 一个字符(char) |
%C | 一个ISO宽字符 |
%d | 有符号十进制整数(int) |
%ld | 有符号十进制长整数(long) |
%e | 浮点数,e-计数法 |
%E | 浮点数,E-计数法 |
%f | 单精度浮点数(默认float,十进制计数法 %.nf 中的n标识精确到小数位后n位) |
%lf | 双精度浮点数(默认double) |
%g | 根据数值不同自动选择%f或%e |
%G | 根据数值不同自动选择%f或%e |
%i | 有符号十进制数(与%d相同) |
%o | 无符号八进制整数 |
%p | 指针 |
%s | 对应字符串char*,%ms最多右对齐输出字符串中的前m列字符,%-ms最多左对齐输出字符串中的前m列字符,%m.ns右对齐输出占m列,但只取字符串中前n个字符 |
%S | 对应宽字符串WCHAR* |
%u | 无符号十进制整数(unsigned int) |
%lu | 无符号十进制长整数(unsigned long) |
%x | 无符号十六进制形式输出整数,字母小写(a - f) |
%lx | 无符号十六进制形式输出长整数,字母小写(a - f) |
%X | 无符号十六进制形式输出整数,字母大写(A - F) |
%lX | 无符号十六进制形式输出长整数,字母大写(A - F) |
- | 表示左对齐输出,省略默认右对齐输出 |
0 | 表示空位用0填充,省略默认空位不填 |
m.n | m指域宽(输出项所占字节数);n指精度,小数的位数,默认n=6 |
l | 对应整型的long,对应实型的double |
h | 将整型的格式字符修正为short型 |
API使用示例
#include <stdio.h>
int main()
{
int iNum, iAge;
char c;
char sAge[16] = {0};
char *rc = NULL;
printf("this is putc ");
putc(59, stdout); // 59对应的ASCII码为 ;
printf("\n");
printf("this is putchar ");
putchar(59); // 59对应的ASCII码为 ;
printf("\n");
puts("this is puts()");
printf("this is printf()\n");
printf("\n");
printf("iNum=%d, 请输入新的值:", iNum);
iNum = getc(stdin);
getchar(); // 读取回车
printf("getc()获取新的 iNum=%d\n", iNum);
printf("\n");
printf("请再次输入新的值: ");
iNum = getchar();
printf("getchar()获取新的 iNum=%d\n", iNum);
printf("\n");
printf("请输入一串字符:");
rc = gets(sAge);
printf("gets()返回rc: %s\n", rc);
printf("gets()获取sAge: %s\n", sAge);
printf("\n");
printf("请输入字符串:");
scanf("%s", sAge);
printf("scanf()获取的字符串为: %s\n", sAge);
printf("\n");
printf("iAge=%d, 请输入新的年龄: ", iAge);
scanf("%d", &iAge);
printf("scanf()获取新的年龄为: %d\n", iAge);
return 0;
}
/**
编译时提示:
/tmp/ccQOB4Oh.o: In function main':
Linux_C_020.c:(.text+0x138): warning: the gets' function is dangerous and should not be used.
运行结果:
this is putc ;
this is putchar ;
this is puts()
this is printf()
iNum=-588257952, 请输入新的值:1
getc()获取新的 iNum=49
请再次输入新的值: 2
getchar()获取新的 iNum=50
请输入一串字符:gets()返回rc:
gets()获取sAge:
请输入字符串:abc
scanf()获取的字符串为: abc
iAge=0, 请输入新的年龄: 25
scanf()获取新的年龄为: 25
*/
文件的读写
在C语言中,可以使用相关接口函数操作文本文件和二进制文件,这些操作包含:创建、打开、读写、关闭等操作。
相关的库函数
文本文件模式 | 描述 |
---|---|
r | 打开文本文件,用于读。流被定位于文件的开始。 |
r+ | 打开文本文件,用于读。流被定位于文件的开始。 |
w | 将文件长度截断为零,或者创建文本文件,用于写。流被定位于文件的开始。 |
w+ | 打开文件,用于读写。如果文件不存在就创建它,否则将截断它。流被定位于文件的开始。 |
a | 打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。流被定位于文件的末尾。 |
a+ | 打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。读文件的初始位置是文件的开始,但是输出总是被追加到文件的末尾。 |
字符串 mode 也可以包含字母 ‘b’ 作为最后一个字符,或者插入到上面提到的任何双字符的字符串的两个字符中间(例如:rb,wb,ab,rb+,r+b,wb+,w+b,ab+,a+b)。这样只是为了和 ANSI X3.159-1989(ANSI ‘C’) 标准严格保持兼容,没有实际的效果;在所有的遵循 POSIX 的系统中,‘b’ 都被忽略,包括 Linux。(其他系统可能将文本文件和二进制文件区别对待,如果在进行二进制文件的 I/O,那么添加 'b’是个好主意,因为你的程序可能会被移植到非 Unix 环境中。)
任何新建的文件将具有模式 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH (0666),然后以进程的掩码值 umask 加以修改。
文件流光标位置
起始位置 | 常量名 | 常量值 |
---|---|---|
文件开头 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
#include <stdio.h>
// 打开文件
// 创建或打开一个文件,类型FILE包含了所有用来控制流的信息
FILE *fopen(const char *path, const char *mode);
// 流光标定位
// 移动文件中的打开文件读写位置指针,offset为偏移量,whence为偏移起始位置(SEEK_SET文件开头、SEEK_CUR当前位置、SEEK_END文件末尾)
int fseek(FILE *stream, long offset, int whence);
// 将文件移动到文件末尾位置,返回文件长度
long ftell(FILE *stream);
// 将当前文件指针移动到文件开始位置
void rewind(FILE *stream);
// 获取文件指针当前相对文件开始位置的偏移量
int fgetpos(FILE *stream, fpos_t *pos);
// 将文件指针移动到相对文件开始位置偏移量的位置
int fsetpos(FILE *stream, fpos_t *pos);
// 关闭文件
// 关闭文件,成功返回0,失败返回EOF
int fclose(FILE *fp);
// 写文件
// 将字符写入到流中,成功返回写入的字符,失败返回EOF
int fputc(int c, FILE *stream);
// 将一个以null结尾的字符串写入到流中,成功返回非负值,失败返回EOF
int fputs(const char *s, FILE *stream);
// 将目标内容按照指定格式写入流中,成功返回写入的字节数(不包含null结束符),失败返回负值
int fprintf(FILE *stream, const char *format, ...);
// 将nmmb个数据元素(每个大小字节长)写入流指向的流,并从ptr给定的位置获取它们。成功返回写入字节数,失败返回0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
// 读文件
// 从流中读取单个字符,成功返回读取到的字符,失败返回EOF
int fgetc(FILE *stream);
// 从流中读取一个长度为size-1的字符串,读取到的字符串存储在s中,遇到'\n'和EOF会停止
char *fgets(char *s, int size, FILE *stream);
// 从流中按照指定格式读取一个字符串,遇到第一个空格和换行符时会停止
int fscanf(FILE *stream, const char *format, ...);
// 从流指向的流中读取nmmb个数据元素,每个大小字节长,并将它们存储在ptr给定的位置。成功返回写入字节数,失败返回0
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
在文件读写的实际应用中使用较多的函数有两组(四个)函数:
文本:fprintf()函数和fscanf()函数,通常用于日志记录。
二进制:fwrite()函数和fread()函数,通常用于数组或结构体等数据结构的存储。
API使用实例
#include <stdio.h>
#include <string.h>
int main()
{
char c;
int rc;
FILE *fp = NULL;
// 文本文件
fp = fopen("/home/iscs/TEST/text", "a+");
if (fp)
{
c = 1;
rc = fputc(c, fp);
printf("fputc rc: %d\n", rc);
c = '\n';
rc = fputc(c, fp);
printf("fputc rc: %d\n", rc);
rc = fputs("fputs\n", fp);
printf("fputs rc: %d, len: %d\n", rc, sizeof("fputs\n"));
rc = fprintf(fp, "%s\n", "fprintf");
printf("fprintf rc: %d, len: %d\n", rc, sizeof("fprintf\n"));
fclose(fp);
}
fp = fopen("/home/iscs/TEST/text", "a+");
if (fp)
{
rc = fgetc(fp);
printf("fgetc rc: %d\n", rc);
rc = fgetc(fp);
printf("fgetc rc: %d\n", rc);
char s[64] = {0};
char *ps = NULL;
ps = fgets(s, 64, fp);
printf("fgets ps: %s, s: %s\n", ps , s);
memset(s, 0, sizeof(s));
rc = fscanf(fp, "%s\n", s);
printf("fscanf rc: %d, s: %s\n", rc, s);
}
// 二进制文件
typedef struct info_s {
int no;
char name[64];
char sex;
short age;
char major[128];
float score;
} info_t;
info_t wStudent = {1713080405, "Yunxi", 1, 25, "电子信息工程", 99.99};
info_t rStudent;
memset(&rStudent, 0, sizeof(info_t));
fp = fopen("/home/iscs/TEST/binary", "ab+");
if (fp)
{
rc = fwrite(&wStudent, sizeof(info_t), 1, fp);
printf("fwrite rc: %d, len: %d\n", rc, sizeof(info_t));
fclose(fp);
}
fp = fopen("/home/iscs/TEST/binary", "ab+");
if (fp)
{
rc = fread(&rStudent, sizeof(info_t), 1, fp);
printf("fread rc: %d, len: %d\n", rc, sizeof(info_t));
fclose(fp);
printf("wStudent: %d, %s, %d, %d, %s, %.02f\n", wStudent.no, wStudent.name, wStudent.sex, wStudent.age, wStudent.major, wStudent.score);
printf("rStudent: %d, %s, %d, %d, %s, %.02f\n", rStudent.no, rStudent.name, rStudent.sex, rStudent.age, rStudent.major, rStudent.score);
}
return 0;
}
/** 运行结果:
fputc rc: 1
fputc rc: 10
fputs rc: 1, len: 7
fprintf rc: 8, len: 9
fgetc rc: 1
fgetc rc: 10
fgets ps: fputs
, s: fputs
fscanf rc: 1, s: fprintf
fwrite rc: 1, len: 204
fread rc: 1, len: 204
wStudent: 1713080405, Yunxi, 1, 25, 电子信息工程, 99.99
rStudent: 1713080405, Yunxi, 1, 25, 电子信息工程, 99.99
*/
拓展:可以将本节的内容与链表一节的简单学生信息管理程序相结合,将输入的信息保存至本地文件中,下次启动时从文件读取,这样就可以避免数据丢失了。
文件IO使用示例
Linux C语言 43-读取配置文件(ini文件)
Linux C语言 44-日志记录