C++ const详解

const详解

c++中const关键字被用来表示一种"常量属性",即变量的值不可被修改。这个关键字也是从C语言中继承来的,但是C语言的const由于无法保证运行时变量的常量属性,所以C++对此引入了常量表来进行升级;const应用于指针的用法和特性几乎和C语言一致;同时,C++也针对引用类型提供了const关键字支持。为了支持OOP,const也可以用来修饰方法。

一、用于普通对象的const

众所周知,C语言中的修饰普通对象的const仅能维持编译期的值不变性,但是运行时值依然是可变的:

int main(int argc, char *argv)
{
    const int a = 0;
    a = 10; // (编译期的const保持)编译错误,因为试图修改一个常量。
    
    // (运行时const属性失效)
    int *pa = (int *)&a;
    *pa = 20; // 正确,此时a的值被修改为20,失去常量属性。
    return 0;
}

为了维持运行时的const语义,c++引入了常量表来记录const对象的名称和值,并将之后所有对const对象的访问修改为访问常量表中的值,以此保证const对象始终如一的常量属性。同时为了保持向下兼容C,C++采取另外一种常量对象的内存分配方式,即:代码中的const常量并不会立刻分配内存,只有当需要该对象的地址时,才为其分配内存,但从不使用该内存。

int main(int argc, char *argv)
{
    const int a = 0; // a成为常量,<a, 0>被保存进入了常量表中,但是并未在栈上为a分配内存。
    a = 10; // 编译错误,因为试图修改一个常量。
    
    int *pa = (int *)&a; // 此时,需要常量a的地址,因此编译器被迫在栈上为a分配一块内存。
    *pa = 20; // 此时,编译器被迫为a分配的内存被写为20,但是编译器不会使用这块内存。
    printf("a = %d\n", a); // 但是,每次对a的访问都会去访问常量表,因此a仍然为0,保持了常量属性。
    return 0;
}

然而,并非所有的常量值都能在编译期被确定。当只有在运行时才能确定常量的值时,该常量将不会进入常量表,仍然保持C语言的运行时不安全的常量属性:

int situation1()
{
    int v = 0;
    const int a = v; // 编译期间无法确定a的值,因此a不会进入常量表。
    a = 10; // 编译错误,因为试图修改一个常量。
    
    int *pa = (int *)&a; // 取得了真正a变量的地址。
    *pa = 20; // 此时a的值被修改为20,失去常量属性。
    return 0;
}
int situation2()
{
    const volatile int a = 0; // 编译期间无法确定a的值,因此a不会进入常量表。
    a = 10; // 编译错误,因为试图修改一个常量。
    int *pa = (int *)&a; // 取得了真正a变量的地址。
    *pa = 20; // 此时a的值被修改为20,失去常量属性。
    return 0;
}

在另外一种情况下,如果赋值号两边的数据类型不同,那么将会产生类型截断,此时const也会失去常量语义:

int situation3()
{
    const long a = 0; // a成为常量,<a, 0>被保存进入了常量表中,但是并未在栈上为a分配内存。
    const int b = a; // 发生了类型截断,因此即便编译期可以确定值,也不会进入常量表。
    int *pb = (int *)&b; // 取得了真正a变量的地址。
    *pb = 20; // 此时b的值被修改为20,失去常量属性。
    return 0;
}

二、用于指针的const

const用于指针时,其行为和C语言中基本一致。例如,顶层const表示指针不能再指向其它地址,底层const保证目前指向的地址中的数据不可被修改:

int main(int argc, char *argv[])
{
    const int *a = nullptr; // 底层const,表示地址中的数据不可被修改。
    int *const b = nullptr; // 顶层const,表示指针不能指向其它位置。
    const int *const c = nullptr; // 顶层和底层const,表示指针不能指向其它位置,
    							  // 并且地址中的数据不可被修改。 
    return 0;
}

三、用于引用的const

const引用没有顶层和底层之分,所有的const引用都是底层的。当针对左值引用使用const时,其行为如同C语言中的const一样,会产生一个新的编译期常量:

int main(int argc, char *argv[])
{
    const int &a = 0; 
    a = 10; // 编译错误,因为试图修改一个常量。
    int *pa = (int *)&a;
    *pa = 20; // 正确,a的值被修改为20.
    return 0;
}

右值不能绑定到左值引用上,但是可以绑定到常量左值引用上,这是因为它可以保证部分右值的不可修改属性。同理,从语义上来看,没有必要使用const修饰右值引用。

四、使用const修饰方法

在类的方法声明中使用const,则表示这个方法不会导致当前对象的改变;同时,为了保持对象的常量属性,const对象只能调用const方法

class Object
{
    int _value;
public:
    Object(int value) : _value(value) {}
    int get_value() const { return _value; }
    void set_value(int value) { _value = value; }
};

int main(int argc, char *argv[])
{
    Object o1(10);
    o1.get_value(); // 正确, 10
    o1.set_value(20); // 正确
    
    const Object o2(10); 
    o2.get_value(); // 正确, 10
    o2.set_value(20); // 错误,const对象只能调用const方法。
    return 0;
}

发布了7 篇原创文章 · 获赞 1 · 访问量 436

猜你喜欢

转载自blog.csdn.net/ZKY1998/article/details/104132593