c++ const理解

1. 如果A是const对象,B是非const对象,则A拷贝给B是允许的。因为这是拷贝,对B更改不会影响到A。
2. 如果A是const对象,R是对A的引用,则R必须定义为对常量对象的引用,因为不允许通过R修改A:
    const int ci = 1024;
    const int &r1 = ci;    // 正确,引用及其对应的对象都是常量
    r1 = 42;            // 错误,r1是对常量的引用,不能用于修改对象
    int &r2 = ci;        // 错误,试图让一个非常量引用指向一个常量对象
3. 如果R是常量引用,即对常量对象的引用(这个叫法可以这么理解:首先它是个引用,其次它是个对常量的引用,常量是定语,起修饰作用),则将其绑定到非常量的对象、字面值、甚至表达式都是可以的,只要该表达式的结果能转换成引用的类型即可。
    int i = 42;
    const int &r1 = i;    // 允许将const int&绑定到一个普通int对象上
    r1 = 0;                // 错误,虽然常量引用可以绑定到非常量对象上,但是不允许通过常量引用修改非常量对象
    const int &r2 = 42;    // 正确:r2是一个常量引用
    const int &r3 = r1 * 2;    // 正确:r3是一个常量引用
    int &r4 = r1 * 2;    // 错误:r4是一个普通的非常量引用
    关于r4为什么是非法的,可以参考C++Primer55页(要想理解这种例外情况的原因...),简单说就是编译器对于r1*2实际上会创建一个临时对象,r4实际上是对临时对象的引用,而对于程序员来讲,一般定义一个非常量引用总会是想着通过这个引用改变对象的值,但是临时对象显然是不可改变的,所以C++就把这类归为非法了。

    int &r5 = r1; //错误:r5是一个普通的非常量引用,r1是一个常量引用

    假设定义有成员函数 const int& Class:func() { return this->m;},目的是防止func改变对象的成员值,函数返回的是常量引用,但是这个常量引用却可以赋值给非常量引用,即

   int &r6 = i;

   r6 = func();是可以编译通过的。这里与r5不同的关键之处在于,r1是左值引用,func()的返回值则不是,实际上我们日常说的引用都是左值引用(C++Primer45),将func的返回值拷贝给r6时,实际只是把返回值引用所代表的对象的值即m的值,赋给了r6,r6指向的仍然是i,后续即使改变r6的值实际改变的是i的值,m的值不会变化,因为r6和m没什么关联。即使把func()返回值定义为int&,其结果是一样的,即也不可能通过r6改变得了m的值。

从这个意义讲,把一个函数的返回值声明为const没有什么意义,无论是const对象还是const引用/指针。如果想控制函数不修改成员的值则应该是在函数参数列表之后加const,这样的函数,其内部如果无意改变了成员的值,则编译器就会报错,防止函数作者自己无意中改变了成员的值。也就是说函数定义为const是防函数作者自己的。该const实际上是修饰了隐含的this指针,详见C++Primer231(紧跟在参数列表后面的const表示this是一个指向常量的指针)。反过来说,如果一个函数定义为const成员函数,则返回值若是引用类型的话,则应当是返回const引用。

把一个成员函数定义为const成员函数还有一个目的是,对于常量对象,是无法使用该对象调用非const成员函数的。(C++Primer231最后一句:不能在一个常量对象上调用普通的成员函数)。

一个声明为const的成员函数,在定义的时候,参数列表后面也要加上const。
4. 对于常量对象,只能使用指向常量对象的指针指向它(为与常量引用的称呼统一起来,我管指向常量对象的指针叫做常量指针,这一点与C++Primer中的叫法不一样):
    const double pi = 3.14;
    double *ptr = π        // 错误,ptr是一个普通指针
    const double *cptr = π    // 正确:cptr是一个常量指针,可以指向一个double常量
    *cptr = 42;                // 错误:不能使用常量指针修改对象的值
5. 对于常量指针,允许把它指向一个非常量对象:
    double dval = 3.14;
    const duble *cptr = &dval;    // 正确,因为完全可以通过cptr访问dval,只是不允许通过cptr改变dval的值罢了
6. 指针常量,即指针本身是常量。
    首先注意其定义方式,那就是让const挨着指针名,例如:int *const p = &x;
    int errNumb = 0;
    int *const curErr = &errNumb;        // curErr将一直指向errNumb
    const double pi = 3.14;
    const double *const pip = π        // pip是一个指向常量对象的指针常量
    指针常量本身虽然是常量,但能不能通过它修改所指向对象的值,跟它本身无关,完全取决于所指向的对象是否是常量:
    *pip = 2.7;        // 错误,pip指向的是一个常量
    *curErr = 1;    // 正确,curErr指向的是一个非常量
7. 先理解一下顶层const的概念,首先注意的是,并不是只有并存两个const的时候才有所谓的顶层const,只有一个const时,也分为顶层const和底层const两种情况。
    简单记法:const修饰的是常量本身时,叫做顶层const,修饰的是指针所指的对象时,叫做底层const,因此实际上我们常用的不涉及指针的const都是顶层const.
    int i = 0;
    int *const p1 = &i; // 不能改变pi的值,这是一个顶层const
    const int ci = 42; // 不能改变ci的值,这是一个顶层const
    const int *p2 = &ci; // 允许改变p2的值,这是一个底层const
    const int *const p3 = p2; // 右边的const修饰的是p3,是一个顶层const,左边的const的修饰的是p3所指向对象,是底层const
    const int &r = ci; // 用于声明引用的const都是底层const,因为引用是对象的别名,修饰引用也就是修饰对象
    当执行对象的拷贝时,顶层const将被忽略掉,例如:
    i = ci;        // 正确,常量引用可以拷贝给非常量对象
    p2 = p3;    // 正确
    当执行对象的拷贝时,底层const需要考虑,一般来说非常量可以拷贝给常量,反之则不行,例如:
    int *p = p3; // 错误:p3指向的是常量对象,如果给了p,则可以通过p修改对象了
    p2 = p3; // 正确:p2和p3均指向的是常量对象
    p2 = &i; // 正确:p2不会被用来改变i的值
    int &r = ci; // 错误:ci是常量
    const int &r2 = i; // 正确:r2不会被用来改变i的值
8. 上述第7条适用于函数的const参数。
    从函数传参的角度理解的话,如果函数const参数是顶层const,即const修饰的是参数本身,则实参是不是常量均可以,也即上面说的,非常量可以拷贝给常量。

9. const作为函数参数的修饰时,其意义在于,禁止函数内部修改实参的值。但有时确看起来不那么明显,例如如下函数

void fuction(const char *A, const char **B);

对于A好理解,A是一个常量指针,函数内部不能通过A修改实参A所指向变量的值。

对于B,B也是一个常量指针,只不过是所指对象是一个指针常量,姑且称为C,也就是说,函数内部将C的地址存到B里,即修改B的值,返回给调用者,但是函数内部不能通过*B修改C所指向对象的值,看起来有点函数作者自己约束自己的意思,而实际上正是这样,它保证作者如果无意中改变了*B所指向的对象的值,编译器就会向他报错。

无论A还是B,它们本身只是一个普通指针,并非指针常量,所以函数可以通过它们输出结果,也就是改变它们本身的值,const限定的只是不能改变它们所指向对象的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值