const 这个关键字会将所修饰的表达式置为常量,编译器会尽全力保证该表达式不可被修改,在const不和指针或者引用结合是,数据的类型是无关const的,但是如果一但和指针和引用相结合,那么考虑数据类型时候必须要考虑const了


class Test
{
public:
    Test(int data = 10):_ptr(new int(data)){}
    ~Test(){
        delete _ptr;
    }

    int GetInt(){
        return *_ptr;
    }
    int GetInt()const{
        return *_ptr;
    }

    int& GetIntRef(){
        return *_ptr;
    }
    int& GetIntRef()const{
        return *_ptr;
    }

    int* GetIntPtr(){
        return _ptr;
    }
    int* GetIntPtr()const{
        return _ptr;
    }  //  int *const _ptr

    int*& GetIntPtrRef(){
        return _ptr;
    }
    int*const& GetIntPtrRef()const{
        return _ptr;
    }
private:
    int *_ptr;
};

int main()
{
    Test t1;
    const Test t2;

    int a1 = t1.GetInt();
    int &b1 = t1.GetInt();
    const int &c1 = t1.GetInt();

    int a2 = t2.GetInt();
    int &b2 = t2.GetInt();
    const int &c2 = t2.GetInt();

    return 0;
}

举个例子,上面代码种有几处是有错误的,找出它~~~~

上面代码种b1是错误的,b2也是
先来看看b1,getInt返回的是一个整形值,它所返回的是一个立即数,那么用引用接收的时候就会出现无法给立即数取址的问题,此时寄存器带出的是一个立即数,而立即数不会有地址的,所以如果要引用,必须是一个常引用,否则编译不通过。
b2也是同样的,立即数不能取址


那如果一开始返回的不是立即数而是一个引用呢?

int main()
{
    Test t1;
    const Test t2;

    int a1 = t1.GetIntRef();
    int &b1 = t1.GetIntRef();
    const int &c1 = t1.GetIntRef();

    int a2 = t2.GetIntRef();
    int &b2 = t2.GetIntRef();
    const int &c2 = t2.GetIntRef();

    return 0;
}

这样编译下来,并没有什么问题,原因是返回一个引用,此时会生成一个临时量,不存在立即数的问题了


那么我们再来看看如果要是返回一个指针呢?

int main()
{
    Test t1;
    const Test t2;

    int *a1 = t1.GetIntPtr();
    int *&b1 = t1.GetIntPtr();
    const int *&c1 = t1.GetIntPtr();

    int *a2 = t2.GetIntPtr();
    int *&b2 = t2.GetIntPtr();
    const int *&c2 = t2.GetIntPtr();

    return 0;
}

此时又出现了刚才的问题,指针本身也是一个在32位系统下4字节的基础类型,所以返回值也会是一个立即数,不能取址,必须用常引用


简单总结一下:
对于赋值表达式的两边

  1. 如果是值,只需要类型相同
  2. 如果是指针,要求必须能取地址
  3. 如果是引用,必须是能取地址,类型相同

再补充3个例子



    int*& GetIntPtrRef(){
        return _ptr;
    }
    int*const& GetIntPtrRef()const{
        return _ptr;
    }

int main()
{
    Test t1;
    const Test t2;

    int *a1 = t1.GetIntPtrRef();
    int *&b1 = t1.GetIntPtrRef();     
    const int *&c1 = t1.GetIntPtrRef();

    int *a2 = t2.GetIntPtrRef();
    int * &b2 = t2.GetIntPtrRef();
    const int *&c2 = t2.GetIntPtrRef();

    return 0;
}

此时编译下来有3个错误,本质上还是类型不相同导致的,GetIntPtrRef()const的返回值是int const,但是b2是一个int *&,所以需要再&前面加上const保持类型相同
c2也是一样的道理,需要再引用前面加const

这里还有一个c1的错误,这个错误有些特殊,前面说过,const修饰的变量,编译器会尽力保证这个值不可被修改,此时const修饰的是&c1这个表达式,所以通过c1是不能修改的,但是此时却可以通过c1这个引用变量修改c1的内存,将引用换个指针就更加明显了,q,p这两个变量都可以修改同一片内存,此时我们告诉编译器,q这是个常量,不准修改了,但是编译器发现通过*p居然也可以修改这里的内存,必然是通过不了的。修改的话可以限定常引用,防止修改同一片内存

const int *const &c1 = t1.GetIntPtrRef();

再看第二个





int ** GetIntPtrPtr()
{
    return &_ptr;
}
int *const* GetIntPtrPtr()const{
    return &_ptr;
}

int main()
{
    Test t1;
    const Test t2;

    int **a1 = t1.GetIntPtrPtr();
    int **b1 = t1.GetIntPtrPtr();
    const int **c1 = t1.GetIntPtrPtr();

    int **a2 = t2.GetIntPtrPtr();
    int **b2 = t2.GetIntPtrPtr();
    const int **c2 = t2.GetIntPtrPtr();

    return 0;
}

此时有4个错误,再常对象t2中_ptr是一个常量不可被修改的,所以 t2所有接收的类型都是int const ,故而a2,b2,c2都有错误
而c1中也就是那个特殊的错误,const修饰的变量编译器会尽力防止内存修改,所以应该是const int const 类型,故而c1也有错误


再来看看最后一个例子

int ** const& GetIntPtrPtrRef(){
    return &_ptr;
}
int *const * const& GetIntPtrPtrRef()const{
    return &_ptr;
}

int main()
{
    Test t1;
    const Test t2;

    int **a1 = t1.GetIntPtrPtrRef();
    int **b1 = t1.GetIntPtrPtrRef();
    const int **c1 = t1.GetIntPtrPtrRef();

    int **a2 = t2.GetIntPtrPtrRef();
    int **b2 = t2.GetIntPtrPtrRef();
    const int **c2 = t2.GetIntPtrPtrRef();

    return 0;
}

这个的错误和上个例子十分相似,在此不多赘述了。


总结:

  1. 等式的两边先进行类型的比较,需要注意const在右边没有*时候是不计算类型的,保证两边的类型首先相同。
  2. 其次注意const修饰的变量,指针或者引用是必须要求参数能够取地址的,如果是一个立即数的话,就必须注意要用常引用。
  3. 再就是那个特殊的错误,对于const与二级指针的结合,要求const修饰的对象的那块内存不能修改,所以两个*之前要么都有const,要么都没有。

且行且歌_C
62 声望8 粉丝

逝者如斯夫,不舍昼夜