Skip to content

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成员函数

正确使用常量可以:

  • 提高代码安全性
  • 增强代码可读性
  • 支持编译器优化
  • 便于代码维护

本站内容仅供学习和研究使用。