关于C++中const限定符的学习

    科技2026-01-28  8

    const

    参考书目:《C++ primer》第五版

    为何定义const变量?有什么好处? 当我们在特定的场景下使用一个变量的时候,我们希望该变量的值仅仅在初始化能设置其值,且后续操作都不能更改它的值,这时我们就需要使用const限定符。它的好处是一旦初始化,其对象无法进行修改。

    初始化

    const int size = 20; //输入一个长度大小

    这样就定义了一个int类型的常量。任何试图对size赋值的操作都会引发错误:

    size = 30; //错误,试图对size再赋值

    由于const对象在创建后就不能更改其值,显然必须在定义的时候对其进行初始化。该初始化的值可以是常数或任意复杂的表达式:

    const int a = get_size(); //正确 const int b = 40; //正确 const int k; //错误,未进行初始化

    const的主要限制就是只能在const类型的对象上执行不改变其内容的操作,比如const int能够和普通的int一样参与运算操作。 在不改变const对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则他们是不是const都无关紧要。

    int i = 40; const int j = i; //正确 int k = j; //正确

    虽然j是一个int常量,但是j中的值还是一个整数。j的常量特征仅仅在执行改变j的操作的时候才会体现出来。当用j初始化k时,只需要等号右边是一个同类型的量即可,无需关注它是一个常量还是变量。正如我们拷贝一个值,我们并没有改变它,拷贝完成后,新的对象和原对象没有任何关系。 这里需要注意,默认情况下,const仅在某一个文件内有效。当多个文件中出现了同名的const变量时,等同于在不同的文件中分别定义了独立的常量。想要在多个文件中都使用一个文件定义的const常量,操作和其他(非常量)对象一样,只需要在初始化的文件和需要使用的外部文件都添加extern关键字进行声明。

    extern const int size = 20; //初始化一个可被引用的常量size extern const int size; //引用外部常量

    const的引用

    和正常的引用一样,const也可以进行引用来绑定到其他对象上,称为对常量的引用。然而它特殊的地方在于,对常量的引用不能用来修改它绑定的对象。

    const int a = 10; const int &b = a; //正确,引用及其对应的的对象都是常量 b = 20; //错误,b为对常量的引用,无法修改其绑定的对象 int &c = a; //错误,试图让一个非常量引用指向一个常量对象

    由于a为常量,因此不能对其进行赋值的操作,上述通过引用去改变a的值也是不允许的。若c的初始化正确,那么可以通过c修改其引用的对象a的值,这显然违背了const的初衷。 Tips:实际上并不存在“常量引用”这个说法。因为引用并非一个对象,我们无法让引用本身保持不变。事实上,由于C++并不允许随意更改引用绑定的对象,从这层意义上理解所有的引用都可以认为是常量。引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系本身。简单来说,在引用关系确定后,两者可以认为是同一个对象,这个对象的值能否修改要看其定义是常量还是非常量。

    初始化和对const的引用

    在C++中,引用的类型必须与其所引用对象的类型一致,但有两种情况除外。初始化常量引用时允许用任意表达式作为初始值(只要表达式最后的结果为对应的类型)。

    int a = 10; const int &b = a; //正确 const int &c = 10; //正确,c为常量引用(右边为一个数) const int &d = b * 2; //正确,d为常量引用(右边为表达式) int &e = b * 2; //错误,e为非常量引用(不可通过e引用来修改常量)

    有时候可能我们会粗心写成了下面这样:

    double a = 3.14; const int &b = a;

    然而更惊讶的是,居然没有报错?原来编译器在执行常量引用的时候,自动将a强制转换成了int类型,然后让b绑定了这个临时量。注意,此时b绑定的对象不是a,而是临时量。如果去掉该const,变量b也会绑定临时量吗?在编译器上运行发现提示错误了。原因很简单,变量的引用,其目的是使用b来更改a,这时b绑定了一个临时量,如何修改对象a的值?因此C++将这种操作认为是非法操作。

    对const的引用可能引用一个并非const的对象

    前面已经说过,常量引用不可以修改指向的对象,但是如果指向对象的类型并非const呢?那我们可以修改对象的值吗?

    int a = 10; int &b = a; //引用b绑定对象a const int &c = a; //c绑定对象a,但是不能通过c改变a的值 b = 0; //允许 c = 0; //错误,c为常量引用,不可以通过c修改对象a的值

    Tips:我们直接修改a,c的值也会改变(但其指向的对象不变)

    指针和const

    和引用一样,指针也可以指向常量或者非常量。可以回想一下常量引用的特点,指向常量的指针也不能改变它指向的对象的值。想要存放常量对象的地址,就只能使用指向常量的指针:

    const int p = 3; //p为常量,其值不可变 int *a = &p; //错误,普通指针不可以指向常量(a可以改变p的值) const int *b = &p; //正确,b可以指向一个int常量 *b = 1; //错误,不能给*b赋值(常量)

    同样,指针的类型必须与其所指对象的类型一致。但是允许一个指向常量的指针指向一个非常量的对象:

    int val = 10; b = &val; //正确,这里使用上文的b,但不能通过b改变val的值

    和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象能不能通过其他途径改变。

    const指针

    指针是对象,而引用不是对象,因此指针和其他对象类型一样,允许把指针本身定为常量。常量指针必须初始化,在初始化完成后,它的值(指针中存放的地址)就不能被改变了。把*放在const前说明指针是一个常量,这里的常量的意思是指针本身的值不变,而不是指针指向的值不变。

    int num = 0; int *const err = # //err将一直指向num const double pi = 3.14; const double *const pip = π //pip是一个指向常量对象的常量指针

    要想更快地理解这些声明的含义,就要从右边往左开始解读。在上面的代码第二行err的声明中,离err最近的符号是const,说明err本身是一个常量对象,下一个符号是*,意思是err是一个常量指针,最后类型说明err是一个指向int对象的常量指针。类似地我们可以推断出pip是一个指向double常量的常量指针。 指针本身是一个常量并不意味着不能通过指针修改其指向对象的值,能否更改完全依赖于所指对象的类型。比如pip是一个指向常量的常量指针,那么无论是pip所指的对象的值还是自己存储的地址都不可改变。相反,err指向的是非常量,那么就可以用err去修改num的值。

    *pip = 1.1; //错误,pip为指向常量的指针 if (* err ){ *err = 3; //正确,num被修改为3 }

    顶层const

    指针是一个对象,它可以指向另外一个对象。因此指针本身是不是常量,以及指针指向的对象是不是常量,实际上是两个独立的问题。C++中用顶层const表示指针本身是个常量,用底层const表示指针指向的对象是一个常量。 指针类型既可以是顶层const也可以是底层const:

    int i = 0; int *const a1 = &i; //不能改变a1的值,顶层const const int c = 10; //不能改变c的值,顶层const const int *a2 = &c; //可以改变a2的值,底层const const int *const a3 = a2; //靠右的const是常量指针顶层const,靠左的是指向常量的底层const const int &r = c; //用于声明引用的const都是底层const

    当我们需要进行对象的拷贝时,常量是顶层const还是底层const的区别就十分明显。顶层const不受任何影响:

    i = c; //正确,c是顶层const,拷贝操作不受影响 a2 = a3; //正确,a2和a3指向的对象都是同类型的常量(底层const),a3顶层部分不影响

    而另一方面,底层const的限制很大。当执行对象进行拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,后者两个对象的数据类型必须能够转换。一般非常量可以转换成常量,反之不行:

    //接上初始化 int *a = a3; // 错误,a3包含底层const定义,而a没有 a2 =a3; // 正确,a2和a3都是底层const a2 = &i; // 正确,int *可以转换成const int * int &r = c; // 错误,普通的int&不能绑定到int常量上 const int &r2 = i; // 正确,const int&可以绑定到一个普通int上
    Processed: 0.032, SQL: 9