C++虽然采取了不少有效的措施(如设private保护)以增加数据的安全性,但是有些数据却往往是共享的,例如实参与形参,变量与其引用,数据与其指针等,人们可以在不同的场合通过不同的途径访问同一个数据对象。既要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以使用const,即把有关数据定义为常量。这篇来了解下常对象、常对象成员、对象的常指针、对象的常引用。
一、常对象
先定义一个Timer类,代码如下:
class Timer{public:int hour;int minute;int second;// this指针使用 -> 运算符调用成员Timer(){this->hour = 0;this->minute = 0;this->second = 0;}// *this指针通过点调用成员Timer(int hour, int minute, int second){(*this).hour = hour;(*this).minute = minute;(*this).second = second;}// 显示时间void show_time(){cout <<hour <<':' <<minute <<':' <<second <<endl;}// 修改时间void set_time(int hour, int minute, int second){this->hour = hour;this->minute = minute;this->second = second;}
};
在定义对象时指定对象为常对象,常对象中的数据成员为常变量且必须要有初值。例如:
Timer const t1(12, 30, 50); //t1常对象
定义常对象的一般形式:
类名 const 对象名[(实参列表)]
或者把const写在最左边:
const 类名 对象名[(实参列表)]
此时对象t1的所有数据成员的值都不能被修改,如果希望保证数据成员不被改变的对象,可以声明为常对象。如果直接修改或调用,系统编译时都会报错【[Error] passing 'const Timer' as 'this' argument discards qualifiers】,如下代码:
#include <iostream>
using namespace std;// Timer类省略int main(){// 声明常对象t1Timer const t1(15, 30, 30);// 修改常对象时间 -(错误,非法)t1.set_time(23, 59, 59);// 显示常对象的时间 -(错误,非法)t1.show_time();return 0;
}
所以对象一旦被声明为常对象,则不能调用该对象的非const型的成员函数(除由系统自动调用的隐式构造函数和析构函数)。而且成员函数添加const后,也不能修改数据成员的值;C++考虑实际需要,对此作了特殊处理,对要修改的数据成员声明为mutable(可变的)。示例代码如下:
#include <iostream>
using namespace std;class Timer{public:mutable int hour;mutable int minute;mutable int second;// this指针使用 -> 运算符调用成员Timer(){this->hour = 0;this->minute = 0;this->second = 0;}// *this指针通过点调用成员Timer(int hour, int minute, int second){(*this).hour = hour;(*this).minute = minute;(*this).second = second;}// 显示时间 - 常成员函数void show_time() const {cout <<hour <<':' <<minute <<':' <<second <<endl;}// 修改时间 - 常成员函数void set_time(int hour, int minute, int second) const{this->hour = hour;this->minute = minute;this->second = second;}
};int main(){Timer const t1(15, 30, 30);// 修改常对象时间t1.set_time(23, 59, 59);// 显示常对象的时间t1.show_time();return 0;
}
此时,再编译则运行正常了。这里需要注意两点:一是数据成员添加mutable,否则调用set_time()函数编译时系统会报错【[Error] assignment of member 'Timer::hour' in read-only object】;二是在show_time()和set_time()括号后添加const,定义为常成员函数。运行如下图:
二、常对象成员
在声明类时将成员声明为const,即声明常数据成员和常成员函数。
如果将数据成员定义为常数据成员,在前面添加const,则不能被赋值。如下代码:
class Timer{public:const int hour;const int minute;const int second;Timer(){// 不能采用构造函数对常数据成员赋初始值,此写法为非法的this->hour = 0;this->minute = 0;this->second = 0;}
};
在系统编译的时候会报错【[Error] uninitialized const member in 'const int'】,那我们如何对常数据成员进行初始化呢?这里可以通过“参数初始化表”对常数据成员进行初始化。代码如下:
#include <iostream>
using namespace std;class Timer{public:const int hour;const int minute;const int second;// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}// 显示时间 - 常成员函数void show_time() const {cout <<hour <<':' <<minute <<':' <<second <<endl;}
};int main(){Timer const t1(15, 30, 30);// 显示常对象的时间t1.show_time();return 0;
}
运行结果如下图:
常成员函数可以引用const定义的常数据成员,也可以引用非const定义的普通数据成员。代码如下:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}// 显示时间 - 常成员函数void show_time() const {cout <<hour <<':' <<minute <<':' <<second <<endl;}
};int main(){Timer const t1(15, 30, 30);// 显示常对象的时间t1.show_time();return 0;
}
对象中的数据成员与成员函数、常成员函数关系如下表:
数据成员 | 非const成员函数 | const成员函数 |
---|---|---|
非const的数据成员 | 可以引用,也可以改变值 | 可以引用,但不可以改变值 |
const数据成员 | 可以引用,但不能改变值 | 可以引用,但不可以改变值 |
const对象数据成员 | 不允许引用和改变值 | 可以引用,但不可以改变值 |
从表中可以看出,凡是包含const定义的都不能改变值,常成员函数可以引用非const定义的数据成员,但非const定义的成员函数是不能引用const定义的数据成员。
常成员函数不能调用另一个非const成员函数。
三、指向对象的常指针
将指向对象的指针变量声明为const型并初始化,这样指针值始终保持为其初值,不能改变。示例如下:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;Timer(){this->hour = 0;this->minute = 0;this->second = 0;}// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}
};int main(){Timer t1(15, 30, 30), t2;Timer * const pt1 = &t1; // const放在指针变量前面// ptl不能改变指向(错误)pt1 = &t2;return 0;
}
如上代码,当pt1定义为常指针,在初始化为&t1时,则不能再改变;所以pt1 = &t2时系统编译时会报错【[Error] assignment of read-only variable 'pt1'】,只读变量pt1在初始化后不能再次赋值。
定义指向对象的常指针的一般形式:
类名 * const 指针变量名 = 对象地址;
注意的是,虽然常指针在初始化后不能改变,但是指向的对象中数据成员是可改变的。代码如下:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;Timer(){this->hour = 0;this->minute = 0;this->second = 0;}// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}// 显示时间 void show_time() {cout <<hour <<':' <<minute <<':' <<second <<endl;}
};int main(){Timer t1(15, 30, 30), t2;Timer * const pt1 = &t1; // const放在指针变量前面// 通过对象t1修改hour值t1.hour = 20;// 使用常指针pt1显示时间pt1->show_time();return 0;
}
运行结果可以看出,hour值已被修改。如下图:
常指针始终指向一个对象,可这样可以防止误操作,增加安全性。
四、指向常变量的指针变量
定义指向常变量的指针变量的一般形式:
const 类型名 *指针变量名;
(1)如果一个变量已被声明为常变量,只能用指向常变量的指针变量指向它,而不能用一般的(指向非const型变量的)指针变量去指向它。示例如下:
#include <iostream>
using namespace std;int main(){const int age = 30;const int *pa = &age; // 正确,常指针pa指向常变量age// 指针变量pa2指向常变量(错误,非法的)int *pa2 = &age;return 0;
}
如上一般的指针变量pa2指向常变量&age,在系统编译时会报错【[Error] invalid conversion from 'const int*' to 'int*'】- 从'const int*'到'int*'的无效转换。
(2)指向常变量的指针变量除了可以指向常变量外,还可以指向未被声明为const的变量。此时不能通过此指针变量改变该变量的值。示例如下:
#include <iostream>
using namespace std;int main(){int age = 30;const int *pa = &age; // 正确,常指针pa可以指向一般(非const定义的)变量ageage = 50; // 一般变量可以直接修改值,虽然被赋给常指针变量。*pa = 35; //错误,不能通过常指针 *pa 改变变量age的值return 0;
}
由上可知,指向常变量的指针变量可以指向一个非const变量,可以通过指针变量访问该变量,但不能通过常指针变量改变该变量的值,否则系统编译时会报错【[Error] assignment of read-only location '* pa'】- 分配只读位置'* pa'。
(3)如果函数的形参是指向非const型变量的指针,实参只能用指向非const变量的指针,而不能用指向const变量的指针。这样,在执行函数的过程中可以改变形参指针变量所指向的变量的值。如果函数的形参是指向const型变量的指针,在执行函数过程中显然不能改变指针变量所指向的变量的值,因此允许实参是指向const变量的指针,或指向非const变量的指针。示例如下:
#include <iostream>
using namespace std;int main(){const int nums[] = {3, 8, 2, 15, 9}; // 定义const型数组名void sortArray(int *arr); // 声明sortArray()形参指向非const型变量的指针// 传入实参const定义变量的地址 - (错误,非法)sortArray(nums);return 0;
}
// 排序数组
void sortArray(int *arr){// 略...
}
由上可知,非const型形参指针变量无法指向const定义的常变量,否则系统编译时会报错【[Error] invalid conversion from 'const int*' to 'int*' 】 - 从'const int*'到’int *'的转换无效,这也符合“(1)”中所阐述的规则。
用指针变量作形参时形参和实参的对应关系
形参 | 实参 | 是否合法 | 改变指针所指向的变量的值 |
---|---|---|---|
指向非const型变量的指针 | 非const变量的地址 | 合法 | 可以 |
指向非const型变量的指针 | const变量的地址 | 非法 | / |
指向const型变量的指针 | const变量的地址 | 合法 | 不可以 |
指向const型变量的指针 | 非const变量的地址 | 合法 | 不可以 |
从表中可以看出,指向常变量的指针变量可以指向const和非const型的变量,而指向非const型变量的指针变量只能指向非const的变量。
五、指向常对象的指针变量
指向常对象的指针变量的概念和指向常变量的指针变量与此类似的,只要将“变量”换成“对象”即可。
(1)如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的指针变量去指向它。示例代码如下:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}
};int main(){// 常变量与常变量的指针变量const int age = 30;const int *pa = &age; // 正确,常指针pa指向常变量age// 常对象与常对象的变量指针Timer const t1(15, 30, 30); //定义常对象t1const Timer *p = &t1; // 定义常指针指向常对象t1return 0;
}
(2)如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象;则其指向的对象不能通过指针来改变的,否则会系统编译时会报错【[Error] assignment of member 'Timer::hour' in read-only object】- 在只读对象中分配成员'Timer::hour'。
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}
};int main(){// 一般变量与常变量的指针变量int age = 30;const int *pa = &age; // 正确,常指针pa指向常变量age// 一般对象与常变量的指针变量Timer t1(15, 30, 30); //定义对象t1const Timer *p = &t1; // 定义常指针指向对象t1// 不能通过指针变量修改对象中数据成员(错误,非法)(*p).hour = 20;// 但是通过对象t1修改是可以的t1.hour = 18;return 0;
}
(3)指向常对象的指针常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。
错误写法:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}
};int main(){void show_time(const Timer *p); //声明函数show_timeTimer t1(15, 30, 30); //定义对象t1// 调用 show_time()显示Timer对象中时间show_time(&t1);return 0;
}// 定义函数show_time
void show_time(const Timer *p){p->hour = 18; //错误写法,常对象的指针不能用来修改对象中数据成员cout <<p->hour <<':' <<p->minute <<':' <<p->second;
}
正确写法:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}
};int main(){void show_time(const Timer *p); //声明函数show_timeTimer t1(15, 30, 30); //定义对象t1// 调用 show_time()显示Timer对象中时间show_time(&t1);return 0;
}// 定义函数show_time
void show_time(const Timer *p){cout <<p->hour <<':' <<p->minute <<':' <<p->second;
}
(4)如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值,但是指针变量本身的值是可以改变的。示例代码如下:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;Timer(){this->hour = 0;this->minute = 0;this->second = 0;}// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}
};int main(){Timer t1(15, 30, 30), t2; //定义对象t1const Timer *p = &t1; //定义指向常对象的指针变量p,并指向对象t1p = &t2; //指针变量 更新指向对象t2(合法)return 0;
}
同时,更改指针指向后,也不能修改对象t2中的数据成员的值。
这里需要注意的是指向对象的常指针变量在形式上和作用上的区别:
Timer * const p; // 指向对象的常指针变量const Timer *p; // 指向常对象的指针变量
1)在“三、指向对象的常指针”中定义指向对象的常指针的一般形式为:
类名 * const 指针变量名 = 对象地址;
2)本节“指向常对象的指针变量”中定义常变量的指针变量一般形式为:
const 类型名 * 指针变量名;
二者不仅在定义的形式上有区域,从本节(4)中可看出,在初始化时:常指针变量一旦初始化不能更改指向;而常对象的指针变量是可以重新指向新的地址。
这里可以对于很多人来说是比较难理解的,一时不容易接受和消化。在C++中,有两种情况可以定义常量:一种是常量指针,另一种是指向常量的指针。
1)常量指针(const pointer)是指向常量的指针,意味你不能通过这个指针来修改它所指向的值,但是你可以改变指针本身的指向。
2)指向常量的指针(pointer to const)是指针本身是常量,不能改变它的指向,但是你可以通过它修改所指向的值。
六、对象的常引用
一个变量的引用就是变量的别名,实质上,变量名和引用名都是指向同一段内存单元。如果形参为变量的引用名,实参为变量名,则在调用函数进行虚实结合时,并不是为形参另外开辟一个存储空间,而是把实参变量的地址传给形参(引用名),这样引用名也指向实参变量。示例如下:
#include <iostream>
using namespace std;class Timer{public:int hour;int minute;int second;Timer(){this->hour = 0;this->minute = 0;this->second = 0;}// 有参构造函数Timer(int h, int m, int s): hour(h), minute(m), second(s){}// 显示时间void show_time(){cout <<hour <<':' <<minute <<':' <<second <<endl;}
};
// 修改小时hour的值
void change_hour(Timer &t){t.hour = 18;
}int main(){Timer t1(15, 30, 30); //定义对象t1change_hour(t1); //调用change_hour()函数t1.show_time(); //显示对象t1时间return 0;
}
运行结果可以看出,形参的引用修改了对象t1中的hour初始值。如下图:
如果不希望在change_hour()函数中修改对象t1中数据成员的值,可以在形参声明变量的引用名前添加const,代码如下:
void change_hour(const Timer &t){t.hour = 18; // 此时则不能通过别名t 来修改hour的值(非法)
}
否则系统编译的时候会报错【[Error] assignment of member 'Timer::hour' in read-only object】- 在只读对象中分配成员'Timer::hour'
七、小结
在C++面向对象程序设计中,经常用常指针和常引用作函数参数。这样既能保证数据安全,使数据不能被随意修改,在调用函数时又不必建立实参的拷贝。用常指针和常引用作函数参数,可以提高程序运行效率。
本篇通过const型数据和引用,复用它们可以对共用的数据进行保护。对于对象有关的const型数据种类较多,形式又有些相似,容易混淆,所以这里简单整理归纳一下。
形式 | 含义 |
---|---|
Timer const t1; 或者const Timer t1; | t1是常对象,其值在任何情况下都不能改变 |
void Timer::fun() const | fun是Timer类中的常成员函数,可以引用,但不能修改本类的数据成员 |
Timer * const p; | p是指向Timer对象的常指针,p的值(即p的指向)不能改变 |
const Timer *p; | p是指向Timer类常对象的指针,其指向的类对象的值不能通过指针来改变 |
Timer &t1 = t; | t1是Timer类对象t的引用,t和t1指向同一段内存空间 |
此篇可能较为难理解,相信大家后期在实际应用中会逐渐熟悉并能熟练他用它们。