目录
前言
周二
IO简介和标准IO
一、IO简介
1.1 什么是IO
1.2 IO的分类
1.3 什么是系统调用和和库函数
1.4 什么是IO接口
二、标准IO
2.1 fopen/fclose函数
2.1 strerror函数
2.3 perror函数
2.4 fputc/fgetc函数
2.5 fgets/fputs函数
三、缓冲区问题
3.1 缓冲区的大小及类型
3.2 行缓存的刷新机制
3.3 全缓存的刷新机制
四、课后作业
周三
标准IO及文件IO
一、标准IO
1.1 fread/fwrite函数
1.2 sprintf/snprintf/fprintf函数
1.3 获取系统函数
1.4 fseek/ftell/rewind光标操作函数
1.5使用标准IO对bmp格式图片打马赛克
二、文件IO
2.1 open/close函数
周四
文件IO、文件属性操作和目录操作
一、文件IO
1.1read/write函数
二、获取文件属性的函数
2.1 stat函数
2.2 getpwuid/getgrid函数
三、目录操作
3.1 打开目录opendir
3.2 读目录readdir
3.3关闭目录closedir
3.4读取任意文件夹下的所有文件实例
课后作业
周五
库、多进程
一、Linux中的库
1.1 什么是库文件
1.2 库文件的分类
1.3 静态库
1.4动态库
二、多进程
2.1 什么是多进程
2.2 进程和程序的区别
2.3 进程的组成
2.4 什么是PID?
2.5 特殊PID的进程
2.6 进程的种类
2.7 进程相关的命令
2.8 进程的状态
2.9 特殊状态的进程
2.10 进程的创建
课后作业
从这一周开始学习的课程是IO进程,为时八天;本周的周一是数据结构的考试,考试的总结写在了上一周的末尾;从周二到周五这四天的时间里,主要学习了:文件IO、标准IO、库和多进程。
老师说从IO进程开始,课程就从基础阶段拔高到应用层面了,经过这四天的学习下来,主要感觉就是:内容多、代码量大、要熟悉和记忆的内容多、易混淆的知识点多。比较重要的内容还是标准IO和文件IO里的海量的函数接口,因此,本次的反思将针对这几方面,进行整理归纳,难点分析和易混淆点辨析。
同样,写此文章,是想以这种碎碎念的方式回顾重点、重复盲点、加深印象,复习、总结和反思本周的学习,仅供后期自己回顾使用。知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!
IO(input&output)是用户空间和内核空间通过API进行的交互。
IO分为:标准IO和文件IO
标准IO:库函数
文件IO:系统调用
系统调用:系统调用是用户空间进入内核空间的一次过程。
优点:操作系统不同,系统调用的函数接口也不同,因此系统调用的移植性比较差。
缺点:系统调用没有缓冲区,所以效率比较低。
库函数:库函数 = 系统调用 + 缓冲区
优点:库函数要比系统调用的效率高。
缺点:库函数的接口比较统一,移植性比较强。
区别辨析:系统调用对内核的访问是即时的,库函数的缓冲区需要刷新才能访问内核。
IO接口就是函数调用,系统已经封装好,使用时直接调用。
例如:
标准IO:fopen fread fwrite fputc fgetc fputs fgets fclose printf scanf...
文件IO:open read write close...
标准IO:库函数 = 系统调用 + 缓冲区
标准IO:fopen fread fwrite fputc fgetc fputs fgets fclose printf scanf...
FILE结构体:
FILE本身是一个结构体,在调用fopen的时候产生一个结构体指针,这个FILE结构体,就代表打开文件的所有的信息(例如缓冲区的大小,光标的位置等信息),并且以后在读写文件的时候通过FILE指针完成。
在一个正在执行的程序中,默认已经有了三个FILE指针:stdin、stdout、stderr;
它们分别代表的是标准输入,标准输出,标准出错。
typedef struct _IO_FILE FILE;
struct _IO_FILE {
char* _IO_buf_base; //缓冲区的起始地址
char* _IO_buf_end; //缓冲区的结束地址
...
}
#include
FILE *fopen(const char *pathname, const char *mode);
功能:使用标准IO接口打开文件
参数:
@pathname:想要打开文件的路径及名字 "/home/linux/1.c"
@mode :打开文件的方式 "r" "r+" "w" "w+" "a" "a+"
r :以只读的方式打开文件,将光标定位到文件的开头
r+ :以读写的方式打开文件,将光标定位到文件的开头
w :以只写的方式打开文件,如果文件存在就清空文件,如果文件不存在就创建文件
w+ :以读写的方式打开文件,如果文件存在就清空文件,如果文件不存在就创建文件
a :以追加的方式打开文件,如果文件不存在就创建文件,如果文件存在光标定位到结尾进行写
a+ :以读和追加的方式打开文件,如果文件不存在就创建文件,如果进行读从开头读,如果写
在结尾写。
返回值:成功返回FILE指针,失败返回NULL,置位错误码
int fclose(FILE *stream);
功能:关闭文件
参数:
@stream:文件指针
返回值:成功返回0,失败返回EOF(-1),置位错误码
实例:
#include
int main(int argc,const char * argv[]) { FILE *fp; if((fp = fopen("./hello.txt","w")) == NULL){ printf("fopen file error\n"); return -1; } //有一个fopen就要对应一个fclose if(fclose(fp)){ printf("fclose file error\n"); return -1; } return 0; }
#include
char *strerror(int errnum);
功能:将错误码转换为错误信息的字符串
参数:
@errnum:错误码
返回值:错误信息的字符串
实例:
#include
#include #include int main(int argc,const char * argv[]) { FILE *fp; if((fp = fopen("./hello.txt","r")) == NULL){ printf("fopen file error\n"); printf("errno = %d,%s\n",errno,strerror(errno)); return -1; } return 0; }
#include
void perror(const char *s);
功能:将错误信息打印到终端上
参数:
@s:用户的附加信息
返回值:无
实例:
注意perror打印时会在结尾部冒号。
#include
int main(int argc,const char * argv[]) { FILE *fp; if((fp = fopen("./hello.txt","r")) == NULL){ perror("fopen file error"); return -1; } return 0; }
put是往文件中写,get是从文件中读;
#include
int fputc(int c, FILE *stream);
功能:向文件中写入字符
参数:
@c:字符的ascii
@stream:文件指针
返回值:成功返回字符ascii值,失败返回EOF(-1)
int fgetc(FILE *stream);
功能:从文件中读取字符
参数:
@stream:文件指针
返回值:成功读取到的字符的ascii,读取到文件结尾或者出错,返回EOF(-1)
fputc实例
#include
int main(int argc, const char* argv[]) { FILE* fp; //以只写的方式打开文件,如果文件不存在就创建文件,如果文件存在就清空文件 if ((fp = fopen("./hello.txt", "w")) == NULL) { perror("fopen file error"); return -1; } fputc('h', fp); //将h字符写入到文件中,同时光标会向后移动一个字符的位置 fputc('e', fp); //将e字符写入到文件中,同时光标会向后移动一个字符的位置 fputc('l', fp); fputc('l', fp); fputc('o', fp); fclose(fp); return 0; } #include
int main(int argc, const char* argv[]) { FILE* fp; //以只写的方式打开文件,如果文件不存在就创建文件,如果文件存在就清空文件 if ((fp = fopen("./hello.txt", "w")) == NULL) { perror("fopen file error"); return -1; } //将26个英文字符写入到文件中 for(int i=0;i<26;i++){ fputc('A'+i,fp); } fclose(fp); return 0; } fgetc实例
#include
int main(int argc, const char* argv[]) { FILE* fp; int ch; //以只读的方式打开文件 if ((fp = fopen("./hello.txt", "r")) == NULL) { perror("fopen file error"); return -1; } //循环读取文件中的内容,如果没有到EOF,就就一直读取。 //并把读取到的内容显示到终端上(fgetc每读取一次光标会向后移动一个字节) while ((ch = fgetc(fp)) != EOF) { printf("%c ", ch); } printf("\n"); fclose(fp); return 0;
1.使用fgetc统任意文件的行号
./a.out filename //命令行传参
#include
int main(int argc, const char* argv[]) { FILE* fp; int ch, 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.循环读取文件中的字符,判断是否等于'\n' //让line++ while ((ch = fgetc(fp)) != EOF) { if (ch == '\n') { line++; } } // 4.将行号打印到终端上 printf("line = %d\n",line); // 5.关闭文件 fclose(fp); return 0; } 2.使用fgetc/fputc实现文件拷贝
./a.out srcfile destfile
#include
int main(int argc, const char* argv[]) { FILE *fp1, *fp2; int ch; // 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 src error"); return -1; } if ((fp2 = fopen(argv[2], "w")) == NULL) { perror("fopen dest error"); return -1; } // 3.循环拷贝 while ((ch = fgetc(fp1)) != EOF) { fputc(ch,fp2); } // 4.关闭文件 fclose(fp1); fclose(fp2); return 0; }
char *fgets(char *s, int size, FILE *stream);
功能:从stream对应的文件中最多读取size-1个字符到s中
读停止:当遇到EOF或者换行符时候会停止,如果是换行符停止的,它会将换换行符存储到s中
s的结束:在s存储的最后一个字符之后通过添加'\0'的形式表示结束
s=0123456789'\n''\0' 读结束的原因是读到'\n','\n'也会读出,并在末尾补'\0'
s=01234'\0' 读结束的原因是读到结尾,返回EOF(-1),读结束,且在末尾补'\0'
参数:
@s:用于存储读取到的字符串的首地址
@size:读取字符串中字符的个数
@stream:文件指针
返回值:成功返回s,失败返回NULL
int fputs(const char *s, FILE *stream);
功能:将s中的内容写入到stream对应的文件中(不包含'\0')
参数:
@s:被写字符串的首地址
@stream:文件指针
返回值:成功返回大于0的值,失败返回EOF
fgets函数的实例(fgets读取文件中的内容)
#include
#define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { FILE* fp; char buf[20] = {0}; if (argc != 2) { printf("input error,try again\n"); printf("usage: ./a.out filename\n"); return -1; } if ((fp = fopen(argv[1], "r")) == NULL) PRINT_ERR("fopen error"); //读取文件第一行的内容(不保证全部读取到) fgets(buf,sizeof(buf),fp); printf("buf = %s\n",buf); fclose(fp); return 0; } fgets读取标准输入的内容
fgets一般用来上程序输入字符串,
因为scanf("%s")不能读取空格,gets在读的时候有越界的风险;
所以fgets是最常用来读取字符串的
#include
#include #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { char buf[20] = {0}; //fgets一般用来上程序输入字符串,因为scanf("%s")不能读取空格 //gets在读的时候有越界的风险,所以fgets是最常用来读取字符串的 // 从标准输入中读取字符到buf中,最多读取sizeof(buf)-1个字符 fgets(buf,sizeof(buf),stdin); //hello'\n''\0' //将字符串中的'\n'设置为'\0' buf[strlen(buf)-1]='\0'; //将读取到的内容显示到终端上 printf("buf = %s\n",buf); return 0; } 使用fgets统计文件的行号
#include
#include #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { FILE* fp; char buf[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(buf, sizeof(buf), fp) != NULL) { //如果buf=sizeof(buf)-1说明读满了 if (strlen(buf) == (sizeof(buf) - 1)) { //读满之后判断倒数第二个字符,如果不是换行就执行下次循环 if (buf[sizeof(buf) - 2] != '\n') continue; } //如果没有读满line++,如果读满了倒数第二个字符是'\n'也让line++ line++; } // 4.将行号打印到终端上 printf("line = %d\n", line); // 5.关闭文件 fclose(fp); return 0; } fputs向文件中写入字符串的实例
#include
#include #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { FILE* fp; char buf[] = "i am test fputs API....."; if ((fp = fopen("hello.txt", "w")) == NULL) { perror("fopen error"); return -1; } fputs(buf,fp); fclose(fp); return 0; } fputs向标准输出或者标准错误中写
#include
#include int main(int argc, const char* argv[]) { char buf[] = "i am test fputs API.....stdout"; char buf1[] = "i am test fputs API.....stderr"; //最终看到的现象都是在终端上显示一句话 //区别stdout有缓冲区,stderr没有缓冲区 fputs(buf,stdout); fputs(buf1,stderr); while(1); return 0; } 练习:使用fgets/fputs实现文件的拷贝
#include
int main(int argc, const char* argv[]) { FILE *fp1, *fp2; char buf[20] = {0}; // 1.校验命令行参数的个数 if (argc != 3) { fputs("input error,try again\n",stderr); fputs("usage: ./a.out srcfile destfile\n",stderr); return -1; } // 2.只读方式打开源文件,以只写方式打开目标文件 if ((fp1 = fopen(argv[1], "r")) == NULL) { perror("fopen src error"); return -1; } if ((fp2 = fopen(argv[2], "w")) == NULL) { perror("fopen dest error"); return -1; } // 3.循环拷贝 fgets成功返回buf(非0就会进入while), //失败返回NULL(void *)0,假退出循环 while (fgets(buf,sizeof(buf),fp1)) { fputs(buf,fp2); } // 4.关闭文件 fclose(fp1); fclose(fp2); return 0; }
全缓存:和文件相关的缓冲区(fp)4096(4K)
行缓存:和终端相关的缓冲区 (stdin stdout) 1024(1k)
无缓存:和标准出错 (stderr) 0
#include
int main(int argc, const char* argv[])
{
int num;
scanf("%d", &num);
printf("stdin size = %ld\n",
stdin->_IO_buf_end - stdin->_IO_buf_base);
printf("stdout size = %ld\n",
stdout->_IO_buf_end - stdout->_IO_buf_base);
printf("stderr size = %ld\n",
stderr->_IO_buf_end - stderr->_IO_buf_base);
FILE* fp;
if ((fp = fopen("./hello.txt", "r+")) == NULL) {
perror("fopen error");
return -1;
}
fputs("hello",fp);
printf("fp size = %ld\n",
fp->_IO_buf_end - fp->_IO_buf_base);
return 0;
}
1.行缓存遇到换行符的时候会刷新缓冲区
2.当程序结束的时候会刷新行缓存
3.当输入输出发生切换的时候
4.当关闭文件的时候会刷新缓冲区
5.缓冲区满也会刷新缓冲区
6.主动刷新缓冲区fflush
#include
int main(int argc, const char* argv[])
{
// 1.行缓存遇到换行符的时候会刷新缓冲区
// printf("hello\n");
// while(1);
// 2.当程序结束的时候会刷新行缓存
// printf("hello");
// 3.当输入输出发生切换的时候
// printf("hello");
// fgetc(stdin);
// while(1);
// 4.当关闭文件的时候会刷新缓冲区
// printf("hello");
// fclose(stdout);
// while (1);
// 5.缓冲区满也会刷新缓冲区
// for(int i=0;i<1025;i++){
// fputc('a',stdout);
// }
// while(1);
// 6.主动刷新缓冲区 int fflush(FILE *stream);
printf("hello");
fflush(stdout);
while (1);
return 0;
}
1.当程序结束的时候会刷新行缓存
2.当输入输出发生切换的时候
3.当关闭文件的时候会刷新缓冲区
4.缓冲区满也会刷新缓冲区
5.主动刷新缓冲区fflush
(与行缓存相比,差了一个换行符刷新)
从终端向程序输入任意的字符串,请把输入的字符串写入(追加)到hello.txt的文件中。
从文件中将最后一行的内容读取出来,并显示到终端上。
#include
int main(int argc,const char * argv[]) { FILE *fp; char buf[1024] = {0}; //1.1.追加打开文件 if((fp = fopen("./hello.txt","a"))==NULL){ perror("fopen error"); return -1; } //1.2.从终端读取字符串 fgets printf("input > "); fgets(buf,sizeof(buf),stdin); //1.3.把字符串写入到文件中 fputs(buf,fp); //1.4.关闭文件 fclose(fp); //2.1.打开文件 if((fp = fopen("./hello.txt","r"))==NULL){ perror("fopen error"); return -1; } //2.2.循环读取直到遇到EOF停止 while(fgets(buf,sizeof(buf),fp)); //2.3.将buf中的数据打印出来 printf("buf = %s",buf); //2.4.关闭文件 fclose(fp); return 0; }
read是往文件(字符数组)写,write是从文件中读,注意与字面意思区分。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件中读内容存储到ptr中,读取的项目的个数是nmemb,每一项的大小是size
参数:
@ptr:用来存储读取到的数据的文件指针
@size:每一项的大小
@nmemb:项目的个数
@stream:文件指针
返回值:成功返回读取到的项目的个数,失败或者错误返回小于项目的个数
如果想要直到是发生的错误或者到了文件的结尾,必须通过ferror或feof完成
if(ferror(fp)){ //如果是真就是错误
printf("错误\n");
}
if(feof(fp)){ //如果为真就是到了文件的结尾
printf("到了文件的结尾");
}
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:将ptr中的数据写入到文件中,每一项的大小是size,项目的个数是nmemb
参数:
@ptr:想要写的数据的字符串的首地址
@size:每一项的大小
@nmemb:项目的个数
@stream:文件指针
返回值:成功返回项目的个数,失败返回小于项目的个数或者是0
fread函数实例
#include
typedef struct { char name[20]; char sex; int age; } stu_t; int main(int argc,const char * argv[]) { FILE *fp; int data; if((fp = fopen("./hello.txt","r"))==NULL) PRINT_ERR("fopen error"); // fread(&data,sizeof(int),1,fp); // printf("data = %d\n",data); stu_t stu; fread(&stu,sizeof(stu_t),1,fp); printf("name=%s,sex=%c,age=%d\n",stu.name,stu.sex,stu.age); fclose(fp); return 0; } fwrite函数实例
#include
typedef struct { char name[20]; char sex; int age; } stu_t; int main(int argc, const char* argv[]) { FILE* fp; char buf[30] = "i am test fwrite func..."; int num = 12345; if ((fp = fopen("./hello.txt", "w")) == NULL) PRINT_ERR("fopen error"); // 1..使用fwrite写字符串 // fwrite(buf,1,strlen(buf),fp); // 2.使用fwrite写入整数 // fwrite(&num,sizeof(num),1,fp); // 3.使用fwrite写入结构体 stu_t stu = { .name = "zhangsan", .sex = 'w', .age = 20, }; fwrite(&stu,sizeof(stu_t),1,fp); fclose(fp); return 0; } 使用fread/fwrite实现文件的拷贝
#include
int main(int argc, const char* argv[]) { FILE *fp1, *fp2; int ret; char buf[20] = { 0 }; // 1.校验命令行参数的个数 if (argc != 3) { fputs("input error,try again\n", stderr); fputs("usage: ./a.out srcfile destfile\n", stderr); return -1; } // 2.只读方式打开源文件,以只写方式打开目标文件 if ((fp1 = fopen(argv[1], "r")) == NULL) PRINT_ERR("fopen src error"); if ((fp2 = fopen(argv[2], "w")) == NULL) PRINT_ERR("fopen dest error"); // 3.循环拷贝 while (!(feof(fp1) || ferror(fp1))) { ret = fread(buf,1,sizeof(buf),fp1); fwrite(buf,1,ret,fp2); } #if 0 while (!(feof(fp1) || ferror(fp1))) { fread(buf,1,sizeof(buf)-1,fp1); fwrite(buf,1,strlen(buf),fp2); memset(buf,0,sizeof(buf)); } #endif // 4.关闭文件 fclose(fp1); fclose(fp2); return 0; }
1.2.1 sprintf函数
int sprintf(char *str, const char *format, ...);
功能:按照控制格式将字符串写入到str中
参数:
@str :存储格式化后的字符串
@format:控制格式
@... :可变参数
返回值:成功返回格式化的字符的个数,失败返回负数
sprintf使用实例
#include
int main(int argc,const char * argv[]) { char str[] = "hello"; char buf[128] = {0}; int num=123456; //相当于将双引号内部的内容格式化到buf的字符数组中 sprintf(buf,"abcd%d%s",num,str); //将字符数组打印出来 printf("buf = %s\n",buf); char ww[5] = {0}; //sprintf不会对越界进行检查,所以在使用的时候要保证格 //式化字符的个数小于等于ww能存储成员的个数 sprintf(ww,"abcdeqqqqqqqqqqqqq"); printf("ww = %s\n",ww); return 0; } #include
typedef struct { char name[20]; char sex; int age; } stu_t; int main(int argc, const char* argv[]) { FILE* fp; char buf[30] = "i am test fwrite func..."; int num = 1234; if ((fp = fopen("./hello.txt", "w")) == NULL) PRINT_ERR("fopen error"); // 1..使用fwrite写字符串 // fwrite(buf,1,strlen(buf),fp); // 2.使用fwrite写入整数 // sprintf(buf,"%d",num); // fwrite(buf,1,strlen(buf),fp); // 3.使用fwrite写入结构体 stu_t stu = { .name = "zhangsan", .sex = 'w', .age = 20, }; sprintf(buf,"%s-%c-%d",stu.name,stu.sex,stu.age); fwrite(buf,1,strlen(buf),fp); fclose(fp); return 0; }
1.2.2 snprintf函数
int snprintf(char *str, size_t size, const char *format, ...);
功能:格式化字符串到str中,最多是size-1个字符
参数:
@str:存储格式化后字符串的首地址
@size:格式化字符的个数,包含'\0'
@format:控制格式
@... :可变参数
返回值:成功返回格式化的字符的个数,失败返回负数
snprintf使用实例
#include
int main(int argc,const char * argv[]) { char str[] = "hello"; char buf[128] = {0}; int num=123456; //相当于将双引号内部的内容格式化到buf的字符数组中 snprintf(buf,sizeof(buf),"abcd%d%s",num,str); //将字符数组打印出来 printf("buf = %s\n",buf); char ww[5] = {0}; //snprinf只会格式化size-1个字符,多出来的直接被丢弃 snprintf(ww,sizeof(ww),"abcdeqqqqqqqqqqqqq"); printf("ww = %s\n",ww); return 0; }
1.2.3 fprintf函数的功能
int fprintf(FILE *stream, const char *format, ...);
功能:将格式化好的字符串写入到对应的文件中
参数:
@stream:文件指针
@format,...:控制格式
返回值:成功返回格式化的字符的个数,失败返回负数
fprintf函数的实例
#include
int main(int argc,const char * argv[]) { //将字符串写入到标准输出中 fprintf(stdout,"hello world\n"); return 0; }
#include
time_t time(time_t *tloc);
功能:返回自1970-01-01 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
int main(int argc,const char * argv[])
{
time_t st;
struct tm *tm;
//1.获取秒钟数
if((st = time(NULL))==-1)
PRINT_ERR("get time error");
//2.转换时间
if((tm = localtime(&st))==NULL)
PRINT_ERR("change time error");
//3.打印系统时间
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;
}
将系统的时间写到文件中
1)snprintf
#include
int main(int argc, const char* argv[]) { time_t st, oldst; struct tm* tm; FILE* fp; char tm_buf[50] = { 0 }; // 0.以追加的方式打开文件 if ((fp = fopen("time.txt", "a")) == NULL) PRINT_ERR("fopen error"); oldst = st = 0; while (1) { // 1.获取秒钟数 if ((st = time(NULL)) == -1) PRINT_ERR("get time error"); if (st != oldst) { oldst = st; // 2.转换时间 if ((tm = localtime(&st)) == NULL) PRINT_ERR("change time error"); // 3.格式化时间 snprintf(tm_buf, sizeof(tm_buf), "%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); // 4.将字符串写入到文件 fputs(tm_buf, fp); fflush(fp); } } // 5.关闭文件 fclose(fp); return 0; } 2)fprintf
#include
int main(int argc, const char* argv[]) { time_t st, oldst; struct tm* tm; FILE* fp; char tm_buf[50] = { 0 }; // 0.以追加的方式打开文件 if ((fp = fopen("time.txt", "a")) == NULL) PRINT_ERR("fopen error"); oldst = st = 0; while (1) { // 1.获取秒钟数 if ((st = time(NULL)) == -1) PRINT_ERR("get time error"); if (st != oldst) { oldst = st; // 2.转换时间 if ((tm = localtime(&st)) == NULL) PRINT_ERR("change time error"); // 3.格式化时间并写入文件 fprintf(fp, "%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); fflush(fp); } } // 5.关闭文件 fclose(fp); return 0; }
int fseek(FILE *stream, long offset, int whence);
功能:设置光标的位置
参数:
@stream:文件指针
@offset:偏移
>0 :向后偏移多少字节
=0 :不偏移
<0 :向前偏移
@whence:从哪里开始偏移
SEEK_SET :从开头开始偏移
SEEK_CUR :从当前位置开始偏移
SEEK_END :从文件结尾开始偏移
返回值:成功返回0,失败返回-1置位错误码
long ftell(FILE *stream);
功能:获取光标位置到开头的字节数
参数:
@stream:文件指针
返回值:成功返回光标到开头的字节数,失败返回-1置位错误码
void rewind(FILE *stream);
功能:设置光标到文件的开头
参数:
@stream:文件指针
返回值:无
rewind(fp) = fseek(fp,0,SEEK_SET);
主要用到的知识:bmp格式图片的基本知识、fseek的使用、fread/fwrite的使用。
1.5.1 bmp格式图片的基本知识
BMP文件格式,又称为Bitmap(位图)
BMP文件的数据按照从文件头开始的先后顺序分为三个部分:
Ø **bmp文件头(bmp file header):**提供文件的格式、大小等信息 (14字节)
Ø **位图信息头(bitmap information):**提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息(40字节)
Ø **位图数据(bitmap data):**图像数据 (RGB565 (2byte) RGB888(3字节))
1.5.2 获取位图的信息
#include
typedef struct {
unsigned char B;
unsigned char G;
unsigned char R;
} RGB_t;
int main(int argc, const char* argv[])
{
FILE* fp;
unsigned int size, width, high;
unsigned short pix;
// 1.校验命令行参数的个数
if (argc != 2) {
fprintf(stderr, "input error,try again\n");
fprintf(stderr, "usage: ./a.out xxx.bmp\n");
return -1;
}
// 2.使用读写的方式打开文件
if ((fp = fopen(argv[1], "r+")) == NULL)
PRINT_ERR("fopen error");
// 3.读信息
fseek(fp, 2, SEEK_SET);
fread(&size, sizeof(unsigned int), 1, fp);
printf("size = %d\n", size);
fseek(fp, 18, SEEK_SET);
fread(&width, sizeof(unsigned int), 1, fp);
printf("width = %d\n", width);
fread(&high, sizeof(unsigned int), 1, fp);
printf("high = %d\n", high);
fseek(fp, 2, SEEK_CUR);
fread(&pix, sizeof(unsigned short), 1, fp);
printf("pix = %d\n", pix);
// 4.关闭文件
fclose(fp);
return 0;
}
1.5.3 对图片打马赛克
#include
#define N 25 //马赛克正方块的边长
typedef struct
{
unsigned char B;
unsigned char G;
unsigned char R;
} RGB_t; //定义的颜色结构体
int main(int argc, const char *argv[])
{
FILE *fp;
unsigned int size, width, high;
unsigned short pix;
// 1.校验命令行参数的个数
if (argc != 2)
{
fprintf(stderr, "input error,try again\n");
fprintf(stderr, "usage: ./a.out xxx.bmp\n");
return -1;
}
// 2.使用读写的方式打开文件
if ((fp = fopen(argv[1], "r+")) == NULL)
PRINT_ERR("fopen error");
// 3.读对应位图的信息
fseek(fp, 2, SEEK_SET);
fread(&size, sizeof(unsigned int), 1, fp);
printf("size = %d\n", size);
fseek(fp, 18, SEEK_SET);
fread(&width, sizeof(unsigned int), 1, fp);
printf("width = %d\n", width);
fread(&high, sizeof(unsigned int), 1, fp);
printf("high = %d\n", high);
fseek(fp, 2, SEEK_CUR);
fread(&pix, sizeof(unsigned short), 1, fp);
printf("pix = %d\n", pix);
RGB_t rgb; //定义颜色结构体变量
//fseek(fp, 54, SEEK_SET); //此行可不加,因为此时的光标已经定位到位图的开头
// 4.打印马赛克方块
//马赛克方块的行数
for (int k = 0; k < high/N; k++)
{
//马赛克方块的列数
for (int g = 0; g < width/N; g++)
{
//使光标位于左下角往右偏移一个像素点的位置
//偏移的目的是防止马赛克区域叠加、那样会使得所有的马赛克都是同一颜色
fseek(fp,3,SEEK_CUR);
//以方块左下角往右偏移一个像素点位置的颜色作为马赛克方块的颜色
fread(&rgb, sizeof(rgb), 1, fp);
//光标回到马赛克方块的最开头
fseek(fp,-3,SEEK_CUR);
//循环打印N*N的马赛克方块
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
fwrite(&rgb, sizeof(rgb), 1, fp);
}
fseek(fp, (1200 - N) * 3, SEEK_CUR);
}
fseek(fp, 54+(k*N*1200*3)+(g+1)*N*3, SEEK_SET);
}
fseek(fp, 54+((k+1)*N*1200*3), SEEK_SET);
}
// 5.关闭文件
fclose(fp);
return 0;
}
效果:
文件IO:系统调用
文件IO:open read write close...
fd文件描述:在使用open函数打开文件的时候返回的就是文件描述符,它是一个整数,这个整数就代表打开的这个文件,一个对这个文件的读写操作都通过fd完成。默认在一个正在执行的程序中0,1,2已经被使用了,分别代表的功能标准输入、标准输出、标准出错。在前面使用标准IO的时候也有这个
数值 | 功能 |
---|---|
0 | 标准输入 |
1 | 标准输出 |
2 | 标准出错 |
三个内容分别对应的是标准IO中的stdin,stdout,stderr。它们三个是FILE *的指针,在这三个指针中包含0,1,2的文件描述符
#include
int main(int argc, const char* argv[])
{
printf("标准输入 = %d\n", stdin->_fileno); //这个fileno就是文件描述符 0
printf("标准输出 = %d\n", stdout->_fileno); //这个fileno就是文件描述符 1
printf("标准错误 = %d\n", stderr->_fileno); //这个fileno就是文件描述符 2
return 0;
}
在linux系统中默认文件描述符的范围[0-1023],可以通过 ulimit -a查看限制,但是这个限制不是一成不变的,可以通过ulimit -n 2048修改这个限制。每一个正在执行的程序都有自己的文件描述符,程序和程序之间的文件描述符相互不干扰。
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:使用文件IO打开文件
参数:
@pathname:文件的路径及名字 "/home/linux/hello.txt"
@flags :打开的方式
O_RDONLY:只读
O_WRONLY:只写
O_RDWR :读写
O_APPEND:追加
O_CREAT :创建,如果在第二个参数中使用了O_CREAT就必须填充第三个参数
O_TRUNC :清空文件
O_EXCL :根O_CREAT结合使用,如果文件已存在返回一个EEXIST
@mode:创建文件的权限
0666 你向创建文件的权限
mode & (~umask) 实际创建的文件的权限 (文件的最大权限是0666,umask是在这个基础上取反的)
mode & (~0002) = mode & 0664
返回值:成功返回文件描述符fd,失败返回-1置位错误码
标准IO | 文件IO | 功能 |
---|---|---|
"r" | O_RDONLY | 以只读的方式打开文件,将光标定位在文件的开头 |
"r+" | O_RDWR | 以读写的方式打开文件,将光标定位到文件的开头 |
"w" | O_WRONLY|O_TRUNC|O_CREAT,0666 | 以只写的方式打开文件,如果文件存在就清空,如果文件不存在就创建,光标在开头 |
"w+" | O_RDWR|O_TRUNC|O_CREAT,0666 | 以读写的方式打开文件,如果文件存在就清空,如果文件不存在就创建,将光标定位到开头 |
"a" | O_WRONLY|O_APPEND|O_CREAT,0666 | 以追加的方式打开文件,如果文件不存在就创建,如果文件存在就结尾写 |
"a+" | O_RDWR|O_APPEND|O_CREAT,0666 | 以读和追加的方式打开文件,如果文件不存在就创建,如果文件存在读在开头,写在结尾 |
#include
int close(int fd);
功能:关闭文件
参数:
@fd:文件描述符
返回值:成功返回0,失败返回-1置位错误码
open函数的实例1
#include
int main(int argc,const char * argv[]) { int fd; if((fd = open("./hello.txt",O_WRONLY|O_TRUNC|O_CREAT,0666))==-1) PRINT_ERR("open error"); printf("fd = %d\n",fd); return 0; } open函数的实例2
要求:如果文件不存在就创建文件,并以只写的方式打开文件,如果文件存在就以只读的方式打开文件
#include
int main(int argc, const char* argv[]) { int fd; fd = open("./hello.txt", O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd == -1) { if (errno == EEXIST) { printf("文件已存在,使用只读方式打开\n"); fd = open("./hello.txt",O_RDONLY); }else{ PRINT_ERR("open error"); } } printf("fd = %d\n",fd); return 0; } close函数实例
#include
int main(int argc, const char* argv[]) { int fd; fd = open("./hello.txt", O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd == -1) { if (errno == EEXIST) { printf("文件已存在,使用只读方式打开\n"); fd = open("./hello.txt", O_RDONLY); } else { PRINT_ERR("open error"); } } printf("fd = %d\n", fd); close(fd); //关闭标准输出 close(1); printf("1111111111111\n"); return 0; }
#include
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到buf中,读的大小是字节
参数:
@fd:文件描述符
@buf:存储读取到数据的首地址
@count:字节(想读的字节个数)
返回值:成功返回读取到的字节的个数,如果是0表示读取到了文件的结尾
如果在读的时候发生了错误,返回-1并置位错误码
ssize_t write(int fd, const void *buf, size_t count);
功能:将buf中的数据写入到fd对应的文件中,count就是写的字节的个数
参数:
@fd:文件描述符
@buf:被写数据的首地址
@count:字节(想写的字节个数)
返回值:成功返回写入字节的个数(如果返回值小于想写的字节的个数不是错误,可能是磁盘被写满了)
失败返回-1置位错误码
write函数的实例
#include
typedef struct { char name[20]; char sex; int age; } stu_t; int main(int argc,const char * argv[]) { int fd; int num=12345; char buf[50] ="i am test write func....\n"; stu_t stu = { .name = "zhangsan", .age = 18, .sex = 'm', }; if((fd = open("./hello.txt",O_WRONLY|O_CREAT|O_TRUNC,0666))==-1) PRINT_ERR("open error"); //1.写入字符串 // write(fd,buf,strlen(buf)); //2.写入整数 // write(fd,&num,sizeof(num)); //3.写入结构体 write(fd,&stu,sizeof(stu)); close(fd); return 0; } read函数的实例
#include
typedef struct { char name[20]; char sex; int age; } stu_t; int main(int argc, const char* argv[]) { int fd; char buf[128] = { 0 }; int num; stu_t stu; if ((fd = open("./hello.txt", O_RDONLY)) == -1) PRINT_ERR("open error"); // 1.读取字符串 // read(fd,buf,sizeof(buf)); // printf("buf = %s\n",buf); // 2.读取整数 // read(fd, &num, sizeof(num)); // printf("num = %d\n", num); // 3.从文件中读取结构体 read(fd, &stu, sizeof(stu)); printf("name = %s,sex = %c,age = %d\n", stu.name, stu.sex, stu.age); close(fd); return 0; } 使用read/write实现文件的拷贝
#include
int main(int argc, const char* argv[]) { int fd1, fd2; char buf[20] = { 0 }; int ret; // 1.校验命令行的参数个数 if (argc != 3) { fprintf(stderr, "input error,try agian\n"); fprintf(stderr, "usage: ./a.out srcfile dtstfile\n"); return -1; } // 2.只读的方式打开源文件 if ((fd1 = open(argv[1], O_RDONLY)) == -1) PRINT_ERR("open src error"); // 3.用只写,创建,清空的方式打开目标文件 if ((fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1) PRINT_ERR("open dest error"); // 4.循环拷贝 while ((ret = read(fd1, buf, sizeof(buf))) > 0) { write(fd2, buf, ret); } // 5.关闭文件 close(fd1); close(fd2); return 0; }
1.2 lseek函数
改变光标位置的函数,与fseek类似。
需要注意的是,lseek的返回值是开头到光标位置的字节数,即:lseek = fseek + ftell
#include
#include
off_t lseek(int fd, off_t offset, int whence);
功能:修改光标的位置
参数:
@fd:文件描述符
@offset:偏移
>0:向后偏移
=0:不偏移
<0:向前偏移
@whence:从哪开始偏移
SEEK_SET:从头开始偏移
SEEK_CUR:从当前位置开始偏移
SEEK_END:从结尾开始偏移
返回值:成功返回光标到开头的字节数,失败返回-1置位错误码
#include
#include
#include
int stat(const char *pathname, struct stat *statbuf);
功能:获取文件的属性信息(获取不到软连接文件,如果向获取软连接文件类型使用lstat)
参数:
@pathname:文件的路径及名字
@statbuf:获取到的文件的属性信息存储到stat结构体中
struct stat {
dev_t st_dev; //磁盘的设备号
//内核识别驱动的唯一编号叫做设备号
//设备号(32bit)=主设备号(高12)+次设备号(低20位)
//主设备号:代表它是那一类设备
//次设备号:同类中的那个设备树
ino_t st_ino; //inode号 ls -i 文件系统识别文件的编号
mode_t st_mode; //文件的类型及权限
在st_mode中13-16这四个bit为就是文件的类型
S_IFMT 0170000 bit mask for the file type bit field
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
if((st_mode & S_IFMT)==S_IFREG){
printf("这是一个普通文件\n");
}
在st_mode中0-8这9个bit为就是文件的权限
获取文件的权限 = st_mode & 0777
nlink_t st_nlink; //文件的硬链接数,文件别名的个数
uid_t st_uid; //uid (id 用户名查看)
gid_t st_gid; //gid (id 用户名查看)
dev_t st_rdev; //设备号(字符、块)
off_t st_size; //文件大小,单位是字节
blksize_t st_blksize; //block的大小 512
blkcnt_t st_blocks; //文件的大小,单位是block
struct timespec st_atim; //最后一次访问文件的时间
struct timespec st_mtim; //文件最后一次被修改的时间
struct timespec st_ctim; //最后一次文件状态改变的时候
};
返回值:成功返回0,失败返回-1置位错误码
stat函数使用实例
#include
int main(int argc, const char* argv[]) { struct stat st; struct tm *tm; if (argc != 2) { fprintf(stderr, "input error,try agian\n"); fprintf(stderr, "usage: ./a.out filename\n"); return -1; } if (stat(argv[1], &st)) PRINT_ERR("get file stat error"); switch (st.st_mode & 0170000) { case 0100000: printf("这是一个普通文件\n"); break; case 0040000: printf("这是一个目录\n"); break; case 0020000: printf("这是一个字符设备文件\n"); break; } printf("inode = %ld,mode=%#o,uid=%d,gid=%d,size=%ld\n",st.st_ino,st.st_mode&0777, st.st_uid,st.st_gid,st.st_size); tm=localtime(&st.st_atim.tv_sec); 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; }
#include
struct passwd *getpwuid(uid_t uid);
功能:根据uid获取passwd结构体
参数
@uid:用户的iD
返回值:成功返回结构体指针,失败返回NULL置位错误码
struct passwd {
char *pw_name; //用户名
char *pw_passwd; //用户密码
uid_t pw_uid; //UID
gid_t pw_gid; //GID
char *pw_gecos; //用户的信息
char *pw_dir; //用户家目录
char *pw_shell; //命令行解析器 /bin/bash
};
#include
struct group *getgrgid(gid_t gid);
根据gid获取group结构体
参数:
@gid:组id
返回值:成功返回结构体指针,失败返回NULL置位错误码
struct group {
char *gr_name; //组名
char *gr_passwd; //组的密码
gid_t gr_gid; //组id
};
综合使用实例
#include
int main(int argc, const char* argv[]) { struct stat st; struct tm* tm; struct passwd* pwd; struct group* grp; if (argc != 2) { fprintf(stderr, "input error,try agian\n"); fprintf(stderr, "usage: ./a.out filename\n"); return -1; } if (lstat(argv[1], &st)) PRINT_ERR("get file stat error"); switch (st.st_mode & S_IFMT) { case S_IFREG: printf("这是一个普通文件\n"); break; case S_IFDIR: printf("这是一个目录\n"); break; case S_IFCHR: printf("这是一个字符设备文件\n"); break; case S_IFLNK: printf("这是软连接文件\n"); break; case S_IFSOCK: printf("这是套接字文件\n"); break; case S_IFBLK: printf("这是一个块设备文件\n"); break; case S_IFIFO: printf("这是一个管道文件\n"); break; } // printf("inode = %ld,mode=%#o,username=%s,groupname=%s,size=%ld\n", // st.st_ino,st.st_mode&0777, // getpwuid(st.st_uid)->pw_name, // getgrgid(st.st_gid)->gr_name, // st.st_size); if ((pwd = getpwuid(st.st_uid)) == NULL) PRINT_ERR("get passwd error"); if ((grp = getgrgid(st.st_gid)) == NULL) PRINT_ERR("get group error"); printf("inode = %ld,mode=%#o,username=%s,groupname=%s,size=%ld\n", st.st_ino, st.st_mode & 0777, pwd->pw_name, grp->gr_name, st.st_size); tm = localtime(&st.st_atim.tv_sec); 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; }
#include
DIR *opendir(const char *name);
功能:打开目录
参数:
@name:目录名
返回值:成功返回DIR结构体指针,失败返回NULL置位错误码
struct dirent *readdir(DIR *dirp);
功能:读取目录下的文件
参数:
@dirp:DIR结构体指针
返回值:成功返回dirent结构体指针,在读取到文件夹的结尾或者出错的时候都会返回NULL
只有出错的时候才会置位错误码
struct dirent {
ino_t d_ino; //文件的inode号
unsigned short d_reclen; //当前结构体所占的字节的大小
unsigned char d_type; //文件的类型
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.
char d_name[256]; //文件的名字
};
int closedir(DIR *dirp);
功能:关闭文件夹
参数:
@dirp:DIR的结构体指针
返回值:成功返回0,失败返回-1置位错误码
#include
int main(int argc, const char* argv[])
{
DIR* dir;
struct dirent* dt;
// 1.检查命令行参数的个数
if (argc != 2) {
fprintf(stderr, "input error,try agian\n");
fprintf(stderr, "usage: ./a.out path\n");
return -1;
}
// 2.打开文件夹
if ((dir = opendir(argv[1])) == NULL)
PRINT_ERR("opendir error");
// 3.循环读取目录下的文件
while ((dt = readdir(dir)) != NULL) {
printf("name=%s,inode=%ld,struct_size=%d ",
dt->d_name,
dt->d_ino,
dt->d_reclen);
switch (dt->d_type) {
case DT_BLK:
printf("这是块设备文件\n");
break;
case DT_CHR:
printf("这是字符设备文件\n");
break;
case DT_DIR:
printf("这是目录\n");
break;
case DT_FIFO:
printf("这是管道文件\n");
break;
case DT_LNK:
printf("这是软连接文件\n");
break;
case DT_REG:
printf("这是普通文件\n");
break;
case DT_SOCK:
printf("这是套接字文件\n");
break;
default:
printf("未知的文件类型\n");
}
}
closedir(dir);
return 0;
}
练习
请输入一个文件名,判断这个目录/home/linux/work/day3,是否存在这个文件如果这个文件不存在,就打印不存在。如果这个文件存在,将这个文件的详细信息输出(文件的类型,文件的权限,文件的用户名,文件的组名,文件的大小,文件的最后一次访问的时间)
#include
#define SERVERPATH "/home/linux/work/day3" int show_file_info(struct stat st) { struct passwd* pwd; struct group* grp; struct tm* tm; switch (st.st_mode & S_IFMT) { case S_IFREG: printf("这是一个普通文件\n"); break; case S_IFDIR: printf("这是一个目录\n"); break; case S_IFCHR: printf("这是一个字符设备文件\n"); break; case S_IFLNK: printf("这是软连接文件\n"); break; case S_IFSOCK: printf("这是套接字文件\n"); break; case S_IFBLK: printf("这是一个块设备文件\n"); break; case S_IFIFO: printf("这是一个管道文件\n"); break; } if ((pwd = getpwuid(st.st_uid)) == NULL) PRINT_ERR("get passwd error"); if ((grp = getgrgid(st.st_gid)) == NULL) PRINT_ERR("get group error"); printf("inode = %ld,mode=%#o,username=%s,groupname=%s,size=%ld\n", st.st_ino, st.st_mode & 0777, pwd->pw_name, grp->gr_name, st.st_size); tm = localtime(&st.st_atim.tv_sec); 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; } int main(int argc, const char* argv[]) { DIR* dir; struct dirent* dt; struct stat st; char name[100] = { 0 }; // 1.检查命令行参数的个数 if (argc != 2) { fprintf(stderr, "input error,try agian\n"); fprintf(stderr, "usage: ./a.out filename\n"); return -1; } // 2.打开文件夹 if ((dir = opendir(SERVERPATH)) == NULL) PRINT_ERR("opendir error"); // 3.循环读取目录下的文件 while ((dt = readdir(dir)) != NULL) { if (strcmp(argv[1], dt->d_name) == 0) { //在文件名前给他拼接一个路径 snprintf(name, sizeof(name), "%s/%s", SERVERPATH, argv[1]); if (lstat(name, &st)) PRINT_ERR("get file stat error"); show_file_info(st); closedir(dir); return 0; } } printf("查询的文件不存在\n"); closedir(dir); return -1; }
将当前的时间写入到tim.txt的文件中,如果ctrl+c退出之后,在再次执行支持断点续写
1.2022-04-26 19:10:20
2.2022-04-26 19:10:21
3.2022-04-26 19:10:22
//按下ctrl+c停止,再次执行程序
4.2022-04-26 20:00:00
5.2022-04-26 20:00:01
注:要求不能使用sleep函数
#include
int main(int argc, const char* argv[])
{
time_t st, oldst; //st存现在的时间,oldst存上一个时间,两个时间不一样时打印时间
struct tm* tm; //调用和转换系统时间
FILE *fp,*fp2;
int count = 0; //打印时间时的开头的序号
int num = 0; //计数用的字符,用任意单个字符都可以
char tm_buf[50] = { 0 };//临时存放计数器内的字符,仅用来暂时存放
// 0.以追加的方式打开文件time.txt,用来记录时间
if ((fp = fopen("time.txt", "a")) == NULL)
PRINT_ERR("fopen time.txt error");
// 1.以追加和可读的方式打开文件count.txt,用来记录序号
if ((fp2 = fopen("count.txt", "a+")) == NULL)
PRINT_ERR("fopen count.txt error");
// 2.程序开始前,更新时间序号
// fread的返回值是count.txt中字符的个数
// 整数变量count接收(fread的返回值+1),即为接下来要打印的时间的序号
count = (fread(&tm_buf,1,sizeof(tm_buf),fp2)+1);
// 3. 循环打印序号和时间
oldst = st = 0;
while (1) {
// 3.1 获取秒钟数
if ((st = time(NULL)) == -1)
PRINT_ERR("get time error");
if (st != oldst) {
oldst = st;
// 3.2 转换时间
if ((tm = localtime(&st)) == NULL)
PRINT_ERR("change time error");
// 3.3 格式化序号的时间,并写入到time.txt文件中
fprintf(fp, "%d.%d-%02d-%02d %02d:%02d:%02d\n",count,tm->tm_year + 1900,
tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
// 3.4 count.txt文件中的字符数量+1
fprintf(fp2,"%d",num);
// 3.5 序号+1
count++;
// 3.6 刷新缓冲区
fflush(fp2);
fflush(fp);
}
}
// 4.关闭文件
fclose(fp2);
fclose(fp);
return 0;
}
在linux系统中库是二进制文件,它是有.c文件编译生成了,这个.c文件中没有main函数。
库文件可以给其他工程师使用,但是其他工程师看不到源码的实现。从而保护了部分企业的利益。
在linux系统中常见的库又两种
静态库:libxxx.a
动态库:libxxx.so
注:xxx就是库的名字
1.3.1 静态库的特点
静态库的格式是libxxx.a,静态库是一个二进制文件。当其他工程师使用这个静态库的时候这个静态库会和工程师编写的代码统一生成a.out文件。a.out的体积就会比较大。但是在a.out运行的时候就不在需要这个静态库文件了。
优点:静态库的运行的效率比较高。
缺点:静态库更新起来比较麻烦。
1.3.2 静态库的制作
gcc -c add.c -o add.o //将add.c只编译不链接生成add.o的文件
ar -cr libadd.a add.o //ar是制作静态库的命令 -c创建库文件 -r将函数放在库文件中
1.3.3静态库的使用
-L : 指定调用库的路径 eg: -L ./
-l : 指定调用库的名字 eg: -ladd
-I : 指定头文件的路径 eg: -I /home/linux/work
eg:
sudo apt-get install tree
gcc main.c -L ../lib/ -ladd -I ../inc -o ../bin/a.out
1.4.1动态库的特点
动态库的格式是libxxx.so,动态库是一个二进制文件。
当其他工程师使用这个动态库的时候将这个函数的符号表和工程师的代码编译生成a.out程序。a.out的体积就会比较小。
优点:动态库更新比较方便。
缺点:在运行a.out的时候需要依赖动态库文件(共享库)。
1.4.2动态库的制作
gcc -fPIC -shared add.c -o libadd.so //制作动态库
//-fPIC:忽略文件的位置
//-shared:表示生成共享库
1.4.3动态库的使用
-L :指定调用库的路径 eg: -L ./
-l :指定调用库的名字 eg: -ladd
-I :指定头文件的路径 eg: -I /home/linux/work
gcc main.c -L ../lib/ -ladd -I ../inc/ -o ../bin/a.out
1.4.4动态库的执行
注:在没有任何操作的时候,直接运行可执行程序会出现如下的错误:
原因:找不到库的位置,因为系统在找库的时候有默认的路径/lib
方法1:将库文件放到/lib
方法2:通过环境变量告诉系统库的位置
方式3:通过系统的配置文件指定库的路径
文件的位置:
修改内容是添加第三行:
让配置文件生效:
执行看效果:
进程:进程是程序的一次执行过程,进程是一个正在执行的任务。
进程是动态的,当程序执行的时候创建进程,当程序结束的时候进程结束。进程是分配资源的最小单位,只要一个进程被创建了操作系统就会为当前的进程分配0-3G的用户空间。比如前面学习的文件描述符其实就是每个进程拥有自己的一套文件描述符[0-1023]。
进程空间相互独立,所以进程比较安全。进程在内核空间被创建创建的进程其实就是一个task_struct(PCB)结构体。进程被放到内核的队列中。
程序:程序是静态的(没有生命周期),它是一个二进制文件,在磁盘上存储。只占用文件本身大小的空间。
进程:进程是程序的一次执行过程,进程是动态的(有生命周期),进程在内存上存储,进程在运行的时候有自己的内存空间。
简单来说,运行存储在磁盘的程序时,在内存中产生了对应的进程。
进程有三部分组成:进程控制块(pcb task_struct)、文本段(TXT段)、数据段(DATA段)。
系统开辟的大小为4G的虚拟内存空间中,0-3G为用户空间,3-4G是内核空间;
用户空间分为:栈区、堆区和静态区:
栈区:由操作系统负责分配和回收;
堆区:有用户手动分配和回收;
静态区分为:BSS段、DATA段、RO段和TXT段:
BSS段:存储未初始化或者默认初始为0的全局变量,属于静态内存分配;
执行期间,bss段内容会被全部设为0。
DATA段:存储已初始化的全局变量,此处的初始化是初始化为除0以外的值,属于静态内存分配。
注意:
(1)初始化为0的全局变量还是被保存在BSS段)
(2)static声明的变量也存储在数据段。
(3)链接时初值加入执行文件;执行时,因为这些变量的值是可以被改变的,所以执行时期必须将其从ROM或Flash搬移到RAM。总之,data段会被加入ROM,但却要寻址到RAM的地址。
RO段(read_only_data段):存储常量数据,比如程序中定义为const的全局变量,#define定义的常量,或者“Hello World”的字符串常量,以只读的方式存储在ROM中。
注意:
(1)有些立即数与指令编译在一起,放在text段。
(2)const修饰的全局变量在常量区;const修饰的局部变量只是为了防止修改,没有放入常量区。
(3)编译器会去掉重复的字符串常量,程序的每个字符串常量只有一份。
(4)有些系统中rodata段是多个进程共享的,目的是为了提高空间利用率。
TXT段(代码段):存储程序代码,运行前就已经确定(编译时确定),通常为只读,可以直接在ROM或Flash中执行,无需加载到RAM。
PID:进程号,是操作系统识别进程的唯一的编号。在linux系统上pid是一个大于等于0的数值。在linux系统上可以使用cat /proc/sys/kernel/pid_max 查看系统的最大的进程号。
另外在/proc目录下看到的所有的以数字命名的文件夹都代表系统上正在运行的进程。
0号进程:0号进程idle,在操作系统上电启动的时候最开始执行的进程。在没有其他进程运行的时候就执行这个进程。
1号进程:1号进程init,1号进程时候0号进程创建的,是在内核空间通过kernel_thread创建的第一个进程。init进程可以完成系统的初始化,还可以为收养孤儿进程。
2号进程:2号进程kthreadd,2号进程的父进程也是0号进程,也是通过kernel_thread函数在内核空间创建,它主要负责进程的调度执行。
进程的种类分为三种:
交互进程:交互进程使用shell维护,可以通过shell和用户进程交互,例如文本编辑器就是交互进程。
批处理进程:批处理进程优先级比较低,一般被放在系统后台队列中执行。例如gcc编译程序过程。
守护进程:守护进程是后台运行的进程,随着系统的启动而启动,随着系统的终止而终止,例如系统上正在运行的各种服务。
1.ps命令
linux@ubuntu:~/work/day1$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 9月28 ? 00:00:12 /sbin/init auto noprompt
root 2 0 0 9月28 ? 00:00:00 [kthreadd]
root 3 2 0 9月28 ? 00:00:00 [rcu_gp]
root 4 2 0 9月28 ? 00:00:00 [rcu_par_gp]
root 6 2 0 9月28 ? 00:00:00 [kworker/0:0H-kb]
uid //用户的id
pid //进程号
ppid //父进程号
TTY //是否有终端于进程对应,如果不是?就说明有交互终端
CMD //进程名
linux@ubuntu:~/work/day1$ ps -ajx
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:12 /sbin/init
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 I< 0 0:00 [rcu_gp]
2 4 0 0 ? -1 I< 0 0:00 [rcu_par_gp]
2 6 0 0 ? -1 I< 0 0:00 [kworker/0:0H-kb]
2 9 0 0 ? -1 I< 0 0:00 [mm_percpu_wq]
PPID:父进程号
PID :进程号
PGID:组id
SID :会话ID
只要在linux系统上打开了一个终端就相当于打开了一个会话,
一个会话包含多个进程组(一个前台进程组和多个后台进程组)
一个进程组内有包含了多个进程。
TTY :是否有终端于进程对应,如果不是?就说明有交互终端
TPGID:只要是-1就是守护进程
STAT:进程的状态
2.top/htop命令
htop(sudo apt-get install htop)
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
64823 linux 20 0 722M 63040 31388 S 0.7 1.6 0:31.84 /home/linux/.vscode-server/bin/da76f93349a
1358 gdm 20 0 3973M 158M 50112 S 0.7 4.1 1:26.05 /usr/bin/gnome-shell
27637 linux 20 0 4302M 308M 94652 S 0.7 7.9 2:51.72 /usr/bin/gnome-shell
27631 linux 20 0 4302M 308M 94652 S 0.0 7.9 24:53.21 /usr/bin/gnome-shell
64785 linux 20 0 939M 79052 33468 S 0.0 2.0 0:22.39 /home/linux/.vscode-server/bin/da76f93349a
72034 linux 20 0 34164 4936 3884 R 0.0 0.1 0:00.08 htop
27640 linux 20 0 4302M 308M 94652 S 0.0 7.9 2:40.77 /usr/bin/gnome-shell
3.kill命令
3.1查看linux系统中的信号
linux@ubuntu:~/work$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
3.2如何发信号
kill -信号号 pid
ctrl+c 等价于 kill -2 pid
3.3只查看进程的信号号
pidof a.out
73070
3.4发信号的命令
killall 进程名 //将所有以a.out命名的进程终止掉
4.查看进程最大进程号
cat /proc/sys/kernel/pid_max
2.8.1进程的状态码
1.进程的状态
D 不能(信号)中断的休眠态
R 运行态
S 可(信号)中断的休眠态
T 停止态
X 死亡态
Z 僵尸态
2.进程的附加状态
< 高优先级的进程
N 低优先级的进程
L 内存锁定
s 会话组组长
l 进程中包含多线程
+ 前台进程
2.8.2进程状态图
2.8.3进程状态切换实例
#include
int main(int argc,const char * argv[])
{
while(1);
return 0;
}
运行上述的程序,状态如下
让上述的程序变为停止状态
ctrl + z (kill -19 pid)
查看停止状态进程的作业号
jobs -l
让进程后台运行
bg 作业号
让进程前台运行
fg 作业号
孤儿进程:父进程死掉之后,子进程就是孤儿进程,孤儿进程被init进程收养。
僵尸进程:进程结束,父进程没有为它回收资源,这个种进程就是僵尸进程。
2.10.1进程创建的原理
在使用程序创建进程的时候,进程的创建是拷贝父进程得到的,只需要稍作修改即可使用(例如pid,ppid)。
2.10.2进程创建的API
#include
#include
pid_t fork(void);
功能:创建一个子进程
参数:
@无
返回值:失败父进程收到-1,并置位错误码
成功父进程收到子进程的pid,子进程收到0
2.10.3进程创建实例(不关注返回值)
1.fork一次创建的进程
#include
int main(int argc,const char * argv[])
{
//创建一个子进程
fork();
while(1);
return 0;
}
2.fork三次创建的进程的个数
#include
int main(int argc,const char * argv[])
{
//如果不关注fork的返回值,fork n次就创建2^n个进程
for(int i=0;i<3;i++){
fork();
}
while(1);
return 0;
}
3.fork和缓冲区结合的问题
#include
int main(int argc,const char * argv[])
{
//问:这个程序打印多少个'-',为什么?
for(int i=0;i<2;i++){
fork();
printf("-");
}
return 0;
}
2.10.4进程创建实例(关注返回值)
#include
int main(int argc, const char* argv[])
{
pid_t pid;
pid = fork();
if (pid == -1) {
// fork失败,没有创建出子进程,父进程报错返回
PRINT_ERR("fork error");
} else if (pid == 0) {
//子进程的代码区
printf("i am child process\n");
} else {
//父进程的代码区
printf("i am parent process\n");
}
return 0;
}
2.10.5父子进程执行的先后顺序
父子进程执行没有先后顺序,时间片轮询上下文切换。
2.10.6父子进程内存空间问题
#include
int main(int argc, const char* argv[])
{
pid_t pid;
int a=10;
pid = fork();
if (pid == -1) {
// fork失败,没有创建出子进程,父进程报错返回
PRINT_ERR("fork error");
} else if (pid == 0) {
a=100;
//子进程的代码区
printf("i am child process,a = %d,&a=%p\n",a,&a);
} else {
sleep(2);
//父进程的代码区
printf("i am parent process,a = %d,&a=%p\n",a,&a);
}
return 0;
}
父进程中的a的变量,因为子进程拷贝了父进程,所以在子进程中也有a的变量,当fork之后父子进程的a的变量相互不干扰。修改子进程a变量的值,父进程a变量的值不会改表。因为子进程是拷贝父进程得到的,所以父子进程中a的虚拟地址是一样的但是对应的物理地址是不一样的。
使用两个进程拷贝同一个文件,父进程拷贝前一半,子进程拷贝后一半。
1.子进程和父进程复制到不同的文件中
#include
int main(int argc, const char* argv[]) { pid_t pid; int fd1,fd2,fd3,fd4; char buf[20] = {0}; char buf2[2] = {0}; int ret = 0; int size = 0; int count = 0; FILE *fp1, *fp2; int ch; // 1.只读的方式打开源文件 if ((fd1 = open("hello.txt", O_RDONLY)) == -1) PRINT_ERR("open src error"); // 2.将文件按字符数平分为两部分 size = lseek(fd1, 0, SEEK_END); size = size / 2; // 3.关闭文件 close(fd1); // 4.创建子进程 pid = fork(); if (pid == -1) { // fork失败,没有创建出子进程,父进程报错返回 PRINT_ERR("fork error"); } else if (pid == 0) { //子进程的代码区 if ((fd1 = open("hello.txt", O_RDONLY)) == -1) PRINT_ERR("open src error"); if ((fd2 = open("child_file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1) PRINT_ERR("open dest error"); // 光标定位到中间位置(size) lseek(fd1,size,SEEK_SET); // 循环拷贝 while ((ret = read(fd1, buf, sizeof(buf))) > 0) { write(fd2, buf, ret); } // 关闭文件 close(fd1); close(fd2); printf("子进程已拷贝完成!\n"); } else { //父进程的代码区 if ((fp1 = fopen("hello.txt", "r")) == NULL) { perror("fopen src error"); return -1; } if ((fp2 = fopen("parent_file.txt", "w")) == NULL) { perror("fopen dest error"); return -1; } // 循环拷贝 for(int i = 0;i 2. 子进程和父进程复制到一个文件中
#include
int main(int argc, const char* argv[]) { pid_t pid; int fd1,fd2,fd3,fd4; char buf[20] = {0}; char buf2[2] = {0}; int ret = 0; int size = 0; int count = 0; FILE *fp1, *fp2; int ch; // 1.只读的方式打开源文件 if ((fd1 = open("hello.txt", O_RDONLY)) == -1) PRINT_ERR("open src error"); // 2.将文件按字符数平分为两部分 size = lseek(fd1, 0, SEEK_END); size = size / 2; // 3.关闭文件 close(fd1); // 4.创建子进程 pid = fork(); if (pid == -1) { // fork失败,没有创建出子进程,父进程报错返回 PRINT_ERR("fork error"); } else if (pid == 0) { //子进程的代码区 if ((fd1 = open("hello.txt", O_RDONLY)) == -1) PRINT_ERR("open src error"); if ((fd2 = open("copy_file.txt", O_WRONLY|O_APPEND|O_CREAT,0666)) == -1) PRINT_ERR("open dest error"); // 光标定位到中间位置(size) lseek(fd1,size,SEEK_SET); // 循环拷贝 while ((ret = read(fd1, buf, sizeof(buf))) > 0) { write(fd2, buf, ret); } // 关闭文件 close(fd1); close(fd2); printf("子进程已拷贝完成!\n"); } else { //父进程的代码区 if ((fp1 = fopen("hello.txt", "r")) == NULL) { perror("fopen src error"); return -1; } if ((fp2 = fopen("copy_file.txt", "w")) == NULL) { perror("fopen dest error"); return -1; } // 循环拷贝 for(int i = 0;i