目录
一、前言
在 C++ 编程中,常量(Constant) 是极其重要的基础概念。
从早期的 #define 宏定义到现代的 const 关键字,它们都能实现“值不变”的目的,但背后的机制完全不同。
本文章从 定义方式 → 底层原理 → 指针修饰 → 实例分析 → 对比总结
带你系统掌握 #define 与 const 的所有知识点。
二、定义常量的两种方式
1️⃣ 使用 #define 预处理器宏
#include <iostream>
using namespace std;
#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'
int main() {
int area = LENGTH * WIDTH;
cout << area << NEWLINE;
return 0;
}
输出:
50
📌 特点:
-
宏是预处理阶段进行的简单文本替换;
-
没有类型检查;
-
编译器看不到它(调试困难);
-
不能使用作用域、命名空间、函数参数等。
2️⃣ 使用 const 关键字
#include <iostream>
using namespace std;
int main() {
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area = LENGTH * WIDTH;
cout << area << NEWLINE;
return 0;
}
输出:
50
📌 特点:
-
const是一种 类型安全的常量声明; -
参与编译器的类型检查;
-
有明确的内存存储位置;
-
可与函数参数、类成员、指针等结合使用。
三、#define vs const 对比表
| 对比项 | #define 宏定义 | const 常量 |
|---|---|---|
| 作用阶段 | 预处理阶段(编译前) | 编译阶段 |
| 类型检查 | 无类型(字符替换) | 有类型(编译器检查) |
| 存储方式 | 不分配内存,仅替换文本 | 分配内存,存储在数据段 |
| 调试支持 | 无法调试(已展开) | 可调试,有符号信息 |
| 作用域 | 全局有效(除非 #undef) | 受作用域限制(块/函数/全局) |
| 能否取消定义 | 可用 #undef 取消 | 不可取消 |
| 能否传递给函数 | ❌ 不能作为参数 | ✅ 可作为函数参数 |
| 能否指定类型 | ❌ 不行 | ✅ 明确的类型约束 |
| 命名规范 | 通常大写宏名:#define PI 3.14 | 可小写/驼峰:const double Pi = 3.14; |
| 安全性 | 可能有边界效应 | 类型安全 |
四、边界效应实例
#define N 2+3
int main() {
double a = N / 2; // 实际展开为 2+3/2 → 3.5,而非 2.5
cout << a << endl;
}
✅ 解决方法:使用括号
#define N (2+3)
五、const 常量的规则与陷阱
1️⃣ 必须初始化
const double pi; // ❌ 错误:未初始化
pi = 3.14159; // 不允许赋值
✅ 正确写法:
const double pi = 3.14159;
2️⃣ 外部 const 可使用 extern
// file1.cpp
extern const int MAX_VALUE;
// file2.cpp
const int MAX_VALUE = 100;
3️⃣ const 在指针中的多种修饰方式
这里可以看我之前写过的文章,里面有详细的记载 const 和 指针 的应用(内容常量,常量指针...),阅读完这三篇内容 读者即可完全掌握const与指针相关操作:C++ 指针与引用全面解析:const 修饰三种形式 + 底层机制 + 八股总结-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/154637648?spm=1001.2014.3001.5502C++ 变量作用域 & 存储期 & 链接性:const / static / extern 全面解析-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/154607017?spm=1001.2014.3001.5502
(1) 指向常量的指针(底层 const)
const int *p = &a;
*p = 10; // ❌ 不允许修改指向内容
p = &b; // ✅ 可以修改指向
(2) 常量指针(顶层 const)
int *const p = &a;
*p = 10; // ✅ 内容可改
p = &b; // ❌ 指针本身不能改
(3) 同时为常量指针且指向常量
const int *const p = &a;
*p = 10; // ❌ 不可改内容
p = &b; // ❌ 不可改指向
🧩 口诀记忆法:
const在*左:内容不可改(底层 const)
const在*右:指针不可改(顶层 const)两边都有:都不可改
4️⃣ const 修饰引用
int a = 10;
const int &ref = a;
ref = 20; // ❌ 不允许修改
a = 20; // ✅ 仍可通过变量改
引用本身不是变量,没有顶层 const,只有底层 const。
六、数据范围与类型注意
const int maxn = 1e+10; // 超过 int 范围,溢出
✅ 改为:
const long long maxn = 1e+10;
或者:
#define MAXN 1e+10 // 无类型,浮点常量替换
注意 %d / %lld 输出格式要匹配类型!
七、宏定义小陷阱:分号错误
#define age2 12;
#define age3 10;
int dd = age2 + age3;
cout << dd; // 输出 12 而不是 22!
原因:宏替换后变成 12;+ 10; ,语句提前结束。
✅ 正确写法:
#define age2 12
#define age3 10
八、const 的扩展应用
| 场景 | 示例 | 含义 |
|---|---|---|
| 修饰变量 | const int a = 10; | 只读变量 |
| 修饰指针 | const int *p | 指向常量的指针 |
| 修饰函数参数 | void func(const string &name); | 防止修改参数 |
| 修饰成员函数 | void show() const; | 不修改类成员 |
| 修饰返回值 | const string getName(); | 返回只读对象 |
九、完整实战例子:计算圆面积与周长
#include <iostream>
using namespace std;
#define PI1 3.14 // 宏定义
const double PI2 = 3.1415926; // 常量定义
int main() {
double r = 10;
cout << "Using #define: area=" << PI1*r*r
<< ", perimeter=" << 2*PI1*r << endl;
cout << "Using const: area=" << PI2*r*r
<< ", perimeter=" << 2*PI2*r << endl;
}
输出:
Using #define: area=314, perimeter=62.8
Using const: area=314.159, perimeter=62.8319
十、总结归纳
| 项目 | #define | const |
|---|---|---|
| 类型 | 无类型 | 有类型 |
| 安全性 | 不安全 | 类型检查 |
| 调试 | 不可调试 | 可调试 |
| 作用域 | 全局 | 局部/全局 |
| 生命周期 | 编译前替换 | 运行期存在 |
| 存储位置 | 代码段 | 数据段 |
| 取消定义 | 可用 #undef | 不可取消 |
| 使用场景 | 条件编译、宏函数 | 常量、参数、成员保护 |
十一、建议的命名规范
| 类型 | 命名示例 | 说明 |
|---|---|---|
| 宏常量 | #define MAX_SPEED 120 | 全大写+下划线 |
| const 常量 | const double Pi = 3.14159; | 首字母大写或驼峰命名 |
十二、总结
宏 #define 是 早期 C 语言的产物,
const 则是 C++ 引入的类型安全改进版。
在现代 C++ 开发中,建议:
-
尽量使用
const替代#define; -
只有在需要条件编译或跨平台时才用
#define; -
合理利用
const修饰函数、指针、引用,让代码更安全、可读、可维护。
486

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



