参考C/C++宏的基本使用方法附例子讲解
宏提供了一种机制,可以使你在编译器替换代码中的符号或者语句,当你代码中存在大量相似的、重复的代码的时候,使用宏可以极大减少代码量。
宏的定义
简单定义
// 定义圆周率
#define PI 3.14159265
// 定义一个空指针
#define NULL ((void*)0)
// 定义一个宏的名字为 SYSTEM_API,但是没有值
#define SYSTEM_API
- C语言中的NULL就是一个预定义 的宏,也就是说编译器之前已经编译好了。
在预编译的时候,编译器会把这些宏替换成它所定义的值。
携带参数
#include<iostream>
#define FUN(x,y) x/y
using namespace std;
int main(){
int res=FUN(8,2);
cout<<res<<endl;//4
return 0;
}
看起来像函数,但是和函数是有区别的:
- 宏是简单的符号替换,不会检查参数类型,函数会严格检查参数类型
- 宏是预处理期间进行的符号替换,所以在运行的时候,不会带来额外的时空开销,函数会在运行的时候执行压栈出栈操作,存在函数调用开销。所以宏也是没有返回值的,而函数调用有返回值
- 宏不能调试,函数可以单步调试
- 宏不支持递归,函数支持递归
- 宏不用在最后加分号
# 和##
# 将一个宏参数转换为字符串
## 把前后两个值连接起来
#define VAR(index) INT_##index
int VAR(1);
// 宏被展开后将成为 int INT_1;
可变长参数
#include<iostream>
using namespace std;
#define trace(fmt, ...) printf(fmt, ##__VA_ARGS__)
int main(){
trace("got a number %d", 34);//got a number 34
return 0;
}
- 使用系统预定义的宏__VA_ARGS__来代替printf的参数
- 添加“##”是为了防止__VA_ARGS__为空时前面那个逗号没有被删除
多行宏
宏的内容比较长转换成多行,用“\"来分隔
避免文件重定义
在一个大的工程里面,可能会有多个文件同时包含一个头文件,这些文件编译链接成一个可执行文件时就会出现大量重定义错误。在头文件中使用#define、#ifndef、#ifdef、#endif能避免头文件重定义。
条件编译
满足某条件就对一组语句进行编译
#ifdef 标识符
程序段1
#else
程序段2
#endif
宏中容易出现的问题
优先级改变
这里容易出错的是:如果用表达式,宏定义只是把x,y简单的换成表达式的内容,然后就会受到运算符优先级的影响,可以通过加上括号来解决这个问题
#include<iostream>
#define FUN(x,y) x/y;
using namespace std;
int main(){
int res1=FUN(5+5,2);
cout<<res1<<endl;//false,7
int res2=FUN((5+5),2);
cout<<res2<<endl;//true,5
return 0;
}
宏参数中逗号带来歧义
参数中的逗号会被视作分隔符
宏和typedef的区别
typedef
C++除了可以声明结构体、共用体、枚举类型之外、还可以用typedef声明一个新的类型名来代替已有的类型名。
- typedef可以声明类型名,但不能用来定义变量
- typedef并没有创造新的类型
- typedef有利于程序的通用与移植
typedef和宏定义的区别
- 宏只要用于定义常量以及书写复杂的内容,typedef主要用于定义类型别名
- 宏替换发生在预处理阶段,属于文本插入替换;typedef是编译的一部分,定义的类型名字是一个独立的类型
- 宏不检查数据类型;typedef会检查数据类型
- 宏不是语句不在最后加分号;typedef是语句
- #define没有作用域的限制,但是typedef有自己的 作用域:函数体内定义的typedef类型名只在该函数内部有效;类内部的只在该类和派生类中有效;与命名空间也有关系。
- typedef char * p_char和#define p_char char *差别很大
前者是一个类型定义,它是char类型的指针;后者定义p_char作为char的类型别名
typedef char * p_char1 #define p_char2 char * p_char1 a,b;p_char2 c,d
其中,a,b,c都是char*类型,而d为char类型(因为define只是做了一个简单的文本替换,有点像之前运算符优先级那个例子)
define和const的区别
编译阶段不同
前者是在预处理阶段编译,后者是在编译、链接、运行的时候起作用
安全性
- define只做替换,不做类型检查和计算,不求解,最好加上一个大括号包含住全部内容不然容易出现类似前面的运算符优先级问题、#define p_char char *之后 p_char c,d替换问题。
- const有数据类型,编译器可以对其类型进行安全检查
内存占用
- 前者将宏名称进行替换,内存中会产生多份相同的备份;后者在程序运行中只有一个备份,并且可以常量折叠
c++中的常量折叠:编译器通过编译器优化进行常量传播和常量折叠;常量传播指的是常量表达式的值在编译时保持不变,常量折叠指的是编译器在编译时用常量表达式的结果替换表达式,将复杂的表达式计算出结果放入常量表
- 宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但是要分配内存空间
处理之后占用空间
define预处理之后占用代码段空间,而const占用数据段空间
是否可以重定义
define通过undef取消定义之后可以重新定义某个符号;const不能重定义。
define独特功能
可以防止文件重复引用。
宏和内联函数的区别
内联函数
内联函数是一种优化手段,用来减少函数调用的开销,提高程序的运行效率。内联函数通常在定义时使用inline关键字来声明。
内联函数的原理是将函数的代码直接嵌入到调用该函数的代码中,而不是通过函数调用的方式执行函数体,可以省去函数入栈、调用跳转等操作。
内联函数和宏定义 的区别
安全性
- 宏只做简单的字符串替换,内联函数可以进行参数类型检查(编译时),且具有返回值
- 内联函数不会产生歧义;而宏定义有可能产生歧义,所以要把参数括起来
- 总结来说就是内联函数有类型检测、语法判断而宏没有
编译过程
宏只是简单的文本替换;内联函数在编译的时候可以直接把函数代码嵌入到目标代码中,可以实现重载
类型
#define是关键字;inline是函数
内联函数适用场景
- 宏定义可以用的地方都可以使用inline函数
- 作为类成员接口函数来读写私有成员或者保护成员,可以提高效率
- 函数体较小一般不超过10行左右
- 函数调用频繁
- 函数中没有复杂的循环递归结构
因为内联函数代码会被直接嵌入到调用函数的位置,内部循环语句会被在每个调用点展开,这会使代码体积变大
- 函数内没有使用外部变量或者静态变量
如果使用了外部变量或者静态变量会导致程序在编译的时候无法确定这些变量的值;为了保证程序正常运行编译器只能将这个内联函数按照普通方式来进行调用。(如果外部变量或者静态变量在函数外部进行定义而在函数内部只是进行访问没有修改,那么内联函数优化有可能会正常进行)
如果想要在内联函数内部使用这些变量,可以考虑将静态变量和外部变量作为内联函数的参数进行传递。为什么无法确定这些变量的值?
- 因为编译器在编译代码的时候会将程序分成多个编译单元,每个编译单元都是独立编译的,每个编译单元只知道自己看到的变量的定义,如果静态变量或者外部变量定义位于不同编译单元中,就无法在编译的时候确定值。而且静态变量和外部变量有可能被其他代码修改,那么就无法在编译的时候确定值。