[C++]浅谈C++中的const

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sixdaycoder/article/details/78111273

从基本用法说起

1. 修饰变量

const是constant的一个表示,字面上来看,以const修饰的是一个常量。实际上,这是一个误解,或者说是历史遗留的问题,用readonly来表示const更为妥当。

最简单的例子,我们都会使用:

const int x = 5;
x = 10;//error

下面我们看一个例子,可能和你印象中的const就不一样了~

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

    const int x = 10;
    int* px = (int*)&x;

    printf("x  at address : %p\n", &x);
    printf("px at address : %p\n", px);

    *px = 20;

    printf("x  is : %d\n",  x);
    printf("px is : %d\n", *px);
}

在上述程序中,我们试图使用px间接改变x的值。
这个的结果是什么呢?

说出来你可能不信,是这样的:

这里写图片描述

显然,x的地址和px指向的地址是一致的,相信大家不会有疑问。关键是我们通过px间接改变了x的值,尽管px指向的地址的内容已经变为了20,但是x的值还是10。

有人会说,这证明了const就是标志一个常量!通过指针也无法修改!

实际上,003AFE2C这块内存的值已经被修改为20了。

但是为什么x的值输出还是10呢?

这是因为,x的值在编译期就已经知道是10了,那么在读取x的值的时候,就会被编译器直接替换为10。

但是,我们要明确一点,就是const修饰的值未必在编译期就可以确定。

我们再看一个例子,来说明这一点:

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



    int scale = 5;
    const int y = 5 * scale;//y的值在运行时才可以确定
    int* py = (int*)&y;

    printf("y  at address : %p\n", &y);
    printf("py at address : %p\n", py);

    *py = 20;

    printf("y  is : %d\n", y);
    printf("py is : %d\n", *py);


    getchar();
    return 0;

}

上述程序的运行结果是这样的:

这里写图片描述

可以看出,由于y的值在编译期没有确定,在运行时,读取y的值要到0026F9C0中去找寻数据,此时通过py已经把y的值修改,所以此时输出y就变成了20。

我们还可以通过下面这个例子进一步说明const修饰的值未必在编译期就可以确定。

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

    const int x = 10;

    int scale = 5;
    const int y = 5 * scale;


    int arrx[x];//fine,编译期已经确定了x是10
    int arry[y];//error,表达式必须含有常量值, 因为y的值在编译期没有确定

    getchar();
    return 0;

}

2. 修饰类的数据成员

可以用const修饰类内成员,需要注意的是一定要给定一个初始值:

class Test {

public:
    const int x;

    Test() {}//error,提示没有对x进行初始化,仅声明了x而已
};

//正确写法
class Test {

public:
    const int x;

    Test() :  x(0){//只能用两列表初始化话的方式
    }
};
//或者
class Test {

public:
    const int x = 10;
};

3. 修饰类的成员函数

const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。一般写在函数的最后来修饰。

 class A
 { 
       void function()const; //常成员函数, 它不改变对象的成员变量.                        
 }

对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用。

a. const成员函数不被允许修改它所在对象的任何一个数据成员。

b. const成员函数能够访问对象的const成员,而其他成员函数不可以。

指针和const

上述的都是一些基本的用法,下面谈一谈const和指针。

我们最常见的const和指针的组合就是 :

int x = 10;
const int* px = &x;

这表示指针指向的那块内存是只读的,但是px还可以指向其他的地址:

    int x = 10;
    const int* px = &x;
    int y = 20;
    px = &y;

还有一种不是特别常见的组合方式:

int x = 10;
int* const px = &x;

这表示指针本身是一个const,它必须初始化指向一个地址。然后不能改变,但是那块地址的内容是可以改变的。

例如:

    int x = 10;
    int* const px = &x;

    *px = 20;
    printf("x is : %d\n", x);//x = 20

    int y = 20;
    px = &y;//error

为了对指针的const做出区分,C++给予了不同的命名:

  • 顶层const(top level const) : 意指指针本身是一个const。顶层const同样适用于其他的数据类型const int x = 10, const double = 3.14, const ClassType obj("xxx")等都属于顶层const,只不过只有牵扯到指针的const的时候我们才使用这样的术语。

  • 底层const(low level const) : 意指指针指向的对象是const属性的。

当然,我们可以把两者用在一起,顶层和底层const共存:

int x = 10;
const int* const p = &x;

那么上述代码p的语义有两层含义 : (1)p本身是const,不可指向别的对象 (2)p指向的对象是const的,不可以使用p来修改x。(当然,这里你可以通过修改x的值来改变*p)。

最后,我们总结一下const和指针用在一起的时候的写法:

  • const int* p = &x(等同于int const* p = &x),底层const。

  • int* const p = &x,顶层const。

一开始可能会被不同的写法搞混,有一个简单的技巧:

如果*在const的左边,那么这是一个顶层const
如果*在const的右边,那么这是一个底层const

C++11的constexpr

之前我们说过,const表示一个只读的含义,且在编译期它的值未必能够确定,从C++11开始,标准加入了一个constexpr的关键词,中文译为字面值常量,它是在编译器就可以确定的值。

我们做个简单的对比:

int main(){
    int x = 10;
    const int c1 = x;
    constexpr int c2 = x;//error,x在编译期无法确定,不能给c2赋值
}

当然,我们有种比较特殊的情况:

const int x = 10;
int main(){
    constexpr int c = x;//ok
}

由于x这次是在堆上分配的内存,在编译期它的值就可以被确定,所以这样赋值是ok的。

同理,我们可以这样做:

int x = 10;

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

    constexpr int* p = &x;

}

和这样做:

const int x = 10;
const int y = x * 5;

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

    constexpr int p = y;


    getchar();
    return 0;

}

如果这里有不明白的地方,推荐找一下C++编译器和运行期的相关知识来补充一下。

其余的用法constexpr和const差不多,也可以用用来修饰函数和指针等,不过,这里有个很特殊的地方:

constexpr int inc(int x) {

    return x + 1;
}


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

    int x = 10;

    int arraya[inc(10)];//ok
    int arrayb[inc(x)];//error

    getchar();
    return 0;

}

同这个例子,我们可以小结。

所以,对于constexpr需要两方面看待。constexpr修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值。但是,传入的参数如果不能在编译时期计算出来,那么constexpr修饰的函数就和普通函数一样了。不过,我们不必因此而写两个版本,所以如果函数体适用于constexpr函数的条件,可以尽量加上constexpr。而检测constexpr函数是否产生编译时期值的方法很简单,就是利用std::array需要编译期常值才能编译通过的小技巧。这样的话,即可检测你所写的函数是否真的产生编译期常值了。

作者:蓝色
链接:https://www.zhihu.com/question/35614219/answer/63798713
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/sixdaycoder/article/details/78111273