Skip to content

C++中的const

const概述

const单词字面意思为常数,不变的。它是C/C++中的一个关键字,是一个限定符,它用来限定一个变量不允许改变,它将一个对象转换成一个常量。

cpp
const int a = 10;
A = 100; //编译错误,const是一个常量,不可修改

C/C++中const的区别

C中的const

常量的引进是在C++早期版本中,当时标准C规范正在制定。那时,尽管C委员会决定在C中引入const,但是他们C中的const理解为”一个不能改变的普通变量”,也就是认为const应该是一个只读变量,既然是变量那么就会给const分配内存,并且在C中const是一个全局只读变量,C语言中const修饰的只读变量是外部连接的。

如果这么写:

c
const int arrSize = 10;
int arr[arrSize];

看似是一件合理的编码,但是这将得出一个错误。 因为arrSize占用某块内存,所以C编译器不知道它在编译时的值是多少?

C++中的const

在C++中,一个const不必创建内存空间,而在C中,一个const总是需要一块内存空间。在C++中,是否为const常量分配内存空间依赖于如何使用。一般说来,如果一个const仅仅用来把一个名字用一个值代替(就像使用#define一样),那么该存储局空间就不必创建。

如果存储空间没有分配内存的话,在进行完数据类型检查后,为了代码更加有效,值也许会折叠到代码中。

不过,取一个const地址,或者把它定义为extern,则会为该const创建内存空间。

C/C++中const异同总结

  • C语言会被存储到只读数据段。C++中全局const当声明extern或者对变量取地址时,编译器会分配存储地址,变量存储在只读数据段。两个都受到了只读数据段的保护,不可修改。

    c
    const int constA = 10;
    int main(int argc, char *argv[]) {
      int* p = (int*)&constA;
      *p = 200;
    }

    以上代码在C/C++中编译通过,在运行期,修改constA的值时,发生写入错误。原因是修改只读数据段的数据。

  • C语言中存储在堆栈区,只是不能通过变量直接修改const只读变量的值,但是可以跳过编译器的检查,通过指针间接修改const值。

    c
    const int constA = 10;
    int* p = (int*)&constA;
    *p = 300;
    printf("constA:%d\n", constA);
    printf("*p:%d\n", *p);

    运行结果:

    shell
    constA:300
    *p:10

    C语言中,通过指针间接赋值修改了constA的值。

    C++中对于局部的const变量要区别对待

    1. 对于基础数据类型,也就是const int a = 10这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存。

      c
      const int constA = 10;
      int* p = (int*)&constA;
      *p = 300;
      cout << "constA:" << constA << endl;
      cout << "*p:" << *p << endl;

      运行结果:

      shell
      constA:10
      *p:300

      constA在符号表中,当我们对constA取地址,这个时候为constA分配了新的空间,*p操作的是分配的空间,而constA是从符号表获得的值。

    2. 对于基础数据类型,如果用一个变量初始化const变量,如果const int a = b,那么也是会给a分配内存。

      c
      int b = 10;
      const int constA = b;
      int* p = (int*)&constA;
      *p = 300;
      cout << "constA:" << constA << endl;
      cout << "*p:" << *p << endl;

      运行结果:

      shell
      constA:300
      *p:300

      constA 分配了内存,所以我们可以修改constA内存中的值。

    3. 对于自定数据类型,比如类对象,那么也会分配内存。

      c
      const Person person; //未初始化age
      //person.age = 50; //不可修改
      Person* pPerson = (Person*)&person;
      //指针间接修改
      pPerson->age = 100;
      cout << "pPerson->age:" << pPerson->age << endl;
      pPerson->age = 200;
      cout << "pPerson->age:" << pPerson->age << endl;

      运行结果:

      shell
      pPerson->age:100
      pPerson->age:200

      为person分配了内存,所以我们可以通过指针的间接赋值修改person对象。

  • C中const默认为外部连接,C++中const默认为内部连接。当C语言两个文件中都有const int a的时候,编译器会报重定义的错误。而在C++中则不会,因为C++中的const默认是内部连接的。如果想让C++中的const具有外部连接,必须显示声明为: extern const int a = 10;

const由C++采用,并加进标准C中,尽管他们很不一样。在C中,编译器对待const如同对待变量一样,只不过带有一个特殊的标记,意思是”你不能改变我”。在C++中定义const时,编译器为它创建空间,所以如果在两个不同文件定义多个同名的const,链接器将发生链接错误。简而言之,const在C++中用的更好。

了解: 能否用变量定义数组:

在支持C99标准的编译器中,可以使用变量定义数组。

  1. 微软官方描述vs2013编译器不支持C99:

    Microsoft C conforms to the standard for the C language as set forth in the 9899:1990 edition of the ANSI C standard.

  2. 以下代码在Linux GCC支持C99编译器编译通过

    c
    int a = 10;
    int arr[a];
    int i = 0;
    for (; i < 10; i++)
     arr[i] = i;
    i = 0;
    for (; i < 10; i++)
     printf("%d\n", arr[i]);

尽量以const替换#define

在旧版本C中,如果想建立一个常量,必须使用预处理器#define MAX 1024;

,于是MAX并没有将其加入到符号表中。但我们使用这个常量获得一个编译错误信息时,可能会带来一些困惑,因为这个信息可能会提到1024,但是并没有提到MAX。如果MAX被定义在一个不是你写的头文件中,你可能并不知道1024代表什么,也许解决这个问题要花费很长时间。

解决办法就是用一个常量替换上面的宏。

const int max= 1024;

const和#define区别总结

  1. const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查

  2. const有作用域,而#define不重视作用域,默认定义处到文件结尾。如果定义在指定作用域下有效的常量,那么#define就不能用

  3. 宏常量没有类型,所以调用了int类型重载的函数。const有类型,所以调用希望的short类型函数

    cpp
    #include <iostream>
    using namespace std;
    
    #define PARAM 128
    const short param = 128;
    
    void func(short a) {
     cout << "short!" << endl;
    }
    
    void func(int a) {
     cout << "int" << endl;
    }
    
    int main(int argc, char *argv[]) {
     func(PARAM);
     func(param);
    
     return 0;
    }

    程序输出:

    shell
    int
    short!
  4. 宏常量不重视作用域

    cpp
    #include <iostream>
    using namespace std;
    
    void func1() {
     const int a = 10;
    #define A 20 
     //#undef A  //卸载宏常量A
    }
    
    void func2() {
     //cout << "a:" << a << endl; //不可访问,超出了const int a作用域
     cout << "A:" << A << endl; //#define作用域从定义到文件结束或者到#undef,可访问
    }
    
    int main(int argc, char *argv[]) {
     func2();
    
     return 0;
    }

    程序输出:

    shell
    A:20

问题: 宏常量可以有命名空间吗?

cpp
#include <iostream>
using namespace std;

namespace MySpace {
#define num 1024
}

void test() {
  //cout << MySpace::NUM << endl; //错误
  //int num = 100; //命名冲突
  cout << num << endl;
}

int main(int argc, char *argv[]) {
  test();

    return 0;
}

程序输出:

shell
1024

注意

const与指针转换时

cpp
const int *  <=   int *  // ok
int * <= const int *  // error

面试题

c和c++中const的区别

点击查看答案

整理结构的话,可能分为以下几点:

  1. 语义差异:只读变量 vs 真正常量
  2. 初始化要求:C允许不初始化,C++必须初始化
  3. 作用域与链接属性:外部 vs 内部
  4. 类型系统严格性:C++更严格,禁止隐式转换
  5. 内存处理机制:符号表优化 vs 分配内存
  6. 应用扩展:C++中的更多用法(函数、类成员等)

1. 语义差异:只读变量 vs 真正常量

  • C语言const修饰的变量是“只读变量”,其值运行时仍可能被修改。例如通过指针强制类型转换可间接修改值,编译器仅警告而非报错。
    c
    const int a = 10;
    int *p = (int*)&a;  // C允许强制转换
    *p = 20;             // 输出结果可能不一致(如输出20或30,取决于优化)
  • C++const是真正的编译期常量,值不可修改。直接通过指针修改会导致未定义行为(UB),且编译器会严格报错。
    cpp
    const int a = 10;
    int *p = &a;        // C++直接报错:无法从const int*转换到int*

在编译时期,编译器会将const变量替换为其值,从而消除对const变量的引用。但是也有例外的情况,在使用一个变量初始化另一个const变量时,编译器会保留对const变量的引用。


2. 初始化要求

  • C:允许不初始化const变量(视为未定义值的只读变量)。
    c
    const int b;  // 合法,但后续无法赋值
  • C++:必须初始化const变量,否则编译报错。
    cpp
    const int b;  // 错误:未初始化的常量

3. 作用域与链接属性

  • Cconst变量默认是外部链接(全局可见),其他文件可通过extern引用。
  • C++const变量默认是内部链接(仅本文件可见),若需全局可见需显式加extern
    cpp
    extern const int g_value = 42;  // 其他文件可通过extern引用

4. 类型系统严格性

  • C:允许隐式类型转换,如const指针与非const指针可互相赋值。
  • C++:强制类型匹配,需显式使用const_cast才能绕过const限定符。
    cpp
    const int a = 10;
    int *p = const_cast<int*>(&a);  // 合法但危险

5. 内存处理机制

  • Cconst变量始终分配内存,即使未使用其地址。
  • C++:采用 常量折叠(Constant Folding) 优化,const变量通常不分配内存,直接替换为字面值;仅当取地址或用于需要内存的操作时才会分配。
    cpp
    const int a = 10;
    int array[a] = {};  // 合法:a被替换为10,不分配内存

6. 应用扩展(C++独有)

  • 函数参数与返回值
    • 使用const修饰参数防止意外修改(如void func(const int& x))。
    • const返回值保护返回的指针或引用(如const int* getData())。
  • 类成员
    • const成员函数(如int get() const)保证不修改对象状态。
    • const对象只能调用const成员函数。
  • 引用与指针
    • 常引用可绑定到临时对象(如const int& r = 10;)。

加分项:实际陷阱与优化建议

  • C++的未定义行为:通过指针强制修改const变量可能导致程序崩溃或数据不一致。
  • 代码安全性:C++的const能显著减少逻辑错误,例如用于接口设计时明确参数权限。
  • constexpr的关系:C++11引入的constexpr进一步强化编译期计算能力,但需注意其与const的区别(如constexpr必须编译期确定值)。

满分答案结构示例 候选人可按照以下逻辑组织回答:

  1. 核心语义差异 → 2. 初始化与作用域 → 3. 类型检查严格性 → 4. 内存处理机制 → 5. C++扩展用法 → 6. 实际应用建议
    需结合代码示例(如指针操作、成员函数)和编译行为(如报错、优化)对比,体现对底层机制的理解。