标准I/O
文件打开和关闭
文件打开
打开就是占用资源,关闭就是释放资源
打开一个标准I/O流:
FILE *fopen(const char *path,const char *mode);
path:普通文件当前路径不需要加目录,其他要使用完整的路径
成功时返回流指针
出错时返回NULL,所以使用fopen函数时必须要判断
mode参数:
重点(必须掌握):
r 或 rb 以只读方式打开文件,文件必须存在 r+或r+b 以读写方式打开文件,文件必须存在 w或wb 以只写方式打开文件,若文件存在,则清空内容;不存在则创建 w+或w+b 以读写方式打开文件,其他同w a或ab 以只写方式打开文件,文件不存在则创建,文件写入的数据被追加到文件末尾 a+或a+b 以读写方式打开文件。其他同 a 总结:r是读,r+是读写,文件都必须存在;w和a都是写,w是清空写,a是追加写,有+都是读写
fopen函数的使用方式
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { printf("Open is fail!\n"); } else { printf("Open file success\n"); } return 1;标准I/O-处理错误信息
extern int errno;
void perror(const char *s);
char *strerror(int errno);
errno 存放错误号,由系统生成
perror先输出字符串s,再输出错误号对应的错误信息,一般加在错误的分支
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { perror("fopen:"); } else { perror("fopen:"); }strerror根据错误号返回对应错误信息
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { printf("fopen:%s\n",strerror(errno));//errno系统定义好了,在#include <errno.h>中 //strerror在#include <string.h>中 } else { perror("Open success\n"); }perror与strerror功能一样
文件关闭
int fclose(FILE *stream);
fclose()调用成功返回0,失败返回EOF(-1),并设置errno
流关闭时自动刷新缓冲区中的数据并释放缓冲区
当一个程序正常终止时,所有打开的流都会被关闭
流一旦关闭后就不能执行任何操作
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { printf("fopen:%s\n",strerror(errno)); } else { //只有打开成功,才能关闭成功 printf("Open success\n"); if(fclose(fp) == 0) { printf("close success!\n"); } else { perror("fclose:"); } }按字符读取
从文件中读取字符
下列函数用来输入一个字符:
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream); //宏
int getchar(void);
成功时返回读取的字符,若到文件末尾或出错时返回EOF(-1),
getchar()等同于fgetc(stdin),getc和fgetc区别是一个是宏,一个是函数
上面的语句选择一条用就行
stdin也是FILE*的指针,是系统定义好的,指向的是标准输入(键盘输入)
FILE *fp; int rec; fp = fopen("1.txt","r"); if(fp == NULL) { perror("fopen:"); return 0; } rec = fgetc(fp); printf("Get char = %c\n",rec); rec = fgetc(fp); printf("Get char = %c\n",rec); rec = fgetc(fp); printf("Get char = %c\n",rec); fclose(fp);打开文件后读取,是从文件开头开始读,读完一个后读写指针会后移
FILE *fp; int rec; fp = fopen("1.txt","r"); if(fp == NULL) { perror("fopen:"); return 0; } rec = getchar(); printf("Getchar() = %c\n",rec); fclose(fp);调用getchar()会阻塞,等待键盘输入
按字符写入
向文件中写入字符
下列函数用来输出一个字符
#include <stdio.h>
int fputc(int c,FILE *stream);
int putc(int c,FILE *stream);
int putchar(int c);
成功时返回写入的字符,出错时返回EOF
putchar(c)等同于fputc(c,stdout)
上面的语句选择一条用就行
stdout也是FILE*的指针,是系统定义好的,指向的是标准输出,输出到屏幕上
FILE *fp; int rec; fp = fopen("1.txt","r+"); if(fp == NULL) { perror("fopen:"); return 0; } int wrc = 'c'; rec = fputc(wrc,fp); if(rec == EOF) { perror("fputc:"); } fclose(fp);putchar(c)
int wrc = 'c'; putchar(wrc);
按行读取
#include <stdio.h>
char *gets(char *s);//读取键盘的输入,不推荐使用
char *fgets(char *s,int size,FILE *stream);
成功时返回s,到文件末尾或出错时返回NULL
gets不推荐使用,容易造成缓冲区溢出
遇到'\n'或已输入size-1个字符时返回,总是包含'\0','\0'也占一位
//读取所有行 FILE *fp; fp = fopen("1.txt","r+"); if(fp == NULL) { perror("fopen:"); } char buff[100]; while(fgets(buff,sizeof(buff),fp) != NULL) { printf("buff = %s",buff); } printf("\n"); fclose(fp);
按行写入
#include <stdio.h>
int puts(const char *s);
int fputs(const char *s,FILE *stream);
成功时返回非负函数,出错时返回EOF
puts将缓冲区s中的字符串输出到stdout,并追加'\n'
fputs将缓冲区s中的字符串输出到stream,不追加'\n'
FILE *fp; fp = fopen("1.txt","r+"); if(fp == NULL) { perror("fopen:"); } const char *s = "hello world hello"; int res = fputs(s,fp); if(res == EOF) { perror("fpus:"); } fclose(fp);
二进制文件读取与写入
#include <stdio.h>
size_t fread(void *ptr,size_t size,size_t n,FILE *fp);
ptr为读取的内容放置位置,size为读多大的内容,n为读的个数,fp为文件
一共读取size*n个字节
读文本文件
FILE *fp; fp = fopen("1.txt","r+"); if(fp == NULL) { perror("fopen:"); } char buff[100]; size_t res; res = fread(buff,10,2,fp); if(res == EOF) { perror("fread:"); } printf("buff = %s\n",buff); fclose(fp);size_t fwrite(const void *ptr,size_t size,size_t n,FILE *fp);
将ptr的内容写到fp中,size是写多大,n是写多少个
读写二进制文件代码
#include <stdio.h> #include <string.h> struct Student{ char name[100]; int age; char sex[8]; }; int main() { struct Student s; FILE *fp; fp = fopen("1.bin","w+"); if(fp == NULL) { perror("fopen:"); } strcpy(s.name,"zhangsan"); s.age = 20; strcpy(s.sex,"male"); //write int res = fwrite(&s,sizeof(s),1,fp); if(res == EOF) { perror("fwrite:"); } fclose(fp); //read fp = fopen("1.bin","r+"); if(fp == NULL) { perror("fopen:"); } struct Student s2; res = fread(&s2,sizeof(s),1,fp); if(res == EOF) { perror("fread:"); return 0; } printf("name:%s age:%d sex:%s\n",s2.name,s2.age,s2.sex); fclose(fp); return 1; }结构体是二进制文件
成功返回读写的对象个数,出错时返回EOF
既可以读写文本文件,也可以读写二进制文件,效率高
注意事项:文件写完后,文件指针指向文件末尾,如果这时候直接读,会读不出来内容
解决办法:
1、移动文件指针(后面说)
2、关闭文件,重新打开
文件的流的刷新与定位
刷新流
#include <stdio.h>
int fflush(FILE *fp);
成功时返回0,出错时返回EOF
将流缓冲区中的数据写入实际文件
Linux下只能刷新输出缓冲区,输入缓冲区丢弃
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { perror("fopen"); return 0; } fwrite("abcdef",7,1,fp); fflush(fp); while(1) { sleep(1); } return 0;
定位流
#include <stdio.h>
long ftell(FILE *stream);
long fseek(FILE *stream,long offset,int whence);
void rewind(FILE *stream);
ftell()成功时返回流的当前读写位置,出错时返回EOF
fseek()定位一个流,成功时返回0,出错时返回EOF
whence参数:SEEK_SET/SEEK_CUR/SEEK_END
SEEK_CUR:以目前的读写位置往后增加offset个位移量
SEEK_END:将读写位置指向文件尾后再增加offset个位移量
SEEK_SET:从距文件开头offset位移量为新的读写位置
offset参数:偏移量,可正可负
打开a模式,fseek无效
rewind()将流定位到文件开始的位置
读写流时,当前读写位置自动后移
示例一:在文件末尾追加字符't'
FILE* fp = fopen("1.txt","r+");
fseek(fp,0,SEEK_CUR);
fputc('c',fp);
覆盖se两个字母写vvv
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { perror("fopen"); return 0; } fwrite("abcdse",6,1,fp); fseek(fp,-2,SEEK_CUR); fwrite("vvv",3,1,fp); fclose(fp);从文件开始位置写内容
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { perror("fopen"); return 0; } fwrite("abcdse",6,1,fp); rewind(fp); fwrite("vvv",3,1,fp); fclose(fp);显示当前文件流的位置
FILE *fp; fp = fopen("1.txt","w"); if(fp == NULL) { perror("fopen"); return 0; } fwrite("abcdse",6,1,fp); rewind(fp); fwrite("vvv",3,1,fp); printf("current fp = %ld\n",ftell(fp)); 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
写入字符串
char buf[100] = {0}; int year = 2025; int month = 12; int day = 15; sprintf(buf,"%d-%d-%d",year,month,day); printf("%s\n",buf);写入文件
FILE *fp; int year = 2025; int month = 12; int day = 15; fp = fopen("1.txt","r+"); if(fp == NULL) { perror("fopen"); return 0; } fprintf(fp,"%d-%d-%d\n",year,month,day); fclose(fp);
格式化输入/从文件或字符串中读取内容
int fscanf(FILE* stream,const char *fmt,...);
int sscanf(char *s,const char *fmt,...);
从字符串中读取内容
int year = 2025; int month = 12; int day = 15; int syear; int smonth; int sday; char buf[100] = {0}; sprintf(buf,"%d-%d-%d",year,month,day); sscanf(buf,"%d-%d-%d",&syear,&smonth,&sday); printf("%d,%d,%d\n",syear,smonth,sday);sprintf(buf,"%d-%d-%d",year,month,day);
sscanf(buf,"%d-%d-%d",&syear,&smonth,&sday);常用其中,"%d-%d-%d"的格式要一致
int year,month,day; FILE *fp; fp = fopen("1.txt","r+"); if(fp == NULL) { perror("fopen"); return 0; } fscanf(fp,"%d-%d-%d",&year,&month,&day); printf("%d-%d-%d\n",year,month,day); fclose(fp);其中,fscanf()中的''%d-%d-%d"要和文件中的数据格式一致
思考联系:每隔1秒向文件test.txt中写入当前系统时间,格式如下:
1、2014-10-15 15:16:42
2、2024-10-15 15:16:43
该程序无限循环,直到Ctrl+C中断程序
每次执行程序时,系统时间追加到文件末尾,序号递增
time()用来获取系统时间(秒数)
time_t time(time_t *seconds)
localtime()将系统时间转换成本地时间
struct tm *localtime(const time_t *timer);//这个结构体包含所有时间类型
#include <stdio.h> #include <time.h> #include <unistd.h> #include <string.h> int main() { FILE *fp; time_t ctime; int line = 0; char buf[32]; fp = fopen("1.txt","a+"); if(fp == NULL) { perror("fopen"); return 0; } //计算行号 while(fgets(buf,32,fp) != NULL) { if(buf[strlen(buf) - 1] == '\n') //判断一行是否结束 { line ++; } } while(1) { line ++; struct tm* ctimestr; ctime = time(NULL); ctimestr = localtime(&ctime); fprintf(fp,"%d. %04d-%02d-%02d %02d:%02d:%02d\n",line,ctimestr->tm_year+1900,ctimestr->tm_mon+1,ctimestr->tm_mday,ctimestr->tm_hour,ctimestr->tm_min,ctimestr->tm_sec); printf("%d. %04d-%02d-%02d %02d:%02d:%02d\n",line,ctimestr->tm_year+1900,ctimestr->tm_mon+1,ctimestr->tm_mday,ctimestr->tm_hour,ctimestr->tm_min,ctimestr->tm_sec); fflush(fp);//刷新,写到磁盘里面 sleep(1); } fclose(fp); return 0; }
文件I/O
文件描述符fd
1、每个打开的文件都对应一个文件描述符
2、文件描述符是一个非负整数,Linux为程序中每个打开的文件分配一个文件描述符
3、文件描述符从0开始分配,依次递增
4、文件IO操作通过文件描述符来完成
5、0为标准输入,1为标准输出,2为错误流
注:文件IO不提供缓冲机制
文件IO的API:
open、close、read、write
文件描述的概念:
0-1023的数字,表示文件。
open函数
#include <fcntl.h>
int open(const char* pathname,int flags,mode_t mode);
int open(const char*pathname,int flags);
pathname:被打开的文件名(可包括路径名)
flags:
O_RDONLY:只读方式打开文件
O_WRONLY:可写方式打开文件
O_RDWR:读写方式打开文件
以上三个参数是互斥的,不能同时选择,下面的参数是可选项
O_APPEND:以添加方式打开文件,所以对文件的写操作都在文件的末尾进行
O_TRUNC:如文件已经存在,那么打开文件时先删除文件中原有数据
O_CREAT:如果文件不存在,就创建一个新文件,并用第三个参数为其设置权限
O_EXCL:如果使用O_CREAT时文件为终端,那么终端不可以作为调用open()系统调用的那个进程的控制终端
mode:被打开文件的存取权限,为8机制表示法
0664
成功时返回文件描述符fd,出错时返回EOF
1、打开文件时使用两个参数
2、创建文件时第三个参数指定新文件的权限(只有在建立新文件时有效)此外真正建文件时的权限会受到umask值影响,实际权限是mode-umaks
3、可以打开设备文件,但是不能创建设备文件
r O_RDONLY r+ O_RDWR w O_WRONLY|O_CREAT|O_TRUNC,0664 w+ O_RDWR|O_CREAT|O_TRUNC,0664 a O_WRONLY|O_CREAT|O_APPEND,0664 a+ O_RDWR|O_CREAT|O_APPEND,0664 int fd; if(fd = open("1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666) == EOF) { perror("open"); return 0; } else { printf("success!\n"); }在man手册的第二章,man 2 open
close函数
#include <unistd.h>
int close(int fd);
1、成功时返回0,出错时返回EOF
2、程序结束时自动关闭所有打开的文件
3、文件关闭时,文件描述符不再代表文件
int fd; int res; if(fd = open("1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666) == EOF) { perror("open"); return 0; } else { printf("success!\n"); } printf("fd = %d\n",fd); res = close(fd);
read函数
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t count);
成功时返回实际读取的字节数,0表示数据已经读完;出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区,一般是数组
count不应超过buf的大小,是指定的数组大小
write函数
#include <unistd.h>
ssize_t write(int fd,void *buf,size_t count);
成功时返回实际写入的字节数,0表示数据已经读完;出错时返回EOF
buf是发送数据的缓冲区,一般是数组
count不要超过buf的大小,要写入的数据的实际的大小
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> int main() { char filename[40] = "1.txt"; char buf_w[20] = "Hello World!"; char buf_r[20] = {0}; // 初始化为0 int fd = open(filename, O_RDWR | O_CREAT, S_IRWXU); // 添加 O_CREAT 确保文件存在 if(fd == -1) { perror("open"); exit(1); // 通常用非0表示错误 } // 写入文件 int ret = write(fd, buf_w, strlen(buf_w)); if(ret == -1) { perror("write"); exit(1); } close(fd); // 重新打开读取 fd = open(filename, O_RDWR); if(fd == -1) { perror("open"); exit(1); } // 移动文件指针到开头 lseek(fd, 0, SEEK_SET); // 读取文件内容 ret = read(fd, buf_r, sizeof(buf_r) - 1); // 留一个位置给 '\0' if(ret == -1) { perror("read"); exit(1); } buf_r[ret] = '\0'; // 手动添加字符串结束符 printf("buf_r = %s\n", buf_r); close(fd); return 0; }注:对于buf的大小,字符串用strlen,二进制用sizeof
lseek函数
lseek函数用来定位文件
#include <unistd.h>
off_t lseek(int fd,off_t offset,intt whence);
成功时返回当前的文件读写位置;出错时返回EOF
参数offset和参数whence同fseek完全一样
whence:
SEEK_CUR:以目前的读写位置往后增加offset个位移量
SEEK_END:将读写位置指向文件尾后再增加offset个位移量
SEEK_SET:从距文件开头offset位移量为新的读写位置
作用:
1、移动文件指针到文件头
lseek(fd,0,SEEK_SET);
2、获取当前文件指针的位置
lseek(fd,0,SEEK_CUR);
3、获取文件长度
lseek(fd,0,SEEK_END);
4、拓展文件的长度,当前文件10b,110b,增加了100个字节
lseek(fd,100,SEEK_END);拓展之后需要写一个数据才管用
文件属性和目录操作
目录的读取
opendir函数
打开目录
#include <dirent.h>
DIR *opendor(const char *name);
DIR *fdopendir(int fd);使用文件描述符,要配合open函数使用
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针;出错时返回NULL
读目录
struct dirent* readdir(DIR *dirp);
struct dirent是用来描述目录流中的一个目录项的结构体类型
包含成员char d_name[256]参考帮助文档
成功时返回目录流dirp中的下一目录项
出错时到末尾时返回NULL
待续...