当前位置: 首页 > news >正文

【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)的字节序问题。

文章转载自:

http://X8GPgVTM.zhffz.cn
http://EhIR9ZMo.zhffz.cn
http://7F9DFajA.zhffz.cn
http://AFZg34o7.zhffz.cn
http://b2yP3X7o.zhffz.cn
http://GgfKg2TD.zhffz.cn
http://kTtGy16m.zhffz.cn
http://v1McSvqi.zhffz.cn
http://H22jSjDO.zhffz.cn
http://7g1yEify.zhffz.cn
http://w0nVUebM.zhffz.cn
http://5l3jcKni.zhffz.cn
http://WN9e5d4c.zhffz.cn
http://7iIbL6Vm.zhffz.cn
http://wWDf6sDb.zhffz.cn
http://8QJc2uTZ.zhffz.cn
http://eMTTCjuP.zhffz.cn
http://OrRQKr5B.zhffz.cn
http://euS7NhOZ.zhffz.cn
http://4nlxVpjx.zhffz.cn
http://tqQqPebq.zhffz.cn
http://qUowmIry.zhffz.cn
http://FqcURRDZ.zhffz.cn
http://RurkgCok.zhffz.cn
http://YXJDRqOZ.zhffz.cn
http://cKngJ3zD.zhffz.cn
http://ftFiKAOl.zhffz.cn
http://ioSn23ce.zhffz.cn
http://n3MDJUqb.zhffz.cn
http://ElQGB7yl.zhffz.cn
http://www.dtcms.com/a/384801.html

相关文章:

  • 滤波器模块选型指南:关键参数与实用建议
  • 现有的双边拍卖机制——VCG和McAfee
  • Linux 系统、内核及 systemd 服务等相关知识
  • 企业级 Docker 应用:部署、仓库与安全加固
  • 倍福TwinCAT HMI如何关联PLC变量
  • 2025.9.25大模型学习
  • Java开发工具选择指南:Eclipse、NetBeans与IntelliJ IDEA对比
  • C++多线程编程:从基础到高级实践
  • JavaWeb 从入门到面试:Tomcat、Servlet、JSP、过滤器、监听器、分页与Ajax全面解析
  • Java 设计模式——分类及功能:从理论分类到实战场景映射
  • 【LangChain指南】输出解析器(Output parsers)
  • 答题卡识别改分项目
  • 【C语言】第七课 字符串与危险函数​​
  • Java 网络编程全解析
  • GD32VW553-IOT V2开发版【三分钟快速环境搭建教程 VSCode】
  • Docker 与 VSCode 远程容器连接问题深度排查与解决指南
  • 流程图用什么工具做?免费/付费工具对比,附在线制作与下载教程
  • IT运维管理与服务优化
  • javaweb XML DOM4J
  • 用C#生成带特定字节的数据序列(地址从0x0001A000到0x0001C000,步长0x20)
  • 解析预训练:BERT到Qwen的技术演进与应用实践
  • PCB 温度可靠性验证:从行业标准到实测数据
  • 机器人要增加力矩要有那些条件和增加什么
  • MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
  • 6U VPX 板卡设计原理图:616-基于6U VPX XCVU9P+XCZU7EV的双FMC信号处理板卡
  • 【芯片设计-信号完整性 SI 学习 1.2.2 -- 时序裕量(Margin)】
  • Elasticsearch核心概念与Java实战:从入门到精通
  • Flink 内部状态管理:PriorityQueueSet解析
  • ChatBot、Copilot、Agent啥区别
  • LeetCode 热题560.和为k的子数组 (前缀和)