IO与进程
IO
IO:就是输入(input)输出(output)。是针对程序的输入输出
在磁盘中的文件只能是Linux内核中的文件系统进行操作,内核提供一些固定的操作方式------函数(通知),这些函数在shell解释器中声明,当应用程序访问磁盘中的文件时,可以调用函数通知内核,然后让内核进行操作。
输入:从文件输入到程序(的内存空间)
输出:从程序(的内存空间)输出到文件中
注意:输入输出是对程序而言
读(出):read,从文件中读取内容到程序(的内存空间)
写(入):write,从程序的内存空间写入到文件中。
注意:读写是对文件而言
IO函数分类
-
系统调用函数
这是一种操作系统提供的功能函数,不同系统函数的形式不同。这些函数也是操作系统的一部分,是应用程序访问内核的接口
-
库函数
库函数是为了实现某个功能封装的接口集合
它提供了一种统一的编程接口。其实程序调用库函数访问文件,都需要调用系统调用函数,然后通知内核,执行对应操作
缓冲机制
实际上,无论是数据的输入输出,还是数据的读写,都是先将数据存放在一个大小有限的内存空间中,然后再读写这个空间的数据到指定文件或者程序中。这个空间就是缓冲区,实现数据的缓存,当读写的数据满了,就会刷新缓冲区(当然还有其他方式实现缓冲区的刷新,后面有讲),也就是将数据读写出去了,恢复空的状态,继续接收下一次读写的数据。也正应如此,如果缓存区没有刷新,那么数据就不会读写完成。
例如在执行写操作时(前提是写入的数据大小不得超过缓冲区空间大小),在后面加一个死循环(目的是阻止刷新缓冲区并退出程序),那么此时虽然写操作的语句执行了,但是数据不会写到文件中。因为这些数据放到了缓冲区中,而缓冲区没有刷新,所以不能输入到文件中。
通过setbuf、setvbuf 可以手动设置缓存区的地址和缓冲区的大小,缓存类型
标准IO提供了3种类型的缓冲方式
全缓存
当缓冲区被填满,或者出现特定的条件,才会刷新缓冲区
刷新是指将内存中的数据读写。
行缓存
当缓冲区被填满,或者当输入或输出遇到新行符’\n’就会刷新缓冲区
无缓存
不进行缓存,自接刷新
fflush( )
功能:强制刷新缓冲区
参数:一个。参数1需要写入数据的文件
返回值:int类型。成功返回
用法:放在需要写操作后面,强制刷新缓冲区,将缓冲区中的内容输出。
fclose( )
/*
功能:刷新缓冲区
fclose()函数刷新文件流指针指向的流(使用fflush()写入任何缓冲输出数据),并关闭底层文件描述符。
头文件:
#include <stdio.h>
函数原型:
int fclose(FILE *stream);
参数:
参数1----FILE * stream
文件流指针。表示需要刷新并关闭的文件流
返回值:int类型
刷新并关闭文件成功----返回0
刷新并关闭文件失败----返回EOF,并设置errno来指示错误. #define EOF -1
*/
/* example.c */
#include <stdio.h>
#include <errno.h>
int main(int argc,char * argv[])
{
FILE * fp = fopen("1.txt","w+");
if(fp < 0)
{
perror("fopen failed");
return -1;
}
int ret1 = fprintf(fp,"%d %s %f\n",1,"hello world",3.14);
if(ret1 < 0)
{
perror("fprintf failed");
return -1;
}
int ret2 = fclose(fp);
if(ret2 < 0)
{
perror("fclose failed");
return -1;
}
return 0;
}
终端文件
当执行程序的时候,系统默为终端文件打开三种文件:
stdout : 终端标准输出。从程序中输出数据到终端(先输出数据到终端缓存区中,然后遇到’\n’或者缓冲区满了就将数据读取到终端 中)---------行缓存
stdin : **终端标准输入。**从终端中读取数据到程序(先读取数据到终端的缓存区中,然后遇到’\n’或者缓冲区满了就读取数据到程序中)---- ----行缓存
stderr: **终端标准错误输出。**向终端输出错误信息-------无缓存
标准IO
I标准IO库由ANSI C标准进行声明,是在系统调用函数的基础上构建的函数库。
标准IO库为应用程序访问文件提供了通用的接口,只需要调用库函数就可以通知内核完成IO(输入输出、读写)操作
标准IO库函数都是由stdio.h这个文件来进行说明的,
文件信息结构体FILE
每个被内核系统打开的文件,都会在内核中申请一段内存空间,用来存放文件的信息。而这些信息具体存放在内存中的一个结构变量中,该结构体类型是由系统声明并定义的,叫做FILE。
在应用程序中需要通过文件信息才能知道访问的文件是谁,之后才能对该文件进行操作。用FILE * 来描述结构体的地址,我问管FILE * 叫做文件流指针。
fopen( )
/*
功能:打开一个文件
头文件:
#include <stdio.h>
函数原型:
FILE * fopen(const char *pathname, const char *mode);
参数:
参数1----const char * pathname
一个常量字符串,表示需要打开文件的路径名
参数2----const char * mode
一个字符串常量,表示文件的打开方式,打开方式有以下几种(可以组合使用,例如rb、wb):
1. r:只读的方式打开文件,从文件的开头读。文件必须存在,如果不存在则打开文件失败,返回NULL
2. w:只写的方式打开文件。如果文件存在则清空文件的内容,如果不存在则创建文件
3. a:以追加写的方式打开文件。如果文件存在,在文件的末尾进行写操作。如果文件不存在就创建文件
4. r+:以读写方式打开,从文件的开头读。文件必须存在,如果不存在则打开文件失败,返回NULL
5. w+:以读写方式打开,如果文件存在则清空文件的内容,如果不存在则创建文件
6. a+:读写追加方式打开,如果文件存在,在文件的末尾进行读写操作。如果文件不存在就创建文件
7. t:文本文件方式打开文件(默认打开方式)
8. b:二进制文件方式打开
返回值:
打开文件成功--------FILE * 一个文件流指针。表示返回文件信息结构体的地址。(非空)
打开文件失败--------返回NULL,并设置错误码
*/
/* example.c */
#include <stdio.h>
#include <errno.h>
int main(int argc, char * argv[])
{
FILE * fp = fopen("1.txt","w");
if(fp == NULL)
{
perror("open 1.txt failed");
return -1;
}
return 0;
}
perror( )
/*
功能:打印错误码的描述信息字符串
头文件:
#include <stdio.h>
#include <errno.h>
函数原型:
void perror(const char *s);
参数:
参数1---const char *s
是一个字符串首地址,表示错误码的描述信息字符串
返回值:
无
*/
/* example.c */
#include <stdio.h>
#include <errno.h>
int main(int argc,char * argv[])
{
FILE * fp = fopen("1.txt","w");
if(fp == NULL)
{
perror("fopen file faile");
return -1;
}
return 0;
}
fclose( )
/*
功能:关闭打开的文件
头文件:
#include <stdio.h>
函数原型:
int fclose(FILE * stream)
参数:
参数1----FILE * stream
文件流指针。表示需要关闭的文件信息结构体地址
返回值:int类型
关闭文件成功----返回0
关闭文件失败----返回EOF,并设置错误码。 #define EOF -1
*/
/*example.c*/
#include <stdio.h>
int main(int argc,char * argv[])
{
FILE * fp = fclose("1.txt");
if(fp < 0)
{
perrorf("1.txt close failed");
return -1;
}
return 0;
}
printf系列
/*
功能:printf()系列中的函数根据如下所述的格式产生输出:
printf()将输出默认写入标准输出流stdout(终端文件);
fprintf()将输出写入给定的输出流对应的文件中;
sprintf ()将输出写入字符串str。(这里不做介绍)
头文件:
#include <stdio.h>
函数原型:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
参数:
printf():
参数1---const char * format
一个字符串首地址。表示格式化字符串的地址
参数...---可以有多个参数,根据格式化字符串来决定,表示要输出的数据(可以是变量名、常量、表达式)
fprintf():
参数1---FILE * stream
一个文件流指针。表示需要输出到的文件的信息结构体地址(输出到哪个文件中)
参数2---const char * format
一个字符串首地址。表示格式化字符串的地址
参数...---可以有多个参数,根据格式化字符串来决定,表示要输出的数据(可以是变量名、常量、表达式)
返回值:int类型
输出成功---返回打输出的字符数(不包括用于结束字符串输出的空字节)。
输出失败---返回EOF,并设置errno来指示错误. #define EOF -1
几个函数的特点:
fprintf()可以向指定文件写入数据,包括终端标准输出(stdout)
printf()默认只能给终端文件(stdout)写数据.stdout是行缓存
*/
/*example.c*/
#include <stdio.h>
#include <errno.h>
int main(int argc,char * argv[])
{
FILE * fp1;
int date1,ret1,ret2,ret3;
float date2 = 3.14;
date1 = 1;
char buf[30] = "hello \n nihao"; //该字符串中有空格符和换行符
fp1 = fopen("1.txt","w");
ret1 = printf("%d\n",date1); //输出date1的值到终端并将返回值给变量ret1
if(ret1 < 0)
{
perror("printf error");
return -1;
}
ret3 = printf("%f\n",date2); //输出date2的值到终端并将返回值给变量ret3
if(ret3 < 0)
{
perror("printf error");
return -1;
}
ret2 = fprintf(fp1,"%s\n",buf); //输出buf的内容到fp1指向的文件中并将返回值给变量ret2
if(ret2 < 0)
{
perror("fprintf error");
return -1;
}
printf("printf() 返回值ret1=%d ret3 =%d\nfprintf() 返回值=%d\n",ret1,ret3,ret2); //打印返回值
fprintf(stdout,"%s",buf); //输出buf的内容到终端标准输出文件中
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vm2lNtdv-1660745122664)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20220812172422330.png)]
从结果可以看出来,ret1 = 2,ret3 = 9,但是我们我们只输入了一个1和3.14,结果怎么是这样呢?这时候就需要返回上面看一下,printf系列返回值是什么了。返回值是输出的字符个数,如果输出字符串不算最后的空字符’\0’,所以’1’+‘\n’就是2个字符,3.14是个小数,默认保留小数点后6位,不足6位用0补上,最终加上前面的’3’和’.‘以及’\n’,一共9个字符。而 "hello \n nihao"是个字符串,字符串默认最后加了’\0’,但是printf系列函数返回值如果遇到字符串,是不算’\0’的,所以最后格式化字符串只输出了hello+空格符+‘\’+‘n’+nihao+手动输入的换行符’\n’一共5+1+1+1+5+1 = 14个字符。
查看1.txt内容可以看出,printf()遇到空格符’ ‘和换行符’\n’正常输出,遇到 “hello \n nihao”(这是一个字符串)最后面默认加到空字符’\0’就刷新缓冲区,将缓冲区中的数据输出后结束,并返回输出的字符数
scanf系列
/*
功能:格式化扫描输入
scanf()默认从终端标准输入(stdin)中格式化读取数据输入到程序中
fscanf()从文件流指针所指向的文件中格式化读取数据输入到程序中
sscanf()从字符串中格式化读取数据输入到程序中(这里不做介绍)
头文件:
#include <stdio.h>
函数原型:
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
参数:
scanf():
参数1---const char * format
一个字符串首地址。表示格式化字符串的地址
参数...---可以有多个参数,根据格式化字符串来决定,表示将格式化读取的数据输入到程序中的地址。(程序中变量的地址)
fscan():
参数1---FILE * stream
一个文件流指针。表示需要读取的文件的信息结构体地址(读取哪个文件)
参数2---const char * format
一个字符串首地址。表示格式化字符串的地址
参数...---可以有多个参数,根据格式化字符串来决定,表示要格式化读取的数据要输入到程序中的地址(程序中变量的地址)
返回值:int类型
如果成功,这些函数返回成功匹配和分配的输入数据的数量
如果失败:返回EOF。以下是错误的类型:
1.成功匹配完之前只要有一处与格式化字符串不匹配的地方,则返回值EOF。
2.如果发生读取错误,也会返回EOF
特点:
scanf函数系列遇到换行符'\n'和空格符' '都会停止匹配。
*/
/* example.c */
#include <stdio.h>
#include <errno.h>
int main(int argc,char * argv[])
{
FILE * fd1 = fopen("3.txt","r");
int date1,date3,ret1,ret2;
float date2;
char buf1[50];
char buf2[50];
char buf3[50];
char buf4[50];
printf("输入数据:\n");
ret1 = scanf("%d%f %s",&date1,&date2,buf1);
if(ret1 < 0)
{
perror("scanf failed");
return -1;
}
ret2 = fscanf(fd1,"%s%d%s",buf2,&date3,buf4);
if(ret2 < 0)
{
perror("fscanf failed");
return -1;
}
printf("打印scanf()输入的数据:\n");
printf("%d %f %s\n",date1,date2,buf1);
printf("打印fscanf()输入的数据:\n");
printf("%s%d%s\n",buf2,date3,buf4);
printf("scanf()返回值 = %d\nscanf()返回值 = %d\n",ret1,ret2);
return 0;
}
问题:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qF0TxMKW-1660745122665)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20220812175209760.png)]
fgetc( )、getc( )、getchar( )、fgets( )
/*
功能:读取字符或者字符串
fgetc()从流中读取下一个字符,并将其作为unsigned char类型转换为int类型返回(ASCII码),或在文件结束或发生错误时返回 EOF。
getc()等价于fgetc(),只是它可以作为一个对流进行多次计算的宏来实现。
getchar()等价于getc(stdin)。
gets()从流中最多读入size-1个字符,并将其存储到第一个参数指向的缓冲区中。读取失败或换行符'\n'后停止。如果读取换行 符'\n',就将这个换行符'\n'存储在缓冲区中。一个终止null字节('\0')存储在缓冲区的最后一个字符之后。
头文件:
#include <stdio.h>
函数原型:
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
char *fgets(char *s, int size, FILE *stream);
参数:
fgetc()
参数1---FILE *stream
一个文件流指针。表示需要从那个文件中读取一个字符
getc()
参数1---FILE *stream
一个文件流指针。表示需要从那个文件中读取一个字符
getchar()
无参数----默认是从终端标准输入读取一个字符
fgets()
参数1----char *s
一个字符指针。表示从文件中读取的数据需要存放在那个缓存区地址中。一般是一个字符数组空间用来存储读取到的数据
参数2----int size
一个整形数据。表示想要从文件中读取的数据大小。实际最大能读取size - 1个字符,最后一个字符后面要加空字符'\0'
参数3----FILE * stream
一个文件流指针。表示从哪个文件中读取数据
返回值:
fgetc()、getc()和getchar()返回读取的无符号字符,在文件结束或发生错误时转换为int或EOF(-1)。(也就是返回字符对应的ASCII 码),这个-1同样是要返回到字符缓存区中的。
fgets()成功时返回存储数据的缓冲区地址,错误时返回NULL,或者文件结束时没有读取任何字符。
fgetc()
特点:不会遇到' '(空格字符)或者'\n'(换行字符)就停止读取,或者读取失败。这些字符和普通字符一样读取,只有读到最后没有 字符了,也就是空字符'\0'才读取失败。
fgets()
特点:该函数遇到'\n'(读走)或者读到size - 1 的一个字符时停止读取,在最后一个字符后面添加'\0'(空字符)
注意fgets()的一些注意事项:
1. 当待读取字符个数大于size-1时,只读取前size-1个字符(前提是中途没有换行符'\n'),最后一个空间加'\0'--- -----完成此次读取
2. 还未到最后一行,当待读取的字符个数小于size-1时,此时待读取的字符串最后一个字符是'\n',需要读取,然后 再'\n'之后加上'\0'----------完成此次读取
3. 读最后一行,当待读取的字符个数小于size-1时,此时待读取的字符串最后没有'\n',所以只需要将字符读入,然后在最 后一个字符后面加上'\0'-------------此次读取完成
4. 当上一次读取完成之后,文件指针指向了文件末尾,下一次读取将会失败,返回NULL
5. 每次读取完都不会清空缓冲区内此次读取的字符,下一次读取是直接从头开始覆盖,所以当待读取的字符的个数N小于size- 1的时候,只会从缓存区起始地址开始覆盖N个字符,然后再第N的字符后面赋值为'\0'(读取最后一行的情况)或者'\n' '\0'(读取不是最后一行的情况),其余的缓冲区空间任然保留上次读取的内容。printf()打印的时候,遇到'\0'就停 止打印,所以上次读取到的内容无法打印出来。
*/
/* 在练习中有例子 */
fputc( ) 、fputs( )、putchar( )、putc( )、puts( )
/*
功能:向文件中写入数据
fputc()将一个字符写入到文件流指针指向的文件中。
fputs()将字符串写入流,不包含终止的空字节('\0')。
putc()等同于fputc(),此外它可以被实现为一个对流进行多次计算的宏。
putchar()等价于putc(c,stdout)。
puts()将字符串和尾随的换行符写入stdout。
头文件:
#include <stdio.h>
函数原型:
int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
int puts(const char *s);
参数:
fputc():
参数1----int c
int类型。表示需要输入的字符ASCII码,不过会被转换为unsigned char 的字符。
参数2----FILE * stream
文件流指针。表示输出到文件流指针指向的文件。
fputs():
参数1----const char *s
常量字符指针。表示需要输入的字符串的地址。
参数2----FILE * stream
文件流指针。表示输出到文件流指针指向的文件。
putc():
参数1----int c
常量字符指针。表示需要输入的字符串的地址。
参数2----FILE * stream
文件流指针。表示输出到文件流指针指向的文件。
putchar():
参数1----int c
int类型。表示需要输入的字符ASCII码,不过会被转换为unsigned char 的字符。
puts():
参数1----const char *s
常量字符指针。表示需要输入的字符串的地址。
返回值:
fputc()、putc()和putchar()返回被写入为无符号字符的字符(无符号字符和unsigned int类型其实是一样的),错误时转换为int或EOF。
puts()和fputs()成功时返回一个非负数,错误时返回EOF。
所以在判断该类函数是否写入数据成功,只需要判断返回值是否大小于0,如果小于0就表示失败,否者成功
*/
/*例子在练习中*/
fread( )
/*
头文件:
#include <stdio.h>
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:
参数1----void *ptr
存放读取出来数据的内存空间地址
参数2----size_t size
size_t 为 unsigned int类型,表示一次读取文件内容的块数据大小
参数3----size_t nmemb
读取文件的块数,
读取文件数据总字节大小为:size * nmemb
参数4----FILE *stream
文件流指针。表示已经打开的文件指针
返回值:
成功----返回实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。这个数字等于仅当size为1时传输的字 节数
失败----如果发生错误,或到达文件末尾,返回值是一个短条目计数(或零)。
流的文件位置指示器根据成功读写的字节数向前移动
*/
/* example.c */
#include <stdio.h>
#include <errno.h>
int main(int argc,char * argv[])
{
FILE * fp = fopen("1.txt","r");
char buf[20];
int block_num = fread(buf,1,sizeof(buf),fp);
if(block_num <= 0)
{
perror("fread failed");
return -1;
}
printf("%s\n",buf);
printf("fread()返回值 = %d\n",block_num);
return 0;
}
运行结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IbhNYc6v-1660745122666)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20220813151400000.png)]
分析:fread()函数读取了指定的20个字节,注意:空格符’ ‘和换行符’\n’也是一个字符,也需要算上
fwrite( )
/*
头文件:
#include <stdio.h>
函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
参数1----const void *ptr
一个万能指针。表示需要哪个地址中的数据写入文件中
参数2----size_t size
size_t 为 unsigned int类型,表示一次写入文件内容的块数据大小
参数3----size_t nmemb
size_t 为 unsigned int类型。表示写入文件的块数
写入文件数据总字节大小为:size * nmemb
参数4----FIEL *stream
已经打开的文件指针。表示需要将数据写入到哪个文件中
返回值:
成功----返回实际写入文件数据的块数目。这个数字等于仅当size为1时传输的字节数
如果发生错误,或到达文件末尾----返回值是一个短条目计数(或零)。
流的文件位置指示器根据成功读写的字节数向前移动。
*/
/* example.c */
#include <stdio.h>
#include <errno.h>
int main(int argc,char * argv[])
{
FILE * fp1 = fopen("1.txt","r");
char buf[20];
int i = 0;
while(1)
{
int block_num = fread(buf,1,sizeof(buf),fp1);
printf("第%d次读取的数据个数为:%d\n",i,block_num);
if(block_num <= 0)
{
printf("fread finished\n");
return -1;
}
//int num = fwrite(buf,1,sizeof(buf),stdout);
int num = fwrite(buf,1,block_num,stdout); //改成这样就不会出现下图中的现象
printf("第%d次写入的数据个数为:%d\n",i,num);
if(block_num <= 0)
{
printf("fwrite finished\n");
return -1;
}
i++;
}
return 0;
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RwOrV31O-1660745122667)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20220813163054319.png)]
分析:要将1.txt中的内容读出到缓冲区buf中,然后将缓冲区buf中的数据写到标准输出文件(终端)。从运行结果可以看出有些不对劲,有部分内容重复了。这是什么情况?其实这是由于feard()函数每次读取完数据到缓冲区,并不会刷新缓存区内容,而是简单地从头覆盖缓冲区数据。fread最后一次读取输就只读了’6’,倒数第二次读取是从hello的第二个‘l’开始读取,读取到6786的8这个位置。所以最后一次读取到的’6’和后面的换行符’\n’会覆盖缓冲区中的’l’和‘o’,然后fwrite()输出整个缓冲区内容,所以出现了如图的情况
怎么解决呢?
只需要将fwrite()中的第三个参数改为读到多少写多少就行了。修改之后的结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6IkHyZw-1660745122668)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20220813164042570.png)]
文件的类型
普通文件(文本文件和二进制文件)、符号链接文件、管道文件、套接字文件、字符文件设备、块设备、目录文件
文件IO
系统调用IO函数的特点:
-
系统调用函数,不再是库函数的一部分,而是系统的一部分。
-
没有用户读写缓冲区,在内核内部有读写缓冲区。标准IO有用户缓冲区的目的:减少内核读写操作文件的次数。
-
文件IO每次读写都要调用内核的文件操作的系统调用,每次都要从文件中读写
文件描述符
文件表述符是非负整数,是文件的标识。
用户使用文件描述符(File description)来访问文件。
里用opem()打开一个文件,内核会返回一个文件描述符。
每个进程都会有一张文件描述符的表,进程刚被创建的时候,标准输入、标准输出、标准错误输出设备文件都会被打开,对应的文 件描述符分别是0,1,2,这些文件描述符被记录在表中。
当进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将次文件描述符记录在表中。所
注意:Linux中一个进程最多打开NR_OPEN_DEFAULT(表示1024 )个文件,所以当文件不在被使用时,需要用close()关闭文件。如果0,1,2这几个文件描述符在程序中没有作用的话,也可以关闭,将他们作为其他刚打开的文件的文件描述符,因此说新创建的文件描述符不一定是当前文件描述符列表中最大的。
open( )
/*
功能:以指定方式打开指定文件,并返回一个文件描述符,用于后续系统调用来找到该文件
open()系统调用打开由pathname指定的文件。如果指定的文件不存在,它可以由open()创建(如果O_CREAT在flags中指定)。
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
参数1----char *pathname
一个字符串地址。表示需要打开文件的路径
参数2----int flags
int类型。表示文件描述标志,说明文件打开方式.
其中部分文件描述符标志(这里就简单说明):
1. O_RDONLY
2. O_WRONLY
3. O_RDWR (前三个比选一个,后面的属性看情况加)
4. O_APPEND
以追加方式打开文件。在每次写之前,文件偏移量被放置在文件的末尾。修改文件偏移量和写操作作为一个单独的原子步骤执 行,利用lseek()函数。
5. O_ASYNC
启用信号驱动的I/O:当文件描述符上的输入或输出变为可能时,生成一个信号(默认为SIGIO,但可以通过fcntl()更改)。
6. O_CREAT
创建一个普通文件,如果文件不存在就创建。mode参数指定创建新文件时要应用的文件模式位[1]。如果O_CREAT和O_TMPFILE 都没有在flags中指定,那么mode将被忽略指定为0,或直接省略)。如果O_CREAT或O_TMPFILE在flags中指定,则必须提供 mode参数;如果没有提供,则从堆栈中获取任意字节将被应用为文件模式。有效模式由进程的umask以通常的方式修改[2]:在没 有默认ACL的情况下,创建文件的模式为(mode & ~umask)。注意,mode只适用于未来对新创建的文件的访问;创建只读文件的 open()调用很可能返回一个读/写文件描述符。
[1].文件模式位
所谓的文件模式位,就是将一个32位的int类型的数据的指定几个位置,用来描述一个模式。例如这里描述文件权限就是用 到了第0~8位来表示。每三位表示一组权限,位数由高到低分别是:用户权限、用户组权限、其他用户权限。每一组都有 读(r)、写(w)、执行(x)权限,每一个权限占一位二进制位,因此文件的权限可以用八进制形式来表述。
[2].修改文件权限掩码
跳转文章的到umask()函数部分,有讲解。
7. O_EXCL--------与O_CREAT一起用,判断文件是否存在
确保这个调用创建了文件:如果这个标志与O_CREAT一起指定,并且pathname已经存在,那么open()会失败,并报错EEXIST。
防止多个进程同时创建同一个文件
8. O_TRUNC--------如果文件存在,就将文件内容清空
如果文件已经存在并且是一个普通文件并且访问模式允许写入(即O_RDWR或O_WRONLY),它将被截断为长度为0。如果文件是 FIFO或终端设备文件,O_TRUNC标志将被忽略。否则,O_TRUNC的作用不指定。
9. O_NOCTTY------如果是终端,当做普通文件操作。那么该终端不会称为调用open()的那个进程的终端
如果pathname指向终端设备,它将不会成为进程的控制终端,即使进程没有控制终端。
参数3----mode_t mode
mode_t 类型。表示文件的权限。该选项只有使用了文件描述符O_CREAT之后才会设置,不然被忽略。
实际权限: (mode & ~umask) umask:文件权限掩码,为了防止普通用户设置的文件权限过高
返回值
open()的返回值是一个文件描述符,一个小的非负整数,用于后续的系统调用(read()、write()、lseek()、fcntl()等)来引用 open文件。
成功调用返回的文件描述符--------这个文件描述符是当前进程未打开的编号最低的文件描述符。
[*]文件描述符
文件描述符:在内核中,每打开一个应用程序,就为其设计了一个线性表,该线性表的每一个元素就表示该应用程序打开的一个文件。这 个设计的大小就是1024字节。这样的话内核只需要将文件在线性表中的节点下标返回给应用程序,应用程序就知道操作的文件是哪个。
一般非负整数标识符都是从3开始,因为打开文件就会默认打开标准输入文件stdin(0)、标准输出文件stdout(1)、标准错误输出文件 stderr(2)
*/
/* example.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char * argv[])
{
int fd = open("1.txt",O_WRONLY | O_CREAT,0664);
}
open("1.txt",O_WRONLY | O_TRUNC | O_CREAT ,0777);
实际的文件权限位(0777 & ~ 0002)
111 111 111 & ~(000 000 010)== 111 111 101
所以实际文件的权限是0775
在系统调用函数底层,实现权限是利用一个32位来描述的。从低位起,每一位表示一个权限。1表示有该权限,0表示没有该权限。想要数据date第N位为1,就利用date & (1<< N)
close( )
/*
功能:Close()关闭文件描述符,这样它就不再指向任何文件,可以被重用。所关联的文件上持有的所有记录锁进程,将被删除。
头文件:
#include <unistd.h>
函数原型:
int close(int fd);
参数:
参数1----int fd
int类型数据。表示需要关闭的文件表述符
返回值:int类型
如果成功----Close()返回0
如果发生错误----返回-1,并适当地设置errno。
*/
/* example.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
int main(int argc,char * argv[])
{
int fd = open("1.txt",O_WRONLY | O_CREAT,0664);
if(fd < 0)
{
perror("open failed");
return -1;
}
if(close(fd) < 0)
{
perror("close failed");
return -1;
}
return 0;
}
read( )、write( )
/*
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
功能:
read()尝试从文件描述符fd引用的文件中读取数据到buf指向的缓冲区中。
对于支持查找的文件,读取操作从文件偏移量开始,并且文件偏移量按读取的字节数递增。如果文件偏移量位于或超过文件末尾,则不读 取字节,并且read()返回零。
Write()将从buf指向的缓冲区写入数据到文件描述符fd引用的文件。
参数:
read():
参数1----int fd
一个文件描述符。表示引用哪个文件读取数据
参数2----void *buf
万能指针。表示读取的数据存放的缓冲区的地址
参数3----size_t count
size_t 类型。表示一次想要读取的字节数。实际一次读取的字节数可能小于该值
write():
参数1----int fd
一个文件描述符。表示引用哪个文件写入数据
参数2----void *buf
万能指针。表示需要写入的数据存放的缓冲区的地址
参数3----size_t count
size_t 类型。表示一次想要写入的字节数。实际一次读取的字节数可能小于该值
返回值:size_t类型
本质是unsgned int类型。表示一次实际读取的字节数
如果成功----则返回读取/写入的字节数(0表示读取文件结束)
如果发生错误----则返回-1,并适当地设置errno来指示错误的原因。
*/
/* example.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
char date[100] = {
0};
int fd = open("1.txt",O_RDONLY);
if(fd < 0)
{
perror("open failed");
return -1;
}
while(1)
{
int realy_size = read(fd,date,10);
if(realy_size < 0)
{
perror("read failed");
return -1;
}
if(realy_size == 0)
{
printf("read finished\n");
return 0;
}
int num = write(1,date,realy_size); //读多少写多少
if(num < 0)
{
perror("write failed");
return -1;
}
}
return 0;
}
lseek( )
/*
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
off_t lseek(int fd, off_t offset, int whence);
功能:
设置文件的偏移量
参数:
参数1----int fd
文件描述符。表示需要设置偏移量的文件
参数2----off_t offset
实质是一个signed int类型。表示偏移量大小(字节为单位)。大于0表示从第3个参数指定的位置向文件尾方向偏移指定大小的位置,小于 0表示从第3个参数指定的位置向文件头方向偏移指定大小的位置。等于0就在第3个参数指定的位置不偏移。
参数3----int whence
int类型。表示设置文件的起始位置。有三种选择选其一:
SEEK_SET:表示设置偏移位置为文件头
SEEK_CUR:表示设置偏移位置为文件当前位置
SEEK_END:表示设置偏移位置为文件尾
返回值:
成功完成后,lseek()返回从文件头开始算起到文件偏移位置设置完成后的位置,一共有多少位置,以字节为单位。
如果发生错误,返回值(off_t) -1,并设置errno来指示错误。
*/
/* example.c */
#include <sys/tpyes.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char * argv[])
{
int fd = open("1.txt",O_RDONLY);
int fd = open("2.txt",O_WRONLY | O_CREAT | O_TRUNC); //文件如果存在就清空,如果不存在创建
int fd = open("1.txt",O_WRONLY | O_CREAT | O_EXCL); //文件如果已经存在,则报错,不存在则创建
int num = lseek(fd,10,SEEK_SET); //从文件开头向文件尾方向偏移10个字节
int num = lseek(fd,10,SEEK_CUR); //从文件当前位置向文件尾方向偏移10个字节
int num = lseek(fd,0,SEEK_END); //设置当前文件位置为文件尾部。可以用来求文件大小 = num
int num = lseek(fd,-10,SEEK_END); //从文件尾向文件头方向偏移10个字节
}
**注意:**偏移到需要的位置后,如果是写入数据操作的话,