前言

#define 通常用作定义常量
#define Pi 3.14

甚至替换类型之类的操作#define 整型 int
以至于当时无聊的时候还真有人用define去写中文编程语言

1
2
3
4
5
6
7
8
#include<iostream>
#define 整数 int
#define 输出 std::cout

int main(){
整数 a = 100;
输出 << a;
}

也是能跑的,但是玩归玩

宏定义的名称也与变量名称规则一致,不允许数字开头


正文

正经情况

实际上嘛,常量除了define还有个const
#define定义在头上可能是因为更直观吧,像windows api里面就有一堆#define

1
2
#define SCREEN_WIDTH 1980
const int screen_width{1980};

c时期define用的多,c++可能更多的用const,但是因为先学c在学c++,所以习惯是潜移默化的。
const定义的好处就是类型检测,限定类型肯定更加严谨。
而#define则相对无脑替换,你可以用它做一些非正常逻辑的事。比如#define true false,真就是真真假假假假真真。
而且有一种情况下,#define会出事,就是只定义了要替换的名,而不定义要替换成什么。#define x,你没有东西要替换

1
2
3
4
5
#define x
int main(){
int x a = 2500;
std::cout << a;
}

要注意不能是int xa,因为这是一个独立的存在,int x a,x才会被宏替换

但是这种在特定情况下有特殊意义:

1
2
3
4
5
#define _in_
#define _out_
int add(_in_ int a,_in_ int b){
return a + b;
}

这种情况下,无非就是像注释一样告诉你这个参数是要输入的。
不过这里值得一提的是jetbrains的软件很早就做了提示形参的功能。
虽然vs2022 在后面版本也支持了类似的功能。
但是这种方法在早期编写的时候的确带来了很大帮助。


undef

之前玩头文件的时候知道防重复除了#pragma once,还有个#ifdef DEBUG #endif,表示从define标记的地方开始到结束。
然后这里还有个undef,是表示取消define。

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>

#define _Test_

int main(){

int _Test_ a = 100;
#undef _Test_
int _Test_ b = 200;

return 0;
}

看到当undef掉_Test_的时候,无法再次使用这个宏。

由于编译特性是自上而下,逐条编译,所以就导致了即使这个undef写在其他函数或者地方,即使没有调用一样也已经删掉这个宏了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>

#define _Test_

void hh(){
#undef _Test_
std::cout << "hello\n";
}

int main(){

int _Test_ a = 100;
#undef _Test_
int _Test_ b = 200;

return 0;
}

所以说有的是bug都是人为的哈哈哈。。


复杂的宏

简单的常量替换已经没啥问题了,宏还有更厉害的就是可以设计表达式

1
2
3
#define SUM(x,y) x+y
#define AVE(x,y) (x+y)/2
#define ToBig(x,y) (x > y ? x : y)

这种表达式就比函数又更骚了。但是他不同于函数,函数要入栈,又要入参,又要保持栈平衡。
在预编译的时候就会展开头文件替换所有的宏。

其次在动态开辟的时候,每次都要手动释放delete。那么也同样可以通过宏去干掉。

1
2
3
4
5
6
7
#define DeNullPtr(x) delete[] x;x=nullptr;

...
int main(){
int *a = new int[100];
DeNullPtr(a);
}

同样是没问题的。
地址是0是因为nullptr的结果。
换个角度也其实挺像内联的,


# 可以将一个标识符参数字符串化

1
2
#define SHOW(x) std::cout << #x
SHOW(1234fg); //=> std::cout << "1234fg";

## 可以连结两个标识符

1
2
#define T1(x,y) void x##y(){std::cout << #y;}
T1(test, 01)

这样就会生成一个函数

看到两个井号连结在了一起,将x和y当作函数名,然后输出y即可。
实际上肯定也能调用显示:
输出01是因为宏定义的时候输出的y部分,要想输出test也可以输出x即可。


结语

define的骚操作取决于程序员有多骚了可以说是。