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
cpp
struct _IO_FILE
{
char *_IO_buf_base; /* 缓冲区的起始地址 */
char *_IO_buf_end; /* 缓冲区的结束地址 */
int _fileno; /*文件描述符*/
};
2.2. fopen函数
cpp
#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使用实例
cpp
#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函数
cpp
#include <stdio.h> int fclose(FILE *stream);
功能:关闭FILE指针
参数: stream:通过fopen获取到的文件指针
返回值:成功返回0,失败返回EOF,置位错误码
fclose代码实例
cpp
#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
cpp
#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)
cpp
#include <string.h>
char *strerror(int errnum);
功能:将errnum转换为对应的错误信息
参数:
errnum:错误码
返回值:错误码对应的错误信息,失败返回"Unknown error nnn"
2.4.5. 直接输出错误信息
cpp
#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函数
cpp
#include <stdio.h>
int fgetc(FILE *stream);
功能:从文件中读取一个字符,以返回值的形式返回
参数: stream:文件指针
返回值:成功返回获取到的字符,失败或者读取到文件结尾返回EOF
fgetc使用实例
cpp
#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函数
cpp
#include <stdio.h>
int fputc(int c, FILE *stream);
功能:向文件中写入一个字符
参数: c:要写入的字符 stream:文件指针
返回值:成功返回写入的字符的ascii码,失败返回EOF
fputc使用实例
cpp
#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函数
cpp
char *fgets(char *s, int size, FILE *stream);
功能:从文件中获取字符串,读取size-1个字节的数据
参数: s:保存获取到的字符串的首地址
size:要读取的字节数 stream:文件指针
返回值:成功返回读取到的字符串,失败或者读取到文件结尾返回NULL
fgets的使用

2.8. sprintf/snprintf/fprintf
cpp
#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家族使用实例
cpp
#include <my_head.h> // 包含自定义头文件
int main(int argc, const char* argv[])
{
// 定义字符变量 var1 并初始化为 'a'
char var1 = 'a';
// 定义整数变量 var2 并初始化为 12345
int var2 = 12345;
// 定义字符数组 var3 并初始化为一个长字符串
char var3[128] = {"12345121111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"};
// 定义字符数组 buf 并初始化为 0
char 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.txt
if (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 光标相关函数
cpp
#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);
功能:将光标偏移到文件开头
参数:文件指针
偏移光标实例
cpp
#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函数的使用
cpp
#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创建文件的时候,如果文件存在,会返回文件已经存在的错误 错误码置为EEXIST
mode:创建文件的权限
如果第二个参数有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 110
umask 0002 000 000 010
实际: 0664 110 110 110
mode 0666 110 110 110
umask 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使用实例
cpp
#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.txt
fd1 = open("./a.txt", O_RDONLY);
if (-1 == fd1)
{
PRINT_ERR("open file error");
return -1;
}
printf("fd1 = %d\n", fd1); // 打印文件描述符 fd1 的值
// 以只读方式打开文件 b.txt
fd2 = open("./b.txt", O_RDONLY);
if (-1 == fd2) {
PRINT_ERR("open file error");
return -1;
}
printf("fd2 = %d\n", fd2); // 打印文件描述符 fd2 的值
// 关闭文件描述符 fd1
close(fd1);
printf("fd1 已关闭\n");
// 再次以只读方式打开文件 b.txt
fd3 = 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函数
cpp
#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:
fd:文件描述符
返回值:成功返回0,失败返回-1,置位错误码
cpp
#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函数
cpp
#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使用实例
cpp
#include <my_head.h> // 包含自定义头文件
int main(int argc, const char* argv[])
{
// 1. 打开文件
// 以只读方式打开文件 a.txt,如果文件不存在则创建,权限设置为 0664
int 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,并初始化为 0
int 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 函数的返回值,表示实际读取的字节数
// 再次从文件中读取数据
// 由于文件指针已经到达文件末尾,再次读取会返回 0
ret = read(fd, buf, sizeof(buf));
printf("ret = %d\n", ret); // 打印 read 函数的返回值,应为 0
// 关闭文件描述符
close(fd);
return 0; // 返回 0 表示程序成功结束
}
write函数使用实例
cpp
#include <my_head.h> // 包含自定义头文件
#include <string.h> // 包含字符串处理函数的头文件
int main(int argc, const char* argv[])
{
// 1. 打开文件
// 以读写方式打开文件 a.txt
int 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函数
cpp
#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使用实例
cpp
#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"); // 如果打开文件失败,打印错误信息
// 读取图片类型 BM
char bftype[3] = {0}; // 用于存储 BMP 文件的类型信息,以字符串形式存储
read(fd, bftype, 2); // 从文件中读取 2 个字节的类型信息
printf("bftype = %s\n", bftype); // 打印 BMP 文件的类型信息
// 读取图片大小 622054
int bfSize = 0; // 用于存储 BMP 文件的大小
read(fd, &bfSize, 4); // 从文件中读取 4 个字节的文件大小信息
printf("bfsize = %d\n", bfSize); // 打印 BMP 文件的大小
// 读取图片头的大小,54
int head_size = 0; // 用于存储 BMP 文件头的大小
lseek(fd, 0xA, SEEK_SET); // 将文件指针移动到文件头中存储文件头大小的位置
read(fd, &head_size, 4); // 从文件中读取 4 个字节的文件头大小信息
printf("head_size = %d\n", head_size); // 打印 BMP 文件头的大小
// 读取图片的宽度 1920
int width = 0; // 用于存储 BMP 图片的宽度
lseek(fd, 0x12, SEEK_SET); // 将文件指针移动到文件头中存储图片宽度的位置
read(fd, &width, 4); // 从文件中读取 4 个字节的图片宽度信息
printf("width = %d\n", width); // 打印 BMP 图片的宽度
// 读取图片的高度 1080
int 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函数
cpp
#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
cpp
#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函数的使用
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
// 1.获取用户的uid
uid_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;
}
cpp
#include<my_head.h>
int main(int argc,const char* argv[])
{
//1.获取组id
gid_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. 目录操作
cpp
#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
cpp
#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 指明库的路径