C语言基础第21天:预处理与文件操作详解

预处理

预处理功能

条件编译

概念

定义:根据设定的条件选择待编译的语句代码

预处理机制:将满足条件的语句进行保留,将不满足条件的语句进行删除,交个下一步编译

语法:

        语法1:根据是否找到标记,来决定是否参与编译(标记存在为真,不存在为假

#ifdef 标记 // 标记 一般使用宏定义
... 语句代码1
#else
... 语句代码2
#endif

        说明: printf("调试模式!\n"); printf("调试模式!\n"); 只能保留一个。 undef 取消已定义的宏(使其变为未定义状态)

#define DEBUG 1 // 定义宏
#undef DEBUG // 取消定义的宏
#ifdef DEBUG
    printf("调试模式!\n"); // 删除
#else
    printf("产品模式!\n"); // 保留
#endif

        语法2根据是否找到标记,来决定是否参与编译(标记不存在为真,存在为假

#ifndef 标记
... 语句代码1
#else
... 语句代码2
#endif

        语法3根据表达式的结果,来决定是否参与编译(表达式成立为真,不成立为假

// 单分支
#if 表达式
... 语句代码1
#endif
// 双分支
#if 表达式
... 语句代码1
#else
... 语句代码2
#endif
// 多分支
#if 表达式1
... 语句代码1
#elif 表达式n
... 语句代码n
#else
... 语句代码n+1
#endif

文件包含

概念:所谓“文件包含处理是指一个源文件可以将另一个源文件的全部内容包含进来。通常用于共享代码、声明或宏定义。一个常规的C语言程序会包含多个源文件( *.c ),当某些公共资源需要在各个源文件中使用时,为了避免多次编写相同的代码,我们一般会进行代码的抽取( *.h ),然后在各个源文件中直接包含即可

注意: *.h 中的函数声明必须要在 *.c 中有对应的函数定义,否则没有意义。(函数一旦声明,就一定要定义)

基本语法

标准库包含(使用尖括号)(会到/usr/include目录下查找)

#include <stdio.h> // 包含标准输入输出库 会到/usr/include目录下查找
#include <stdlib.h> // 包含标准库函数

自定义文件包含(使用双引号)(会先在当前目录下查找,找不到再到/usr/include目录下查找)

#include "myheader.h" // 包含当前目录下的自定义头文件
#include "utils/tool.h" // 包含子目录下的头文件

预处理机制

将文件中的内容替换文件包含指令

使用场景

头文件包含:通常将函数声明、宏定义、结构体定义等放在 .h 头文件中,通过 #include 引入到需要使用的 .c 文件中

代码复用:可以将一些通用代码片段(如工具函数)放在单独的文件中,通过包含实现复用

注意事项

避免循环包含(如 a.h 包含 b.h ,同时 b.h 又包含 a.h

为防止头文件被重复包含,通常会使用条件编译保护(推荐):

// 在myheader.h中
#ifndef MYHEADER_H // _MYHEADER_H, __MYHEADER_H
#define MYHEADER_H
... 头文件内容
#endif

或者使用 #pragma once (非标准但被大多数编译器支持)

#pragma once
... 头文件内容

其他指令(了解)

1. #line 用于修改当前的行号和文件名(主要用于编译器调试,很少手动使用)

2. #error 在编译阶段当条件满足时抛出错误信息,并终止编译

3. #pragma 用于向编译器传递特定指令(不同编译器支持的 #pragma 功能不同)

例如:

  • #pragma once :确保头文件只被包含一次(类似 #ifndef 的效果),简单但兼容性稍差
  • #pragma pack(n) :设置结构体成员的对齐方式为 n 字节
  • #pragma warning(disable: 1234) :禁用特定警告编号的编译警告

库文件

什么是库文件

        库文件本质上是经过编译后生成的可被计算机执行的二进制代码。但注意库文件不能独立运行,库文件需要加载到内存中才能执行。库文件大量存在于Windows,LinuxMacOS 等软件平台上。

库文件的分类

  • 静态库
    • windowsxxx.lib
    • linuxlibxxxx.a
  • 动态库(共享库)
    • windowsxxx.dll
    • linuxlibxxxx.so.major.minor (libmy.so.1.1)

注意:不同的软件平台因编译器、链接器不同,所生成的库文件是不兼容的。

静态库与动态库的区别

        1. 静态库链接时,将库中所有内容包含到最终的可执行程序中(程序和库合一)

        2. 动态库链接时,将库中的符号信息包含到最终可执行文件中,在程序运行时,才将动态库中符号的具体实现加载到内存中(程序和库分离)。

静态库与动态库的优缺点

1. 静态库

  • 优点:生成的可执行程序不再依赖静态文件
  • 缺点:可执行程序体积较大

2. 动态库

  • 优点:生成的可执行程序体积小;动态库可被多个应用程序共享
  • 缺点:可执行程序运行依然依赖动态库文件

静态库与动态库对比

库文件创建

Linux系统下库文件的命名规范: libxxx.a (静态库) libxxxx.so (动态库)

静态库文件的生成

1. 将需要生成库文件对应的源文件( *.c )通过编译(不链接)生成 *.o 目标文件

2. ar 命令将生成的 *.o 打包生成 libxxx.a

动态库文件的生成

1. 将需要生成库文件对应的源文件( *.c )通过编译(不链接)生成 *.o 目标文件

2. 将目标文件链接为 *.so 文件

注意:如果在代码编译过程或者运行中链接了库文件,系统会到 /lib /usr/lib 目录下查找库文件,所以建议直接将库文件放在 /lib 或者 /usr/lib ,否则系统可能无法找到库文件,造成编译或者运行错误。

扩展内容

1. 查看应用程序(例如:app)依赖的动态库

2. 动态库使用方式:

  • 编译时链接动态库,运行时系统自动加载动态库
  • 程序运行时,手动加载动态库
  • 实现:
    • 涉及内容
      • 头文件: #include <dlfcn.h>
      • 接口函数: dlopen、dlclose、dlsym
      • 依赖库: -ldl
      • 句柄handler:资源的标识
    • 示例代码:
#include <stdio.h>
#include <dlfcn.h>
int main(int argc,char *argv[])
{
    // 1. 加载动态库 "/lib/libdlfun.so"
    // - RTLD_LAZY: 延迟绑定(使用时才解析符号,提高加载速度)
    // - 返回 handler 是动态库的句柄,失败时返回 NULL
    void* handler = dlopen("/lib/libdlfun.so", RTLD_LAZY);
    if (handler == NULL)
    {
        // 打印错误信息(dlerror() 返回最后一次 dl 相关错误的字符串)
        fprintf(stderr, "dlopen 失败: %s\n", dlerror());
        return -1;
    }

    // 2. 从动态库中查找符号 "sum"(函数名)
    // - dlsym 返回 void*,需强制转换为函数指针类型 int sum(int *arr, int size);
    // - 这里假设 "sum" 是一个接受两个int*,int参数、返回 int 的函数
    int (*paddr)(int*, int) = (int (*)(int*,int))dlsym(handler, "sum");
    if (paddr == NULL)
    {
        fprintf(stderr, "dlsym 失败: %s\n", dlerror());
        dlclose(handler); // 关闭动态库(释放资源)
        return -1;
    }

    // 3. 调用动态库中的函数 "sum",计算{11,12,13,14,15}的累加和
    int arr[5] = {11,12,13,14,15};
    printf("sum=%d\n", paddr(arr, sizeof(arr)/sizeof(arr[0])));
    // 4. 关闭动态库(释放内存和资源)
    dlclose(handler);

    return 0;
}

文件I/O

文件基础概念

文件定义

存储在外存储器(磁盘、U盘、移动硬盘等)上的数据集合,是操作系统管理数据的基本单位。

文件操作核心

  • 文件内容的读取(输入操作 Input
  • 文件内容的写入(输出操作 Output

文件缓冲机制

  • 系统为文件在内存中创建缓冲区(通常4KB大小)
  • 程序操作实际上是在缓冲区进行
  • 数据像水流一样流动,称为文件流
  • 缓冲机制减少了直接访问外存的次数,提高效率

文件分类

1. 文本文件(ASCII文件)

  • ASCII码形式存储
  • 可直接用文本编辑器查看
  • 示例:.txt.c.h文件

2. 二进制文件

  • 以二进制形式存储
  • 需要特定程序才能解析
  • 示例:.exe.jpg.dat文件

文件标识

1. 文件系统中:路径 + 文件名

  • Windows示例: D:\edu\test.txt
  • Linux示例: /home/user/data.bin

2. C程序中:文件指针(FILE *类型)

FILE *fp; // 声明文件指针

文件操作的基本步骤

1. 打开文件:建立程序与文件的连接

2. 文件处理/读写文件:读写操作

3. 关闭文件:释放资源

文件打开与关闭

fopen()

说明:打开文件

函数原型:

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);

参数详解:

  • path :文件路径(绝对路径或相对路径)
  • mode :打开模式
    • 基本模式(ASCII模式):
      • r 以只读方式打开文件,文件指针指向文件起始位置。
      • r+ 以读写方式打开文件,文件指针指向文件起始位置。
      • w 以写的方式打开文件,如果文件存在则清空,不存在就创建,文件指针指向文件起始位置。
      • w+ 以读写方式打开文件,如果文件不存在则创建,否则清空,文件指针指向文件起始位置。
      • a 以追加方式打开文件,如果文件不存在则创建,文件指针指向文件末尾位置。
      • a+ 以读和追加方式打开文件,如果文件不存在则创建,如果读文件则文件指针指向文件起始位置,如果追加()则文件指针指向文件末尾位置。
    • 二进制模式:
      • rb wb ab 等,其实就是基本模式的前提下,加 b
    • 特殊模式:
      • x :独占模式(与 w 组合,文件必须不存在)

返回值:

  • 成功:返回文件指针(指向FILE结构体)
  • 失败:返回NULL(需要检查errno

fclose()

说明:关闭文件

函数原型:

#include <stdio.h>
int fclose(FILE *fp);

参数

  • fp (文件指针):指向要关闭的 FILE 对象的指针,通常由 fopen() 或类似函数返回。

返回值

  • 成功:返回 0 (即 EXIT_SUCCESS )。
  • 失败:返回 EOF (通常是 -1 ),并设置 errno 指示错误原因(如磁盘已满、文件被占用等)。

注意事项:

  • 必须关闭所有打开的文件
  • 程序退出时未关闭的文件可能丢失数据
  • 多次关闭同一文件指针会导致未定义行为
#include <stdio.h>
#include <stdlib.h>
int main()
{
    // 打开文件
    FILE *fp = fopen("texst.txt","w");
    if (!fp)
    {
        perror("打开文件失败!");
        return -1; // exit(1);
    }
    // 读写文件
    // 关闭文件
    if(fclose(fp) != 0)
    {
        perror("关闭文件失败!");
        return -1;
    }

    return 0;
}

文件顺序读写

单字符读写

fgetc()

说明:单字符的读取

函数原型:

int fgetc(FILE *fp);

参数说明:

  • fp :待操作的文件指针

返回值:

  • 成功:返回字符的ASCII
  • 失败:或者文件末尾返回EOF-1

fputc()

说明:单字符的写出

函数原型:

int fputc(int c, FILE *fp);

参数说明:

  • c :待写出的字符的ASCII
  • fp :待操作的文件指针

返回值:

  • 成功:返回写入的字符的ASCII
  • 失败:返回EOF-1

行读写(多字符读写)

fgets()

说明:读取字符串(字符数组)

函数原型:

#include <stdio.h>
char* fgets(char *buf, int size, FILE *fp);

功能描述:

  • 从指定的文件流( fp )中读取一行字符串(以 \n 结尾),并将其存储到缓冲区 buf 中。
  • 读取的字符数最多为 size - 1 (保留一个位置给 \0 )。
  • 如果遇到 换行符 \n 文件结束符 EOF ,则停止读取。
  • 读取的字符串末尾会自动添加 \0 (空字符),使其成为合法的 C 字符串。

参数:

  • buf (缓冲区):用于存储读取数据的字符数组(必须足够大)。
  • size (最大读取长度):最多读取 size - 1 个字符(防止缓冲区溢出)。
  • fp (文件指针):要读取的文件流(如 stdin fopen() 返回的指针等)。

返回值

  • 成功:返回 buf 的指针(即读取的字符串)。
  • 失败或到达文件末尾(EOF:返回 NULL

fputs()

说明:写入字符串

函数原型:

int fputs(const char *str, FILE *fp);

功能描述:

  • 将字符串 str 写入到指定的文件流 fp 中。
  • 不自动添加换行符:与 puts() 不同, fputs 不会在末尾自动添加 \n
  • 遇到 \0 停止:写入过程遇到字符串结束符 \0 时终止( \0 本身不会被写入)。

参数:

  • str :要写入的字符串(必须以 \0 结尾)。
  • fp :目标文件流(如 stdout 、文件指针等)。

返回值:

  • 成功:返回一个非负整数(通常是 0 或写入的字符数)。
  • 失败:返回 EOF -1),并设置 errno 指示错误原因(如磁盘已满、文件不可写等)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值