目录

前言

周二

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简介和标准IO

一、IO简介

1.1 什么是IO

IO(input&output)是用户空间和内核空间通过API进行的交互。

1.2 IO的分类

IO分为:标准IO和文件IO

标准IO:库函数

文件IO:系统调用

1.3 什么是系统调用和和库函数

系统调用:系统调用是用户空间进入内核空间的一次过程。

优点:操作系统不同,系统调用的函数接口也不同,因此系统调用的移植性比较差。

缺点:系统调用没有缓冲区,所以效率比较低。

库函数:库函数 = 系统调用 + 缓冲区

优点:库函数要比系统调用的效率高。

缺点:库函数的接口比较统一,移植性比较强。

区别辨析:系统调用对内核的访问是即时的,库函数的缓冲区需要刷新才能访问内核。

1.4 什么是IO接口

IO接口就是函数调用,系统已经封装好,使用时直接调用。

例如:

标准IO:fopen fread fwrite fputc fgetc fputs fgets fclose printf scanf...

文件IO:open read write close...

二、标准IO

标准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; //缓冲区的结束地址...
}

2.1 fopen/fclose函数

#include <stdio.h>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 <stdio.h>int main(int argc,const char * argv[])
{FILE *fp;if((fp = fopen("./hello.txt","w")) == NULL){printf("fopen file error\n");return -1;}//有一个fopen就要对应一个fcloseif(fclose(fp)){printf("fclose file error\n");return -1;}return 0;
}

2.1 strerror函数

#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("fopen file error\n");printf("errno = %d,%s\n",errno,strerror(errno));return -1;}return 0;
}

2.3 perror函数

#include <stdio.h>void perror(const char *s);
功能:将错误信息打印到终端上
参数:@s:用户的附加信息
返回值:无

实例:

注意perror打印时会在结尾部冒号。

#include <stdio.h>int main(int argc,const char * argv[])
{FILE *fp;if((fp = fopen("./hello.txt","r")) == NULL){perror("fopen file error");return -1;}return 0;
}

2.4 fputc/fgetc函数

put是往文件中,get是从文件中

#include <stdio.h>int fputc(int c, FILE *stream);
功能:向文件中写入字符
参数:@c:字符的ascii@stream:文件指针
返回值:成功返回字符ascii值,失败返回EOF(-1)int fgetc(FILE *stream);
功能:从文件中读取字符
参数:@stream:文件指针
返回值:成功读取到的字符的ascii,读取到文件结尾或者出错,返回EOF(-1)

fputc实例

#include <stdio.h>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 <stdio.h>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 <stdio.h>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 <stdio.h>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 <stdio.h>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;
}

2.5 fgets/fputs函数

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,失败返回NULLint fputs(const char *s, FILE *stream);
功能:将s中的内容写入到stream对应的文件中(不包含'\0')
参数:@s:被写字符串的首地址@stream:文件指针
返回值:成功返回大于0的值,失败返回EOF

fgets函数的实例(fgets读取文件中的内容)

#include <stdio.h>#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 <stdio.h>
#include <string.h>#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 <stdio.h>
#include <string.h>#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 <stdio.h>
#include <string.h>#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 <stdio.h>
#include <string.h>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 <stdio.h>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;
}

三、缓冲区问题

3.1 缓冲区的大小及类型

全缓存:和文件相关的缓冲区(fp)4096(4K)

行缓存:和终端相关的缓冲区 (stdin stdout)  1024(1k)

无缓存:和标准出错 (stderr)  0

#include <stdio.h>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;
}

3.2 行缓存的刷新机制

1.行缓存遇到换行符的时候会刷新缓冲区

2.当程序结束的时候会刷新行缓存

3.当输入输出发生切换的时候

4.当关闭文件的时候会刷新缓冲区

5.缓冲区满也会刷新缓冲区

6.主动刷新缓冲区fflush

#include <stdio.h>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;
}

3.3 全缓存的刷新机制

1.当程序结束的时候会刷新行缓存

2.当输入输出发生切换的时候

3.当关闭文件的时候会刷新缓冲区

4.缓冲区满也会刷新缓冲区

5.主动刷新缓冲区fflush

(与行缓存相比,差了一个换行符刷新)

四、课后作业

从终端向程序输入任意的字符串,请把输入的字符串写入(追加)到hello.txt的文件中。

从文件中将最后一行的内容读取出来,并显示到终端上。

#include <stdio.h>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.从终端读取字符串 fgetsprintf("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;
}

周三

标准IO及文件IO

一、标准IO

1.1 fread/fwrite函数

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 <head.h>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 <head.h>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 <head.h>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 0while (!(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 sprintf/snprintf/fprintf函数

1.2.1 sprintf函数

int sprintf(char *str, const char *format, ...);
功能:按照控制格式将字符串写入到str中
参数:@str   :存储格式化后的字符串@format:控制格式@...   :可变参数
返回值:成功返回格式化的字符的个数,失败返回负数

sprintf使用实例

#include <head.h>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 <head.h>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 <head.h>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 <head.h>int main(int argc,const char * argv[])
{//将字符串写入到标准输出中fprintf(stdout,"hello world\n");return 0;
}

1.3 获取系统函数

#include <time.h>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;    //月 +1int tm_year;   //年 + 1900int tm_wday;   /*周几 (0-6, Sunday = 0) */int tm_yday;   /*在一年中的第多少天 */int tm_isdst;  /* 夏令时 */};  
#include <head.h>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 <head.h>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 <head.h>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;
}

1.4 fseek/ftell/rewind光标操作函数

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);

1.5使用标准IO对bmp格式图片打马赛克

主要用到的知识: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 <head.h>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 <head.h>#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:系统调用

文件IO:open read write close...

fd文件描述:在使用open函数打开文件的时候返回的就是文件描述符,它是一个整数,这个整数就代表打开的这个文件,一个对这个文件的读写操作都通过fd完成。默认在一个正在执行的程序中0,1,2已经被使用了,分别代表的功能标准输入、标准输出、标准出错。在前面使用标准IO的时候也有这个

数值 功能
0 标准输入
1 标准输出
2 标准出错

三个内容分别对应的是标准IO中的stdin,stdout,stderr。它们三个是FILE *的指针,在这三个指针中包含0,1,2的文件描述符

#include <head.h>int main(int argc, const char* argv[])
{printf("标准输入 = %d\n", stdin->_fileno); //这个fileno就是文件描述符   0printf("标准输出 = %d\n", stdout->_fileno); //这个fileno就是文件描述符  1printf("标准错误 = %d\n", stderr->_fileno); //这个fileno就是文件描述符  2return 0;
}

在linux系统中默认文件描述符的范围[0-1023],可以通过 ulimit -a查看限制,但是这个限制不是一成不变的,可以通过ulimit -n 2048修改这个限制。每一个正在执行的程序都有自己的文件描述符,程序和程序之间的文件描述符相互不干扰。

2.1 open/close函数

#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);
功能:使用文件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 <unistd.h>int close(int fd);
功能:关闭文件
参数:@fd:文件描述符
返回值:成功返回0,失败返回-1置位错误码

open函数的实例1

#include <head.h>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 <head.h>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 <head.h>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;
}

周四

文件IO、文件属性操作和目录操作

一、文件IO

1.1read/write函数

#include <unistd.h>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 <head.h>
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 <head.h>
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 <head.h>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 <sys/types.h>
#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);
功能:修改光标的位置
参数:@fd:文件描述符@offset:偏移>0:向后偏移=0:不偏移<0:向前偏移@whence:从哪开始偏移SEEK_SET:从头开始偏移SEEK_CUR:从当前位置开始偏移SEEK_END:从结尾开始偏移
返回值:成功返回光标到开头的字节数,失败返回-1置位错误码

二、获取文件属性的函数

2.1 stat函数

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>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 fieldS_IFSOCK   0140000   socketS_IFLNK    0120000   symbolic linkS_IFREG    0100000   regular fileS_IFBLK    0060000   block deviceS_IFDIR    0040000   directoryS_IFCHR    0020000   character deviceS_IFIFO    0010000   FIFOif((st_mode & S_IFMT)==S_IFREG){printf("这是一个普通文件\n");}在st_mode中0-8这9个bit为就是文件的权限获取文件的权限 = st_mode & 0777nlink_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的大小 512blkcnt_t  st_blocks;      //文件的大小,单位是blockstruct timespec st_atim;  //最后一次访问文件的时间struct timespec st_mtim;  //文件最后一次被修改的时间struct timespec st_ctim;  //最后一次文件状态改变的时候 };
返回值:成功返回0,失败返回-1置位错误码

stat函数使用实例

#include <head.h>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;
}

2.2 getpwuid/getgrid函数

#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
功能:根据uid获取passwd结构体
参数@uid:用户的iD
返回值:成功返回结构体指针,失败返回NULL置位错误码struct passwd {char   *pw_name;       //用户名char   *pw_passwd;     //用户密码uid_t   pw_uid;        //UIDgid_t   pw_gid;        //GIDchar   *pw_gecos;      //用户的信息char   *pw_dir;        //用户家目录char   *pw_shell;      //命令行解析器 /bin/bash};#include <grp.h>
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 <head.h>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;
}

三、目录操作

3.1 打开目录opendir

#include <dirent.h>DIR *opendir(const char *name);
功能:打开目录
参数:@name:目录名
返回值:成功返回DIR结构体指针,失败返回NULL置位错误码

3.2 读目录readdir

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]; //文件的名字};

3.3关闭目录closedir

int closedir(DIR *dirp);
功能:关闭文件夹
参数:@dirp:DIR的结构体指针
返回值:成功返回0,失败返回-1置位错误码

3.4读取任意文件夹下的所有文件实例

#include <head.h>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 <head.h>#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 <head.h>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文件中的字符数量+1fprintf(fp2,"%d",num);// 3.5 序号+1count++;// 3.6 刷新缓冲区fflush(fp2);fflush(fp);}}// 4.关闭文件fclose(fp2);fclose(fp);return 0;
}

周五

库、多进程

一、Linux中的库

1.1 什么是库文件

在linux系统中库是二进制文件,它是有.c文件编译生成了,这个.c文件中没有main函数。

库文件可以给其他工程师使用,但是其他工程师看不到源码的实现。从而保护了部分企业的利益。

1.2 库文件的分类

在linux系统中常见的库又两种

静态库:libxxx.a

动态库:libxxx.so

注:xxx就是库的名字

1.3 静态库

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.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:通过系统的配置文件指定库的路径

文件的位置:

修改内容是添加第三行:

让配置文件生效:

执行看效果:

二、多进程

2.1 什么是多进程

进程:进程是程序的一次执行过程,进程是一个正在执行的任务。

进程是动态的,当程序执行的时候创建进程,当程序结束的时候进程结束。进程是分配资源的最小单位,只要一个进程被创建了操作系统就会为当前的进程分配0-3G的用户空间。比如前面学习的文件描述符其实就是每个进程拥有自己的一套文件描述符[0-1023]。

        进程空间相互独立,所以进程比较安全。进程在内核空间被创建创建的进程其实就是一个task_struct(PCB)结构体。进程被放到内核的队列中。

2.2 进程和程序的区别

程序:程序是静态的(没有生命周期),它是一个二进制文件,在磁盘上存储。只占用文件本身大小的空间。

进程:进程是程序的一次执行过程,进程是动态的(有生命周期),进程在内存上存储,进程在运行的时候有自己的内存空间。

简单来说,运行存储在磁盘的程序时,在内存中产生了对应的进程。

2.3 进程的组成

进程有三部分组成:进程控制块(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。

2.4 什么是PID?

PID:进程号,是操作系统识别进程的唯一的编号。在linux系统上pid是一个大于等于0的数值。在linux系统上可以使用cat /proc/sys/kernel/pid_max 查看系统的最大的进程号。

另外在/proc目录下看到的所有的以数字命名的文件夹都代表系统上正在运行的进程。

2.5 特殊PID的进程

0号进程:0号进程idle,在操作系统上电启动的时候最开始执行的进程。在没有其他进程运行的时候就执行这个进程。

1号进程:1号进程init,1号进程时候0号进程创建的,是在内核空间通过kernel_thread创建的第一个进程。init进程可以完成系统的初始化,还可以为收养孤儿进程。

2号进程:2号进程kthreadd,2号进程的父进程也是0号进程,也是通过kernel_thread函数在内核空间创建,它主要负责进程的调度​执行。

2.6 进程的种类

进程的种类分为三种:

交互进程:交互进程使用shell维护,可以通过shell和用户进程交互,例如文本编辑器就是交互进程。

批处理进程:批处理进程优先级比较低,一般被放在系统后台队列中执行。例如gcc编译程序过程。

守护进程:守护进程是后台运行的进程,随着系统的启动而启动,随着系统的终止而终止,例如系统上正在运行的各种服务。

2.7 进程相关的命令

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 -ajxPPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND0      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+  Command64823 linux      20   0  722M 63040 31388 S  0.7  1.6  0:31.84 /home/linux/.vscode-server/bin/da76f93349a1358 gdm        20   0 3973M  158M 50112 S  0.7  4.1  1:26.05 /usr/bin/gnome-shell27637 linux      20   0 4302M  308M 94652 S  0.7  7.9  2:51.72 /usr/bin/gnome-shell27631 linux      20   0 4302M  308M 94652 S  0.0  7.9 24:53.21 /usr/bin/gnome-shell64785 linux      20   0  939M 79052 33468 S  0.0  2.0  0:22.39 /home/linux/.vscode-server/bin/da76f93349a72034 linux      20   0 34164  4936  3884 R  0.0  0.1  0:00.08 htop27640 linux      20   0 4302M  308M 94652 S  0.0  7.9  2:40.77 /usr/bin/gnome-shell3.kill命令3.1查看linux系统中的信号linux@ubuntu:~/work$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR111) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+338) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+843) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+1348) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-1253) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-758) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-263) SIGRTMAX-1  64) SIGRTMAX3.2如何发信号kill -信号号 pid    ctrl+c  等价于 kill -2 pid3.3只查看进程的信号号pidof a.out730703.4发信号的命令killall 进程名 //将所有以a.out命名的进程终止掉4.查看进程最大进程号cat /proc/sys/kernel/pid_max

2.8 进程的状态

2.8.1进程的状态码

1.进程的状态D    不能(信号)中断的休眠态R    运行态S    可(信号)中断的休眠态T    停止态X    死亡态Z    僵尸态
2.进程的附加状态<    高优先级的进程N    低优先级的进程L    内存锁定s    会话组组长l    进程中包含多线程+    前台进程

2.8.2进程状态图

2.8.3进程状态切换实例

#include <head.h>int main(int argc,const char * argv[])
{while(1);return 0;
}

运行上述的程序,状态如下

让上述的程序变为停止状态

ctrl + z (kill -19 pid)

查看停止状态进程的作业号

jobs -l

让进程后台运行

bg 作业号

让进程前台运行

fg 作业号

2.9 特殊状态的进程

孤儿进程:父进程死掉之后,子进程就是孤儿进程,孤儿进程被init进程收养。

僵尸进程:进程结束,父进程没有为它回收资源,这个种进程就是僵尸进程。

2.10 进程的创建

2.10.1进程创建的原理

在使用程序创建进程的时候,进程的创建是拷贝父进程得到的,只需要稍作修改即可使用(例如pid,ppid)。

2.10.2进程创建的API

#include <sys/types.h>
#include <unistd.h>pid_t fork(void);
功能:创建一个子进程
参数:@无
返回值:失败父进程收到-1,并置位错误码成功父进程收到子进程的pid,子进程收到0

2.10.3进程创建实例(不关注返回值)

1.fork一次创建的进程

#include <head.h>int main(int argc,const char * argv[])
{//创建一个子进程fork();while(1);return 0;
}

2.fork三次创建的进程的个数

#include <head.h>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 <head.h>int main(int argc,const char * argv[])
{//问:这个程序打印多少个'-',为什么?for(int i=0;i<2;i++){fork();printf("-");}return 0;
}

2.10.4进程创建实例(关注返回值)

#include <head.h>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 <head.h>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 <head.h>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<size;i++){ch = fgetc(fp1);fputc(ch,fp2);}// 关闭文件fclose(fp1);fclose(fp2);printf("父进程已拷贝完成!\n");}return 0;
}

2. 子进程和父进程复制到一个文件中

#include <head.h>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<size;i++){ch = fgetc(fp1);fputc(ch,fp2);}// 关闭文件fclose(fp1);fclose(fp2);printf("父进程已拷贝完成!\n");}return 0;
}

华清远见嵌入式培训_第六周回顾与反思相关推荐

  1. 华清远见嵌入式培训_第三周回顾与反思

    目录 前言 周一 一.函数 1.1 指针函数 1.2 函数指针 1.3 函数指针数组(了解) 1.4 指向函数指针数组的指针(了解) 二.typedef 2.1 typedef的使用 2.2 type ...

  2. 华清远见嵌入式培训_第五周回顾与反思

    前言 这是在华清学习的第五个周末,这一周主要学习的是数据结构.老师说数据结构是一门非常庞大的学科,单单是数据结构里的一个小分支,单拎出来一周都未必可以学透,因此这周的数据结构课程里更多的是思维方向的学 ...

  3. 华清远见嵌入式培训_第七周回顾与反思(上)

    前言 七天的国庆假期已经结束,紧接着的是七天的工作日,前几天的我感叹假期七天有多短,此时此刻的我就感叹七天有多长! 言归正传,本周的前五天课里,紧接着假期前的IO进程课程,第一天学习了:进程中各种常用 ...

  4. 华清远见嵌入式培训_第一周回顾与反思

    目录 第一天 周二 1.1 Linux系统的使用 1.2 终端 1.3 Linux系统命令 1.4 vi编辑器的使用 第二天 周三 2.1 顺利进入("hello world")阶 ...

  5. 华清远见嵌入式培训_第二周回顾与反思

    目录 前言 周一 一.switch..case 1.1 注意事项 1.2 使用练习 二.循环控制语句 2.1 使用goto实现循环 2.2 while循环 2.3 do..while 循环 2.4 f ...

  6. 华清远见嵌入式培训_第四周回顾与反思

    目录 前言 周二 Linux命令拓展 1 软件安装相关的命令 1.1 离线安装命令--dpkg 1.2 在线安装命令--apt-get 2. 归档和拆包的命令(tar)(重点) 3. 文件操作相关的命 ...

  7. 华清远见嵌入式培训---入学

    读者们好,我是华清的一名学员,去年9月报名参加了华清远见的培训,因为本人大四还没有毕业,一直在学校上线上课程,今年2月才加入到了线下班级,在这里想谈一下来到华清半个多月的感受. 首先说一下我为什么培训 ...

  8. linux内核培训广州,嵌入式Linux驱动开发高级培训班-华清远见嵌入式培训中心

    课程目标 本课程以案例教学为主,系统地介绍Linux下有关FrameBuffer.MMC卡.USB设备的驱动程序开发.参加本课程学习的学员,因为具备了Linux设备驱动开发基础,所以本课程针对性较强, ...

  9. 【华清远见嵌入式培训】网络编程(更新中)

    Pre-learning:网络编程 不借助第三方软件实现不同主机.不同操作系统之间的通信.搭建服务器与客户端. Chapter 1 网络发展史与背景 1.1 网络发展简史 ARPnet(阿帕网)因为没 ...

最新文章

  1. 网络电视视频服务器所放位置,pptv网络电视中多屏互动服务器删除的具体操作步骤...
  2. 基于 Token 的多平台身份认证架构设计
  3. 计算机等级考试三级数据库考点,全国计算机等级考试辅导:三级数据库考点
  4. 【CASS精品教程】CASS 9.2 for AutoCAD2014启动提示文件加载,怎么处理?
  5. 最新基于adt-bundle-windows-x86的android开发环境搭建
  6. 1. JanusGraph的优势
  7. java创建项目出现怎么办_maven创建项目后main/java missing的解决方法
  8. 带你动手编程的强化学习著作,每行代码都是它的温柔!
  9. 早该知道的7个JavaScript技巧
  10. Neo4j下载安装教程
  11. 目标检测中的非极大值抑制(NMS)
  12. SAP计划策略40测试
  13. 高中数学怎么学好学好高中数学的技巧
  14. 占坑:fullpage.js 在edge浏览器上上下移动鼠标(不滚动滚轮)会翻页的问题
  15. UVALive 6436 The Busiest City
  16. 【洛谷】入门2 分支结构
  17. THREE.OrbitControls参数控制解释
  18. 苹果A15仿生芯片集成150亿个晶体管,较A14增加近30%
  19. 计算机房清洁安排表怎么做,机房总安排表.doc
  20. 海报图片怎么做二维码?如何在线做二维码图片?

热门文章

  1. html瞄点四大名著有图片,四大名著手抄报图片图和文字资料
  2. 通过canvas画出爱心图案,表达你的爱意!
  3. 北京大学肖臻老师《区块链技术与应用》公开课笔记8——BTC挖矿篇
  4. 基于51单片机的温湿度检测及调节系统
  5. 算法之红黑树/JAVA
  6. 基于单片机的多功能数字时钟设计
  7. 如何让新入职员工尽快融入集体,进入工作岗位
  8. python3GUI--仿做一个网易云音乐By:PyQt5(附下载地址)
  9. leetcode954.二倍数对数组C++(绝对值排序)
  10. 林轩田机器学习技法(Machine Learning Techniques)笔记(一)