1. 标准库IO简介
标准库IO特点:通过操作系统提供的接口(API)和操作系统进行交互。(接近100个函数)
1.1. IO的过程
操作系统:向上为用户提供操作接口,向下为统筹控制硬件。
操作系统的组成:内核+外壳(shell)。
linux内核的五大功能:进程管理,内存管理,文件管理,网络管理,设备管理。
C语言基础最早接触的IO:
- printf 标准输出
- scanf 标准输入
1.2. IO的种类
- 文件IO (系统调用)
- 标准IO (库函数)
1.3. 文件IO(系统调用)
系统调用是受控的内核入口,我们通过系统调用从用户空间进入内核空间(另外一种说法:从用户空间进入内核空间的过程叫做系统调用),常用于linux系统。
特点:系统调用的可移植性差,效率相对较低,实时性强。
1.4. 标准IO(库函数)
简单可以理解成:库函数 = 系统调用+缓冲区
库函数的可移植性强,效率高,实时性差。
(库函数减少了系统调用的次数,从而提高的内核的工作效率)
1.5. 常用的函数接口
- 文件IO:open,close,read,write,lseek
- 标准IO:fopen,fclose,fread,fwrite,.......
2. 标准IO(库函数)
2.1. FILE指针
FILE是标准库中定义的一个结构体。
在这个结构体中包含了打开文件的相关信息。
FILE指针就是一个结构体指针。我们通过FILE指针去对文件进行操作。
当使用fopen函数成功打开文件的时候,就会返回一个FILE指针,后续我们对文件的读写操作都是通过这个FILE指针去进行的。
每个正在运行的程序都会维护自己的FILE指针,即使多个程序打开同一个文件,对应的FILE指针也是不同的。
每个正在运行的程序都有三个默认打开的FILE指针。
- 标准输入:stdin -- scanf
- 标准输出:stdout --- printf
- 标准出错:stderr --- perror
struct _IO_FILE
{ char *_IO_buf_base; /* 缓冲区的起始地址 */ char *_IO_buf_end; /* 缓冲区的结束地址 */ int _fileno; /*文件描述符*/
};
2.2. fopen函数
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:打开pathname对应的文件
参数:pathname:文件的路径和名字mode:打开文件的模式 "r" "r+"r 以只读的方式打开文件,光标定位在文件的开头r+ 以读写的方式打开文件,光标定位在文件开头w 以只写的方式打开文件,光标定位在文件开头。如果文件不存在就创建,如果文件存在就清空w+ 以读写的方式打开文件,光标定位在文件开头。如果文件不存在就创建,如果文件存在就清空a 以追加的方式打开文件,光标定位在文件结尾,如果文件不存在就创建a+ 以追加和读的方式打开文件,如果写光标会定位到文件的结尾,如果读,光标会定位在文件的开头。如果文件不存在就创建
返回值:成功返回文件对应的FILE指针,失败返回NULL,置位错误码
fopen使用实例
#include<stdio.h>
int main()
{FILE *fp = NULL;//定义一个FILE指针//使用fopen 打开文件// //以只读的方式打开文件,文件不存在会报错// fp = fopen("./a.txt","r");// if(NULL == fp){// printf("fopen a.txt error\n");// return -1;// }//以只写的方式打开文件,文件不存在就创建,存在就清空fp = fopen("./a.txt","w");if(NULL == fp){printf("fopen a.txt error\n");return -1;}//不管以什么样的方式去打开文件,当前用户一定要有对该文件的操作权限return 0;
}
2.3. fclose函数
#include <stdio.h> int fclose(FILE *stream);
功能:关闭FILE指针
参数: stream:通过fopen获取到的文件指针
返回值:成功返回0,失败返回EOF,置位错误码
fclose代码实例
#include<stdio.h>
int main()
{//1.先做一个正常的输入int a = 0;scanf("%d",&a);printf("第一次输出a = %d\n",a);fclose(stdin); //关闭标准输入scanf("%d",&a);printf("第二次输出a = %d\n",a);//2.做一个正常的输出printf("111111111111\n");fclose(stdout); //关闭标准输出printf("2222222222222\n");return 0;
}
2.4. 错误码
2.4.1. 错误码的概念
- 错误码是系统调用失败之后,内核向用户空间返回的错误信息的编号。
- 错误码本质是一个整数,错误码保存在errno.h中的一个全局变量errno中。
- 错误码在内核中有4k个。
/usr/include/asm-generic/errno-base.h
2.4.2. 错误的原理
2.4.3. 错误码使用实例1
#include<stdio.h>
#include<errno.h>
int main(int argc,const char* argv[])
{//打开一个不存在的文件FILE *fp = fopen("b.txt","r");if(NULL == fp){printf("errno = %d\n",errno);//errno = 2,表示没有这个文件//return -1;}//成功调用函数,并不会置位错误码fp = fopen("a.txt","r");printf("errno = %d\n",errno);return 0;
}
2.4.4. 将错误码转换为错误信息的函数(strerror)
#include <string.h>char *strerror(int errnum);功能:将errnum转换为对应的错误信息参数:errnum:错误码返回值:错误码对应的错误信息,失败返回"Unknown error nnn"
2.4.5. 直接输出错误信息
#include <stdio.h> void perror(const char *s);
功能:直接输出错误信息,输出之后,会自动添加换行
参数: s:错误信息的提示,后面会自动添加冒号
返回值:空#include<stdio.h> #include<errno.h>
#include<string.h>
int main(int argc,const char* argv[])
{ //打开一个不存在的文件 FILE *fp = fopen("b.txt","r");
if(NULL == fp)
{ perror("fopen b.txt error"); }
return 0; }
2.5. fgetc函数
#include <stdio.h>
int fgetc(FILE *stream);
功能:从文件中读取一个字符,以返回值的形式返回
参数: stream:文件指针
返回值:成功返回获取到的字符,失败或者读取到文件结尾返回EOF
fgetc使用实例
#include <my_head.h>int main(int argc, const char *argv[])
{// 1.打开文件FILE *fp = fopen("./a.txt", "r");if (NULL == fp)PRINT_ERR("fopen a.txt error");// 2.从文件中读取一个字符,光标向后偏移一个字节char ch = 0;ch = fgetc(fp);printf("ch = %c\n", ch); //读取到 '1'//再次读取,读取到 '2'ch = fgetc(fp);printf("ch = %c\n", ch);//从终端获取一个字符ch = fgetc(stdin); //这种用法,等价于getchar()printf("ch = %c\n", ch);ch = fgetc(stdin); //这种用法,等价于getchar()printf("ch = %c\n", ch);return 0;
}
2.6. fputc函数
#include <stdio.h>
int fputc(int c, FILE *stream);
功能:向文件中写入一个字符
参数: c:要写入的字符 stream:文件指针
返回值:成功返回写入的字符的ascii码,失败返回EOF
fputc使用实例
#include<my_head.h>int main(int argc,const char* argv[])
{// //1.只写的方式打开文件// FILE *fp = fopen("./a.txt","w");// if(NULL == fp)// PRINT_ERR("fopen a.txt error");// //2.fputc向文件中写入内容// fputc('h',fp);// fputc('e',fp);// fputc('l',fp);// fputc('l',fp);// fputc('o',fp);// //3.向终端输出内容// fputc('a',stdout);//'a'会输出到终端中,这种用法和putchar()完全一致//以读写的方式打开文件FILE *fp = fopen("./a.txt","r+");if(NULL == fp)PRINT_ERR("fopen a.txt error");//fputc向文件中写入内容fputc('h',fp); //如果文件中原本有内容,那么fputc会将光标后的字符给覆盖掉//光标问题:读写用的是同一个光标fgetc(fp);//光标向后偏移一个字节fputc('0',fp);//会将文件中第二个字符给覆盖掉return 0;
}
2.7. fgets函数
char *fgets(char *s, int size, FILE *stream);
功能:从文件中获取字符串,读取size-1个字节的数据
参数: s:保存获取到的字符串的首地址
size:要读取的字节数 stream:文件指针
返回值:成功返回读取到的字符串,失败或者读取到文件结尾返回NULL
fgets的使用
2.8. sprintf/snprintf/fprintf
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
功能:向文件中格式化输出内容
参数:stream:文件指针format:输出格式...:可变参
返回值:成功返回格式化输出的字符的个数,失败返回负数
int sprintf(char *str, const char *format, ...);
功能:向数组中格式化输出内容
参数:str:输出的目标地址format:输出格式...:可变参
返回值:成功返回格式化输出的字符的个数,失败返回负数
int snprintf(char *str, size_t size, const char *format, ...);
功能:向数组中格式化输出内容
参数:str:输出的目标地址size:格式化输出字符的个数format:输出格式...:可变参
返回值:成功返回格式化输出的字符的个数,失败返回负数
printf家族使用实例
#include <my_head.h> // 包含自定义头文件int main(int argc, const char* argv[])
{// 定义字符变量 var1 并初始化为 'a'char var1 = 'a';// 定义整数变量 var2 并初始化为 12345int var2 = 12345;// 定义字符数组 var3 并初始化为一个长字符串char var3[128] = {"12345121111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"};// 定义字符数组 buf 并初始化为 0char buf[128] = {0};// 1. 使用 sprintf 函数将格式化的字符串写入 buf// 注意: sprintf 不会检查缓冲区大小,可能导致数组越界// sprintf(buf, "var1 = %c var2 = %d var3 = %s", var1, var2, var3);// printf("buf = [%s]\n", buf);// 2. 使用 snprintf 函数将格式化的字符串写入 buf// snprintf 解决了数组越界问题,虽然有时会警告,但程序不会崩溃snprintf(buf, sizeof(buf), "var1 = %c var2 = %d var3 = %s", var1, var2, var3);printf("buf = [%s]\n", buf);// 3. 使用 fprintf 向文件中格式化输出内容FILE *fp = fopen("a.txt", "w"); // 以写模式打开文件 a.txtif (NULL == fp) // 检查文件是否成功打开PRINT_ERR("fopen error"); // 如果文件打开失败,打印错误信息fprintf(fp, "var1 = %c var2 = %d var3 = %s", var1, var2, var3); // 向文件写入格式化字符串fclose(fp); // 关闭文件return 0; // 返回 0 表示程序成功结束
}
2.9. fseek/ftell/rewind 光标相关函数
#include <stdio.h>
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);
功能:获取光标当前位置到开头的字节数
参数:文件指针
返回值:成功返回当前位置到开头的字节数,失败返回-1,置位错误码void rewind(FILE *stream);
功能:将光标偏移到文件开头
参数:文件指针
偏移光标实例
#include<my_head.h>int main(int argc,const char* argv[])
{//1.以读写的方式打开文件FILE *fp = fopen("./a.txt","r+");if(NULL == fp)PRINT_ERR("fopen error");char ch = 0;//2.从当前位置向后偏移光标fseek(fp,3,SEEK_CUR);ch = fgetc(fp);printf("ch = %c\n",ch);//3.从文件开头向后偏移fseek(fp,3,SEEK_SET);ch = fgetc(fp);printf("ch = %c\n",ch);//4.从文件结尾偏移光标fseek(fp,-3,SEEK_END);ch = fgetc(fp);printf("ch = %c\n",ch);//5.从文件结尾向后偏移int ret = fseek(fp,3,SEEK_END);printf("ret = %d\n",ret);//0,不会出错fputc('a',fp); //会在文件结尾后空出三个字节,然后再将a写入//6.从文件开头向前偏移ret = fseek(fp,-3,SEEK_SET);printf("ret = %d\n",ret);//-1,出错//ftell函数的使用fseek(fp,0,SEEK_END);int pos = ftell(fp);printf("pos = %d\n",pos); //如果光标位置在结尾,获取到的刚好是文件的大小//rewind函数rewind(fp);pos = ftell(fp);printf("pos = %d\n",pos);return 0;
}
3. 文件IO(系统调用)
3.1. 文件描述符
- 在文件IO中我们文件描述符去操作文件。
- 文件描述符本质就是一个非负的整数。
- 文件描述符的最大的限制是1024.
- 可以通过ulimit -a去查看
- 可以通过 ulimit -n 2048 修改文件描述符的最大个数
每个正在行的程序都有三个默认打开的文件描述符
- 标准输入:0
- 标准输出:1
- 标准出错:2
- 文件描述符的分配,遵循最小分配原则。
- 系统会把最小的没有被分配的文件描述符分配给你。
- 文件描述符如何操作文件?
3.2. open函数的使用
#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);
功能:打开文件
参数:pathname:文件的路径和名字 flags:打开文件的模式O_RDONLY:以只读的方式打开文件O_WRONLY:以只写的方式打开文件O_RDWR:以读写的方式打开文件 //以上三种打开文件的模式必须包含一个O_APPEND:以追加的方式打开文件O_CREAT:创建文件的标志如果flags中有O_CREAT这个参数,那么就需要使用到第三个参数O_TRUNC:清空文件的标志O_EXCL :以独占的方式打开文件必须配合O_CREAT使用加了这个参数之后,使用open创建文件的时候,如果文件存在,会返回文件已经存在的错误 错误码置为EEXISTmode:创建文件的权限如果第二个参数有O_CREAT那么就需要填充这个参数 比如:open("./a.txt",O_RDWR|O_CREAT|O_EXCL,0666)0666就表示我想要创建的文件的权限实际创建出来的文件的权限和umask相关mode & (~umask)umask:是创建文件的掩码umask查看的方式:umask 命令修改umask的方式: umask 掩码值mode 0666 110 110 110umask 0002 000 000 010实际: 0664 110 110 110mode 0666 110 110 110umask 0222 010 010 010实际: 0444 100 100 100
返回值:成功返回文件描述符,失败返回-1置位错误码文件描述符遵循最小分配原则open("./a.txt",O_RDWR|O_CREAT|O_EXCL)"r":O_RDONLY
"r+":O_RDWR
"w":O_WRONLY|O_CREAT|O_TRUNC
"w+":O_RDWR|O_CREAT|O_TRUNC
"a":O_APPEND|O_WRONLY|O_CREAT
"a+":O_APPEND|O_RDWR|O_CREAT
flags的原理
open使用实例
#include <my_head.h> // 包含自定义头文件int main(int argc, const char* argv[])
{int fd = 0;// 打开文件,只读的方式打开// fd = open("./a.txt", O_RDONLY);// if (-1 == fd)// PRINT_ERR("open file error");// 以"w+"的方式打开文件// fd = open("./a.txt", O_RDWR | O_CREAT | O_TRUNC, 0664);// if (-1 == fd)// PRINT_ERR("open file error");// 以独占的方式打开文件// fd = open("./a.txt", O_RDWR | O_CREAT | O_EXCL, 0664);// if (-1 == fd) {// if (errno == EEXIST) {// printf("文件已经存在\n");// // 发现文件存在,我换个名字,重新创建一下// fd = open("./b.txt", O_RDWR | O_CREAT | O_EXCL, 0664);// if (-1 == fd) {// printf("再次创建文件失败\n");// return -1;// }// } else {// printf("打开失败\n");// }// }// 文件描述符遵循最小分配原则int fd1, fd2, fd3;// 以只读方式打开文件 a.txtfd1 = open("./a.txt", O_RDONLY);if (-1 == fd1) {PRINT_ERR("open file error");return -1;}printf("fd1 = %d\n", fd1); // 打印文件描述符 fd1 的值// 以只读方式打开文件 b.txtfd2 = open("./b.txt", O_RDONLY);if (-1 == fd2) {PRINT_ERR("open file error");return -1;}printf("fd2 = %d\n", fd2); // 打印文件描述符 fd2 的值// 关闭文件描述符 fd1close(fd1);printf("fd1 已关闭\n");// 再次以只读方式打开文件 b.txtfd3 = open("./b.txt", O_RDONLY);if (-1 == fd3) {PRINT_ERR("open file error");return -1;}printf("fd3 = %d\n", fd3); // 打印文件描述符 fd3 的值return 0; // 返回 0 表示程序成功结束
}
3.3. close函数
#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:fd:文件描述符
返回值:成功返回0,失败返回-1,置位错误码
#include<my_head.h>int main(int argc,const char* argv[])
{int var1 = 0;scanf("%d",&var1);printf("var1 = %d\n",var1);close(0);//关闭标准输入scanf("%d",&var1);printf("var1 = %d\n",var1);printf("111111111111\n");close(stdout->_fileno);//关闭标准输出printf("22222222222222\n");perror("msg");close(2); //关闭标准出错perror("msg");return 0;
}
3.4. read/write函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取count个字节到buf中
参数:fd:文件描述符buf:保存读取到的数据的首地址count:要读取的字节数
返回值:成功返回实际读取到的字节数,如果读取到文件结尾,返回0,失败返回-1.置位错误码
ssize_t write(int fd, const void *buf, size_t count);
功能:将buf中的数据,向文件中写入count个字节
参数:fd:文件描述符buf:保存我们要写入的数据count:想要写入的字节数
返回值:成功返回实际写入的字节数,失败返回-1,置位错误码
read使用实例
#include <my_head.h> // 包含自定义头文件int main(int argc, const char* argv[])
{// 1. 打开文件// 以只读方式打开文件 a.txt,如果文件不存在则创建,权限设置为 0664int fd = open("./a.txt", O_RDONLY | O_CREAT, 0664);if (-1 == fd) {PRINT_ERR("open error"); // 如果打开文件失败,打印错误信息return -1; // 返回 -1 表示程序失败}// 2. 从文件中读取字符串char buf[128] = {0}; // 定义一个长度为 128 的字符数组 buf,并初始化为 0int ret = 0; // 定义一个整数变量 ret 用于存储 read 函数的返回值// 使用 read 函数从文件描述符 fd 中读取数据到 buf 中,最多读取 sizeof(buf) 字节ret = read(fd, buf, sizeof(buf));printf("buf = [%s]\n", buf); // 打印读取到的字符串printf("ret = %d\n", ret); // 打印 read 函数的返回值,表示实际读取的字节数// 再次从文件中读取数据// 由于文件指针已经到达文件末尾,再次读取会返回 0ret = read(fd, buf, sizeof(buf));printf("ret = %d\n", ret); // 打印 read 函数的返回值,应为 0// 关闭文件描述符close(fd);return 0; // 返回 0 表示程序成功结束
}
write函数使用实例
#include <my_head.h> // 包含自定义头文件
#include <string.h> // 包含字符串处理函数的头文件int main(int argc, const char* argv[])
{// 1. 打开文件// 以读写方式打开文件 a.txtint fd = open("./a.txt", O_RDWR);if (-1 == fd) {PRINT_ERR("open error"); // 如果打开文件失败,打印错误信息return -1; // 返回 -1 表示程序失败}// 2. 向文件中写入内容, 写入整型int a = 100;// 使用 write 函数将整型变量 a 写入文件write(fd, &a, sizeof(a));// 3. 向文件中写入字符串char buf[128] = {"hello world"};// write(fd, buf, sizeof(buf)); // 这会将数组中所有的数据写进文件中,包括0// 使用 write 函数将字符串 buf 写入文件,只写入字符串的实际长度int ret = write(fd, buf, strlen(buf));printf("ret = %d\n", ret); // 打印 write 函数的返回值,表示实际写入的字节数// 关闭文件描述符close(fd);return 0; // 返回 0 表示程序成功结束
}
3.5. lseek函数
#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:从文件结尾开始偏移
返回值:成功返回当前光标位置到文件开头的距离,失败返回(off_t) -1置位错误码
lseek使用实例
#include <my_head.h>// 定义一个 RGB 结构体,用于表示颜色的 RGB 值
typedef struct RGB
{unsigned char b; // 蓝色通道的值unsigned char g; // 绿色通道的值unsigned char r; // 红色通道的值// unsigned char a;//如果位深是32,需要用到这个成员,是透明度
} rgb_t;/*** @brief 主函数,程序的入口点* * 该函数用于打开一个 BMP 图片文件,读取其头部信息,* 并在图片的左上角绘制一个 500x300 的彩色矩形。* * @param argc 命令行参数的数量* @param argv 命令行参数的数组* @return int 程序的退出状态码,0 表示正常退出*/
int main(int argc, const char *argv[])
{// 1.打开图片文件int fd = open("./ddm.bmp", O_RDWR); // 以读写模式打开指定的 BMP 文件if (-1 == fd)PRINT_ERR("fopen error"); // 如果打开文件失败,打印错误信息// 读取图片类型 BMchar bftype[3] = {0}; // 用于存储 BMP 文件的类型信息,以字符串形式存储read(fd, bftype, 2); // 从文件中读取 2 个字节的类型信息printf("bftype = %s\n", bftype); // 打印 BMP 文件的类型信息// 读取图片大小 622054int bfSize = 0; // 用于存储 BMP 文件的大小read(fd, &bfSize, 4); // 从文件中读取 4 个字节的文件大小信息printf("bfsize = %d\n", bfSize); // 打印 BMP 文件的大小// 读取图片头的大小,54int head_size = 0; // 用于存储 BMP 文件头的大小lseek(fd, 0xA, SEEK_SET); // 将文件指针移动到文件头中存储文件头大小的位置read(fd, &head_size, 4); // 从文件中读取 4 个字节的文件头大小信息printf("head_size = %d\n", head_size); // 打印 BMP 文件头的大小// 读取图片的宽度 1920int width = 0; // 用于存储 BMP 图片的宽度lseek(fd, 0x12, SEEK_SET); // 将文件指针移动到文件头中存储图片宽度的位置read(fd, &width, 4); // 从文件中读取 4 个字节的图片宽度信息printf("width = %d\n", width); // 打印 BMP 图片的宽度// 读取图片的高度 1080int high = 0; // 用于存储 BMP 图片的高度read(fd, &high, 4); // 从文件中读取 4 个字节的图片高度信息printf("high = %d\n", high); // 打印 BMP 图片的高度// 读取图片的位深int bitcount = 0; // 用于存储 BMP 图片的位深lseek(fd, 0x1C, SEEK_SET); // 将文件指针移动到文件头中存储图片位深的位置read(fd, &bitcount, 4); // 从文件中读取 4 个字节的图片位深信息printf("bitcount = %d\n", bitcount); // 打印 BMP 图片的位深// 修改图像数据int ret = lseek(fd, 54, SEEK_SET); // 偏移光标到像素数据之前printf("ret = %d\n", ret); // 打印文件指针的偏移量rgb_t color = {227, 22, 233}; // 定义一个 RGB 颜色值,用于绘制矩形// 在图片的左上角绘制一个 500x300 的彩色矩形for (int j = 0; j < 300; j++){for (int i = 0; i < 500; i++){write(fd, &color, sizeof(rgb_t)); // 将指定的颜色值写入文件}lseek(fd, (1920 - 500) * 3, SEEK_CUR); // 跳过当前行剩余的像素数据}close(fd); // 关闭文件return 0;
}
3.6. stat函数
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
功能:获取文件信息
参数:pathname:文件的路径和名字statbuf:保存文件信息的结构体的首地址
返回值:成功返回0,失败返回-1,置位错误码struct stat {dev_t st_dev; /* 文件所在磁盘的设备号 */ino_t st_ino; /* 文件的inode号 */mode_t st_mode; /* 文件的类型和权限 */ nlink_t st_nlink; /* 硬连接数,对于目录文件来说,这个数值是目录下子目录的个数*/uid_t st_uid; /* 文件所属用户的用户id */gid_t st_gid; /* 文件所属组的组id */dev_t st_rdev; /* 如果文件是设备文件,那么该字段为设备文件的设备id */off_t st_size; /* 文件的大小 */blksize_t st_blksize; /* 文件系统一页内存的大小*/blkcnt_t st_blocks; /* 文件所占块儿的数量*/struct timespec st_atim; /* 上次访问文件的时间 */struct timespec st_mtim; /* 上次修改文件的时间 */struct timespec st_ctim; /*上次文件状态改变的时间 */
};对于st_mode字段的解释:
mode_t st_mode;
st_mode :12~15bit位表示的是文件的类型
st_mode: 0~8个bit位表示的是文件的权限
获取文件类型:S_IFMT 0170000 文件类型的掩码S_IFSOCK 0140000 套接字文件S_IFLNK 0120000 链接文件S_IFREG 0100000 普通文件S_IFBLK 0060000 块设备文件S_IFDIR 0040000 目录文件S_IFCHR 0020000 字符设备文件S_IFIFO 0010000 管道文件stat(pathname, &sb);if ((sb.st_mode & S_IFMT) == S_IFREG) {/* Handle regular file */}也可以通过如下宏定义去判断文件类型:如果文件类型匹配,下列的宏定义会返回1;S_ISREG(m) is it a regular file?S_ISDIR(m) directory?S_ISCHR(m) character device?S_ISBLK(m) block device?S_ISFIFO(m) FIFO (named pipe)?S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
stat(pathname, &sb);if (S_ISREG(sb.st_mode)) {/* Handle regular file */} 文件权限的获取:st_mode & 0777 即可获取到文件的所有权限信息如果想要查看系统提供的宏定义,可以 man 7 inode
#include <my_head.h>/*** @brief 主函数,程序入口,用于获取并输出文件的属性信息* * 该函数接收一个命令行参数作为文件名,通过 `stat` 函数获取文件的属性信息,* 然后输出文件的基本信息、访问/修改/状态改变时间、文件类型和权限。* * @param argc 命令行参数的数量* @param argv 命令行参数数组,其中 argv[1] 应为要查询的文件名* @return int 程序的退出状态码,0 表示成功,-1 表示入参错误*/
int main(int argc, const char *argv[])
{// 检查命令行参数数量是否正确if (argc != 2){// 若参数数量不对,输出错误信息printf("入参错误\n");// 提示正确的使用方式printf("Usage:./a.out filename\n");return -1;}// 1.获取文件属性信息// 定义一个 stat 结构体变量,用于存储文件的属性信息struct stat st;// 调用 stat 函数获取指定文件的属性信息stat(argv[1], &st);// 2.输出文件信息// 输出文件的 i 节点号、用户 ID、文件大小、块大小、占用块数printf("ino:%ld,uid:%d,size:%ld,blksize:%ld,blks:%ld\n",st.st_ino, st.st_uid, st.st_size, st.st_blksize, st.st_blocks);// 输出文件上次被访问的时间printf("上次访问文件的时间%ld\n", st.st_atime);// 输出文件内容上次被修改的时间printf("上次修改文件的时间%ld\n", st.st_mtime);// 输出文件状态(如权限、所有者等)上次改变的时间printf("上次文件状态改变的时间%ld\n", st.st_ctime);// 3.输出文件的类型和权限// 根据文件的模式位判断文件类型switch (st.st_mode & __S_IFMT){case __S_IFSOCK:// 如果是套接字文件,输出相应信息printf("是套接字文件\n");break;case __S_IFLNK:// 如果是符号链接文件,输出相应信息printf("是链接文件\n");break;case __S_IFREG:// 如果是普通文件,输出相应信息printf("是普通文件\n");break;case __S_IFBLK:// 如果是块设备文件,输出相应信息printf("是块设备文件\n");break;case __S_IFDIR:// 如果是目录文件,输出相应信息printf("是目录文件\n");break;case __S_IFCHR:// 如果是字符设备文件,输出相应信息printf("是字符设备文件\n");break;case __S_IFIFO:// 如果是命名管道文件,输出相应信息printf("是管道文件\n");break;}// 输出文件的权限,以八进制形式显示printf("mode = %#o\n", st.st_mode & 0777);return 0;
}
3.7. getpwuid,getgrgid函数的使用
#include <my_head.h>int main(int argc, const char *argv[])
{// 1.获取用户的uiduid_t uid = getuid();// 2.根据uid获取用户信息struct passwd *usr_info = NULL;usr_info = getpwuid(uid);if(NULL == usr_info)PRINT_ERR("getpwuid error");printf("用户名:%s 用户密码:%s uid:%d gid:%d 用户描述:%s 家目录:%s 命令行解释器:%s \n",\usr_info->pw_name,usr_info->pw_passwd,usr_info->pw_uid,usr_info->pw_gid,\usr_info->pw_gecos,usr_info->pw_dir,usr_info->pw_shell);return 0;
}
#include<my_head.h>int main(int argc,const char* argv[])
{//1.获取组idgid_t gid = getgid();//2.通过组id获取组信息struct group * gr_info = NULL;gr_info = getgrgid(gid);if(NULL == gr_info)PRINT_ERR("getgrgid error");printf("组名:%s 组密码:%s 组id:%d \n",\gr_info->gr_name,gr_info->gr_passwd,gr_info->gr_gid);return 0;
}
3.8. 目录操作
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开目录
参数:name:目录的路径和名字
返回值:成功返回目录指针,失败返回NULL,置位错误码#include <dirent.h>
struct dirent *readdir(DIR *dirp);
功能:从目录中读取内容,每读取一次,返回一个保存文件信息的结构体指针
参数:dirp:opendir返回的目录指针
返回值:成功返回一个结构体指针,这个结构体指针指向的结构体中包含了目录中某个文件的相关信息失败:返回NULL,置位错误码如果读取到文件结尾,返回NULL
struct dirent {ino_t d_ino; /* 文件的inode号*/off_t d_off; /* 不需要关注 */unsigned short d_reclen; /* 这条记录的长度 */unsigned char d_type; /*
文件的类型 */DT_BLK 块儿设备文件.
DT_CHR 字符设备文件.
DT_DIR 目录文件.
DT_FIFO 管道文件.
DT_LNK 链接文件.
DT_REG 普通文件.
DT_SOCK 套接字文件.
DT_UNKNOWN 未知的文件类型char d_name[256]; /* 文件的名字 */};#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录指针
返回值:成功返回0,失败返回-1,置位错误码
4.库的制作
4.1. 库的概念
库就是将.c文件编程生成的二进制文件。当我们需要使用的时候,直接将库文件链接到我们的可执行程序中即可。
windows:
- xxx.lib -- 静态库
- xxx.dll -- 动态库
linux:
- libxxx.a -- 静态库
- libxxx.so -- 动态库
4.2. 静态库
4.2.1. 静态库的概念
- 静态库的使用,需要将整个库文件编译到我们的可执行程序中去。
- 使用静态库的可执行程序,体积较大。
- 使用静态库的可执行程序,执行效率更高。
- 使用静态库的可执行程序,运行的时候,不需要依赖库文件。
- 静态库的更新比较麻烦。
4.2.2. 静态库的制作
1.编译生成.o文件
gcc -c xxx1.c xxx2.c xxx3.c
2.将.o文件制作生成库文件
ar -cr libxxx.a xxx1.o xxx2.o xxx3.o
ar:制作静态库的命令
c:create
r:replace
上面的命令就是制作生成了静态库,将.o文件放入了库中
4.2.3. 静态库的使用
1.直接编译
gcc main.c libxxx.a -o main
2.更加标准的编译方式
gcc main.c -lxxx -L 库的路径
-l表示链接库,xxx表示库的名字
-L 指明库的路径
main.c
#include<my_head.h>
#include"add.h"
int main(int argc,const char* argv[])
{ int sum = 0; sum =add_func(10,20); printf("sum = %d\n",sum); return 0;
}
add.c
int add_func(int x,int y){ return x+y; }
add.h
#ifndef __ADD_H__ #define __ADD_H__ int add_func(int x,int y); #endif
编译和使用的命令
4.3. 动态库(共享库)
4.3.1. 动态库的概念
- 动态库的使用,只会将库中函数的符号表编译到可执行程序中。
- 使用动态库的可执行程序,体积较小。
- 使用动态库的可执行程序执行效率会低一些。
- 使用动态库的执行程序,运行的时候,需要依赖库文件。
- 动态库的更新比较方便。
4.3.2.动态库的制作
方式1:
- gcc -c -fPIC xxx.c -o xxx.o //编译生成.o文件
- gcc -shared xxx.o -o libxxx.so
- //-fPIC:表示忽略文件位置(赋予动态库共享的特性)
- //-shared 制作动态库的命令
方式2:直接编译生成库文件
gcc -shared -fPIC xxx.c -o libxxx.so
4.3.3. 动态库的使用
- gcc main.c -lxxx -L 库的路径
- -l表示链接库,xxx表示库的名字
- -L 指明库的路径