目录
一、#define vs typedef vs using vs 引用&
学习日志|Day 3
第三篇文章主要梳理了以下关键字的高频考点:
define / typedef / using / 引用& / inline / const
主要是对以下关键字进行对比总结:
define vs typedef vs using vs 引用 、define vs inline 、const vs define
一、#define vs typedef vs using vs 引用&
1. 简要回答
-
#define:预处理阶段的字符串替换,没类型、没作用域、没检查。 -
typedef(以及C++11的using):给类型起别名,参与编译,有类型检查,更安全。
2. 详细解释
1)#define
#define是预处理指令,在编译之前就已经把宏展开完了,编译器根本“看不到”宏本身,只看到展开后的结果。- 不参与类型系统 → 不做类型检查。
- 宏只是文本替换 → 容易出错(比如少括号)。
- 没有作用域的概念,只要宏未被
#undef,全文件有效。
2)typedef
typedef是在 类型系统里起别名:- 只是给已有类型取一个新名字,本身不分配内存。
- 参与编译,有类型检查(写错类型编译器会报错)。
- 遵守作用域规则:在块内/命名空间内定义则只在对应作用域内可见。
3)C++11 using(现代 C++ 的首选方式)
using 是 typedef 的现代替代写法:
using TPINT = int*;
它比 typedef 更强大:
-
语法更自然(左边是名字,右边是类型)
-
可用于模板别名(typedef 做不到)
template<typename T>
using Vec = std::vector<T>;
-
能给函数指针、STL类型别名写出更清晰的形式;
typedef void (*Func1)(int, double);
using Func2 = void(*)(int, double);
推荐规则:
现代 C++ 中:能用
using就不用 typedef。
4)typedef 与 引用(reference)
很多人误以为 typedef 和引用都“起别名”,其实不对。
(1)引用
-
给“变量”起别名,而不是给类型起别名(不是类型别名)
-
必须立即绑定一个对象
-
不能绑定 null
-
不能更换绑定关系(不能重新指向其他对象)
-
本质是 变量别名,不是类型别名
-
运行期概念(和具体对象绑定)
(2)typedef 是“类型别名”
typedef int& Ref;
(3)引用是“变量别名”
int a = 10;
int& r = a;
引用必须绑定到某个对象,且无法“重新绑定”。
(3) 两者关系
引用不等价 typedef 的一个关键证明
-
typedef 可以“给引用类型本身起别名”,但引用语义不因此改变。
-
typedef 不是引用机制。
示例:
int a = 1, c = 2;
int& b = a; // b 绑定 a
b = c; // 把 c 的值赋给 a,a 变成 2
如果引用是 typedef,应该能像下面这样改变“别名绑定对象”:
typedef int Alias;
Alias x = a; // 是新变量,不是别名
x = c; // 不影响 a
结论:
typedef 只是类型别名,只在编译期起名字; 引用是对象别名,运行期必须绑定某个变量,两者完全不同,只是 typedef 恰好可以给引用类型起别名。
3. 图表总结
| 对比项 | #define | typedef / using(类型别名) | 引用(reference,变量别名) |
|---|---|---|---|
| 本质 | 预处理器文本替换 | 类型别名(编译期) | 变量别名(运行期) |
| 生效阶段 | 预处理阶段 | 编译阶段 | 运行阶段 |
| 类型检查 | ❌ 无 | ✅ 有 | 不涉及类型检查(绑定对象后就是对象本身) |
| 是否有作用域 | ❌ 无(只要未 #undef) | ✅ 遵循 C++ 作用域规则 | 取决于引用的作用域 |
| 是否分配内存 | ❌ 不分配 | ❌ 不分配(只是类型别名) | ❌ 无额外内存(通常优化为同一对象) |
| 是否绑定具体对象 | ❌ 不绑定 | ❌ 不绑定 | ✅ 必须立即绑定且不可重新绑定 |
| 是否可重新绑定 | - | - | ❌ 不能修改引用的绑定关系 |
| 可处理复杂类型 | ❌ 容易出问题 | ⭐ 非常适合(特别是函数指针、模板容器) | ❌ 不适合(不是类型别名) |
| 模板别名 | ❌ 不支持 | ⭐ using 完全支持 | ❌ 不相关 |
| 是否能给引用起别名 | ❌ 不能 | ✅ 可以 typedef int& R | ❌ 引用不是“类型别名”,不能互换 |
| 推荐程度(现代 C++) | ❌ 不推荐用于类型 | ✔ typedef 可用 / ⭐ using 最推荐 | 引用不参与类型定义,是另一类机制 |
| 使用场景核心定位 | 字符串替换、常量宏 | 类型别名(复杂类型重命名) | 变量别名(指向具体对象) |
4. 代码示例
#include <iostream>
using namespace std;
// 宏定义“指针类型”
#define PINT int*
// typedef 定义指针类型别名
typedef int* TPINT;
// using 定义指针类型别名(现代C++推荐)
using UPINT = int*;
// typedef 给引用类型起别名(注意语义)
typedef int& RefInt;
int main() {
// 宏展开后:int* a, b; → a是指针,b是普通int!
PINT a, b;
TPINT c, d; // c、d 都是 int* 指针
UPINT e, f; // e、f 也都是 int*,using 效果最好
int x = 10, y = 20;
a = &x;
// b = &y; // 编译错误(b不是指针)
c = &x;
d = &y;
// 使用 typedef 定义的引用类型
RefInt r = x; // r 是 x 的引用
r = y; // r = y 表示 x = y,而不是 r 绑定到 y!
cout << *a << " " << *c << " " << *d << endl;
cout << "x = " << x << ", y = " << y << ", r = " << r << endl;
return 0;
}
10 10 20
x=20 y=20 r=20
5. 面试常问 & 易错点
-
Q:typedef 会分配内存吗?
A:不会,它只是给类型起一个别名。 -
Q:用宏定义函数和 typedef 定义函数指针的区别?
A:宏函数是纯文本替换,无类型检查;函数指针是完整的类型,调用时会做类型检查。 -
Q:实际开发中推荐用哪个来做“类型别名”?
A:肯定是typedef(C++11 以后更推荐using =)。宏只用于少量简单场景。 -
Q:typedef 与 define 的区别?
A:类型系统 vs 文本替换。 -
Q:typedef 与 using 的区别?
A:using 支持模板,是现代 C++ 推荐写法。 -
Q:typedef 与引用是什么关系?
A:typedef 可以给“引用类型”起别名,但引用是变量别名,两者完全不同。
define 是“替换”,typedef 是“类型”,using 是“现代类型”,引用是“对象别名”。
6. 一句话总结
#define 是预处理阶段的纯文本替换,没有任何类型检查,也没有作用域,全局生效,适合简单常量和宏;
而 typedef 是编译阶段参与类型系统的类型别名,会进行严格的类型检查,遵循作用域规则,更安全也更现代;
using 则是 C++11 引入的更强大的类型别名语法,语义清晰、支持模板,是现代 C++ 的首选写法;
而引用(reference)虽然看起来像“别名”,但它是运行期绑定的变量别名,不属于类型别名体系,不能重新绑定,与 typedef/using 的编译期类型别名本质完全不同。
总结一句话:define 是“替换”,typedef/using 是“类型”,引用是“对象”,各自语义和用途完全不同,能用类型别名绝不用宏。」
二、#define vs inline
1. 简要回答
#define 是预处理阶段的纯文本宏替换,没有类型、没有作用域、没有检查;
inline 是编译阶段的内联函数,请求编译器将函数体展开,属于类型系统、语义安全、可调试。
宏只是“替换”,inline 是真正的“函数”。
2. 详细解释
1)#define(宏函数)
-
纯文本替换,不属于 C++ 类型系统
-
不做类型检查,参数可能被多次求值
-
没有作用域,除非手动
#undef -
调试器看不到宏本身
-
容易引发副作用,例如
SQR(a++)导致 a++ 执行两次
典型示例:
#define SQR(x) ((x) * (x))
若调用:
SQR(a++);
实际展开为:
((a++) * (a++))
产生严重副作用。
2)inline(内联函数)
-
仍然是真正的函数(有类型检查)
-
编译器可选择是否内联(不是强制)
-
避免函数调用开销(栈操作、跳转等)
-
参数只求值一次(安全)
-
作用域清晰(和普通函数一致)
-
可在调试器中看到并单步调试
-
若函数体过大/有循环/递归 → 编译器会拒绝 inline
示例:
inline int sqr(int x) {
return x * x;
}
3. 图表总结
| 对比项 | #define(宏函数) | inline(内联函数) |
|---|---|---|
| 本质 | 文本替换 | 函数(参与类型系统) |
| 类型检查 | ❌ 无 | ✅ 有 |
| 作用域 | ❌ 无 | ✅ 有 |
| 参数多次求值风险 | ⭐ 高 | ❌ 无 |
| 调试支持 | ❌ 看不到宏 | ⭐ 可正常调试 |
| 副作用风险 | 大 | 小 |
| 编译器优化权 | 无 | 编译器可决定是否内联 |
| 推荐程度 | ❌ 不推荐 | ⭐ 强烈推荐 |
4. 代码示例
#include <iostream>
using namespace std;
#define SQR_MACRO(x) ((x) * (x))
inline int sqr_inline(int x) {
return x * x;
}
int main() {
int a = 3;
int r1 = SQR_MACRO(a++); // a++ 执行两次
a = 3;
int r2 = sqr_inline(a++); // a++ 执行一次
cout << "宏函数 r1=" << r1 << " a=" << a << endl;
cout << "inline r2=" << r2 << " a=" << a << endl;
return 0;
}
输出:
宏函数 r1=12 a=5
inline r2=9 a=4
| 对比 | 宏版本 SQR_MACRO(a++) | inline 版本 sqr_inline(a++) |
|---|---|---|
| 是否多次求值 | 是(两次 a++) | 否(一次 a++) |
| 执行序列 | 3++ → 4++ | 3++ |
| a 最终值 | 5 | 4 |
| 返回值 | 3*4=12 | 3*3=9 |
| 是否安全 | ❌ 不安全 | ✔ 安全 |
5. 面试常问 & 易错点
1)为什么宏函数有危险?
因为会进行“多次求值”,例如 SQR(a++) 导致 a++ 执行两次。
2)inline 一定会被内联吗?
不会!只是“请求”,编译器可能拒绝。
3)哪些情况下 inline 会失效?
-
函数体过大
-
有循环、递归
-
编译器优化策略决定不内联
4)为什么 inline 安全?
因为它仍然是普通函数,具有类型检查与单次参数求值。
6. 一句话总结
define 是预处理阶段的危险文本替换;
inline 是类型安全的函数内联请求,现代 C++ 中所有“宏函数”都应改写为 inline。
三、#define vs const
第二天已经详细介绍了 const 和之前关于 const 的具体内容,具体链接如下:
1. 简要回答
#define 定义的常量本质是“字符替换”,没有类型检查,也没有作用域,不安全;
const 是真正带类型的常量,有类型系统约束与作用域,能参与编译优化,是 C++ 中定义常量的正确方式。
2. 详细解释
1)#define(宏常量)
-
本质是文本替换,不进入符号表
-
不参与类型检查
-
没有作用域(全文件有效)
-
调试器看不到宏
-
容易出错,如须加括号保护
#define PI 3.14
编译器实际看到的是 3.14,根本不知道“PI”这个标识符存在。
2)const(带类型常量)
-
有明确类型,例如
const double PI = 3.14; -
在符号表中可见,可被调试
-
遵循作用域(块级、命名空间、类内均可)
-
可参与类型检查(重载解析更精准)
-
可作为函数参数类型明确
-
可参与优化(编译器可能将其当立即数)
3. 图表总结
| 对比项 | #define 常量 | const 常量 |
|---|---|---|
| 本质 | 文本替换 | 有类型的常量 |
| 生效阶段 | 预处理 | 编译期 |
| 是否类型检查 | ❌ 无 | ✅ 有 |
| 是否进入符号表 | ❌ 不进入 | ⭐ 可进入 |
| 是否有作用域 | ❌ 无 | ⭐ 遵循 C++ 作用域 |
| 是否可调试 | ❌ 不可 | ⭐ 可查看 |
| 安全性 | ❌ 易出错 | ⭐ 安全、可优化 |
| 推荐程度 | ❌ 不推荐 | ⭐ 强烈推荐 |
4. 代码示例(含输出)
#include <iostream>
using namespace std;
#define PI_MACRO 3.14
const double PI_CONST = 3.14;
void foo(double x) {
cout << "foo: " << x << endl;
}
int main() {
foo(PI_MACRO); // 类型不明确(宏替换)
foo(PI_CONST); // 类型 double
const int N = 3;
int arr[N] = {1,2,3};
cout << sizeof(arr)/sizeof(arr[0]) << endl;
}
输出:
foo: 3.14
foo: 3.14
3
5. 面试常问 & 易错点
1)define 定义常量的最大问题?
无类型,无作用域,调试困难,容易踩坑。
2)const 一定会分配内存吗?
不一定,编译器可能直接优化成立即数。
3)define 和 const 谁更推荐?
永远推荐 const(以及现代 C++ 的 constexpr)。
4)define 会进入符号表吗?
不会,因此调试器中看不到“PI”,只看到 3.14。
6. 一句话总结
define 是“替换”,const 是“类型化常量”;宏常量不安全,const 才是 C++ 中正确的常量写法。
432

被折叠的 条评论
为什么被折叠?



