【C语言】第八课 输入输出与文件操作​​

C语言中的输入输出(I/O)和文件操作是与计算机交互和管理数据的基础。

📝 1. 标准输入输出(标准I/O)

标准I/O主要用于与控制台(终端)进行交互。

1.1 输出函数 printf

printf 函数用于向标准输出(通常是屏幕)发送格式化后的数据。

  • 函数原型

    int printf(const char *format, ...);
    
    • format: 这是一个字符串,包含了要输出的文本以及格式控制说明符(占位符)。
    • ...: 可变参数列表,提供需要插入到格式字符串中的数据,数量与类型必须与格式控制说明符匹配。
  • 常用格式说明符

    说明符含义
    %d有符号十进制整数
    %u无符号十进制整数
    %f / %lffloat / double 类型浮点数 (对于 printf%f 也可用于 double)
    %c单个字符
    %s字符串
    %p指针地址
    %x十六进制整数(小写字母)
    %%输出一个百分号
  • 示例

    #include <stdio.h>
    int main() {
        int age = 25;
        double height = 1.75;
        printf("Age: %d, Height: %.2f meters\n", age, height); // 保留两位小数
        return 0;
    }
    
1.2 输入函数 scanf

scanf 函数用于从标准输入(通常是键盘)读取格式化输入。

  • 函数原型

    int scanf(const char *format, ...);
    
    • format: 格式控制字符串,指定如何解析输入数据。
    • ...: 必须是变量的地址(使用 & 取地址操作符,字符串名除外)。
  • 重要注意事项

    • 安全风险scanf 在读取字符串时不会检查目标缓冲区的大小,极易导致缓冲区溢出绝对避免使用 %s 而不指定长度。
    • 安全替代:使用 fgets 读取一行到缓冲区,再用 sscanf 解析,或使用带宽度的 scanf (如 %9s 用于大小为10的char数组)。
    • 返回值:返回成功读取并赋值的输入项数,可用于检查输入是否成功或遇到文件结尾(EOF)。
  • 示例

    #include <stdio.h>
    int main() {
        int num;
        char str[10];
        printf("Enter a number and a string (max 9 chars): ");
        if (scanf("%d %9s", &num, str) == 2) { // 检查返回值,并限制字符串读取长度
            printf("You entered: %d and %s\n", num, str);
        } else {
            printf("Input error!\n");
        }
        return 0;
    }
    
1.3 其他标准I/O函数
  • getchar() / putchar(char c): 用于读取和写入单个字符
  • gets() / puts(): 避免使用 gets(),因为它无法限制输入长度,极其危险。用 fgets(char *s, int size, stdin) 代替。

💾 2. 文件输入输出(文件I/O)

文件I/O用于将数据持久化保存到存储设备,或从存储设备读取数据。

2.1 文件指针与 fopen

在C语言中,操作文件通常使用 FILE 结构体指针(文件流指针)。

  • 打开文件 - fopen:

    FILE *fopen(const char *filename, const char *mode);
    
    • filename: 要打开的文件名(包含路径)。
    • mode: 文件打开模式。
    • 返回值:成功时返回指向FILE对象的指针,失败时返回 NULL务必检查返回值!
  • 常用文件打开模式

    模式含义文件不存在时
    "r"只读打开文本文件返回NULL
    "w"只写打开文本文件。截断文件长度至0(清空原内容)创建新文件
    "a"追加模式打开文本文件,写入数据被加到文件末尾创建新文件
    "r+"读写打开文本文件返回NULL
    "w+"读写打开文本文件。截断文件长度至0(清空原内容)创建新文件
    "a+"读写打开文本文件,写入数据被加到文件末尾创建新文件
    "rb", "wb", "ab", "r+b", etc.与上述类似,但以二进制模式打开文件,用于非文本数据

    示例

    #include <stdio.h>
    int main() {
        FILE *fp;
        fp = fopen("data.txt", "r"); // 尝试以只读模式打开文件
        if (fp == NULL) { // 必须检查文件是否成功打开
            perror("Error opening file");
            return 1;
        }
        // ... 文件操作
        fclose(fp); // 最后别忘了关闭文件!
        return 0;
    }
    
2.2 文件读写函数

文件打开后,可以使用多种函数进行读写。

  • 格式化文件读写

    • int fprintf(FILE *stream, const char *format, ...); - 类似于 printf,但输出到文件。
    • int fscanf(FILE *stream, const char *format, ...); - 类似于 scanf,但从文件读取。
    fprintf(fp, "Number: %d\n", 100); // 将格式化字符串写入文件
    fscanf(fp, "%d", &num);           // 从文件中读取格式化数据
    
  • 字符读写

    • int fgetc(FILE *stream); - 从文件读取一个字符。
    • int fputc(int char, FILE *stream); - 向文件写入一个字符。
  • 字符串(行)读写

    • char *fgets(char *str, int n, FILE *stream); - 从文件读取一行字符串,最多读取 n-1 个字符,自动添加 '\0'相对安全
    • int fputs(const char *str, FILE *stream); - 向文件写入一个字符串。
  • 二进制数据块读写 (非常重要!):

    • 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);
      • ptr: 指向要读取或写入的数据内存块的指针。
      • size: 每个数据项的字节大小(常用 sizeof() 获取)。
      • nmemb: 要读取或写入的数据项个数。
      • stream: 文件指针。
      • 返回值:成功读取或写入的数据项个数(若非 nmemb,可能遇到错误或文件尾)。

    示例:读写结构体数组

    #include <stdio.h>
    struct Student {
        char name[20];
        int age;
    };
    int main() {
        struct Student stu_list[2] = {{"Alice", 20}, {"Bob", 21}};
        struct Student read_list[2];
        FILE *fp = fopen("data.bin", "wb"); // 二进制写入
        if (fp == NULL) return 1;
        // 将整个结构体数组写入文件
        size_t written = fwrite(stu_list, sizeof(struct Student), 2, fp);
        fclose(fp);
        fp = fopen("data.bin", "rb"); // 二进制读取
        if (fp == NULL) return 1;
        // 从文件中读取整个结构体数组
        size_t read = fread(read_list, sizeof(struct Student), 2, fp);
        fclose(fp);
        printf("Read %zu students.\n", read);
        return 0;
    }
    
2.3 文件随机存取

文件位置指针指示当前读写位置。你可以移动它来实现随机访问。

  • int fseek(FILE *stream, long offset, int whence); - 移动文件位置指针。
    • offset: 偏移量。
    • whence: 基准位置,常用:
      • SEEK_SET - 文件开头。
      • SEEK_CUR - 当前位置。
      • SEEK_END - 文件末尾。
  • long ftell(FILE *stream); - 返回当前文件位置指针的位置(相对于文件开头的字节偏移量)。
  • void rewind(FILE *stream); - 将文件位置指针重置回文件开头。
2.4 关闭文件与错误处理
  • int fclose(FILE *stream); - 非常重要! 关闭已打开的文件流,释放系统资源并确保缓冲区数据写入磁盘。文件操作完毕后必须关闭
  • 错误处理函数
    • void perror(const char *s); - 打印最近一次系统错误的信息,前面可加上自定义字符串 s
    • int ferror(FILE *stream); - 测试给定流上的错误标识符,如果设置了则返回非零值。

🔧 3. 文件描述符与系统调用(底层I/O)

在Unix/Linux系统中,C标准库的文件操作函数(如fopen, fread)底层是基于系统调用实现的。

  • 文件描述符(File Descriptor, fd)

    • 是一个小的非负整数,用于在进程内唯一标识一个打开的文件。
    • 每个进程启动时默认打开三个标准流,其文件描述符为:
      • 0: 标准输入stdin
      • 1: 标准输出stdout
      • 2: 标准错误stderr
    • 用户打开文件时,系统会分配一个新的、当前未用的最小文件描述符(如3, 4, 5…)。
  • 常用系统调用函数 (需包含 <unistd.h><fcntl.h>):

    系统调用功能说明类比标准I/O函数
    open()打开或创建文件,返回文件描述符fopen()
    read()从文件描述符读取数据fread(), fgetc()
    write()向文件描述符写入数据fwrite(), fputc()
    close()关闭文件描述符fclose()
    lseek()移动文件偏移指针(类似 fseekfseek()
  • 示例:使用系统调用复制文件

    #include <unistd.h>
    #include <fcntl.h>
    #define BUF_SIZE 8192
    int main() {
        int src_fd = open("source.txt", O_RDONLY);
        int dest_fd = open("dest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
        char buf[BUF_SIZE];
        ssize_t num_read;
        while ((num_read = read(src_fd, buf, BUF_SIZE)) > 0) {
            write(dest_fd, buf, num_read);
        }
        close(src_fd);
        close(dest_fd);
        return 0;
    }
    

💎 核心概念总结与对比

概念/操作标准I/O (缓冲I/O)底层I/O (系统调用,无缓冲I/O)
核心对象FILE 结构体指针 (文件流)文件描述符 (fd) - 整数
打开fopen()open()
关闭fclose()close()
读取fread(), fscanf(), fgetc(), fgets()read()
写入fwrite(), fprintf(), fputc(), fputs()write()
定位fseek(), ftell(), rewind()lseek()
优点带缓冲,通常效率更高;移植性好;格式化读写方便更接近操作系统,控制更精细;某些特定操作必须使用
缺点缓冲可能带来延迟;某些底层操作不支持需要自己管理所有细节;通常更复杂

🛡️ 4. 最佳实践与安全注意事项

  1. 始终检查返回值:无论是 fopen, fread, fwrite, scanf 还是系统调用,必须检查其返回值以确保操作成功,这是编写健壮程序的基础。
  2. 始终关闭文件:使用 fcloseclose 释放资源,防止资源泄漏。
  3. 警惕缓冲区溢出:避免使用不安全的函数(如 gets, 不加限制的 %s in scanf),优先使用 fgets 或指定宽度。
  4. 理解文本模式与二进制模式:在Windows平台上,文本模式(无"b")会对换行符(\n\r\n)进行转换,而二进制模式(有"b")则不会。处理非文本文件(如图片、视频)或需要跨平台时,务必使用二进制模式
  5. 注意字节序(Endianness):在不同系统间通过二进制格式传输数据时,要考虑多字节数据(如int)的字节序问题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值