C语言-预处理篇

0 前言

>>返回AUTOSAR系列文章目录<<

预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器处理代码中的宏命令,在处理完毕后删除所有宏命令

所有的预处理器命令都是以井号#开头。井号#必须是第一个非空字符,但是#include define之间可以有任意空格

预处理器命令不是C语言,所以不以分号;结尾

预处理有以下功能:

  • 文本处理相关功能:
    ① 文件引用和避免重复引用
    ② 定义宏常量
    ③ 定义宏函数
    ④ 代码剪裁

  • 编译器指令相关功能:
    ⑤ 内存标签
    ⑥ 嵌入式汇编

1 文件引用和避免重复引用

1.1 文件引用

文件引用是单纯的文本替换功能,在生成预编译.i文件时,将被引用文件的文本内容替换到#include语句所在的行

#include语句可以用来引用任意类型的文本文件,但是最常用的是引用头文件。头文件是扩展名为.h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享


有两种类型的头文件:程序员编写的头文件和编译器自带的头文件:

#include <file>
#include "file"

#include < >引用的是系统自带库,它在系统目录的标准列表中搜索名为 file 的文件

#include " "引用的是用户自定义库。它在包含当前文件的目录中搜索名为 file 的文件


文件引用无论引用了多少层,都会被替换。预处理器首先替换掉当前#include语句,如果替换后出现新的#include语句,则再次替换,直到全部#include语句替换完成


1.2 避免重复引用

复杂的嵌套引用是,必然会产生一些头文件被多次引用。头文件被多次引用在预处理时不报错,但是在代码编译时会报错

为避免重复引用,通常会引用每个头文件都定义一个宏常量,如果宏常量已经被定义,则不再引用该头文件

// my_example.h文件
#ifndef _MY__EXAMPLE_H  //如果_MY__EXAMPLE_H没被定义,则执行#endif之前的内容
# define _MY__EXAMPLE_H

//code

#endif

这样,my_example.h文件只会被引用一次


2 宏定义常量

2.1 用法

宏定义常量由三部分组成,第一部分是#define本身,第二部分是选定的缩写,也被称为。第三部分称为替换文本。从宏变成替换文本的过程称为宏展开

#define 宏 替换文本
  • 注意,无论是宏还是替换文本,都是纯文本。宏定义只负责把文本换成文本,不会进行任何计算分析。怎样理解文本是程序进程中进行的

  • 注意,宏不能与代码中出现的任何标签的一部分重名,否则将会直接将其替换掉

  • 所以宏要全部大写,并且命名要长,不要单独使用ON,OFF,UP,DOWN,RUN这些常用词汇

  • 但是“ ”内的文本不会被认为是宏:

/***** 示例程序 *****/
#include <stdio.h>
#define TWO 2
int main()
{
    printf("TWO = %d", TWO);
    return 0;
}


/***** 示例结果 *****/
TWO = 2

2.2 示例


#define常用来定义常量和字符串常量,例如:

#define PI 3.1415926
#define FILE_PATH E:\folder1\1.txt

#define 中还可以包含其他宏:

#define TWO    2
#define Four   TWO*TWO

#define也可以用来替换C表达式。例如:

#include <stdio.h>
#define PX printf("X is %d.\n", x)

int main()
{
    int x = 2;
    PX;
    return 0;
}

3 宏定义函数

3.1 用法

#define 可以创建类似函数的类函数宏,可以带一个或多个参数。类函数宏名也要全大写

#include <stdio.h>
#define SQUARE(X)  X * X
int main()
{
    int x, y, z;
    x = SQUARE(2);      // x = 2 * 2 = 4
    y = SQUARE(2+2);    // y = 2 + 2 * 2 + 2 = 8
    z =4/SQUARE(2);     // Z = 4 / 2 * 2 = 4
    return 0;
}

示例中X可以被任何文本替换,但是替换后的文本计算优先级难以控制,所以在使用中最好将替换文本里的变量都加上(),整个替换体也加上()

#include <stdio.h>
#define SQUARE(X)  ((X) * (X))
int main()
{
    int x, y, z;
    x = SQUARE(2);      // x = ((2) * (2)) = 4
    y = SQUARE(2+2);    // y = ((2 + 2) * (2 + 2)) = 16
    z =4/SQUARE(2);     // Z = 4/((2) * (2)) = 1
    return 0;
}

有参数的宏支持多个参数:

#define MAX(X, Y)  ((X) > (Y) ? (X) : (Y))

3.2 宏运算符

编译器不会识别宏运算符,它们会在预处理时被执行。宏运算符仅允许出现在宏的替换体中。

3.2.1 宏延续运算符 \

一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符\

#define MSG "Hello \
	world"

3.2.2 字符串常量化运算符 #

字符串常量化运算符#只会出现在有参数的宏当中,且被放在宏参数前面。

“”中的宏无法被替换,#用于解决这个问题。注意,#X本身依然不能放在“”

#X == "X"   
/***** 示例程序 *****/
#include <stdio.h>
#define MSG(a,b) printf("I am " #a",I am "#b)
int main(void)
{
        MSG(chuhe,20);
        return 0;
} 


/***** 示例结果 *****/
I am chuhe,I am 20

3.2.3 标记粘贴运算符 ##

标记粘贴运算符##只会出现在有参数的宏当中,且被放在宏参数前面,用于将两个标识符合并为一个标识符。

#define MK_ID(i)   n##i

int MK_ID(1), MK_ID(2);   // 等效于 int n1,n2;

4 代码剪裁

在大型工程中,通常会写全功能的代码,但某些功能在本次项目不需要,则可以使用代码剪裁,可以让代码中一些不执行部分在编译时被删除,节省代码容量

//my_example.h
#define STD_ON        0
#define STD_OFF       1
#define PROJECT1_SWITCH STD_ON
//my_example.c
#include "my_example.h"

#if(PROJECT1_SWITCH == STD_ON)
//project1 code
#endif

project1 code部分代码只会在#define PROJECT1_SWITCH STD_ON时才编译,在#define PROJECT1_SWITCH STD_OFF时不参与编译

>>返回AUTOSAR系列文章目录<<

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值