Non-Profit, International

Spirit unsterblich.

C/C++ const

字数统计:1321 blog

使用 const 关键字修饰的变量的值不能修改,称为常量,但是 C 语言中实际上可以通过指针修改常量的值,C++ 中则不能。

由于指针的存在,指针变量有三种状态: const type *, type * const, const type * const,即 const 修饰基本数据类型,const 修饰指针,const 修饰指针和基本数据类型。

在 C++ 中,常量是严格禁止修改的,但是在 C 中,可以通过强制类型转换为一个普通指针类型的临时变量,通过这个变量修改原本的数据:


const int a = 0;
int *p = (int *)&a;
*p = 1; // 合法且a变成1,这是 C 不安全的原因之一

const 修饰基本数据类型被称为底层(low-level) const,const 修饰指针被称为顶层(high-level) const,并且由于指针也可以指向指针,引用也可以指向指针(默认被 const 修饰),所以在使用上又复杂许多。

指针和引用

示例:


int ival = 1;
int &re = ival;   // re是对ival的引用
int &re2 = &ival; // 错误,引用的对象不能是地址
int *pi = &ival;  // pi是指向ival的指针
int *&pi2 = pi;   // ok,pi2是一个对指针的引用

根据汇编代码可以得到,引用在底层实现上和指针是一样的,区别只在于引用的对象一定不为空,并且引用默认被 const 修饰。 不过标准通过编译器额外对引用增加了一些限制,避免在二义性做出选择。

顶层 const 和底层 const


int i = 0;
const int * p1 = &i;
int *x = p1; // 错误,*x不具备底层const资格
const int *x2 = p1; // 正确,具备底层const资格
const int *x3 = &i;  // 正确,&i是int *,可以转成const int *


int num_a = 1;
int const  *p_a = &num_a; //底层const
*p_a = 2;  //错误


int num_b = 2;
int *const p_b = &num_b; //顶层const
p_b = &num_a;  //错误


#include <cstdio>
int main(){
    int num_c = 3;
    int num_d = 4;
    const int *const p_c = &num_c;
    *p_c = 4;      //expression must be a modifiable lvalue
    p_c = & num_d; //expression must be a modifiable lvalue
    num_c = 4;
    const int num_e = 5;
    num_e = 6;     //expression must be a modifiable lvalue
    getchar();
}

通过上面的代码可以看到,既可以直接用 const 修饰基本数据库类型,也可以在声明指针的时候,用底层 const 修饰指针,此时无法根据指针修改数据的值。

此时需要注意一点,如果函数的形参非 const,而实参是 const,那么这段程序就是错误的,C++ Primer 中解释到,这是为了避免函数修改 const 变量,如果确定安全,此时需要 const_cast。

const_cast

用法:const_cast <type> (expression)

const_cast 可以在 调用时 忽略 const 或者添加 const,并不能改变原有的 const 状态。

type 可以使用 type 本身,也可以使用被 const 修饰的 type(如const_cast <const type> (expression)),这种情况一般不需要,因为非 const 修饰的变量可以直接赋给 const 修饰的变量。

数组的指针和指针的数组


char *arr[4]; //这个数组每个元素都是一个指针
char (*pa)[4];//pa是一个指向数组的指针

再看如下代码:


char *arr[4] = {"hello", "world", "string1", "string2"};

arr 是一个指针数组,有四个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。

数组 arr[4] 的四个指针元素,分别存放着这四个字符串的首地址,arr+1 会跳过四个字节,也就是一个指针的大小,达到下一个元素的首地址。

这就相当与定义 char *p1 = "hello"char *p1 = "world"char *p3 = "string1"char *p4 = "string2",这是四个指针,每个指针存放一个字符串首地址,然后用 arr[4] 这个数组分别存放这四个指针,就形成了指针数组。

注意,字符型指针数组能够直接用字符串初始化是特例,因为用引号引起来的字符串实际上可以转换为一个指向该字符串的指针。

参考:
最后修改:2021-11-15

若无特殊声明,本人原创文章以 CC BY-SA 3.0 许可协议 提供。