IO进程 day01
IO进程
- 1. 课程介绍
- 2. IO
- 3. 标准IO和文件IO
- 3.1. 标准IO
- 3.2. 文件IO
- 3.3. 标准IO的调用逻辑
- 3.4. 缓存机制
- 5. 标准IO
- 5.1. 标准IO的特点
- 5.2. 流
- 5.2.1. 定义
- 5.2.2. 分类
- 5.2.3. 流指针 FILE*
- 5.3. 缓存区的分类
- 5.4. 计算缓存区大小
- 5.4.1. 计算
- 5.4.2. fflush
- 5.5. 标准IO的函数接口(上)
- 5.5.1. fopen/fclose
- 5.5.2. fgetc/fputc
- 5.5.3.fgets/fputs
- 5.5.4. perror
- 5.5.5. fprintf
- #练习一
- #练习二
- time
- localtime
1. 课程介绍
- IO
文件IO
标准IO
目录IO
库:静态库,动态库 - 进程
进程:进程,进程通信
线程:线程,线程通信
2. IO
对文件进行输入输出操作,打开文件——读文件——写文件
I:input——输入——写文件
O: output——输出——读文件
文件放在磁盘空间,断电不丢失
bcd-lsp 文件类型 b 块设备文件 c 字符设备文件 d 目录文件 – 普通文件 L 链接文件 s 套接字文件 P 管道文件
3. 标准IO和文件IO
3.1. 标准IO
- 由C库定义的标准输入输出函数,不同的操作系统只要有C库,就可以直接用,可移植性强
- C库定义时调用文件IO,在C库调用标准IO之前,由C库判断操作系统,从而调用相应的文件IO
3.2. 文件IO
- 内核层向应用层提供的输入输出函数接口,由内核提供。
- 内核不同文件IO不同,不同内核不可通用,可移植性差。
3.3. 标准IO的调用逻辑
标准IO由C库提供,C库封装相应函数时,通过封装内核层的文件IO来进行调用。调用标准IO时,C库中有判断操作系统的代码块,以此来保证高可移植性的特点
if(Linux操作系统)
{
调用Linux的系统函数(文件IO)
}
if(Windows操作系统)
{
调用Windows的系统函数(文件IO)
}
if(macos操作系统)
{
调用macos的系统函数(文件IO)
}
3.4. 缓存机制
缓存区只有在使用时才会被开辟
没有缓存机制的系统调用:
应用层读写文件——调用内核层函数接口——内核层与硬件层交互——拿到数据返回给应用层——每次读写重复操作
有缓存机制的系统调用:
应用层读写文件——调用内核层函数接口——内核层与硬件层交互——拿到数据返回应用层的缓存区——每次读写从缓存区拿数据
5. 标准IO
5.1. 标准IO的特点
1. 标准 IO 是 C 库中提供的一组专门用于输入输出的函数接口
2. 标准 IO 由ANSI C 标准定义,不仅在Unix系统上,在很多操作系统上都实现了标准IO——可移植性高
3. 标准 IO 通过缓存机制减少系统调用次数,提高效率
4. 标准 IO 默认打开三个流,标准输入(stdin),标准输出(stdout),标准出错(stderr)
5. 标准 IO 库的所有操作都是围绕流(stream)来进行的,在标准IO中,流用FILE *来描述
5.2. 流
5.2.1. 定义
所有的IO操作仅是简单的从程序移进或者移出,这种字节流,就称为流。
5.2.2. 分类
文本流/二进制流
5.2.3. 流指针 FILE*
FILE* 类型是FILE结构体的结构体指针,FILE结构体由标准库定义,用于表示一个打开的文件或输入输出流
查看结构体:vi -t FILE
查找的是FILE是结构体名,所以输入1,回车
追踪:ctrl + ] 返回追踪:ctrl + [
FILE是_IO_FILE结构体重定义的名,所以追踪_IO_FILE,然后找定义,所以输入1,回车
定义 注释 _IO_read_ptr 读取地址 _IO_read_end 读取结束后地址 _IO_read_base 读取起始地址 _IO_write_base 写入起始地址 _IO_write_ptr 写入地址 _IO_write_end 写入结束后地址 _IO_buf_base 缓存起始地址 _IO_buf_end 缓存结束后地址
5.3. 缓存区的分类
1. 全缓存:基于文件
刷新缓存:
1. 程序正常退出: return,exit,fclose(关闭流指针)
2. fflush:强制刷新缓存区
3. 缓存区满
2. 行缓存:基于终端:stdin/stdout
刷新缓存:
1. 程序正常退出: return,exit,fclose(关闭流指针)
2. fflush:强制刷新缓存区
3. 缓存区满
4. 换行
3. 不缓存:stderr
5.4. 计算缓存区大小
5.4.1. 计算
stdout->_IO_buf_end - stdout->_IO_buf_base;
// 高地址 - 低地址
5.4.2. fflush
#include <stdio.h>
int fflush(FILE* stream);
功能:强制刷新缓存区
参数:流指针,NULL刷新所有流
返回值:成功返回0 不成功返回-1(EOF),更新errno
5.5. 标准IO的函数接口(上)
函数 | 函数功能 |
---|---|
fopen/fclose | 打开/关闭文件 |
fgetc/fputc | 读/写一个字符 |
fgets/fputs | 读/写一个字符串 |
fread/fwrite | 读/写一个二进制文件 |
fseek | 移动文件指针 |
5.5.1. fopen/fclose
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:打开文件
参数:
path:打开文件的路径
mode:打开方式
打开方式 权限 功能 r 只读 文件指针定位到文件开头(有文件) r+ 可读可写 文件指针定位到文件开头(有文件) w 只写 文件不存在则创建,文件存在则清空,文件指针定位到文件开头 w+ 可读可写 文件不存在则创建,文件存在则清空,文件指针定位到文件开头 a 只写 文件不存在则创建,文件存在则追加,文件指针定位到文件末尾 a+ 可读可写 文件不存在则创建,文件存在则追加,文件指针定位到文件末尾
返回值:
成功:返回文件流指针
失败:返回NULL,更新errno
一个任务中最多打开1024个文件,一个文件可以被重复打开,打开文件是有限资源,使用结束之后及时关闭
#include <stdio.h>
int fclose(FILE *stream);
功能:关闭文件
参数:流指针,指向打开的文件
返回值:成功0,失败EOF且更新errno
示例:
#include <stdio.h>
int main ()
{
FILE* fp = fopen("./test.c", "w");
if(fp == NULL)
{
perror("open err");
return -1;
}
printf("open success\n");
fclose(fp);
if(fp != NULL)
{
perror("close err");
return -1;
}
printf("close succes\n");
return 0;
}
5.5.2. fgetc/fputc
fgetc
#include <stdio.h>
int fgetc(FILE *stream);
功能:从文件中读取一个字符
参数:流指针,指向目标文件
返回值:
成功:返回读取到的字符的ASCII值
失败:读取到文件末尾或读取失败返回-1
fputc
#include <stdio.h>
int fputc(int c, FILE *stream);
功能:向文件中写入一个字符
参数:
c:写入的字符的ASCII值
stream:流指针,指向要写入的文件
返回值:
成功:返回写入的ASCII值
失败:返回EOF,更新errno
练习
使用fgetc和fputc实现将文件1(file1)中的内容复制到文件2(file2)里面#include <stdio.h> int main () { // 打开两个文件 // 只读打开文件1,因为要读取文件1的内容 FILE* fp1 = fopen("./file1.c", "r"); if(fp1 == NULL) { perror("open file1 err"); return -1; } // 只写打开文件2,因为只需要向文件2中写入内容 FILE* fp2 = fopen("./file2.c", "w"); if(fp2 == NULL) { perror("open file2 err"); return -1; } // 循环读取文件1 的字符,写入文件2中 int ch = 0; while(ch = fgetc(fp1) != -1) fputc(ch,fp2); // 关闭文件 fclose(fp1); fclose(fp2); }
5.5.3.fgets/fputs
fgets
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:从文件中获取指定长度的字符串
参数:
s:字符串存放首地址
size:期望获取的字符个数
stream:流指针,指向目标文件
返回值:
成功:返回获取的字符串首地址
失败或读到文件结尾返回NULL
注意事项
- fgets获取到字符串之后会自动补’\0’,所以实际获取的字符个数为size-1
- 文件中不满size-1个字符,有多少字符读多少字符,最后自动补’\0’
- 当读取到’\n’时,停止读取内容,再次调用fgets之后从下一行起始位置开始继续读取
fputs
#include <stdio.h>
int fputs(const char *s, FILE *stream);
功能:向指定文件中输入字符串
参数:
s:输入字符串的首地址
stream:指向目标文件
返回值:
成功返回输入字符个数
失败返回EOF
5.5.4. perror
#incldue <errno.h>
void perror(const char *s);
功能:根据errno获取错误信息,将信息输出到终端
参数:s:提示内容
返回值:无
5.5.5. fprintf
#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
功能:向指定文件中以指定的格式写入数据
参数:
stream:流指针,指向目标文件
format:指定格式
…:多个参数
返回值:
成功返回输出字符个数
失败返回EOF
示例1
在printf的基础上多了一个指向文件的参数
int age = 15;
char* name = "Zhangsan";
double height = 5.9;
fprintf(file, "name is %s\n", name);
fprintf(file, "age is %d\n", age);
fprintf(file, "height is %.1f", height);
示例2
#include <stdio.h>
int main ()
{
// 打开一个文件
FILE* fp = fopen("./file.txt", "w");
// 容错
if(fp == NULL)
{
perror("open file.txt err");
return -1;
}
// 向打开的文件中输入内容
fprintf(file.txt,"hello world\n");
// 关闭文件
fclose(file.txt);
}
#练习一
用fgetc和fputc实现cat -n file的功能,从命令行输入文件名
// 用fgetc和fputc实现cat -n file的功能,从命令行输入文件名
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp = NULL; // 定义流指针,指向目标文件
int n = 1; // 行号,从1开始
char ch = 0; // 获取从文件中获得的字符,方便输出和判断换行
int flag = 1; // 作为一个从新一行的开始读取的标志,文件读取到新一行的开始时置1
// 命令行传参判错
if (argc != 2)
{
perror("argc err");
return EOF;
}
// 打开文件
fp = fopen(argv[1], "r");
// 容错判断
if (fp == NULL)
{
perror("open err");
return EOF;
}
// 循环输出文件内容
while (1) // 死循环一直输出
{
// 获取字符
ch = fgetc(fp);
// 文件内容打印完成之后结束循环
if (ch == EOF)
break;
// 判断是否需要打印行号
if (flag == 1) // flag作为新一行的标志,为1时说明是新的一行
{
printf("%-4d", n++); // ++在后先输出后自加
flag = 0; // 新的一行开始后flag置0,说明后面的字符不是新的一行,不需要打印行数
}
// 判断获取到的字符是否为\n
if (ch == '\n')
flag = 1; // 读取到\n说明这个字符打印之后,就是新的一行,标志变量置1,表示后面要换行了
// 输出字符
fputc(ch, stdout); // stdout表示向终端输入
}
// 关闭文件
fclose(fp);
return 0;
}
#练习二
编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl+C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
time
#include <time.h>
time_t time(time_t *tloc);
功能:获取1970-01-01 00:00:00到现在的秒数
参数:tloc:保存获取到的时间的地址
返回值:
成功返回获取到的时间
失败返回-1
localtime
#include <time.h>
struct tm *localtime(const time_t *timep);
功能:将time获取的时间转换成结构体中的格式
参数:timep保存时间变量的地址
返回值:
成功返回结构体地址
失败返回NULL
struct tm
{
int tm_sec; // seconds(0-60)
int tm_min; // minutes(0-59)
int tm_hour; // Hours(0-23)
int tm_yday; // day in the year(0-365, 1 Jan = 0)
int tm_wday; // day in the week (0-6, sunday = 0)
int tm_mday; // day in the month (1-31)
int tm_mon; // month(0-11)
int tm_year; // year-1900
}
#include <stdio.h>
#include <time.h>
#include <string.h>
#define N 64
int main(int argc, char const *argv[])
{
// 定义变量
FILE *fp = NULL; // 指向文件
time_t t = 0; // 获取当前时间(秒)
struct tm *tm = NULL; // 获取当前的时间
int n = 1; // 行号
char ch[N] = {}; // 计算行数时获取字符串判断\n
// 打开文件
fp = fopen("./file1.txt", "a+");
// 计算行数
while (fgets(ch,64,fp) != NULL) // fgets获取到文件结尾后会返回NULL,然后循环结束
if (ch[strlen(ch)-1 == '\n']) // strlen不算\0,strlen是长度,长度-1是最后一个元素
n++;
while (1)
{
// 获取时间
time(&t);
tm = localtime(&t);
// 向终端和文件输入时间
fprintf(fp, "%-4d%d-%d-%d %d:%d:%d\n", n, tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
printf("%-4d%d-%d-%d %d:%d:%d\n", n, tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
n++;
// 刷新缓存
fflush(NULL);
// 步长为1秒
sleep(1);
}