04-文件IO和标准IO

IO

什么是IO

#include <stdio.h>

std:standard 标准的
IO:input output
i:输入,数据从外部存储设备输入到内存中
o:输出,数据从内存到外部存储设备

存储设备:
1.硬件:机械硬盘,固态硬盘
2.内存:SDROM DDR4
总结:IO就是数据从硬盘到内存,内存到硬盘的流动

IO分类

1.文件IO
文件IO是由系统提供的基本IO函数,是与系统绑定的,又称之为系统调用

1.文件IO的复用性低
2.文件IO涉及到用户空间到内核空间的切换,cpu模式切换,C代码调用汇编指令等等。属于一种耗时操作,应该尽量减少文件IO的使用
3.每调用一次文件IO,就会进行一次空间切换

2.标准IO
标准IO是根据ANSI标准,对文件IO进行了二次封装(scanf printf)

if(操作系统 == windows){
	w_input;
}else if(操作系统 == ubuntu){
	l_input;
}

思考:既然有了文件IO,为什么还需要标准IO?
答案:为了达到代码的可移植性,让代码的复用性更高

作用:
1.提高代码的可移植性和复用性
2.提高代码的输入输出效率
设置了一个缓冲区,缓冲区满或者满足一定条件后,陷入到内核中,由内核完成对硬件的操作,大大减少了对文件IO的调用次数。

2.标准IO

1.流和流指针

1.概念
流(stream):将数据一个一个地移入或移出文件的形式,叫做字节流;
流指针(FILE*):每打开一个文件,就会在内存中申请一片空间(缓冲区),管理这片内存空间的变量都存储在FILE结构体中,FILE结构体由系统定义的,我们直接拿来用就好。

2.查看FILE结构体成员

$ vi -t FILE #可以用于查看系统提供的数据类型,结构体,变量,宏定义
输入1
struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags.
#define _IO_file_flags _flags
                                                                   
  /* The following pointers correspond to the C++ streambuf protoco
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields direct
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */ 缓冲区的起始地址
  char* _IO_buf_end;    /* End of reserve area. */ 缓冲区的结尾地址
  /* The following fields are used to support backing up and undo. 
  char *_IO_save_base; /* Pointer to start of non-current get area.
  char *_IO_backup_base;  /* Pointer to first valid character of ba
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
安装vim: 	vimconfig.tar.bz2
	1.	tar –xvf vimconfig.tar.bz2
	2. 	执行 ping ww.baidu.com 	保证有网络
	3.	sudo apt-get install vim 		安装vim
	4. 	./config.sh 				进入解压目录(vimconfig)

3.标准输入输出流

在main函数启动之前,会默认打开三个流指针:
stdin -----标注输入-----从终端获取数据
stdout -----标准输出-----将数据输出到终端
stderr -----标准错误输出

2.标准IO函数

1.常见的标准IO函数

fopen	/fclose	打开/关闭一个文件
fprintf /fscanf 将标准格式化数据输出到文件中/从文件标准格式化获取数据
fputc   /fgetc  将单个字符输出到文件中/从文件中获取单个字符
fputs   /fgets  将字符串输出到文件中/从文件中获取字符串
fwrite  /fread  将数据的二进制数输出/输入到文件中
fseek           偏移文件指针

2.man手册

$man man #查看man手册
 1   Executable programs or shell commands #linux的shell指令
 2   System calls (functions provided by the kernel)  #Linux的系统调用(文件IO)
 3   Library calls (functions within program libraries) #Linux的库调用(标准IO)

3.标准IO函数的使用

1.fopen

fopen
功能:打开一个文件
头文件:
#include <stdio.h>
原型:
FILE *fopen(const char *path, const char *mode);
参数:
char *path:指定要打开的文件的路径
char *mode:文件的打开方式
返回值:
成功返回打开的流指针
失败返回NULL,更新errno

r      Open text file for reading.  The stream is positioned at the beginning of the file.
//以只读的方式打开;
r+     Open for reading and writing.  The stream is positioned at the beginning of the file.
//以读写的方式打开,如果文件不存在,打开文件失败;
w      Truncate file to zero length or create text file for writing.  The stream is positioned at the beginning of the file.
//以写的方式打开,如果文件不存在,则创建该文件;如果文件存在,则清空文件。
w+     Open for reading and writing.  The file is created if it does not exist, otherwise it is  truncated.   The  stream  is positioned at the beginning of the file.
//以读写的方式打开,如果文件不存在,则创建该文件;如果文件存在,则清空文件。
a      Open  for  appending (writing at end of file).  The file is created if it does not exist.  The stream is positioned at the end of the file.
//以写的方式打开文件,如果文件不存在,则创建文件;如果文件存在,则以追加的方式写文件;
a+     Open for reading and appending (writing at end of file).  The file is created if it does not exist.  The initial  file position for reading is at the beginning of the file, but output is always appended to the end of the file.
//以读写的方式打开文件,如果文件不存在,则创建文件;如果文件存在,则以追加的方式写文件;
//fopen
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./fopen","w");
	if(p == NULL){
		printf("打开失败\n");
		return -1;
	}
	printf("打开成功\n");
	fclose(fp);
}
2.perror

功能:通过errno(错误码),打印错误信息
头文件:
#include <stdio.h>
原型:
void perror(const char* s);
参数:
char* s:用于提示的字符
#include <errno.h>
int errno
errno:本质上是一个全局变量,对文件进行操作的时候,会出现各种错误,errno中已经定义好了各种数值,与错误相对应
位置:/usr/include/asm-generic/errno.h errno-bash.h

//perror,errno
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./fopen","r");
	if(p == NULL){
		printf("打开失败\n");
		printf("%d\n",errno);
		perror("fopen");
		return -1;
	}
	printf("打开成功\n");
	fclose(fp);

}
3.fclose

功能:关闭一个文件;
头文件:
#include <stdio.h>
原型:
int fclose(FILE* fp);
参数:
FILE *fp:指定要关闭的文件流指针
返回值:
成功,返回0
失败,返回-1,更新errno

//fclose
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./fopen","r");
	if(p == NULL){
		perror("fopen");
		return -1;
	}
	printf("打开成功\n");
	int ret = fclose(fp);
	if(ret < 0){ //if(fclose(fp) < 0)
		printf("关闭失败\n");
	}else{
		printf("关闭成功\n");
	}
}

思考:能否对一个文件进行重复fopen和fclose
答案:不能。
1.调用fopen时,会去堆空间申请一片缓冲区,并且返回的流指针中储存了这片缓冲区的首地址
2.不要重复打开一个文件,会造成资源浪费,而且,Linux内核文件的打开次数是有限制的(1024个)
3.调用fclose时,会通过free释放指定的堆空间,再次调用fclose,会造成重复释放。

4.fprintf

功能:将标准格式化数据输出到文件中
头文件:
#include <stdio.h>
原型:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
参数:
FILe *stream:文件流指针
const char *fromat:格式化数据
...:不定参数
返回值:
成功,返回输出的数据个数
失败,返回负数,更新errno

//fprintf
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./test.c","w");
	if(p == NULL){
		perror("fopen");
		return -1;
	}
	printf("打开成功\n");
	//将"hello"输出到文件中
	if(fprintf(fp,"hello") < 0){
		perror("fprintf");
		return -1;
	}
	
	if(fclose(fp) < 0){
		perror("fclose");
		return -1;
	}
	printf("关闭文件\n");
}
5.fscanf

功能:从文件中标准格式化获取数据
头文件:
#include <stdio.h>
原型:
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
参数:
FILe *stream:文件流指针
const char *fromat:格式化数据
...:不定参数
返回值:
成功,返回获取的数据个数。大于等于0
失败,或读取到文件结尾,返回EOF(其实就是-1),更新errno

#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./test.c","r");
	if(p == NULL){
		perror("fopen");
		return -1;
	}
	
	char buf[12] = "";
	char buf1[12] = "";
	scanf("%s%s",buf,buf1);
	printf("%s \n",buf,buf1);
	//按照"%s %s"获取文件内容,分别存入到buf和buf1
	fscanf(fp,"%s %s",buf,buf1);
	
	fclose(fp) < 0
}
6.fputc

功能:将单个字符输出到文件中
头文件:
#include <stdio.h>
原型:
int fputc(int c, FILE *stream);
参数:
int c:指定输出的字符
FILe *stream:文件流指针
返回值:
成功,返回输出字符的ASCII码
失败,返回EOF,更新errno

//fputc
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./test.c","w");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	if(fputc('a',fp) < 0){
		perror("fputc");
		return -1;
	}
	fclose(fp);
}
7.fgetc

功能:从文件中获取单个字符
头文件:
#include <stdio.h>
原型:
int fgetc(FILE *stream);
参数:
FILe *stream:文件流指针
返回值:
成功,返回获取到的字符,从字符类型转化为int类型
失败,返回EOF,更新errno

//fgetc
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp;
	fp = fopen("./test.c","w+");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	if(fputc('a',fp) < 0){
		perror("fputc");
		return -1;
	}
	int ret = fgetc(fp);
	if(ret < 0){
		printf("%d\n",ret);//输出-1
		perror("fgetc"); //succeed,未关闭的文件结尾默认加EOF
		return -1;
	}
	printf("%d\n",ret);
	
	fclose(fp) < 0
}

文件结尾默认加\nEOF

练习
1.计算文件的大小
2.写一个函数,计算一个文件总共有几行;(提示:判断文件有几个’\n’),就算是文件的结尾也会有个换行符
3.实现一个文件的拷贝,例如将a.txt中的内容,拷贝到b.txt

//计算文件大小
#include <stdio.h>
int getsize(FILE* fp){
	int size = 0;
	while(fgetc(fp) != EOF){
		szie++;
	}
	return size;
}
int main(int argc, const char *argv[])
{
	if(argc < 2){
		printf("Usage %s filename\n",argv[0]);
		return -1;
	}
	//打开文件
    FILE *fp = fopen(argv[1],"r");
    if(NULL == fp){
        perror("fopen");
        return -1; 
    }   
    //计算文件大小
    int szie = getsize(fp);
    printf("size = %d\n",size);
	//关闭文件
    fclose(fp);
    return 0;
} 
//计算文件行数
#include <stdio.h>
int mygetline(FILE* fp){
	int line = 0,res;
	while((res = fgetc()) != EOF){
		if(res == '\n')
			line++;
	}
	return line;
}
int main(int argc, const char *argv[])
{
	if(argc < 2){
		printf("Usage %s filename\n",argv[0]);
		return -1;
	}
	//打开文件
    FILE *fp = fopen(argv[1],"r");
    if(NULL == fp){
        perror("fopen");
        return -1; 
    }   
    //计算文件行数
    int line = mygetline(fp);
    printf("line = %d\n",size);
	//关闭文件
    fclose(fp);
    return 0;
} 
//拷贝文件
#include <stdio.h>
void my_copy(FILE* src,FILE* dest){
    int res;
    while((res = fgetc(src)) != EOF){
        fputc(res,dest);
    }   
}
int main(int argc, const char *argv[])
{
    if(argc < 3){ 
        printf("Usage %s file_src file_dest\n",argv[0]);
        return -1; 
    }   
    FILE *src,*dest;
    src = fopen(argv[1],"r");
    if(NULL == src){
        perror("fopen");
        return -1; 
    }   
    dest = fopen(argv[2],"w");
    if(NULL == dest){
        perror("fopen");
        return -1; 
    }   
	//拷贝文件
    my_copy(src,dest);

    fclose(src);
    fclose(dest);
    return 0;
}  
8.缓冲区

标准IO的内容都是存储在缓冲区中,然后输出到硬件中

  • 1.全缓冲

操作对象:对普通文件进行操作(即通过fopen打开的文件),通过FILEfp流指针维护一个4K大小的缓冲区
大小:4K=4
1024=4096 或 fp->_IO_buf_end - fp->_IO_buf_base = 4096

//全缓冲
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE* fp = fopen("./test.c","r");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	//由于系统优化,如果只打开文件,不操作的话,并不会真正的去申请空间
	fputc('a',fp);
	int size = fp->_IO_buf_end - fp->_IO_buf_base;
	printf("%d\n",size);
	fclose(fp) < 0
}
  • 刷新缓冲区
    • 1)缓冲区满
    • 2)用fflush强制刷新缓冲区
    • 3)关闭流指针的时候刷新
    • 4)程序正常退出的时候刷新(return)
    • 5)调用exit函数 – 退出整个进程

fflush
功能:强制刷新缓冲区
头文件:
#include <stdio.h>
原型:
int fflush(FILE *stream);
参数:
FILe *stream:文件流指针
返回值:
成功,返回0
失败,返回-1,更新errno

exit
功能:退出进程
头文件:
#include <stdlib.h>
原型:
void exit(int status);
参数:
int status:可以输入任意int类型数据

#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./test.txt","w");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	putc('a',fp);
	fflush(fp); //刷新缓冲区,a直接写进去
	while(1){
		if(i<4096)
			putc('b',fp);
		i++;
	} //缓冲区刚好为4096,不刷新
	fclose(fp);
	return 0;
}
  • 2.行缓冲

操作对象:标准输入(stdin)和标准输出(stdout)
大小:1K 或 fp->_IO_buf_end - fp->_IO_buf_base = 1024byte
关闭fclose(stdout)

  • 刷新缓冲区
    • 1)缓冲区满
    • 2)用fflush强制刷新缓冲区
    • 3)关闭流指针的时候刷新
    • 4)程序正常退出的时候刷新(return)
    • 5)调用exit函数 – 退出整个进程
    • 6)遇到’\n’刷新
//这个程序需加#if #elif #endif 后执行
#include <stdio.h>
#include <stdlib.h> //exit
int main(int argc, const char *argv[])
{
	//1.缓冲区满
	int i=0;
	while(i<1024+1){ //比缓冲区多才能输出
		fputc('a',stdout);
		i++;
	}
	//2.fflush强制刷新
	fputc('b',stdout);
	fflush(stdout);
	//3.关闭流指针
	fputc('a'.stdout);
	fclose(stdout);
	//4.程序正常退出
	fputc('a'.stdout);
	return 0;
	//5.调用exit函数
	fputc('a',stdout);
	exit(1);
	//6.遇到'\n'
	printf("fff\n");//putc('\n',stdout);
	
	while(1);
	return 0;
}
  • 3.无缓冲

操作对象:标准错误输出(stderr)
大小:0,无缓冲,perror调用的就是stderr

#include <stdio.h>
int main(int argc, const char *argv[])
{
	fprintf(stderr,"hello world");
	while(1){
	return 0;
}
9.fputs

功能:将字符串输出到文件中
头文件:
#include <stdio.h>
原型:
int fputs(const char *s, FILE *stream);
参数:
char *s:指定要输出的字符串首地址
返回值:
成功,返回0
失败,返回-1,更新errno

10.fgets

功能:从文件中获取字符串
头文件:
#include <stdio.h>
原型:
char *fgets(char *s, int size, FILE *stream);
参数:
char *s:存储获取到的字符串
int size:从文件中读取 size-1 个字符,因为要保留’\0’;注意:如果一行不足size个,则读取个数为一行的实际个数;
返回值:
成功,返回char* s
失败,返回NULL,更新errno

#include <stdio.h>
int main(int argc, const char *argv[])
{
	//通过fopen打开的文件是全缓冲
	FILE *fp = fopen("./test.txt","w+");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	//文件指针已经移动到d之后
	if(fputs("hello world",fp) < 0){
		perror("fputs");
		return -1;
	}
	//关闭再打开让文件指针指向头
	fclose(fp);
	FILE *fp = fopen("./test.txt","r");
	//fseek(fp,0,SEEK_SET);直接调用fseek函数比较方便
	char buf[10] = "";
	fgets(buf,10,fp);
	printf("%s\n",buf);//hello wor
	fclose(fp);
	return 0;
}
11.fwrite

功能:将数据的二进制形式写入文件中
头文件:
#include <stdio.h>
原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
void *ptr:要输出的数据地址,可以是任意类型的数据;
size_t size:以size个字节为单位输出。例如:
如果要输出的是int类型数据,size == 4;
如果要输出的是char类型数据,size == 1;
如果要输出的是自定义结构体类型,struct aa ,size == sizeof(aa);
size_t nmemb:要输出的数据个数;
FILE *stream:流指针;
返回值:
成功,返回输出的数据个数,其实就是nmemb;
失败,返回-1,更新errno;

//fwrite,写入的是二进制
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./test.txt","w");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	int arr[4] = {1,2,3,4};
	if(fwrite(arr,sizeof(int),sizeof(arr),fp) < 0){
		perror("fwrite");
		return -1;
	}
	printf("fwrite succeed\n");
	fclose(fp);
	return 0;
}
12.fread

功能:从文件中读取二进制数据;
头文件:
#include <stdio.h>
原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
void *ptr:成功读取数据后,数据存储在该位置;
size_t size:以size个字节为单位输出。例如:
如果要输出的是int类型数据,size == 4;
如果要输出的是char类型数据,size == 1;
如果要输出的是自定义结构体类型,struct aa ,size == sizeof(aa);
size_t nmemb:要输出的数据个数;
FILE *stream:流指针;
返回值:
成功,返回读取的数据个数,其实就是nmemb;
失败,返回-1,更新errno;

//fwrite,写入的是二进制
#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./test.txt","r");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	int arr[4];
	if(fread(arr,sizoef(int),3,fp) < 0){
		perror("read");
		return -1;
	}
	printf("fread succeed\n");
	int i=0;
	for(i=0;i<3;i++)
		printf("%d ",arr[i]);
	putchar(10);
	fclose(fp);
	return 0;
}
13.fseek

功能:偏移流指针;
头文件:
#include <stdio.h>
原型:
int fseek(FILE *stream, long offset, int whence);
参数:
FILE *stream:流指针;
long offset:偏移量;
int whence:SEEK_SET:文件开头
SEET_CUR:文件指针当前位置
SEET_END:文件结束位置
返回值:
成功,返回0
失败,返回-1,更新errno;

long ftell(FILE *stream); 获取文件当前位置到文件开头的偏移量
例子:long int size = ftell(fp);
void rewind(FILE *stream); 偏移文件指针到文件开头,相当于 fseek(fp, 0, SEEK_SET);

思考:如何使用fseek() ftel()计算文件大小

//使用fseek() ftel()计算文件大小
#include <stdio.h>
int main(int argc, const char *argv[])
{
	//通过fopen打开的文件是全缓冲
	FILE *fp = fopen("./test.txt","w+");
	if(NULL == fp){
		perror("fopen");
		return -1;
	}
	//文件指针已经移动到d之后
	if(fputs("hello world",fp) < 0){
		perror("fputs");
		return -1;
	}
	//将文件指针移动到EOF前
	if(fseek(fp,0,SEEK_END) < 0){
		perror("fseek");
		return -1;
	}
	long int size = ftell(fp);
	fprintf(stdout,"%ld",size);
	fclose(fp);
	return 0;
}
14.time

功能:获取1970-01-01起至今的秒数;
头文件:
#include <time.h>
原型:
time_t time(time_t *tloc);
参数:
time_t *tloc:存储获取到的时间;
返回值:
成功,返回获取到的时间;
失败,返回(time_t) -1;更新errno;

//方法1
time_t tloc;
time(&tloc);
printf("%ld\n",tloc);
//方法2
tloc = time(NULL);
printf("%ld\n",tloc);
15.localtime

功能:将1970-01-01起至今的秒数,转换成日历格式;
头文件:
#include <time.h>
原型:
struct tm *localtime(const time_t *c);
参数:
time_t *timep:time函数返回的值
返回值:
成功,返回存储日历格式的结构体指针: struct tm *
struct tm
{
int tm_sec; /* Seconds. [0-60] (1 leap second) /
int tm_min; /
Minutes. [0-59] /
int tm_hour; /
Hours. [0-23] /
int tm_mday; /
Day. [1-31] /
int tm_mon; /
Month. [0-11] / 月 = tm_mon+1;
int tm_year; /
Year - 1900. / 年 = tm_year+1900;
int tm_wday; /
Day of week. [0-6] / 星期 = tm_wday+1;
int tm_yday; /
Days in year.[0-365] */
}
失败,返回NULL

//localtime
#include <stdio.h>
#include <time.h>
int main(int argc, const char *argv[])
{
	time_t tloc = time(NULL);
	struct tm* info = localtime(&tloc);
	if(NULL == info){
		perror("localtime");
		return -1;
	}
	printf("%d-%d-%d %02d-%02d-%02d\n",info->tm_year+1900,info->tm_mon+1,\
	info->tm_mday,info->tm_hour,info->tm_min,info->tm_sec);
	return 0;
}
16.sprintf

功能:将数据格式化输出到字符串中,可以做数据拼接
头文件:
#include <stdio.h>
原型:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
参数:
char *str:接收的字符串
const char *format:格式化输出
...:不定参数
返回值:

17.sscanf

功能:将字符串以标准格式输入
头文件:
#include <stdio.h>
原型:
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
参数:
char *str:字符串
const char *format:格式化输出
...:不定参数
返回值:

//sprintf,sscanf
#include <stdio.h>
int main(int argc, const char *argv[])
{
    int num = 100;
    char name[10] = "lisi";
    char sex = 'f';
    float score = 89.5;

    printf("%d %s %c %.1f\n",num, name, sex, score);

    char buf[50] = ""; 
    sprintf(buf, "%d %s %c %.1f",num, name, sex, score);

    printf("%s\n", buf);

    int num1;
    char name1[10] = ""; 
    char sex1;
    float score1;                                                                                            
    sscanf(buf, "%d %s %c %f",&num1, name1, &sex1, &score1);
    printf("%d %s %c %f\n", num1, name1, sex1, score1);


    return 0;
}

3.文件IO

1.文件IO是不带缓冲区的
2.文件IO函数是由操作系统提供的,与操作系统绑定的,又称之为系统调用
3.文件IO是通过文件描述符来维护一个文件

文件描述符

1.概念

1.尝试打开一个文件的时候,系统会主动给这个文件分配一个编号(文件描述符),用这个编号来描述文件
⒉标准I0是对文件IO的二次封装,在文件I0的基础上封装了一个缓冲区,同时会将文件描述符也一起封装到FILE结构体中.
3.文件IO对文件的读写,是通过文件描述符实现的。
4.标准IO对文件的读写,是通过操作流指针实现的。

2.特殊的文件描述符

特殊的流指针特殊的文件描述符数值
stdinstdin->_fileno0STDIN_FILENO
stdoutstdout->_fileno1STDOUT_FILENO
stderrstderr->_fileno2STDERR_FILENO
//stdin->_fileno
#include <stdio.h>                                                                                                                         
int main(int argc, const char *argv[])
{
	printf("%d %d %d\n",stdin->_fileno,stdout->_fileno,stderr->_fileno);
	return 0;
}

3.文件描述符的总量

文件描述符的标号:[0,1023]。总共1024个
其中0,1,2分别对应stdin、stdout、stderr

注意:

1.每一个进程能打开的最大文件描述符是1023,数量是1024个;
2.文件描述符的资源是有限的,在不使用的情况下,需要关闭;

getdtableszie()函数

功能:获取当前进程能打开的文件描述符总量
头文件:
#include <unistd.h>
原型:
int getdtablesize(void);
参数:
返回值:当前进程能打开的文件描述符总量
例子:int size = getdtablesize(); //szie = 1024

//用fopen查看文件描述符总量
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
    FILE *fp;
    while(1){
        fp = fopen("./1.txt","w");
        if(NULL == fp){
            perror("fopen");
            break;
        }
        printf("%d\t",fp->_fileno);
    }
    putchar(10);
    return 0;
}

2.文件IO的函数

1.常见的文件IO函数

man 2 卷
open / close 打开 / 关闭一个文件
read / write 读 / 写一个文件
lseek 偏移

2.文件IO的使用

1.open

功能:打开并可能创建一个文件或设备
头文件:
#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);
参数:
char *pathname:指定要打开的文件路径及文件名
int flags:文件的打开方式;如果要包含多种方式,可以用按位或(|)的方式连接
O_RDONLY:只读方式打开
O_WRONLY:只写方式打开
O_RDWR:读写方式打开
以上三种,必须包含一种
O_APPEND:追加的方式; flags = O_WRONLY | O_APPEND
O_CREAT:文件不存在则创建;
O_TRUNC:清空文件;
mode_t mode:文件的八进制权限,如0777,0664;
如果falgs中包含了O_CREAT,则必须要定义文件权限,如果不加,则忽略O_CREAT


S_IRWXU 00700 user (file owner) has read, write and execute permission
S_IRUSR 00400 user has read permission
S_IWUSR 00200 user has write permission
S_IXUSR 00100 user has execute permission
**************文件所属用户的权限
S_IRWXG 00070 group has read, write and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
**************文件所属组的权限
S_IRWXO 00007 others have read, write and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
**************文件其他用户的权限


返回值:
成功,返回对应的文件描述符
失败,返回-1,更新errno

作业:fopen的r r+ w w+ a a+,分别对应open的flags的哪几种组合

//open,close
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
    int fd = open("./test.c",O_RDWR|O_CREAT|O_TRUNC,0777);
    if(fd < 0){ 
        perror("close");
        return(-1);
    }   
    printf("open succeed %d\n",fd);
    close(fd);
    return 0;
}
2.umask

The permissions of the created file are (mode & ~umask).
文件创建的真实权限是 mode & (~umask);
1.什么是umask

umsk:文件权限掩码,会影响文件创建时候的权限,实际文件的权限是 mode & (~umask);

2.查看umask

$ umask 得到结果是 0002
实际创建的文件权限:0777 & (~0002) = 0775

3.设置umask

方法1:在终端上设置,但只在当前终端有效
$ umask 0000 将umask清0
方法2:通过umask()函数实现

umsk()函数

功能:设置umask的值;
头文件:
#include <sys/stat.h>
原型: mode_t umask(mode_t cmask);
参数:
返回值:

//umask
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
    umask(0000);//设置文件权限掩码为0000(八进制)
    int fd = open("./open", O_RDWR|O_CREAT|O_TRUNC, 0777);
    if(fd < 0){
		perror("open");
		return -1;
	}
    printf("open succeed %d\n", fd);
    close(fd);
    return 0;
}
3.close

功能:关闭文件描述符
头文件:
#include <unistd.h>
原型:
int close(int fd);
参数:
int fd:指定要关闭的文件描述符;
返回值:
成功,返回0;
失败,返回-1, 更新errno;

4.write

功能:将数据写入文件描述
头文件:
#include <unistd.h>
原型:
ssize_t write(int fd, const void *buf, size_t count);
参数:
int fd:指定文件描述符;
void *buf:要写入文件的数据的首地址;可以是任意类型;
size_t count:要写入的数据大小,以字节为单位;如果要写入int a[2], count = 8;
返回值:
成功,返回写入的数据个数;
失败,返回-1,更新errno;

//write
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h> //strlen
int main(int argc, const char *argv[])
{
    int fd = open("./test.c",O_WRONLY|O_TRUNC);
    if(fd < 0){
        perror("open");
        return -1;
    }
    char buf[10] = "";
    fgets(buf,10,stdin);//终端输入hello world回车
    int ret = write(fd,buf,strlen(buf));//strlen不计'\0'
    if(ret < 0){
        perror("write");
        return -1;
    }
    printf("write %d\n",ret);//输出write 9,文件里为hello wor
    close(fd);
    return 0;
}
5.read

功能:从文件描述符读取数据;
头文件:
#include <unistd.h>
原型:
ssize_t read(int fd, void *buf, size_t count);
参数:
int fd:文件描述符;
void *buf:存储读取到的数据;
size_t count:要读取的数据大小,以字节为单位;
返回值:
成功,返回读取到的数据个数;如果读到文件结尾返回0;
失败,返回-1,更新errno;

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
    int fd = open("./test.c",O_RDONLY);
    if(fd < 0){ 
        perror("open");
        return -1; 
    }   
    char buf[10] = ""; 
    int ret = read(fd,buf,10);//fd文件里写了hello world
    if(ret < 0){ 
        perror("read");
        return -1; 
    }   
    printf("read %d :%s\n",ret,buf);//read 10:hello word
    close(fd);
    return 0;
}   
6.lseek

功能:偏移文件指针;
头文件:
#include <sys/types.h>
#include <unistd.h>
原型:
off_t lseek(int fd, off_t offset, int whence);
参数:
int fd:文件描述符;
off_t offset:相对于 int whence 偏移量;
int whence
SEEK_SET 文件开头;
SEEK_CUR 当前位置;
SEEK_END 文件结尾;
返回值:
成功,返回设置后的文件指针相对于文件开头的偏移量;
失败,返回-1,更新errno;

用于计算文件的大小:
off_t size = lseek(fd, 0, SEEK_END); //size就是文件的大小;

//lseek
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>//lseek
#include <fcntl.h>
#include <string.h>//bzero

int main(int argc, const char *argv[])
{
    umask(0);
    int fd = open("./write.txt", O_RDWR|O_CREAT|O_TRUNC, 0777);
    if(fd < 0)
    {   
        perror("open");
        return -1; 
    }
    printf("open成功\n");

    char buf[15] = ""; 
    //fgets(buf, 15, stdin);
    scanf("%s", buf);
	//写入
    int ret = write(fd, buf, strlen(buf));
    if(ret < 0)
    {   
        perror("write");
        return -1;
    }   
    printf("写入成功 %d\n", ret);

    //关闭重新打开;
    //用lseek函数
    lseek(fd, 0, SEEK_SET);
   //读取
    bzero(buf, 15);
    ret = read(fd, buf, 20);
    if(ret < 0)
    {
        perror("read");
        return -1; 
    }   
    printf("%s\n",buf);

    close(fd);
    return 0;
}

练习:通过write和read函数实现图片的拷贝;

//通过write和read函数实现图片的拷贝;
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
    if(argc != 3){ 
        printf("Usage %s filesrc filedist\n",argv[0]);
        return -1; 
    }   
    umask(0);
    int fd_src = open(argv[1],O_RDONLY);
    if(fd_src < 0){ 
        perror("open src");
        return -1; 
    }   
    int fd_dist = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664);
    if(fd_dist < 0){ 
        perror("open dist");
        close(fd_src);
        return -1; 
    }   
    char buf[20] = ""; 
    int ret;
    while((ret = read(fd_src,buf,20)) > 0){ 
        write(fd_dist,buf,ret);
    }   
    close(fd_src);
    close(fd_dist);
    return 0;
} 

3.文件属性相关的函数

1.stat

功能:获取文件的属性;
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
原型:
int stat(const char *path, struct stat *buf);
参数:
char *path:指定文件的路径或路径加名字;
struct stat *buf:系统定义的结构体,存储文件的属性;

struct stat {
	dev_t     st_dev;     /* ID of device containing file */
	ino_t     st_ino;     /* inode number */ 				inode号 		    %ld
	mode_t    st_mode;    /* protection */ 					文件的类型+文件的权限  %o
	nlink_t   st_nlink;   /* number of hard links */		硬连接数 			%d
	uid_t     st_uid;     /* user ID of owner */			当前用户的uid		%d
	gid_t     st_gid;     /* group ID of owner */			当前用户组的gid 		%d
	dev_t     st_rdev;    /* device ID (if special file) */
	off_t     st_size;    /* total size, in bytes */		文件大小			    %ld
	blksize_t st_blksize; /* blocksize for filesystem I/O */
	blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
	time_t    st_atime;   /* time of last access */			最后一次被访问的时间  %ld
	time_t    st_mtime;   /* time of last modification */	最后一次被修改的时间
	time_t    st_ctime;   /* time of last status change */	最后一次改变状态的时间
	};

返回值:
成功,返回0;
失败,返回-1,更新errno;

//stat例子
struct stat buf;
int ret = stat(argv[1],&buf);
if(ret < 0){
	perror("stat");
	return -1;
}
2.提取文件权限

mode_t st_mode; /* protection */ 文件的类型+文件的权限st_mode;
是一个32位无符号的整形变量,其中低9位[0 , 8],代表了文件的权限(rwx rwx rwx);
用st_mode & 相应权限,如果结果大于0, 说明有该权限;如果结果等于0,说明没有该权限;

st_mode: 100664
   664-> 110 110 100
   & 	 100 000 000     -->0400
    ------------------
    	 100 000 000 	----> 0400 文件所属用户有可读权限。

对应的权限的宏:

S_IRWXU    00700     mask for file owner permissions
S_IRUSR    00400     owner has read permission
S_IWUSR    00200     owner has write permission
S_IXUSR    00100     owner has execute permission
S_IRWXG    00070     mask for group permissions
S_IRGRP    00040     group has read permission
S_IWGRP    00020     group has write permission
S_IXGRP    00010     group has execute permission
S_IRWXO    00007     mask for permissions for others (not in group)
S_IROTH    00004     others have read permission
S_IWOTH    00002     others have write permission
S_IXOTH    00001     others have execute permission
//mode_t st_mode
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

//方法1
void getPermission(mode_t m)
{
    if(m & 0400)
        printf("r");
    else
        printf("-");

    if(m& 0200)
        printf("w");
    else
        putchar('-');

    if(m & 0100)
        putchar('x');
    else
        putchar('-');

    /*****************************/
    if(m & 0040)
        printf("r");
    else
        printf("-");

    if(m& 0020)
        printf("w");
    else
        putchar('-');

    if(m & 0010)
        putchar('x');
    else
        putchar('-');
    
    /*****************************/
    if(m & 0004)
        printf("r");
    else
        printf("-");

    if(m& 0002)
        printf("w");
    else
        putchar('-');

    if(m & 0001)
        putchar('x');
    else
        putchar('-');

    putchar(10);

}
//方法2
void getPermission(mode_t m)
{
    int i = 0;
    for(i = 8; i>=0; i--)
    {
        if((m & (1 << i)) == 0)
        {
            printf("-");
            continue;
        }

        switch(i)//或switch(i%3){case2:putcahr('r');case1:...
        {
        case 8:
        case 5:
        case 2:
            putchar('r');
            break;

        case 7:
        case 4:
        case 1:
            putchar('w');
            break;

        case 6:
        case 3:
        case 0:
            putchar('x');
            break;

        }                               
    }

    putchar(10);
}

int main(int argc, const char *argv[])
{
    if(argc < 2)
    {
        fprintf(stderr, "运行参数缺失\n");
        return -1;
    }

    struct stat buf;
    int ret = stat(argv[1], &buf);
    if(ret < 0)
    {
        perror("stat");
        return -1;
    }

    //文件的类型+权限
    printf("mode : %o\n", buf.st_mode);
    getPermission(buf.st_mode);

    return 0;
}

折叠:选中要折叠的->zf
打开折叠:zE或左右键
关上折叠:zc

3.提取文件类型

7种文件类型:bsp-lcd

i)通过宏函数判断

S_ISREG(m) is it a regular file? -
S_ISDIR(m) directory? d
S_ISCHR(m) character device? c
S_ISBLK(m) block device? b
S_ISFIFO(m) FIFO (named pipe)? p
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) l
S_ISSOCK(m) socket? (Not in POSIX.1-1996.) s
参数:
mode_t st_mode;
返回值:
1,代表是该类型文件;
0,不是该该类型文件;

//mode_t st_mode通过宏函数判断提取文件类型
void getType(mode_t m)
{
    if(S_ISREG(m))
        putchar('-');
    else if(S_ISDIR(m))
        putchar('d');                                                                              
    else if(S_ISCHR(m))
        putchar('c');
    else if(S_ISBLK(m))
        putchar('b');
    else if(S_ISFIFO(m))
        putchar('p');
    else if(S_ISLNK(m))
        putchar('l');
    else if(S_ISSOCK(m))
        putchar('s');
}

ii)通过提取文件类型

mode_t st_mode; /* protection */ 文件的类型+文件的权限
st_mode:是一个32位无符号的整形变量,其中低[18位 , 13位],共6bit,代表了文件的类型;
S_IFMT 0170000 bit mask for the file type bit fields
st_mode & S_IFMT :提取出[低18位,低13位]:6bit,这6bit代表文件类型。

st_mode:100775
    	100775		001 000 000 111 111 101
S_IFMT  170000     001 111 000 000 000 000
    ----------------------------------------------------
    				001 000 ---------------------->提取出6bit: 0100000
将结果与下列宏作比较,如果相等,那么就是对应的文件类型:
		   S_IFSOCK   0140000   socket
           S_IFLNK    0120000   symbolic link
           S_IFREG    0100000   regular file
           S_IFBLK    0060000   block device
           S_IFDIR    0040000   directory
           S_IFCHR    0020000   character device
           S_IFIFO    0010000   FIFO
void getType(mode_t m)
{              
    mode_t mode = m&S_IFMT;
    switch(mode)
    {   
    case S_IFSOCK:
        putchar('s');
        break;
    case S_IFLNK:
        putchar('l');
        break;
    case S_IFREG:
        putchar('-');
        break;
    case S_IFBLK:
        putchar('b');
        break;
    case S_IFDIR:
        putchar('d');
        break;
    case S_IFCHR:
        putchar('c');
        break;
    case S_IFIFO:
        putchar('p');
        break;
    }   
}
4.获取文件所属用户

uid_t st_uid; /* user ID of owner */ 当前用户的uid
通过struct stat结构体中的 uid_t st_uid 成员变量获取文件所属用户

getpwuid函数

功能:通过struct stat结构体中的 uid_t st_uid 成员变量获取文件所属用户;
头文件:
#include <sys/types.h>
#include <pwd.h>
原型:
struct passwd *getpwuid(uid_t uid);
参数:
uid_t uid:文件所属用户的uid;
返回值:
成功,返回存储文件所属用户信息的结构体地址;
失败,返回NULL,更新errno;

struct passwd {
	char   *pw_name;       /* username */
	char   *pw_passwd;     /* user password */
	uid_t   pw_uid;        /* user ID */
	gid_t   pw_gid;        /* group ID */
	char   *pw_gecos;      /* user information */
	char   *pw_dir;        /* home directory */
	char   *pw_shell;      /* shell program */
};
sudo chown root:root 文件名		将文件所属用户改成root
sudo chown linux:root 文件名		将文件所属用户改成linux
5.获取文件所属组用户

gid_t st_gid; /* group ID of owner */ 当前用户组的gid

getgrgid函数

功能:根据 struct stat 结构体中的gid_t st_gid,获取文件所属的组;
头文件:
#include <sys/types.h>
#include <grp.h>
原型:
struct group *getgrgid(gid_t gid);
参数:
gid_t gid:组ID;
返回值:
成功,返回存储用户组信息的结构体地址;
失败,返回NULL;

struct group {
	char   *gr_name;       /* group name */
	char   *gr_passwd;     /* group password */
	gid_t   gr_gid;        /* group ID */
	char  **gr_mem;        /* group members */
};

练习:读取一个文件的信息,main函数外部传参的方式。要求输出结果格式如下:
-rw-rw-r-- 1 linux linux 11 Sep 23 05:24 fileno

4.操作文件目录的函数

1.opendir

功能:打开一个目录文件;
头文件:
#include <sys/types.h>
#include <dirent.h>
原型:
DIR *opendir(const char *name);
参数:
char *name:指定要打开的目录文件路径及目录名;
返回值:
成功,返回目录流指针;
失败,返回NULL;更新errno;

2.closedir
3.readdir

功能:通过DIR*读取目录;
头文件:
#include <dirent.h>
原型:
struct dirent *readdir(DIR *dirp);
参数:
DIR *dirp:指定的目录流指针;
返回值:
成功,返回 struct dirent *结构体地址;
失败,返回NULL,更新errno;

struct dirent {
	ino_t          d_ino;       /* inode number */
	off_t          d_off;       /* not an offset; see NOTES */
	unsigned short d_reclen;    /* length of this record */
	unsigned char  d_type;      /* type of file; not supported
	                               by all filesystem types */
	char           d_name[256]; /* filename */
};
//opendir/closedir/readdir
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

int main(int argc, const char *argv[])
{
    DIR* dirp = opendir("../1_stdio/");
    if(NULL == dirp)
    {   
        perror("opendir");
        return -1; 
    }   
    printf("打开成功\n");

    struct dirent* dir_read;
    while(1)
    {   
        dir_read = readdir(dirp);
        if(NULL == dir_read)
        {   
            break;
        }   

        printf("%ld %s\n", dir_read->d_ino, dir_read->d_name);
    }   

    if(closedir(dirp)<0)
    {   
        perror("closedir");
        return -1; 
    }

    printf("关闭成功\n");

    return 0;                                                           
} 

练习:1.代码实现,打开目录,显示该目录下面所有文件的属性,权限,最有一次被修改的时间,类似ls –l。