织梦cms做好的网站怎样上传到服务器,哪个网站免费建站最好,wordpress音乐网站主题,滨州北京网站建设文章目录 前言一、C语言文件I/O复习文件操作#xff1a;打开和关闭文件操作#xff1a;顺序读写文件操作#xff1a;随机读写stdin、stdout、stderr 二、承上启下三、Linux系统的文件I/O系统调用接口介绍open()close()read()write()lseek() Linux文件相关重点 复习C文件IO相… 文章目录 前言一、C语言文件I/O复习文件操作打开和关闭文件操作顺序读写文件操作随机读写stdin、stdout、stderr 二、承上启下三、Linux系统的文件I/O系统调用接口介绍open()close()read()write()lseek() Linux文件相关重点 复习C文件IO相关操作 认识文件相关系统调用接口 认识文件描述符理解重定向 对比fd和FILE理解系统调用和库函数的关系 理解文件系统中inode的概念 认识软硬链接对比区别 认识动态静态库学会结合gcc选项制作动静态库 前言 文件 内容 属性 所以对文件的所有操作被分为 a. 对内容操作 b. 对属性操作 我们要访问一个文件的时候都是要先把这个文件打开的 打开前是普通的磁盘文件 打开后文件被加载到内存中 打开的步骤是由操作系统来做的 一个进程可以打开很多文件所以操作系统运行时被打开的文件是很多的操作系统当然要对这些被打开的文件做管理管理的方式是先描述再组织。因此一个文件要被打开一定要先在内核中形成被打开的文件对象。 本文研究的文件操作的本质是进程 和 内存中被打开被加载的文件 的关系 一、C语言文件I/O复习
文件操作打开和关闭
函数签名描述FILE fopen(const char path, const char* mode)打开文件并返回指向文件的指针int fclose(FILE *stream)关闭文件
模式描述“r”读取打开文件进行输入操作。文件必须存在。“w”写入创建一个空文件进行输出操作。如果同名文件已存在其内容将被丢弃文件被视为新的空文件。“a”追加打开文件进行输出操作将数据追加到文件末尾。输出操作总是在文件末尾写入数据扩展文件大小。重新定位操作fseek、fsetpos、rewind将被忽略。如果文件不存在则创建文件。“r”读取/更新打开文件进行更新操作既可读又可写。文件必须存在。“w”写入/更新创建一个空文件并打开它进行更新操作既可读又可写。如果同名文件已存在其内容将被丢弃文件被视为新的空文件。“a”追加/更新打开文件进行更新操作既可读又可写所有输出操作都在文件末尾写入数据。重新定位操作fseek、fsetpos、rewind影响下一次的输入操作但输出操作将位置移回文件末尾。如果文件不存在则创建文件。 [!Attention] 不带号的模式在文件打开时会对原文件进行擦除覆盖操作具体来说 w模式 如果使用 “w” 模式打开一个文件它会创建一个空文件如果同名文件已存在则会清空该文件的内容。换句话说打开文件时如果文件已经存在原文件的内容将被抹掉。a模式 如果使用 “a” 模式打开一个文件文件指针会移动到文件末尾写入的数据将追加到文件的末尾。如果文件不存在则会创建一个新文件。原文件的内容不会被清空而是保留在文件中。 这是在不带号的写入模式下的行为。要同时进行读写而不清空文件内容可以考虑使用带号的模式如 “r” 或 “a”。 实验一下
#include stdio.hint main()
{FILE *filePointer;// 打开文件filePointer fopen(test.txt, w);if (filePointer NULL) {printf(文件打开失败。\n);return 1;}printf(文件打开成功执行其他文件操作...\n);// 执行其他文件操作...// 关闭文件if (fclose(filePointer) 0) {printf(文件关闭成功。\n);} else {printf(文件关闭失败。\n);}return 0;
}文件操作顺序读写
函数签名描述int fputc(int c, FILE *stream)将一个字符写入文件int fgetc(FILE *stream)从文件中读取一个字符char *fgets(char *s, int size, FILE *stream)从文件中读取一行内容并存储到字符串 s 中int fputs(const char *s, FILE *stream)将字符串 s 写入文件size_t fread(void *ptr, size_t size, size_t count, FILE *stream)从文件中读取二进制数据size_t fwrite(void *ptr, size_t size, size_t count, FILE *stream)向文件中写入二进制数据
#include stdio.hint main() {FILE *filePointer;char ch;// 写入文件filePointer fopen(test.txt, w);if (filePointer NULL) {printf(文件打开失败。\n);return 1;}fputc(A, filePointer);fclose(filePointer);// 读取文件filePointer fopen(test.txt, r);if (filePointer NULL) {printf(文件打开失败。\n);return 1;}ch fgetc(filePointer);printf(读取的字符%c\n, ch);fclose(filePointer);return 0;
}文件操作随机读写
函数签名描述int fseek(FILE *stream, long offset, int whence)设置文件指针偏移量用于定位读写位置long ftell(FILE *stream)返回当前文件指针的位置void rewind(FILE *stream)将文件指针重置到文件开头int feof(FILE *stream)检测是否到达文件末尾
#include stdio.hint main() {FILE *filePointer;// 写入文件filePointer fopen(test.txt, w);if (filePointer NULL) {printf(文件打开失败。\n);return 1;}fputs(Hello, World!, filePointer);fclose(filePointer);// 随机读取文件filePointer fopen(test.txt, r);if (filePointer NULL) {printf(文件打开失败。\n);return 1;}fseek(filePointer, 7, SEEK_SET); // 移动到文件第8个字符的位置char ch fgetc(filePointer);printf(随机读取的字符%c\n, ch);fclose(filePointer);return 0;
}stdin、stdout、stderr
C默认打开的三个输入输出流是stdin、stdout和stderr仔细观察发现这三个流的类型都是FILE*, fopen返回值类型文件指针这三个流通常在程序开始运行时就已经打开并且不需要使用fopen等函数手动打开。它们分别用于标准输入、标准输出和标准错误输出。 stdin标准输入流 stdin代表标准输入流通常与键盘输入相关联。对应的文件指针是FILE* stdin。你可以使用scanf等函数从stdin中读取输入数据。 stdout标准输出流 stdout代表标准输出流通常与屏幕输出相关联。对应的文件指针是FILE* stdout。你可以使用printf等函数将输出写入到stdout中。 stderr标准错误输出流 stderr代表标准错误输出流通常也与屏幕输出相关联。对应的文件指针是FILE* stderr。与stdout相比stderr通常用于输出错误消息以便在程序发生错误时将错误信息与正常输出区分开。
这些标准流的使用使得C程序能够在不同环境中运行而不用关心具体的输入和输出设备。在程序中你可以直接使用这些流而无需显式打开或关闭它们。例如可以通过fprintf将输出写入到文件而不仅仅是屏幕或者通过fscanf从文件而不是键盘读取输入。 二、承上启下
上面的fopen fclose fread fwrite 都是C标准库当中的函数我们称之为库函数libc。而 open close read write lseek 都属于系统提供的接口称之为系统调用接口回忆一下我们讲【Linux】从冯诺依曼体系结构到操作系统 时画的一张图
系统调用接口和库函数的关系就是库函数封装了系统调用接口。 所以可以认为f#系列的函数都是对系统调用的封装方便二次开发。 如何封装 fopen函数在上层为用户申请FILE结构体变量并返回该结构体的地址(FILE*)在底层通过系统接口open打开对应的文件得到文件描述符fd并把fd填充到FILE结构体当中的_fileno变量中至此便完成了文件的打开操作。 三、Linux系统的文件I/O
系统调用接口介绍
先介绍一个小技巧 关于Linux常用的传参方式函数传入标志位的小技巧 C语言常通过一个整形来传递选项但是当选项较多时每一个选项都用一个整形太浪费空间所以有人想出了办法 – 使用一个比特位来传递一个选项这样一个整形就可以传递32种选项大大节省了空间具体案例如下 #include stdio.h#define Print1 1 // 0001
#define Print2 (11) // 0010
#define Print3 (12) // 0100
#define Print4 (13) // 1000void Print(int flags)
{if(flagsPrint1) printf(hello 1\n);if(flagsPrint2) printf(hello 2\n);if(flagsPrint3) printf(hello 3\n);if(flagsPrint4) printf(hello 4\n);
}int main()
{Print(Print1);Print(Print1|Print2);Print(Print1|Print2|Print3);Print(Print3|Print4);Print(Print4);return 0;
}如上我们将宏与比特位对应然后在 Print 函数中编写每一个宏对应的功能之后我们就可以在其他函数中通过调用 Func 函数并传递对应的选项来达到我们想要的效果并且我们可以通过按位或来实现同时传递几个选项。 open() 系统调用open用于打开或创建一个文件。open函数是一个系统调用用于打开文件或创建新文件返回值是一个文件描述符后续的文件操作可以使用这个文件描述符。 pathname: 要打开或创建的目标文件 flags: 打开文件时可以传入多个参数选项用下面的一个或者多个常量进行“按位或”运算构成flags。 常用的flags的可选参数 文件打开方式含义如果指定文件不存在O_RDONLY以只读形式打开出错O_WRONLY以只写形式打开出错O_RDWR以读写形式打开出错O_APPEND向文本文件尾添加数据出错O_CREAT如果文件不存在创建新文件建立一个新的文件O_TRUNC打开文件时清空文件中之前的数据出错
close()
系统调用close关闭一个文件 read() read从文件中读数据 返回值 如果成功则返回读取的字节数(0表示文件结束)并将文件位置提前该字节数。如果这个数字小于请求的字节数则不会报错;例如发生这种情况可能是因为现在实际可用的字节数减少了(可能是因为接近文件末尾或者因为我们正在从管道或终端读取数据)或者因为read()被信号中断。发生错误时返回-1并适当地设置errno。在这种情况下文件位置(如果有的话)是否改变是未指定的。
下面用read.c来测试read()系统调用
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include sys/fcntl.h
#include unistd.h#define FILE_NAME file1.txtint main() {int fd open(FILE_NAME, O_RDONLY);if(fd -1) {perror(open);return 1;}char buf[1024];//C语言字符串以\0结尾所以留一个位置来放置int ret read(fd, buf, sizeof(buf) - 1);//read读到文件末尾返回0while(ret ! 0) {buf[ret] \0;printf(%s, buf);ret read(fd, buf, sizeof(buf) - 1);}close(fd);
}
现象
write()
write向文件中写数据
下面用write.c来测试write()系统调用
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdio.h
#include string.h#define FILE_NAME1 file1.txt //已存在
#define FILE_NAME2 file2.txt //不存在int main() {//以只写形式打开并清空文件中之前的数据int fd1 open(FILE_NAME1, O_WRONLY | O_TRUNC); //创建文件并以只写形式打开并指定文件的默认权限为0666(还受umask的影响)//同时我们可以通过umask接口手动设置当前进程的文件掩码而不使用从父进程继承过来的umaskumask(0000); int fd2 open(FILE_NAME2, O_WRONLY | O_CREAT | O_TRUNC, 0666);//错误处理if(fd1 -1 || fd2 -1) {perror(open);return 1;}const char* buf1 hello file1\n;const char* buf2 hello file2\n;int cnt 5;while(cnt--) {//注意这里strlen求得的长度不用加1因为字符串以\0结尾只是C语言的特性而文件中并不这样规定write(fd1, buf1, strlen(buf1));write(fd2, buf2, strlen(buf2));}close(fd1);close(fd2);
}
现象 [!Attention] 上面的文件操作的三个细节 如果在向文件中写入数据时没有指定O_TRUNC选项而是直接写入数据新数据会从文件的当前位置开始写入而不会影响文件中原有的数据。如果新数据的长度小于文件当前的大小那么文件的尾部会保留原有的数据就比如先写入五行 hello world再写入五行 hello 创建 file2.txt 时我们通过 umask 系统调用将 umask 由默认的 0002 设置为了 0000(第一个0代表八进制)然后将 open() 系统调用的最后一个参数 mode 设置为 0666所以 file2 的最终权限为 文件的默认权限 ~umask 即 0666 ~0000 亦即 0666 在C语言中字符串是以\0空字符或Null字符结尾的字符数组。但是在文件中存储字符串时并不要求在文件中以\0结尾。文件系统仅是按照写入的字节数来存储数据而不关心字符串的结尾字符。因此当你使用write函数将字符串写入文件时不需要把字符串结尾的\0字符写入文件只需写入字符串本身即可。 如果在写入文件时将\0字符写入 可能会导致一些乱码的问题 所以说write函数的第三个参数 count 应该设置为 strlen(str)表示写入字符串的长度而不包括字符串结尾的\0字符。 lseek()
lseek“lseek代表long seek”是Linux系统调用之一用于在文件中移动文件指针的位置。它是对文件进行随机访问的关键系统调用之一。lseek可以用于设置文件偏移量以便在文件中执行读取或写入操作。
函数原型如下 fd 是文件描述符表示要操作的文件。offset 是文件偏移量可以为正数、负数或零用于指定相对于whence参数的偏移位置。whence 指定了偏移量的基准位置可以是以下值之一 SEEK_SET相对于文件的起始位置进行偏移。SEEK_CUR相对于当前文件指针的位置进行偏移。SEEK_END相对于文件的末尾位置进行偏移。
lseek函数的返回值是新的文件偏移量如果调用出现错误则返回 -1。
使用示例
#include fcntl.h
#include stdio.h
#include unistd.hint main() {int fd open(example.txt, O_RDWR);if (fd -1) {perror(open);return 1;}// 使用 lseek 将文件指针移动到文件末尾off_t end_position lseek(fd, 0, SEEK_END);if (end_position -1) {perror(lseek);close(fd);return 1;}printf(当前文件大小%lld 字节\n, (long long)end_position);// 使用 lseek 将文件指针移动到文件开头off_t start_position lseek(fd, 0, SEEK_SET);if (start_position -1) {perror(lseek);close(fd);return 1;}printf(文件指针已重置到文件开头\n);close(fd);return 0;
}在上述示例中lseek函数用于将文件指针移动到文件的末尾获取文件的大小然后将文件指针重新设置到文件开头。这演示了lseek在文件中移动文件指针的基本用法。