C++中const与指针、引用的分析(转自china_unix GP-King)

    C++中函数的参数相比C语言中的函数参数要复杂的多,其中主要的缘由是C++中引入了引用以及const限定符。这两个对象的引入,使得C++中的函数参数变得异常的复杂多变,每一种类型都具备比较适合的使用范围。
本文详细的分析const与指针、引用在一块儿存在状况下的状况分析。
 
首先介绍引用
    引用是对象的别名,必须在初始化的过程当中与一个具体的对象绑定起来,绑定完成之后就不再可以修改了,引用貌似和指针有很大的类似性,可是引用是引用,是一个别名,而指针是一个变量,只是变量中保存的是对象的地址,引用并不分配新的内存空间。所以一个引用与一块内存区域绑定,是这块内存区域的别名,就如同数组名同样,数组是内存中的一块区域,数组名是这块区域的名字,可是在内存中并不存在额外的区域保存数组名。引用就是一块内存区域的别名。C++中变量实质上就是一块内存区域,咱们在用&i就能够获得变量i的地址,也就是说变量是这块区域的名字。对变量的操做实质上就是对这块内存中内容的操做。别名是这块区域的另外一个名字。采用任何一个名字对这块区域修改都会影响区域的内容。


  1. int vari = 10;
  2. int &refr = vari;
  3. vari = 20;
  4. cout << refr << " " << vari << endl;
  5. refr = 30;
  6. cout << refr << " " << vari << endl;
上面的 int &refr = vari实质上就是变量vari的别名,也就是保存vari变量地址的别名,这个地址中保存的是一个int类型的数据,数据能够经过vari来操做,能够修改。由于refr是一个别名,也能够经过该别名对这块内存区域进行操做。也就是说别名的操做就是针对一块特定的内存区域,这个经过变量操做的效果是一致的。
其次说说const限定符
    const的引入使得在函数操做中的一些问题复杂了,可是更加的合理了,const限定符是指上是指一个常量。这个常量在通常的状况下就是限定变量不能修改,可是当这个限定符与引用、指针结合在一块儿时,使得不少的问题不在明朗。
 
     一、const与指针


  1. int *ptr;
  2. const int *ciptr;
  3. int const *icptr;
  4. int * const cptr;
  5. const int * const cicptr;
上面是关于const与指针结合时的各类状况,这并不仅是C++中常常遇到的问题,在C语言中也会有相似的讨论,虽然const并非C语言中的关键字。
int * ptr         是指定义一个指向int 类型的指针ptr。
const int *ciptr 是指定义一个指向const int 类型的指针ciptr,这是const 限定的是(* ciptr),也就是对指针解引用,即const限定的就是数据自己,而非指针。因此ciptr是一个指向常int型数据的指针。
int const * icptr其实和上面的 const int *ciptr是一致的由于const只是一个限定符,在int前仍是后都 没有影响,他限定的仍然是(*icptr),并非icptr,也就是icptr也是指向常int型数据的指针, 也就是说在icptr这个指针看来,它指向的数据是常数,是不能改变的,可是是否真的不能改变,须要依据实际的类型的分析,只是不能经过这个指针来改变。也就是说该指针是一个自觉得本身指向常量的指针。
int * const cptr 这时候咱们仍是结合const的位置分析,const限定的是cptr,cptr咱们能够知道是一个指针,也就是说const限定的是一个指针,而不是指针指向的数据,也就是说这种定义实质上是定义一个常指针,也就是指针指向的地址是不变的,也就是cptr是不能被赋值的,不能采用这个指针指向其余的对象或者地址。可是这个地址中的数据并非常数,是能够改变的,能够经过对*cptr进行修改。这种指针实质上也就使得指针的效果大大减少,并不经常使用。
const int * const cicptr 这种定义结合上面的分析咱们知道是一个指向常量的常指针,也就说这个指针指向的地址是一个常地址,指针不能指向其余的对象或者地址。 同时对指针cicptr来讲,我指向的这个地址中的内容也是不能修改的,是一个常量,是不能修改的,可是该地址的数据可否修改还须要进行实际的分析。
 
     二、关于指针的初始化、赋值的问题
    对于const类型的数据,若是须要定义指针,这时候只能定义const类型的数据,这是为何呢?由于对于const对象来讲,数据是确定不能修改的,若是定义指向非const的指针,程序员可能就会经过指针来修改对象的值,可是const对象是不能被修改的,确定会出现错误。所以const的变量只能采用指向常量的指针来指向。通常来讲若是将一个非const指针指向了一个const对象,编译的过程当中就会抛出以下的错误:
  1. invalid conversion from ‘const int*’ to ‘int*
    可是对于指向常量的指针并不必定指向的数据就是常量,这是一个 很是重要的技术点,指向常量的指针指向的数据只是针对这个指针而言,他认为本身指向的数据是常量,休想经过他来修改指向的对象。也就是说指向常量的指针在初始化、赋值的时能够初始化或者赋值为非const变量的地址,便可以指向非const的对象,只是不能经过该指针来修改对象罢了。
    同时须要注意:对于指向const的指针,初始化过程比较方便,不要求是const对象的地址,能够采用非const对象的地址初始化,甚至还能够采用指向非const的指针直接赋值初始化,这时指针本身认为本身指向的对象是常量。可是不能将指向const的指针直接赋值给指向非const的指针,若是不当心赋值也会出现上面出现的问题。下面参看一段小的代码,说明其中的一些问题。


  1. #include <iostream>
  2. #include <string>
  3. #include <vector>

  4. using namespace std;

  5. int main()
  6. {
  7.         int num = 20; 
  8.         const int array_size = 10; 

  9.     
  10.         int *pnum = &num;
  11.         const int * cpnum = &num;
  12.         /*const int *指针能够采用int *指针直接初始化*/
  13.         const int *csize1 = pnum; 


  14.         /*可是int * 指针不能采用const int *制做初始化*/
  15.         //int *psize = &array_size;
  16.         /*const类型数据只能采用指向const的指针来指向*/
  17.         const int *csize = &array_size;

  18.         cout << "Before change..." << endl;
  19.         cout << "The num of num = " << num << endl;
  20.         cout << "*pnum = " << *pnum << " " 
  21.                 << "*cpnum = " << *cpnum << " " 
  22.                 << "csize1 = " << *csize1 << endl;

  23.         num = 30; 
  24.     
  25.         cout << "After changed..." << endl;
  26.         cout << "The num of num = " << num << endl;
  27.         cout << "*pnum = " << *pnum << " " 
  28.                 << "*cpnum = " << *cpnum << " " 
  29.                 << "csize1 = " << *csize1 << endl;

  30.         return 0;
  31. }
 
    从上面的结果咱们能够知道指向const的指针能够采用非const变量的地址进行初始化或者赋值操做,同时也能够采用指向非const指针直接初始化指向const的指针。同时指向const的指针不能赋值给指向非const的指针,会出现const int *不能转换到int *的问题。

     三、const与引用
    关于const和引用在一块儿状况下也存在一些相似于指针的状况,可是毕竟引用相比指针要简单,这时候状况也比较简单.可是我认为分析引用应该与分析指针是同样也存在相似的问题。可是引用就只有两种,非const的引用和const的引用。


  1. int num = 10;

  2. int &newname = num;
  3. const int &othername = num;
   引用主要是上面的两种,这两种的区别相对来讲比较大,并且加入了const限定符之后,引用的能力每每变的更加的强大。
   通常来讲对于const对象而言,通常只能采用const引用,这与前面的const对象只能采用指向const对象的缘由是同样的,若是对引用没有进行限定,可能会经过引用修改数据,这是不容许的。也就是说const引用与指向const对象的指针有必定的类似性,即不能经过这个引用或者指针来修改原来的数据。保证数据的const特性。也就是说非const引用不能引用const对象,若是不当心引用编译器会出现下面的错误:
  1. invalid initialization of reference of type ‘int&’ from expression of type ‘const int’
所以非const引用只能针对非const的同类型数据。这是须要注意的。好比string,和字符串字面值都不能直接引用。由于类型不相同,这是在C++函数定义中常常出现的问题,在后期的博文中再分析。 在引用中加入const的就是对于这个引用而言,不能经过本身来修改原始的数据,这与指向const的指针有很大的类似性,
 
    可是每每const引用的初始化并不必定要去对象是const的,甚至能够是不一样类型的对象,这种优越性是连指针(指针只能指向同一类型的数据,若是必定要指向须要强制类型转换)都没有的,也就是能够将不一样类型的非const或者const对象来初始化一个const引用。可是这个const限定符就限定了该引用指向的对象是不能经过该引用来修改的。若是尝试采用const的引用进行修改,编译器会出现以下的错误:
  1. error: assignment of read-only reference...
    综合上面的描述可知: 非const引用只能绑定到该引用同类型(string和字符串字面值(const char *)之间都不能够)的非const对象,而const引用则能够绑定到任意的一种对象上(非const、const、甚至不一样类型),这种差异在函数的参数中有较大的体现。
    经过下面的例子来讲明一下上面的分析:


  1. #include <iostream>
  2. #include <string>
  3. #include <vector>

  4. using namespace std;

  5. int main()
  6. {
  7.         int num = 20;
  8.         const int array_size = 10;

  9.         int &pnum = num;
  10.         const int &cpnum = num;
  11.         /*采用引用直接初始化const类型的引用*/
  12.         const int &csize1 = pnum;

  13.         /*const的变量不能采用非const的引用*/
  14.         //int &psize = array_size;
  15.         /*const类型数据只能采用指向const的指针来指向*/
  16.         const int &csize = array_size;

  17.         cout << "Before change..." << endl;
  18.         cout << "The num of num = " << num << endl;
  19.         cout << "pnum = " << pnum << " "
  20.                 << "cpnum = " << cpnum << " "
  21.                 << "csize1 = " << csize1 << endl;

  22.         num = 30;
  23.         cout << "After the first changed..." << endl;
  24.         cout << "The num of num = " << num << endl;
  25.         cout << "pnum = " << pnum << " " 
  26.                 << "cpnum = " << cpnum << " " 
  27.                 << "csize1 = " << csize1 << endl;
  28.    
  29.         /*经过引用修改变量的值*/
  30.         pnum = 40;
  31.         cout << "After the second changed..." << endl;
  32.         cout << "The num of num = " << num << endl;
  33.         cout << "pnum = " << pnum << " "
  34.                 << "cpnum = " << cpnum << " "
  35.                 << "csize1 = " << csize1 << endl;

  36.         /*不能采用const的引用修改对象,
  37.         *这与指向const的指针特性的类似处*/
  38.         /*
  39.         csize1 = 50;
  40.         cout << "After the second changed..." << endl;
  41.         cout << "The num of num = " << num << endl;
  42.         cout << "pnum = " << pnum << " " 
  43.                 << "cpnum = " << cpnum << " " 
  44.                 << "csize1 = " << csize1 << endl;
  45.         */

  46.         double dnum = 10.1;
  47.         /*非const的引用只能绑定相同类型的对象*/
  48.         //int &dname = dnum;

  49.         /******************************************
  50.         *const引用能够绑定不一样类型的对象,
  51.         *所以const引用就能更加方便的做为函数的形参
  52.         *******************************************/
  53.         const int &dothername = dnum;

  54.         return 0;
  55. }

上面的实验结果基本上符合分析的结论。
 
总结
    const的使得引用与指针的变化更加复杂,整体而言,const主要是保证了经过指针或者引用不修改原始的数据,可是至于原始的数据是否能够修改,这就须要参看数据的类型。
    在存在const的对象中,只能采用包含限定符const的引用或者指向const的指针来操做。
    const的引用比较强大,初始化的过程当中能够采用任意的对象,const对象,非const对象,甚至其余类型的数据。const引用支持隐式类型转换。而指向const的指针则不能,只能指向同一类型的数据,可是能够采用强制类型转换,初始化或者赋值过程当中对数据类型没有要求,能够是const对象的地址,也能够是非const对象的地址。
     const引用和指向const对象的指针都是本身觉得本身指向的对象是不能修改的,采用const的指针或者引用就能避免原始数据修改。
相关文章
相关标签/搜索