C++ 常量
概述
常量是在程序执行期间值不能被修改的数据。C++提供了多种定义常量的方法,包括const关键字、constexpr关键字、宏定义等。正确使用常量可以提高代码的安全性、可读性和性能。
🔒 const关键字
基本const变量
cpp
#include <iostream>
int main() {
// 基本const变量
const int MAX_SIZE = 100;
const double PI = 3.14159;
const char GRADE = 'A';
const std::string MESSAGE = "Hello, World!";
std::cout << "最大尺寸: " << MAX_SIZE << std::endl;
std::cout << "圆周率: " << PI << std::endl;
std::cout << "等级: " << GRADE << std::endl;
std::cout << "消息: " << MESSAGE << std::endl;
// 以下代码会导致编译错误
// MAX_SIZE = 200; // 错误!不能修改const变量
// PI = 3.14; // 错误!不能修改const变量
// const变量必须在声明时初始化
// const int UNINITIALIZED; // 错误!const变量必须初始化
return 0;
}const指针
cpp
#include <iostream>
int main() {
int value1 = 10;
int value2 = 20;
// 1. 指向const对象的指针(指向的值不能修改)
const int* ptr_to_const = &value1;
std::cout << "指向const的指针: " << *ptr_to_const << std::endl;
// *ptr_to_const = 30; // 错误!不能通过指针修改值
ptr_to_const = &value2; // 正确!可以改变指针本身
std::cout << "改变指针后: " << *ptr_to_const << std::endl;
// 2. const指针(指针本身不能修改)
int* const const_ptr = &value1;
std::cout << "const指针: " << *const_ptr << std::endl;
*const_ptr = 30; // 正确!可以修改指向的值
// const_ptr = &value2; // 错误!不能修改指针本身
std::cout << "修改值后: " << *const_ptr << std::endl;
// 3. 指向const对象的const指针
const int* const const_ptr_to_const = &value2;
std::cout << "const指针指向const: " << *const_ptr_to_const << std::endl;
// *const_ptr_to_const = 40; // 错误!不能修改值
// const_ptr_to_const = &value1; // 错误!不能修改指针
return 0;
}const引用
cpp
#include <iostream>
#include <string>
// 使用const引用作为函数参数(避免拷贝,防止修改)
void print_string(const std::string& str) {
std::cout << "字符串: " << str << std::endl;
// str += " modified"; // 错误!不能修改const引用
}
// 返回const引用
class Person {
private:
std::string name_;
int age_;
public:
Person(const std::string& name, int age) : name_(name), age_(age) {}
// 返回const引用,防止外部修改
const std::string& getName() const {
return name_;
}
// 非const版本,允许修改
std::string& getName() {
return name_;
}
int getAge() const { return age_; }
};
int main() {
std::string message = "Hello, C++!";
// const引用可以绑定到左值
const std::string& ref1 = message;
std::cout << "const引用: " << ref1 << std::endl;
// const引用可以绑定到右值
const std::string& ref2 = "Temporary String";
std::cout << "绑定右值: " << ref2 << std::endl;
// 使用const引用参数
print_string(message);
print_string("Direct String");
// 类中的const引用
Person person("Alice", 25);
const Person const_person("Bob", 30);
std::cout << "普通对象姓名: " << person.getName() << std::endl;
std::cout << "const对象姓名: " << const_person.getName() << std::endl;
// 修改非const对象
person.getName() = "Alice Smith";
std::cout << "修改后姓名: " << person.getName() << std::endl;
return 0;
}⚡ constexpr关键字 (C++11)
编译时常量
cpp
#include <iostream>
#include <array>
// constexpr函数
constexpr int square(int x) {
return x * x;
}
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// constexpr类
class Rectangle {
private:
int width_, height_;
public:
constexpr Rectangle(int w, int h) : width_(w), height_(h) {}
constexpr int area() const {
return width_ * height_;
}
constexpr int perimeter() const {
return 2 * (width_ + height_);
}
};
int main() {
// constexpr变量在编译时计算
constexpr int SIZE = 10;
constexpr int SQUARED = square(5); // 编译时计算
constexpr int FACT_5 = factorial(5); // 编译时计算
std::cout << "SIZE: " << SIZE << std::endl;
std::cout << "5的平方: " << SQUARED << std::endl;
std::cout << "5的阶乘: " << FACT_5 << std::endl;
// 可以用于数组大小(必须是编译时常量)
constexpr int ARRAY_SIZE = square(4);
std::array<int, ARRAY_SIZE> arr;
std::cout << "数组大小: " << arr.size() << std::endl;
// constexpr对象
constexpr Rectangle rect(3, 4);
constexpr int area = rect.area(); // 编译时计算
constexpr int perimeter = rect.perimeter(); // 编译时计算
std::cout << "矩形面积: " << area << std::endl;
std::cout << "矩形周长: " << perimeter << std::endl;
// 运行时值不能用于constexpr
int runtime_value;
std::cin >> runtime_value;
// constexpr int bad = square(runtime_value); // 错误!
int good = square(runtime_value); // 正确,运行时计算
return 0;
}constexpr vs const
cpp
#include <iostream>
int get_runtime_value() {
return 42;
}
int main() {
// const: 运行时常量,值在运行时确定后不能修改
const int runtime_const = get_runtime_value();
std::cout << "运行时常量: " << runtime_const << std::endl;
// constexpr: 编译时常量,值必须在编译时确定
constexpr int compile_time_const = 42;
std::cout << "编译时常量: " << compile_time_const << std::endl;
// 错误示例
// constexpr int bad = get_runtime_value(); // 错误!不是编译时常量
// constexpr可以用于模板参数和数组大小
constexpr int TEMPLATE_PARAM = 10;
std::array<int, TEMPLATE_PARAM> compile_time_array;
// const不能用于需要编译时常量的地方
// std::array<int, runtime_const> runtime_array; // 错误!
// constexpr变量也是const的
// compile_time_const = 50; // 错误!constexpr变量不能修改
return 0;
}📍 宏定义 (#define)
基本宏定义
cpp
#include <iostream>
// 简单宏定义
#define MAX_SIZE 1000
#define PI 3.14159
#define MESSAGE "Hello from macro"
// 函数式宏
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
// 多行宏
#define PRINT_INFO(name, age) \
do { \
std::cout << "姓名: " << (name) << std::endl; \
std::cout << "年龄: " << (age) << std::endl; \
} while(0)
// 条件编译宏
#define DEBUG_MODE 1
#if DEBUG_MODE
#define DEBUG_PRINT(x) std::cout << "DEBUG: " << (x) << std::endl
#else
#define DEBUG_PRINT(x)
#endif
int main() {
std::cout << "最大尺寸: " << MAX_SIZE << std::endl;
std::cout << "圆周率: " << PI << std::endl;
std::cout << "消息: " << MESSAGE << std::endl;
// 使用函数式宏
int a = 5, b = 3;
std::cout << "5的平方: " << SQUARE(a) << std::endl;
std::cout << "最大值: " << MAX(a, b) << std::endl;
std::cout << "绝对值: " << ABS(-10) << std::endl;
// 使用多行宏
PRINT_INFO("张三", 25);
// 调试宏
DEBUG_PRINT("这是调试信息");
return 0;
}宏的问题和注意事项
cpp
#include <iostream>
#define BAD_SQUARE(x) x * x // 危险的宏定义
#define GOOD_SQUARE(x) ((x) * (x)) // 安全的宏定义
#define INCREMENT(x) ++x // 副作用问题
int main() {
// 问题1:优先级问题
int result1 = BAD_SQUARE(2 + 3); // 期望25,实际11 (2 + 3 * 2 + 3)
int result2 = GOOD_SQUARE(2 + 3); // 正确结果25 ((2 + 3) * (2 + 3))
std::cout << "BAD_SQUARE(2 + 3): " << result1 << std::endl;
std::cout << "GOOD_SQUARE(2 + 3): " << result2 << std::endl;
// 问题2:副作用问题
int x = 5;
int result3 = GOOD_SQUARE(INCREMENT(x)); // x被增加两次!
std::cout << "x的值: " << x << std::endl; // x = 7,不是期望的6
// 问题3:类型不安全
double d = 2.5;
std::cout << "GOOD_SQUARE(2.5): " << GOOD_SQUARE(d) << std::endl;
// 更好的解决方案:使用constexpr函数
constexpr auto safe_square = [](auto x) { return x * x; };
std::cout << "lambda square: " << safe_square(2 + 3) << std::endl;
return 0;
}🔢 枚举常量
传统枚举
cpp
#include <iostream>
// 传统枚举
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
enum Status {
PENDING = 1,
RUNNING = 5,
STOPPED = 10
};
// 匿名枚举用作常量
enum {
BUFFER_SIZE = 1024,
MAX_CONNECTIONS = 100
};
int main() {
Color background = RED;
Status current_status = RUNNING;
std::cout << "背景颜色: " << background << std::endl;
std::cout << "当前状态: " << current_status << std::endl;
// 枚举可以隐式转换为整数
int color_value = background;
std::cout << "颜色值: " << color_value << std::endl;
// 使用枚举常量
char buffer[BUFFER_SIZE];
std::cout << "缓冲区大小: " << sizeof(buffer) << std::endl;
return 0;
}强类型枚举 (C++11)
cpp
#include <iostream>
// 强类型枚举(枚举类)
enum class Direction {
North,
South,
East,
West
};
enum class ErrorCode : int {
Success = 0,
FileNotFound = 1001,
AccessDenied = 1002,
NetworkError = 2001
};
// 指定底层类型
enum class Priority : char {
Low = 'L',
Medium = 'M',
High = 'H'
};
int main() {
Direction player_direction = Direction::North;
ErrorCode last_error = ErrorCode::Success;
Priority task_priority = Priority::High;
// 强类型枚举不能隐式转换为整数
// int dir_value = player_direction; // 错误!
int dir_value = static_cast<int>(player_direction); // 需要显式转换
std::cout << "方向值: " << dir_value << std::endl;
std::cout << "错误码: " << static_cast<int>(last_error) << std::endl;
std::cout << "优先级: " << static_cast<char>(task_priority) << std::endl;
// 强类型枚举避免名称冲突
enum class Color { Red, Green, Blue };
enum class Traffic { Red, Yellow, Green };
Color car_color = Color::Red;
Traffic light_state = Traffic::Red;
// 没有冲突,类型安全
return 0;
}🏛️ 静态常量成员
类中的常量
cpp
#include <iostream>
#include <string>
class MathConstants {
public:
// 静态const成员(整型可以在类内初始化)
static const int MAX_ITERATIONS = 1000;
static const char DELIMITER = ',';
// 非整型静态const成员需要在类外定义
static const double PI;
static const std::string DEFAULT_NAME;
// C++11: 静态constexpr成员
static constexpr double E = 2.71828;
static constexpr int ARRAY_SIZE = 10;
};
// 类外定义静态const成员
const double MathConstants::PI = 3.14159265359;
const std::string MathConstants::DEFAULT_NAME = "Unknown";
class Configuration {
private:
const int id_; // 实例const成员
const std::string name_; // 实例const成员
public:
// const成员必须在初始化列表中初始化
Configuration(int id, const std::string& name)
: id_(id), name_(name) {}
// const成员函数
int getId() const { return id_; }
const std::string& getName() const { return name_; }
// const成员不能被修改
// void setId(int new_id) { id_ = new_id; } // 错误!
};
int main() {
// 访问静态常量成员
std::cout << "最大迭代次数: " << MathConstants::MAX_ITERATIONS << std::endl;
std::cout << "圆周率: " << MathConstants::PI << std::endl;
std::cout << "自然常数: " << MathConstants::E << std::endl;
std::cout << "默认名称: " << MathConstants::DEFAULT_NAME << std::endl;
// 使用常量数组大小
int array[MathConstants::ARRAY_SIZE];
std::cout << "数组大小: " << sizeof(array) / sizeof(int) << std::endl;
// 实例const成员
Configuration config(123, "MyConfig");
std::cout << "配置ID: " << config.getId() << std::endl;
std::cout << "配置名称: " << config.getName() << std::endl;
return 0;
}🛡️ const正确性
const成员函数
cpp
#include <iostream>
#include <vector>
class Container {
private:
std::vector<int> data_;
mutable int access_count_; // mutable允许在const函数中修改
public:
Container() : access_count_(0) {}
// 非const成员函数
void add(int value) {
data_.push_back(value);
}
// const成员函数(承诺不修改对象状态)
size_t size() const {
access_count_++; // mutable成员可以修改
return data_.size();
}
// const版本的访问器
const int& at(size_t index) const {
if (index >= data_.size()) {
throw std::out_of_range("索引越界");
}
access_count_++;
return data_[index];
}
// 非const版本的访问器
int& at(size_t index) {
if (index >= data_.size()) {
throw std::out_of_range("索引越界");
}
access_count_++;
return data_[index];
}
// const成员函数可以调用其他const成员函数
void print() const {
std::cout << "容器大小: " << size() << std::endl;
for (size_t i = 0; i < size(); ++i) {
std::cout << at(i) << " ";
}
std::cout << std::endl;
std::cout << "访问次数: " << access_count_ << std::endl;
}
};
// 函数重载:const和非const版本
void process_container(Container& container) {
std::cout << "处理非const容器" << std::endl;
container.add(42); // 可以修改
}
void process_container(const Container& container) {
std::cout << "处理const容器" << std::endl;
// container.add(42); // 错误!不能调用非const函数
container.print(); // 可以调用const函数
}
int main() {
Container container;
container.add(1);
container.add(2);
container.add(3);
// 非const对象
std::cout << "=== 非const对象 ===" << std::endl;
process_container(container);
container.print();
// const对象
std::cout << "\n=== const对象 ===" << std::endl;
const Container const_container = container;
process_container(const_container);
return 0;
}const_cast的使用
cpp
#include <iostream>
#include <string>
// 遗留API,接受非const指针但实际不修改数据
void legacy_function(char* str) {
std::cout << "遗留函数处理: " << str << std::endl;
// 实际上不修改str的内容
}
class CacheExample {
private:
mutable std::string cached_result_;
mutable bool cache_valid_;
public:
CacheExample() : cache_valid_(false) {}
// const函数但需要修改缓存
const std::string& expensive_computation() const {
if (!cache_valid_) {
// 使用mutable,避免const_cast
cached_result_ = "计算结果";
cache_valid_ = true;
}
return cached_result_;
}
// 不好的做法:使用const_cast
const std::string& bad_computation() const {
if (!cache_valid_) {
// 移除const限定符(危险!)
auto* non_const_this = const_cast<CacheExample*>(this);
non_const_this->cached_result_ = "计算结果";
non_const_this->cache_valid_ = true;
}
return cached_result_;
}
};
int main() {
// const_cast的合理使用:调用遗留API
const std::string message = "Hello, World!";
// 需要调用期望非const参数的遗留函数
// legacy_function(message.c_str()); // 错误!const char*不能转换为char*
// 使用const_cast(确保函数不会修改数据)
legacy_function(const_cast<char*>(message.c_str()));
std::cout << "消息未被修改: " << message << std::endl;
// 缓存示例
const CacheExample cache_obj;
std::cout << "第一次调用: " << cache_obj.expensive_computation() << std::endl;
std::cout << "第二次调用: " << cache_obj.expensive_computation() << std::endl;
return 0;
}📋 常量最佳实践
常量设计原则
cpp
#include <iostream>
#include <string>
#include <array>
// 1. 使用有意义的常量名
namespace GameConfig {
constexpr int MAX_PLAYERS = 4;
constexpr double GRAVITY = 9.81;
constexpr char SEPARATOR = '|';
const std::string DEFAULT_PLAYER_NAME = "Player";
}
// 2. 将相关常量组织在一起
class NetworkConstants {
public:
static constexpr int DEFAULT_PORT = 8080;
static constexpr int MAX_CONNECTIONS = 100;
static constexpr double TIMEOUT_SECONDS = 30.0;
static const std::string DEFAULT_HOST;
};
const std::string NetworkConstants::DEFAULT_HOST = "localhost";
// 3. 使用constexpr而不是宏(当可能时)
constexpr double calculate_circle_area(double radius) {
return 3.14159 * radius * radius;
}
// 4. 常量数组
constexpr std::array<int, 5> FIBONACCI = {1, 1, 2, 3, 5};
int main() {
// 使用命名空间常量
std::cout << "最大玩家数: " << GameConfig::MAX_PLAYERS << std::endl;
std::cout << "重力常数: " << GameConfig::GRAVITY << std::endl;
// 使用类常量
std::cout << "默认端口: " << NetworkConstants::DEFAULT_PORT << std::endl;
std::cout << "默认主机: " << NetworkConstants::DEFAULT_HOST << std::endl;
// 编译时计算
constexpr double area = calculate_circle_area(5.0);
std::cout << "圆形面积: " << area << std::endl;
// 常量数组
std::cout << "斐波那契数列: ";
for (const auto& num : FIBONACCI) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}常量选择指南
cpp
#include <iostream>
// 选择合适的常量类型
int main() {
// 1. 编译时已知的简单值:使用constexpr
constexpr int COMPILE_TIME_CONSTANT = 42;
// 2. 运行时确定但不变的值:使用const
const int runtime_input = []() {
std::cout << "输入一个数字: ";
int value;
std::cin >> value;
return value;
}();
// 3. 字符串字面量:通常使用const
const std::string APPLICATION_NAME = "MyApp";
// 4. 数组大小等模板参数:必须使用constexpr
constexpr size_t ARRAY_SIZE = 10;
std::array<int, ARRAY_SIZE> my_array;
// 5. 类中的常量:根据需要选择static const或static constexpr
class Example {
public:
static const std::string CLASS_NAME; // 运行时常量
static constexpr int CLASS_VERSION = 1; // 编译时常量
};
std::cout << "编译时常量: " << COMPILE_TIME_CONSTANT << std::endl;
std::cout << "运行时常量: " << runtime_input << std::endl;
std::cout << "应用名称: " << APPLICATION_NAME << std::endl;
std::cout << "数组大小: " << my_array.size() << std::endl;
return 0;
}总结
C++提供了多种定义常量的方法,每种都有其特定用途:
常量类型对比
| 类型 | 特点 | 适用场景 |
|---|---|---|
| const | 运行时常量,不可修改 | 运行时确定的不变值 |
| constexpr | 编译时常量,可用于模板 | 编译时已知的值 |
| #define | 预处理器替换 | 条件编译,简单常量 |
| enum | 命名常量集合 | 相关常量分组 |
| static const | 类级别常量 | 类相关的常量 |
最佳实践
- 优先使用constexpr而不是#define
- 合理使用const保证数据不被意外修改
- 将相关常量组织在命名空间或类中
- 为常量选择有意义的名称
- 在合适的地方使用const成员函数
正确使用常量可以:
- 提高代码安全性
- 增强代码可读性
- 支持编译器优化
- 便于代码维护