C语言进阶——宏

目录

1.宏的定义

1.1无参宏定义

1.2带参宏定义

2.#define的替换规则

3.宏与函数的对比

3.1代码长度

3.2执行速度

3.3操作符优先级

3.4参数求值

3.5参数类型

3.6调试

3.7命名约定

4.#undef

5.#和##

5.1字符串化运算符(#)

5.2标记连接运算符(##)

6.条件编译

6.1#if、#elif、#else 和 #endif

6.2#ifdef 和#ifndef

7.预定义符号

8.小拓展:offsetof宏的实现

8.1offsetof宏

8.2MY_OFFSETOF


1.宏的定义

宏值是编程中一种常见的预处理指令,主要用于在代码中进行文本替换。它通过#define指令定义,允许开发者使用标识符(宏名)来表示特定的值或表达式,从而提高代码的可读性、可维护性和效率。宏值分为两种类型:无参宏定义和带参宏定义。

1.1无参宏定义

无参宏定义用于将标识符替换为固定的值或字符串。其基本格式为:

#define 宏名 替换值

例如:

甚至可以是一段语句(注意语句后不应加“;”)

1.2带参宏定义

带参宏定义类似于函数,但仅在预处理阶段进行文本替换,不进行类型检查。其格式为:

#define 宏名(参数1, 参数2, ...) 替换表达式

例如:

2.#define的替换规则

#define的替换规则总体分为以下几个步骤:
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们会被替换。

2.替换后被插入到程序中原来文本的位置,对于宏,参数名被他们替换。

3.最后,再次扫描,看看是否包含任何由#define定义的符号。如果是,重复上述过程,最终替换成没有宏的现象。

注意:
1.宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。(宏是没有递归的概念)
2.当预处理搜索#define定义的符号时,字符串常量的内容不被搜索

3.宏与函数的对比

3.1代码长度

宏:每次调用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度将大幅增加。

函数:函数代码只出现在一个地方,每次调用这个函数时,调用的都是那个地方的同一份代码。

3.2执行速度

宏:更快。

函数:存在函数调用/返回的额外开销,更慢。

3.3操作符优先级

宏:宏在使用时只会简单地将宏名替换而不会进行计算,参数的求值是在所有周围下上文环境里,除非给它们加上括号,否则邻近操作符的优先级,可能会产生不可预测的结果。

函数:函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值更容易预测。

3.4参数求值

宏:参数每次用于宏定义时。它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预料的结果。

函数:参数在函数被调用前只求值一次,在函数中多次使用参数,并不会导致多种求值过程,参数的副作用并不会造成任何特殊问题。

3.5参数类型

宏:宏与类型无关,只要参数的操作是合法的,它可以适用于任何参数类型。

函数:函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使它们的任务是相同的。

3.6调试

宏:宏不方便调试。

函数:函数可以逐句调试。

3.7命名约定

一般来讲函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者。

我们平时的习惯是:把宏名全大写,函数名不要全大写。

4.#undef

在C语言中,#undef指令用于取消之前定义的宏。这个指令的语法是#undef 标识符,它的作用是将前面定义的宏标识符取消定义。#undef的使用可以避免宏定义冲突,增强代码的可读性。

5.#和##

在C语言中,#和##是两个特殊的预处理运算符,主要用于宏定义中,分别实现字符串化和标记连接功能。

5.1字符串化运算符(#)

#运算符会将宏参数转换为字符串字面量。这在生成调试信息或打印变量名时非常有用。

在这里,#N将参数N转换为字符"a"。

5.2标记连接运算符(##)

##运算符将两个标记拼接成一个新的标记,常用于动态生成变量名或函数名。

此例中,CON(a, b)被展开为ab,并成功访问变量。

6.条件编译

在C语言中,程序员可以根据不同的条件,有选择性地编译不同的代码块(忽略其它的代码块),从而产生不同的目标文件,这种机制称为条件编译。

6.1#if、#elif、#else 和 #endif

#if、#elif、#else 和 #endif 允许我们根据特定的条件(一个表达式的值),来决定哪些代码段应该被编译,哪些应该被忽略。它们的基本用法如下:

#if 常量表达式1
    //代码段1
    //如果 常量表达式1 的值为真,则编译 代码段1,忽略其它代码
#elif 常量表达式2
    //代码段2
    //如果 常量表达式2 的值为真,则编译 代码段2,忽略其它代码
#else
    //代码段n
    //如果以上表达式的值都为假,则编译 代码段n,忽略其它代码
#endif

注意:

#if #else 命令和 if else 语句的用法非常类似,都是一种分支结构。但是,#if 命令要求判断条件为“常量表达式”,不能包含变量以及函数调用,而 if 语句后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。(#if #else 是在预处理阶段进行替换,无法获取变量的值,所以表达式中不能包含变量)

写完可以在末尾加上注释,以后如果嵌套使用方便查找。

6.2#ifdef 和#ifndef

#ifdef用于判断某个宏是否定义,如果宏已定义,则编译对应的代码块。#ifndef功能正好相反,二者仅支持判断单个宏是否已经定义。

常见用途:头文件保护。防止头文件被多次包含。(也可用#pragma once代替)

7.预定义符号

在C语言中,预定义符号是由编译器提供的特殊符号,具有特定的含义和功能。

常见的预定义符号:
1. __LINE__: 表示当前代码所在的行号。
2. __FILE__: 表示当前源文件的文件名。
3. __DATE__: 表示当前编译的日期,格式为"MMM DD YYYY",例如"Jul 29 2023"。
4. __TIME__: 表示当前编译的时间,格式为"HH:MM:SS",例如"10:30:36"。
5. __STDC__: 表示当前编译器是否符合C语言标准。如果定义了该符号,则表示编译器符合C语言标准;否则,表示不符合。
6. NULL: 表示空指针常量。

8.小拓展:offsetof宏的实现

8.1offsetof宏

offsetof 宏是 C 语言中的一个重要工具,它用于计算结构体成员相对于结构体开头的字节偏移量。这个宏定义在 stddef.h 头文件中,其返回值是一个 size_t 类型的整数,表示成员在结构体中的位置。

size_t offsetof(type, member);

type 是结构体的类型,而 member 是结构体中的成员。

8.2MY_OFFSETOF

我们假设了一个地址为0的结构体,通过这个地址去找到我们需要的成员,该成员的地址便是相对于结构体开头的字节偏移量,将其强制转化为size_t类型。这样便实现了offsetof宏的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值