001库函数IO基础

一、计算机的组成部分

(一)硬件系统

计算机硬件由五部分组成,核心逻辑与组件如下:

  • 核心部件:运算器 + 控制器 = 中央处理器(CPU),负责信息的高速运算处理。

  • 存储部件:存储器用于存储程序、数据和文件,分为:

    • 内部存储器(如内存条):速度快,容量可达数百兆字节至数 G 字节。

      RAM:随机访问存储器 掉电会丢失

    • 外部存储器(如固态硬盘):速度较慢,容量可达数十 G 或数百 G 以上。

      ROM:只读存储器 掉电不会丢失

  • 交互部件:输入设备(键盘、鼠标等)与输出设备(显示器、打印机等 I/O 设备),通过输入输出控制系统管理与主存储器的信息交换。

    image-20260113091442532

(二)软件系统

硬件需依赖软件控制才能工作,软件系统与硬件系统相互依赖,分为两类:

  • 系统软件:包括操作系统(如 Windows、Linux)、硬件驱动程序等,负责连接应用软件与底层硬件。
  • 应用软件:用户可直接使用的程序集合,由程序设计语言开发(如 C、Java)。

(三)文件系统

文件系统是程序和数据在存储器中的存储标准 / 格式,操作系统按此标准访问磁盘数据。

文件是由数据组成

  1. 常见文件系统类型

    • Windows 系统:本地磁盘默认 NTFS 格式,另有U盘使用 FAT32 等。
    • Linux 系统:支持 ext2、ext3、ext4、vfat 等
  2. Linux中的文件类型

    • Linux系统也支持多种文件系统类型,可以在Linux系统根目录的/proc目录下查阅filesystems

image-20260113092006241

查找

  • U盘FAT32和windos的NTFS的作用区别

  • 简述Linux内核作用

二、Linux 系统核心机制

(一)用户空间与内核空间隔离

Linux 系统为保障安全,不允许用户空间直接访问硬件,需通过内核提供的函数接口实现硬件控制:

  1. 层级结构:用户空间(应用层)→ 系统调用接口 → 内核空间(内存管理、进程管理、文件系统等)→ 驱动层 → 硬件层。

  2. 核心流程:用户编写源文件(xxx.c)→ 编译器编译生成可执行文件 → 内核读取可执行文件参数 → 驱动程序控制硬件寄存器。

  3. 计算机中搭载的Linux操作系统就属于系统软件,操作系统的作用是用来连接应用软件和底层硬件,因为涉及到Linux内核的安全管理机制,所以用户空间是没有办法直接访问硬件设备的。

    image-20260113092219033

(二)根文件系统(rootfs)

  1. 核心作用:Linux 内核启动后挂载的第一个文件系统,提供根目录、shell 终端、应用程序存储等基础服务,内核离开根文件系统无法工作。

    Linux系统是离不开文件系统的,在Linux内核启动之后首先搭载的就是根文件系统rootfs,根文件系统rootfs本质就是文件系统,只不过是Linux内核挂载的第一个文件系统。而Linux内核源码是存储在文件系统中,所以在linux系统启动的过程中启动引导程序uboot会加载内核并利用内核挂载根文件系统。

  2. 启动流程:上电 → 启动引导程序(uboot)→ 加载 Linux 内核 → 挂载 rootfs 根文件系统 → 安装驱动、执行脚本 → 提供字符终端、运行应用程序。

    image-20260113093026057

    根文件系统被挂载之后,一些服务程序和一些脚本文件才可以存储在文件系统中,其他的文件系统才可以被挂载。

(三)目录结构

  1. 组织形式:所有文件和目录以根节点 “/” 为起点,呈倒置树状结构,便于管理维护。
  2. 常用目录说明
    • /bin:系统启动时常用命令。
    • /dev:设备文件(如 /dev/sda、/dev/null)。
    • /etc:系统和软件的配置文件。
    • /home:各用户主目录。
    • /lib:函数库与内核模块。
    • /proc:虚拟目录,存 “系统进程 / 内核信息”(如 filesystems 文件)。
    • /usr:第三方软件、头文件(如 /usr/include/stdio.h)。
    • /sbin:存 “管理员用的系统命令”(比如reboot

image-20260113093207085

Linux系统的所有程序和数据都是以“*文件*”的形式存储在文件系统中,所有Linux 用户和程序看到的文件、目录、软连接及文件保护信息等都存储在其中。这种机制有利于用户和操作系统的交互,这也是*Linux系统“**一切皆文件**”的原因。*

  1. 目录查看工具tree命令(需提前安装:sudo apt-get install tree

四)文件类型

Linux 系统中 “一切皆文件”,共 7 种类型,通过ls -l命令首字符区分:

类型标识 文件类型 说明
- 普通文件 存储普通数据(如文本、压缩包)
d 目录文件 存放目录项,类似 Windows 文件夹
p 管道文件 进程间通信(命名管道 FIFO)
s 套接字文件 网络间通信
l 链接文件 间接访问目标文件,类似 Windows 快捷方式
c 字符设备文件 字符设备的应用层访问接口
b 块设备文件 块设备的应用层访问接口

三、Linux 文件操作接口(遵循 POSIX 标准)

POSIX 标准统一了不同操作系统的访问接口,提高程序兼容性和可移植性,标准 C 库函数遵循该标准。

标准C库中关于文件输入输出的函数接口一般被称为标准IO,访问文件常用的标准IO函数有fopen()、fread()、fwrite()、fclose()、fgetc()、fputc()、fgets()、fputs()、fprintf()、fscanf()等。

image-20260113094148818

(一)文件操作核心函数

1. 打开文件(fopen)

image-20260113094358063

  • 功能:以指定模式打开文件,建立文件流关联。

  • 头文件#include <stdio.h>

  • 函数原型FILE *fopen(const char *pathname, const char *mode);

  • 参数说明

    • pathname:文件路径(如 "demo.txt")。
    • mode:打开模式(常用:r - 只读、w - 只写(创建 / 清空)、a - 追加、r+- 读写)。
      image-20260113094502605
  • 返回值:成功返回 FILE 指针,失败返回 NULL。

  • 关键说明:FILE 是结构体类型,包含文件描述符、缓冲区指针、出错标志等信息,定义在/usr/include/libio.h中。

    头文件stdio.h中有关于FILE类型的相关描述

    image-20260113094557304

    打开头文件<libio.h>之后,可以找到关于FILE结构体类型的定义

    image-20260113094739099

    • 讲解:用户可以在一个程序中利用fopen函数打开多个文件,每次打开一个文件,内核就会从*堆内存*中申请一块FILE结构体大小的空间用来存储文件的所有信息,然后按照文件打开的顺序把每个打开的文件的结构体形成一条链表,然后使用链表头进行管理。

    • 注意:打开文件的目的无非就是对文件进行读写操作,所以每次当程序运行的时候已经有三个文件流被打开,分别是标准输入stdin、标准输出stdout、标准出错stderr,这三者在stdio.h中也是FILE指针。

      image-20260113095032814

      所以内核在管理被打开文件的时候,链表中已经有三个结点存在,然后再把新节点头插入到链表中。

      img

      标准IO和系统IO的内核管理方式不同,-------标准IO是通过链表方式管理

2. 关闭文件(fclose)

  • 功能:关闭文件,刷新缓冲区,释放堆内存资源。
  • 函数原型int fclose(FILE *stream);
  • 返回值:成功返回 0,失败返回 EOF(-1)。
  • 注意:不可重复关闭同一文件,否则导致段错误(重复释放堆内存)。

image-20260113095127666

3. 读取文件

标准C库中提供了多个读取函数来满足用户的不同需求,这些函数大体分为三类:字符读取(fgetc)、按行读取(fgets)、按块读取(fread)。

3.1字符读取:fgetc 函数

image-20260113095832728

  • 核心功能

从指定文件流中读取单个字符,读取后自动将文件位置指示器向后移动 1 字节,适用于逐字符处理文本或二进制文件。

函数原型与头文件

  • 原型int fgetc(FILE *stream);

  • 参数stream 为指向目标文件的 FILE 类型指针(如 fopen 函数的返回值)。

  • 返回值说明

​ 成功:返回读取字符的 ASCII 码(以 unsigned char 类型转换为 int 类型返回)。

​ 失败 / 文件末尾:返回 EOF(宏定义,值为 -1,定义于 libio.h 头文件中)。

在标准库中还提供了另一个*函数getc()*,这个函数的作用等效于fgetc()函数,只不过getc()函数的实现是利用宏定义而已。

image-20260113095934907

image-20260113095958771

image-20260113100030932

  • 标准输入读取:getchar(void) 等价于 getc(stdin),专门用于从标准输入(键盘)读取单个字符。

练习:

在本地磁盘打开一个存储少量数据的文本demo.txt,利用fgetc函数把文本中的字符输出到屏幕,当文本中所有字符都输出完成后就结束程序。

#include <stdio.h>
int main() {
    // 打开文件
    FILE *file = fopen("demo.txt", "r");
    if (NULL == file) {
        perror("open demo.txt error");
        return -1;
    }

    // 逐字符读取并输出
    int ch;
    while ((ch = fgetc(file)) != EOF) {
        printf("%c", ch);
    }
    printf("\n");

    // 关闭文件
    fclose(file);
    return 0;
}
3.2 按行读取:fgets函数

image-20260113100501322

  • 核心功能

文件指针stream指向的文件中读取一行字符,并把读取的字符存储在指针s所指向的字符串内,当读取到n-1个字符、或者已经读取到文件末尾(EOF)、或者读取到换行符’\n’时,则函数调用停止。

函数原型与头文件

  • 原型char *fgets(char *restrict s, int n, FILE *restrict stream);

  • 参数

    • s:指向存储读取数据的缓冲区指针。
    • n:缓冲区大小(最多读取 n-1 个字符,剩余 1 字节用于存储字符串结束符 \0)。
    • stream:目标文件的 FILE 类型指针。
  • 返回值说明

​ 成功:返回缓冲区指针 s(即使未读满 n-1 个字符,只要读取到有效字符或换行符即返回)。

​ 失败 / 文件末尾:

​ 若未读取到任何字符就到达文件末尾,返回 NULL,缓冲区内容不变。

​ 若读取过程中发生错误,返回 NULL,缓冲区内容不确定。

  • 关键特性与注意事项

停止条件:满足以下任一条件即停止读取:

  1. 读取到 n-1 个字符;
  2. 读取到换行符 \n(会保留在缓冲区中);
  3. 到达文件末尾(EOF)。

缓冲区要求:需确保缓冲区大小足够,避免数据截断;若一行数据长度超过 n-1,会分多次读取。

image-20260113100845639

问题:为什么fgets函数读取到换行符\n时会结束?fgets函数中的参数n的意义是什么??

用户调用fopen打开文件之后,可以把数据写入到文件中以及从文件中读取数据,但是实现读取和写入的过程中其实内核并没有直接操作文件,而是在操作指向文件的结构体指针FILE,也就是用户写入的数据和读取的数据会先存储在FILE结构体的*缓冲区*中,当用户调用刷新缓冲区的函数或者其他读写函数时,FILE结构体的缓冲区会被刷新,数据才会被系统写入文件。

image-20260113101451500

缓冲区

缓冲区的出现其实就是由于输入设备和输出设备对于数据的读写速度比较慢,其实就是CPU为了降低输入输出次数,目的是为了提高运行效率,避免长时间的等待,所以内核就在内存中提供了一块空间作为缓冲区,缓冲区也可以称为缓存(Cache),是属于内存空间的一部分。

根据IO设备的不同,可以把缓冲区分为输入缓冲区和输出缓冲区,同样,根据刷新形式的不同,可以把缓冲区分为三种:全缓冲、行缓冲、无缓冲

全缓冲:指的是当缓冲区被填满就立即把数据冲刷到文件、或者在关闭文件、读取文件内容以及修改缓冲区类型时也会立即把数据冲刷到文件,一般读写文件的时候会采用

无缓冲:指的是没有缓冲区,直接输出,一般linux系统的标准出错stderr就是采用无缓冲, 这样可以把错误信息直接输出。

行缓冲:指的是当缓冲区被填满(一般缓冲区为4KB,就是4096字节)或者缓冲区中遇到 换行符’\n’时,或者在关闭文件、读取文件内容以及修改缓冲区类型时也会立即把 数据冲刷到文件中,一般操作IO设备时会采用,比如printf函数就是采用行缓冲。

当然,全缓冲和行缓冲除了以上几种情况外,当程序结束时缓冲区也会被刷新,另外,也可以采用函数库中的fflush函数手动刷新缓冲区。

img

注意:对于标准输出stdout而言默认是采用行缓冲的,而对于标准出错stderr而言默认是采用无缓冲的,对于普通文件而言默认是采用全缓冲的。

3.3 按块读取:fread函数

image-20260113102030086

  • 核心功能

从文件流中按指定大小和数量读取数据块,适用于二进制文件(如图片、视频、可执行文件)或批量数据处理,读取效率高于逐字符 / 逐行读取。

  • 原型size_t fread(void *restrict ptr, size_t size, size_t nmemb, FILE *restrict stream);

  • 参数

ptr:指向存储数据的缓冲区指针(需提前分配足够空间)。

size:单个数据块的字节大小(如 sizeof(int)1024 等)。

nmemb:计划读取的数据块个数。

stream:目标文件的 FILE 类型指针。

  • 返回值说明

​ 成功:返回实际读取到的数据块个数(可能等于或小于 nmemb)。

​ 特殊情况:

​ 若 sizenmemb 为 0,返回 0,不执行读取操作。

​ 到达文件末尾:返回的块数小于 nmemb,剩余未读取的块数对应的数据未获取。

​ 读取错误:返回的块数小于 nmemb,需通过 ferror(stream) 确认。

问题:可以知道函数的返回值如果小于nmemb则说明可能出现读取错误或者到达文件末尾,那应该如何区分这两种情况?

可以通过标准库中提供的两个函数区分,一个函数是feof(),另一个则是ferror函数。

image-20260113102749625

通过 feof(stream) 判断是否到达文件末尾,ferror(stream) 判断是否存在读取错误。
feof 检测到末尾返回非零值 ferror 检测到出错返回非零值

posted @ 2026-01-13 11:05  郭小胖  阅读(0)  评论(0)    收藏  举报