《Unix/Linux系统编程》第九章读书笔记
自学教材第9章学习笔记
一、任务内容
- 自学教材第九章,提交学习笔记(10分)
本章是复习C语言中的文件操作内容,结构化从文本文件操作,二进制文件操作两个大内容考虑,以前可能只关注文本文件的操作,我们以后更多的是操作二进制文件。 文本文件中考虑字符读写,行读写,任意位置读写等 文件操作都有什么? 二进制文件和文本文件如何转换? 数据结构如何读写? ... - 知识点归纳以及自己最有收获的内容 (3分)
- 问题与解决思路(2分)
- 实践内容与截图,代码链接(3分)
- ...(知识的结构化,知识的完整性等,提交markdown文档,使用openeuler系统等)(2分)
二、知识归纳
(一)思维导图
(二)知识点整理
1.I/O库函数
-
fread()算法
(1)在第一次调用fread()时,FILE结构体的缓冲区是空的,fread()使用保存的文件描述符fd发出一个n = read(fd, fbuffer, BLKSIZE);
系统调用,用数据块填充内部的fbuf[]。然后,它会初始化fbu[]的指针、计数器和状态变量,以表明内部缓冲区中有一个数据块。接着,通过将数据复制到程序的缓冲区,尝试满足来自内部缓冲区的fread()调用。如果内部缓冲区没有足够的数据,则会再发出一个read()系统调用来填充内部缓冲区,将数据从内部缓冲区传输到程序缓冲区,直到满足所需的字节数(或者文件无更多数据)。将数据复制到程序的缓冲区之后,它会更新内部缓冲区的指针、计数器等,为下一个 fread()请求做好准备。然后,它会返回实际读取的数据对象数量。
(2)在随后的每次fread()调用中,它都尝试满足来自FILE结构体内部缓冲区的调用。当缓冲区变为空时,它就会发出read()系统调用来重新填充内部缓冲区。因此,fread()一方面接受来自用户程序的调用,另一方面向操作系统内核发出 read()系统调用。除了read()系统调用之外,所有fread()处理都在用户模式映像中执行。它只在需要时才会进入操作系统内核,并且以一种最高效匹配文件的方式进人。它会提供自动缓冲机制。
-
fwrite()算法
fwrite()算法与fread()算法相似,只是数据传输方向不同。最开始,FILE结构体的内部缓冲区是空的。在每次调用fwrite()时,它将数据写入内部缓冲区,并调整缓冲区的指针、计数器和状态变量,以跟踪缓冲区中的字节数。如果缓冲区已满,则发出 write()系统调用,将整个缓冲区写入操作系统内核。 -
fclose()算法
若文件以写的方式被打开,fclose()会先关闭文件流的局部缓冲区。然后,它会发出一个close(fd)系统调用来关闭FILE结构体中的文件描述符。最后,它会释放FILE结构体,并将FILE指针重置为NULL。 - https://blog.csdn.net/xiaxiaoyule/article/details/44050507?utm_source=app&app_version=4.15.0&code=app_1562916241&uLinkId=usr1mkqgl919blen (优质函数汇总)
2.I/O库函数和系统调用的关系
-
系统调用
可以理解是操作系统为用户提供的一系列操作的接口(API),这些接口提供了对系统硬件设备功能的操作。这么说可能会比较抽象,举个例子,我们最熟悉的 hello world 程序会在屏幕上打印出信息。程序中调用了 printf() 函数,而库函数 printf 本质上是调用了系统调用 write() 函数,实现了终端信息的打印功能。 -
库函数
库函数可以理解为是对系统调用的一层封装。系统调用作为内核提供给用户程序的接口,它的执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数再提供给程序员,更方便于程序员编码。 -
库函数有可能包含有一个系统调用,有可能有好几个系统调用,当然也有可能没有系统调用,比如有些操作不需要涉及内核的功能。
系统调用实际上就是指最底层的一个调用,在linux程序设计里面就是底层调用的意思。面向的是硬件。而库函数调用则面向的是应用开发的,相当于应用程序的API。
在 fread()和 fwrite()的一些实现中,例如在GNU libc库中,如果请求的大小以BLKSIZE为单位,它们可以使用系统调用将以BLKSIZE为单位的数据直接从内核传输到用户指定的缓冲区。即便如此,使用IO库函数仍然需要其他的函数调用。因此,使用系统调用的程序实际上比使用I/O库函数的程序更高效。但是,如果不是以BLKSIZE为单位进行读/写,那么fread()和 fwrite()可能更高效。
3.I/O库模式
-
模式参数
fopen()中的模式参数可以指定为:"r"、"w"、"a",分别代表读、写、追加。
每个模式字符串可包含一个+号,表示同时读写,或者在写入、追加情况下,如果文件不存在则创建文件。- "r+":表示读/写,不会截断文件。
- "w+":表示读/写,但是会先截断文件;如果文件不存在,会创建文件。
- "a+":表示通过追加进行读/写;如果文件不存在,会创建文件。
-
字符模式
int fgetc(FILE *fp); //get a char from fp, cast to int int ungetc(int c, FILE *fp); //push a previously char got by fgetc() back to stream int fput(int c, FILE *fp); //put a char to fp
注意,fgetc()返回的是整数,而不是字符。这是因为它必须在文件结束时返回文件结束符。文件结束符通常是一个整数-1,将它与文件流中的任何字符区分开。
对于fp=stdin或stdout,可能会使用c=getchar(); putchar(c);来代替。对于运行时效来说,getchar()和putchar()通常不是getc()和 putc()的缩小版本。相反,可以将它们实现为宏,以避免额外的函数调用。
-
行模式
char *fgets(char *buf, int sizr, FILE *fp); //从fp中读取最多为一行(以\n结尾)的字符 int fputs(char *buf, FILE *fp); //将buf中的一行写入fp中
当fp是stdin或stdout时,也可以使用以下函数,但它们并非fgets()和 fputs()的缩减版本。
gets (char *buf) ; //input line from stdin but without checking length
puts(char *buf) ; // write line to stdout
-
格式化I/O
FMT为格式字符串-
格式化输入
scanf (char *FMT, &items) ; //from stdin fscanf(fp, char *FMT, &items) ; //from file stream
-
格式化输出
printf(char *FMT, items); //to stdout fprintf(fp, char *FMT, items); //to file stream
-
-
内存中的转换函数
sscanf(buf,FMT,&items); //input from buf[ ] in memory sprintf(buf,FMT,items); //print to buf[ ] in memroy
注意,sscanf()和 sprintf()并非IO函数,而是内存中的数据转换函数。例如,atoi()是一个标准库函数,将一串ASCII数字转换成整数,但是大多数Unix/Linux系统没有itoA()函数,因为转换可由sprintf()完成,所以不需要它。
4.文件缓冲流
每个文件流都有一个FILE结构体,其中包含一个内部缓冲区。对文件流进行读写需要遍历FILE结构体的内部缓冲区。文件流可以使用三种缓冲方案中的一种。
-
无缓冲:从非缓冲流中写入或读取的字符将尽快单独传输到文件或从文件中传输。例如,文件流stderr通常无缓冲。到 stderr 的所有输出都会立即发出。
-
行缓冲:遇到换行符时,写人行缓冲流的字符以块的形式传输。例如,文件流stdout通常是行缓冲,逐行输出数据。
-
全缓冲:写入全缓冲流或从中读取的字符以块大小传输到文件或从文件传输。这是文件流的正常缓冲方案。
通过fopen()创建文件流之后,在对其执行任何操作之前,用户均可发出一个setvbuf(FTLE *stream,char *buf, int node, int size)
调用来设置缓冲区(buf)、缓冲区大小(size)和缓冲方案(mode),它们必须是以下一个宏: -
_IONBUF:无缓冲。
-
_IOLBUF:行缓冲。
-
_IOFBUF:全缓冲。
对于行缓冲流或全缓冲流,可用fflush(stream)立即清除流的缓冲区。
5.变参函数
目前,C语言和C++会强制执行类型检查,但这两种语言仍然允许参数数量可变的函数。这些函数必须至少使用一个参数进行声明,后跟3个点,如
int func(int m, int n ...) //n = last specified parameter
在函数内部,可以通过C语言库宏访问参数:
void va_start(va_list ap,last); // start param list from last parameter
type va_arg(va_list ap, type); // type = next parameter type
va_end(va_list ap); // clear parameter list
三、实践过程
(1)OpenEuler-20.03-LTS-SP3-x86的安装
虚拟机安装欧拉openEuler操作系统 « 久酷 (jiucool.org)
(2)vim的安装与实现
Linux ubuntu下载vim - 八英里 - 博客园 (cnblogs.com)
(3)文件代码的实现
1.将数据写入文件file.txt
2.二进制文件与文本文件的转化
首先考虑了vim,因为不太清楚vim的功能,在查找资料后发现不行
在Linux下使用vim配合xxd查看并编辑二进制文件 - killkill - 博客园 (cnblogs.com) vim只能实现查看和编辑二进制文件,无法实现两者的转换
接着在网上查找资料,得到如下解答
- 对二进制文件中的二进制数据以16位为一组转换为相应的ascii码
- 在文本文件中直接以二进制格式输出即可
二进制文件转化成文本文件_ivy_0709的博客-CSDN博客_二进制文件转换
3.数据结构的读写
在c语言中file被定义成为一个结构体,该结构体的具体定义如下
typedef struct _iobuf {
int cnt; // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
char *ptr; // 下一个要被读取的字符的地址
char *base; // 缓冲区基地址
int flag; // 读写状态标志位
int fd; // 文件描述符
// 其他成员
} FILE;
open()返回的文件指针即为该结构体的指针,如果直接采用->常规方式对此结构体进行访问是非常危险的。此时对此结构体进行读写操作要使用到文件操作函数来进行读写。