​

  一、const介绍  

  在C语言中我们想定义常量一般是用#define 宏来实现的,但是C++里面用const修饰常变量,从而便于管理代码。

    常变量: const 类型说明符 变量名

    常引用: const 类型说明符 &引用名

    常对象: 类名 const 对象名

    常成员函数: 类名::fun(形参) const

    常数组: 类型说明符 const 数组名[大小]

    常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名

  首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:



const int a=5; 与 int const a=5; 等同

类名 const 对象名 与 const 类名 对象名 等同


  二、const用法

  1. 如上述,修饰常变量,常引用等等。,在同时涉及指针和const时,会有顶层、底层const的讨论
  2. 修饰函数参数
    ​viod foo(const int *a ,const int &b );​​这里a是指针变量指向int类型的常变量,a是变量,可以更改指向,a所指向的对象是常量。b是int 类型的引用,与常引用一个道理,在函数体内无法进行修改
  3. 修饰函数返回值,可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。

  三、指针介绍

  指针(pointer)实现了对其他对象的间接访问。指针本身也是一个对象,可以进行赋值和拷贝。



int x=10;
int *p = &x;
//这里p就是指向变量x的一个指针,存放着变量x实际存放的地址


  四、常量指针与指针常量

  首先需要区分常量指针与指针常量,方法是:从右往左,遇到数据类型如"int"读作“整型”,“const”读作“常量”,“ * ”读作指向,例如下面:



const int p;        //整型常量
const int *p; //指向整型常量的指针(从右往左读)
int const *p; //指向常量整型的指针(同上)
int *const p; //常量指针,指向整型
const int *const p; //常量指针,指向整型常量
int const *const p; //常量指针,指向常量整型(同上)


  因此从读法上区分指针常量与常量指针就变得十分简单了。对于他们的理解可以是:

  对于指针常量,被指向的对象是常量;对于常量指针,是指针本身是常量。



const double pi = 3.14;        //pi是常量,不能改变
double *ptr = π // 错误,ptr仅仅是普通指针
const double *cptr = π //cptr指向了一个“对象”,这个对象是常量
*cptr = 42; //不能对cptr赋值,因为它已经是一个常量了。
double dval = 3.14; //dval可以改变
cptr = &dval; //可以通过改变dval来改变cptr


  所以和常量引用一样,指向常量的指针没有规定所指向的对象必须是常量,它仅仅要求不能借助这个指针改变对象的值,而没有规定那个对象的值不能通过其他方法改变。

  



#include <iostream>
using namespace std;

int main()
{
/*
int t = 3;
int &r = t;
int newt = 4;
r = newt;
cout << newt << '\n' << r << endl;
*/
//上面可以正常执行为4和4

/*
int t =3;
const int &r = t;
int newt = 4;
r = newt;
cout << newt << '\n' << r << endl;//错误
*/

int i = 3;
const int *p = &i;
int newi = 4;
p = &newi;
cout << newi << endl;
cout << *p << endl;
//可以正常执行为4和4

return 0;
}


  对于常量指针(const pointer),必须进行初始化,且一旦初始化完成就无法修改。*在const之前说明指针是一个常量,例如:

  



#include <iostream>
using namespace std;

int main()
{
int a = 2;
int c = 3;

const int * pi = &a;
cout << pi << '\t' << *pi <<endl;
pi = &c;
cout << pi << '\t' << *pi <<endl;
cout << &c << '\t' << c <<endl;

int * const ptr = &a;
cout << ptr << '\t' << *ptr <<endl;
*ptr = c;
cout << ptr << '\t' << *ptr << endl;
cout << &a << '\t' << a << endl;

/*执行结果如:
0x6dfee4 2
0x6dfee0 3
0x6dfee0 3
0x6dfee4 2
0x6dfee4 3
0x6dfee4 3
*/
return 0;
}


  可见作为常量指针,ptr被牢牢扣死在了0x6dfee4这个位置上,因为这个位置就是一个“常量”。

  最后总结:看const与 * 的相对位置(从右往左念)

  • 常量指针:type * const ptr
  • 指针常量:const *type ptr

  五、顶层与底层const 

    const修饰符用于修饰常量,在程序运行过程中一旦初始化就不会再改变

    如果我们要描述一个指针是常量或者指针指向的对象是常量时,会出现一些歧义。

    因为指针通常来说也是一个变量,但是我们也可以用const修饰符来获得常量指针。

    所以为了区分这两种情况,引入顶层const和底层const两个名词。

    顶层const :表示指针本身是常量

    底层const: 表示指针指向的对象是常量



const int i=1;
const int *p = &i; //底层const,p的值可以改变,但i的值不能改变
int const *p = &i; //底层const,同上,无差别
const int j = 0;
p = &j; //正确,j是常量



int i =10;
int *const p = &i; //顶层const,p是常量指针,初始化之后不能修改。
int j=0;
p = &j; //error: expression must be a modifiable lvalue



const int i = 0;
const int *const p =&i; //常量指针p指向常量对象i


   



int i = 0;
int *const p1 = &i; //不能改变p1,顶层
const int ci = 42; //不能改变ci,顶层
const int *p2 = &ci;//可改变p2,底层(改变方法参见上面指针常量的例子)
const int *const p3 = p2 //右const为顶层,左const为底层
const int &r = ci //用于声明的const都是底层
i = ci //顶层ci,拷贝没毛病
p2 = p3 // p2,p3指向对象类型相同,p3顶层部分不受影响


  当执行拷贝操作的时候,底层与顶层的区分就出来了。



int num_c = 3;
const int *p_c = &num_c; //p_c为底层const的指针
//int *p_d = p_c; //错误,不能将底层const指针赋值给非底层const指针
const int *p_d = p_c; //正确,可以将底层const指针复制给底层const指针


  当执行对象拷贝时,两者需要有相同的底层const资格,或者两个对象数据类型能转换。一般来讲,非常量可转换为常量,反之不可。