第九章 I/O库函数
一.知识点归纳
(一)I/O库函数与系统调用
- 系统调用函数:open()、read()、write()、lseek()、close()
- I/O库函数:fopen()、fread()、fwrite()、fseek()、fclose()
二者区别:
- 在系统调用程序中,文件描述符fd是一个整数。在库I/O程序中,fp是一个文件流指针。
- 系统调用open()打开一个文件进行读取,并返回一个整数文件描述符fd,如果open()失败,则返回-1。I/O库函数fopen()返回一个FILE结构体指针,如果fopen()失败,则返回NULL。
- 系统调用程序使用while循环读取/写入文件内容。在每个迭代中,它发出read()系统调用,将最多4KB的字符读入buf[]。然后,它将各字符从buf[]写到文件描述符1中,这是该进程的标准输出。正如前文所指出的,使用系统调用一次写入一个字节非常低效。相反,I/O库程序仅仅使用fgetc(fp)从文件流中获取字符,通过putchar()输出字符,直至文件结束符。
(二)I/O库函数的算法
1.fread算法
使用 fread() 对文件进行读操作,函数原型如下:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数和返回值含义:
参数 | 含义 |
---|---|
ptr | fread()将读取到的数据存放在参数 ptr 指向的缓冲区中 |
size | fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节 |
nmemb | 参数 nmemb 指定了读取数据项的个数 |
stream | FILE 指针 |
返回值 | 调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数size 等于 1);如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,那么到底发生了错误还是到达了文件末尾,fread()不能区分文件结尾和错误,究竟是哪一种情况,此时可以使用 ferror()或 feof()函数来判断 |
2.fwrite算法
fwrite() 库函数进行写操作,函数原型如下:
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数和返回值含义:
参数 | 含义 |
---|---|
ptr | 将参数 ptr 指向的缓冲区中的数据写入到文件中 |
size | 参数 size 指定了每个数据项的字节大小,与 fread()函数的 size 参数意义相同 |
nmemb | 参数 nmemb 指定了写入的数据项个数,与 fread()函数的 nmemb 参数意义相同 |
stream | FILE 指针 |
返回值 | 调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数size 等于 1);如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0) |
3.fclose算法
打开的文件通过 fclose() 进行关闭,函数原型如下:
#include <stdio.h>
int fclose(FILE *stream);
4.fopen算法
使用fopen() 打开或创建文件,函数原型如下:
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
参数和返回值含义:
参数 | 含义 |
---|---|
path | 参数 path 指向文件路径,可以是绝对路径、也可以是相对路径 |
mode | 参数 mode 指定了对该文件的读写权限,是一个字符串,稍后介绍 |
返回值 | 调用成功返回一个指向 FILE 类型对象的指针(FILE * ),该指针与打开或创建的文件相关联,后续的标准 I/O 操作将围绕 FILE 指针进行。如果失败则返回 NULL,并设置 errno 以指示错误原因 |
参数 mode 补充: | |
mode | 说明 |
- | - |
r | 以只读方式打开文件 |
r+ | 以可读可写方式打开文件 |
w | 以只写方式打开文件,如果参数path指定的文件存在,将文件长度截断为0,如果指定文件不存在 则创建该文件 |
w+ | 以可读可写方式打开文件,如果参数path指定的文件存在,将文件长度截断为0;如果指定文件 不存在则创建该文件 |
a | 以只写方式打开文件,打开以进行追加内容(在文件末尾写入),如果文件不存在则创建该文件 O_WRONLY |
a+ | 以可读可写方式打开文件,以追加方式写入 (在文件末尾写入),如果文件不存在则创建该文件 |
5.fseek算法和ftell算法
- 库函数 fseek() 用于设置文件读写位置偏移量,和 lseek() 功能相同,但 lseek() 用于文件 I/O,而库函数 fseek() 则用于标准 I/O,函数原型:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
参数和返回值含义:
参数 | 含义 |
---|---|
stream | FILE 流指针 |
offset | 与 lseek() 函数的 offset 参数意义相同,相对偏移位置的偏移值 |
whence | 与 lseek()函数的 whence 参数意义相同,偏移位置 |
返回值 | 成功返回 0;发生错误将返回-1,并且会设置 errno 以指示错误原因;与 lseek()函数的返回值意义不同,这里要注意 |
- 库函数 ftell() 可用于获取文件当前的读写位置偏移量,函数原型如下:
#include <stdio.h>
long ftell(FILE *stream);
参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回-1,并会设置 errno 以指示错误原因
(三)字符模式I/O
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 fputc(int c,FILE *fp); // put a char to fp
(四)行模式I/O
char *fgets(char *buf,int size,FILE *fp): 从fp中读取最多一行(以\n结尾)的字符。
int fputs(char *buf,FILE *fp): 将buf中的一行写入fp中。
(五)格式化I/O
-
格式化输入
scanf(char *FMT,&item); // from stdin
fscanf(fp,char *FMT,&items); // from file stream
-
格式化输出
printf(char *FMT,item); // 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 memory
二.问题与解决思路
1.argc、argv的具体含义是什么?
argc和argv参数在用命令行编译程序时有用。main( int argc, char* argv[], char** env ) 中:
- 第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。
- 第二个参数,char*型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
- argv[0]指向程序运行的全路径名
- argv[1]指向在DOS命令行中执行程序名后的第一个字符串
- argv[2]指向执行程序名后的第二个字符串
- argv[3]指向执行程序名后的第三个字符串
- argv[argc]为NULL
- 第三个参数,char** 型的env,为字符串数组。env[]的每一个元素都包含ENVVAR=value形式的字符串,其中ENVVAR为环境变量,value为其对应的值。平时使用到的比较少。
2.open()打开时flags传参O_RDONLY、O_WRONLY等的作用含义?
头文件:#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
定义函数:
int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
函数说明:
-
参数 pathname 指向欲打开的文件路径字符串. 下列是参数flags 所能使用的旗标:
- O_RDONLY 以只读方式打开文件
- O_WRONLY 以只写方式打开文件
- O_RDWR 以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合.
- O_CREAT 若欲打开的文件不存在则自动建立该文件.
- O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
- O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机.
- O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
- O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
- O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
- O_NDELAY 同O_NONBLOCK.
- O_SYNC 以同步的方式打开文件.
- O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
- O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。 注:此为Linux2. 2 以后特有的旗标, 以避免一些系统安全问题.
-
参数mode 则有下列数种组合, 只有在建立新文件时才会生效, 此外真正建文件时的权限会受到umask 值所影响, 因此该文件权限应该为 (mode-umaks).
- S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.
- S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.
- S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.
- S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.
- S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.
- S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.
- S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.
- S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.
- S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.
- S_IROTH 00004 权限, 代表其他用户具有可读的权限
- S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.
- S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.
返回值:若所有欲核查的权限都通过了检查则返回0 值, 表示成功, 只要有一个权限被禁止则返回-1.
三.苏格拉底挑战
用户
我在学习库I/O函数的算法知识点,请你以苏格拉底的方式对我进行提问
AI
当然,我会以苏格拉底的方式提问你关于库I/O函数的算法知识点。请回答下面的问题:
- 为什么在进行文件写入操作时,使用 fwrite() 函数比使用 fputs() 函数更适合处理二进制数据?
- 如何使用 fseek() 函数在文件中定位到指定的位置?
- 在处理大文件时,如何确定合适的缓冲区大小?缓冲区大小的选择与文件的大小有关吗?
- 在进行文件操作时,如何处理可能出现的错误?你可以举例说明一些常见的错误处理方法。
请思考并回答这些问题,我将根据你的回答继续引导你的学习。
用户
- fwrite() 函数比 fputs() 函数更适合处理二进制数据的原因是 fwrite() 函数可以按照指定的大小将数据块写入文件,而 fputs() 函数是按照字符串进行写入。二进制数据可能包含任意的字节值,而字符串通常以 null 字符('\0')作为结束标志,因此在处理二进制数据时,使用 fwrite() 函数可以更灵活地写入指定大小的数据块。
- 使用 fseek() 函数可以在文件中定位到指定的位置。fseek() 函数的原型为:int fseek(FILE *stream, long int offset, int origin)。其中,stream 是文件指针,offset 是偏移量,origin 是偏移起点。常见的偏移起点有以下三种:
SEEK_SET:从文件开头开始计算偏移量。
SEEK_CUR:从当前位置开始计算偏移量。
SEEK_END:从文件末尾开始计算偏移量。
例如,要将文件指针定位到文件开头,可以使用 fseek(file, 0, SEEK_SET)。 - 在处理大文件时,确定合适的缓冲区大小需要综合考虑多个因素。一般来说,较大的缓冲区大小可以减少磁盘访问次数,提高读写效率。但是,缓冲区大小的选择不仅与文件的大小有关,还与系统内存大小、磁盘读写速度等因素有关。可以通过实验和性能测试来确定合适的缓冲区大小,找到一个平衡点,既能满足性能需求,又不会占用过多的内存资源。
- 在进行文件操作时,可能会出现各种错误,如文件不存在、权限不足、磁盘空间不足等。常见的错误处理方法包括:
检查返回值:检查库I/O函数的返回值,如果返回值表示操作失败,可以根据具体情况采取相应的错误处理措施。
错误码:一些库I/O函数会返回特定的错误码,可以根据错误码来判断和处理错误。
异常处理:使用异常处理机制来捕获和处理可能发生的异常,以保证程序的稳定性。
例如,在打开文件时,可以检查 fopen() 函数的返回值是否为 NULL,如果为 NULL,则表示打开文件失败,可以输出错误信息或者采取其他处理措施。
AI
非常好的回答!你对库I/O函数的算法知识点有了很好的理解。
用户
针对库I/O函数的算法知识,我了解了吗
AI
是的,你对库I/O函数的算法知识有了很好的理解。你正确地回答了关于不同函数之间的区别、适用场景以及错误处理方法等问题。你展示了对库I/O函数的理解和应用能力。
如果你还有其他问题或者需要进一步讨论其他主题,请随时告诉我。我将很乐意继续帮助你。