一、IO基础

1.1 IO的概念

1> i:input o:output

2> 所谓IO就是程序对外部设备完成信息的交换过程

1.2 已经接触的IO

1> #include<sdtio.h> 该头文件中封装了有关标准的输入输出相关的操作

2> 接触的IO函数:printf\scanf、getchar\putchar、gets\puts

3> 以上函数的特点:输入输出的目标都是终端文件

1.3 分类

1> 标准IO:依赖于库函数,程序提供的函数,该函数中封装了一个缓冲区,调用函数时先将要操作的数据放入缓冲区,等待缓冲区刷新时机到了后,该函数在调用系统调用函数将数据同一刷入到内核空间,效率较高

2> 文件IO:依赖于系统调用,内核提供的函数,每次进行该调用,都会执行一次系统调用,效率较低

3> 区别:标准IO = 文件IO + 缓冲区

4> 每次调用系统调用函数,进程进入阻塞状态,程序就会从用户空间向内核空间进行一次切换,当IO事件完成后,进程会重新进入就绪状态

img

1.4 常用接口函数

1> 标准IO:printf\scanf、getchar\putchar、gets\puts 、fopen\fclose、fprintf\fscanf、fgetc\fputc、fgets\fputs、fread\fwrite、fseek\ftell\rewind

2> 文件IO:open、close、read、write、lseek

img

二、标准IO

2.1 FILE文件结构体类型

1> 该类型是在stdio.h中已经定义了的用于描述一个文件的所有信息的结构体类型

2> 由fopen函数打开某个文件,所返回的操作句柄就是文件指针,后期可以通过该指针操作文件

2> 该结构体中的内容为

如何找到FILE结构体

1、到根目录下的usr目录中的include目录中:ctags -R 创建索引

2、在任意地方输入指令:vi -t 要查询的名字

3、找到对应的文件数字后,输入数字并回车,查看对应文件信息

4、使用 ctrl + ] 继续向前追 使用ctrl + [ :回退

1
2
3
4
5
6
7
8
9
10
11
245 struct _IO_FILE {



257 char* _IO_buf_base; /* Start of reserve area. */ 缓冲区的起始地址
258 char* _IO_buf_end; /* End of reserve area. */ 缓冲区的结束地址

267
268 int _fileno; //文件描述符,用于调用系统调用函数
}

3> 特殊的文件指针有三个,这三个指针,主要是针对于终端操作的

stdin:标准输入流指针

stdout:标准输出流指针

stderr:标准错误输出流指针

2.2 fopen函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
       #include <stdio.h>

FILE *fopen(const char *pathname, const char *mode);
功能:通过指定的模式,打开指定的文件,并返回该文件的地址
参数1:是一个字符串,表示要打开的文件,是要打开的文件的路径。 例如: "/usr/include/stdio.h"
参数2:也是一个字符串,表示要打开的文件模式,以下面的字符开头,表示不同的打开方式
r Open text file for reading. The stream is positioned at
the beginning of the file.
//以只读的形式打开一个文件,光标定位在开头,如果文件不存在,则open函数报错

r+ Open for reading and writing. The stream is positioned
at the beginning of the file.
//以读写的形式打开一个文件,光标定位在开头,如果文件不存在,则open函数报错

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.
//以读写追加的形式打开文件,如果文件不存在,则创建文件,如果文件存在则结尾写,开头读
返回值:成功返回打开的文件的指针,失败返回NULL并置位错误码

2.3 fclose函数

1
2
3
4
5
6
       #include <stdio.h>

int fclose(FILE *stream);
功能:关闭指定的文件
参数:由open函数打开的文件指针
返回值:成功返回0, 失败返回EOF(-1),并置位错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>

int main(int argc, const char *argv[])
{
FILE *fp; //定义文件指针

fp = fopen("./test.txt", "r");
//功能:以只写的形式,打开当前路径下的test.txt文件
//如果文件不存在,则open函数会报错
//如果文件存在,则正常打开,可以读取数据
//fp = fopen("./test.txt", "w");
//功能:以只写的形式打开当前路径下的test.txt文件
//如果文件不存在,则创建一个文件
//如果文件存在,则清空文件中的内容
if(NULL == fp) //说明文件打开失败
{
printf("open file error\n");
return -1;
}

printf("文件打开成功\n");

//关闭文件
fclose(fp);

return 0;
}

2.4 错误码

1> 错误码:当调用内核提供的函数出错时,内核空间向用户空间返回一个错误信息,每个错误信息会对应一个编号,内核空间向用户空间返回的就是该编号,这个编号就是错误码

2> 查看错误码:vi -t EIO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 5 #define EPERM        1  /* Operation not permitted */
6 #define ENOENT 2 /* No such file or directory */
7 #define ESRCH 3 /* No such process */
8 #define EINTR 4 /* Interrupted system call */
9 #define EIO 5 /* I/O error */
10 #define ENXIO 6 /* No such device or address */
11 #define E2BIG 7 /* Argument list too long */
12 #define ENOEXEC 8 /* Exec format error */
13 #define EBADF 9 /* Bad file number */
14 #define ECHILD 10 /* No child processes */
15 #define EAGAIN 11 /* Try again */
16 #define ENOMEM 12 /* Out of memory */
17 #define EACCES 13 /* Permission denied */
18 #define EFAULT 14 /* Bad address */
19 #define ENOTBLK 15 /* Block device required */
20 #define EBUSY 16 /* Device or resource busy */
21 #define EEXIST 17 /* File exists */
22 #define EXDEV 18 /* Cross-device link */
23 #define ENODEV 19 /* No such device */
24 #define ENOTDIR 20 /* Not a directory */
25 #define EISDIR 21 /* Is a directory */

3> 处理错误码和错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<errno.h>
int error;
错误码定义在当前头文件中,如果想要使用错误码变量,需要加上该头文件

#include <string.h>

char *strerror(int errnum);
功能:将给定的错误码,转换成错误信息
参数:错误码
返回值:错误信息


#include <stdio.h>

void perror(const char *s);
功能:输出最新的错误码对应的错误信息
参数:字符串,是一个提示信息,原样输出,并在后面加上一个冒号,冒号后是错误信息
返回值:无
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<errno.h>
#include<string.h>

int main(int argc, const char *argv[])
{

//定义文件指针
FILE * fp = NULL;

//以只读的形式打开当前路径下的test.txt文件
fp = fopen("./test.txt", "r");
if(NULL == fp)
{
// printf("open flie error,errno = %d,errmsg = %s\n",\
errno, strerror(errno));
perror("fopen error"); //输出当前错误码对应的错误信息

return -1;
}


//关闭文件
fclose(fp);

return 0;
}

2.5 fputc、fgetc函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       #include <stdio.h>

int fputc(int c, FILE *stream);
功能:向指定的文件中输出一个字符
参数1:要输出的字符数据
参数2:要输出的文件指针,由fopen函数打开后指向的某个文件
返回值:成功返回输出的字符,失败返回EOF

#include <stdio.h>

int fgetc(FILE *stream);
功能:从指定的文件中读取一个字符
参数:文件指针
返回值:读取光标所在位置的一个字符,失败返回EOF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;

//以只写的形式打开文件
if((fp=fopen("./test.txt", "w")) == NULL)
{
perror("fopen error");
return -1;
}

//向文件中写入字符数据
fputc('H', fp);
fputc('e', fp);
fputc('l', fp);
fputc('l', fp);
fputc('o', fp);
fputc('\n', fp);

//关闭文件
fclose(fp);

//重新以只读的形式打开文件
if( (fp = fopen("./test.txt", "r")) == NULL)
{
perror("fopen r error");
return -1;
}

//定义一个变量,用于从文件中读取数据
char buf = 0;
while(1)
{
buf = fgetc(fp); //从外部文件中读取一个字符
if(buf == EOF)
{
break;
}

printf("%c", buf); //输出到终端上
}

/*
while((buf = fgetc(fp)) != EOF)
{
printf("%c", buf);
}*/


//关闭文件
fclose(fp);

return 0;
}

练习:

1> 使用fgetc统计给定的某个文件的行号

2> 使用fgetc和fputc完成两个文件的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<stdio.h>

int main(int argc, const char *argv[])
{
//判断外部传参的个数
if(argc != 3)
{
printf("input file error\n");
printf("usage:./a.out srcfile dstfile\n");
return 0;
}

//定义两个指针,分别指向源文件和目标文件
FILE *srcfp, *dstfp;
//以只读的形式打开源文件
if((srcfp = fopen(argv[1], "r")) == NULL)
{
perror("srcfile open error");
return -1;
}
//以只写的形式打开目标文件
if((dstfp = fopen(argv[2], "w")) == NULL)
{
perror("dstfile open error");
return -1;
}


//定义一个字符搬运工
char buf = 0;
while((buf = fgetc(srcfp)) != EOF)
{
//将从源文件中读取的字符写入到目标文件
fputc(buf, dstfp);
}

//关闭两个文件
fclose(srcfp);
fclose(dstfp);

printf("拷贝成功\n");

return 0;
}

2.6 fputs、fgets函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
       #include <stdio.h>

int fputs(const char *s, FILE *stream);
功能:将指定的字符串输出到指定的文件中去
参数1:要输出的字符串的起始地址
参数2:要被输出的文件
返回值:成功返回输出字符的个数,失败返回-1


#include <stdio.h>

char *fgets(char *s, int size, FILE *stream);
功能:从指定的文件中读取最多size-1个字符到s数组中
参数1:存放字符串的容器起始地址
参数2:要读取的字符个数,最大是size-1个字符,如果文件中的字符个数小于size-1
则有多少读多少,最后也会补个'\0',读取过程中,如果遇到'\n'也会结束一次读取
并且将'\n'放入字符串中,后面再补个'\0'
参数3:要被读取的文件
返回值:成功返回读取字符的个数,失败返回EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;
if((fp = fopen("./test.txt", "w")) == NULL)
{
perror("fopen error");
return -1;
}

//将一个字符串输出到文件中
fputs("hello world\n", fp);
fputs("hello world\n", fp);
fputs("hello world\n", fp);
fputs("hello world\n", fp);
//关闭文件
fclose(fp);

//以只读的形式重新打开文件
if((fp = fopen("./test.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

//定义一个字符串的搬运工
char buf[8] = "";
while(1)
{
char * res = fgets(buf, sizeof(buf), fp);

if(res == NULL)
{
break;
}

printf("%s\n", buf);
}
//关闭文件
fclose(fp);

return 0;
}

img

2.7 关于时间的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <time.h>

time_t time(time_t *tloc);
功能:获取系统当前的时间,从197011000秒开始累计的秒数
参数:时间变量的指针
返回值:返回系统累计的时间
使用方式1
time_t sys_time;
time(&sys_time);

使用方式2
time_t sys_time = time(NULL);



struct tm *localtime(const time_t *timep);
功能:将给定的秒数,转变成时间类型的结构体指针
参数:由time函数获取的秒数的地址
返回值:时间结构体类型的指针
struct tm {
int tm_sec; /* Seconds (0-60) */ 秒数
int tm_min; /* Minutes (0-59) */ 分钟
int tm_hour; /* Hours (0-23) */ 小时
int tm_mday; /* Day of the month (1-31) */ 月中天数
int tm_mon; /* Month (0-11) */ 月份+1
int tm_year; /* Year - 1900 */ 年份 + 1900
int tm_wday; /* Day of the week (0-6, Sunday = 0) */ 周中天数
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */ 年中天数
int tm_isdst; /* Daylight saving time */ 夏令时
};

2.8 sprintf函数

1
2
3
4
5
6
int sprintf(char *str, const char *format, ...);
功能:将后面的格式串转换为字符串放入一个给定的字符数组中
参数1:存放转换格式串的字符数组首地址
参数2:格式串
参数3:不定参数,个数由参数2中的格式控制符决定
返回值:成功返回转换的字符个数,失败返回-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>
#include<time.h>

int main(int argc, const char *argv[])
{
//定义一个时间类型的变量,存储秒数
time_t sys_time = 0;

//调用时间函数,获取秒数
time(&sys_time); //sys_time = time(NULL);

//将秒数转变成时间结构体类型
struct tm* t = localtime(&sys_time);

//printf("%4d-%02d-%02d %02d:%02d:%02d\n",t->tm_year+1900, t->tm_mon+1,\
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);

char buf[128] =""; //存储格式串的容器

sprintf(buf,"%4d-%02d-%02d %02d:%02d:%02d\n",t->tm_year+1900, t->tm_mon+1,\
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);

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

return 0;
}

作业:

1> 使用fgets统计一个文件的行号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* Filename: 01line.c
* Author: linus
* Date: 2023-12-29
* Version: 1.0
*
* Description: The purpose of this code.
*/

#include <stdio.h>

int main(int argc, const char *argv[]){
FILE *fp=NULL;
if(argc!=2){
puts("输入有误");
puts("eg:/a.out test.txt");
return -1;
}
if((fp=fopen(argv[1],"r"))==NULL){
perror("fopen error:");
return -1;
}

int line=0;
//char buff=0;

char buff[128];
while(NULL!=fgets(buff,sizeof(buff),fp)){
line++;
}

printf("此文件有%d行\n",line);
fclose(fp);
return 0;
}

2> 使用fgets、fputs完成两个文件的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* Filename: 02cp.c
* Author: linus
* Date: 2023-12-29
* Version: 1.0
*
* Description: The purpose of this code.
*/

#include <stdio.h>

int main(int argc, const char *argv[]){
if(argc!=3){
puts("输入有误!");
return -1;
}
FILE* fp=NULL;
FILE*fp_cp=NULL;
if((fp=fopen(argv[1],"r"))==NULL){
perror("打开1文件出错:");
return -1;
}

if((fp_cp=fopen(argv[2],"w"))==NULL){
perror("打开2文件出错:");
return -1;
}

char buff[1024];
while(fgets(buff,sizeof(buff),fp)!=NULL){
fputs(buff,fp_cp);
}

puts("拷贝成功!");
fclose(fp);
fclose(fp_cp);
return 0;
}

3> 向文件中输出当前的系统时间

1
2
3
4
5
6
7
8
9
10
1、16:42:50
2、16:42:51
3、16:42:52
。。。
ctrl + C
./a.out
6、17:10:03
7、17:10:04
8、17:10:05
。。。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
* Filename: 03time.c
* Author: linus
* Date: 2023-12-29
* Version: 1.0
*
* Description: The purpose of this code.
*/

#include <stdio.h>
#include <time.h>
#include <unistd.h>

time_t sys_time = 0;
char time_buff[128] = ""; // 写入文件的字符串
struct tm *t;
int line = 1;

int get_time()
{
time(&sys_time);
t = localtime(&sys_time);
sprintf(time_buff, "%4d[%4d/%02d/%02d-%02d:%02d:%02d]\n", line,
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);

return t->tm_sec;
}

int main(int argc, const char *argv[])
{

FILE *fp = NULL;
int last_sec = -1;

if ((fp = fopen("./time.txt", "r")) == NULL)
{
perror("打开文件失败:");
// return -1;
}

// 先读出文件有多少行
char buff[128];
while (NULL != fgets(buff, sizeof(buff), fp))
{
line++;
}

while (1)
{
// a:以结尾写的形式打开文件,如果文件不存在,则创建文件,如果文件存在则结尾写,光标定位在开结尾
if ((fp = fopen("./time.txt", "a")) == NULL)
{
perror("打开文件失败:");
return -1;
}
sleep(1);

// 每秒才写入
if (last_sec != get_time())
{
last_sec = t->tm_sec;
fputs(time_buff, fp);
line++;
}
printf("%d\n", line);
fclose(fp);
}

return 0;
}

2.9 snprintf函数

1> 对于sprintf而言,如果字符数组的长度,小于字符串的长度,那么存储字符串时,就没有把字符串的结束标志存储起来,使用时会很危险

2> 基于此,引入了snprintf,多提供一个参数,size,用于控制读取的个数

3> snprintf比sprintf使用更加安全

1
2
3
4
5
6
7
int snprintf(char *str, size_t size, const char *format, ...);
功能:将指定的格式串中的最多size-1个字符写入到str字符数组中
参数1:要存储字符串的字符数组起始地址
参数2:要读取的字符个数,最大是size-1
参数3:格式串
参数4:可变参数
返回值:成功返回转换的字符串的长度,失败返回-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
#include<string.h>

int main(int argc, const char *argv[])
{
char buf[12];

//gets(buf); //使用该函数输入字符串比较危险
//fgets(buf, sizeof(buf), stdin);

//buf[ strlen(buf)-1 ] = '\0'; //将最后一个字符换成'\0'
//printf("buf = %s\n", buf);
//fputs(buf, stdout); //将数据直接输出
//


//sprintf(buf, "%s-%s-%c-%d\n", "zhangsan", "23111班", 'M', 99);
snprintf(buf,sizeof(buf), "%s-%s-%c-%d\n", "zhangsan", "23111班", 'M', 99);

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



return 0;
}

2.10 关于缓冲区问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int _flags;/*高阶单词是_IO_MAGIC;休息是旗帜。* /

/*下面的指针对应于c++的streambuf协议。*/
char * _IO_read_ptr;/*当前读指针*/
char * _IO_read_end;/*获取区域结束。*/
char * _IO_read_base;/*开始放回+获取区域。*/
char * _IO_write_base;/*放置区域的起始点。*/
char * _IO_write_ptr;/*当前的put指针。*/
char * _IO_write_end;/*放置区域结束。*/
char * _IO_buf_base;/*保护区开始。*/
char * _IO_buf_end;/*保护区结束。*/

/*以下字段用于支持备份和撤销。*/
char * _IO_save_base;/*指向非当前获取区域起始的指针。*/
char * _IO_backup_base;/*指向备份区域第一个有效字符的指针*/
char * _IO_save_end;/*指向非当前获取区域结束的指针。* /

缓冲区本质上就是一段内存在源码中,和文件有关的结构体中有很多的指针变量,如上所示,这些指针就是在维护缓冲区。

此时我们就可以知道,缓冲区是由要打卡文件的进程申请的,也是由这个进程来维护的,缓冲区存在于FILE结构体中。

1> 缓冲区的分类

行缓存:和终端相关的读写操作指针对应的缓冲区称为行缓存(stdin、stdout),大小为1024字节

全缓存:和文件相关的读写操作指针对应的缓冲区称为全缓存(fp),大小为4096字节

不缓存:和标准出错输出流相关的文件指针对应的缓冲区(stderr),大小为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>

int main(int argc, const char *argv[])
{
printf("%ld\n", stdout->_IO_buf_end - stdout->_IO_buf_base); //未使用时,大小为0
printf("%ld\n", stdout->_IO_buf_end - stdout->_IO_buf_base); //行缓存1024

char ch = getchar(); //使用一次stdin
printf("%ld\n", stdin->_IO_buf_end - stdin->_IO_buf_base); //行缓存1024


perror("hello"); //使用一次stderr
printf("%ld\n", stderr->_IO_buf_end - stderr->_IO_buf_base); //不缓存的大小:0


//全缓存的大小
FILE *fp = NULL;
if((fp = fopen("./time.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

fgetc(fp); //从文件中读取一个字符

printf("%ld\n", fp->_IO_buf_end - fp->_IO_buf_base); //全缓存的大小:4096

//关闭文件
fclose(fp);


return 0;
}

2> 缓冲区的刷新时机

1、行缓存刷新时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<stdio.h>

int main(int argc, const char *argv[])
{
/*缓冲区刷新时机未到,不会刷新
printf("hello world");
while(1);
*/

/*1、遇到'\n'会刷新行缓存
printf("hello world\n");
while(1);
*/

/*2、当程序结束后,会刷新缓冲区
printf("hello world");
*/

/*3、当文件指针关闭时,会刷新缓冲区
printf("hello world");
fclose(stdout); //关闭标准输出流指针
while(1);
*/


/*4、手动调用fflush函数,刷新缓冲区
printf("hello world");
fflush(stdout);
while(1);
*/

/*5、当输入输出发生切换时,也会刷新缓冲区
int num = 0;
printf("请输入num的值:");
scanf("%d", &num);

while(1);
*/

//6、缓冲区满了后,会刷新行缓存
for(int i=0; i<1025; i++)
{
putchar('A');
}
while(1);




return 0;
}

fflush函数,用于强制刷新缓冲区。

1
2
3
4
5
6
       #include <stdio.h>

int fflush(FILE *stream);
功能:刷新指定的文件指针的缓冲区
参数:要刷新的缓冲区文件指针
返回值:成功返回0,失败返回EOF,并置位错误码

2> 全缓存刷新时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;
//打开文件
if((fp = fopen("./time.txt", "w+")) == NULL)
{
perror("fopen error");
return -1;
}

/*如果刷新时机未到,不会刷新全缓存
fputs("hello world", fp);
while(1);
*/

/*1、遇到'\n',不会刷新全缓存
fputs("hello world\n", fp);
fputc('\n', fp);
while(1);
*/

/*2、程序结束后,会刷新全缓存
fputs("hello world", fp);
exit(EXIT_SUCCESS); //退出程序
*/


/*3、当文件指针关闭时,会刷新全缓存
fputs("hello world", fp);
fclose(fp);
while(1);
*/

/*4、手动调用fflush函数刷新缓冲区,会刷新全缓存
fputs("hello world", fp);
fflush(fp);
while(1);
*/

/*5、当输入输出发生切换时,也会刷新缓冲区
fputs("hello world", fp); //向文件中写入数据
fgetc(fp); //从文件中读取数据

while(1);
*/

//6、当缓冲区满了后,会刷新全缓存
for(int i=0; i<4097; i++)
{
fputc('A', fp);
}
while(1);
return 0;
}

3> 不缓存的刷新时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>

int main(int argc, const char *argv[])
{
/*1、直接放入
fputc('A', stderr); //向标准出错中放入数据
while(1);
*/

//2、放入字符串
fputs("hello world", stderr);
while(1);

return 0;
}

2.11 fprintf、fscanf函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int fprintf(FILE *stream, const char *format, ...);
功能:向指定的文件中,输出指定的格式串
参数1:要被输出的文件
参数2:格式串,可以包含格式控制符
参数3:不定参数,根据参数2中的格式控制符而定
返回值:成功返回输出的字符个数,失败返回EOF


int fscanf(FILE *stream, const char *format, ...);
功能:从给定的文件中,以指定的格式,读取数据到程序中来
参数1:指定的文件指针
参数2:格式串,可以包含格式控制符
参数3:不定参数,根据参数2中的格式控制符而定
返回值:成功返回写入数据的个数,失败返回一个负数,并置位错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>

int main(int argc, const char *argv[])
{
//向标准出错文件指针缓冲区中写入一个格式串
//参数1:文件指针,可以是特殊的文件指针,也可以是自定义文件指针
//参数2:要输出的格式串
fprintf(stderr, "%s-%s-%d\n", "zhangpp", "23111班", 90);

//定义一个变量
int num;
//从标准输入流中读取4个字节中的数据,到指定程序中
fscanf(stdin, "%d", &num);
printf("num = %d\n", num);

return 0;
}

练习:使用fprintf和fsacnf完成注册登录功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//定义注册函数
int do_register()
{
//1、以追加的形式打开文件
FILE * wfp = NULL;
char userName_reg[20]; //注册账号
char pwd_reg[20]; //注册密码

//提示输出注册账号和密码
printf("请输入注册账号:");
fgets(userName_reg, sizeof(userName_reg), stdin);
printf("请输入注册密码:");
fgets(pwd_reg, sizeof(pwd_reg), stdin);

//将两个字符串中的换行改成'\0'
userName_reg[strlen(userName_reg)-1] = '\0';
pwd_reg[strlen(pwd_reg)-1] = '\0';

//打开文件
if((wfp = fopen("./usr.txt", "a+")) == NULL)
{
perror("fopen error");
return -1;
}

//2、将注册账号和注册密码写入到文件中,中间有空格隔开,每组用换行隔开
fprintf(wfp, "%s %s\n", userName_reg, pwd_reg);


//3、关闭文件
fclose(wfp);

printf("注册成功\n");
return 0;
}



//自定义登录功能
int do_login()
{
//定义容器存放登录账号和登录密码
char userName_log[20];
char pwd_log[20];

//定义变量存储文件读取的账号和密码
char userName[20];
char pwd[20];

//提示并输入登录账号和密码
printf("请输入登录账号:");
fgets(userName_log, sizeof(userName_log), stdin);
printf("请输入登录密码:");
fgets(pwd_log, sizeof(pwd_log), stdin);

//将两个字符串中的换行改成'\0'
userName_log[strlen(userName_log)-1] = '\0';
pwd_log[strlen(pwd_log)-1] = '\0';

//打开文件,进行账号和密码的比对
FILE *rfp = NULL;
if((rfp = fopen("./usr.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

//循环遍历所有文件中的账户
while(1)
{
int res = fscanf(rfp, "%s %s", userName, pwd); //读取文件中的用户
if(res < 0)
{
printf("登录失败\n");
//关闭文件
fclose(rfp);
return 1; //表示登录失败
}

//判断登录账号和密码是否匹配
if(strcmp(userName_log,userName)==0 && strcmp(pwd_log, pwd)==0)
{
printf("登录成功\n");
//关闭文件
fclose(rfp);
return 0;
}
}
}


/**********************主程序******************************/
int main(int argc, const char *argv[])
{

//定义一个变量存储功能选项
char menu;

//制作菜单框架
while(1) //重复使用菜单框架
{
printf("\t\t=====1、注册=====\n");
printf("\t\t=====2、登录=====\n");
printf("\t\t=====0、退出=====\n");

printf("请输入功能选项:");
scanf("%c", &menu);
while(getchar() != '\n'); //吸收垃圾字符以及回车

//对菜单选项多分支选择
switch(menu)
{
case '1':
{
//执行注册功能
do_register();
}
break;

case '2':
{
//执行登录功能
do_login();
}
break;

case '0':
exit(EXIT_SUCCESS); //退出程序

default:printf("您输入的功能有误请重新输入!!!\n");
}

printf("请输入任意键按回车清屏!!!\n");
while(getchar() != '\n');
system("clear"); //调用终端指令清屏
}

return 0;
}

2.12 fwrite、fread函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从stream指向的文件中读取nmemb项数据,每一项的大小为size,将数据存储在ptr指向的空间中
参数1:存放数据的起始地址
参数2:每一项的大小
参数3:读取数据的总项数
参数4:要被读取的文件
返回值:成功返回读取的项数,失败返回一个小于项数的值
注意:fread出错时,并不能区分是因为文件读取结束还是程序调用出错,需要使用feof或ferror来判断



size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:将ptr指针指向的空间中,以每项大小为size字节,的nmemb项数据,写入到stream指向的文件中
参数1:要写入的数据起始地址
参数2:每一项的大小
参数3:写入数据的总项数
参数4:要被写入的文件
返回值:成功返回写入的项数,失败返回返回一个小于项数的值,或者是0

1> 使用fread、fwrite读写一个字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针,以只写的形式打开
FILE *fp = NULL;
if((fp = fopen("./time.txt", "w")) == NULL)
{
perror("fopen error");
return -1;
}

//将一个字符串写入到文件中
char wbuf[128] = "hello world";

//调用fwrite函数将数据写入文件
fwrite(wbuf, 1, sizeof(wbuf), fp);

//关闭文件
fclose(fp);

//以只读的形式再次打开文件
if((fp = fopen("./time.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

//读取文件内容
char rbuf[5] = "";
fread(rbuf, 1, sizeof(rbuf), fp);
//关闭文件
fclose(fp);

//输出rbuf数据
fwrite(rbuf, 1, sizeof(rbuf), stdout);

return 0;
}

2> 使用fread、fwrite读写一个整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针,以只写的形式打开
FILE *fp = NULL;
if((fp = fopen("./time.txt", "w")) == NULL)
{
perror("fopen error");
return -1;
}
int num = 12;

//调用fwrite函数将数据写入文件
fwrite(&num, sizeof(int), 1, fp);

//关闭文件
fclose(fp);

//以只读的形式再次打开文件
if((fp = fopen("./time.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

//读取文件内容
int key = 0;

fread(&key, sizeof(int), 1, fp);
//关闭文件
fclose(fp);

//输出rbuf数据
printf("key = %d\n", key); //12

return 0;
}

3> 使用fread、fwrite读写一个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<stdio.h>

struct stu_t
{
int numb; //学号
char name[20]; //姓名
double score; //成绩
};

int main(int argc, const char *argv[])
{
//定义文件指针,以只写的形式打开
FILE *fp = NULL;
if((fp = fopen("./time.txt", "w")) == NULL)
{
perror("fopen error");
return -1;
}

//将三个学生信息写入到文件中
struct stu_t s[3] = {{1001, "张三", 90}\
,{1002, "李四", 95}\
,{1003, "二狗", 80}};

//调用fwrite函数将数据写入文件
fwrite(s, sizeof(struct stu_t), 3, fp);

//关闭文件
fclose(fp);


//以只读的形式再次打开文件
if((fp = fopen("./time.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

//读取文件内容
struct stu_t t;
fread(&t, sizeof(t), 1, fp);
fread(&t, sizeof(t), 1, fp);
//关闭文件
fclose(fp);

//输出rbuf数据
printf("numb = %d, name = %s, score = %.2lf\n", t.numb, t.name, t.score);

return 0;
}

2.13 feof、ferror函数

1> 由于fread出错时,不区分到低是文件结束函数调用失败,需要使用对应函数判断

2> 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int feof(FILE *stream);
功能:判断给定的文件指针是否已经到文件末尾
参数:文件指针
返回值:如果到达文件末尾返回值真,否则返回假

int ferror(FILE *stream);
功能:判断文件指针使用是否出错
参数:文件指针
返回值:如果出错返回值真,否则返回假

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针,打开文件
FILE *fp = NULL;
if((fp = fopen("./usr.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}

//定义容器,读取数据
char buf[12] = "";
while(!feof(fp))
{
int res = fread(buf, 1, sizeof(buf), fp);
//将读取的信息打印出来
fwrite(buf, 1, res, stderr);

/*
if(feof(fp)) //文件读取结束
{
printf("读取到文件末尾\n");
break;
}*/

if(ferror(fp)) //读取失败
{
printf("读取失败\n");
break;
}

}

//关闭文件
fclose(fp);

return 0;
}

2.14 关于光标的函数(fseek、ftell、rewind)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>

int fseek(FILE *stream, long offset, int whence);
功能:将指定的文件指针从指定的位置处开始偏移
参数1:文件指针
参数2:偏移量
>0:表示向后偏移
=0:表示不偏移
<0:表示下向前偏移
参数3:偏移的起始位置
SEEK_SET:文件起始位置
SEEK_CUR:文件当前位置
SEEK_END:文件结尾位置
返回值:成功返回0,失败返回-1并置位错误码


long ftell(FILE *stream);
功能:返回当前光标所在位置
参数:文件指针
返回值:当前文件指针所在位置,失败返回-1,并置位错误码
eg: fseek(fp, 0, SEEK_END);
ftell(fp); //?文件大小


void rewind(FILE *stream);
功能:将光标重新定位在开头 : fseek(fp,0,SEEK_SET)
参数:文件指针
返回值:无

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<stdio.h>

struct stu_t
{
int numb; //学号
char name[20]; //姓名
double score; //成绩
};


int main(int argc, const char *argv[])
{
//定义文件指针,以只写的形式打开
FILE *fp = NULL;
if((fp = fopen("./time.txt", "w+")) == NULL)
{
perror("fopen error");
return -1;
}

//将三个学生信息写入到文件中
struct stu_t s[3] = {{1001, "张三", 90}\
,{1002, "李四", 95}\
,{1003, "二狗", 80}};

//调用fwrite函数将数据写入文件
fwrite(s, sizeof(struct stu_t), 3, fp);

//将光标定位在李四前面
//fseek(fp, sizeof(struct stu_t) ,SEEK_SET);
//fseek(fp,-2*sizeof(struct stu_t) ,SEEK_END);
fseek(fp,-2*sizeof(struct stu_t) ,SEEK_CUR);

//读取文件内容
struct stu_t t;
fread(&t, sizeof(t), 1, fp);
//关闭文件
fclose(fp);

//输出rbuf数据
printf("numb = %d, name = %s, score = %.2lf\n", t.numb, t.name, t.score);



return 0;
}

图像数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<stdio.h>

int main(int argc, const char *argv[])
{
//定义文件指针打开文件
FILE *fp = NULL;
if((fp = fopen("./jntm.bmp", "r+")) == NULL)
{
perror("fopen error");
return -1;
}

//向后偏移两个字节得到文件的大小
fseek(fp, 2, SEEK_SET);

unsigned int size;
fread(&size, sizeof(size), 1, fp); //从文件中读取一个整形数据
printf("size = %d\n", size);

//想要更改像素
unsigned char color[3] = {255, 0, 0}; //纯蓝色

//将光标偏移到图像数据处
fseek(fp, 54, SEEK_SET);


for(int i=0; i<100; i++) //外行
{
for(int j=0; j<640; j++) //内列
{
fwrite(color, sizeof(color), 1, fp);
}
}

//关闭文件
fclose(fp);

return 0;
}

作业

1> 使用fread、fwrite完成两个文件的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
* Filename: 04freadcp.c
* Author: linus
* Date: 2024-01-02
* Version: 1.0
*
* Description: The purpose of this code.
*/
#include <stdio.h>

int main(int argc, const char *argv[])
{
//判断是否输错
if(argc!=3)
{
puts("输入错误!");
return -1;
}
//定义文件指针
FILE* fp=NULL;
FILE* fp_cp=NULL;

//打开文件
if((fp=fopen(argv[1],"r"))==NULL)
{
perror("fopen error:");
return -1;
}

if((fp_cp=fopen(argv[2],"w"))==NULL)
{
perror("fopen error:");
return -1;
}

char buff;

//循环遍历拷贝
while (1)
{
fread(&buff,1,sizeof(buff),fp);
fwrite(&buff,1,sizeof(buff),fp_cp);
if(feof(fp))
{
break;
}
}

//关闭文件
fclose(fp);
fclose(fp_cp);


return 0;
}

2> 将注册登录框架重新实现一遍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
* Filename: 09test.c
* Author: linus
* Date: 2024-01-02
* Version: 1.0
*
* Description: 重复使用菜单框架
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum Operate
{
REGISTER,
LOGIN,
EXIT
};
char *op_msg[] = {"注册", "登录", "退出"};

enum Operate menu;

int do_register()
{
FILE *wfp =NULL;
char username_reg[20];
char pwd_reg[20];

printf("请输入账号:");
fgets(username_reg,sizeof(username_reg),stdin);
printf("请输入密码:");
fgets(pwd_reg,sizeof(pwd_reg),stdin);

username_reg[strlen(username_reg)-1]='\0';
pwd_reg[strlen(pwd_reg)-1]='\0';

if((wfp=fopen("./user.txt","a+"))==NULL)
{
perror("open user.txt error:");
return -1;
}

fprintf(wfp,"%s %s\n",username_reg,pwd_reg);
fclose(wfp);
printf("注册成功!\n");

return 0;
}

int do_login()
{
FILE *wfp =NULL;
char input_username[20];
char input_pwd[20];

char username_reg[20];
char pwd_reg[20];

printf("请输入账号:");
fgets(input_username,sizeof(input_username),stdin);
printf("请输入密码:");
fgets(input_pwd,sizeof(input_pwd),stdin);

input_username[strlen(input_username)-1]='\0';
input_pwd[strlen(input_pwd)-1]='\0';

if((wfp=fopen("./user.txt","r"))==NULL)
{
perror("open user.txt error:");
return -1;
}

while(1)
{
int res = fscanf(wfp,"%s %s",username_reg,pwd_reg);
if (res<0)
{
printf("没有这个用户\n");
fclose(wfp);
return 1;
}

if(strcmp(username_reg,input_username)==0 && strcmp(pwd_reg,input_pwd)==0){
printf("登录成功!\n");
fclose(wfp);
return 0;
}

}

}

int main(int argc, const char *argv[])
{
while (1)
{
for (int i = 0; i <= EXIT; i++)
{
printf("【%d】:%s\t", i, op_msg[i]);
if ((i + 1) % 2 == 0)
{
puts("");
}
}
puts("");
printf("请输入要执行的操作:");
scanf("%d", &menu);
while (getchar() != '\n'); // 吸收非法字符
switch (menu)
{
case REGISTER:
do_register();
break;
case LOGIN:
do_login();
break;
case EXIT:
exit(EXIT_SUCCESS);
default:
printf("输入序号有误,请重新输入:");
}
printf("按任意键清屏\n");
while (getchar() != '\n'); // 吸收非法字符
system("clear");
}

return 0;
}

3> 完成图像文件信息的读写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
* Filename: 05imgpr.c
* Author: linus
* Date: 2024-01-02
* Version: 1.0
*
* Description: The purpose of this code.
*/

#include <stdio.h>

int main(int argc, const char *argv[]){
FILE *fp=NULL;
if(NULL==(fp=fopen("./output.bmp","r+")))
{
perror("fopen error:");
return -1;
}

//向后偏移两个字节得到文件的大小
fseek(fp, 0x02, SEEK_SET);

unsigned int size;
fread(&size, sizeof(size), 1, fp); //从文件中读取一个整形数据
printf("size = %d\n", size);


unsigned int x,y;
//向后偏移两个字节得到分辨率的大小
fseek(fp, 0x12, SEEK_SET);
fread(&x, sizeof(size), 1, fp); //从文件中读取一个整形数据
fread(&y, sizeof(size), 1, fp); //从文件中读取一个整形数据
printf("x = %d\ny = %d\n", x,y);


//将光标偏移到图像数据处
fseek(fp, 54, SEEK_SET);

//想要更改像素
unsigned char color[3] = {255, 0, 0}; //纯蓝色

for(int i=0; i<800; i++) //外行
{
for(int j=0; j<33; j++) //内列
{
fwrite(color, sizeof(color), 1, fp);
}
}

//关闭文件
fclose(fp);

return 0;
}

三、文件IO

  1. 文件IO是基于系统调用
  2. 程序每进行一次系统调用,就会从用户空间向内核空间进行一次切换,执行效率较慢
  3. 目的:由于后期进程间通信,如管道、套接字通信,都使用的是文件IO,所以引入文件IO操作的概念

3.1 文件描述符

  1. 件描述符本质上是一个非负整数,每个打开的文件,都会对应一个整数用于系统调用
  2. 每个程序打开文件的个数是有上限的,默认是1024个,可以通过ulimit -a进行查看
  3. 文件描述符使用原则:最小未分配原则
  4. 当使用open函数打开一个文件时,系统会给该文件分配一个文件描述符作为句柄
  5. 当一个程序运行时,默认会打开三个文件描述符,分别对应标准输入、标准输出、标准出错
1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>

int main(int argc, const char *argv[])
{
printf("%d\n", stdin->_fileno); //0
printf("%d\n", stdout->_fileno); //1
printf("%d\n", stderr->_fileno); //2

return 0;
}

3.2 open函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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:文件路径,是一个字符串表示要打开的文件
参数2:打开标识
三个必须选一个:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)
后面的可以以位或的方式连接,表示拥有该属性
O_CREAT:表示创建一个文件, 当第二个参数中有O_CREAT时,第三个参数必须要加上
O_APPEND:追加方式打开文件
O_TRUNC:清空文件内容
O_NONBLOCK:以非阻塞形式打开文件
O_EXCL:确保本次操作一定创建文件,如果文件已经存在,则open函数会报错,错误码为EEXIST
eg: "w": O_WRONLY | O_CREAT | O_TRUNC
"r":O_RDONLY
"a":O_WRONLY | O_APPEND | O_CREAT
"w+":O_RDWR | O_CREAT | O_TRUNC
"r+":O_RDWR
"a+":O_RDWR | O_CREAT | O_APPEND
┌──────────────┬───────────────────────────────┐
│ fopen() mode │ open() flags │
├──────────────┼───────────────────────────────┤
│ r │ O_RDONLY │
├──────────────┼───────────────────────────────┤
│ w │ O_WRONLY | O_CREAT | O_TRUNC │
├──────────────┼───────────────────────────────┤
│ a │ O_WRONLY | O_CREAT | O_APPEND │
├──────────────┼───────────────────────────────┤
│ r+ │ O_RDWR │
├──────────────┼───────────────────────────────┤
│ w+ │ O_RDWR | O_CREAT | O_TRUNC │
├──────────────┼───────────────────────────────┤
│ a+ │ O_RDWR | O_CREAT | O_APPEND │
└──────────────┴───────────────────────────────┘

参数3:如果第二个参数中有O_CREAT,该参数必须设置,表示文件的权限,如果不设置,该文件的权限是一个随机权限
文件最终的权限是由给定的权限 & ~umask 得到
终端的umask的值,可以通过指令 umask查看,可以通过指令 umask -n更改当前终端的umask的值
一般创建普通文件最大权限为:664
目录文件权限最大权限为:775

返回值:成功返回一个新的文件描述符,失败返回-1并置位错误码。

3.3 close函数

1
2
3
4
5
6
       #include <unistd.h>

int close(int fd);
功能:关闭指定的文件描述符
参数:要关闭的文件描述符,关闭后,该文件描述符可以分配给其他文件使用
返回值:成功返回0,失败返回-1并置位错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<myhead.h>

int main(int argc, const char *argv[])
{
int fd = -1; //定义文件描述符变量

fd = open("./test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664);
if(fd == -1)
{
perror("open error");
return -1;
}

printf("fd = %d\n", fd); //?3

//关闭文件描述符
close(fd);




return 0;
}

3.4 write\read函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
       #include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
功能:将buf指向的地址中count个字节,写入到fd指向的文件中
参数1:文件描述符
参数2:容器起始地址,void*类型,表明可以写入任何类型的数据
参数3:要写入数据的个数
返回值:成功返回写入的字符个数,失败返回-1并置位错误码


#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
功能:从fd文件中,将count个字节读取到buf对应的容器中
参数1:文件描述符
参数2:容器起始地址,void*类型,表明可以读取任何类型的数据
参数3:要读取数据的个数
返回值:成功返回读取字节的个数,失败返回-1并置位错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<myhead.h>

int main(int argc, const char *argv[])
{
//定义文件描述符变量
int fd = -1;

//以只写的形式打开文件
if((fd = open("./test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664)) ==-1)
{
perror("open error");
return -1;
}
printf("fd = %d\n", fd); //3

char buf[128] = "hello world ni hao I love China\n";
//将字符串写入到文件
int res = write(fd, buf, strlen(buf));
printf("res = %d\n", res); //

//关闭文件
close(fd);

//以只读的形式打开文件
if((fd = open("./test.txt", O_RDONLY)) ==-1)
{
perror("open error");
return -1;
}
printf("fd = %d\n", fd); //3

char rbuf[128] = "";
res = read(fd, rbuf, sizeof(rbuf));
printf("res = %d\n", res); //?
write(stdout->_fileno, rbuf, res);

//关闭文件
close(fd);




return 0;
}

练习:使用read、wriet完成两个文件的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<myhead.h>

int main(int argc, const char *argv[])
{
//判断传入的文件个数
if(argc != 3)
{
printf("input file error\n");
printf("usage:./a.out srcfile dstfile\n");
return -1;
}

//定义文件描述符变量
int srcfd, dstfd;

//以只读的形式打开源文件
if((srcfd = open(argv[1], O_RDONLY)) ==-1)
{
perror("open srcfile error");
return -1;
}
//以只写的形式打开目标文件
if((dstfd = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0664)) ==-1)
{
perror("open dstfile error");
return -1;
}

//不断得将源文件中的内容读出,并写入的目标文件中
//直到源文件全部读取结束
char buf[128] = "";
while(1)
{

memset(buf, 0, sizeof(buf)); //将容器清空

int res = read(srcfd, buf, sizeof(buf)); //从源文件中读取数据

write(dstfd, buf, res); //将数据写入目标文件

//对读取的数据个数进行判断
if(res == 0)
{
break;
}
}


//关闭两个文件
close(srcfd);
close(dstfd);

printf("拷贝成功\n");

return 0;
}

3.5 光标移动(lseek)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
       #include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
功能:移动光标位置
参数1:要移动光标的文件描述符
参数2:偏移量
>0:表示向后偏移
=0:表示不偏移
<0:表示向前偏移
参数3:偏移的起始位置
SEEK_SET:从文件开头偏移
SEEK_CUR:从文件光标当前位置偏移
SEEK_END:从文件末尾开始偏移
返回值:成功返回光标当前位置,失败返回(off_t-1并置位错误码
lseek = fseek + ftell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<myhead.h>

int main(int argc, const char *argv[])
{
//定义文件描述符变量
int fd = -1;

//以只读的形式打开图片文件
if((fd = open("./bb.bmp", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}

//读取图像的大小
//偏移2个自己
lseek(fd, 2, SEEK_SET);
//读取数据
unsigned int size;
read(fd, &size, sizeof(unsigned int));
printf("size = %d\n", size); //?


size = lseek(fd, 0, SEEK_END); //将光标定位在结尾,将光标位置返回
printf("size = %d\n", size); //?

//关闭文件
close(fd);

return 0;
}

3.6 关于文件描述符的拷贝问题

1> 使用赋值的情况直接拷贝,多个文件描述符变量,共享同一个文件光标,通过一个文件描述符进行移动,所有的文件描述符的光标都会改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<myhead.h>

int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}

printf("fd1 = %d\n", fd1); //3

int fd2 = fd1; //对文件描述符直接进行拷贝

//使用fd1将光标后移10个字节
lseek(fd1, 12, SEEK_SET);

//从fd2中读取数据
char buf[10] = "";
read(fd2, buf, sizeof(buf)-1);

printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标



return 0;
}

2> 使用dup函数,完成文件描述符的拷贝

1
2
3
4
5
6
7
       #include <unistd.h>

int dup(int oldfd);
功能:通过旧的文件描述符,拷贝出一个新的文件描述符,新文件描述符遵循最小未分配原则
参数:旧文件描述符
返回值:新文件描述符,失败返回-1并置位错误码
执行后,新旧两个文件描述符都指向同一个打开的文件,共享文件光标位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<myhead.h>

int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}

printf("fd1 = %d\n", fd1); //3


int fd2 = dup(fd1); //拷贝旧文件描述符,产生一个新文件描述符
printf("fd2 = %d\n", fd2); //4

//使用fd1将光标后移10个字节
lseek(3, 12, SEEK_SET);

//从fd2中读取数据
char buf[10] = "";
read(4, buf, sizeof(buf)-1);

printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标



return 0;
}

3> 使用dup2函数完成两个文件描述符的拷贝

1
2
3
4
5
6
7
        int dup2(int oldfd, int newfd);
功能:通过拷贝旧的文件描述符到新的文件描述符中
参数1:旧文件描述符
参数2:新文件描述符,如果newfd已经指向了某个已经打开的文件,则在进行拷贝之前,先将其关闭
返回值:成功返回新的文件描述符,失败返回-1并置位错误码
注意:newfd不是使用最小为分配原则,因为newfd在调用之前可能已经指向某个文件,调用后,无论是newfd函数oldfd都指向oldfd指向的文件
通过该方式复制的文件描述符,依然共享同一个文件的光标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<myhead.h>

int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}


//定义一个文件描述符变量,打开另一个文件
int fd2 = -1;
if((fd2 = open("./bb.bmp", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}

printf("fd1 = %d\n", fd1); //3
printf("fd2 = %d\n", fd2); //4

//执行文件描述符拷贝工作
dup2(fd1, fd2);




//使用fd1将光标后移10个字节
lseek(3, 12, SEEK_SET);

//从fd2中读取数据
char buf[10] = "";
read(4, buf, sizeof(buf)-1);

printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标



return 0;
}

4> 多次使用open函数完成对同一个文件的打开时,不同的文件描述符使用的是独立的光标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<myhead.h>

int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}


//定义一个文件描述符变量,打开另一个文件
int fd2 = -1;
if((fd2 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}

printf("fd1 = %d\n", fd1); //3
printf("fd2 = %d\n", fd2); //4

//使用fd1将光标后移10个字节
lseek(fd1, 12, SEEK_SET);

//从fd2中读取数据
char buf[10] = "";
read(fd2, buf, sizeof(buf)-1);

printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标



return 0;
}

四、文件属性获取(stat)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
       #include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *statbuf);

功能:将给定的文件的相关属性,通过statbuf返回出来
参数1:要获取属性的文件路径是一个字符串
参数2:文件属性结构体指针,需要传递一个文件属性类型的结构体变量
文件属性结构体类型:
struct stat {
dev_t st_dev; /* ID of device containing file */ 设备id号
ino_t st_ino; /* Inode number */ 文件的inode号
mode_t st_mode; /* File type and mode */ 文件的类型和权限
nlink_t st_nlink; /* Number of hard links */ 硬链接数
uid_t st_uid; /* User ID of owner */ 用户id号
gid_t st_gid; /* Group ID of owner */ 组id号
dev_t st_rdev; /* Device ID (if special file) */ 特殊文件的设备号
off_t st_size; /* Total size, in bytes */ 总大小,以字节为单位
blksize_t st_blksize; /* Block size for filesystem I/O */ 块的大小
blkcnt_t st_blocks; /* Number of 512B blocks allocated */ 块的个数
};
关于文件类型和文件权限的介绍
使用 st_mode & S_IFMT后的到文件的类型
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 管道文件类型

使用 st_mode & 0777后得到文件的权限
返回值:成功返回0,失败返回-1并置位错误码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<myhead.h>

int main(int argc, const char *argv[])
{
//定义文件属性类型的数据
struct stat sb; //用于存储获得的文件属性

//调用函数的到文件属性
stat(argv[1], &sb);

switch(sb.st_mode&S_IFMT)
{
case S_IFSOCK:
{
printf("这是套接字文件\t");
}
break;
case S_IFLNK:
{
printf("这是链接文件\t");
}
break;
case S_IFREG:
{
printf("这是普通文件\t");
}
break;
case S_IFBLK:
{
printf("这是块设备文件\t");
}
break;
case S_IFDIR:
{
printf("这是目录文件\t");
}
break;
case S_IFCHR:
{
printf("这是字符设备文件\t");
}
break;
case S_IFIFO:
{
printf("这是管道文件\t");
}
break;

}

printf("%#o\t%ld\t%ld\n", sb.st_mode&0777, sb.st_size, sb.st_ino);

return 0;
}

五、目录相关操作

5.1 opendir函数

1
2
3
4
5
6
7
       #include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
功能:打开一个指定的目录,并返回该目录的目录指针
参数:要打开的目录,是一个字符串
返回值:成功返回目录指针,失败返回NULL并置位错误码

5.2 closedir函数

1
2
3
4
5
6
7
8
       #include <sys/types.h>

#include <dirent.h>

int closedir(DIR *dirp);
功能:关闭一个已经打开的目录指针
参数:目录指针
返回值:成功返回0,失败返回-1并置位错误码

5.3 readdir函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
       #include <dirent.h>

struct dirent *readdir(DIR *dirp);
功能:读取指定目录中的下一个文件或目录的信息
参数:目录指针
返回值:成功返回当前文件或目录的信息,失败返回NULL并置位错误码
struct dirent {
ino_t d_ino; /* Inode number */ //当前目录或文件的inode号 8字节
off_t d_off; /* Not an offset; see below */ 光标偏移量 8字节
unsigned short d_reclen; /* Length of this record */ 当前记录的大小 2字节
unsigned char d_type; /* Type of file; not supported 文件类型 1字节
by all filesystem types */
char d_name[256]; /* Null-terminated filename */ 文件名
};

关于文件或目录类型
DT_BLK This is a block device.

DT_CHR This is a character device.

DT_DIR This is a directory.

DT_FIFO This is a named pipe (FIFO).

DT_LNK This is a symbolic link.

DT_REG This is a regular file.

DT_SOCK This is a UNIX domain socket.

DT_UNKNOWN The file type could not be determined.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include<myhead.h>

int main(int argc, const char *argv[])
{
//判断外部传参个数
if(argc != 2)
{
printf("input error\n");
printf("usage:./a.out name\n");
return -1;
}

//定义目录指针
DIR *dp = NULL;
//打开目录
if((dp = opendir(argv[1])) == NULL)
{
perror("opendir error");
return -1;
}

//读取目录中的文件或目录信息
struct dirent *sdp = NULL;
while((sdp = readdir(dp)) != NULL)
{
//输出当前文件或目录的信息
printf("inode:%10ld, size:%10d, %10s, ",\
sdp->d_ino, sdp->d_reclen, sdp->d_name);

//输出类型
switch(sdp->d_type)
{
case DT_BLK:
{
printf("b\n");
}
break;
case DT_CHR:
{
printf("c\n");
}
break;

case DT_DIR:
{
printf("d\n");
}
break;

case DT_FIFO:
{
printf("p\n");
}
break;
case DT_LNK:
{
printf("l\n");
}
break;
case DT_REG:
{
printf("-\n");
}
break;
case DT_SOCK:
{
printf("s\n");
}
break;
}
}





//关闭目录
closedir(dp);

return 0;
}

img