1.选择标准库还是linux系统函数库
大部分情况下选择标准库,以支持跨平台的需求;
2.常见IO函数
2.1 fopen()函数
#include<stdio.h>
FILE *fopen(const char *path,const char*mode)
参数:
path:
是一个字符串,包含欲打开的文件路径及文件名
mode:
r:只读方式打开,将文件指针指向文件头。
r+:读写方式打开,将文件指针指向文件头。
w:写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
w+:读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
a:写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
a+:读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
返回值:
成功:返回一个指向FILE结构的指针
失败:返回NULL,并且会设置相应的errno
2.2 fclose()函数
#include <stdio.h>
int fclose( FILE *stream );
返回值:
对于输出流,fclose函数会在文件关闭前刷新缓冲区,如果它执行成功,fclose返回零值
注意点:
fclose函数执行后,就可以把缓冲区内最后剩余的数据输出到内核缓冲区,并释放文件指针和有关的缓冲区。
2.3 fgetc()函数
#include<stdio.h>
int fgetc (FILE *fp);
参数:
fp:
需要读取的文件指针
返回值:
返回值类型为int,不是char。
读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。
工作机制:
在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。
注意:
这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。
补充:
1.EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1。fgetc() 的返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)
EOF 不绝对是 -1,也可以是其他负数,这要看编译器的实现。
2.返回EOF时,如何区分是读取到文件末尾还是读取失败?
通过 feof() 或者ferror()进行状态判断。
feof() 函数
int feof ( FILE * fp );
用来判断文件内部指针是否指向了文件末尾,当指向文件末尾时返回非零值,否则返回零值。
ferror() 函数
int ferror ( FILE *fp );
出错时返回非零值,否则返回零值。
#include<stdio.h>
int main()
{
FILE *fp;
int ch;
//如果文件不存在,给出提示并退出
if( (fp=fopen("D:\\demo.txt","rt")) == NULL ){
puts("Fail to open file!");
exit(0);
}
//每次读取一个字节,直到读取完毕
while( (ch=fgetc(fp)) != EOF ){
putchar(ch);
}
putchar('\n'); //输出换行符
if(ferror(fp)){
puts("读取出错");
}else{
puts("读取成功");
}
fclose(fp);
return 0;
}
2.4 fputc()函数
#incldue<stdio.h>
int fputc ( int ch, FILE *fp );
参数:
ch :
为要写入的字符
fp :
要写入的文件指针
返回值:
返回值类型为 int
写入成功时返回写入的字符,失败时返回 EOF
说明:
1.被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,并将写入的字符放在文件开头。如需保留原有文件内容,并把写入的字符放在文件末尾,就必须以追加方式打开文件。不管以何种方式打开,被写入的文件若不存在时则创建该文件。
2.每写入一个字符,文件内部位置指针向后移动一个字节。
2.5 fgets()函数
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:
从 stream 流中读取 size -1个字符存储到字符指针变量 s 所指向的内存空间,在读入的最后一个字符后加上串结束标志’/0’。
参数:
s :
要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名
size:
从文件中读出的字符串不超过 size-1个字符
stream:
从何种流中读取,可以是标准输入流 stdin,也可以是文件流。
返回值
如果成功,该函数返回相同的 s 参数;
如果到达文件末尾或者没有读取到任何字符,s的内容保持不变,并返回一个空指针;
如果发生错误,返回一个空指针。
文件内容指针移动问题:
如果一行的字符串没读取完会,下一次会接着上一次读取,文件内容指针会做相应移动;
如果读完了,文件内容指针会指向下一行开头,直接从下一行开头开始读取。
2.6 fputs()函数
#include<stdio.h>
int fputs(const char *str, FILE *stream)
功能
把字符串str写入到指定的流 stream 中,但不包括空字符。
参数
str:
这是一个数组,包含了要写入的以空字符终止的字符序列。
stream:
这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
返回值:
成功返回一个非负值,如果发生错误则返回 EOF。
2.6 feek()函数
#include<stdio.h>
int fseek( FILE *stream, long offset, int whence);
功能
对流的文件指针重定位;
参数
stream:
要重定位的流
offset:
相对于whence的偏移量,整数表示正向偏移,负数表示负向偏移
whence:
要偏移的起始位置。SEEK_SET: 文件开头;SEEK_CUR: 当前位置;SEEK_END: 文件结尾,也可以依次用0,1和2表示。
返回值
如果执行成功,stream将指向以whence为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败(比如offset取值大于等于210241024*1024,即long的正数范围2G),则不改变stream指向的位置,函数返回一个非0值。
举例
fseek(fp,100L,0); 把fp指针移动到离文件开头100字节处;
fseek(fp,100L,1); 把fp指针移动到离文件当前位置100字节处;
fseek(fp,-100L,2); 把fp指针退回到离文件结尾100字节处、
stdio: FILE类型贯穿始终
fread();
fwrite();
fprintf(); //常用于输出重定向
sprintf(); //可以当作atoi()的反向函数
snprinf(); //相比sprintf(),可以防止写入越界
scanf();
//文件位置指针
fseek();
ftell();
rewind();
fflush();
3.man命令使用
man 1 + 命令:基本linux命令
man 2 + 命令:系统调用函数
man 3 + 命令: 标准库函数
man 7 + 命令:讲机制
eg: man 7 TCP
man 7 epoll
man 7 socket
4.fopen()函数详解
SYNOPSIS:
FILE *fopen(const char *path, const char *mode);//常量指针,path和mode在函数体内均不可改变
DESCRIPTION
fopen()返回值FILE*为结构体,指向的内容存放在堆上。需要与fclose()配对使用,以免造成内存泄漏;
打开模式:
w:无则创建,有则清空
两种特殊打开模式:r and r+
打开文件,如果不存在,则当前调用结束,返回值出错;
linux下rb可以忽略b,见下图
4.1 errno详解
errno:宏定义的全局变量
查看errno编码表方式:
vim /usr/include/asm-generic/errno-base.h //编号1~34
vim /usr/include/asm-generic/errno.h //编号35~133
如何测试errno为宏定义,而不是int类型?
编写测试文件,errnotest.c
#include <errno.h>
void main()
{
int a = 0;
errno;
}
gcc -E errnotest.c, 预处理#的内容
结果如下:
int a没有展开,errno已经按照宏定义展开
5.fread()与fwrite()
从stream中按照固定size读取numeb次,写入到ptr指向的地址中去。fwrite相反。
用于成块读取数据
缺点之一:不能检查写入或读取到的地址边界大小;
缺点之二:返回值问题,如果size不为1,可能不能识别小于size的具体个数;
1-> 数据量足够
2-> 数据量只有5个字节
fread(buf, 1, 10, fp);
1-> 读取次数10 -> 读取量可以计算出为10字节
2-> 5 -> 5字节
fread(buf, 10, 1, fp);
1-> 1 -> 10字节
2-> 0 -> 计算不出有多少字节待读取
建议:
fread(buf, 1, nemb, fp),进行单字节读取,当做fgetc()使用;
fwrite(buf, 1, nemb, fp),单字节写入,当做fputc()使用;
diff A B //比较A B两文件文本内容是否相同
6.文件位置指针函数
//文件位置指针
fseek();//SEEK_SET 文件开头 SEEK_CUR 文件当前位置 SEEK_END 文件末尾
rewind(); <=> (void)fseek(FILE* stream, 0L, SEEK_SET);
把文件位置指针定位到文件开头
fseek():如果执行成功,stream将指向以whence为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败(比如offset取值大于等于210241024*1024,即long的正数范围2G),则不改变stream指向的位置,函数返回一个非0值。
ftell():用于得到文件位置指针stream当前位置相对于文件首的偏移字节数。
long型范围2G-1~2G+·1
如果fseek()与ftell()结合使用,则最多能够处理2G大小的文件,因为ftell()返回值需要为+;
空洞文件: 刚开始下载文件,计算要下载的文件大小,然后通过fseek()生成一个和下载文件大小一样的空洞文件,以提前占用磁盘的内存空间;空洞文件内容全部是\0
假如要下载文件大小为2G,则fseek(fp, 2G, SEEK_END);
然后对下载文件切片,使用多线程分别下载切片的部分;
小结
1.缓冲区的作用:大多数情况下是好事,合并系统调用
分类:
行缓冲:换行时候刷新,满了的时候刷新,强制刷新(标准输出是行缓冲,因为是终端设备)
全缓冲:满了的时候刷新,强制刷新(默认,只要不是终端设备)
无缓冲:如stderr,需要立即输出的内容
切换缓冲类型函数:setvbuf();
2.如果一个函数的返回值是指针,如fopen(),且有逆函数(如fopen()的逆函数为fclose()),则
函数返回的指针一般放在堆上面。如果没有互逆操作,有可能在堆、栈或者静态区,
需要写函数验证;
3.不更改当前环境下,一个进程打开时,默认自动打开三个流,stdin, stdout, stderr
查看当前进程最多产生文件数
ulimit -a
4.宏会占用编译时间,不占用进程系统调用时间;
函数调用相反;内核更多通过宏来节省时间;应用程序处于稳定、可靠性,更多还是函数调用;