当前位置: 首页 > backend >正文

C++ const以及相关关键字

const是 C++ 中一个非常重要的关键字,它的核心思想是**“只读”(read-only)**。通过const,我们可以告诉编译器某个数据不应该被修改。这不仅可以防止我们意外地修改数据,还能让编译器进行优化,并使代码的意图更加清晰。


1. const 变量:编译期常量与运行期常量

const变量必须在声明时进行初始化,之后其值就不能再被改变。根据初始化的时机,我们可以将其分为两种。

a) 编译期常量 (Compile-time Constant)

编译期常量的值在编译阶段就已经确定了。编译器可以直接将其替换为具体的值,有点类似宏替换,但更安全(有类型检查)。

特点:

  • 用一个常量值(字面量)或另一个编译期常量来初始化。
  • 可以用于数组大小、模板参数、case标签等需要编译期确定值的场景。

示例:

const int MAX_USERS = 100;       // 100是字面量,编译期确定
char user_names[MAX_USERS];      // OK, 数组大小必须是编译期常量const int MAX_LEVEL = MAX_USERS / 10; // MAX_USERS是编译期常量,所以MAX_LEVEL也是

在现代 C++ (C++11及以后) 中,更推荐使用 constexpr 来明确表示一个值是编译期常量。

b) 运行期常量 (Run-time Constant)

运行期常量的值在程序运行时才被确定,但一旦初始化后,其值就不能再改变。

特点:

  • 用一个变量、函数返回值或其他在运行时才能确定的值来初始化。
  • 其“常量”属性体现在初始化之后不可修改

示例:

#include <iostream>int getUserInput() {int val;std::cout << "请输入一个数字: ";std::cin >> val;return val;
}int main() {const int USER_ID = getUserInput(); // USER_ID的值在运行时由用户输入确定std::cout << "你的ID是: " << USER_ID << std::endl;// USER_ID = 100; // 错误!编译不通过,因为USER_ID是const,初始化后不能再赋值return 0;
}

在这个例子中,USER_ID 的值直到程序运行到getUserInput()时才确定,但一旦被赋值,它就成为了一个只读的常量。


2. const 指针

这是const用法中最容易混淆的部分。判断的关键是const修饰的是谁。一个简单的记忆法则是**“从右向左读”**,* 读作 pointer to (指向…的指针)。

a) const int *p (指向常量的指针)
  • 读法: p is a pointer to a const int (p 是一个指向常量 int 的指针)。
  • 也写作: int const *p (效果完全相同)。
  • 含义: 指针 p 所指向的不能通过 p 来修改,但指针 p 本身可以被修改,即可以指向其他地址。
  • 记忆: “锁值,不锁向” (The value is locked, the direction is not)。

示例:

int a = 10, b = 20;
const int *p = &a;// *p = 15; // 错误!不能通过p修改它所指向的值
p = &b;    // 正确!p可以指向别处

常见用途: 作为函数参数,防止函数内部修改指针所指向的数据。
void printArray(const int* arr, int size);

b) int * const p (常量指针)
  • 读法: p is a const pointer to an int (p 是一个指向 int 的常量指针)。
  • 含义: 指针 p 本身的值(即它存储的地址)是常量,不能被修改。但它所指向的可以通过 p 来修改。
  • 关键点: 因为指针本身是常量,所以必须在声明时初始化。
  • 记忆: “锁向,不锁值” (The direction is locked, the value is not)。

示例:

int a = 10, b = 20;
int * const p = &a; // 必须在声明时初始化*p = 15;    // 正确!可以修改p所指向的值
// p = &b;     // 错误!p本身是常量,不能再指向别处
c) const int * const p (指向常量的常量指针)
  • 读法: p is a const pointer to a const int (p 是一个指向常量 int 的常量指针)。
  • 含义: 指针 p 本身和它所指向的都不能被修改。
  • 记忆: “值和向都锁” (Both value and direction are locked)。

示例:

int a = 10, b = 20;
const int * const p = &a; // 必须在声明时初始化// *p = 15; // 错误!不能修改值
// p = &b;  // 错误!不能修改指向

3. const 成员函数

const 成员函数是类设计中的一个重要概念,它承诺该函数不会修改对象的数据成员

  • 语法: 在函数声明和定义的参数列表后面加上 const 关键字。
    ReturnType functionName(params) const;

  • 内部机制:const 成员函数内部,this 指针的类型是 const ClassName*。而非 const 成员函数的 this 指针类型是 ClassName*。由于 this 指向一个常量对象,所以你不能通过它来修改任何成员变量。

示例:

class Rectangle {
private:int width, height;
public:Rectangle(int w, int h) : width(w), height(h) {}// const 成员函数,它承诺不修改 width 或 heightint getArea() const {// width = 10; // 错误!不能在const成员函数中修改成员变量return width * height;}// 非 const 成员函数,它可以修改成员变量void setWidth(int w) {width = w;}
};

4. const 对象

const 对象是指在声明时使用 const 关键字修饰的对象。一旦一个对象被声明为const,它的状态在构造完成后就不能再被修改。

  • 规则: const 对象只能调用 const 成员函数
  • 原因: 调用非 const 成员函数意味着有可能会修改对象的状态,这违背了对象本身的 const 属性。编译器会阻止这种行为。

示例:

const Rectangle r(10, 5); // r是一个const对象int area = r.getArea();   // 正确!getArea() 是一个 const 成员函数
// r.setWidth(20);         // 错误!setWidth() 不是 const 成员函数,不能被const对象调用

5. mutable 关键字

mutableconst 的一个“例外”。它允许在 const 成员函数中修改被 mutable 修饰的成员变量。

  • 用途: 当一个成员变量不属于对象的“逻辑状态”,而是一些内部状态(如缓存、互斥锁、调试计数器等)时,可以使用 mutable。即使在逻辑上是“只读”的操作(如 get 方法),也可能需要更新这些内部状态。

示例:
假设我们想为一个计算密集型的 getLength() 方法添加缓存。

#include <cmath>class Line {
private:double x1, y1, x2, y2;mutable double cachedLength; // 缓存的长度mutable bool isCacheValid;   // 缓存是否有效public:Line(double x1, double y1, double x2, double y2): x1(x1), y1(y1), x2(x2), y2(y2), isCacheValid(false) {}double getLength() const {if (!isCacheValid) {// 这段代码在const成员函数中,但可以修改mutable成员cachedLength = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));isCacheValid = true;std::cout << " (计算并缓存) ";}return cachedLength;}
};int main() {const Line line(0, 0, 3, 4); // 一个const对象std::cout << line.getLength() << std::endl; // 输出: (计算并缓存) 5std::cout << line.getLength() << std::endl; // 输出: 5 (直接从缓存读取)
}

getLength() 从外部看是一个只读操作,所以声明为 const 是合理的。但内部为了效率需要修改缓存,mutable 完美地解决了这个矛盾。


6. const_cast:去除 const 属性

const_cast 是 C++ 四种类型转换操作符之一,它的唯一作用就是添加或移除变量的 constvolatile 属性。这通常被认为是一个危险的操作。

  • 语法: const_cast<new_type>(expression)

  • 核心警告: 如果你对一个最初就被声明为 const 的变量使用 const_cast 去掉 const 属性,并尝试修改它,其结果是未定义行为 (Undefined Behavior)。这意味着程序可能会崩溃,也可能看起来正常工作,或者产生无法预料的结果。

    为什么是未定义行为? 因为编译器可能会对原始 const 变量进行优化,比如把它放在只读内存段(如 .rodata),或者在编译时就用其值替换所有引用。强行写入只读内存会导致段错误(segmentation fault)。

合规但需谨慎的用例:
最常见的(也是少数合理的)用例是与一些旧的、设计不佳的C风格API交互。这些API可能接受一个 char* 参数,但函数内部并不会修改它。

// 假设这是一个我们无法修改的旧API
// 它承诺不会修改str,但参数类型不是 const char*
void legacy_c_function(char* str) {printf("String: %s\n", str);
}int main() {const char* my_message = "Hello, world!";// 直接调用会报错:cannot convert 'const char*' to 'char*'// legacy_c_function(my_message); // 我们确信函数是安全的,所以使用const_castlegacy_c_function(const_cast<char*>(my_message)); // OK
}

危险的错误用例(导致未定义行为):

int main() {const int magic_number = 42;// p 指向一个const intconst int* p = &magic_number;// 使用 const_cast 去掉 const 属性int* p_non_const = const_cast<int*>(p);// 尝试修改一个最初就是 const 的变量*p_non_const = 99; // !!! 未定义行为 !!!// 程序可能在这里崩溃,也可能不会std::cout << magic_number << std::endl; // 输出可能是42,也可能是99,或者其他
}

总结: 除非你百分之百确定你在做什么(比如调用一个const不正确的旧API),否则应避免使用 const_cast。滥用它会破坏const提供的类型安全保证。

http://www.xdnf.cn/news/19929.html

相关文章:

  • Ubuntu 25.04搭建hadoop3.4.1集群详细教程
  • Access开发导出PDF的N种姿势,你get了吗?
  • 开源本地LLM推理引擎(Cortex AI)
  • OpenTenBase vs MySQL vs Oracle,企业级应用数据库实盘对比分析
  • 使用国外网络的核心问题有哪些?
  • 基于 epoll 的高并发服务器原理与实现(对比 select 和 poll)
  • 十七、单线程 Web 服务器
  • (自用)PowerShell常用命令自查文档
  • AI重构出海营销:HeadAI如何用“滴滴模式”破解红人营销效率困局?
  • Flink 网络消息队列 PrioritizedDeque
  • C52单片机独立按键模块,中断系统,定时器计数器以及蜂鸣器
  • OpenLayers常用控件 -- 章节三:鼠标位置坐标显示控件教程
  • 多线程入门到精通系列: 从操作系统到 Java 线程模型
  • 快鹭云业财一体化系统技术解析:低代码+AI如何破解数据孤岛难题
  • 飞算JavaAI开发在线图书借阅平台全记录:从0到1的实践指南
  • 【C++】详解形参和实参:别再傻傻分不清
  • Android adb shell命令分析应用内存占用
  • 2025全国大学生数学建模C题保姆级思路模型(持续更新):NIPT 的时点选择与胎儿的异常判定
  • Trae + MCP : 一键生成专业封面——从概念到落地的全链路实战
  • java对接物联网设备(一)——使用okhttp网络工具框架对接标准API接口
  • SVN和Git两种版本管理系统对比
  • Hunyuan-MT-7B模型介绍
  • 使用Vue.js和WebSocket打造实时库存仪表盘
  • window使用ffmep工具,加自定义脚本执行视频转码成h264(运营人员使用)
  • P13929 [蓝桥杯 2022 省 Java B] 山 题解
  • 第三方网站测评:【WEB应用文件包含漏洞(LFI/RFI)的测试步骤】
  • 神经网络模型介绍
  • LeetCode 3132.找出与数组相加的整数2
  • 机器学习算法在Backtrader策略稳定性中的作用分析
  • pytorch可视化工具(训练评估:Tensorboard、swanlab)