UNIX环境编程-标准IO (7)
UNIX环境编程-标准IO (7)
IO:input & output 一切实现的基础
IO分类:
- stdio(标准IO)–优先使用 对于不同的系统均可以使用
- sysio系统调用IO(文件IO)
标准IO优点:
- 移植性性好、合并系统调用(可以为读写做一个加速的机制)
- 在不同的系统下,标准IO所依赖的系统调用的函数不同
一、fopen()
1.fprintf
fprintf
是 C 语言标准库中的一个函数,用于将格式化的数据输出到文件。它是 printf
函数的文件版本,printf
输出到标准输出,而 fprintf
可以输出到任意文件
1 |
|
参数的意义如下:
stream
:这是一个指向FILE
类型的指针,代表要写入的文件。可以是任何已打开的文件,包括stdout
。format
:这是一个格式字符串,它定义了输出的格式。格式字符串可以包含普通字符和格式说明符。...
:这是可变参数列表,它们的类型和数量由format
字符串中的格式说明符决定。
函数返回写入的字符数,或者在出错时返回一个负数。
关于刷新缓冲区,C 语言标准库提供了 fflush
函数来刷新一个文件的缓冲区。如果你想立即将 fprintf
的输出发送到文件,而不等待缓冲区满或文件关闭,你可以使用 fflush
函数。
fflush
函数的原型如下:
1 | int fflush(FILE *stream); |
stream
是你想刷新的文件的 FILE
指针。函数成功时返回0,出错时返回EOF。
示例:
1 |
|
这个例子首先打开一个文件用于写入,然后使用 fprintf
将一行文字写入文件,接着使用 fflush
将输出立即写入文件,最后关闭文件
在ubuntu终端可以使用man fopen
查看fopen()
函数相关信息
1 |
|
Return
1 | Upon successful completion fopen(), fdopen() and freopen() return a FILE pointer. Otherwise, NULL is returned and errno is set to indicate the error. |
实例:
1 |
|
运行可执行文件输出:
1 | fopen() failed! errno = 2 |
这样不太方便,因为对于报错的errno有很多,如果这样进行使用,后续还需要进入该目录下,查看该errno所对应的报错信息是什么
2.perror
这个函数可以对errno报错的信息进行自动关联(自动关联全局变量errno)
1 | NAME |
实例:
1 |
|
运行可执行文件
1 | bash: ./fopen: 没有那个文件或目录 |
3.strerror
1 | NAME |
实例:
1 |
|
运行可执行文件
1 | bash: ./fopen: 没有那个文件或目录 |
4.fopen相关问题
(1)fopen返回的FILE类型指针指向的是哪一个空间
栈 or 静态区 or 堆 答案: 堆上
栈是局部变量存放的空间,不对 fopen返回的内容需要在整个程序(进程)运行期间均存在,所以不在栈上
静态区 用static修饰该FILE变量.如下,但是需要注意的是,如果fopen函数被重复调用的时候,这个
static FILE tmp;
只会被声明一次,则如果多次调用fopen
函数那么之前所返回的FILE指针则会被覆盖,不能用,有效的则是最后调用的那个fopen
函数
1 | FILE *fopen(const char *pathname, const char *mode) |
- 堆上,如下,需要对所使用的内存空间进行动态开辟
malloc
,那么fcolse
函数则是对这块动态开辟的空间进行释放
1 | FILE *fopen(const char *pathname, const char *mode) |
问题:一个函数中定义一个静态变量,并且将静态变量地址作为函数的返回值,在程序中多次调用这个函数,会出现什么情况?
答:在一个函数中定义一个静态变量,并将该静态变量的地址作为函数的返回值,可以使得每次调用该函数时都访问到同一个变量。由于静态变量在程序的整个运行周期内都存在,并且仅在第一次调用时进行初始化,因此每次调用该函数时返回的都是同一个变量的地址。
二、字符输入与输出
1.模拟cp 指令将 src文件 复制到dest文件中
1 | cp src dest |
(1)fgetc
1 | int fgetc(FILE *stream); |
(2)fputc
1 | int fputc(int char, FILE *stream) |
(3)实现代码
1 |
|
创建两个txt
文件分别文src.txt
以及dest.txt
,在src.txt
下写入123456
,dest.txt
为空白
运行可执行文件先在终端输入 (make mycpy)
1 | ./mycpy src.txt dest.txt |
使用终端命令比较两个文件是否相同(Enter之后无任何输出表示两个文件是相同的)
1 | $ diff src.txt dest.txt |
2.测试文件中存在有效字符数量
实现代码
1 |
|
运行可执行文件
3.字符串相关
(1)fgets
C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
1 | SYNOPSIS |
- str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串
- n – 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流
return
- 该函数返回一个非负值,如果发生错误则返回 EOF
(2)fputs
C 库函数 int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符
1 |
|
- str – 这是一个数组,包含了要写入的以空字符终止的字符序列
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流
return
- 该函数返回一个非负值,如果发生错误则返回 EOF
4.fread 和 fwrite
(1)fread
注意:fread只能操作工整的数据
C 库函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从给定流 stream 读取数据到 ptr 所指向的数组中
1 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) |
- ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针
- size – 这是要读取的每个元素(这个元素可以是结构体等)的大小,以字节为单位
- nmemb – 这是元素的个数,每个元素的大小为 size 字节
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流
return
- 成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾(返回值则会小于等于0)
使用事项:
一般都是一个字节一个字节的读取
1 | fread(buf,1,BUFSIZE,fps); |
(2)fwrite
C 库函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 把 ptr 所指向的数组中的数据写入到给定流 stream 中。
1 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) |
- ptr – 这是指向要被写入的元素数组的指针。
- size – 这是要被写入的每个元素的大小,以字节为单位。
- nmemb – 这是元素的个数,每个元素的大小为 size 字节。
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
return
- 如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误
5.printf与scanf函数族
(1)sprintf
C 库函数 int sprintf(char *str, const char *format, …) 发送格式化输出到 str 所指向的字符串。
1 | int sprintf(char *str, const char *format, ...); |
- str – 这是指向一个字符数组的指针,该数组存储了 C 字符串。
- format – 这是字符串,包含了要被#写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
(2)snprintf
snprintf() 是一个 C 语言标准库函数,用于格式化输出字符串,并将结果写入到指定的缓冲区,与 sprintf() 不同的是,snprintf() 会限制输出的字符数,避免缓冲区溢出
C 库函数 int snprintf(char *str, size_t size, const char *format, …) 设将可变参数**(…)按照 format 格式化成字符串,并将字符串复制到 str 中,size** 为要写入的字符的最大数目,超过 size 会被截断,最多写入 size-1 个字符
与 sprintf() 函数不同的是,snprintf() 函数提供了一个参数 size,可以防止缓冲区溢出。如果格式化后的字符串长度超过了 size-1,则 snprintf() 只会写入 size-1 个字符,并在字符串的末尾添加一个空字符(\0)以表示字符串的结束
1 | int snprintf ( char * str, size_t size, const char * format, ... ); |
- str – 目标字符串,用于存储格式化后的字符串的字符数组的指针
- size – 字符数组的大小
- format – 格式化字符串
- … – 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化
return
snprintf() 函数的返回值是输出到 str 缓冲区中的字符数,不包括字符串结尾的空字符 \0。如果 snprintf() 输出的字符数超过了 size 参数指定的缓冲区大小,则输出的结果会被截断,只有 size - 1 个字符被写入缓冲区,最后一个字符为字符串结尾的空字符 \0
需要注意的是,snprintf() 函数返回的字符数并不包括字符串结尾的空字符 \0,因此如果需要将输出结果作为一个字符串使用,则需要在缓冲区的末尾添加一个空字符 \0
(3)fscanf
C 库函数 int fscanf(FILE *stream, const char *format, …) 从流 stream 读取格式化输入
1 | int fscanf(FILE *stream, const char *format, ...) |
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流
- format – 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
(4)sscanf
C 库函数 int sscanf(const char *str, const char *format, …) 从字符串读取格式化输入
1 | int sscanf(const char *str, const char *format, ...) |
- str – 这是 C 字符串,是函数检索数据的源
- format – 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
6.文件位置函数和缓冲区刷新函数
在读取与写入的时候,存在文件位置指针
(1)fseek
C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数
1 | int fseek(FILE *stream, long int offset, int whence) |
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流
- offset – 这是相对 whence 的偏移量,以字节为单位
- whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
return
- 如果成功,则该函数返回零,否则返回非零值
(2)ftell
C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置
1 | long int ftell(FILE *stream) |
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流
return
- 该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值
使用实例:
1 |
|
创建src.txt文件其中内容:123456\0
表示七个字节
运行可执行文件
(3)rewind
使用这个函数可以直接让文件位置指针达到文件内容的开头
C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头
1 | void rewind(FILE *stream) |
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流
(4)fflush
C 库函数 int fflush(FILE *stream) 刷新流 stream 的输出缓冲区
1 | int fflush(FILE *stream) |
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个缓冲流
return
如果成功,该函数返回零值。如果发生错误,则返回 EOF,且设置错误标识符(即 feof)
测试代码:
1 |
|
对上述代码进行make,然后运行可执行文件,发现终端无输出
原因: printf函数向标准终端进行输出时,是典型的行缓冲,是碰到换行符号或者一行满的时候以此来刷新缓冲区
修正:
1 |
|
上述代码make之后运行会输出:Before while(1)..
使用fflush,强制刷新缓冲区
1 |
|
make之后终端运行
(5)缓冲区存在的作用
优点
- 行缓冲:换行的时候刷新,满了的时候刷新,强制刷新(标准输出就是这样,因为是终端设备)
- 全缓冲:满了的时候刷新,强制刷新(默认是,只要不是终端设备)
- 无缓冲:如stderr,需要立即输出的内容
一个文件的缓冲模式默认为全缓冲模式,但是缓冲模式是可以进行修改的
7.getline
完整的获取文件一行的内容
1 |
|
lineptr
:指向一个字符指针的指针,用于存储读取到的行。如果*lineptr
是 NULL 或n
是零,函数将为您分配一个新的缓冲区。当您完成使用后,需要使用free
函数来释放这块内存n
:是指向已分配的内存大小的指针。它可能会被函数修改,表示新分配的大小stream
:是一个文件指针,指示从哪里读取数据,例如stdin
返回值:
- 成功:返回读取的字符数量(不包括结尾的 null 字符)。
- 错误或读到文件结束:返回
-1
。
在编译程序时需要在CMakeLists.txt中添加该函数所需要的宏定义_D_GNU_SOURCE
(1)程序实例
读取文件内容
main.c
1 |
|
使用getline函数其参数的初始化是至关重要的,如果没有做好,会出现初始化
CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.16) |
创建src.txt
1 | zhouxuezhi |
CMake之后运行程序,查看结果
1 | 12 |
需要注意的是,getline内部实际使用的malloc开辟动态的内存空间存储,读取到的内容(linebuf
),并且当一行内容超过120字节时,则会使用realloc,在之前的malloc开辟的空间基础上继续增加120个字节的内容(类推)
内存泄漏,getline存在可控的内存泄漏,因为函数内部使用malloc开辟动态内存空间,但是最后并没有使用free函数将开辟的内存空间自动释放
(2)mygetline函数实现
返回类型ssize_t为有符号整形,其值为获取的字符数,文件结束则返回-1,包括换行符’\n’,但不包括字符串结束符’\0’。linepter用来存储获得的字符串,size_t为无符号整形表示linepter的字节数。当*linepter为空时函数则动态为其分配空间,注意要将size_t值赋0。当linepter的空间不足时,函数会通过realloc,重新分配更大的空间。stream为文件指针,用于读取文件
mygetline.h
1 |
|
mygetline.c
1 |
|
main.c
1 |
|
工程目录结构
1 | (base) zxz@ubuntu:~/Proj/C_C++/linux_c/sys/getline_test$ tree |
CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.16) |
运行结果
1 | (base) zxz@ubuntu:~/Proj/C_C++/linux_c/sys/getline_test/bin$ ./main ../src.txt |
运行结果与getline函数相同
8.临时文件
(1)tmpfile
C 库函数 FILE *tmpfile(void) 以二进制更新模式(wb+)创建临时文件。被创建的临时文件会在流关闭的时候或者在程序终止的时候自动删除
1 | FILE *tmpfile(void) |
return
如果成功,该函数返回一个指向被创建的临时文件的流指针。如果文件未被创建,则返回 NULL
实例代码:
1 |
|
让我们编译并运行上面的程序,它将在 /tmp 文件夹中创建一个临时文件,但是一旦程序退出,临时文件会被自动删除