一、学习内容
-
IO基础
-
概念
程序与外部设备进行交换的信息过程
-
分类
-
标准IO
封装好的相关库函数、有缓冲区、文件指针eg:fprintf、fscanf、fputc、fgetc、fputs、fgets、fopen、fclose...-
操作流程
FILE *fp通过fopen打开文件 使用fprintf、fputc、fputs、fwrite把在程序的结果写入文件中进行保存 使用fscanf、fgetc、fgets、fread把在文件中的数据读取到程序运行
-
-
文件IO
系统(内核)提供的相关函数,无缓冲区、文件描述符eg:open、close、read、write、seek...
-
-
-
标准IO
-
提供的数据类
-
FILE文件结构体类型
-
缓冲区
-
缓冲区开始地址
char * _IO_buf_base; -
缓冲区结束地址
char *_IO_buf_end; -
类型
-
行缓存
-
概念
标准输入、输出指针(stdin、stdout)对应缓冲区,大小为1024字节 -
刷新时机
-
换行刷新
-
程序结束后刷新
-
文件指针关闭后刷新
-
使用后fflush刷新文件指针后回刷新
-
输入输出切换后刷新
-
缓存区满会刷新(满了1024字节数据后再放数据进去,刷新行缓存,释放1024字节的数据,等再有如此情况出现会再次刷新行缓存,释放数据)
-
-
-
全缓存
-
概念
普通文件指针(FILE *fp)对应缓存,大小为4096字节 -
刷新时机
-
程序结束后刷新
-
文件指针关闭后刷新
-
使用fflush函数
-
输入和输出切换时刷新
-
缓存区满了后再添加数据会先释放先前的4096字节再添加新的数据
-
-
-
不缓存
-
概念
标准出错文件指针对应缓冲区,大小为0
-
-
-
-
-
文件指针
-
stdin(标准输入指针)
-
stdout(标准输出指针)
-
stderr(标准出错指针)
-
-
-
fopen(打开文件)
FILE *fopen(const char *pathname, const char *mode);-
返回值
成功返回打开的文件地址 失败返回NULL -
参数
-
参数1:要打开文件的路径
-
参数2:文件打开的方式
-
r
只读形式,读光标在开头,不存在就报错 -
r+
读写形式,读写光标在一起且在开头,不存在就报错 -
w
只写形式,文件存在就清空文件,不存在就创建该文件,光标在开头 -
w+
读写形式,文件存在就清空文件,不存在就创建该文件,读写光标都在开头 -
a
追加形式,文件不存就创建该文件,光标在文件结尾 -
a+
读或追加形式,文件不存在就创建该文件,第一次读数据光标在开头,否则读写光标都在末尾
-
-
-
功能
用指定方式打开一个指定文件,并返回该文件的地址
-
-
fclose(关闭文件)
int fclose(FILE *stream)-
返回值
成功返回0 失败返回EOF -
参数
要关闭的文件指针 -
功能
关闭给定文件指针
-
-
fgetc\fputc(单字符的输入和输出)
-
fputc
int fputc(int c,FILE *stream)-
返回值
成功返回写入字符的ascii值 失败返回EOF -
参数
参数1:要写入的字符串
参数2:打开的文件指针 -
功能
把字符写入到文件指针stream指向的文件中
-
-
fgetc
int fgetc(FILE *stream)-
返回值
成功返回ascii值 失败返回EOF -
参数
打开文件的指针 -
功能
从给定的文件中读取一个字符
-
-
-
错误码
-
概念
内核空间函数调用出问题时,内核空间会向用户空间反馈一个错误信息,为了方便查看错误信息,将这些错误信息进行编号,这个错误号码就叫做错误码 -
函数strerror
char *strerror(int errnum);-
返回值
错误码对应的错误信息 -
参数
错误码 -
功能
将给定的错误码,转变成错误信息
-
-
perror函数
void perror(const char *s)-
无返回值
-
参数
提示字符串,会自动提供一个冒号输出结束后会自己加一个换行符 -
功能
向标准错误缓冲区写入最新的错误码所对应的错误信息
-
-
-
fputs\fgets(字符串输入输出)
-
fputs
int fputs(const char *s, FILE *stream);-
返回值
成功犯规写入字符个数(字符串长度) 失败返回EOF -
参数
参数一:要写入的字符串的起始地址
参数二:打开文件的指针 -
功能
将给定的字符串写入到指定的文件中
-
-
fgets
char *fgets(char *s, int size, FILE *stream);-
返回值
成功返回容器的起始地址 失败返回NULL -
参数
参数1:存放数据所用的容器
参数2:读取的大小
参数3:文件指针 -
功能
从stream所指向的文件中,最多读取size-1的字符带s中,读取过程中遇到回车或者文件结束,会结束本次读取,回车也会被写入到s容器中,并在s容器的结尾加上'\0'
-
-
-
系统时间函数
-
time_t time(time_t *tloc);
-
功能
获取从1970年1月1日0时0分0秒,到目前累计的秒数 -
参数
用于接收的秒数 -
返回值
秒数 -
使用方式
-
使用方式1: time_t sys_time = time(NULL)
-
使用方式2: time_t sys_time = 0; time(&sys_time);
-
-
-
struct tm *localtime(const time_t *timep);
-
功能
将time_t 秒数,转换为时间类型的结构体 -
参数
time_t 类型的时间秒数 -
返回值
时间结构体
-
-
-
sprintf\snprintf(将格式串转换为字符串)
-
sprintf
int sprintf(char *str, const char *format, ...);-
返回值
成功返回转换的字符个数,失败返回-1 -
参数
参数1:存放格式串的字符数组
参数2:格式串,可以包含格式控制符
参数3:可变参数,根据参数2中的格式控制符个数确定 -
功能
将格式串转换为字符串放入字符数组中
-
-
snprintf
int snprintf(char *str, size_t size, const char *format, ...);比sprintf更加安全,因为多了一个size的限制其余相同
-
-
fprintf\fscanf(格式化读写函数)
-
fprintf
int fprintf(FILE *stream, const char *format, ...);-
返回值
成功返回写入文件的字符实际个数 失败返回-1 -
参数
参数1:文件指针
参数2:格式串,可以包含格式控制符
参数3:可变参数,根据参数2而定 -
功能
向指定文件中写入一个格式串
-
-
fscanf
int fscanf(FILE *stream, const char *format, ...);-
返回值
成功返回读取数据的个数 失败返回EOF -
参数
参数1:文件指针
参数2:格式串,可以包含格式控制符
参数3:可变参数,根据参数2而定 -
功能
从指定文件中读取一些数据放入程序中
-
-
-
fread\fwrite(模块化读写)
-
fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);-
返回值
成功读取返回本次读取的项数值 失败返回一个小于项数的数字 -
参数
参数1:要读取的数据的起始地址,万能指针,可以写入任意类型的数据
参数2:每一项数据的大小
参数3:要读取数据的总项数
参数4:要被读取的文件 -
功能
从stream文件中读取nmemb项数据,每项数据大小为size,将读取出来的数据放入ptr指向的容器中
-
-
fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream)-
返回值
成功写入返回本次写入的项数值 失败返回一个小于项数的数字 -
参数
参数1:要写入的数据的起始地址,万能指针,可以写入任意类型的数据
参数2:每一项数据的大小
参数3:要写入数据的总项数参数4:要被写入的文件 -
功能
将起始地址为ptr的nmemb项数据(每一项的大小为size),写入到stream文件中
-
-
-
-
脑图
二、作业
1> 使用fread和fwrite完成两个文件的拷贝,要求源文件和目标文件由外界输入
代码解答:
#include <myhead.h>
#define BUFFER_SIZE 1024 // 定义缓冲区大小为 1024 字节
int main(int argc, const char *argv[])
{
FILE *source = NULL; // 文件指针,用于源文件
FILE *dest = NULL; // 文件指针,用于目标文件
// 检查命令行参数个数是否至少包含源文件和目标文件两个参数
if (argc < 3) {
fprintf(stderr, "Usage: %s <source file> <destination file>\n", argv[0]); // 提示正确使用方法
return -1; // 返回错误代码 -1
}
// 打开源文件
if ((source = fopen(argv[1], "r")) == NULL) {
perror("fopen source error"); // 打印源文件打开失败的错误信息
return -1; // 返回错误代码 -1
}
// 打开目标文件
if ((dest = fopen(argv[2], "w+")) == NULL) {
perror("fopen dest error"); // 打印目标文件打开失败的错误信息
fclose(source); // 关闭已经成功打开的源文件
return -1; // 返回错误代码 -1
}
size_t a; // 用于存储 fread 返回的已读取字节数
char buffer[BUFFER_SIZE]; // 定义缓冲区,用于存储每次从源文件读取的内容
// 循环从源文件中读取数据,每次最多读取 BUFFER_SIZE 字节,直到读到文件末尾
while ((a = fread(buffer, 1, BUFFER_SIZE, source)) > 0) {
// 将从源文件中读取的 a 字节数据写入目标文件
fwrite(buffer, 1, a, dest);
}
// 关闭源文件和目标文件,确保资源被释放
fclose(source);
fclose(dest);
// 打印文件拷贝完成的提示信息
printf("文件拷贝完成。\n");
return 0; // 正常结束程序
}
效果图展示:
2> 使用fgets获取当前文件的总行数
代码解答:
#include <myhead.h>
#define BUFFER_SIZE 1024
int main(int argc, const char *argv[])
{
FILE *fp=NULL;
if((fp=fopen("./file.txt","r"))==NULL)
{
perror("fopen error");
return -1;
}
char buffer[BUFFER_SIZE];
int line_count=0;
while(1)
{
char *res=fgets(buffer,BUFFER_SIZE,fp);
if(res==NULL)
{
break;
}
line_count++;
}
fclose(fp);
printf("文件有%d行",line_count);
return 0;
}
效果图展示:
3> 向文件中每一秒打印当前系统时间同时也在终端上面显示
例如:
1、17:10:30
2、17:10:31
3、17:10:32
ctrl +c结束程序
./a.out
4、17:20:15
5、17:20:16
注意前面的编号!!
代码解答:
#include <stdio.h>
#include <time.h>
#include <signal.h>
volatile int stop = 0; // 全局变量,用于控制程序停止
// 处理 Ctrl+C 信号的函数
void handle_sigint(int sig) {
stop = 1; // 当接收到 Ctrl+C 信号时,将 stop 设置为 1 以终止循环
}
int main() {
FILE *file = fopen("output.txt", "a"); // 以追加模式打开文件
if (file == NULL) { // 检查文件是否成功打开
perror("fopen error");
return -1;
}
signal(SIGINT, handle_sigint); // 注册信号处理函数,处理 Ctrl+C
int count = 1; // 行编号
time_t rawtime; // 当前时间
struct tm *timeinfo; // 用于存储本地时间
struct tm prev_timeinfo = {0}; // 用于存储前一秒的时间
while (!stop) { // 无限循环,直到 stop 被设置为 1
time(&rawtime); // 获取当前时间
timeinfo = localtime(&rawtime); // 转换为本地时间
// 如果当前秒数与之前的秒数不同,说明已经过了一秒
if (timeinfo->tm_sec != prev_timeinfo.tm_sec) {
// 输出编号和时间到终端
printf("%d、%04d-%02d-%02d %02d:%02d:%02d\n",
count,
timeinfo->tm_year + 1900, // 年份
timeinfo->tm_mon + 1, // 月份
timeinfo->tm_mday, // 日
timeinfo->tm_hour, // 小时
timeinfo->tm_min, // 分钟
timeinfo->tm_sec); // 秒
// 同样输出编号和时间到文件
fprintf(file, "%d、%04d-%02d-%02d %02d:%02d:%02d\n",
count,
timeinfo->tm_year + 1900, // 年份
timeinfo->tm_mon + 1, // 月份
timeinfo->tm_mday, // 日
timeinfo->tm_hour, // 小时
timeinfo->tm_min, // 分钟
timeinfo->tm_sec); // 秒
fflush(file); // 刷新文件缓冲区,确保数据立即写入
count++; // 编号自增
prev_timeinfo = *timeinfo; // 更新前一秒的时间
}
}
fclose(file); // 关闭文件
printf("\n程序结束。\n"); // 打印结束信息
return 0; // 正常退出程序
}
效果图展示:
三、总结
学习内容概述:
今天的学习重点是IO基础,主要包括标准IO和文件IO的操作流程及其使用方法。你学习了标准IO如何通过封装好的库函数与文件进行交互,以及文件IO如何通过系统提供的函数来进行无缓冲区的文件操作。此外,还涉及缓冲区的类型、文件指针的操作、字符与字符串的输入输出操作、错误码处理机制、系统时间函数、格式化读写函数、模块化读写等内容。
学习难点:
1. 缓冲区类型和刷新机制:
行缓存、全缓存、无缓存的概念及其刷新时机在实际操作中较为抽象,特别是如何正确管理和优化缓冲区的使用。
2. 文件指针与文件描述符的区别:
标准IO和文件IO的核心差异之一在于文件指针和文件描述符的使用,理解它们各自的应用场景需要通过更多的实践。
3. 系统时间函数的使用:
理解time()函数获取的时间戳以及如何转换为结构体时间格式是时间操作中容易混淆的部分。
主要事项:
1. 标准IO通过缓冲区来提升效率,缓冲区的类型和大小需要根据使用场景合理选择,并通过`fflush`等函数手动刷新缓冲区。
2. 文件操作时要注意不同模式(如`r`、`w`、`a`等)下的读写权限,以及文件打开失败后的错误处理(使用`strerror`或`perror`函数)。
3. 格式化输入输出函数(如`fprintf`、`fscanf`)和模块化读写函数(如`fread`、`fwrite`)的区别在于前者基于格式,后者基于数据块进行读写。
未来学习的重点:
1. 进一步理解和优化缓冲区管理:
在实际项目中,如何根据不同的场景选择行缓存、全缓存或无缓存,以提升程序的性能。
2. 深入掌握文件IO的应用场景:
在无缓冲区的文件IO操作中,深入理解文件描述符的作用,并学习如何进行高效的文件读写。
3. 实践系统时间和日期处理:
熟练掌握系统时间的获取和格式化输出,特别是涉及到跨平台的时间操作和时间戳处理。