最早接触的IO
#include <stdio.h> //标准IO的头文件
标准IO的接口:printf scanf
什么是标准IO/文件IO?
标准IO:库函数
fopen fgetc fputc fputs fgets fread fwrite fclose printf scanf...
文件IO:系统调用
open read write close...
什么是系统调用/什么是库函数?
系统调用:就是linux内核给用户提供的进入内核的接口,只要调用对应的函数就会从用户空间进入到内核空间。系统调用效率比较低(没有缓冲区),系统调用的移植性差。但不是说系统调用不重要,它有自己独特的使用场景,例如读取鼠标上报的键值就不需要缓冲区。
库函数:库函数=缓冲区+系统调用。库函数的效率比较高,库函数的移植性比较强。
标准IO
什么是FILE?
FILE是应用层的一个结构体,这个结构体是调用fopen的时候产生的,FILE是用来记录fopen打开时候的各种信息的结构体(只读,光标,缓冲区)。以后对这个文件的操作都是通过这个FILE完成的。在一个正在执行的程序中默认已经有三个文件指针了。
文件指针(FILE *) | 功能 |
---|---|
stdin | 标准输入 |
stdout | 标准输出 |
stderr | 标准出错 |
/vscode快捷键
// 跳转 ctrl+鼠标左键
// 回退 alt + <-
// 对齐 alt + shift + f
// 注释 ctrl + /
// 开关终端 ctrl + j
typedef struct _IO_FILE FILE;
struct _IO_FILE {
char* _IO_buf_base; /*缓冲区的起始地址 */
char* _IO_buf_end; /*缓冲区的结束地址. */
};
fopen/fclose函数使用
man man
1 可执行程序或 shell 命令
2 系统调用(内核提供的函数)
3 库调用(程序库中的函数)
man 2 系统调用接口
man 3 库函数接口
fopen/fclose的API
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:使用标准IO打开文件
参数:
@pathname:打开的文件名 "/home/linux/1.c"
@mode :打开文件的方式 "r" "r+"
r :以只读的方式打开文件,将光标定位到文件的开头
r+ :以读写的方式打开文件,将光标定位到文件的开头
w :以只写的方式打开文件,如果文件存在就清空文件,如果文件不存在就创建文件
将光标定位的开头
w+ :以读写的方式打开文件,如果文件存在就清空文件,如果文件不存在就创建文件
将光标定位的开头
a :以追加的方式打开文件,如果文件不存在就创建文件,如果文件存在将光标定位到文件结尾
a+ :以读和追加的方式打开文件,如果文件不存在就创建文件,读光标在开头,写光标在结尾。
返回值:成功返回FILE的结构体指针,失败返回NULL,置位错误码
int fclose(FILE *stream);
功能:关闭文件
参数:
@stream:文件指针
返回值:成功返回0,失败返回EOF(-1)(end of file),置位错误码
fopen/fclose的实例
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp;
// 以只写的方式打开文件,如果文件不存在就创建文件
// if ((fp = fopen("./hello.txt", "w")) == NULL) {
// printf("打开文件失败\n");
// return -1;
// }
// if ((fp = fopen("./hello.txt", "r")) == NULL) {
// printf("打开文件失败\n");
// return -1;
// }
if ((fp = fopen("./hello.txt", "a")) == NULL) {
printf("打开文件失败\n");
return -1;
}
// 关闭文件
fclose(fp);
printf("hello DC22091 everyone!!!!\n"); //就是在向标准输出中写数据
// fclose(stdout); // 关闭标准输出
printf("hello DC22091 everyone*****\n");//这句话不会在终端上显示
fclose(stdin); //关闭标准输入
int a;
scanf("%d",&a); //scanf就会阻塞接收数据了
printf("a = %d\n",a);
return 0;
}
linux内核错误码问题
linux错误的问题
错误码使用实例
#include <stdio.h>
#include <errno.h>
int main(int argc, const char* argv[])
{
FILE* fp;
if ((fp = fopen("./hello.txt", "r")) == NULL) {
printf("打开文件失败,errno = %d\n",errno);
return -1;
}
// 关闭文件
fclose(fp);
return 0;
}
将错误码转换为错误信息的函数
#include <string.h>
char *strerror(int errnum);
功能:将错误码转换为错误信息的函数
参数:
@errnum:错误码
返回值:错误信息
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char* argv[])
{
FILE* fp;
if ((fp = fopen("./hello.txt", "r")) == NULL) {
printf("打开文件失败,errno = %d\n",errno);
//将错误码转换为错误信息,通过printf打印出来
printf("%s\n",strerror(errno));
return -1;
}
// 关闭文件
fclose(fp);
return 0;
}
perror打印错误函数
void perror(const char *s);
功能:只要错误码被置位了,它就可以直接打印出错误信息
参数:
@s:用户自己附加的信息
返回值:无
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp;
if ((fp = fopen("./hello.txt", "r")) == NULL) {
//只要函数失败置位错误码,perror函数就可以将错误信息打印出来
perror("open error\n");
return -1;
}
// 关闭文件
fclose(fp);
return 0;
}
fgetc/fputc函数的使用
fgetc/fputc函数API
int fgetc(FILE *stream);
功能:从文件中读取一个字符(光标会随着读自动向后移动)
参数:
@stream:文件指针
返回值:成功返回读取到字符的Ascii,失败返回EOF
int fputc(int c, FILE *stream);
功能:向文件中写一个字符(光标向后移动)
参数:
@c:字符
@stream:文件指针
返回值:成功返回写字符的Ascii,失败返回EOF
fgetc函数的使用
#include <stdio.h>
int main(int argc,const char * argv[])
{
FILE *fp;
if((fp = fopen("./hello.txt","r"))==NULL){
perror("fopen error");
return -1;
}
printf("%c",fgetc(fp)); //从文件中读一个字符,打印到终端上,光标向后移动一个字符的位置
printf("%c",fgetc(fp)); //文件每一行结尾都有换行符(除了最后一行,文件最后有EOF)
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%c",fgetc(fp));
printf("%d",fgetc(fp));
putchar(10);
fclose(fp);
return 0;
}
fputc函数的使用
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp;
if ((fp = fopen("./hello.txt", "w")) == NULL) {
perror("fopen error");
return -1;
}
fputc('h', fp); //向文件中写入h字符,光标会向后移动一个字符的位置
fputc('e', fp); //向文件中写入e字符,光标会向后移动一个字符的位置
fputc('l', fp);
fputc('l', fp);
fputc('o', fp);
fclose(fp);
return 0;
}
fgetc/fputc的练习
1.编写一个程序用于统计文件行号
./a.out hello.c
#include <stdio.h>
// ./a.out filename
int main(int argc, const char* argv[])
{
FILE* fp;
char ch;
int count = 0;
// 1.校验参数的个数
if (argc != 2) {
printf("input error,try again\n");
printf("usage : ./a.out filename\n");
return -1;
}
// 2.以只读方式打开文件
if ((fp = fopen(argv[1], "r")) == NULL) {
perror("fopen error");
return -1;
}
// 3循环读取文件中的字符
while ((ch = fgetc(fp)) != EOF) {
if (ch == '\n') {
count++;
}
}
//4.打印行号
printf("line = %d\n",count);
//5.关闭文件
fclose(fp);
return 0;
}
使用fgetc和fputc实现文件的拷贝
./a.out srcfile destfile
注:可以使用diff命令比较文件 diff srcfile destfile 如果命令没有结果说明两个文件相同
#include <stdio.h>
// ./a.out srcfile destfile
int main(int argc, const char* argv[])
{
FILE *fp1, *fp2;
char ch;
int count = 0;
// 1.校验参数的个数
if (argc != 3) {
printf("input error,try again\n");
printf("usage : ./a.out srcfile destfile\n");
return -1;
}
// 2.以只读方式打开源文件
if ((fp1 = fopen(argv[1], "r")) == NULL) {
perror("fopen error");
return -1;
}
// 3.以只写方式打开目标
if ((fp2 = fopen(argv[2], "w")) == NULL) {
perror("fopen error");
return -1;
}
// 4.循环读取文件中的字符
while ((ch = fgetc(fp1)) != EOF) {
fputc(ch, fp2);
}
// 5.关闭文件
fclose(fp1);
fclose(fp2);
return 0;
}
fgets/fputs函数的使用
fgets/fputs函数的API
char *fgets(char *s, int size, FILE *stream);
功能:从文件(stdin)中读取一个字符串到s中
fgets遇到EOF或者'\n'的时候就会停止,会将'\n'存储到s中,s中存放字符的结尾是'\0'
参数:
@s:内存的首地址
@size:想要读取的字符的个数(最多size-1)
@stream:文件指针(fp stdin)
返回值:成功返回的是s,如果出错或到了文件结尾返回NULL
int fputs(const char *s, FILE *stream);
功能:将s中的内容写入到文件中(stdout)
参数:
@s:字符串的首地址
@stream:文件指针
返回值:成功返回>0的数,失败返回EOF(-1)
fgets使用实例
1.使用fgets从文件中读取字符串
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp;
char s[128] = {0};
if ((fp = fopen("./hello.txt", "r")) == NULL) {
perror("fopen error");
return -1;
}
//从文件中读取字符串到s中,遇到‘\n’或EOF会停止
fgets(s,sizeof(s),fp);
puts(s);
fgets(s,sizeof(s),fp);
puts(s);
fclose(fp);
return 0;
}
fgets从标准输入中读取字符串
#include <stdio.h>
#include <string.h>
int main(int argc, const char* argv[])
{
FILE* fp;
char s[128] = {0};
// hello\n\0
fgets(s,sizeof(s),stdin);
//将终端上输入的'\n'清除
s[strlen(s)-1]='\0';
printf("s = %s\n",s);
return 0;
}
使用fgets统计文件的行号
./a.out filename
char s[10];
#include <stdio.h>
#include <string.h>
// ./a.out filename
int main(int argc, const char* argv[])
{
FILE* fp;
char s[10] = { 0 };
int line = 0;
// 1.校验参数的个数
if (argc != 2) {
printf("input error,try again\n");
printf("usage : ./a.out filename\n");
return -1;
}
// 2.以只读方式打开文件
if ((fp = fopen(argv[1], "r")) == NULL) {
perror("fopen error");
return -1;
}
// 3循环读取文件中的字符
while (fgets(s, sizeof(s), fp) != NULL) {
if (strlen(s) == (sizeof(s) - 1)) {
if (s[sizeof(s) - 2] != '\n')
continue;
}
line++;
}
// 4.打印行号
printf("line = %d\n", line);
// 5.关闭文件
fclose(fp);
return 0;
}
fputs使用实例
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp;
char s[] = "sdfasdfasfd****fasdfasdfas\n";
if ((fp = fopen("./hello.txt", "w")) == NULL) {
perror("fopen error");
return -1;
}
fputs(s,fp); //向文件中写入字符串
fputs(s,stdout);//向终端上写入字符串
fclose(fp);
return 0;
}
练习:使用fgets和fputs实现文件的拷贝
./a.out srcfile destfile
#include <stdio.h>
// ./a.out srcfile destfile
int main(int argc, const char* argv[])
{
FILE *fp1, *fp2;
char s[128] = {0};
int count = 0;
// 1.校验参数的个数
if (argc != 3) {
printf("input error,try again\n");
printf("usage : ./a.out srcfile destfile\n");
return -1;
}
// 2.以只读方式打开源文件
if ((fp1 = fopen(argv[1], "r")) == NULL) {
perror("fopen error");
return -1;
}
// 3.以只写方式打开目标
if ((fp2 = fopen(argv[2], "w")) == NULL) {
perror("fopen error");
return -1;
}
// 4.循环读取文件中的字符
while (fgets(s,sizeof(s),fp1)) {
fputs(s,fp2);
}
// 5.关闭文件
fclose(fp1);
fclose(fp2);
return 0;
}
将系统的时间写入到文件中
获取系统时间的API
#include <time.h>
time_t time(time_t *tloc);
功能:获取从1970-1-1 00:00:00到当前的秒钟数
参数:
@tloc:写为NULL
返回值:成功返回秒钟数,失败返回-1置位错误码
struct tm *localtime(const time_t *timep);
功能:将秒钟转换为tm的结构体,这个结构体内部就是年月日时分秒
参数:
@timep:秒钟数的地址
返回值:成功返回tm结构体指针,失败返回NULL,置位错误码
struct tm {
int tm_sec; //秒
int tm_min; //分钟
int tm_hour; //小时
int tm_mday; //天
int tm_mon; //月 +1
int tm_year; //年 + 1900
int tm_wday; //周几(0-6, Sunday = 0)
int tm_yday; //在一年内的天数
int tm_isdst; //夏令时
};
获取系统时间实例
#include <stdio.h>
#include <time.h>
#define PRINT_ERR(msg) \
do { \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char* argv[])
{
time_t ts;
struct tm* tm;
if ((ts = time(NULL)) == -1)
PRINT_ERR("get time error");
if ((tm = localtime(&ts)) == NULL)
PRINT_ERR("change time error");
printf("%d-%02d-%02d %02d:%02d:%02d\n",
tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
return 0;
}
snprintf函数
int snprintf(char *str, size_t size, const char *format, ...);
功能:将字符串格式化到str中
参数:
@str:内存的首地址
@size:字符个数(最多将size-1个字符格式化到str中,最后存放的是'\0')
@format,...:和printf的用法一样
返回值:成功返回大于0的数(格式化字符的个数),失败返回负数
#include <stdio.h>
int main(int argc,const char * argv[])
{
char s[128] = {0};
char *p = "hello world";
char ch='W';
int year = 2022;
int month=11;
//功能将双引号内部的字符串写入到s的数组中,不向终端显示
snprintf(s,sizeof(s),"%04d-%02d-%s-%c",year,month,p,ch);
//将s中的数据显示到终端上
printf("%s\n",s);
return 0;
}
练习:将当前的时间写入到time.txt的文件中,如果ctrl+c退出之后,在再次执行支持断点续写
1.2022-11-04 19:10:20
2.2022-11-04 19:10:21
3.2022-11-04 19:10:22
//按下ctrl+c停止,再次执行程序
4.2022-11-04 20:00:00
5.2022-11-04 20:00:01
#include <stdio.h>
#include <time.h>
#define PRINT_ERR(msg) \
do { \
perror(msg); \
return -1; \
} while (0)
int get_file_line(FILE *fp)
{
char ch=0;
int line=0;
while((ch = fgetc(fp))!=EOF){
if(ch=='\n'){
line++;
}
}
return line;
}
int main(int argc, const char* argv[])
{
FILE *fp;
time_t ts, ots;
struct tm* tm;
int line=0;
char buf[128] = {0};
if((fp = fopen("./time.txt","a+"))==NULL)
PRINT_ERR("fopen error");
line = get_file_line(fp);
ts = ots = 0;
while (1) {
if ((ts = time(NULL)) == -1)
PRINT_ERR("get time error");
if (ts != ots) {
ots = ts;
if ((tm = localtime(&ts)) == NULL)
PRINT_ERR("change time error");
snprintf(buf,sizeof(buf),"%4d.%d-%02d-%02d %02d:%02d:%02d\n",
line++,tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
fputs(buf,fp);
fflush(fp); //强制刷新缓冲区
}
}
return 0;
}