Linux IO编程 搭建开发环境 学习笔记
虚拟机网络模式
配置 Ubuntu 联网时,稳定的网络连接是基础前提!
虚拟机里的这些网络模式(桥接、NAT、仅主机、自定义、LAN段),决定了 Ubuntu 虚拟机如何跟主机、外部网络打通;选对模式,既能让 Ubuntu 联网装软件,又能让主机顺利实现远程;

| 模式 | 核心特点 | 外网访问能力 | 与主机通信能力 | 典型应用场景 |
|---|---|---|---|---|
| 桥接模式 | 虚拟机与主机共享物理网卡,获取同网段 IP | 可直接访问外网 | 同一网段可双向通信 | 需对外提供服务时用,如搭建 Web 服务器、与其他设备互相访问等 |
| NAT 模式 | 虚拟机用私有 IP,流量经主机 NAT 转发 | 可访问外网(依赖主机 NAT 转发) | 天然互通(无需额外配置) | 开发首选,如快速联网装环境、主机直连 SSH 等 |
| 仅主机模式 | 虚拟机连封闭虚拟网卡 | 与外网完全隔离,无法访问外网 | 同一虚拟网络可双向通信 | 本地隔离测试,如单机服务调试、数据不对外的开发环境等 |
| 自定义模式 | 指定 VMnet1/8/0,灵活配网络 | 取决于所选虚拟网络 | 取决于所选虚拟网络 | 复杂场景,如多虚拟网卡协同、特定协议模拟等 |
| LAN 区段 | 多虚拟机连专属虚拟网卡,区段外无法访问 | 默认无法访问外网 | 默认无法通信 | 模拟复杂网络,如多虚拟机模拟服务器集群,测试内部通信等 |
日常开发(尤其是个人/常规项目开发)中,NAT 模式和桥接模式因适配开发需求、配置简单,使用频率远高于其他模式;仅主机、自定义、LAN 区段模式受场景限制,很少在常规开发流程中用到;
| 对比维度 | NAT 模式 | 桥接模式 |
|---|---|---|
| 网络原理 | 虚拟机借主机网络“转发”上网(类似连热点) | 虚拟机与主机平级连物理网络(类似插网线) |
| IP配置 | 自动分配,无需手动 | 需手动设同网段 IP(易冲突) |
| 联网复杂度 | 选模式即联网,零配置 | 需配 IP / 网关 / DNS,易出错 |
| VS Code远程连接 | 天然互通,填 IP 直连 | 需确保 IP 同网段且不冲突 |
| 开发适配场景 | 首选!日常写代码、装环境、远程开发 | 需对外提供服务(如虚拟机搭网站供手机访问) |
| 安全隔离性 | 虚拟机藏于主机内网,防外部干扰 | 暴露在物理网络,易被局域网设备访问 |
NAT网段计算
在 VMware 虚拟机环境中,NAT 模式的网段由宿主机虚拟网卡(VMnet8)的网络配置决定;
NAT网段计算:
• 定位虚拟网卡:查看 Windows 主机中 VMware Network Adapter VMnet8 的网络属性;
• 获取关键参数:获取 IP 地址及子网掩码;
• 计算网段地址:网段 = 虚拟网卡IP 按位与(&) 子网掩码
以 192.168.159.1/24 网段为例(子网掩码 255.255.255.0),整个网段的 IP 范围为 192.168.159.0 ~192.168.159.255 ,但需严格区分可用主机地址与特殊地址:
• 可用主机地址:192.168.159.3 ~ 192.168.159.254(共252个可用地址),这些地址未被系统占用,可安全分配给虚拟机作为静态 IP;
• 特殊地址:特殊地址不可进行分配;
| 地址 | 用途说明 | 能否给虚拟机(Ubuntu)用 |
|---|---|---|
| 192.168.159.0 | 网段标识地址 | 不能(纯标识网段用) |
| 192.168.159.1 | 宿主机虚拟网卡地址(VMnet8) | 不能(已被主机占用,冲突必断网) |
| 192.168.159.2 | NAT 网关地址(转发流量用) | 不能(已被系统占用,不能分配) |
| 192.168.159.255 | 广播地址(给全网段发消息) | 不能(用了会导致网段内设备混乱) |
配置 Ubuntu NAT 联网流程
确认Windows环境 NAT 网段:在Windows操作系统中,借助VMware虚拟网络适配器(VMnet8)相关设置,查询并记录 NAT 模式下的网段信息(含 IP 地址、子网掩码等),为后续Ubuntu配置提供网络参数依据;
Ubuntu系统网络配置:
• 选定网络模式:将 Ubuntu 网络连接模式指定为 NAT 模式,使虚拟机可借助主机网络转发机制访问外部网络;
• 配置静态IP地址:需确保配置的静态IP地址处于 Windows 端得到的 NAT 网段可用范围内,保证网络通信正常;
• 验证静态 IP 配置:通过查看网络信息指令(ip addr),检查静态 IP 地址是否生效、网络参数是否正确等等,确保 Ubuntu 可正常上网;
配置Ubuntu SSH免密登录流程
更新软件源:
同步系统软件源信息,确保后续安装软件版本最新;
sudo apt update
SSH服务部署:
• 安装 OpenSSH 服务:为远程连接提供基础支持;
sudo apt install openssh-server
• 启动 SSH 服务:启动服务,使 Ubuntu 具备接收 SSH 连接的能力;
sudo systemctl start ssh
• 设置开机自启动:配置服务开机自动启动,避免系统重启后需手动启动;
sudo systemctl enable ssh
• 验证服务状态:检查服务运行状态,确保服务正确启动(状态显示active(running)为正常);
sudo systemctl status ssh
生成密钥对:
分别在 Windows 和 Ubuntu 系统生成 SSH 密钥对(用于加密认证);
• Windows 端:打开命令提示符,执行以下命令生成密钥对;
ssh-keygen
• Ubuntu 端:打开终端,执行以下命令生成密钥对;
ssh-keygen
公钥互信配置:
• 新建授权文件( Ubuntu 端):用于存储公钥,文件名固定;
touch ~/.ssh/authorized_keys
• 追加 Windows 公钥:Windows 系统生成的公钥文件,粘贴到 Ubuntu 系统的新建授权文件中;
• 追加 Ubuntu 公钥:将自身公钥也追加到授权文件,实现双向免密(可选,按需配置);
网络连通性验证与远程登录:
• ping 测试:在 Windows 命令提示符中,执行以下命令测试与 Ubuntu 系统的网络连通性;
ping Ubuntu的IP地址
• SSH 远程登录:若网络连通,执行以下命令通过 SSH 登录 Ubuntu 系统;
首次登录需输入 yes 确认,完成认证后即可进入 Ubuntu 主目录,实现免密远程访问;
ssh Ubuntu用户名@Ubuntu的IP地址
linux操作系统分层结构


IO介绍
IO概述:IO(Input/Output)是计算机系统中数据在不同实体间传输交互的抽象过程,涵盖程序与外部设备(如文件、硬件、网络)的数据读写行为。本质上,它是程序与外部环境建立“数据通道”的核心机制,支撑着程序获取输入、输出结果的基本逻辑,是软件与硬件、本地与远程交互的底层依托;
IO作用:IO 是程序与外部环境交互的基础桥梁。一方面,它为程序提供 “输入” 能力,支撑程序从文件、硬件、网络获取运行所需的数据(如游戏加载资源文件、程序读取用户输入);另一方面,通过 “输出” 功能,程序可将计算结果持久化存储(如写入日志文件)、可视化展示(如输出到终端)或远程传输(如网络数据发送);
IO种类:可从实现层级和应用场景两个维度分类
• 按实现层级分类:
• 系统调用IO:也称之为文件IO,直接对接操作系统内核的最底层接口;逻辑极简、性能接近硬件,但是需要手动处理缓冲区、错误码等细节;
• 库函数IO:也称之为标准IO,基于系统调用封装的高层接口;自动管理缓冲区、跨平台兼容、简化开发,是用户态程序的常用选择;
按应用场景分类:
• 文件 IO:针对磁盘文件的读写(如日志记录、配置文件操作).可通过“系统调用”或“库函数”实现;
• 设备 IO:与硬件设备交互(如键盘输入、显示器输出).操作系统通过“设备抽象”将设备转换为文件系统中的特殊文件,使程序能用统一的文件操作接口访问所有硬件,这正是 Linux“一切皆文件”理念的核心;
• 网络 IO:实现网络数据收发(如浏览器访问网页、APP通讯).依赖系统调用或者是更高层的框架;
| 对比维度 | 系统调用 | 库函数 |
|---|---|---|
| 层级定位 | 操作系统内核提供的底层接口(如 open) |
基于系统调用 / 纯用户逻辑封装的高层工具(如 C 标准库 fopen) |
| 运行空间 | 运行于内核空间,需切换 CPU 特权级(用户态 → 内核态) | 运行于用户空间,无需特权级切换 |
| 功能本质 | 直接操作硬件 / 内核资源(如读写磁盘、创建进程) | ① 封装系统调用实现复杂功能(如 printf 调用 write)② 纯用户态逻辑(如 strlen 计算字符串长度) |
| 性能开销 | 因“用户态 ↔ 内核态”切换,开销较大 | ① 若封装系统调用,开销略低(减少切换次数) ② 纯用户态函数开销极小 |
| 移植性 | 依赖操作系统内核,移植性差(如 Linux 与 Windows 系统调用不同) | 跨平台库(如 C 标准库),移植性好(同一函数适配多系统) |
标准IO介绍
定义:标准 I/O 是 C 库提供的输入输出函数集合,由 ANSI C 标准统一规范,为跨平台 IO 操作奠定基础;
跨平台性:在UNIX 系统、Windows系统、Linux 等多系统操作均实现标准 I/O 库,让代码跨平台开发更顺畅;
高效实现原理:通过缓冲机制(内存临时存储数据),减少直接系统调用次数,降低硬件交互开销,提升 IO效率;
易用性设计:基于系统底层调用封装,屏蔽复杂硬件操作,开发者无需关注设备细节,简单调用函数即可完成 IO;

流(stream)
定义:所有 I/O 操作抽象为字节的“移入/移出”程序的过程,这种字节流动形态,称为流。
分类:
• 文件流:按字符编码(如 ASCII、UTF-8)处理,注重文本语义;
• 二进制流:直接操作原始字节,保留数据原始形态(如图片、可执行文件);
默认打开的流:
• stdin:标准输入流(如键盘输入)
• stdout:标准输出流(如屏幕打印)
• stderr:标准错误流(专用于输出错误信息)
FILE对象
定义:FILE 对象是一个结构体,用于存储文件或流相关的所有状态信息(如文件描述符、缓冲区大小、缓冲区当前位置、文件结束标志等等);
FILE对象与流对应关系:
• 每个流在内存中由一个 FILE 对象表示,通过 FILE* 指针访问;
标准流的特殊 FILE 对象:
• stdin:是标准输入流对应的 FILE 对象指针,通常关联到键盘输入;
• stdout:是标准输出流对应的 FILE 对象指针,通常关联到控制台输出;
• stderr:是标准错误流对应的 FILE 对象指针。通常关联到控制台,用于输出错误信息;
由于 FILE 对象通常较大且内部结构复杂,标准 I/O 函数不直接操作FILE对象本身,而是通过FILE 指针(FILE*)来间接访问。FILE 指针是指向 FILE 对象的内存地址,所有文件操作(如打开、读写、关闭)都基于这个指针进行;
FILE指针创建与销毁:通过fopen( )进行创建FILE指针,通过fclose( )进行销毁FILE指针;
FILE指针操作:所有标准 I/O 函数都以 FILE 指针作为参数,常见操作包括读写操作、定位操作、错误检查等等;
FILE 对象与底层系统调用的关系:FILE 对象是标准库层面的抽象,而 Linux 内核通过文件描述符(如 0、1、2 对应标准输入 / 输出 / 错误)管理文件。FILE 对象内部维护了一个文件描述符,将标准 I/O 函数映射到底层系统调用(如 read()、write()),并提供缓冲区优化;
系统头文件tags生成及vim验证
为系统头文件(/usr/include)生成tags索引,让vim可快速跳转、检索头文件中的函数、类型、宏等;
man手册使用方式
man手册是用于在linux系统中查询命令、函数等的帮助手册,不同的章节分类便于快速定位获取各类linux系统相关的帮助信息;
以下是一些常用用法:
• man man:查看man手册自身的章节说明,了解man手册整体架构和使用规范;
• man 1 要查看的内容:查看可执行程序或shell命令的帮助文档,“1”对应用户命令章节;
• man 2 要查看的内容:查看系统调用(内核提供的函数)相关帮助,“2”对应系统调用章节;
• man 3 要查看的内容:查看库调用(库中的函数),“3”对应库函数章节;
......
fopen函数
| 项目 | 详细说明 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | FILE *fopen(const char *pathname, const char *mode); |
|
| 功能 | 用标准 I/O 的方式打开文件 | |
| 参数 | pathname: | 文件路径和名字。不写路径默认打开当前路径下的文件。 |
| mode: | 打开文件的方式 - r:只读,光标定位到文件开头,文件不存在则报错。- r+:读写,光标定位到文件开头,文件不存在则报错。- w:只写,文件存在则清空,不存在则创建,光标定位到开头。- w+:读写,文件存在则清空,不存在则创建,光标定位到开头。- a:追加(只写),文件不存在则创建,存在则不清空,光标定位到结尾。- a+:读和追加,文件不存在则创建,存在则不清空;读操作时光标在文件开头,写操作总是在文件结尾。 |
|
| 返回值 | 成功:返回 FILE 指针失败:返回 NULL,并设置错误码 |
|
fclose函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fclose(FILE *stream); |
|
| 功能 | 用标准IO的方式关闭文件 | |
| 参数 | stream |
文件指针 |
| 返回值 | 成功:返回 0 |
|
失败:返回 EOF(-1),并重置错误码 |
||
错误码的原理
在Linux系统中,内核通过预定义宏(如 ENOENT)标识错误,错误码为正整数,内核返回时用负数传递;

strerror函数-错误码转换为错误信息
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <string.h> |
|
| 原型 | char *strerror(int errnum); |
|
| 功能 | 将错误码转换为错误信息 | |
| 参数 | errnum |
错误码 |
| 返回值 | 成功:返回指向错误信息的字符串指针 | |
若 errnum 无效:返回结果由系统实现决定(通常返回通用错误描述,如 "Unknown error nnn") |
||
perror函数-错误信息打印函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | void perror(const char *s); |
|
| 功能 | 打印错误信息。如果 s 不为 NULL 且 *s 不为空字节('\0'),则先打印参数字符串 s,后跟一个冒号和一个空格;然后打印与当前 errno 值对应的错误消息和一个换行符。 |
|
| 参数 | s |
用户的附加信息。 • 若 s 非空(s != NULL 且 *s != '\0'),则输出 s:(字符串 + 冒号 + 空格)。• 若 s 为空(s == NULL 或 *s == '\0'),则不同系统可能输出“: 错误信息”或直接输出“错误信息”。 |
| 返回值 | 无(void) |
|
fgetc函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fgetc(FILE *stream); |
|
| 功能 | 从文件流中读取当前位置的字符。每次调用 fgetc 会自动移动文件指针,指向下一个字符位置,为后续读取做准备。 |
|
| 参数 | stream |
文件指针 |
| 返回值 |
成功:返回无符号字符( 若返回 |
|
失败或读到文件结束:返回 EOF(-1)。 |
||
fputc函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fputc(int c, FILE *stream); |
|
| 功能 | 向文件中写入一个字符 | |
| 参数 | c |
要写入的字符 |
stream |
文件指针 | |
| 返回值 | 成功:返回写入的字符的ASCII码 | |
| 失败:返回EOF | ||
fgets函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | char *fgets(char *s, int size, FILE *stream); |
|
| 功能 |
从 最多读取 遇到 EOF 或 |
|
| 参数 | - s |
存放读取内容的内存首地址 |
- size |
想要读取的字节数 | |
- stream |
文件指针 | |
| 返回值 | - 成功:返回 s(即读取到的字符串地址) |
|
- 失败或读到文件结束:返回 NULL |
||
fputs函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fputs(const char *s, FILE *stream); |
|
| 功能 | 将字符串 s 的内容写入到 stream 指向的文件中,仅写入字符串有效字符(不包含末尾 \0) |
|
| 参数 | s |
要写入字符串的首地址 |
stream |
文件指针 | |
| 返回值 | 成功:返回非负整数 | |
失败:返回 EOF(-1) |
||
缓冲区
数据流动流程图

标准IO的缓冲区
定义:标准 I/O 中的缓冲区是C语言标准库为优化文件读写效率设计的内存机制,本质上就是一段由FILE结构体管理的内存空间;
作用:通过临时存储数据、批量处理,可以减少硬件IO(如磁盘、终端)的交互次数,提升程序效率;
缓冲策略:标准 I/O 中设计了行缓冲、全缓冲、无缓冲三种策略,来适配不同的场景,也支持手动修改;
• 行缓冲:默认用于终端设备(如stdout、stdin),Linux系统中行缓冲的大小为1024(1KB);
• 全缓冲:默认用于文件(如 .txt),Linux系统中全缓冲大小通常是4096(4KB);
• 无缓冲:不使用临时内存暂存,数据直接写入硬件(如stderr);无缓冲区的大小为0;
| 缓冲模式 | 说明 | 典型对象 |
|---|---|---|
| 全缓冲 | 填满缓冲区才进行实际I/O(如写磁盘) | 普通文件(fopen 打开的文件) |
| 行缓冲 | 遇到换行符 \n 或缓冲区满时才I/O |
终端交互设备(如 stdin、stdout 指向终端时) |
| 无缓冲 | 立即进行I/O,不缓存 | stderr |
行缓冲刷新时机:
• 遇到换行符“\n”会刷新缓冲区;
• 程序正常结束会刷新缓冲区;
• 当文件关闭时会刷新缓冲区;
• 在输入和输出发生切换时会刷新缓冲区;
• 缓冲区写满(未到换行符)会刷新缓冲区;
• 主动调用fflush函数会强制刷新缓冲区;
fflush函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fflush(FILE *stream); |
|
| 功能 | 将指定流(stream)缓冲区中尚未写入的内容,强制写入到对应的输出设备(如文件、终端等)中 | |
| 参数 | stream |
用于指定需要刷新缓冲区的流 |
| 返回值 | 成功:返回0,刷新了流缓冲区; | |
| 失败:返回EOF,并将errno设置为具体的错误类型 | ||
全缓冲刷新时机
• 程序正常结束会刷新缓冲区;
• 当文件关闭时会刷新缓冲区;
• 在输入和输出发生切换时会刷新缓冲区;
• 缓冲区写满(未到换行符)会刷新缓冲区;
• 主动调用fflush函数会强制刷新缓冲区;
手动设置缓冲
覆盖默认缓冲策略:不同系统/语言对IO流的默认缓冲设置不同(例如C语言中stdout默认行缓冲,stderr默认无缓冲),手动设置可按需调整;
性能与实时性平衡:
• 行缓冲:适合交互式输出(如命令行程序),每行输出后自动刷新;
• 全缓冲:适合大量数据批量写入(如日志文件),减少IO次数;
• 无缓冲:适合需要立即显示的关键信息(如错误日志);
特殊场景需求:例如网络编程中避免数据堆积,或调试时强制输出中间结果;
手动设置缓冲典型应用场景:
• 日志系统:根据日志级别设置缓冲(错误日志无缓冲,普通日志全缓冲);
• 交互式工具:命令行程序中确保用户输入后立即响应;
• 嵌入式系统:受限内存环境下自定义缓冲区大小;
setbuf函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | void setbuf(FILE *stream, char *buf); |
|
| 功能 | 设置指定文件流的缓冲区 | |
| 参数 | strea | 指向 FILE 对象的指针,标识要设置缓冲区的文件流 |
| buf | 指向用户提供的缓冲区的指针。如果提供了缓冲区,其大小必须至少为 BUFSIZ 字节(通常在 stdio.h 中定义);如果为 NULL,则是无缓冲模式 |
|
| 返回值 | 无 | |
setbuf函数允许两种方式的缓冲:
• 全缓冲:如果buf指向一个大小大于0的缓冲区,那么IO操作将使用全缓冲模式;
• 无缓冲:如果buf为NULL,或者是大小为0,那么IO操作将变为无缓冲模式;
setbuf函数注意事项:
• 调用setbuf函数应该在打开文件流或者是任何IO操作之前,否则可能不会生效;
• 如果要使用自定义的缓冲区,确保缓冲区足够大,以避免缓冲区溢出;
• 当使用自定义缓冲区时,不要在buf指向的缓冲区被释放后再进行IO操作,以免导致未定义的行为;
sleep函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <unistd.h> |
|
| 原型 | unsigned int sleep(unsigned int seconds); |
|
| 功能 | 用于让程序暂停执行指定的时间 | |
| 参数 | seconds |
指定程序暂停执行的秒数 |
| 返回值 | 成功:返回0; | |
| 失败:若程序被信号中断,返回未休眠的秒数 | ||
setvbuf函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int setvbuf(FILE *stream, char *buf, int mode, size_t size); |
|
| 功能 | 更加灵活地控制文件流的缓冲行为,允许指定缓冲模式和缓冲区的大小。注意:该函数必须在对 stream 执行任何其他操作前调用。 |
|
| 参数 | stream |
指向 FILE 对象的指针,标识要设置缓冲区的文件流。 |
buf |
指向用户提供的缓冲区的指针。如果为 NULL,函数会自动分配一个大小为 size 的缓冲区。 |
|
mode |
缓冲模式,必须是以下之一: • _IOFBF:全缓冲• _IOLBF:行缓冲• _IONBF:无缓冲(当 mode 为 _IONBF 时,size 参数被忽略) |
|
size |
缓冲区的大小(以字节为单位)。 | |
| 返回值 | 成功:返回 0; |
|
失败:返回非 0 值。 |
||
setbuf函数注意事项:
• setvbuf必须在文件流执行任何读写操作之前调用,文件流初始化后,否则可能导致设置失效,或触发未定义行为;
• 若使用自定义缓冲区(buf != NULL),需确保缓冲区大小 size 与实际需求匹配.若 size 过小,可能导致数据溢出;若 size 过大,会浪费内存资源;
• 若使用自动分配缓冲区(buf = NULL),size需设置合理值(通常建议不小于系统默认缓冲区大小,如BUFSIZ),避免因缓冲区过小影响性能或触发溢出;
• 禁止在缓冲区被释放后对文件流执行 IO 操作,否则会触发未定义行为(如访问已释放内存导致崩溃);
• 当 mode 为 _IONBF(无缓冲)时,size 参数会被忽略(即使设置非零值也无效),需避免依赖 size 配置无缓冲模式;
• 不同系统对 mode 的支持可能有差异(如某些嵌入式环境可能不支持行缓冲 _IOLBF),需根据实际平台验证行为;
二进制读写函数
fopen函数
打开文件的方式
| 文本模式 | 二进制模式 | 含义 |
|---|---|---|
r |
rb |
以只读方式打开文件,文件不存在则报错;光标定位于文件开头。 |
r+ |
r+b |
以读写方式打开文件,文件不存在则报错;光标定位于文件开头。 |
w |
wb |
以只写方式打开文件,文件存在则清空,文件不存在则创建;光标定位于文件开头。 |
w+ |
w+b |
以读写方式打开文件,文件存在则清空,文件不存在则创建;光标定位于文件开头。 |
a |
ab |
以追加方式打开文件,文件存在保持内容不变,文件不存在则创建;光标定位于文件末尾。 |
a+ |
a+b |
以读和追加方式打开文件,文件存在则保持内容不变,文件不存在则创建;读光标定位于文件开头,追加光标定位于文件末尾。 |
|
说明: 在每个选项中加入b字符用来告知函数库打开的文件为二进制文件,而非纯文本文件不过在Linux系统中会忽略该符号,加不加b字符对文件操作的实际行为没有任何影响若代码需移植到Windows,建议显式加b(如rb/wb),避免模式差异导致问题 |
||
fread函数
| 类别 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); |
|
| 功能 | 从 stream 中读取 nmemb 项数据,每项大小为 size,将它们存入 ptr 指向的位置中。 |
|
| 参数 | ptr |
存储读取到的数据的首地址。 |
size |
每个数据项的大小(单位:字节)。一般将其置为 1,让项目数表示字节数。 | |
nmemb |
要读取的数据项的最大数量。 | |
stream |
文件指针。 | |
| 返回值 | 成功:返回实际读取的数据项数量。 | |
返回值小于 nmemb 时,可能是遇到了文件结束(EOF)或者出现了错误。返回值为 0 时,表示没有读取到任何数据。 注意: fread 不区分文件结束(EOF)或读取错误,调用者必须使用 feof 函数或 ferror 函数来区分具体场景。 |
||
feof函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int feof(FILE *stream); |
|
| 功能 | 判断是否读取到文件的结尾 | |
| 参数 | stream |
文件指针 |
| 返回值 | 如果文件已到达末尾,返回一个非0值(真) | |
| 如果文件未到达末尾,返回0(假) | ||
| feof 是 “被动检测” 结尾只有当读取操作(如 fread/fgetc)实际触发了文件末尾时,标志位才会置为结束状态,文件结束标志只能通过clearerr 来清除直接用 feof 判断未读文件,返回的一定是 0(因为还没开始读,不存在 “已到结尾”) | ||
ferror函数
| 项目 | 描述 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int ferror(FILE *stream); |
|
| 功能 | 检查由 stream 指定的错误标志 |
|
| 参数 | stream |
文件指针 |
| 返回值 | 成功(发生了错误):真(非零); | |
| 失败(没有发生错误):假(0) | ||
| ferror 只检测 “是否发生过错误”,不主动触发错误错误标志位一旦置位,后续若不调用 clearerr 清除,ferror 会一直返回非 0,直到标志被手动清除 | ||
clearerr函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | void clearerr(FILE *stream); |
|
| 功能 | 清除由 stream 指定的文件结束标志和错误标志 |
|
| 参数 | stream |
文件指针 |
| 返回值 | 无 | |
| 当feof检测到文件结尾后,如果想要重新读取文件,必须先调用clearerr清除结束标志,否则feof会持续返回非0;当ferror检测到错误,修复问题后,必须先调用clearerr清除错误标志,让文件流恢复可用状态; | ||
fwrite函数
| 项目 | 描述 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); |
|
| 功能 | 将 ptr 指向的内存数据,按 size 字节为 1 项、共 nmemb 项,写入 stream 文件流。 |
|
| 参数 | • ptr |
指向要写入数据的内存首地址 |
• size |
每个数据项的大小(单位:字节),一般将其置为 1,让项目数表示字节数 | |
• nmemb |
要写入的数据项的最大数量 | |
• stream |
文件指针 | |
| 返回值 | 成功时返回实际写入的数据项数量。 | |
若返回值小于 nmemb,可能是写入错误或到达文件末尾,可通过 ferror 或 feof 函数进一步判断。 |
||
格式化输出函数
sprintf函数
| 项目 | 描述 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int sprintf(char *str, const char *format, ...); |
|
| 功能 | 将 format 控制格式的内容写到 str 对应的内存中(不会向终端显示) |
|
| 参数 | - str |
内存地址 |
- format |
格式化字符串,用于定义输出的格式 | |
- ... |
可变参数列表 | |
| 返回值 | 成功:返回格式化的字符的个数 | |
| 失败:返回一个负数(通常是 -1) | ||
snprintf函数
| 项目 | 说明 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int snprintf(char *str, size_t size, const char *format, ...); |
|
| 功能 | 将 format 控制格式的内容写到 str 对应的内存中(不会向终端显示) | |
| 参数 | str | 内存地址 |
| size | 最多格式化 size 个字符,其中包含一个 '\0' |
|
| format | 格式化字符串,用于定义输出的格式 | |
| ... | 可变参数列表 | |
| 返回值 | 成功:返回格式化的字符个数 | |
| 失败:返回一个负数(通常是 -1) | ||
fprintf函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fprintf(FILE *stream, const char *format, ...); |
|
| 功能 | 将控制格式化的字符串写入到文件中 | |
| 参数 | - stream |
文件指针 |
- format |
格式化字符串,用于定义输出的格式 | |
- ... |
可变参数列表 | |
| 返回值 | - 成功:返回格式化的字符的个数 | |
| - 失败:返回一个负数(通常是 -1) | ||
获取系统时间函数
time函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <time.h> |
|
| 原型 | time_t time(time_t *tloc); |
|
| 功能 | 获取自 1970-01-01 00:00:00 到当前的秒数 | |
| 参数 | tloc |
若传入非空指针,函数会把“秒数结果”同时写入该变量中; 若传入 NULL,函数仅返回秒数但不写入变量 |
| 返回值 | 成功:返回秒数 | |
| 失败:返回错误码,并设置相应的错误码 | ||
localtime函数
| 类别 | 详细信息 | |
|---|---|---|
| 所需头文件 | #include <time.h> |
|
| 原型 | struct tm *localtime(const time_t *timep); |
|
| 功能 | 将 time_t 类型的秒数转换为 struct tm 结构体(包含年、月、日、时、分、秒等信息) |
|
| 结构体定义 | struct tm {int tm_sec; /* 秒 (0-60) */int tm_min; /* 分 (0-59) */int tm_hour; /* 时 (0-23) */int tm_mday; /* 一个月中的第几天 (1-31) */int tm_mon; /* 月 (0-11, 0 表示一月) */int tm_year; /* 年份 - 1900 */int tm_wday; /* 星期几 (0-6, 0 表示周日) */int tm_yday; /* 一年中的第几天 (0-365, 0 表示 1 月 1 日) */int tm_isdst; /* 夏令时标志 */}; |
|
| 参数 | timep |
指向 time_t 类型变量的指针,表示从 1970‑01‑01 00:00:00 +0000 (UTC) 起经过的秒数 |
| 返回值 | 成功:返回指向 struct tm 结构体的指针; |
|
失败:返回 NULL,并设置相应的错误码 |
||
文件指针控制函数
fseek函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | int fseek(FILE *stream, long offset, int whence); |
|
| 功能 | 设置文件流的光标位置 | |
| 参数 | stream |
文件指针 |
offset |
光标的偏移量 >0:向后偏移; =0:不偏移; <0:向前偏移; |
|
whence |
偏移起始位置
|
|
| 返回值 | 成功:返回 0 | |
| 失败:返回 -1,并设置相应的错误码 | ||
rewind函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | void rewind(FILE *stream); |
|
| 功能 | 将光标恢复到文件的开头 | |
| 参数 | stream |
文件指针 |
| 返回值 | 无 | |
ftell函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <stdio.h> |
|
| 原型 | long ftell(FILE *stream); |
|
| 功能 | 返回文件流当前位置相对于文件起始位置的偏移量(以字节为单位) | |
| 参数 | stream |
文件指针 |
| 返回值 | 成功 | 返回字节数 |
| 失败 | 返回 -1,并设置相应的错误码 | |
文件IO介绍
文件IO是在POSIX(可移植操作系统接口)中定义的一组输入输出的函数,涉及操作系统和应用程序之间的交互,用于读取和写入存储在永久性存储介质(如硬盘、SSD、USB闪存盘等)上的数据;
跨平台性:不同操作系统(如UNIX、Windows、Linux)的文件IO机制存在差异,接口、调用方式和底层实现逻辑各有特点;
如Windows下有CreateFile等专属函数,Linux则基于open、read等系统调用;
高效实现原理:
• 直接交互硬件/文件系统:绕过高级缓冲,直接发起系统调用访问磁盘等设备,数据传输路径短,适合对实时性、精准控制要求高的场景(如设备驱动开发);
• 无用户态缓冲:不依赖用户态缓冲机制,每次读写更贴近硬件真实操作,但频繁调用会增加系统开销,需开发者按需优化(如手动合并读写请求);
易用性设计:
• 贴近底层逻辑:需开发者熟悉操作系统 API、文件权限、设备特性等,直接处理缓冲区管理、错误码解析(如Linux中errno),对开发经验要求高;
• 灵活但复杂:可精准控制文件打开模式(只读、读写等)、数据传输细节,却也因需手动处理资源释放(如close函数)、并发冲突等问题,增加开发复杂度;
特点:
• 没有缓冲机制,每次调用都会引起系统调用;
• 围绕文件描述符进行操作,相当于标准IO中的流(FILE *),文件描述符是正整数;
• 默认打开三个描述符:标准输入(0)标准输出(1)标准错误(2)•除目录文件,其他类型文件都可以操作;
• 可移植性相对较弱;
与标准IO对比
| 对比维度 | 文件 IO | 标准 IO |
|---|---|---|
| 概念 | 对文件系统中的文件进行读写 | 程序与终端(控制台)的交互 |
| 用途 | 持久化存储、跨程序数据交换 | 即时交互、简单调试 |
| 操作对象 | 磁盘文件(需路径或描述符) | 以流为抽象,覆盖终端和磁盘文件 |
| 数据流向 | 程序 ⇄ 磁盘(持久化) | 程序 ⇄ 终端(临时交互) |
| 缓冲机制 | 默认缓冲 | stdout:行缓冲 stderr:无缓冲 |
| 错误处理 | 需显式处理文件不存在、权限不足等 | 通常无需特殊处理(终端直接反馈) |
| 典型场景 | 配置文件读写、日志记录、数据存储等 | 命令行参数输入、结果显示、错误提示等 |
| 性能特点 | 大文件或批量读写时,通过手动优化(调整读写块大小)更高效 | 频繁小数据读写时,标准 IO 的缓冲机制更高效 |
| 生命周期 | 数据持久保存 | 会话结束后数据丢失 |
文件描述符
文件描述符是操作系统内核为每个打开的文件/设备分配的唯一整数标识符,用于标识和跟踪进程与文件的交互,在Linux/Unix中,它是底层文件IO操作的核心参数;
本质:
• 它是指向内核中维护的文件表(File Table),该表中记录了文件的状态,位置指针等信息;
• 每个进程都有自己独立的文件描述符表,通常是从0开始的编号,相互不干扰;Linux系统中一个正在执行的程序中文件描述符的范围是[0-1023],可通过ulimit调整,一般使用默认值;
作用:
• 连接用户进程与内核资源:通过文件描述符,进程可以请求内核进行文件读写、设备操作等,无需关心底层实现细节;
• 资源管理:操作系统通过文件描述符跟踪哪些文件被哪些进程打开,并在进程关闭文件或退出时自动回收资源;
特点:
• 唯一性:同一个进程内,每个文件描述符对应唯一的打开文件;
• 非负整数:通常是小整数(如0、1、2),但可动态分配(如 open()返回的新描述符);
• 跨函数传递:可在不同系统调用间传递,代表对同一文件的持续操作;
常见文件描述符编号:
| 编号 | 宏定义 | 用途 | 对应设备 |
|---|---|---|---|
| 0 | STDIN_FILENO |
标准输入(Standard Input) | 通常是键盘 |
| 1 | STDOUT_FILENO |
标准输出(Standard Output) | 通常是终端屏幕 |
| 2 | STDERR_FILENO |
标准错误(Standard Error) | 通常是终端屏幕 |
文件打开与关闭函数
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); |
|
| 功能 | 用文件 I/O 的方式打开文件 | |
| 参数 | pathname | 文件路径和名字。不写路径默认打开当前路径下的文件 |
| flags | 打开文件的方式: • O_RDONLY:以只读方式打开,文件不存在则失败• O_WRONLY:以只写方式打开,文件不存在则失败• O_RDWR:以读写方式打开,文件不存在则失败• O_APPEND:以追加方式打开,文件不存在则失败• O_CREAT:文件不存在则创建文件。若使用此标志,必须填写第三个参数 mode(创建文件的权限)• O_TRUNC:如果文件存在则清空• O_EXCL:与 O_CREAT 结合使用。若文件不存在则创建;若文件存在则报错,错误码为 EEXIST |
|
| mode | 创建文件的权限,一般用八进制表示。实际创建出来的文件权限 = (mode & ~umask)。 示例: umask 默认值 0002,最大文件权限 0666,则实际权限 = (0666 & ~0002) = (0666 & 0664) = 0664 |
|
| 返回值 | 成功:返回文件描述符 | |
失败:返回 -1,错误码 errno 会被正确设置 |
||
标准IO和文件IO打开方式对比'
| 文件IO | 标准IO | 功能描述 |
|---|---|---|
O_RDONLY |
"r" |
以只读的方式打开文件,将光标定位到开头 |
O_RDWR |
"r+" |
以读写的方式打开文件,将光标定位在开头 |
O_WRONLY | O_CREAT | O_TRUNC, 0666 |
"w" |
以只写的方式打开文件:如果文件不存在就创建;如果文件存在则清空,光标在开头 |
O_RDWR | O_CREAT | O_TRUNC, 0666 |
"w+" |
以读写的方式打开文件:如果文件不存在就创建;如果文件存在则清空,光标在开头 |
O_WRONLY | O_APPEND | O_CREAT, 0666 |
"a" |
以追加的方式打开文件:如果文件不存在就创建;如果存在则光标在结尾 |
O_RDWR | O_APPEND | O_CREAT, 0666 |
"a+" |
以读和追加的方式打开文件:如果文件不存在就创建;如果存在,读光标始终在开头,写光标始终在结尾 |
close函数
| 类别 | 内容 | |
|---|---|---|
| 所需头文件 | #include <unistd.h> |
|
| 原型 | int close(int fd); |
|
| 功能 | 用文件IO的方式关闭文件 | |
| 参数 | fd |
文件描述符 |
| 返回值 | 成功:返回0 | |
失败:返回-1,错误码errno会被正确设置 |
||
文件读写函数
read函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <unistd.h> |
|
| 原型 | ssize_t read(int fd, void *buf, size_t count); |
|
| 功能 | 用文件IO的方式读取文件中的内容 | |
| 参数 | fd | 文件描述符 |
| buf | 存储读取到的数据的首地址 | |
| count | 想要读取的字节的个数 | |
| 返回值 | 成功 | 返回读取到的字节的个数;返回0表示读取到文件的结尾 |
| 失败 | 返回-1,错误码errno会被正确设置 | |
write函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <unistd.h> |
|
| 原型 | ssize_t write(int fd, const void *buf, size_t count); |
|
| 功能 | 用文件IO的方式往文件中写入内容 | |
| 参数 | - fd: |
文件描述符 |
- buf: |
想要写入数据的首地址 | |
- count: |
想要写入的字节的个数 | |
| 返回值 | - 成功:返回写入的字节的个数;返回0表示不会写任何的数据 | |
| - 失败:返回-1,错误码errno会被正确设置 | ||
文件定位与控制函数
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:从文件的结尾注意:若文件以 O_APPEND 打开,write 会强制将偏移量置为文件末尾,lseek 调整的偏移量仅对 read 生效 |
|
| 返回值 | 成功:返回从文件开头计算出的结果偏移量(单位:字节) | |
失败:返回 -1,错误码 errno 会被正确设置 |
||
fcntl函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <unistd.h>#include <fcntl.h> |
|
| 原型 | int fcntl(int fd, int cmd, ... /* arg */); |
|
| 功能 | 对打开的文件描述符进行多种控制(如复制文件描述符、设置和获取文件描述符标志、实现文件锁等等) | |
| 参数 | - fd: |
文件描述符 |
- cmd: |
指定要执行的控制操作命令 - F_DUPFD:复制描述符- F_GETFD:获取描述符标志- F_SETFL:设置文件状态标志(arg 是整数,如 O_NONBLOCK)- F_GETLK:获取锁信息(arg 是 struct flock *)- F_SETLK:尝试加锁(非阻塞,arg 是 struct flock *)- F_SETLKW:阻塞加锁(arg 是 struct flock *)- ... |
|
- ...: |
可选参数,是否必填由 cmd 决定 |
|
| 返回值 | - F_DUPFD:成功返回新描述符,失败返回 -1- F_GETFD / F_GETFL / F_GETLK:成功返回对应值,失败返回 -1- F_SETFD / F_SETFL / F_SETLK / F_SETLKW:成功返回 0,失败返回 -1 |
|
struct flock结构体
struct flock {
short l_type; // 锁类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁)
short l_whence; // 锁的基准位置(同 lseek 的 whence:SEEK_SET / SEEK_CUR / SEEK_END)
__kernel_off_t l_start; // 锁的起始偏移量
__kernel_off_t l_len; // 锁的长度(0 表示到文件末尾)
__kernel_pid_t l_pid; // 持有锁的进程 ID(仅 F_GETLK 有效)
};
文件元信息查询函数
stat函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <sys/stat.h>#include <unistd.h> |
|
| 原型 | int stat(const char *pathname, struct stat *statbuf); |
|
| 功能 | 获取文件的属性信息 | |
| 参数 | pathname |
文件路径和名字。不写路径默认打开当前路径下的文件。如果是符号链接,会跟随链接,获取最终指向的“目标文件”的属性。 |
statbuf |
用于保存文件属性信息的内存空间的首地址。 | |
| 返回值 | 成功:返回0 | |
| 失败:返回-1,错误码errno会被正确设置 | ||
stat结构体
struct stat {
dev_t st_dev; /* 磁盘设备号 */
ino_t st_ino; /* 文件的inode号,文件系统识别文件的唯一编号,通过ls -i查看 */
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; /* 文件系统IO块的大小 */
blkcnt_t st_blocks; /* 块的个数 */
struct timespec st_atim; /* 最后一次访问的时间 */
struct timespec st_mtim; /* 最后一次被修改的时间 */
struct timespec st_ctim; /* 最后一次状态改变的时间 */
};
/* 方便访问时间戳中秒数的宏定义 */
#define st_atime st_atim.tv_sec
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
文件的权限及类型
文件权限的位域(0-8位):st_mode 的低 9 位(第 0-8 位 )分为三组,每组 3 位:
• 第 0-2 位:其他用户(others)的读、写、执行权限;
• 第 3-5 位:组用户(group)的读、写、执行权限;
• 第 6-8 位:所有者(user)的读、写、执行权限;
文件类型的位域(12-15位):st_mode的高4位用于标识文件类型,用__S_IFMT作为“掩码”提取这4位以下是一些宏定义:
#define __S_IFMT 0170000 /* 这些位决定了文件的类型 */
#define __S_IFDIR 0040000 /* 目录文件 */
#define __S_IFCHR 0020000 /* 字符设备文件 */
#define __S_IFBLK 0060000 /* 块设备文件 */
#define __S_IFREG 0100000 /* 普通文件 */
#define __S_IFIFO 0010000 /* 管道文件 */
#define __S_IFLNK 0120000 /* 软链接文件 */
#define __S_IFSOCK 0140000 /* 套接字文件 */
lstat函数
| 类别 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <sys/stat.h>#include <unistd.h> |
|
| 原型 | int lstat(const char *pathname, struct stat *statbuf); |
|
| 功能 | 获取文件的属性信息 | |
| 参数 | - pathname |
文件路径和名字。不写路径默认打开当前路径下的文件。如果是符号链接,不会跟随链接,直接获取“符号链接本身”的属性。 |
- statbuf |
用于保存文件属性信息的内存空间的首地址 | |
| 返回值 | 成功:返回 0 | |
失败:返回 -1,错误码 errno 会被正确设置 |
||
fstat函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <sys/stat.h>#include <unistd.h> |
|
| 原型 | int fstat(int fd, struct stat *statbuf); |
|
| 功能 | 通过文件描述符获取已打开文件的属性信息 | |
| 参数 | fd: |
文件描述符 |
statbuf: |
用于保存文件属性信息的内存空间的首地址 | |
| 返回值 | 成功:返回 0 | |
| 失败:返回 -1,错误码 errno 会被正确设置 | ||
目录遍历操作函数
opendir函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <dirent.h> |
|
| 原型 | DIR *opendir(const char *name); |
|
| 功能 | 打开目录 | |
| 参数 | name: |
目录名 |
| 返回值 | 成功:返回目录的指针 | |
失败:返回 NULL,错误码 errno 会被正确设置 |
||
closedir函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <dirent.h> |
|
| 原型 | int closedir(DIR *dirp); |
|
| 功能 | 关闭目录 | |
| 参数 | dirp: |
目录的指针 |
| 返回值 | 成功:返回0 | |
失败:返回-1,错误码 errno 会被正确设置 |
||
readdir函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <dirent.h> |
|
| 原型 | struct dirent *readdir(DIR *dirp); |
|
| 功能 | 读取目录下的文件 | |
| 参数 | dirp: |
目录指针 |
| 返回值 | 成功:返回 dirent 结构体指针; |
|
失败:到了目录的结尾返回 NULL,错误码 errno 会被正确设置 |
||
dirent结构体
struct dirent {
ino_t d_ino; /* 该目录项对应的文件/目录的 inode 编号 */
off_t d_off; /* 当前目录项在目录流中的偏移量 */
unsigned short d_reclen; /* 当前目录项记录的长度(字节数) */
unsigned char d_type; /* 文件类型----但是并不支持所有文件系统 */
char d_name[256];/* 文件名 */
};
/* d_type 可能的取值(宏定义) */
#define DT_BLK /* 块设备文件 */
#define DT_CHR /* 字符设备文件 */
#define DT_DIR /* 目录 */
#define DT_FIFO /* 命名管道(FIFO) */
#define DT_LNK /* 符号链接 */
#define DT_REG /* 普通文件 */
#define DT_SOCK /* UNIX套接字 */
用户/组ID映射函数
getpwuid函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <pwd.h> |
|
| 原型 | struct passwd *getpwuid(uid_t uid); |
|
| 功能 | 根据 uid 获取用户信息结构体 | |
| 参数 | uid: |
用户的 id |
| 返回值 | 成功:返回用户信息的结构体指针 | |
失败:返回 NULL,错误码 errno 会被正确设置 |
||
passwd结构体
struct passwd {
char *pw_name; /* 用户名 */
char *pw_passwd; /* 是否有密码 */
uid_t pw_uid; /* 用户ID */
gid_t pw_gid; /* 组ID */
char *pw_gecos; /* 描述信息 */
char *pw_dir; /* 家目录 */
char *pw_shell; /* 命令行解析器 */
};
getgrgid函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <sys/types.h>#include <grp.h> |
|
| 原型 | struct group *getgrgid(gid_t gid); |
|
| 功能 | 根据 gid 获取组结构体 | |
| 结构体定义 |
struct group {
char *gr_name; // 组名
char *gr_passwd; // 组的密码
__gid_t gr_gid; // 组号
char **gr_mem; // 组成员列表
}; |
|
| 参数 | gid: |
组号 |
| 返回值 | 成功:返回结构体指针 | |
失败:返回 NULL,错误码 errno 会被正确设置 |
||
模拟Linux命令 ls 的实现
命令ls的实现
• 练习要求:模拟ls -l基础功能,查看单个文件的详细信息
linux@ubuntu:~$ ls -l a
-rw-rw-- 1linux linux 0 Jul 15 17:11 a
linux@ubuntu:~$
实现思路:
• 对输入参数的参数个数进行检查;
• 获取目标文件的属性,如果获取失败,可以通过perror输出错误信息并退出;
• 对文件类型与权限进行解析;
• 其他信息输出(硬链接数、文件所有者、文件所属组、文件大小、修改时间、文件名);
strftime函数
| 项目 | 内容 | |
|---|---|---|
| 所需头文件 | #include <time.h> |
|
| 原型 | size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); |
|
| 功能 | strftime函数可以将 struct tm 结构体所表示的时间,按照指定的格式字符串,转化为一个格式化后的字符串。 |
|
| 参数 | - s |
指向用于存储格式化后时间字符串的字符数组指针。 |
- max |
表示字符数组 s 的最大长度,即最多可以写入 max - 1 个字符。 |
|
- format |
指向一个格式字符串,该字符串包含普通字符以及转换说明符(以 % 开头)。 |
|
- tm |
指向 struct tm 结构体的指针,struct tm 结构体中包含分解后的时间信息,例如年、月、日等。 |
|
| 返回值 | 成功:返回写入到字符数组 s 中的字符数(不包括字符串结束符 \0)。 |
|
失败:返回 0,表示没有成功将时间格式化并写入到指定的字符数组中。 |
||
日期相关格式
| 格式说明符 | 含义 | 示例(以 2024 年 7 月 15 日为例) |
|---|---|---|
%Y |
四位数的年份 | 2024 |
%y |
两位数的年份 | 24 |
%m |
月份,数字表示,补前导零(01 - 12) | 07 |
%b |
本地语言环境下的月份缩写,英文环境下如 Jan 等 | Jul(英文环境) |
%B |
本地语言环境下的月份全称,英文环境下如 January 等 | July(英文环境) |
%d |
月份中的日期,数字表示,补前导零(01 - 31) | 15 |
%e |
月份中的日期,数字表示,不补前导零(1 - 31) | 15 |
%a |
本地语言环境下的星期几缩写,英文环境下如 Mon 等 | Mon(假设 7 月 15 日是周一,英文环境) |
%A |
本地语言环境下的星期几全称,英文环境下如 Monday 等 | Monday(假设 7 月 15 日是周一,英文环境) |
%j |
一年中的第几天,数字表示,补前导零(001 - 366) | 197(2024 年 7 月 15 日是一年第 197 天) |
时间相关格式
| 格式说明符 | 含义 | 示例(以 14:30:25 为例) |
|---|---|---|
%H |
24 小时制的小时,数字表示,补前导零(00 - 23) | 14 |
%I |
12 小时制的小时,数字表示,补前导零(01 - 12) | 02 |
%k |
24 小时制的小时,数字表示,不补前导零(0 - 23) | 14 |
%l |
12 小时制的小时,数字表示,不补前导零(1 - 12) | 2 |
%M |
分钟,数字表示,补前导零(00 - 59) | 30 |
%S |
秒,数字表示,补前导零(00 - 59) | 25 |
%p |
本地语言环境下的上午或下午标识,英文环境下为 AM 或 PM | PM(14 点对应下午) |
库文件介绍
定义:库文件是一组预先编译好的函数、类或数据的集合,以文件的形式存在,用于被其他程序调用;
本质:库文件是一种可执行代码的二进制形式,是代码复用的一种方式,将常用功能封装起来,方便开发者在不同项目中使用;
作用:
• 提高开发效率:避免重复编写相同功能的代码,减少开发时间;
• 便于代码维护:集中管理功能模块,修改和升级库时,只需更新库文件,无需修改使用库的程序;
• 保护代码隐私:可以将核心代码封装在库中,只提供接口供外部使用,隐藏实现细节;
库的分类:静态库和共享库(动态库)
在不同平台中都存在大量的库,不同平台(如 Linux vs Windows)的指令集、二进制格式、系统调用不同,导致库无法跨平台直接使用(比如 Linux 的 .so 不能在 Windows 上运行);
linux平台下库文件命名:
• 静态库命名规则为:libxxx.a(以lib为前缀+自定义库名+.a后缀);
• 动态库命名规则为:libxxx.so(以lib为前缀+自定义库名+.so后缀);
若涉及到版本管理,动态库命名可扩展为:libxxx.so.x.y.z
x 为主版本号,不兼容旧版;y 为次版本号,兼容旧版;z 为发行版本号,修复问题不影响接口
静态库介绍及制作
定义:静态库是在程序编译链接阶段,其代码会被完整地”复制”到最终生成的可执行文件中,成为可执行文件的一部分,编译完成后,静态库与可执行文件“绑定”,运行时无需额外依赖;
特点:
• 可执行文件独立运行,不依赖外部库文件;
• 静态库会使可执行文件体积较大,但是效率比较高;
• 若静态库更新,需重新编译链接程序;
静态库文件格式:
• windows平台下为.lib;Linux平台下为.a;Mac平台下为.a;
静态库制作及使用
准备源文件:编写实现库功能的源文件(如 .c或 .cpp);
编译生成目标文件:使用编译器将源文件编译成目标文件(如 .o或.obj);
示例:gcc -c源文件.c -o目标文件.o
打包生成静态库:使用归档工具将目标文件打包成静态库;
示例:ar-cr静态库.a 目标文件.o
编译主程序并链接静态库,生成可执行文件;
示例:gcc目标文件.o -l库名 -L库路径 -o 可执行文件
说明:如果库名是libmyadd.a,则库名是myadd,编译器会自动拼接lib前缀和.a后缀进行查找库文件;
静态库所用核心选项
| 工具 | 选项 | 作用 | 示例命令 |
|---|---|---|---|
| GCC | -c |
只编译不链接,生成目标文件(.o) | gcc -c add.c -o add.o |
-o |
指定输出文件名称 | gcc -c main.c -o main.o |
|
-l |
指定要链接的库名(省略lib前缀和.a后缀) | gcc main.c -lmyadd -o main |
|
-L |
指定库文件搜索路径 | gcc main.c -L./lib -lmyadd -o main |
|
| AR | -c |
创建新的归档文件(静态库) | ar -cr libmyadd.a add.o |
-r |
将目标文件添加到归档文件中(替换已存在的文件) | ar -cr libmyadd.a add.o |
动态库介绍及制作
定义:动态库也称之为共享库,是在程序运行时才被加载到内存中,可以被多个可执行文件共享使用,为它们提供函数、数据等资源;
特点:
• 可执行文件运行时,需依赖系统中存在的动态库;
• 可执行文件体积小,有效节省磁盘和内存空间;
• 更新灵活,只要接口不变,无需重新编译依赖它的可执行文件;
动态库文件格式:
• windows平台下为.dll;Linux平台下为.so;Mac平台下为.dylib;
动态库制作及使用
准备源文件:编写实现库功能的源文件(如 .c或 .cpp);
编译生成与位置无关的目标文件:
使用编译器编译源文件时,添加相关选项(如GCC的-fPIC选项)生成位置无关代码(PIC)的目标文件;
示例:gcc -c -fPIC 源文件.c -o目标文件.o
链接生成动态库:使用编译器将目标文件链接为动态库,多个程序可共享同一份动态库文件,节省内存;
示例:gcc -shared 目标文件.o -o 动态库.so
编译时通过-L指定路径、-l指定库名生成可执行文件;
示例:gcc 源文件.c -l库名 -L库路径 -o 可执行文件
运行时需通过LD_LIBRARY_PATH环境变量、/etc/ld.so.conf配置或系统路径,保证动态库可被加载;
示例1:通过LD_LIBRARY_PATH环境变量运行;
export LD_LIBRARY_PATH=$PWD(=后面就是库路径,PWD表示当前路径)
示例2:通过/etc/ld.so.conf 配置运行;
编辑配置文件添加动态库路径:sudo vim /etc/ld.so.conf(在此文件中添加动态库路径)
添加完成之后,执行sudo ldconfig命令更新系统缓存
示例3:通过系统路径运行:优先考虑/usr/local/lib路径,然后进行更新系统缓存
系统级库放/usr/lib,用户自定义库放 /usr/local/lib;
动态库所用核心选项
| 工具 | 选项 | 作用 | 示例 |
|---|---|---|---|
| gcc(编译阶段) | -c |
只编译不链接,生成目标文件(.o),不生成可执行程序 | gcc -c add.c -o add.o |
-fPIC |
生成位置无关代码(PIC),确保动态库可被加载到内存任意地址并正常运行(动态库必需) | gcc -c -fPIC add.c -o add.o |
|
-o |
指定输出文件名称(如目标文件.o或动态库.so) | gcc -c -fPIC add.c -o add.o |
|
| gcc(链接阶段) | -shared |
将目标文件(.o)链接为动态库(.so),是生成动态库的核心选项 | gcc -shared add.o -o libmyadd.so |
| gcc(使用阶段) | -L |
指定动态库的搜索路径(编译时用于查找 .so 文件) | gcc main.c -lmyadd -L./lib -o main |
-l |
指定要链接的动态库名称(省略lib前缀和.so后缀;如libmyadd.so 对应 -lmyadd) | gcc main.c -lmyadd -L. -o main |
|
| 系统工具 | ldconfig |
更新系统动态库缓存,使新添加的动态库(如放入/usr/lib)被系统识别(需sudo权限) | sudo ldconfig |
静态库与动态库的区别
链接方式:静态库在编译时链接,动态库在运行时链接;
可执行文件体积:静态库使可执行文件体积大,动态库使可执行文件体积小;
内存占用:多个程序使用同一静态库时,每个程序都有一份库的副本,占用内存多;
多个程序共享同一动态库时,内存中只有一份库的副本,节省内存;
更新维护:静态库更新需重新编译链接程序;动态库更新只需替换库文件,无需重新编译链接程序;
依赖性:静态库无运行时依赖;动态库有运行时依赖,库缺失程序无法运行;
适用场景:
• 静态库:适合对程序独立性要求高,库不常更新,且对可执行文件启动速度有要求的场景;
• 动态库:适合库需要频繁更新,多个程序共享库功能,且希望减小可执行文件体积的场景;
库制作常见问题及解决方案
静态库链接时提示未定义的引用:
• 可能原因:库文件路径错误、库名拼写错误、库中确实不存在该函数或变量的定义;
• 解决方法:检查库路径和库名是否正确,确认库中是否实现了所需功能;
动态库运行时提示无法找到库文件:
• 可能原因:库文件路径未加入系统默认搜索路径或者是未通过环境变量指定;
• 解决方法:将动态库复制到系统默认库路径(如/usr/local/lib),或设置相应的环境变量(如Linux的LD_LIBRARY_PATH);
动态库版本冲突:
• 可能原因:程序依赖的动态库版本与系统中安装的版本不一致;
• 解决方法:安装程序所需版本的动态库,或重新编译程序使其适配系统中的库版本;
更多推荐

所有评论(0)