C++中const和引用修饰变量和函数的总结

来源:互联网 发布:网络推广课程 编辑:程序博客网 时间:2024/04/30 16:31

一、对于修饰变量的用法

对于const和&的基础用法就不说了。下面说点之前有误区和容易错的地方

const修饰变量的误区

关于const类型,这里有一个我之前的误区,我以为const定义的时候只能用常量初始化,但是实际上是没有这个规定的。const定义的变量可以用各种const,非const对其进行初始化,甚至可以是函数的返回值之类的。

const int a = 5//在编译的时候进行初始化,即类似于#define,进行全文替换const int b = size();      //在执行的时候初始化int n =7;const int c = n;         //在执行的时候初始化

这个其实非常显然啦,因为在函数传参的时候,就能看出来,形参可以是const的,但是传进去的实参可以是非const的。[1]

对于这个问题就可以看出中文各种博客上面的说法有多垃圾了,就我看过的80%的博客都说const必须要用常量初始化,同时在编译时就完成了初始化。以后技术问题少看中文博客,之前被误导了那么久。


常量指针和指向常量的指针

指向常量的指针

const int *p,表示指向常量的指针,按照之前上篇文章中的方法C++
中复合类型声明的理解从右到左看,p是一个指针,指向const
int,表示的意义就是不能通过p来修改p指向的值,而p本身所指向的地址是可以修改的。(此处也可以写成int const *p是一样的)

常量指针

int *const p,表示p本身是一个const,所以p所指的地址不会变,但是地址里的值是可以修改的。

有个需要注意的地方是:常量引用

const int i1 = 1024;const int &ci1 = i1;int i2 = 2048;const int &ci2 = i2;    //允许将const int&绑定到一个普通int上,其实常量引用可以用任何值进行初始化

其实常量引用这个说法是一个非常不严谨的说法,因为C++中本身就不允许随便改变引用所绑定的对象。所以所有引用都是常量。而常量引用类比于上面指针的说法,应该是对常量的引用
还要记住一点就是常量引用可以用任何值进行初始化,即可以用右值对其初始化。

 int i = 1024const int &ri = i*2

对于这一点,可以用复制构造函数去理解,在C++11标准的右值引用出来之前,就是用常量引用来延长一个右值的生命周期的。其在计算机内的实现可以认为是这样的:

const int tmp = i*2const int &ri = tmp;

即ri绑定的是一个临时变量。在C最早版本的时候,引用时可以绑定左值和右值的,只不过如果出现上面这种情况,ri绑定一个临时量,如果ri不是const的话,其修改也会导致临时量的修改,而此时临时值已经被销毁了。所以ri必须是const的。


顶层const与底层const

这里有一个顶层const和底层const的区别,正常情况下,const类型的变量可以给任何变量赋值和初始化的。但是指针就不一样了,指针本身是个对象,而其可以指向另外一个对象,所以指针本身是否是const和其所指的是不是一个常量就是两个独立的问题。
顶层const是指const修饰变量本身,即变量/对象本身是;而底层const则表示这个变量所指向/引用的值不可变,即不能通过这个变量修改其值,只有对常量的引用和指向常量的指针是底层const

int i=0;int *const r3 = &i;const int r1 = i;

以上例子是顶层const即对象本身是const的。而

const int *r4 = &i;const int &r5 = i; 

则是底层const,其所指的对象是常量,即不能通过自己修改原来数的值
对于底层const而言,底层const只能赋值给同样是底层的const,即int* r6 = r4会报错。
注意下面一个问题

const int m = 0;      //m本身是const类型的值const int* m2 = &m;     //所以&m是一个const int*类型的,即常量取地址得到的是一个底层constint* const m3 = &m; //所以第二行没有错,第三行会报错:const int i = 0;   //i是底层constint &i2 = i;     //报错,因为i2不是底层const 

第一个报的错误就是 “const int *” 类型的值不能用于初始化 “int *const” 类型的实体
第二个报的错误就是将 “int &” 类型的引用绑定到 “const int” 类型的初始值设定项时,限定符被丢弃


二、对于修饰函数的用法

引用限定符

先看一个神奇的语句

string s1 = "hello ";string s2 = "world!";s1+s2 = "wow!";string("adfasdf") = "123";   //注意此时一定要加string,因为C++对于常量字符串是用const char*数组保存的,

以上这三句话是可以顺利运行的,原因在于s1+s2得到的是一个右值,而string类型的对象,会重载=运算符:
string &operator=(const string&)
而=运算符可以作为一个函数进行调用,而对对象的函数进行调用时是不在乎其是左值还是右值,如(s1+s2).size()肯定是可以运行的,所以上面的那个=号也是可以运行的。
还有一点需要注意就是=号返回的是一个左值,所以如果对于连等的情况a1 = a2 = a3,其从右向左一次调用=号,等价于a1 = (a2 = a3),而此时调用的是=是复制运算符,而不是移动复制运算法符,因为返回的是左值。
所以本质上来说,因为函数只能限制传入的参数类型,和返回的类型,并不能限制this本身的类型导致的。

但是现在新标准为了填旧标准的这个坑,就加入了引用限定符(reference qualifier)虽然string这个类为了像前兼容,上述写法还是可以用的,但是对于我们自己写的对象来说可以避免这种情况了。跟const类似,引用限定符是加在参数列表之后的:

Foo &Foo::operator=(const Foo&) &;         //this此时只能是可修改的左值Foo &Foo::operator=(const Foo&) &&;        //this此时只能是右值

在函数声明结尾加入&和&&来限制this本身。所以此时再重载=运算符,其后加上&,就能避免之前的那种情况了。
还有几点需要注意:

  • const和&都只能用来修饰类的非static成员函数,在函数的声明和定义中都要出现
  • 一个函数可以右const和&同时修饰,但是此时const必须加在&之前

const限定符

这个其实没啥说的,就是在把const加在参数列表之后。

  • 常量函数只能调用常量函数。
  • 常量对象只能调用其中的常量函数

这里加一个const和#define用法上的区别

  • const便于进行类型检查:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误
  • 可以节省空间,避免不必要的内存分配:const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝

    更多const的用法总结详见这篇文章:关于C++ const 的全面总结

0 0
原创粉丝点击