共享数据的保护——常对象
虽然数据隐藏保证了数据的安全性,但各种形式的数据共享却又不同程度地破坏了数据的安全。因此,对于既需要共享、又需要防止被修改的数据应该声明为常量。因为常量在程序运行期间是不可改变的,所以可以有效地保护数据。教材1.2中已经介绍了简单数据类型的常量。其实,关键字const也可以修饰对象,它相当于一种自定义数据类型的常量,被称之为常对象。本节将介绍常引用、常对象和常成员。
2.6.1 常引用
如果在声明引用时用const修饰,那么该引用就是一个常引用。常引用意味着所引用的对象不能被更改。如果用常引用做形参,那么可以保证实参不会意外地被修改。声明常引用的语法形式如下:
const 数据类型 &引用名
例2.21 常引用举例
// 文件路径名: s2_21\smain2_21.cpp
#include<iostream>
using namespace std;
void Print( const int& n );
int main( )
{
int i=10;
Print(i);
}
void Print( const int& n )
{
cout<<n<<endl;
}
程序运行结果:
10
在上例中,常引用n做形参,在函数print中不能修改n所引用的数据,因此函数的实参i不会被更改。
2.6.2 常对象
如果希望一个对象自创建时起就不再改变它的状态,即它的所有数据成员的值都不再改变,那么可以用const关键字来修饰这个对象,将它声明为常对象。这样,系统会确保它的数据成员值在对象的整个生命周期内都不会被改变。声明一个常对象其实就是在创建对象的语句中添加const关键字,相关的语法形式如下:
类名 const 对象名 ( 实参表 ) ;
或者
const 类名 对象名 ( 实参表 )
请注意,常对象在声明的同时必须被初始化,并从此不能改写对象的数据成员。
例2.22 分析下列错误的地方,说明理由。
// 文件路径名: s2_22\smain2_22.cpp
#include<iostream>
using namespace std;
class Point
{
int x, y;
public:
Point(int a, int b)
{
x=a;
y=b;
}
void Move( int a, int b)
{
x+=a;
y+=b;
}
void Print( )
{
cout<<"x="<<x<<"y="<<y<<endl;
}
};
int main( )
{
const Point point1( 10,10); // 声明并初始化常量对象
// point1.Move(2,2); // 常对象point1的数据不能被更改,所以错误
// point1.Print( ); // Print函数的this指针不能指向常对象
return 0;
}
在上例中,常对象point1去调用普通成员函数Move,而此函数在执行的过程中可能要改写对象的数据成员,因此与常对象的概念相矛盾;另外,成员函数Print里面输出的x和y值是有this指针指明所属对象的,而this指针不能指向常对象,故而出错。常对象常常被用在对象的引用上,比如下面的例子。
例2.23 修改例2.17“计算两点间的距离并输出”,在友元函数的参数中使用常对象的引用。
// 文件路径名: s2_23\smain2_23.cpp
#include <iostream>
#include <cmath>
using namespace std;
class CPoint
{
public:
CPoint( double xx=0, double yy=0 )
{
x=xx;
y=yy;
}
double GetX( )
{
return x;
}
double GetY( )
{
return y;
}
friend double fDist( const CPoint &p1, const CPoint &p2 );
private:
double x,y;
};
double fDist( const CPoint &p1, const CPoint &p2 )
{
double x=double(p1.x-p2.x); // 由于是友元关系,p1、p2可以直接访问所指对象的私有成员x和y
double y=double(p1.y-p2.y);
return sqrt(x*x+y*y);
}
int main( )
{
CPoint a (1,1);
CPoint b (4,5);
cout<<"The distance is:";
cout<< fDist( a, b )<<endl;
return 0;
}
程序运行结果为:
The distance is: 5
在上例中,main函数创建了2个Cpoint类对象a和b,它们都是普通对象,但当调用友元函数fDist时,由于参数声明的是引用,所以把a和b的引用传给p1和p2;又因为参数被声明为const,所以传递到函数中的a和b的引用在函数执行的过程中不能被改变,由此也保证了实参a和b自身数据成员的安全。
常成员
1. 常成员函数
例2.21告诉我们,常对象是不能调用普通成员函数的。因为普通成员函数可能会修改对象的状态,即使某个成员函数在执行的过程中没有改变任何对象的状态,但系统还是会认为由常对象去调用它是不安全的。为了确保常对象调用函数的安全性,C++提出来一种特殊的成员函数——常成员函数。
所谓常成员函数,就是用const关键字说明的函数,也可以称为访问函数。其格式如下:
返回类型 函数名(参数表) const
常成员函数只能访问对象的数据成员,而不能修改它们的值,也不能在函数体中调用非const修饰的成员函数。****C++规定,常对象只能调用类的常成员函数以及类的静态成员函数。
下面让我们为例2.21添加一个常成员函数,以此来解决常对象point1调用函数的安全性。
例2.24 常成员函数举例。
// 文件路径名: s2_24\smain2_24.cpp
#include<iostream>
using namespace std;
class Point
{
int x, y;
public:
Point(int a, int b)
{
x=a;
y=b;
}
void Move( int a, int b)
{
x+=a;
y+=b;
}
void Print( ) const
{
cout<<"x="<<x<<", y="<<y<<endl;
}
};
int main( )
{
const Point point1( 10,10); // 声明并初始化常量对象point1
point1.Print( ); // print函数现在变成常成员函数,所以可以被point1调用
return 0;
}
程序运行结果:
x=10, y=10
关于常成员函数,还需注意的是:const是函数类型的一个组成部分,因此在常成员函数实现部分也要带有const关键字。
常成员函数照样可以用于重载,例2.25就是一个例子。
例2.25 成常成员函数在函数重载中举例。
// 文件路径名: s2_25\smain2_25.cpp
#include<iostream>
using namespace std;
class A
{
public:
int GetValue( ) const
{
return w*h;
}
int GetValue( )
{
return w+h;
}
A(int x,int y)
{
w=x,h=y;
}
A( ){ };
private:
int w, h;
};
int main( )
{
A const a(3,4);
A c(2,6);
cout<<a.GetValue( )<<endl<<c.GetValue( )<<" cctwlTest"; // 输出和
return 0;
}
程序执行结果:
12
8 cctwlTest
在上例中,定义了2个成员函数GetValue,它们的函数名、参数表以及返回类型都相同,唯一不同的是,其中一个用const修饰,是常成员函数。那么在调用同名函数GetValue时,常对象a调用的是常成员函数GetValue,普通对象c调用的是普通成员函数GetValue。从这个例子可以看出,当两个成员函数的函数原型相同,只区别在有无const修饰的时候,系统可以视之为函数重载,并且根据调用的对象来匹配相应的函数:常对象调用常成员函数,普通对象调用普通函数
2. 常数据成员
就像一般数据一样,类的数据成员也可以是常量,使用const来修饰的数据成员被称为常数据成员。如果在一个类中声明了常数据成员,那么任何函数都不能对该数据成员赋值。构造函数对该数据成员的初始化也比较特殊,只能通过初始化列表来完成。
例2.26 常数据成员举例。
// 文件路径名: s2_26\smain2_26.cpp
#include<iostream>
using namespace std;
class A
{
public:
A( int i ) : a(i){ }
void Print( )const
{
cout<<a<<":"<<b<<":"<<endl;
}
private:
const int a;
static const int b;
};
const int A::b=10;
int main( )
{
A a1(100), a2(0);
a1.Print( );
a2.Print( );
}
程序运行结果:
100:10
0:10
在上例中,建立对象a和b,并以100和0为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员赋初值。关于常数据成员,需要注意以下几点:
一、构造函数对常数据成员进行初始化时必须通过初始化列表进行。
二、对象创建之后,其常数据成员的值不能在任何函数中改写。
三、如果类有多个重载构造函数都必须初始化常数据成员。
浙公网安备 33010602011771号