常变量,常指针,指向常变量的指针以及常引用。

1 const的作用

const关键字一般表示为常量。主要作用在于对共用数据的保护。将某个变量或者对象设为常量后,则无法使用函数或者语句来改变这个常变量或者常对象,以此来起到保护数据的作用

2 常变量和常对象

2.1 常变量

常变量的定义方法如下:

const 变量类型 变量名;

以下这种定义方法等效:

变量类型 const 变量名;

2.2 常对象

常对象的定义方法如下:

const 类名 对象名 (实参列表);

以下这种定义方法等效:

类名 const 对象名 (实参列表);

常对象需要注意的是:

  • 常对象其数据成员都不可以被改变,有特例,采用关键字mutable;
  • 常对象不能调用其非const型的成员函数,不论这些非const型的成员函数是否改变对象的数据成员,只能用const型的成员函数。这是为了安全起见的,强制性规定这样,因为谁也不能保证那些非const函数是否真的不改变常对象的数据成员;
  • 常成员函数可以访问常对象中的数据,但是不允许修改常对象中的数据成员;
  • 常对象并不意味常对象里的数据成员都是常变量,两种是不同的概念;
  • 常对象里面可以有非const型的成员函数。但是常对象不能使用她们;
  • 常成员函数不能调用一个非const型的成员函数。

2.3 常数据成员与常成员函数

常数据成员与一般的常变量的用法无异,即该数据成员的值是不能改变的。其声明需要在声明类的时候进行,因为限定的是类中的数据:

const 数据类型 变量名词;

或者:

数据类型 const 变量名称;

常成员函数主要作用是为了配合常变量使用。因为常变量它只能使用常成员函数来访问对象的数据成员。

声明同样是需要在声明类的时候进行,不难理解,与此同时,在定义函数的时候,也需要使用const关键字。因此它需要在两个地方使用const关键字:

声明:

函数返回值类型 函数名(形参列表) const;

例如:

void get_diff (int a, int b) const;

定义:

函数返回值类型 函数名(形参列表) const {}

例如:

void get_diff(int a, int b){ 
    return (a-b);
}

需要注意的:

  • 对于常数据成员,只能通过构造函数的参数初始化表对常数据成员进行初始化;

例如:

Time::Time(int h,int m, int s):hour(h),minute(m),second(s){}

而如下的做法是错误的:

Time::Time(int h){
hour=h;
}

因为常数据成员及常变量是不能被赋值的, 只能在初始化的时候给值。

  • const是函数类型的一部分,声明和定义都不能少,但是在调用的时候不能加上const关键字,按照普通函数一样调用即可。

3 常指针和指向常变量的指针

这一块的内容非常容易被混淆,从核心上来理解:

常指针:是一个指针变量,并且这个东西是不能改变的。不能改变的是指针的地址,而不是地址指向的变量。

打个比方:你有一套房子,这个房子的地理位置是永恒不变的,愚公移山这种特例不管,但是这个房子里住的人可不是永恒不变的,所有常指针,就类似于这个房子。

指向常变量的指针:首先这不是是一个普通的指针,他是指向常变量的指针,虽然它的值当然是可以改变的,也就是说它指向谁都是可以的,不是固定的。但是不能用它来改变它所指向变量的值。

打个比方:这是一个可移动的房子,所以它的地址当然不是固定的,同时它既可以租给那些固执的人(const型的),也可以租给那些善变的(非const型的)。但是你却无法通过这个移动的房子去改变租客,尽管租客自己能改变自己。比喻有点牵强,但是能说明指向常变量的指针的特性。

既然说到了常指针,又说到了指向常变量的指针,那当然存在一个指向常变量的常指针了。
这个比方非常好打,就是你有一套房子,只能给固定的人住,那么这个房子就是指向常变量(固定的人)的常指针(房子的地址不会改变)。

说了理解部分的,下面是一些规矩。

3.1 常指针

声明:声明和初始化一起搞定。因为常指针也算是一个常变量,不能赋值,只能初始化。

变量类型 * const 指针变量名 = 指向的变量的地址

例如:

int a = 10 ;
int * const p = &a; 

除了用在变量上,当然还可以用在类上。也就是指向类的常指针。

声明和指向变量的常指针没什么两样:

类名 * const 指针变量名 = 对象地址

例如:

Time t1(10,30,55);
Time * const pt = &t1;

3.2 指向常变量的指针

声明:

const 类型名 * 指针名;

或者指向常对象的指针:

const 类名 * 指针名

3.3 常变量与指向常变量的指针

首先要区别常指针和指向常变量的指针。这在开始已经介绍过,简单的来说其实可以归结为两句话:

  • 指向常变量的指针——不能通过指针改变变量的值
  • 指向变量的常指针——不能改变指针的值

其次,还有几点需要注意:

  • 常变量必须要用指向常变量的指针变量来指向它
  • 指向常变量的指针既可以指向常变量,也可以指向普通变量。只是不能通过指针来改变所指向变量的值

以下几个例子可以说明一定的问题:

int a=10;
int * const p1=&a;//指向变量a的常指针,必须在声明时赋值
int b=20;
p1=&b;//这是错误的,p是常指针,不能改变


const int c=100;
int *p2=&c;//错误,常变量不能用普通指针来指向它
int * const p3=&c;//错误,常变量也不能用常指针来指向它
const int *p4=&c;//正确,常变量必须要用指向常变量的指针来指向它
int d=200;
p4=&d;//正确,指向常变量的指针也可以只想非const变量
*p=300;//错误,不能通过指向常变量的指针来改变所指向的变量
d=300;//正确

4 数据及对象的常引用以及常变量的引用

const引用的声明如下:

const 类型 &引用名=被应用的变量;  //引用在声明时必须要初始化

首先回到引用的本质,引用即别名。也就是说引用是不能更改的,即一个别名不能用在两个人身上,也不能把一个别人用过的别名用到其他人身上。这就是说,引用一旦初始化后,和被引用的变量终生绑定。从这个特质来看,有点像常指针。指向那个对象,就得一直指向它。

值得注意的是:一个变量可以取多个别名啊~~~只是一个别名只能用在一个人身上。

而const引用则类似于指向常变量的常指针(注意这里不是指向常变量的指针,后者可以先后不止指向一个值)。也就是const引用既可以引用普通的变量,也能引用const变量。但是,这一点非常重要,和指向常变量的指针一样,不能通过const引用来改变其引用的变量的值。

来看例子:

int a=10;
const int b=20;
int & c1=a;//没毛病
const int & c2=a; //没毛病,常引用自然可以引用非const变量
int & c3=b;//错误,常量必须要用常引用来引用
const int &c4=b;//没毛病,常量用常引用来引用
c2=30;//错误,虽然c2指向的a不是常变量,但是不能通过常引用来改变它的值
a=30;//没毛病,给他取个常引用的别名并不能让他成为常变量

结论:常变量必须要用常引用,常引用可以引用常变量也可以引用非常变量,但是不能通过常引用来改变所引用的变量的值。

5 在函数中的应用

5.1 指针

  • 如果函数的形参是指向非const型变量的指针,那么实参不能传递一个const变量的地址吗,也不能使用指向const变量的指针(即使其指向的对象是非const的)。只能用指向非const变量的指针,或者是非const变量的地址,当然,也可以是指向非const变量的常指针。这是因为传递过去的地址可能会在函数调用中改变该地址所指向变量的值,采用指向const变量的指针或者传递一个const变量的地址这都无法改变其所指向变量的值,这样会发生冲突,而在编译时编译器往往不能知道函数是否会改变其所指变量的值,因此从语法上就规避掉了这个危险。
  • 如果函数的形参是指向const型变量的指针,那么实参可以是指向const变量的值,也同时是指向非const变量的指针,可以是const变量的地址,也可以是非const变量的地址。这个很好理解,由于形参是指向const型变量的指针,那么在函数调用的过程中,永远不会改变其所指对象的值。因此实参用const类型的地址当然是没问题的,用非const类型的地址更没问题。非const类型可没说一定要改,不改当然也没问题。

5.2 引用

  • 如果形参是普通的引用,那么实参必须传递一个普通的变量地址或者普通的引用,不能传递const引用,这点和指针的情况一样

  • 如果形参是const引用,那么实参既可以是普通的变量地址或普通的引用,也可以是const引用

6 总结

const这个关键字,一般用来保护数据。体现在两个方面:

第一个方面,用于声明const常量或者常对象,由于不可改变的属性,可以用来保护数据本身不被更改;

第二个方面,在于函数调用这一块。C++中,尤其是面向对象的时候,传址是更简洁有效的,这样不会需要临时的存储空间。但是传址会存在一个问题,那就是遇到不想改变的值,会有风险。于是就有了指向常变量(常对象)的指针,和常引用。

如果需要保证数据在调用函数的过程中不被改变,需要将函数的形参声明为const指针或者引用,这样传址过来可以保证在函数调用的时候不会改变。如果需要保证数据在整个程序都不会改变,则需要将函数的形参声明为const,并且实参也应该声明为const,这样可以万无一失了。