1 基本概念
1.1 系统调用IO与标准IO的区别
-
(1) 系统调用IO:内核提供的一系列接口,不同内核的接口不同。
-
(2) 标准IO:合并系统调用并将内核接口进行封装后的库函数,可移植性高。
-
(3) 标准IO与系统IO的对比:① 可移植性:标准IO>文件IO;② 效率:多文件时标准IO的效率更好。
-
(4) 合并系统调用:标准IO并没有马上将缓冲区的内容输出到文件,减少了对外设的使用,效率更高。
-
(5) 原则:能用标准IO就用标准IO。
1.2 数据流
-
(1) 流:数据在内存和文件之间的路径。
-
(2) 输入流:数据从文件输入到内存的路径。
-
(3) 输出流:数据从内存输出到文件的路径。
-
(4) 文本流和二进制流:
-
① 文本文件以ASCII码、Unicode、GBK等编码格式存储,windows下’\r’ '\n’为换行。
-
② 二进制文件以二进制格式存储,大部分文件都是二进制文件。
-
③ 默认打开的流为:stdout(标准输出流)、stdin(标准输入流)、stderr(标准错误流)。
-
-
(5) 在Linux下打开流就是打开了一个FILE*。
1.3 文件类型
-
(1) 文件:数据,Linux下一切皆文件。
-
(2) linux下查看文件类型:ls -la。
-
(3) 输入输出设备:① 输入设备:鼠标、键盘;②输出设备:显示器;③串口即是输入也是输出设备。
文件类型 | 标识符 | 描述 |
---|---|---|
常规文件 | _ | 普通的文件 |
目录文件 | d | 一个目录 |
字符文件 | c | 鼠标、键盘等输入设备 |
块设备文件 | b | 硬盘、磁盘、U盘等存储设备 |
管道文件 | p | |
套接字文件 | s | 用在网络编程 |
符号链接文件 | i | 类似于windows快捷方式 |
1.4 缓冲机制
-
(1) 全缓冲:当缓冲区满了才会执行实际IO操作。
-
(2) 行缓冲:当缓冲区中遇到了换行符才会进行时间IO操作,最大长度为1024。
-
(3) 无缓冲:直接输出到外设。
-
(4) 标准IO的缓冲类型:stdout和stdin默认行缓冲,stderr默认无缓冲。
-
(5) 缓冲的原因:外设的传输速度低于CPU,加缓冲可以减少外设传输的次数。
1.5 man手册
手册位置 | 内容 |
---|---|
man 1 | 常用linux指令 |
man 2 | 系统调用函数 |
man 3 | 库函数 |
man 7 | 机制 |
1.6 FILE关键字
-
FILE:文件结构体,保存了一系列文件操作的属性。
-
FILE*的存放位置:需要打开和释放,故存放在堆区,若在全局区只能打开一个,栈区则不能返回局部变量地址。除了固定字段比如ID信息之类的,其余的有逆反操作的都存放在堆中。
-
缓冲区的刷新就是将指针 ptr 变为缓冲区的基地址 ,同时 cnt 的值变为0 ,因为缓冲区刷新后里面是没有数据的。
typedef struct _iobuf {
int cnt; // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
char *ptr; // 下一个要被读取的字符的地址
char *base; // 缓冲区基地址
int flag; // 读写状态标志位
int fd; // 文件描述符
// 其他成员
} FILE;
2 打开和关闭
2.1 打开函数
#include <stdio.h>
/*
功能:打开成功,但会文件流指针,打开失败返回NULL,并将设置对应errno。
路径:绝对路径和相对路径。
备注:每次打开都会创建一个流。
*/
FILE *fopen(const char *pathname, const char *mode);
/*打开新的流并关闭之前的流*/
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
2.2 读写模式
读写权限 | 描述 |
---|---|
r | 只读,流位于文件开头。 |
r+ | 读写,流位于文件开头。 |
w | 只写。如果文件不存在,则会创建该文件,否则会截断该文件。流位于文件的开头。 |
w+ | 读写。如果文件不存在,则会创建该文件,否则会截断该文件。流位于文件的开头。 |
a | 只写。如果文件不存在,则会创建该文件。流位于文件的末尾后一个字节。 |
a+ | 读写。如果文件不存在,则会创建该文件。读取的初始文件位置在文件的开头,但输出在文件的末尾后一个字节。 |
- 文件不存在用:w、w+、a、a+。
- 可读写:后面加加好。
- 文件指针在末尾的下一个位置:a、a+。
- windows下二进制读写加b,linux下不区分二进制和文本读写。
2.3 打开数量
-
linux下使用ulimit -a 命令可以看到open file的数量1024。
-
但默认打开了stdin、stdout、stderr,所以还能打开1021个文件。
-
使用ulimit -a -n 数量来修改open file的数量。
2.4 新建文件权限
-
新建的文件的权限公式:0666 &~ umask,其中umask越大则权限越下。
-
linux下可以使用umask来查询umask大小。
-
umask 001 修改权限。
-
vim /etc/profile 永久生效,source /etc/profile生效。
-
umask -S查看具体权限分配。
2.5 打印错误信息
/*errno是定义好的宏*/
#include <errno.h> /*errno 是一个宏定义的错误号*/
/*perror函数打印错误信息*/
#include <stdio.h>
void perror(const char *s); /*将错误信息拼接在s后面*/
/*获取errno对应的错误字符串*/
#include <string.h>
char *strerror(int errnum);
2.6 关闭函数
#include <stdio.h>
int fclose(FILE *stream);
/*
功能:关闭FILE指针,关闭成功返回0,否则返回EOF,并设置errno。
注意:不能同时关闭一个指针两次。
备注:关闭的时候会将缓冲区全部刷新到文件中。
*/
2.7 打开关闭模版
/********打开关闭模版*******
* @preif 打开和关闭文件,打印错误信息
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv){
/*注意文件不存在时,需要切换到root才能创建文件*/
FILE *fp = fopen ("/home/src_test.txt","w");
int flag = 0;
if(!fp){ /*fp为NULL,则打开文件失败*/
fprintf(stdout,"%d",__LINE__);
perror("fopen: "); /*打印错误信息*/
exit(1);
}
fprintf(stdout,"%d fopen is success!\n",__LINE__);
/*中间执行逻辑*/
flag = fclose(fp); /*关闭文件*/
if(flag != 0){
fprintf(stderr,"%d fclose: %s\n",__LINE__,strerror(errno));
exit(1);
}
fprintf(stdout,"%d fclose is success!\n",__LINE__);
return 0;
}
3 字符读写
3.1 字符读取函数
#include <stdio.h>
/*从标准流中获取字符,成功时将该字符转为int返回;若到文件末尾或出错时返回EOF*/
int fgetc(FILE *stream); /*返回值为负数是为了容纳EOF*/
int getc(FILE *stream); /*fgetc的宏实现*/
int getchar(void); /*从标准输入流中获取一个字符*/
3.2 判断错误、末尾
/*判断是否到了文件尾*/
int feof(FILE *stream); /*判断文件尾部指示符,如果如果文件指示符指向文件尾部,返回非0*/
void clearerr(FILE *stream); /*为流所指向的流清除文件结尾和错误指示符,文件尾部指示符只能通过该函数清除*/
/*判断是否错误*/
int ferror(FILE *stream); /*测试流所指向的流的错误指示符,如果设置了,则返回非零。错误指示器只能通过clearerr()重置*/
3.3 字符写入函数
#include <stdio.h>
/*将字符输入到输出流,成功返回c转为的int,否则返回EOF*/
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream); /*fputs的宏实现*/
int putchar(int c); /*输入到stdin*/
3.4 读写模版
/********单字节读写模版*******
* @preif 将test_src拷贝到test_src中
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv){
FILE *fp_src = NULL;
FILE *fp_dest = NULL;
int ch = 0;
/*判断参数个数*/
if(argc < 3){
fprintf(stderr,"%d 缺少参数\n",__LINE__);
exit(1);
}
/*打开文件fp_src*/
fp_src = fopen(argv[1],"r");
if(!fp_src){
fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
exit(1);
}
/*打开文件fp_dest*/
fp_dest = fopen(argv[2],"w");
if(!fp_dest){
fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
fclose(fp_src);
exit(1);
}
/*字符读写*/
while((ch = fgetc(fp_src)) >= 0){
if(fputc(ch,fp_dest)!=ch){
fprintf(stderr,"%d fputc: %s",__LINE__,strerror(errno));
fclose(fp_dest);
fclose(fp_src);
exit(1);
}
}
/*判断是否出错*/
if(ferror(fp_src)){
fprintf(stderr,"%d fgetc: %s\n",__LINE__,strerror(errno));
}
fclose(fp_dest);
fclose(fp_src);
return 0;
}
4 行读写
4.1 行读取函数
#include <stdio.h>
/*从流中获取size个大小的字节到缓冲区,读取成功返回字符串,否则返回NULL*/
char *fgets(char *s, int size, FILE *stream);
/*fgets的结束形式,读到size - 1或者遇到'\n'时停止,并在后面加'\0'*/
/*读取一行,刚好size-1个字符,但文件指针在尾部,则需要读取两次*/
char *gets(char *s); /*从stdin中读取,不检查缓冲区溢出,不会记录换行/
4.2 行写入函数
#include <stdio.h>
/*将字符串输入到流中区,但不包括'\0',成功返回非负数,否则返货EOF或者ERROR*/
int fputs(const char *s, FILE *stream);
int puts(const char *s); /*输入到标准流stdout*/
4.3 读写模版
/********行读写模版*******
* @preif 将test_src拷贝到test_src中
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv){
FILE *fp_src = NULL;
FILE *fp_dest = NULL;
char buf[1024];
/*判断参数个数*/
if(argc < 3){
fprintf(stderr,"%d 缺少参数\n",__LINE__);
exit(1);
}
/*打开文件fp_src*/
fp_src = fopen(argv[1],"r");
if(!fp_src){
fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
exit(1);
}
/*打开文件fp_dest*/
fp_dest = fopen(argv[2],"w");
if(!fp_dest){
fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
fclose(fp_src);
exit(1);
}
/*字符串读写*/
while(fgets(buf,1024,fp_src)!= NULL){
if(fputs(buf,fp_dest) < 0){
fprintf(stderr,"%d fputs: %s",__LINE__, strerror(errno));
exit(0);
}
}
/*判断是否出错*/
if(ferror(fp_src)){
fprintf(stderr,"%d fgets: %s\n",__LINE__,strerror(errno));
}
fclose(fp_dest);
fclose(fp_src);
return 0;
}
5 对象读写
5.1 对象读取函数
#include <stdio.h>
/*从流中获取nmemb个对象,每个对象的长度为size,将其保存到ptr中,返回无符号的数:读到的对象个数*/
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
/*如果读取到的字节不满足一个对象则不会计数,所以为了直到读到了多少个字节,可以每个对象一个字节大小*/
5.2 对象写入函数
#include <stdio.h>
/*从ptr中获取n个size长度大小的对象,写入到流中,成功返回写入的对象的数量*/
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
/*
备注:
fread和fwrite既可以读取文本也可以读取二进制文件。
返回值:返回成功读写的块数,也即 nmemb。
如果返回值小于 nmemb:
对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。
*/
5.3 读写模版
/********对象读写模版*******
* @preif 将test_src拷贝到test_src中
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv){
FILE *fp_src = NULL;
FILE *fp_dest = NULL;
char buf[1024];
/*判断参数个数*/
if(argc < 3){
fprintf(stderr,"%d 缺少参数\n",__LINE__);
exit(1);
}
/*打开文件fp_src*/
fp_src = fopen(argv[1],"r");
if(!fp_src){
fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
exit(1);
}
/*打开文件fp_dest*/
fp_dest = fopen(argv[2],"w");
if(!fp_dest){
fprintf(stderr,"%d fopen: %s\n",__LINE__,strerror(errno));
fclose(fp_src);
exit(1);
}
/*字符串读写*/
while(fread(buf,1,1,fp_src) == 1){
if(fwrite(buf,1,1,fp_dest) != 1){
fprintf(stderr,"%d fwrite: %s",__LINE__, strerror(errno));
exit(0);
}
}
/*判断是否出错*/
if(ferror(fp_src)){
fprintf(stderr,"%d fread: %s\n",__LINE__,strerror(errno));
}
fclose(fp_dest);
fclose(fp_src);
return 0;
}
6 格式化读写
6.1 格式化输出
#include <stdio.h> /*返回输出的字符个数,错误返回负数*/
int printf(const char *format, ...); /*输出到标准输出*/
int fprintf(FILE *stream, const char *format, ...); /*输出到某个标准流*/
int sprintf(char *str, const char *format, ...); /*输出到字符串,不检查字符串是否溢出*/
int snprintf(char *str, size_t size, const char *format, ...); /*输出多少个到字符串,包含'\0'一共size个*/
#include <stdlib.h> /*字符串转整型*/
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
#include <stdlib.h> /*字符串转浮点型*/
double atof(const char *nptr);
6.2 格式化输入
#include <stdio.h> /*返回读取的字符个数,如果遇到文末或者读取错误返回EOF*/
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...); /*从字符串中输入*/
6.3 字符串转换
/********将数据转为字符串输出到文件*****/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv){
FILE *fp = NULL;
char str[1024] = " ";
char name[10] = "simon";
int age = 18;
char sex = 'b';
if(argc < 2){
fprintf(stderr,"%d main参数个数不足",__LINE__);
exit(1);
}
sprintf(str,"%s\n%d\n%c\n",name,age,sex);
fp = fopen(argv[1],"w");
if(!fp){
fprintf(stderr,"%d fopen: %s",__LINE__, strerror(errno));
exit(1);
}
fprintf(fp,"%s",str);
fclose(fp);
return 0;
}
7 刷新与定位
7.1 刷新函数
#include <stdio.h>
/*成功返回0,否则返回EOF或者设置errno*/
int fflush(FILE *stream);
/*传参NULL刷新所有*/
/*只能刷新输出,输入丢弃*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(void){
printf("hello");
fflush(NULL);
sleep(5); /*延时5us*/
printf("world");
return 0;
}
7.2 定位函数
#include <stdio.h>
/*
从wherece便宜offset个位置,正向后,负向前
返回值:偏移量
whence:SEEK_SET起始, SEEK_END文末, SEEK_CUR当前
备注:文件的打开使用a模式 fseek无效
*/
int fseek(FILE *stream, long offset, int whence);
/*
返回值:当前位置
long局限:负数用不了,只能使用2G大小
*/
long ftell(FILE *stream);
/*
定位到起始位置
*/
void rewind(FILE *stream);
/*空洞文件:直接占位,后面再写入*/
/*
补充方言
用off_t替换long,可以定位更大的文件
需要define _FILE_OFFSET_BITS,64位
不支持C89和C99
*/
#include <stdio.h>
int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(void){
FILE *fp = NULL;
long index = 0;
char str[1024];
fp = fopen("/home/test_src","r");
if(!fp){
fprintf(stderr, "%d fopen: %s", __LINE__, strerror(errno));
exit(1);
}
if(fgets(str,7,fp) == NULL){
fprintf(stderr,"%d fgets: %s", __LINE__, strerror(errno));
exit(1);
}
fprintf(stdout,"%s\n",str);
index = ftell(fp);
fprintf(stdout,"index = %ld\n",index);
fseek(fp,0,SEEK_END);
index = ftell(fp);
fprintf(stdout,"index = %ld\n",index);
rewind(fp);
index = ftell(fp);
fprintf(stdout,"index = %ld\n",index);
fclose(fp);
return 0;
}
7.3 查询文件字数
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv){
/*
查询字节数
*/
FILE *fp = NULL;
long ret = 0;
if(argc < 2){
fprintf(stderr,"%d 参数不够\n",__LINE__);
exit(1);
}
fp = fopen(argv[1],"r");
if(!fp){
fprintf(stderr,"%d fclose: %s\n",__LINE__,strerror(errno));
exit(1);
}
fseek(fp,0,SEEK_END);
ret = ftell(fp);
fprintf(stdout,"total: %ld\n",ret);
fclose(fp);
return 0;
}
8 补充
8.1 getline函数
#include <stdio.h>
/*需要再Makefile中加 -D_GNU_SOURCE*/
/*
传入一个字符串指针的地址,一个整型的地址
传入之前需要将lineptr = NULL,n = 0,方便识别第一次malloc
lineptr保存获取的行的首地址
n保存整个行的元素个数
读取成功返回读取的个数,失败返回-1,且设置errno
*/
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
/*内存分配方式:120,再两倍扩容*/
/*没有销毁方式,存在可控的内存泄漏,线程结束后会回收*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define _GNU_SOURCE
int main(void){
FILE *fp = NULL;
char *Line = NULL;
size_t LineSize = 0;
fp = fopen("/etc/brltty.conf","r");
if(!fp){
fprintf(stderr,"%d fopen: %s",__LINE__,strerror(errno));
exit(1);
}
while(getline(&Line,&LineSize,fp) >= 0){
fprintf(stdout,"%s",Line);
}
fclose(fp);
return 0;
}
8.2 临时文件
/*临时文件:不冲突,及时销毁*/
/*直接获取一个可用文件名,但两步完成,可能多线程下会冲突*/
#include <stdio.h>
char *tmpnam(char *s); /*s最好为NULL*/
/*直接获取一个匿名文件,不会与其他人冲突,并且fclose时会自行销毁*/
#include <stdio.h>
FILE *tmpfile(void);