泉州网站制作专业,友情链接的作用大不大,建筑网片厂家,咸宁网站开发C语言中文件的相关操作
文件的打开 使用文件的打开函数需要引入这个头文件#xff1a;#include fcntl.h open函数 int open(char const *pathname, int flags, mode_t mode) 功能#xff1a;打开已有的文件或者创建新文件参数 pathname#xff1a;文件路径名…C语言中文件的相关操作
文件的打开 使用文件的打开函数需要引入这个头文件#include fcntl.h open函数 int open(char const *pathname, int flags, mode_t mode) 功能打开已有的文件或者创建新文件参数 pathname文件路径名可以是相对路径或绝对路径flags打开文件的标志状态标志多选使用|常用的有以下几种 O_RDONLY只读O_WRONLY只写O_RDWR读写O_APPEND追加O_CREAT不存在即创建已存在即打开O_EXCL不存在即创建已存在即报错O_TRUNC不存在即报错一般配合O_CREAT使用已存在即清空O_RDONLY O_WRONLYO_RDWR三者只能选择一个 mode权限模式格式例如0777、0755 返回值反回非负整数作为文件描述符。如果返回-1表示打开文件失败 使用示例 #include stdio.h // 使用perror
#include fcntl.h // 使用openint main(void)
{// 打开文件不存在就创建int fd open(./test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0755);if(fd -1){perror(open);return -1;}// 操作文件// 关闭文件return 0;
}文件的关闭 使用文件的关闭函数需要引入这个头文件#include unistd.h close函数 int close(int fd); 功能关闭处于打开状态的文件描述符参数fd表示的是处于打开状态的文件描述符返回值成功返回0失败返回-1 使用示例 #include stdio.h // 使用perror
#include fcntl.h // 使用open
#include unistd.h // 使用closeint main(void)
{// 打开文件不存在就创建int fd open(./test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0755);if(fd -1){perror(open);return -1;}// 操作文件// 关闭文件int ret close(fd);if(ret -1){perror(close);return -1;}return 0;
}文件打开和关闭在内核中的结构 多次打开同一个文件无论是在同一个进程中还是在不同的进程中都会在系统内核中产生一个v节点 每次打开文件都会产生一个新的文件表项各自维护各自的文件状态标志和当前文件偏移 多个进程打开同一个文件其实是产生了多个文件表项而v节点其实只有一个 以下是使用一个进程打开一个文件的示例图 下面是使用多个进程打开一个文件的示例图
文件描述符 为了便于管理在系统中运行的各个进程内核会维护一张存有各进程信息的列表称为进程表。系统中的每个进程在进程表中都占有一个表项。每个进程表项都包含了针对特定进程的描述信息如PID、UID、GID其中也包含了一个称为文件描述符表的数据结构。 文件描述符的每个表项都至少包含两个数据项–文件描述符表中和文件表项指针所谓文件描述符其实就是文件描述符表项在文件描述符表中从0开始的下标open函数的返回值就是文件描述符表项在文件描述符表中的下标 文件描述符表中前三个都是默认的在unistd.h头文件中被定义 #define STDIN_FILENO 0 标准输入#define STDOUT_FILENO 1 标准输出#define STDERR_FILENO 2 标准错误 以下是示例图 下面是根据文件描述表的前三个默认被定义的特性写的一个示例 #include stdio.h
#include fcntl.h
#include unistd.hint main(void)
{// 如果这里直接使用open那返回的文件描述符表项一般都是3// 所以我这里做了一些操作让open返回的文件描述符表项为0close(1); // 关闭1标准输出int fd open(./stdout.txt, O_WRONLY | O_CREAT | O_TRUNC, 0755);if(fd -1){perror(open);return -1;}printf(fd %d\n, fd);// 这里如果关闭了fd会出现写不进去// if(close(fd) -1)// {// perror(close);// return -1;// }return 0;
}上面一个例子在写的时候发现了一些问题目前还不知道是啥原因不能关闭文件关闭了就会发现写不进去下面是我用dup2函数写的 #include stdio.h
#include fcntl.h
#include unistd.hint main(void) {int fd open(./stdout.txt, O_WRONLY | O_CREAT | O_TRUNC, 0755);if (fd -1) {perror(open);return -1;}// 使用dup2函数将新的文件描述符复制到文件描述符1if (dup2(fd, STDOUT_FILENO) -1) {perror(dup2);return -1;}printf(输出重定向到文件\n);printf(此时fd%d\n, fd);if (close(fd) -1) {perror(close);return -1;}return 0;
}文件的写入 使用文件的写入函数需要引入这个头文件#include unistd.h write函数 ssize_t wirte(int fd, void const *buf, size_t count); 功能向指定的文件写入数据参数 fd文件描述符即open函数的返回值buf内存缓冲区即要写入的数据count期望写入的字节数 返回值成功返回实际写入的字节数失败返回-1 示例 #include stdio.h // perror
#include fcntl.h // open
#include unistd.h // close wirte
#include string.h // strlenint main(void)
{// 1. 打开文件int fd open(./test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0755);if(fd -1){perror(open);return -1;}// 2. 写入数据char *buf 哈哈;ssize_t size write(fd, buf, strlen(buf));if(size -1){perror(write);return -1;}printf(实际写入了%ld个字节\n, size);// 3. 关闭文件if(close(fd) -1){perror(close);return -1;}return 0;
}文件的读取 使用文件的读取函数需要引入这个头文件#include unistd.h read函数 ssize_t read(int fd, void *buf, size_t count); 功能从指定的文件中读取数据参数 fd文件描述符buf内存缓冲区存读取到的数据count期望读取的字节数 返回值成功返回实际读取的字节数失败返回-1 示例 #include stdio.h // perror
#include fcntl.h // open
#include unistd.h // close readint main(void)
{// 1. 打开文件这里使用的是 int open(const char *pathname, int flags);直接读取的int fd open(./test.txt, O_RDONLY);// 2. 读取数据char buf[16] {};ssize_t size read(fd, buf, sizeof(buf));if(size -1){perror(read);return -1;}printf(读取的字节数是%ld,读取到内容是 %s\n, size, buf);// 3. 关闭文件if(close(fd) -1){perror(close);return-1;}return 0;
}补充部分
在 Unix-like 操作系统中man 命令后面的数字参数用于指定手册页面的节section。不同的节对应于不同类型的手册页面每个节有其特定的内容和用途。以下是常见的 man 命令数字参数及其对应的节
man 1用户命令User Commands。该节包含系统中可用的一般用户命令和可执行程序的手册页面。例如man 1 ls 将显示与列表命令 ls 相关的手册页面。man 2系统调用System Calls。该节包含操作系统提供的内核级别函数和系统调用接口的手册页面。这些函数和接口用于访问低级别的操作系统功能。例如man 2 open 将显示与文件系统调用 open 相关的手册页面。man 3C 库函数Library Functions。该节包含标准 C 库函数和其他库函数的手册页面。它提供了函数的接口、参数和返回值等详细信息。例如man 3 printf 将显示与打印函数 printf 相关的手册页面。其他节除了上述常见的节之外还存在其他节用于特定目的或特定类型的手册页面。例如man 5 用于文件格式和配置文件的手册页面man 7 用于杂项的手册页面。
因此根据所选择的数字参数man 命令可以用来查看不同类型的手册页面从用户命令、系统调用到库函数等。可以根据需要选择适当的数字参数来浏览相关的手册页面以获取相关命令、函数或系统的详细信息和用法说明。 文件读写的位置 使用lseek函数可以改变文件读写位置使用它需要引用unistd.h头文件 lseek函数 off_t lseek(int fd, off_t offset, int whence); 功能 调整文件读写位置参数 fd文件描述符offset文件读写位置偏移字节数whenceoffset参数的偏移起点 SEEK_SET文件头SEEK_CUR当前位置最后被读写字节下一个字节SEEK_END文件尾 返回值成功返回文件读写位置失败返回0 示意图 示例代码 #include stdio.h
#include fcntl.h
#include unistd.h
#include string.hint main(void)
{// 打开文件int fd open(./test.txt, O_RDWR | O_CREAT | O_TRUNC, 0644);if(fd -1){perror(open);return -1;}// 写入数据char *w_data aaaaaaa;if(write(fd, w_data, strlen(w_data)) -1){perror(write);return -1;}// 跳转文件读写位置到开头if(lseek(fd, 0, SEEK_SET) -1){perror(lseek);return -1;}// 写入数据这里会将之前的数据覆盖并不会将原来的数据自动往后移char *w_data2 abcdefghij;if(write(fd, w_data2, strlen(w_data2)) -1){perror(write);return -1;}// 调整文件读写位置 3 SEEK_SETif(lseek(fd, 3, SEEK_SET) -1){perror(lseek);return -1;}// 读取文件数据char r_data1[100] {};if(read(fd, r_data1, sizeof(r_data1)) -1){perror(read1);return -1;}printf(第一次读取数据: %s\n, r_data1);// 调整文件读写位置 -5 SEEK_CUR如果这里填 4 SEEK_CUR因为上一个读取操作已经将指针移到最后了if(lseek(fd, -5, SEEK_CUR) -1){perror(lseek);return -1;}// 读取文件数据char r_data2[100] {};if(read(fd, r_data2, sizeof(r_data2)) -1){perror(read1);return -1;}printf(第二次读取数据: %s\n, r_data2);// 调整文件读写位置 -2 SEEK_ENDif(lseek(fd, -2, SEEK_END) -1){perror(lseek);return -1;}// 读取数据char r_data3[100] {};if(read(fd, r_data3, sizeof(r_data3)) -1){perror(read);return -1;}printf(第三次读取数据: %s\n, r_data3);// 关闭文件if(close(fd) -1){perror(close);return -1;}return 0;
}文件描述符的复制
dup函数 dup函数可以复制文件描述项可以将指定的文件描述项复制到最小的空闲项中使用这个函数需要引入unistd.h头文件 dup函数 int dup(int oldfd); 功能复制文件描述符表的特定条目到最小可用项参数 oldfd源文件描述符 返回值成功返回模板文件描述符失败返回-1 示例图 示例代码 #include stdio.h
#include fcntl.h
#include string.h
#include unistd.hint main(void)
{// 打开文件int fd open(./test.txt, O_RDWR | O_CREAT | O_TRUNC, 0644);if(fd -1){perror(open);return -1;}// 写入数据char *w_data abcdefg;if(write(fd, w_data, strlen(w_data)) -1){perror(write);return -1;}// 使用dup赋值int newfd dup(fd);if(newfd -1){perror(dup);return -1;}// 移动fd的文件读写位置到dif(lseek(fd, 3, SEEK_SET) -1){perror(lseek);return -1;}// 读取newfd的数据看是否随着fd的读写位置而改变char r_data[100] {};if(read(fd, r_data, sizeof(r_data)) -1){perror(read);return -1;}printf(读取newfd的数据: %s\n, r_data); // defg// 关闭文件if(close(newfd) -1){perror(newfd close);return -1;}if(close(fd) -1){perror(fd close);return -1;}return 0;
}dup2函数 dup2函数可以复制文件描述项可以将指定的文件描述项复制到指定的空闲项中使用这个函数需要引入unistd.h头文件 dup2函数 int dup2(int oldfd, int newfd); 功能复制文件描述符表中特定条目到指定项参数 oldfd源文件描述符newfd目标文件描述符 返回值成功返回目标文件描述符newfd失败返回-1使用前一定要确保newfd是空闲的 示例代码 #include stdio.h
#include fcntl.h
#include string.h
#include unistd.hint main(void)
{// 打开文件int fd open(./test.txt, O_RDWR | O_CREAT | O_TRUNC, 0644);if(fd -1){perror(open);return -1;}// 复制文件描述项int newfd dup2(fd, STDOUT_FILENO);if(newfd -1){perror(dup2);return -1;}// 测试是否复制成功printf(test,hahah);// 关闭文件复制的不需要手动关闭,手动关闭会发现上一步printf写不进去if(close(fd) -1){perror(close);return -1;}return 0;
}访问测试 想要查看操作文件的权限可以使用access函数使用这个函数需要引入unistd.h头文件 access函数 int access(char const *pathname, int mode); 功能判断当前进程是否可以对某个给定的文件执行某种访问。参数 pathname文件路径mode被测试的权限 R_OK是否可读W_OK是否可写X_OK是否可执行F_OK是否存在 返回值成功返回0失败返回-1 示例代码 #include stdio.h
#include unistd.hint main(int argc, char *argv[])
{// 判断是否输入文件名if(argc 2){fprintf(stderr, 用法: %s filename/filepath\n, argv[0]);return -1;}// 判断文件是否存在if(access(argv[1], F_OK) -1){printf(%s is no such file!\n, argv[1]);return -1;}printf(%s: , argv[1]);// 判断文件是否可读if(access(argv[1], R_OK) 0)printf(read );// 判断文件是否可写if(access(argv[1], W_OK) 0)printf(write );// 判断文件是否可执行if(access(argv[1], X_OK) 0)printf(execute);printf(\n);return 0;
}修改文件大小 以下两个函数是用于修改文件大小的函数一般在下载的时候都会用到这类函数使用这两个函数需要引入unistd.h头文件 truncate函数 int truncate(char const *path, off_t length); 功能修改指定文件的大小参数 path文件路径length文件大小 返回值成功返回0失败返回-1 示例代码这里展示的是截断的效果会从尾部往前开始截短 #include stdio.h
#include string.h
#include fcntl.h
#include unistd.hint main(void)
{// 打开文件int fd open(./test.txt, O_RDWR | O_CREAT | O_TRUNC, 0644);if(fd -1){perror(open);return -1;}// 写入数据char *data hello bhlu; // 10字节if(write(fd, data, strlen(data)) -1){perror(write);return -1;}// 将读写位置移到最后看返回的位置int pos1 lseek(fd, 0, SEEK_END);if(pos1 -1){perror(lseek);return -1;}printf(初始状态: 文件字节长度: %d\n, pos1);// 截短文件为4字节if(truncate(./test.txt, 4) -1){perror(truncate);return -1;}printf(截短成功\n);// 将读写位置移到最后看返回的位置int pos2 lseek(fd, 0, SEEK_END);if(pos2 -1){perror(lseek);return -1;}printf(截短后: 文件字节长度: %d\n, pos2);// 关闭文件if(close(fd) -1){perror(close);return -1;}return 0;
}/*
初始状态: 文件字节长度: 10
截短成功
截短后: 文件字节长度: 4此时文件: hell
*/ftruncate函数 int ftruncate(int fd, off_t length); 功能修改指定文件的大小参数 fd文件描述符length文件大小 返回值成功返回0失败返回-1 示例代码这里展示的加长的效果加长之后会在尾部空字符填充 #include stdio.h
#include string.h
#include fcntl.h
#include unistd.hint main(void)
{// 打开文件int fd open(./test.txt, O_RDWR | O_CREAT | O_TRUNC, 0644);if(fd -1){perror(open);return -1;}// 写入数据char *data hello; // 5字节if(write(fd, data, strlen(data)) -1){perror(write);return -1;}// 将读写位置移到最后看返回的位置int pos1 lseek(fd, 0, SEEK_END);if(pos1 -1){perror(lseek);return -1;}printf(初始状态: 文件字节长度: %d\n, pos1);// 加长文件为10个字节if(ftruncate(fd, 10) -1){perror(truncate);return -1;}printf(加长成功\n);// 将读写位置移到最后看返回的位置int pos2 lseek(fd, 0, SEEK_END);if(pos2 -1){perror(lseek);return -1;}printf(加长后: 文件字节长度: %d\n, pos2);// 关闭文件if(close(fd) -1){perror(close);return -1;}return 0;
}/*
初始状态: 文件字节长度: 5
加长成功
加长后: 文件字节长度: 10此时文件: hello^^^^^ ^就是ASCII码中的空字符
*/文件锁
当一个文件同时被多个进程进行写操作的时候就会出现造成数据错乱以下是一个每秒往文件里写一个字符的示例代码。
#include stdio.h
#include string.h
#include fcntl.h
#include unistd.hint main(int argc, char *argv[])
{if(argc 2){fprintf(stderr, 用法: %s string\n, argv[0]);return -1;}// 打开文件int fd open(./test.txt, O_WRONLY | O_CREAT | O_APPEND, 0644);if(fd -1){perror(open);return -1;}// 写入数据一秒写一个for(int i 0; i strlen(argv[1]); i){if(write(fd, argv[1][i], sizeof(argv[1][i])) -1){perror(write);return -1;}sleep(1);}// 关闭文件if(close(fd) -1){perror(close);return -1;}return 0;
}下面使用使用两个进程同时执行这个代码./write haha ./write bhlu 可以看到写入的数据是错乱的所以才需要使用文件锁来保证写入数据的正确性。
使用文件锁需要用到fcntl函数使用这个函数需要引入fcntl.h头文件 fcntl函数 int fcntl(int fd, F_SETLK/F_SETLKW, struct flock *lock); 功能加解锁 参数 F_SETLK非阻塞模式加锁F_SETLKW阻塞模式加锁 lock对文件要加的锁 struct flock {short l_type; // 锁类型F_RDLCK/F_WRLCK/F_UNLCKshort l_whence; // 锁区偏移起点: SEEK_SET/SEEK_CUR/SEEK_ENDoff_t l_start; // 锁区偏移字节数off_t l_len; // 锁区字节数pid_t l_pid; // 加锁进程的PID-1表示自动设置
}返回值成功返回0.失败返回-1 示例代码 #include stdio.h
#include string.h
#include fcntl.h
#include unistd.hint main(int argc, char *argv[])
{if(argc 2){fprintf(stderr, 用法: %s string\n, argv[0]);return -1;}// 打开文件int fd open(./test.txt, O_WRONLY | O_CREAT | O_APPEND, 0644);if(fd -1){perror(open);return -1;}// 创建锁struct flock lock;lock.l_type F_WRLCK; // 写锁lock.l_whence SEEK_SET; // 头部开始lock.l_start 0;lock.l_len 0; // 锁到文件尾lock.l_pid -1;// 阻塞加锁if(fcntl(fd, F_SETLKW, lock) -1){perror(fcntl);return -1;}// 写入数据for(int i 0; i strlen(argv[1]); i){if(write(fd, argv[1][i], sizeof(argv[1][i])) -1){perror(write);return -1;}sleep(1);}// 解锁struct flock unlock;unlock.l_type F_UNLCK; // 解锁unlock.l_whence SEEK_SET;unlock.l_start 0;unlock.l_len 0;unlock.l_pid -1;if(fcntl(fd, F_SETLKW, unlock) -1){perror(fcntl);return -1;}// 关闭文件if(close(fd) -1){perror(close);return -1;}return 0;
}使用阻塞加锁的展示图 非阻塞加锁部分代码 // 非阻塞加锁
while(fcntl(fd, F_SETLK, lock) -1)
{if(errno EACCES || errno EAGAIN){printf(文件被锁定稍等一下\n);sleep(1);}else {perror(fcntl);return -1;}
}// 非阻塞解锁
if(fcntl(fd, F_SETLK, unlock) -1)
{perror(fcntl);return -1;
}非阻塞加锁效果
文件锁的内核结构
每次对给定文件的特定区域加锁都会通过fcntl函数向系统内核传递flock结构体该结构体中包含了锁的一些信息具体可以参考上面的函数介绍。系统内核收集到所有进程所加的各种锁就会将flock结构体中的信息以链表的形式形成一张锁表锁表的起始地址就保存在该文件的v节点中任何一个进程通过fcntl函数对文件加锁系统内核都要遍历这张表当出现已经存在冲突锁的情况下会出现阻塞或报错。解锁其实就是调整或删除锁表中的相应节点 文件的元数据 可以通过以下三个函数获取文件的元数据使用这三个函数都必须引用sys/stat.h头文件 int stat(char const *path, struct stat *buf); int fstat(int fd, struct stat *buf); int lstat(char const *path, struct stat *buf); lstat函数与另外两个函数的区别在于它不跟踪符号链接假设有一个文件a现在有一个b软连接指向a它返回的是b的信息前面两个都会返回a的信息。 功能三个函数功能都差不多从i节点中提取文件的元数据即文件的属性信息 参数 path文件路径 buf文件元数据结构 struct stat {dev_t st_dev; // 设备IDino_t st_ino; // i节点号mode_t st_mode; // 文件的类型和权限nlink_t st_nlink; // 硬链接数uid_t st_uid; // 拥有者用户idgid_t st_git; // 拥有者组iddev_t st_rdev; // 特殊设备IDoff_t st_size; // 总字节数blksize_t st_blksize; // I/O块字节数blkcnt_t st_blocks; // 存储块数time_t st_atime; // 最后访问时间time_t st_mtime; // 最后修改时间time_t st_ctime; // 最后状态改变时间}/*其中mode_t类型其原始类型在32位系统中被定义unsigned int但目前只有低16位有意义即B0-B15B15 - B12文件类型B11 - B9设置用户ID设置组ID粘滞B8 - B6属主权限B5 - B3数组权限B2 - B0其他用户权限*/fd文件描述符 返回值成功返回0失败返回-1 示例代码 // 输出文件的元数据
#include stdio.h
#include string.h
#include sys/stat.h
#include time.h// 转类型和权限
char *f_mode(mode_t m)
{static char s[11];if (S_ISDIR(m)){strcpy(s, d); // 目录}else if(S_ISSOCK(m)){strcpy(s, s); // 本地套接字 }else if(S_ISCHR(m)){strcpy(s, c); // 字符设备}else if(S_ISBLK(m)){strcpy(s, b); // 块设备}else if(S_ISLNK(m)){strcpy(s, l); // 符号链接}else if(S_ISFIFO(m)){strcpy(s, p); // 有名管道}else{strcpy(s, -);}// 属主 属组 其他用户 权限strcat(s, m S_IRUSR ? r : -);strcat(s, m S_IWUSR ? w : -);strcat(s, m S_IXUSR ? x : -);strcat(s, m S_IRGRP ? r : -);strcat(s, m S_IWGRP ? w : -);strcat(s, m S_IXGRP ? x : -);strcat(s, m S_IROTH ? r : -);strcat(s, m S_IWOTH ? w : -);strcat(s, m S_IXOTH ? x : -);return s;
}// 转换时间
char *f_time(time_t t)
{static char time[20];struct tm *l localtime(t);sprintf(time, %04d-%02d-%02d %02d:%02d:%02d,l-tm_year 1900, l-tm_mon 1, l-tm_mday, l-tm_hour, l-tm_min, l-tm_sec);return time;
}int main(int argc, char *argv[])
{if(argc 2){fprintf(stderr, 用法: %s 文件名\n, argv[0]);return -1;}// 获取文件的元数据struct stat s;if(stat(argv[1], s) -1){perror(stat);return -1;}printf(设备ID: %lu\n, s.st_dev);printf(i节点号: %ld\n, s.st_ino);printf(硬链接数: %lu\n, s.st_nlink);printf(用户ID: %u\n, s.st_uid);printf(组ID: %u\n, s.st_gid);printf(特殊设备ID: %lu\n, s.st_rdev);printf(总字节数: %ld\n, s.st_size);printf(IO块字节数: %ld\n, s.st_size);printf(存储块数: %ld\n, s.st_blksize);printf(文件类型和权限: %s\n, f_mode(s.st_mode));printf(最后访问时间: %s\n, f_time(s.st_atime));printf(最后修改时间: %s\n, f_time(s.st_mtime));printf(最后状态修改时间: %s\n, f_time(s.st_ctime));return 0;
}内存映射文件
之前已经介绍过内存映射的建立与解除以下是使用内存映射文件的例子
// 通过映射打开文件并写入数据
#include stdio.h
#include string.h
#include sys/mman.h
#include fcntl.h
#include unistd.hint main(void)
{// 打开文件int fd open(./mmap.txt, O_RDWR | O_CREAT | O_TRUNC);if(fd -1){perror(open);return -1;}// 修改文件大小if(ftruncate(fd, 4096) -1){perror(ftruncate);return -1;}// 获取文件大小int f_len lseek(fd, 0, SEEK_END);if(f_len -1){perror(lseek);return -1;}// 建立文件映射char *start mmap(NULL, f_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(start MAP_FAILED){perror(mmap);return -1;}// 写入数据strcpy(start, haha\n);// 解除映射if(munmap(start, f_len) -1){perror(munmap);return -1;}// 关闭文件描述符close(fd);return 0;
}