乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

const 与 constexpr

定义与作用

C++ 中的 const 和 constexpr 是两个不同层次的不变性机制:

关键字含义引入版本
const运行期只读——变量初始化后不可修改C++98
constexpr编译期常量——值在编译时确定C++11

const 回答"能不能改"的问题;constexpr 回答"什么时候求值"的问题。一个 const 变量可能在运行期初始化,而 constexpr 变量必须在编译期就能求值。

核心原理

const 的多重角色

顶层 const 与底层 const

const int ci = 10;          // 顶层 const:ci 本身不可变
const int* pi = &ci;        // 底层 const:pi 可改,但所指对象不可改
int* const cp = &x;         // 顶层 const:cp 不可改,所指对象可改
const int* const ccp = &ci; // 顶层 + 底层

这个区分在 auto 推导时至关重要——auto 会丢弃顶层 const 但保留底层 const。

constexpr 的本质

C++11 中 constexpr 函数限制严格——函数体只能包含一条 return 语句。C++14 大幅放宽,允许局部变量、循环和条件分支。

完整示例

示例一:const 正确性——学生成绩管理

场景说明:设计一个 GradeBook 类,通过 const 成员函数保证查询操作不修改对象状态,演示 const 正确性的好处。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>

class GradeBook {
public:
    void add_student(const std::string& name, double score) {
        names_.push_back(name);
        scores_.push_back(score);
    }

    // const 成员函数:承诺不修改对象状态
    double average() const {
        if (scores_.empty()) return 0.0;
        double total = std::accumulate(scores_.begin(), scores_.end(), 0.0);
        return total / scores_.size();
    }

    double highest() const {
        if (scores_.empty()) return 0.0;
        return *std::max_element(scores_.begin(), scores_.end());
    }

    size_t student_count() const {
        return names_.size();
    }

    // 返回 const 引用:防止外部意外修改
    const std::vector<std::string>& names() const { return names_; }
    const std::vector<double>& scores() const { return scores_; }

private:
    std::vector<std::string> names_;
    std::vector<double> scores_;
    mutable size_t average_query_count_{0};  // 即使在 const 函数中也可修改
};

// 接收 const 引用:承诺不修改传入对象
void print_report(const GradeBook& book) {
    std::cout << "===== 成绩报告 =====" << std::endl;
    std::cout << "学生人数: " << book.student_count() << std::endl;
    std::cout << "平均分:   " << book.average() << std::endl;
    std::cout << "最高分:   " << book.highest() << std::endl;

    const auto& names = book.names();
    const auto& scores = book.scores();
    for (size_t i = 0; i < names.size(); ++i) {
        std::cout << "  " << names[i] << ": " << scores[i] << std::endl;
    }
}

int main() {
    GradeBook book;
    book.add_student("张三", 88.5);
    book.add_student("李四", 92.0);
    book.add_student("王五", 76.5);
    book.add_student("赵六", 85.0);

    print_report(book);

    // 验证 const 安全性:
    // book.names().push_back("攻击者");  // 编译错误:返回的是 const&
    // print_report 中 book.average() 合法,因为 average() 是 const

    return 0;
}

预期输出:

===== 成绩报告 =====
学生人数: 4
平均分:   85.5
最高分:   92
  张三: 88.5
  李四: 92
  王五: 76.5
  赵六: 85

逐段分析:

  • average() 等查询函数标记为 const,编译器会在编译期验证它们确实不修改成员
  • print_report 参数是 const GradeBook&,内部只能调用 const 成员函数——这是编译器强制保证的
  • 返回 const std::vector<T>& 而非值拷贝:零拷贝开销 + 防外部修改
  • mutable 允许在 const 函数中修改特定成员(如缓存、计数),用于逻辑 const 而非物理 const

示例二:constexpr 编译期计算

场景说明:使用 constexpr 在编译期计算阶乘和斐波那契数列,用于数组大小声明和模板参数。

#include <iostream>
#include <array>

// C++11 写法:单一 return 语句
constexpr int factorial_11(int n) {
    return n <= 1 ? 1 : n * factorial_11(n - 1);
}

// C++14 写法:允许多条语句(编译器支持时可用)
constexpr int factorial_14(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

// 编译期计算斐波那契
constexpr int fibonacci(int n) {
    return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    // 编译期常量用于数组大小
    constexpr size_t size = factorial_11(5);  // 120
    std::array<int, size> arr{};               // 编译期确定大小

    std::cout << "数组大小: " << arr.size() << std::endl;

    // 编译期常量用于模板参数
    std::array<int, factorial_11(4)> fib_arr{};  // 24

    // 运行期也可调用 constexpr 函数
    int n;
    std::cout << "输入 n: ";
    std::cin >> n;
    std::cout << n << "! = " << factorial_14(n)
              << " (运行期计算)" << std::endl;

    // const vs constexpr
    const int rt_const = n * 2;            // 运行期只读
    constexpr int ct_const = factorial_11(6); // 编译期常量 = 720

    // constexpr 的威力:保证编译期可求值
    // constexpr int bad = n * 2;  // 编译错误:n 不是编译期常量

    std::cout << "6! = " << ct_const << " (编译期常量)" << std::endl;
    std::cout << "fibonacci(10) = " << fibonacci(10)
              << " (可在编译期求值)" << std::endl;

    return 0;
}

编译命令:

g++ -std=c++14 constexpr_demo.cpp -o constexpr_demo

预期交互:

数组大小: 120
输入 n: 5
5! = 120 (运行期计算)
6! = 720 (编译期常量)
fibonacci(10) = 55 (可在编译期求值)

逐段分析:

  • factorial_11 符合 C++11 约束(单一 return),但风格较晦涩
  • factorial_14 写法直观,但需 C++14 支持
  • constexpr size_t size = factorial_11(5) 在编译期求值,结果是真正的编译期常量,可用于 std::array 的模板参数
  • const int rt_const = n * 2 只是运行期只读,不能用于模板参数
  • constexpr 函数的双重特性:参数是编译期常量时编译期求值,参数是运行期变量时运行期求值

示例三:const 指针的"左右"辨析

#include <iostream>

int main() {
    int x = 10, y = 20;

    // 从右向左读
    const int* p1 = &x;    // p1 是 → 指针 → 指向 → int → const
                           // 指向的 int 不可修改
    int const* p2 = &x;    // 与 p1 完全相同(const 在 * 前即可)

    int* const p3 = &x;    // p3 是 → const → 指针 → 指向 → int
                           // 指针本身不可修改

    const int* const p4 = &x; // 指针和所指对象都不可修改

    // *p1 = 30;   // 错误:p1 指向 const int
    p1 = &y;        // 正确:p1 本身不是 const

    *p3 = 30;       // 正确:p3 指向的 int 可修改
    // p3 = &y;    // 错误:p3 本身是 const

    std::cout << "x=" << x << ", y=" << y << std::endl;

    return 0;
}

易错场景与面试考点

易错场景

场景错误表现正确做法
混淆 const int* 和 int* const编译错误或语义错误从右向左读:const 在 * 前修饰所指,在 * 后修饰指针
const 成员函数中修改成员编译错误使用 mutable 或重新设计接口
返回非 const 引用暴露内部状态外部可绕过封装修改返回 const T&
constexpr 函数内写 C++11 不允许的语句编译错误使用递归或升级到 C++14
把 const 变量当编译期常量const int 不能用于模板参数用 constexpr 保证编译期求值

常见面试问题

  1. const 和 constexpr 的区别?——const 只保证运行期不可修改,值可能在运行期才确定;constexpr 强制编译期求值,是真正的编译期常量。

  2. const int*、int const*、int* const、const int* const 分别是什么意思?——前两者等价(指向 const int 的指针);第三个是 const 指针(指向 int);第四个是 const 指针指向 const int。

  3. const 成员函数的作用?什么情况下需要两个版本(const 和非 const)?——承诺不修改成员变量,使函数可在 const 对象上调用。重载 const 和非 const 版本可提供读写和只读两种接口(如 operator[])。

  4. mutable 的作用?——允许在 const 成员函数中修改标记为 mutable 的成员变量,常用于缓存、互斥锁、访问计数等"逻辑不变但物理可变"的场景。

  5. C++11 中 constexpr 函数的限制是什么?为什么?——函数体只能包含一条 return 语句。这是为了简化编译器的实现复杂度。C++14 放宽了这一限制。

  6. constexpr、宏常量 #define、const 如何选择?——优先 constexpr(类型安全、作用域控制),其次 const(运行期只读),避免用宏(无类型、无作用域)。

小结

  • const = 运行期只读,"我不会改";constexpr = 编译期常量,"我早就知道"
  • 从右向左读指针声明,区分顶层 const 和底层 const
  • const 成员函数是接口契约,配合 const& 传参形成 const 正确性体系
  • constexpr 让大量计算从运行期移到编译期,提升性能同时保证类型安全
  • 避免使用宏常量,始终优先 constexpr
上一页
字符串与原始字符串字面量
下一页
nullptr 与空指针