《C++面向对象程序设计(第2版)》“3.6共用数据的保护”P90-P98
分析一个普通例子:
1 #include<iostream>
2 using namespace std;
3
4 class Time
5 {
6 public:
7 Time(int h, int m, ints):hour(h),minute(m),sec(s){};
8 void show();
9 void display();
10 private:
11 int hour;
12 int minute;
13 int sec;
14 };
15
16 void Time::show()
17 {
18 hour = 10;
19 cout<<"int show() functionhour="<<hour<<endl;
20 }
21
22 void Time::display()
23 {
24 cout<<"int display() functionhour="<<hour<<endl;
25
26 }
27
28 int main()
29 {
30 const Time t1(13,34,45);
31 t1.show();
32 t1.display();
33
34 return 0;
35 }
上面的例子中Time::show()改变Time类中hour成员变量的值;Time::display()没有修改Time类中的成员变量。
1 常对象
(1)如果一个对象被定义为常对象,则通过该对象只能调用它的常成员函数,而不能调用该对象的普通成员函数(除了由系统自动调用的隐式的构造函数与析构函数)。如:
上面代码中Line 30 修改为const Time t1(13,34,45);
则 Line 31 t1.show() //企图调用常对象t1中普通成员函数,非法
Line 32 t1.display()//企图调用常对象t1中普通成员函数,非法
这是为了防止普通成员函数会修改常对象中的数据成员,那么:Time::display()并没有修改常对象中的数据成员,为什么不允许呢?
分析:因为不能仅靠编程者这种细致的检查保证程序不出错,编译系统充分考虑到可能出现的情况,对不安全因素进行排斥。那么:为什么编译系统不专门检查函数的代码,看它是否修改常对象中的数据成员的值呢?实际上,函数的定义与函数的声明可能不在同一个源文件中。而编译器则是以一个源文件为单位,无法测出两个源文件之间是否矛盾。如果有错,只有在连接或运行阶段才能发现。这样就给调试程序带来不便。
现在,编译系统只检查函数的声明,只要发现调用了常对象的成员函数,而且该函数未被声明为const就会报错。
注意:1)函数的声明与定义中都要加const,函数调用不用加const
2)const添加位置:void display() const;
(2)常成员函数可以访问常对象中的数据成员,但不允许修改常对象中的数据成员,(除非常对象中的数据成员声明为mutable)
(3)常对象必须有初值。
2 常对象成员
2.1 常数据成员
常数据成员的值不能改变,只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其它函数都不能对常数据成员赋值。
2.2 常成员函数
声明常成员函数的一般格式为:
类型名 函数名(参数表) const
const为函数类型的一部分,在声明函数和定义函数时都要有关键字const,在调用时不必加const。
const数据成员可以被const成员函数引用,也可被非const的成员函数引用。
说明:“2.2中讲到的const数据成员可以被非const的成员函数引用”与 1中提到“const对象不能被非const的普通成员函数引用”是否矛盾??《C++面向程序设计(第2版)》P93提到“不要误认为常对象中的成员函数都是常成员函数。常对象只能保证其数据成员是常数据成员,其值不被改变”,这样看来常对象是否等于把对象中的数据成员都定义为const??
分析:
虽然从理解上说,定义常对象时候由此产生的常成员变量等同于直接在类体中定义常成员变量,但是在编译器看来,这两种方式产生的常变量不同。
(1)常对象产生的常成员变量,即便函数内定义了修改它的函数,只要该函数没有被调用,编译就能通过。如下图:
(2)直接在类体中定义的常变量,只要定义了修改常变量的函数体,即便该函数体没有被调用,编译也无法通过。如下:
由于以上原因造成了“常对象中的数据成员不能被非const成员函数访问”与“const数据成员可以被非const成员函数访问”的区别。
2.3 指向对象的常指针
将指针变量声明为const型,这样指针变量始终保持初值,不能改变,即其所指向不变。
Timet1(10,12,15),t2;
Time *const ptr1; //const位置在指针变量名前面,指定ptr1是常指针变量
ptr1 =&t1; //ptr1指向对象t1,此后不能再改变指向
ptr1 =&t2; //错误,ptr1不能改变指向
注意:指向对象的常指针变量的值不能改变,即始终指向同一个对象,但可以改变其所指向对象(如t1)的值。
什么时候需要用指向对象的常指针呢?如果想将一个指针变量固定地址与一个对象相联系(即该指针变量始终指向一个对象),可以将它指定为const型指针变量。这样可以防止误操作,增加安全性。
往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象。
2.4 指向常对象的指针变量
常变量的指针:
(1)如果一个变量已被声明为常变量,只能用指向常变量的指针变量指向它,而不能用一般的(指向非const型变量的)指针去指向它。
const char c[]=”boy”;//定义const型的char数据
const char *p1;//定义p1为指向const型的char变量的指针变量
p1=c; //合法,p1指向常变量(char数组的首元素)
char *p2 = c; //不合法,p2不是指向常变量的指针变量
(2)指向常变量的指针除了可以指向常变量外,还可以指向未被声明为const的变量。此时不能通过此指针变量改变该变量的值。
char c1=’a’;//定义字符变量c1,它并未声明为const
const char *p;//定义了一个指向常变量的指针变量p
p=&c1;//使p指向字符变量c1
*p=’b’;//非法,不能通过p改变变量c1的值;
c1=’b’//合法,没有通过p访问c1,c1不是常变量
注意:定义指向常变量的指针变量p并使它指向c1,并不意味着把c1也声明为常变量,而只是在用指针变量访问c1期间,c1具有常变量的特征,其值不能改变,在其它情况下,c1仍然是一个普通的变量,其值是可以改变的。
如果希望在任何情况下都不能改变c1的值,则应把它定义为const型,如:
const char c1 = ‘a’;
(3)如果函数的形参是指向普通(非const)变量的指针变量,实参只能是指向普通(非const)变量的指针,而不能指向const变量的指针。
以上介绍的是指向常变量的指针变量,指向常对象的指针变量的概念与使用与此类似,只要把“变量”换成“对象”即可。
3 指向对象的常指针变量与指向常对象的指针变量区别
Time * const p; //指向对象的常指针变量 p的值(p的指向)不能修改
const Time *p; //指向常对象的指针变量,p指向的类对象的值不能通过p来修改
3.1 指向对象的常指针变量
将指针变量声明为const型,这样指针变量始终保持初值,不能改变,即其所指向不变。
Timet1(10,12,15),t2;
Time *const ptr1; //const位置在指针变量名前面,指定ptr1是常指针变量
ptr1 =&t1; //ptr1指向对象t1,此后不能再改变指向
ptr1 =&t2; //错误,ptr1不能改变指向
注意:指向对象的常指针变量的值不能改变,即始终指向同一个对象,但可以改变其所指向对象(如t1)的值。
什么时候需要用指向对象的常指针呢?如果想将一个指针变量固定地址与一个对象相联系(即该指针变量始终指向一个对象),可以将它指定为const型指针变量。这样可以防止误操作,增加安全性。
往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象。
3.2 指向常对象的指针变量
指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在执行过程中不被修改。
……
int main()
{
void fun(const Time *);//函数声明,形参是指向常对象的指针变量
Time t1(10,13,56);
fun(&t1); //实参是对象t1的地址
return 0;
}
void fun(const Time *p) //定义fun函数
{
p->hour = 18; //错误
cout<<p->hour<<endl;
}