预处理和多文件编程

1. 预处理
1.1 概述(了解)

C 语言程序在编译过程中,完成的操作,可以将代码中需要进行预处理的内容,例如 :宏,头文件引入,条件编译……在预处理阶段加载完毕

gcc -E 目标.c -o 目标.i # 预处理,将文件中的预处理内容引入到代码中
gcc -S 目标.i -o 目标.s # 编译,预处理的 C 文件转换为汇编文件
gcc -c 目标.s -o 目标.o # 汇编,将汇编文件转换为二进制文件
gcc 目标.o -o a.out # 链接,将二进制 .o 组装打包为可执行文件
1.2 宏
1.2.1 无参宏
  • 格式:

     #define COUNT 10
    /*
    #define 标识符 替换数据
    	1. 标识符通常要大写,不同的单词之间采用下划线分隔
    	2. 替换数据可以是数值常量,字符常量,字符串等等。
    	3. 替换数据之后没有分号 ; 
    	4. 标识符和替换数据之间没有赋值号 = 
    */
    
  • 理解含义:

    无参宏可以理解为带有名称的常量,预处理阶段采用最基本的替换逻辑,将代码中使用的无参宏替换为 宏 对应的数据。

  • 【注意】:由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。

#include <stdio.h>

//在预处理过程中,宏之后是什么,就替换什么
#define COUNT 10

int main(int argc, char const *argv[])
{

    /*
    #define COUNT 10
        预处理结果
        printf("count : %d\n", 10); 

    #define COUNT 10;
        预处理结果
        printf("count : %d\n", 10;); 
    */
    printf("count : %d\n", COUNT);   

    printf("%s:%d\n", __FILE__, __LINE__);

    /*
    #define COUNT 10
        预处理结果
        10 = 200;
        无参宏数据一旦在定义时确定,后续无法修改,本身就不是变量仅提供宏之后的代码内容,或者数据
    */
    // COUNT = 200;
    return 0;
}
1.2.2 有参宏
  • 格式:

     #define F(a, b) (a + b)
    /*
    #define 标识符(参数1,参数2,...,参数n) 替换列表 
    */
    
  • 理解含义:

    带有参数的宏,会将用户提供的所谓的宏参数替换到后续的代码中,这里需要关注替换之后的结果情况,必要时可以提供小括号保证代码的完整性。

  • 【注意】:标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义。

#include <stdio.h>

#define F(a, b) (a + b)

int main(int argc, char const *argv[])
{   
    printf("结果:  %d\n", F(2, 5));
    /*
    #define F(a, b) a + b
        printf("结果:  %d\n", 5 * F(2, 5));
        预处理替换之后
            printf("结果:  %d\n", 5 * 2 + 5);
    
    #define F(a, b) (a + b)
        printf("结果:  %d\n", 5 * F(2, 5));
        预处理替换之后
            printf("结果:  %d\n", 5 * (2 + 5));
    */
    printf("结果:  %d\n", 5 * F(2, 5));
 
    return 0;
}
1.3 条件编译
1.3.1 #if
  • #if 条件语句编译判断

    #if 之后需要一定的条件判断,通常是常量整数表达式,必须有 #endif 结尾。

  • 【注意】

    #if是要判断它后面表达式的真假,为真执行 #if 后的代码,直到 #else 结束或者 #endif 结束。

#include <stdio.h>

#define JH 0
#define HG 1

int main(int argc, char const *argv[])
{
#if JH
    printf("今天中吃黄焖鸡米饭!\n");
#else
    printf("今天中吃杨记炒拉条(城东路东大街交叉口西北角) 新疆拉条烧烤城!\n");
#endif
    return 0;
}
1.3.2 #ifdef
  • #ifdef 是判断当前代码中指定【宏】是否存在
    • 如果存在,执行 #ifdef 之后的代码,直到 #else 或者 #endif 出现为止。(无论 #else#endif 谁先出现)
    • 不存在,如果有 #else 那么宏不存在会执行#else#endif 之间的语句,#else 可以不使用
  • 【注意】
    • #ifdef 只是判断后面的宏有没有定义,而不在乎宏的值,宏是 0 是 1 对它来说都没有区别,只要预先定义了,执行 #ifdef 后的代码
    • 必须有 #endif 配合使用
#include <stdio.h>

/*
当前定义了一个宏,但是没有提供任何的替换要求和数据
仅声明当前宏存在
*/
#define JH
/*
undef 后面对应的宏名称,表示取消当前宏定义
*/
// #undef JH

int main(int argc, char const *argv[])
{
/*
#ifdef 是用于判断当前代码中指定宏是否存在,如果有
执行 #ifdef 到 #else 之间的语句,不存在,执行
#else 到 #endif 之间的语句,#else 可以不使用
*/
#ifdef JH
    printf("今天中吃黄焖鸡米饭!\n");
#else
    printf("今天中吃杨记炒拉条(城东路东大街交叉口西北角) 新疆拉条烧烤城!\n");
#endif
    return 0;
}
1.3.3 #ifndef

#ifndef#ifdef 作用相反,

  • 宏 如果不存在,执行#ifndef 之后的内容。
  • 存在执行会执行#else#endif 之间的语句。
#include <stdio.h>

/*
当前定义了一个宏,但是没有提供任何的替换要求和数据
仅声明当前宏存在
*/
#define JH
/*
undef 后面对应的宏名称,表示取消当前宏定义
*/
#undef JH

int main(int argc, char const *argv[])
{
/*
#ifndef 表示如果指定宏没有定义执行 #ifndef 到 #else 或者 到 #endif 
内容,如果对应宏定义了,执行 #else 到 #endif 之间的内容,或者不执行
任何 #ifndef 包括内容
*/
#ifndef JH
    printf("今天中吃黄焖鸡米饭!\n");
#else
    printf("今天中吃杨记炒拉条(城东路东大街交叉口西北角) 新疆拉条烧烤城!\n");
#endif
    return 0;
}
1.3.4 #undef

取消宏定义

#define JH // 定义了一个宏
#undef JH // 取消当前定义宏
#include <stdio.h>

// 定义了宏 VALUE 替换数据为 100
#define VALUE 100

int test();

int main(int argc, char const *argv[])
{
    printf("main value : %d\n", VALUE); // 100
    test();

// 取消 VALUE 宏,从取消位置开始,整个代码无法再次使用对应的宏
#undef VALUE

// 重新定义了一个 宏,宏对应的范围是从定义位置开始,到文件末尾
#define VALUE 10000
    return 0;
}

int test()
{
    printf("test value : %d\n", VALUE); // 10000
}
2. 自定义头文件
2.1 头文件概述
  • 头文件是 .h 文件,主要是 C/C++ 的声明文件

  • 作用:

    • 定义宏
    • 声明数据类型,数据类型别名定义
    • 声明函数
  • 固定的文档格式

    #ifndef 头文件名称英文全拼大写
    #define 头文件名称英文全拼大写
    
    // 头文件内容
    
    #endif
    
  • 例子:jh.h

    #ifndef JH
    #define JH
    
    // 头文件内容
    
    #endif
    
    #include <stdio.h>
    #include "jh.h"
    
    int main() {}
    
    • 以上.c 代码预处理之后

      #ifndef JH // 当前  JH 宏没有定义,执行#ifndef 到 #endif 之间的内容
      #define JH // 当前条件编译执行之后,代码中已经定义了 JH 宏
      
      // 头文件内容
      
      #endif
      #ifndef JH // 再次引入对应的头文件,JH 已存在,#ifndef 到 #endif 之间内容不参与编译
      	// 不会出现,头文件多次引入,类型,函数多次声明。
      #define JH
      
      // 头文件内容
      
      #endif
      int main() {}
      
2.2 头文件案例

在这里插入图片描述

student.h

#ifndef STUDENT
#define STUDENT
/*
头文件中的内容
    1. 声明的数据类型
    2. 声明的函数
    3. 引入其他头文件
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct student
{
    int id;
    char name[32];
    int age;
} Student;

/*
根据用户提供的数据内容,创建一个 Student 数据,数据所在内存空间为【堆区】

@param id   学生 ID
@param name 学生姓名
@param age  学生年龄
@return 创建成功返回学生在内存堆区的地址,否则返回 NULL
*/
Student *create_student(int id, char *name, int age);

/*
展示学生相关数据

@param stu 学生数据在内存中的首地址
*/
void show(Student *stu);

/*
释放学生占用的内存空间

@param stu 学生数据所在内存【堆区】空间首地址
*/
void free_student(Student *stu);

#endif

student.c

/*
<> 方式引入头文件,是直接在系统指定库中搜索目标头文件
"" 方式引入头文件,是在当前文件夹中搜索目标头文件,如果没有找到
到系统指定库文件夹搜索

一般情况下,"" 引入头文件都是自定义头文件
*/
#include "student.h"

Student *create_student(int id, char *name, int age)
{
    Student *stu = (Student *)malloc(sizeof(Student));

    if (NULL == stu)
    {
        return NULL;
    }

    memset(stu, 0, sizeof(Student));

    stu->id = id;
    strcpy(stu->name, name);
    stu->age = age;

    return stu;
}

void free_student(Student *stu)
{
    free(stu);
}

void show(Student *stu)
{
    printf("ID: %d, Name: %s, Age: %d\n",
           stu->id, stu->name, stu->age);
}

main.c

/*
虽然在 student.h 已经存在对应的头文件,但是建议系统头文件
如果在不同的文件中,需要再次引入,避免丢失必要的头文件内容
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "student.h"

int main(int argc, char const *argv[])
{
    Student *stu = create_student(1, "JH", 56);

    show(stu);
    free_student(stu);

    return 0;
}

编译

gcc main.c student.c
# 得到 a.out 可执行文件
  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值