文件编程的两种方式

                        1.标准IO的库函数实现:C库→系统

                         2.系统调用实现:系统中的函数

Linux系统将一切操作转换为文件:

目的:方便操作

文件的通用操作方式:①打开文件

                                    ②读取/编写文件

                                    ③关闭文件

一.库函数(标准C库中的标准IO函数)

通过减少对系统的调用来提高效率

对应函数:

 (1)打开文件:fopen

FILE *fopen(const char *pathname,const char *mode);

1.功能:流打开函数  打开pathname指定的文件,关联一个流

           ①流(stream):流就是内存和硬盘直接形成一个数据通道

           ②FILE *:流指针(描述的文件操作的这个动态对象) 关联到流上,并代表这个流

           ③fopen的作用:相当于将硬件上的文件转换成了内存的流指针

           ④errno:是系统设计错误的全局变量

                errno =2

                printf("想要输出的字符串");

                输出效果:想要的输出的字符串:错误原因

实参:FILE *fd=fopen(argv[i],"r")

2.参数 :① @pathname:表示打开的文件

               ②@mode:模式 

                【r  只读】【 r+ 可读可写】:文件必须存在

               【w 只写】【w+ 可读可写】:如果文件存在,截断成0;如果不存在,则创建一个文件                【a 追加的写】【a+ 追加的读写】:如果文件存在,则在末尾写,如果不存在,则创建

3.返回值:成功返回FILE *指针

                失败NULL并设置errno用来表明错误的原因

(2)读写:

                系统中,默认打开三个文件,直接用:

                                     流指针          相应设备      系统中的文件描述符

                标准输入        stdin          关联到键盘                  0

                标准输出        stdout        关联到屏幕                  1

                标准出错        stderr         关联到屏幕                  2

1.fgetc/fputc:单个字符读写

fgetc()

int  fgetc(FILE  *stream);

①功能:从stream中读取字符

实参:fgetc(fp);

②参数:@stream  要读取的文件流指针(FILE *stream就是上面的fp)

③返回值:

        成功:读取到字符的ASCII码值

        失败:EOF(文件结尾的标志EOF(-1),并不真正存在在文件中)

getchar()<=>fgetc(stdin)


fputc()

函数原型:int fputc(int c, FILE *stream)

功能:将字符c写到stream中

调用的实参:fputc(c,fp)

参数:@c:想要写的字符

          @steam:想要写入的文件()

返回值:成功:返回被写入的字符

               失败:返回EOF;

window系统中,实现文件操作是需要区分文本文件和二进制文件的

Linux系统中,操作是不区分文本文件和二进制文件


2.fgets/fputs:读写字符串

fgets和fputs实现文件的拷贝,对于二进制文件大量存在'\0',fgets读取文件没有问题,但是fputs输出字符串,遇到'\0'就会结束输出


fgets()

char *fgets(char *s,int size,FILE *stream);

①功能:从stream中读取一个字符串,将字符串保存到s指定的size大小一块空间

实参:fgets(buf,sizeof(buf),fp)

②参数:@s:表示存储读取结果的一块字符空间

               @size:表示一次读取多少个字节

               @stream就是要读取的文件

③返回值:

                成功:返回s

                失败:NULL

注意:

//读取文件结尾也返回NULL

//fgets读取结束,

        ①EOP会结束;

        ②遇到'\0'('\n'会被读到最终的buffer中),'\n'(换行)会读取结束;

        ③size空间读满会读取结束;

//fgets会自己在buffer中最后一个字符的后面加上'\0',处理成字符串。


fputs()

函数原型:char *fputs(char *s,FILE *stream);

实参:fputs(s,fp)

1.功能:将s中的字符串,输出到stream对应的文件中

2.参数:@s:表示的存放字符串的一块空间

           @stream:要输出的文件

3.返回值:成功 非负值

                失败 EOF

遇到'\0'输出会结束。(字符串的结束标志)

3.fread/fwrite

fread()

函数原型:size_t fread(void *ptr,szie_t size,size_t nmemb,FILE *stream);//按对象的二进制读写

实参:fread(buf,sizeof(char),1023,fp_s)

1.功能:       从指定的文件中stream中读取nmemb个元素,每个元素size大小的这么多数据,存放                      到指定ptr文件中

2.参数:     @ptr        存储数据的空间的首地址 :存储数据的空间char buf[1024]

                   @size        每个元素的大小 

                   @nmemb        元素的个数  1024-1;

                   @stream        要读取的文件:流指针:fp_s

3.返回值:     成功 返回读取到的对象个数

                       失败 返回0(达到结尾返回0)


fwrite()

函数原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

实参:fwrite(buf,szieof(char),ret,fp_d);

1.功能:        将ptr所在空间的nmemb个大小为size的

2.参数:        @ptr        存储数据的空间的首地址

                   @size        每个元素的大小

                   @nmemb        元素的个数

                   @stream        要写入的文件:流指针:fp_d

3.返回值:     成功 返回读取到的对象个数

                   失败 返回0(达到结尾返回0

fread和fwrite配合使用时

char buf[1024];

int ret = fread(buf,sizeof(char),1023,fp_s);
fwrite(buf,sizeof(char),ret,fp_d);

(3)关闭:fclose()

函数原型:int fclose(FILE *stream);

实参:fclose(fd);

1.功能:       关闭流指针,并且刷新流中的数据到指定位置,同时关闭底层的文件描述符

2.参数:       @stream:要关闭的流指针(文件)

3.返回值:   成功 返回0

                    失败 返回EOF


函数 功能描述
FILE *fopen(const char *path, const char *mode) 打开文件,返回文件指针(FILE*);mode 为打开模式(如 "r" 读,"w" 写)。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从文件读取数据到内存,返回成功读取的元素个数。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 将内存数据写入文件,返回成功写入的元素个数。
int fclose(FILE *stream) 关闭文件,释放资源,返回 0 表示成功。
int fgetc(FILE *stream) / int fputc(int c, FILE *stream) 读写单个字符。
char *fgets(char *s, int size, FILE *stream) / int fputs(const char *s, FILE *stream) 读写字符串(fgets 会读取换行符,fputs 不会自动添加换

辅助函数:feof 与 ferror

int feof(FILE *stream);   // 判断是否到达文件末尾
int ferror(FILE *stream); // 判断是否发生读写错误
示例:区分 EOF 与错误
#include <stdio.h>

int main(void)
{
    int ret;
    while ((ret = fgetc(stdin)) != EOF) {
        printf("ret = %c:%d\n", ret, ret);
    }

    if (feof(stdin)) {
        printf("file end!\n");
    } else if (ferror(stdin)) {
        printf("file error!\n");
    }
    return 0;
}

a
ret = a:97
file end!

其他格式化输出函数

函数 说明
printf(format, ...) 输出到标准输出
fprintf(fp, format, ...) 输出到文件流
sprintf(str, format, ...) 输出到字符串缓冲区
snprintf(str, size, format, ...) 安全版本,防止溢出

缓存机制

1.行缓冲,1k,terminal,主要用于人机交互

缓冲区满或者遇到\n刷新        //大小为1024 个字节

//行缓存多是关于终端的一些操作

刷新条件:1.遇到\n刷新

                  2.缓存区满刷新

                  3.程序正常结束刷新

                  4.fflush强制刷新        //fflush(stdout)

2.全缓冲,4K,主要用于文件的读写

缓存区满刷新缓存区        //大小为4096

//对普通文件进行标注IO操作,建立的缓存一般为全缓存

刷新条件:1.缓存区满刷新

                  2.程序结束刷新

                  3.fflush强制刷新        //fflush(fp);

./a.out > 1.txt        输出重定向

./a.out < 1.txt        输入重定向

3.无缓冲,0k,主要用于出错处理信息的输出 stderr

//不对数据缓存直接刷新

定位函数 --- fseek()  |  ftell()  |  rewind()

(1)fseek()

函数原型:int fseek(FILE *stream, long offset, int whence);

实参:fseek(fp,0,SEEK_SET);       //文件开头定位到偏移量为0的位置(开头)

fseek(fp,100,SEEK_SET);     //将文件开头向后偏移100

1.功能:将stream指定的文件,从whence的位置,定位到offset的位置

2.参数:@stream:表示要定位的文件

           @offset :偏移量       

                offset < 0,需要考虑参考点是否可以

                offset > 0,如果大于文件本身大小是可以的

                注意:文件往后偏移量如果大于文件本身是可以的,如果想占有存储空间,

                           必须要有写的操作多出来的空间,被称为空洞,用\0占位

           @whence :参考点

3.参考点:

        SEEK_SET        //文件开头

        SEEK_CUR        //文件当前位置

        SEEK_END        //文件末尾

4.返回值:     成功 返回0

                   失败 返回-1 && errno会被设置

        创建空洞文件:

                        1.先定位

                        2.偏移一定字节数(超过直接文件大小的偏移量)

                        3.然后做一次写操作(不管写入多大,如果小于偏移量,用\0占位)

创建空洞文件示例
#include <stdio.h>

int main(void)
{
    FILE *fp = fopen("hole.txt", "w");
    if (fp == NULL)
    {
        perror("fopen fail");
        return -1;
    }

    int n = 0;
    scanf("%d", &n);

    fseek(fp, n - 1, SEEK_SET);  // 定位到目标位置前一个字节
    fputc('\0', fp);              // 写入一个空字符,创建空洞

    fclose(fp);
    return 0;
}

效果:创建大小为 n 字节的空洞文件,中间填充 \0


模拟云盘下载:复制文件并创建空洞

#include <stdio.h>

int main(int argc, const char* argv[])
{
    if (argc != 3)
    {
        printf("Usage: %s <src> <dest>\n", argv[0]);
        return -1;
    }

    FILE *fp_s = fopen(argv[1], "r");
    FILE *fp_d = fopen(argv[2], "w");
    if (fp_s == NULL || fp_d == NULL)
    {
        perror("fopen fail");
        return -1;
    }

    // 获取源文件大小
    fseek(fp_s, 0, SEEK_END);
    long len = ftell(fp_s);
    printf("len = %ld\n", len);

    // 创建同大小空洞文件
    fseek(fp_d, len - 1, SEEK_SET);
    fputc('\0', fp_d);
    fflush(fp_d);

    // 重置指针,开始复制
    rewind(fp_s);
    rewind(fp_d);

    int ret = 0;
    while ((ret = fgetc(fp_s)) != EOF)
    {
        fputc(ret, fp_d);
    }

    fclose(fp_s);
    fclose(fp_d);
    return 0;
}

理想效果:成功复制文件,目标文件大小与源一致,内容完全相同。


(2)ftell()

函数原型:long ftell(FILE *stream);

实参:int len=ftell(fp);

1.功能:获得stream对应文件的当前位置指示器的值,获得文件的大小

(3)rewind()

函数原型:void rewind(FILE *stream);

实参:rewind(fp);

功能:将文件定位到开头

时间转换函数:

二.文件IO(系统调用)

函数 功能描述

int open(const char *pathname, int flags, mode_t mode)

打开文件,返回文件描述符(非负整数);flags 为打开标志(如 O_RDONLY 只读),mode 为创建文件时的权限(如 0644)。

ssize_t read(int fd, void *buf, size_t count)

从文件描述符 fd 读取数据到 buf,返回实际读取的字节数(0 表示文件末尾,-1 表示错误)。

ssize_t write(int fd, const void *buf, size_t count)

将 buf 中的数据写入 fd,返回实际写入的字节数(-1 表示错误)。

int close(int fd)

关闭文件描述符,返回 0 表示成功。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>  // 包含open函数的标志定义

int main() {
    // 打开源文件(只读)和目标文件(创建、只写、截断)
    int src_fd = open("source.txt", O_RDONLY);
    // O_WRONLY:只写;O_CREAT:不存在则创建;O_TRUNC:存在则清空
    int dest_fd = open("dest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (src_fd == -1 || dest_fd == -1) {
        perror("文件打开失败");
        exit(1);
    }

    // 缓冲区:一次读写1024字节
    char buf[1024];
    ssize_t n;
    // 循环读写,直到文件末尾(read返回0)
    while ((n = read(src_fd, buf, sizeof(buf))) > 0) {
        // 确保所有数据都被写入(可能需要多次write)
        ssize_t total_written = 0;
        while (total_written < n) {
            ssize_t written = write(dest_fd, buf + total_written, n - total_written);
            if (written == -1) {
                perror("写入失败");
                exit(1);
            }
            total_written += written;
        }
    }

    if (n == -1) {  // 检查read是否出错
        perror("读取失败");
        exit(1);
    }

    // 关闭文件描述符
    close(src_fd);
    close(dest_fd);
    printf("文件复制成功\n");
    return 0;
}
系统调用的打开标志(open 的 flags 参数)
标志 含义
O_RDONLY 只读打开。
O_WRONLY 只写打开。
O_RDWR 读写打开。
O_CREAT 若文件不存在则创建(需配合 mode 参数指定权限)。
O_TRUNC 若文件存在则清空内容。
O_APPEND 追加模式,写入数据到文件末尾。
文件权限与 umask

实际文件权限 = mode & ~umask

mode:  0666 (rw-rw-rw-)
umask: 0022 (----w--w-)
~umask:111101101
结果:0644 (rw-r--r--)
read 与 write 系统调用
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
  • read:从文件描述符读取数据到缓冲区
  • write:将缓冲区数据写入文件描述符

注意read 读取的数据若用于字符串处理,需手动添加 \0 结尾。

使用 read/write 实现 cat 功能
#include<stdio.h>
#include<unistd.h>

int main(void)
{
    char buf[1024];
    int ret = read(0, buf, sizeof(buf));  // 从stdin读
    write(1, buf, ret);                   // 写入stdout
    return 0;
}

运行结果:

$ ./a.out
hello
ret = 6 buf = hello

关键注意事项

  1. 错误处理:文件操作可能失败(如文件不存在、权限不足),需通过返回值(fopen 返回 NULLopen 返回 -1)判断,并使用 perror 打印错误原因。
  2. 资源释放:文件使用完毕后必须关闭(fclose 或 close),否则会导致资源泄漏(文件描述符耗尽)。
  3. 缓冲刷新:标准 IO 的缓冲数据不会立即写入磁盘,可通过 fflush 强制刷新,或在 fclose 时自动刷新。
  4. 二进制与文本模式:在 Windows 系统中,"b" 模式(如 "rb""wb")用于二进制文件(避免换行符转换),Linux 系统无区别。

(4)lseek 定位文件偏移

off_t lseek(int fd, off_t offset, int whence);
    • 功能:移动文件读写位置
    • 常用操作
      • lseek(fd, 0, SEEK_SET); → 定位开头
      • lseek(fd, 0, SEEK_END); → 定位末尾
      • off_t len = lseek(fd, 0, SEEK_END); → 获取文件大小

    三、标准 IO 与系统调用 IO 的核心区别

    维度 标准 IO(stdio.h) 系统调用 IO(unistd.h)
    接口类型 库函数(封装系统调用) 系统调用(直接与内核交互)
    缓冲机制 有缓冲(全缓冲、行缓冲、无缓冲) 无缓冲(每次调用直接操作硬件)
    操作对象 文件指针(FILE* 文件描述符(int,非负整数)
    效率 高(减少系统调用次数) 低(频繁调用系统调用,开销大)
    可移植性 高(符合 C 标准,跨平台一致) 低(不同操作系统的系统调用细节可能不同)
    适用场景 普通文件读写、文本处理、用户态应用程序 设备驱动、高性能 IO、需要精确控制的场景

    目录:

    (1)打开目录:opendir

    函数原型:DIR *opendir(const char *name)

    使用时需要添加头文件:

    #include<sys/types.h>

    #include<dirent.h>

    1.功能:打开一个目录文件并关联一个目录流

    2.实参:DIR *dp=opendir("目录名")

    3.参数:@name:要打开目录名字字符串

    4.返回值:

                    成功:目录流指针

                    失败:NULL并且errno会被设置


    (2)读写:目录没有写的操作,读readdir

    添加头文件:

    #include<dirent.h>

    函数原型:struct dirent *readdir(DIR *dirp)

    struct dirent {
               ino_t                         d_ino;                     
    索引节点编号 
               off_t                          d_off;                      
    指向节点的编号 
               unsigned short        d_reclen;                该编号并非偏移量; 此记录的长度
               unsigned char          d_type;                文件类型;并非所有文件系统类型都支持此类型
               char                           d_name[256];        空字符结尾的文件名 
           };

    1.实参:readdir(dp)

    2.功能:从dirp中读取目录项,返回对应一个结构体

    3.参数:@dirp:要操作的目录流指针

    4.返回值:

                    成功 :读到的某一个目录项的结构体指针

                    失败:NULL&&errno


    (3)关闭:closedir

    #include<dirent.h>

    函数原型:int clsoedir(DIR *dirp);

    实参:clsoedir(dp);

    1.功能:关闭目录流

    2.参数:dirp:目录流指针

    3.返回值:

                    成功 0

                    失败:-1并且errno会被设置


    硬链接 vs 软链接 对比
    特性 硬链接 软链接
    inode 编号 与原文件相同 不同
    是否占用空间 否(仅目录项) 是(存储路径)
    能否跨文件系统
    能否链接目录
    原文件删除后状态 仍可访问 变为无效(悬空链接)

    获取文件状态:stat 系统调用

    #include <sys/stat.h>
    int stat(const char *pathname, struct stat *statbuf);
    • 功能:获取指定路径文件的状态信息。
    • 参数
      • pathname:文件路径。
      • statbuf:接收信息的 struct stat 缓冲区。
    • 返回值
      • 成功:0
      • 失败:-1,并设置 errno
    struct stat 主要字段

    struct stat {
        dev_t     st_dev;         /* 包含文件的设备 ID */
        ino_t     st_ino;         /* inode 编号 */
        mode_t    st_mode;        /* 文件类型和权限 */
        nlink_t   st_nlink;       /* 硬链接数 */
        uid_t     st_uid;         /* 所有者 UID */
        gid_t     st_gid;         /* 所属组 GID */
        off_t     st_size;        /* 文件大小(字节) */
        blkcnt_t  st_blocks;      /* 分配的 512 字节块数 */
        time_t    st_atime;       /* 最后访问时间 */
        time_t    st_mtime;       /* 最后修改时间 */
        time_t    st_ctime;       /* 最后状态变更时间(如权限修改) */
    }

    文件类型宏定义(st_mode & S_IFMT
    宏定义 含义 对应字符
    S_IFSOCK 套接字 s
    S_IFLNK 符号链接 l
    S_IFREG 普通文件 -
    S_IFBLK 块设备 b
    S_IFDIR 目录 d
    S_IFCHR 字符设备 c
    S_IFIFO FIFO(命名管道) p
    权限宏定义(掩码)
    类别 宏定义 说明
    用户 S_IRUSR 读权限
    S_IWUSR 写权限
    S_IXUSR 执行权限
    S_IRGRP 组读权限
    S_IWGRP 组写权限
    S_IXGRP 组执行权限
    其他 S_IROTH 其他人读权限
    S_IWOTH 其他人写权限
    S_IXOTH 其他人执行权限
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <time.h>
    
    int main(int argc, const char *argv[])
    {
        if (argc != 2)
        {
            printf("Usage: %s <filename>\n", argv[0]);
            return -1;
        }
    
        struct stat st;
        if (stat(argv[1], &st) < 0)
        {
            perror("stat fail!\n");
            return -1;
        }
    
        // 文件类型名称与符号映射
        char type_name[7][20] = {"socket", "symbolic", "regular", "block", "directory", "character", "FIFO"};
        char type[7] = {'s', 'l', '-', 'b', 'd', 'c', 'p'};
        int i = 0;
    
        // 判断文件类型
        switch (st.st_mode & S_IFMT)
        {
        case S_IFSOCK: i = 0; break;
        case S_IFLNK:  i = 1; break;
        case S_IFREG:  i = 2; break;
        case S_IFBLK:  i = 3; break;
        case S_IFDIR:  i = 4; break;
        case S_IFCHR:  i = 5; break;
        case S_IFIFO:  i = 6; break;
        }
    
        // 输出基本信息
        printf("File: %s\n", argv[1]);
        printf("Size: %ld\tBlocks: %ld\tIO Blocks: %ld\t %s\n",
               st.st_size, st.st_blocks, st.st_blksize, type_name[i]);
        printf("Device: %ld\t Inode: %ld\t Links: %ld\n",
               st.st_dev, st.st_ino, st.st_nlink);
    
        // 输出权限字符串
        printf("Access: (%#o/", (st.st_mode & S_IRWXU) | (st.st_mode & S_IRWXG) | (st.st_mode & S_IRWXO));
        putchar(type[i]);
        st.st_mode & S_IRUSR ? putchar('r') : putchar('-');
        st.st_mode & S_IWUSR ? putchar('w') : putchar('-');
        st.st_mode & S_IXUSR ? putchar('x') : putchar('-');
    
        st.st_mode & S_IRGRP ? putchar('r') : putchar('-');
        st.st_mode & S_IWGRP ? putchar('w') : putchar('-');
        st.st_mode & S_IXGRP ? putchar('x') : putchar('-');
    
        st.st_mode & S_IROTH ? putchar('r') : putchar('-');
        st.st_mode & S_IWOTH ? putchar('w') : putchar('-');
        st.st_mode & S_IXOTH ? putchar('x') : putchar('-');
        putchar(')');
    
        printf(" Uid: (%d) Gid: (%d)\n", st.st_uid, st.st_gid);
    
        // 格式化输出时间
        struct tm *ptm = localtime(&st.st_atim.tv_sec);
        printf("Access: %04d-%02d-%02d %02d:%02d:%02d\n",
               ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
               ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    
        ptm = localtime(&st.st_mtim.tv_sec);
        printf("Modify: %04d-%02d-%02d %02d:%02d:%02d\n",
               ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
               ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    
        ptm = localtime(&st.st_ctim.tv_sec);
        printf("Change: %04d-%02d-%02d %02d:%02d:%02d\n",
               ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
               ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    
        return 0;
    }
    

    理想运行结果

    $ ./file_info hello.c
    File: hello.c
    Size: 1234    Blocks: 8    IO Blocks: 4096     regular
    Device: 2052     Inode: 123456     Links: 1
    Access: (0644/-rw-r--r--) Uid: (1000) Gid: (1000)
    Access: 2025-08-14 11:30:12
    Modify: 2025-08-14 11:29:45
    Change: 2025-08-14 11:29:45

    Logo

    智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

    更多推荐