文件基础
概念:
一组相关数据的有序集合
文件类型
不同操作系统支持的文件类型是不相同的,这里列出的是linux
- 常规文件 r
- 目录文件 d
- 字符设备文件 c
- 块设备文件 b
- 管道文件 p
- 套接字文件 s
- 符号链接文件 l
标准I/O
由ANSI C标准定义
主流操作系统上都实现了C库
特点: 标准I/O通过缓冲机制减少系统调用,实现更高的效率
FILE
标准IO用一个结构体类型来存放打开文件的相关信息
标准IO的所有操作都是围绕FILE来进行的
流(stream)
FILE又被称为流(stream)又被分为文本流、二进制流两种
windows:
二进制流:换行符 -- ' '
文本流:换行符 -- ' '' '
linux
换行符:' '
流的缓冲类型
全缓冲:
当流的缓冲区无数据或空间时才执行实际I/O操作
行缓冲:
当在输入和输出中遇到换行符时,才执行I/O操作
当流和一个终端关联时,典型的行缓冲
无缓冲:
数据直接写入文件,流不进行缓冲
标准I/O预定的3个流,程序运行时自动打开
标准输入流 | 0 | STDIN_FILENO | stdin |
标准输出流 | 1 | STDOUT_FILENO | stdout |
标准错误流 | 2 | STDERR_FILENO | stderr |
流打开
FILE *fopen(const char * path,const char *mode)
成功时返回流指针;错误返回NULL
- 当用fopen新建文件后,该文件的权限为0666(rw-rw-rw-)
- 在linux系统中 umask设定会影响文件的权限访问 可以通过umask函数进行对应的修改
mode参数:
"r"或"rb" | 以只读的方式打开文件,文件必须存在 |
"r+"或"r+b" | 以读写的方式打开文件,文件必须存在 |
"w"或"wb" | 以只写的方式打开文件,若文件存在则文件长度清零。若文件不存在则创建 |
"w+"或"w+b" | 以只写方式打开文件,其他同"w" |
"a"或"ab" | 以只写的方式打开文件,若文件不存在则创建;向文件写入的数据将被追加到文件末尾 |
"a+"或"a+b" | 以读写的方式打开文件。其他同"a" |
*当给"b"参数时候,表示以二进制方式打开文件,但Linux下忽略该参数
错误流处理
void perror(const char *)
输入提示关键字 捕获当前错误并加输入的关键字返回
打印错误信息 例如
int main() { FILE *fp; if ((fp = fopen("test","r+"))==NULL){ perror("fopen"); return -1; } } // out // fopen: No such file or directory
#include <stdio.h> #include <string.h> // 需要引入 #include <errno.h> // 需要引入 标准错误输入 int main() { FILE *fp; if ((fp = fopen("test","r+"))==NULL){ printf("fopen:%s",strerror(errno)); return -1; } }
判断是否出错或结束
#include <stdio.h> int ferror(FILE *stream); int feof(FILE *stream); // ferror()返回1表示流出错;否则返回0 // feof()返回1表示已经到末尾,否则返回0
读写流
流支持不同的读写方式
读写一个字符:
// fgetc()/fputc() 一次读/写一个字符
输入
#include <stdio.h> int fgetc(FILE *stream); int getc(FILE *stream); int getchar(void); // 成功时返回读取的字符 // 若到文件的末尾或出错返回EOF
Demo:
#include <stdio.h> int main() { int ch; ch = fgetc(stdin); // 从标准输入获取 printf("%c", ch); }
#include <stdio.h> int main() { FILE *fp; int ch = 0; int i = 0; int count = 0; if ((fp = fopen("./a.out", "r")) == NULL) { perror("fopen"); return -1; } while ((ch = fgetc(fp)) != EOF) { count++; } printf("文本长度 :%d", count); }
输出
#include <stdio.h> int fputc(int c,FILE *stream); int putc(int c,FILE *stream); int putchar(int c); // 成功时返回写入的字符 // 出错时返回EOF
Demo:
#include <stdio.h> int main() { putc('a',stdout); putchar(' '); } // out // a //
#include <stdio.h> int main() { FILE *fp; int ch = 0; if ((fp = fopen("./a.out", "w")) == NULL) { perror("fopen"); return -1; } for (ch = 'a'; ch <= 'z', ch++;) { fputc(ch,fp); // 输出到文本 } }
文件copy
#include <stdio.h> int main(int argc, char *argv[]) { FILE *src, *desc; int i; if (argc < 3) { puts("Few parameters:project : <src> <desc>"); return -1; } // 打开目标文件 if ((src = fopen(argv[1], "r")) == NULL) { perror("src:"); return -1; } if ((desc = fopen(argv[2], "w")) == NULL) { perror("desc:"); fclose(src); // 失败释放打开的句柄 return -1; } while ((i = fgetc(src)) != EOF) { fputc(i, desc); } fclose(src); fclose(desc); }
读写一行:
// fgets()/fputs() 一次读/写一行
输入
#include <stdio.h> char *gets(char *s); char *fgets(char *s,int size,FILE *stream); // 成功时返回s,到文件末尾或出错时返回NULL // gets不推荐使用,容易造成缓冲区溢出; // ***遇到' '或已输入size-1个字符返回时,总是包含' '
输出
#include <stdio.h> int puts(const char *s); int fputs(const char *s,FILE *stream); // 成功时返回输出的字符个数 // 出错返回EOF // puts将缓冲区s中的字符串输出到stdout,并追加' '
demo:
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; char buf[] = "hello world"; if ((fp = fopen(argv[1], "a")) == NULL) { perror("fopen"); return -1; } fputs(buf, fp); }
读写若干个对象:
// fread()/fwrite() 每次读/写若干个对象,而每个对象具有相同的长度
#include <stdio.h> size_t fread(void *ptr,size_t size,size_t n,FILE *fp); // 容器指针,存储元素的长度,读取的长度,文件句柄 size_t fwrite(const void *prt,size_t size,size_t n,FILE *fp); // 成功返回读写的对象个数 // 出错返回EOF
demo
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; int buf[10] = {}; if ((fp = fopen(argv[1], "a")) == NULL) { perror("fopen"); return -1; } if (fread(buf, sizeof(int), 10, fp) < 0) { perror("fread"); return -1; } }
copy文件
#include <stdio.h> #define N 1024 int main(int argc, char *argv[]) { FILE *src, *desc; int i[N] = {}; int n = 0; if (argc < 3) { puts("Few parameters:project : <src> <desc>"); return -1; } // 打开目标文件 if ((src = fopen(argv[1], "r")) == NULL) { perror("src:"); return -1; } if ((desc = fopen(argv[2], "w")) == NULL) { perror("desc:"); fclose(src); // 失败释放打开的句柄 return -1; } if ((n = fread(i, sizeof(int), N, src)) > 0) { fwrite(i, n, sizeof(int), desc); } fclose(src); fclose(desc); }
刷新流
fflush()
#include <stdio.h> int fflush(FILE *fp); // 成功时返回0 // 出错返回EOF
刷新会将缓冲区的内容落盘
定位流
ftell/fseek/rewind
#include <stdio.h> long ftell(FILE *stream); long fseek(FILE *stram,long offset,int whence); // offset表示偏移量 可正可负 whence表示基准点 void rewind(FILE *stram); // ftell()成功时返回流的当前读写位置,错误时返回EOF // fseek()定位一个流,成功时返回0,出错时返回EOF // whence参数:SEEK_SET-文件开头/SEEK_CUR-当前位置/SEEK_END-文件结尾 // rewind将流定位到起始位置 // 读写流时,当前读写位置自动后移
Demo:
在文件末尾增加字符't'
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; // 打开目标文件 if ((fp = fopen(argv[1], "r")) == NULL) { perror("src:"); return -1; } // 移动到文章末尾 fseek(fp, 0, SEEK_END); // 增加字符t fputc('t', fp); fclose(fp); }
获取文件长度
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; int i; // 打开目标文件 if ((fp = fopen(argv[1], "r")) == NULL) { perror("src:"); return -1; } // 移动到文章末尾 fseek(fp, 0, SEEK_END); // 获取文件长度 i = ftell(fp); printf("长度为%d",i); fclose(fp); }
格式化输出
#include <stdio.h> int printf(const char *fmt,...); int fprintf(FILE *stream,const char *fmt,...) int sprintf(char *s,const char *fmt,...) // 成功时返回输出的字符个数 // 出错时返回EOF
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; int year, month, day; char buf[64]; // 打开目标文件 if ((fp = fopen(argv[1], "r")) == NULL) { perror("src:"); return -1; } year = 2020; month = 05; day = 05; // 输入到fp的流中 fprintf(fp, "%d-%d-%d", year, month, day); // 输入到缓冲区中 sprintf(buf, "%d-%d-%d", year, month, day); }
关闭流
int fclose(FILE *stream);
调用成功返回0,失败返回EOF,并设置errno
流成功关闭后会自动刷新缓冲中的数据并释放缓冲区
当一个程序正常终止时,所有打开的流都会被关闭
流一旦关闭将不能对其执行任何操作
在linux中打开流的最大个数(修改ulimit可更改)
1021 + stdin + stdout + stderr = 1024
文件I/O
POSIX(可移植操作系统接口)定义的一组函数
不提供缓冲机制,每次读写操作都引起系统调用
核心概念是文件描述符
可以访问各种类型文件
在Linux下,标准IO基于文件IO实现
文件描述符
每一个打开的文件都对应一个文件描述符
文件描述符是一个非负整数。Linux为程序中每打开的问你件分配一个文件描述符
文件描述符从0开始分配,一次递增
文件IO操作通过文件描述符来完成
文件描述符的0,1,2的含义
分别是标准输入,标准输出,标准错误 一一对应
打开文件、创建文件
#include <fcntl.h> int open(const char *path,int oflag,mode_t mode); // path文件路径 // oflag打开方式,可以是多个参数 // 当open成功时返回文件描述符 // 出错返回EOF // 创建文件时第三个参数指定文件的权限 // 设备文件只能打开不能创建
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 权限, 代表其他用户具有可执行的权限.
Demo:
#include <stdio.h> #include <fcntl.h> // 以只写的方式打开文件,如果不存在则创建,如果存在则清空 int main(int argc, char *argv[]) { int fd; if ((fd = open("1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { perror("open"); } }
#include <stdio.h> #include <fcntl.h> #include <string.h> // 需要引入 #include <errno.h> // 需要引入 标准错误输入 // 以读写的方式打开文件,如果不存在则创建,如果存在则报错 int main(int argc, char *argv[]) { int fd; if ((fd = open("1.txt", O_RDWR | O_CREAT, 0666)) < 0) { if (errno == EEXIST) { perror("exist error"); } else { perror("other error"); } } }
读取文件
#include <unistd.h> ssize_t read(int fd,void *buf,size_t count); // buf容器 count 一般定为缓冲区的大小 保证缓冲区不会溢出 // 成功返回实际读取的字节数 // 失败返回EOF // 读到文件尾时返回0
demo:统计文件大小
#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd, n, total; char buf[64]; if ((fd = open(argv[1], O_RDONLY)) < 0) { perror("open"); return -1; } // 读取文件的内容 // 获取文件大小 while ((n = read(fd, buf, 64)) > 0) { total += n; } }
写入文件
#include <unistd.h> ssize_t write(int fd,void *buf,size_t count); // 成功时返回实际写入的字节数 // 出错返回EOF // count不应超过buf大小
demo:键盘输入
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { int fd; char buf[64]; if ((fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { perror("open"); return -1; } // 读取文件的内容 // 获取文件大小 while (fgets(buf, 20, stdin) > 0) { if (strcmp(buf, "quit ") == 0) { break; } write(fd, buf, strlen(buf)); } }
demo:文件的复制
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #define N 64 int main(int argc, char *argv[]) { int src, desc; int n; char buf[N]; if (argc < 3) { perror("Parameter less than 3"); return -1; } // 打开源文件 只读 if ((src = open(argv[1], O_RDONLY)) < 0) { perror("open src"); return -1; } // 打开目标文件 if ((desc = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { perror("open desc"); close(src); return -1; } // 获取文件大小 while ((n = read(src, buf, N)) > 0) { write(desc, buf, n); } close(src); close(desc); }
定位
#include <unistd.h> off_t lseek(int fd,off_t offset,int whence); // 与fseek类似 whence基准点一样 // 成功返回当前文件的读写位置 // 出错返回EOF
文件关闭
#include <unistd.h> int close(int fd); // 成功时返回0 // 出错时返回EOF // 程序结束时自动关闭所有打开的文件 // 文件关闭后,就不能对文件描述符进行操作
访问(打开)目录
#include <dirent.h> DIR *opendir(const char*name); // name目录路径 // DIR是用来描述一个打开的目录文件的结构体类型 // 成功时返回目录流指针 // 错误时返回NULL
查看目录内容
#include <dirent.h> struct dirent *readdir(DIR *dirp); // struct dirent是用来描述目录流中一个目录项的结构体类型 // 包含成员 char d_name[256] —— 文件名 其他参考帮助文档 // 成功时返回目录流dirp中下一个目录项 // 出错或到末尾是返回NULL
demo:查看指定目录下的文件名
#include <stdio.h> #include <dirent.h> int main(int argc, char *argv[]) { DIR *dirp; struct dirent *dp; if (argc < 2) { perror("Parameter is less than 2"); return -1; } // 打开目录 if ((dirp = opendir(argv[1])) == NULL) { perror("opendir"); return -1; } while ((dp = readdir(dirp)) != NULL) { printf("%s ", dp->d_name); } closedir(dirp); }
关闭目录流
#include <dirent.h> int closedir(DIR *dirp); // 成功返回0 // 失败返回-1
修改文件的访问权限 chmod/fchmod
#include <sys/stat.h> int chomd(const char *path,mode_t mode); int fchmod(int fd,mode_t mode); // mode 例如 0666 权限码 // 成功时返回0 // 出错返回EOF // root用户和所有者才可以修改文件的访问权限 // demo: chomd("text.txt",0666);
获取文件的属性
#include <sys/stat.h> int stat(const char *path,struct stat *buf); //path为路径,buf为获取存放的结构体 int lstat(const char *path,struct stat *buf); int fstat(int fd,struct stat*buf); // stat与lstat区别: 如果path是符号链接stat获取到的是目标文件的属性,而lstat获取的是链接文件的属性 // 成功时返回0 // 出错返回EOF
// struct stat结构体属性 // mode_t st_mode 文件类型和访问权限 // uid_t st_uid 所有者id // uid_t st_gid 用户组id // off_t st_size 文件大小 // time_t st_mtime 最后修改时间 // ....
mode_t st_mode 判断文件类型
// 通过系统提供的宏来判断文件类型 st_mode & 0170000 S_ISREG(st_mode) 0100000 S_ISDIR(st_mode) 0040000 S_ISCHR(st_mode) 0020000 S_ISBLK(st_mode) 0060000 S_ISFIFO(st_mode) 0010000 S_ISLNG(st_mode) 0120000 S_ISSOCK(st_mode) 0140000
mode_t st_mode 获取权限
// 通过系统提供的宏来获取we年访问权限 // 所有者 S_IRUSR——00400——8 可读 S_IWUSR——00200——7 可写 S_IXUSR——00100——6 // 同组用户 S_IRGRP——00040——5 S_IWGRP——00020——4 S_IXGRP——00010——3 // 其他组用户 S_IROTH——00004——2 S_IWOTH——00002——1 S_IXOTH——00001——0
// 通过系统提供的宏来获取文件的访问权限 // 所有者 S_IRUSR——00400——8 可读 S_IWUSR——00200——7 可写 S_IXUSR——00100——6 // 同组用户 S_IRGRP——00040——5 S_IWGRP——00020——4 S_IXGRP——00010——3 // 其他组用户 S_IROTH——00004——2 S_IWOTH——00002——1 S_IXOTH——00001——0