C++中的const 引用

来源:互联网 发布:深圳海关数据 编辑:程序博客网 时间:2024/05/22 05:08

最近编程总遇到const的问题,于是就想总结一下,这里的总结不是想面面俱到,而是想把个人觉得有必要注意的问题和那些让人感觉const美妙的地方梳理一下,欢迎留言补充

本文大概将分三个层面

  • const指针、const引用、const引用形参
  • const在类中
  • const与重载

------------- const指针、const引用、const引用形参 --------------------

【1】const修饰指针和引用

1. 术语“const引用”就是“指向const对象的引用”,习惯说成const引用与非const引用。这点与指针不同,指针中“const指针”与“指向const对象的指针”是不同的。

2. 值得注意的是:const引用和指向const对象的指针二者有一个共同点:const引用既可以指向const对象又可以指向非const对象;指向const对象的指针亦是如此.

3. 关于const指针与指向const对象的指针,举一个很简单例子

1
2
3
4
5
6
int m=1, n=5;
const int *p1=&m;   //指向const对象的指针:const修饰的是*p1, 即*p1的值是只读的;但是p1这个指针是可以修改的。
int const p2=&n;  //const指针:const修饰的是p2, 即p2这个指针是只读的;但是*p2的值是可以修改的。
p1=&n;              //p1的指针修改为变量n的地址,而这个地址就是p2,相当于p1=p2;
*p2=3;              //*p2的值修改为3,当然*p1的值也就是3
printf("%d %d\n",*p1,*p2);

【2】形参为const引用的好处

说到const,就不得不提const引用类型的形参,真正理解了const引用形参的好处,才发现它真是美妙的很

简单总结一下,欢迎补充

a. 当实参的类型比较大时,复制开销很大(形参初始化时),引用会“避免复制”。(这在传递类对象时比较常用)

b. “避免修改实参”,当使用引用时,如果调用者希望只使用实参并不修改实参,则const可以避免使用该引用修改实参

c. 相比非const引用形参,更具实用性:形参可以使用const对象初始化,可使用字面值或右值表达式的实参来初始化

下面各给一示例

1
2
3
4
5
6
7
8
a. void search(const vector<int> & vec) 避免了实参的复制开销
b. 同a例,可避免对实参做出修改
c. 如下函数,调用时
void search(string & s);         调用: search("hello"); // Error 实参为字面值常量
void search(const string & s);   调用: search("hello"); // OK
再如
void search(int & v);            调用: search(v1+v2);   // Error 实参是一个右值,无法给引用赋值(需要左值)
void search(const int & v);      调用: search(v1+v2);   // OK

总结
    const的使得引用与指针的变化更加复杂,总体而言,const主要是保证了通过指针或者引用不修改原始的数据,但是至于原始的数据是否可以修改,这就需要参看数据的类型。
    在存在const的对象中,只能采用包含限定符const的引用或者指向const的指针来操作。
    const的引用比较强大,初始化的过程中可以采用任意的对象,const对象,非const对象,甚至其他类型的数据(尤其是在函数参数中的使用)。const引用支持隐式类型转换。而指向const的指针则不能,只能指向同一类型的数据,但是可以采用强制类型转换,初始化或者赋值过程中对数据类型没有要求,可以是const对象的地址,也可以是非const对象的地址。
    const引用和指向const对象的指针都是自己以为自己指向的对象是不能修改的,采用const的指针或者引用就能避免原始数据修改。

---------------const 在类中----------------------------------------

一般来讲,使用const修饰函数,则函数一定是类成员函数。

【1】const对象只能调用const成员函数 非const对象可以调用const成员函数

解释:不能将指向const对象的指针赋值给非const对象指针,而允许将非const对象指针赋值给指向const对象的指针。

为什么这样解释?这源自于调用成员函数时发生的事情:

调用成员函数时会发生的事情:将调用对象与函数绑定,即将成员函数隐含的形参this初始化为调用对象的地址。

因此,const对象传递的实参地址为const class * const this,而非const成员函数被调用时还是使用了class *const this的形参接收,结果就是把指向const对象的指针赋给非const对象的指针,这是不允许的,所以const对象只能调用const成员函数;后者同理,非const对象在调用const成员函数时实质上是将非const对象的指针(实参)赋值给了const对象指针(形参),而这又是可以的,故非const对象可以调用const成员函数。

【2】const成员变量不能被修改,且必须在初始化列表中赋值;

const成员函数不能改变成员变量, 且不能调用非const成员函数(即不可有改变值的企图)

相反,非const成员函数当然可以调用const成员函数

【3】const成员函数的返回值:值类型 & const引用类型  (不可返回非const引用)

1
2
3
4
5
6
7
8
9
10
11
example.
class Vec
{
private:
    vector<string> textVec;
public:
    const string & text_line(size_t lineNum) const
    {
        return textVec.at(lineNum-1);
    }
};

如果成员函数为const,则对象调用该常函数传递this时已经变成了const对象,如果函数写成如下形式

1
2
3
4
string & text_line(size_t lineNum) const
{
    return textVec.at(lineNum-1);
}

则编译会报错:不能将const string 转化为 string &,因为此时相当于将一个非const引用指向了一个const类型变量。故要返回引用就必须是const引用。

------------返回值类型---

当然还有其他的写法,即让函数返回值为值类型,即

1
2
3
4
string text_line(size_t lineNum) const
{
    return textVec.at(lineNum-1);
}

此时实际上是做了一个内存拷贝,因为string是类类型,有人会问,那这样不是相当于一个const对象赋值给非const对象吗?

对于上述函数,实际是调用了string的拷贝构造,而且拷贝构造的参数便是const string类型,这里又涉及到const类型作参数的问题,上面已讲

对于函数的返回值为值类型,无非就两种,一种是普通的内置类型,进行赋值操作;一种是类类型,调用拷贝构造进行内存拷贝。如下:

1
2
3
4
const int a = 9;
int b = a;   (int &bb = a; // error)  // 赋值
const set<int> s;
set<int> ss = s;   // 内存拷贝

所以我们要注意:

不可以将const对象指针或引用赋值给非const对象的指针和引用,但是可以将const对象初始化给非const对象;

const与非const在对象层次(not引用或指针层次)上的互相赋值是允许的,可逆的;就像

1
2
3
const int a = 9;
int aa = a;
const int aaa = aa;

-------------------const与重载---------------------------------------

基于const重载的情况有两种,一种是基于函数形参类型是否为const的重载;一种是基于类成员函数是否为const的重载

【1】   基于函数形参类型是否为const的重载

当且仅当形参为引用或指针类型时,形参是否为const才有影响

-----引用类型----

可基于函数的引用形参是指向const对象还是指向非const对象,实现重载,示例如下:

1
2
3
4
5
6
A: void search(Student &);
B: void search(const Student &);
const Student a;
Student b;
search(a);  // 调用B
search(b);  // 调用A

注:根据上面讲的我们知道,如果形参是普通引用,则不能将const对象传递给该形参。

如果传递的是非const对象,按上面提到的,这两种函数都是可行的,因为非const对象既可用于初始化const引用,也可初始化非const引用。那么没有二义性么?

这就是编译器在调用函数时遵循的原则首先是“精确匹配”,我们知道,将const引用初始化为非const对象需要隐式转换的,所以不符合精确匹配,故调用A。

-----指针类型----

基于指针形参的const重载与引用是一样的。只是有一个问题要注意:

上面术语说到const引用就是指向const对象的引用,指针则区别于const指针和指向const对象的指针。

所以要注意:不能基于指针本身是否为const来重载函数

1
2
3
4
void search(int * ptr);
void search(int const ptr);
// 这不能实现重载,指针本身是否const并不会带来区别
// 在函数调用时,形参都复制了指针的值

【2】   基于类成员函数是否为const的重载

在类中,基于成员函数是否为const,可重载一个成员函数,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Vec
{
private:
    vector<string> textVec;
public:
    const string & text_line(size_t lineNum) const
    {
        return textVec.at(lineNum-1);
    }
    string & text_line(size_t lineNum)
    {
        return textVec.at(lineNum-1);
    }
};
0 0
原创粉丝点击