前言
该文章基于Linux环境用于介绍基础IO。本文涉及C语言文件IO相关操作、认识文件相关的系统调用接口、认识文件操作符,理解重定向、通过对比fd和FILE,理解系统调用与库函数之间的关系、理解文件系统中的inode相关概念、认识软硬连接以及认识动静态库,结合gcc制作动静态库等。
一、C语言文件IO
1. C语言文件接口以及打开方式
- 文件接口
文件操作函数 | 功能说明 |
fopen | 打开文件 |
fclose | 关闭文件 |
fwrite | 以二进制形式写入文件 |
fread | 以二进制形式读取文件 |
fputc | 写入一个字符 |
fgetc | 读取一个字符 |
fputs | 写入一个字符串 |
fgets | 读取一个字符串 |
fprintf | 格式化写入数据 |
fscanf | 格式化读取数据 |
fseek | 设置文件指针的位置 |
ftell | 计算当前文件指针相对于起始位置的偏移量 |
rewind | 设置文件指针到文件的起始位置 |
ferror | 判断文件操作过程中是否发生错误 |
feof | 判断文件指针是否读取到文件末尾 |
- 打开方式
打开方式 | 说明 |
r | 打开文件用于只读 |
r+ | 打开文件用于读和写 |
w | 打开文件用于写文件,若文件存在,则会清空文件内容,若文件不存在,则会创建之 |
w+ | 与 “w” 的区别在于,增加了读 |
rb | 打开文件,用二进制形式读 |
rb+ | 与"rb" 的区别在于,增加了写 |
wb | 打开文件,以二进制形式写 |
wb+ | 与"wb" 的区别在于,增加了读 |
a | 以尾部追加的方式打开一个文本文件用于只写 |
a+ | 与"a" 的区别在于,增加了读 |
ab | 以尾部追加的方式打开一个二进制文件用于只写 |
ab+ | 与"a" 的区别在于,增加了读 |
下面是用C语言进行读写操作的例子:
- 写文件
#include <stdio.h> #include <string.h> int main() { FILE* fp = fopen("myfile.txt", "w"); if (!fp) { printf("fopen error!\n"); } const char* msg = "hello world!\n"; int count = 5; while (count--) { fwrite(msg, strlen(msg), 1, fp); } fclose(fp); return 0; }
结果:
- 读文件
#include <stdio.h> #include <string.h> int main() { FILE* fp = fopen("myfile.txt", "r"); if (!fp) { printf("fopen error!\n"); } char buf[1024]; const char* msg = "hello world!\n"; while (1) { //注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明 size_t s = fread(buf, 1, strlen(msg), fp); if (s > 0) { buf[s] = 0; printf("%s", buf); } if (feof(fp)) { break; } } fclose(fp); return 0; }
结果:
2. 对当前路径的理解
学习了C语言文件操作过后,我们知道当用fopen以写入的方式打开一个文件时,如果该文件不存在,则会在当前路径下创建该文件,那么什么是当前路径呢?
例如,我们在test_file目录下运行mytest,发现该目录出现了一个myfile.txt文件。
那么,是否可以下结论说,当前路径就是我的可执行程序所在的路径呢?现在我们将myfile.txt删除,回退到上一级目录下再次执行mytest。
我们可以发现,myfile.txt最终出现在了当前所执行mytest的路径中。
当该可执行程序执行起来变成进程之后,我们可以通过获取该进程的PID,然后根据PID在根目录下的proc目录查看该进程的信息。
在这里我们可以看到两个软链接文件cwd和exe,cwd就是进程运行时我们所处的路径,而exe就是该可执行程序的所处路径。
由此我们可以得出结论:我们这里所说的当前路径不是指可执行程序所处的路径,而是指该可执行程序运行成为进程时我们所处的路径。
3. 默认打开的三个流
Linux下一切皆文件,意思是在Linux中我们可以把任何东西都看成文件,那么显示器和键盘等也就相当于是文件。因为我们能在显示器中看到数据,是因为我们向显示器里面写入了数据,电脑能够得到我们从键盘敲入的字符,是因为电脑从键盘中读取了数据。
那么既然显示器和键盘等都是文件,为什么我们不需要提前打开"显示器文件" 和 “键盘文件”,就能直接进行键盘和显示器的相关操作呢?
需要注意的是,打开文件一定是进程运行的时候打开的,而任何进程在运行的时候都会默认打开三个输入输出流,即标准输入流、标准输出流以及标准错误流,对应到C语言当中就是stdin、stdout以及stderr。
其中,stdin对应的是键盘,stdout和stderr对应的是显示器。
通过查看man手册,我们会发现stdin、stdout以及stderr都是FILE* 类型的,也就是文件类型。
extern FILE *stdin; extern FILE *stdout; extern FILE *stderr;
当我们的C程序被运行起来时,操作系统就会默认使用C语言的相关接口将这三个输入输出流打开,之后我们才能调用类似于scanf和printf之类的函数向键盘和显示器进行相应的输入输出操作。
值得注意的是: 不止是C语言当中有标准输入流、标准输出流和标准错误流,C++当中也有对应的cin、cout和cerr,其他所有语言当中都有类似的概念。实际上这种特性并不是某种语言所特有的,而是由操作系统所支持的。
二、 系统文件IO
1. 系统接口
open
系统接口中使用open函数打开或者创建目标文件,man手册中的open:
参数解读:
pathname:要打开或创建的目标文件
flags: 打开文件时,传入的参数选项,用一个或者多个常量进行 “或” 运算,构成flags。
mode:给目标文件设置的权限
flags参数:
常量名 | 说明 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读,写打开 |
O_CREAT | 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 |
O_APPEND | 追加写 |
注意:前面三个常量,必须指定一个且只能指定一个。
返回值:
打开成功返回目标文件的文件描述符fd,失败则返回 -1。
write
系统接口中使用write向目标文件写入数据,man手册中的write:
参数解读:
fildes:文件描述符
buf:数据缓冲区
nbyte:向文件中写入数据的字节数
返回值:
ssize_t:有符号整型,在32位机器上等同于int,在64位机器上等同于long
写入成功,则返回实际写入数据的字节数;
写入失败,则返回 -1。
利用系统接口写文件的例子:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main() { umask(0); int fd = open("myfile.txt", O_WRONLY | O_CREAT, 0664); if(fd < 0) { perror("open"); return 1; } int count = 5; const char* msg = "hello world !\n"; int len = strlen(msg); while(count--) { write(fd, msg, len); } close(fd); return 0; }
read
系统接口中使用read函数读取文件中的数据,man手册中的read:
参数解读:
fildes:文件描述符
buf:数据缓冲区
nbyte:向文件中写入数据的字节数
返回值:
ssize_t:有符号整型,在32位机器上等同于int,在64位机器上等同于long
读取成功,则返回实际读取数据的字节数;
读取失败,则返回 -1。
利用系统接口读文件的例子:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main() { int fd = open("myfile.txt", O_RDONLY); if(fd < 0) { perror("open"); return 1; } const char* msg = "hello world !\n"; char buf[1024]; while(1) { ssize_t s = read(fd, buf, strlen(msg));//类比write if(s > 0) { printf("%s", buf); } else { break; } } close(fd); return 0; }
close
系统接口中使用close关闭文件,man手册中的close:
参数解读:
fildes:文件描述符
返回值:
关闭成功,则返回 0;
关闭失败,则返回 -1。
【Linux学习】基础IO2:https://developer.aliyun.com/article/1383893