文件IO与进程(详细解读+简单练习)

IO与进程

IO

IO:就是输入(input)输出(output)。是针对程序的输入输出

在磁盘中的文件只能是Linux内核中的文件系统进行操作,内核提供一些固定的操作方式------函数(通知),这些函数在shell解释器中声明,当应用程序访问磁盘中的文件时,可以调用函数通知内核,然后让内核进行操作。

输入:从文件输入到程序(的内存空间)

输出:从程序(的内存空间)输出到文件中

注意:输入输出是对程序而言

读(出):read,从文件中读取内容到程序(的内存空间)

写(入):write,从程序的内存空间写入到文件中。

注意:读写是对文件而言

IO函数分类

  1. 系统调用函数

    这是一种操作系统提供的功能函数,不同系统函数的形式不同。这些函数也是操作系统的一部分,是应用程序访问内核的接口

  2. 库函数

    库函数是为了实现某个功能封装的接口集合

    它提供了一种统一的编程接口。其实程序调用库函数访问文件,都需要调用系统调用函数,然后通知内核,执行对应操作

缓冲机制

实际上,无论是数据的输入输出,还是数据的读写,都是先将数据存放在一个大小有限的内存空间中,然后再读写这个空间的数据到指定文件或者程序中。这个空间就是缓冲区,实现数据的缓存,当读写的数据满了,就会刷新缓冲区(当然还有其他方式实现缓冲区的刷新,后面有讲),也就是将数据读写出去了,恢复空的状态,继续接收下一次读写的数据。也正应如此,如果缓存区没有刷新,那么数据就不会读写完成。

例如在执行写操作时(前提是写入的数据大小不得超过缓冲区空间大小),在后面加一个死循环(目的是阻止刷新缓冲区并退出程序),那么此时虽然写操作的语句执行了,但是数据不会写到文件中。因为这些数据放到了缓冲区中,而缓冲区没有刷新,所以不能输入到文件中。

通过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个字节
}

**注意:**偏移到需要的位置后,如果是写入数据操作的话,

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值