《C++面向对象软件设计及构建》文章翻译:4.8 控制变化 ,4.8 Controlling Change
对于那些变量、方法和参数,可以以一种特殊的方式来声明,使得,限制住它们能够引起变化或者被改变的能力。可以对变量进行限制,使得,它具有一个常量的、不可改变的值;可以对方法进行限制,使得,它无法对自己正在操作的对象进行变动;可以对参数进行限制,使得,被传入该个参数的目标方法,无法对这个参数进行修改。
利用这种形式对变量、方法和参数进行限制,是狠有用的,具体来说,有三个原因支持这种做法。第一,这些额外的声明内容,更清晰地表达了程序员的意图。通常情况下,这些意图,无法以其它方式表达出来,并且,可能会在无意中被曲解且违反了,于是导致预期之外的行为及系统错误。加上额外的声明之后,编译器就能够检测到并阻止不符合原始意图的使用方式。第二,如果当真出现了错误的话,这些额外的声明有助于缩小系统中必须对之进行检查的范围。如果数据以错误的方式发生了改变,那么,其直接原因必然不会存在于那些已经声明为不会引起改变或不允许引起改变的区域。第三,这些额外的信息通常能够帮助编译器更好地对可执行程序中的代码进行优化。例如,如果编译器知道某个变量不会被改变的话,它就不需要生成相应的代码来保存及恢复这个变量的值。
在C++中,使用关键字 const (常量) 作为修饰符,可以中方法、变量和参数的声明中使用,其意义是,表明,对被修饰的事物进行限制,使得它不可以引起改变或者被改变。这个关键字在实际声明中会出现在不同的位置,具体取决于它是用来修饰一个方法、变量还是参数。并且,针对指针来使用const的话,其用法会有点难以理解,我们日后会说这个。
对于const 属性的执行,是由编译器来进行的。因此,假如妳尝试向一个常量变量或对象进行赋值操作的话,会产生一个编译错误。如果在妳所写的代码中,某个常量方法试图对该个对象的数据中的某些部分进行修改的话,也会在编译期间产生一个错误。
常量变量和对象
对于const 属性的最简单的使用方式,就是,用来修饰变量和对象,在这种情况下,它的意义就是,表明,对应的变量或对象是无法改变的。这就使得,某些变量及对象,能够在程序的整个执行过程中,保持着一个一直不变的值。以下是那些应当声明成常量(const)的值的示例:数学常量(例如,圆周率的值);系统限制(例如,数组下标范围);编程约定(例如,针对某个标志位的代码或值);编程信息(例如,版本号);以及,固定的应用程序信息(例如,某个标准对话框窗口的默认位置)。这些常量所对应的代码,如下所示:
const double Pi = 2.141598; // 数学常量
const int MAX_ARRAY_LENGTH = 100; // 系统限制
const int YesAnswer = 0; // 编程约定
const int NoAnswer = 1; // 编程约定
const int VersionNumber = 1; // 编程信息
const int ReleaseNumber = 5; // 编程信息
const Location dialogLocation (200,200); // 应用程序信息
注意,内置类型(整型(int)、浮点型(double),等等)和用户自定义类(例如,Shape)的对象,都可以被声明成常量(constant)。另外需要注意,声明成常量(const)的变量,必须在声明的时候就指定一个初始值,因为,日后再也无法为这个变量提供值了 - 因为它是常量(const),不可能在声明之后再改变它的值。
正如之前所说的,对常量变量或对象进行赋值的行为,会被编译器检测到并且报告错误。例如,以上面代码中的声明为基础,再写以下代码的话:
Pi = 2.5;
NoAnswer = 2;
dialogLocation = Location(100,100);
就会全部被编译器检测到并报告为编译期错误。
常量方法
关键字const,如果用来修饰某个方法的声明语句的话,其意义就是,表明,这个方法,不会对具体发生调用时的那个对象做出改变。以下这个稍加修改的Location 类版本,展示了,如何针对方法使用const 修饰符。为了展示这个示例,还向类中加入了两个方法,setX()和setY(),它们会修改由该Location 对象所维护的坐标值。
|
class Location { // 扩展1 private: int currentX, currentY; public: Location(int x, int y); // 指定位置 Location(); // 默认位置 int Xcoord() const; // 返回x轴坐标值 int Ycoord() const; // 返回y轴坐标值 void setX(int newx); // 修改x坐标值 void setY(int newy); // 修改y坐标值 }; // 以下是实现代码 Location::Location( int x, int y ) { currentX = x; currentY = y; } Location::Location () { currentX = -1; currentY = -1; } int Location::Xcoord() const { return currentX; } int Location::Ycoord() const { return currentY; } void Location::setX(int newX) { currentX = newX; } void Location::setY(int newY) { currentY = newY; } |
在这砣代码中,使用了const修饰符,它用来声明,那两个方法,Xcoord()和Ycoord(),不会对它们所操作的Location 对象做出修改。这两个方法,是典型的常量(const)方法:它们返回关于该个对象的信息,而不会改变该个对象自身。这种类型的方法,只是简单地查询该对象的信息,而不会改变它。而另外两个方法,setX()和setY()呢,却不能被声明成常量(const)方法,因为,它们确实要修改它们所操作的那个对象。
注意,在上面的示例中,const修饰符既要在类的定义中该个方法所对应的位置出现,也要在类的实现代码中给出该个方法的具体实现的位置出现。
常量方法,可以在常量对象上调用。因为常量方法并不会修改该个对象,所以,调用常量方法并不会破坏该个对象本身的常量属性。例如:
const Location dialogLocation (200,200); // 应用程序信息
...
int dialogX = dialogLocation.Xcoord(); // 没问题
int dialogY = dialogLocation.Ycoord(); // 没问题
dialogLocation.setX(300); // 错误
在这个示例中,可以针对常量对象dialogLocation 来调用常量方法Xcoord()和Ycoord()。然而,非常量方法,例如setX(),就不能在常量对象上调用,因为,它们可能会改变该个对象,这样就破坏了该个对象的常量属性。
一个非常量的对象,可以是常量和非常量方法的调用主体。例如:
Location loc(300,300); // 不是常量对象
...
int x = loc.Xcoord(); // 没问题
loc.setX(x+20); // 没问题 - 可以修改对象
在这个示例中,"loc"对象并不是一个常量对象。于是,任何有效的方法都可以在这个对象上调用,无论它们是常量方法还是非常量方法。
常量参数
const修饰符经常与通过引用传递的参数来配套使用,以实现,既具有值传递方式的安全性,又具有引用传递方式的高效性。回忆一下之前说的,通用引用传递参数,效率更高,因为,它并不会为当前所传递的对象制作一份副本,当相应的对象狠大时(例如,MB尺寸的jpeg 图片),这种效率提升就显得尤其重要了。
下面是Location 类的另外一个扩展版本,用于展示,如何在通过引用传递的参数上使用const。在这个扩展版本中,加入了一个新的方法,它的作用是,测试,某个Location 对象中所定义的坐标,是否与另外一个通过参数传递的Location 对象的坐标相同。此处的参数对象,是通过引用来传递的, 以避免通过复制传递的过程中的任何额外开销。同时,它被声明为常量(const),因为,只是为了确认两个Location是否相同的话,无需对参数对象进行修改。
|
class Location { // 扩展2 private: int currentX, currentY; public: Location(int x, int y); // 指定位置 Location(); // 默认位置 int Xcoord() const; // 返回x轴坐标 int Ycoord() const; // 返回y轴坐标 int isSameAs(const Location& other) const; // 两个位置是否相同 }; // 具体实现如下 Location::Location( int x, int y ) { currentX = x; currentY = y; } Location::Location () { currentX = -1; currentY = -1; } int Location::Xcoord() const { return currentX; } int Location::Ycoord() const { return currentY; } int Location::isSameAs(const Location& other) const{ if ((currentX == other.Xcoord()) && (currentY == other.Ycoord()) ) return 1; else return 0; } |
再次注意,针对参数的const修饰符,会在两处出现:在类定义中该个方法的位置出现;也会在实现代码中具体给出该个方法的地方出现。
注意,isSameAs()方法,本身被声明为一个常量(const)方法,因为,它不会改变自己所作用于其上的那个对象。因此,可以在常量Location 对象上调用这个方法,如下所示:
const Location dialogLocation (200,200); // 应用程序信息
...
Location someWhere();
// 对someWhere 进行操作……
if (dialogLocation.isSameAs(someWhere)) {...}
注意,此处的参数对象,someWhere,不需要是一个常量对象。然而,isSameAs 方法所使用的那个引用,是被const 所修饰的。这样,isSameAs方法就无法修改这个参数的值。即便是,如本示例中所展示的这样,该个参数本身并不是常量对象,也没关系。换句话说,从这个方法调用的角度来看,该个参数被当成一个常量对象来使用,而不管它本身是不是真正的常量对象。
针对指针使用const
const修饰符,也可以与指针配套使用,具体有三种使用方式,取决于这个修饰符具体是修饰了什么:可以修饰指针所指向的那个对象(下图中的lp1);可以修饰指向该个对象的那个指针(下图中的lp2);也可以同时修饰指针和被指向的对象(下图中的lp3)。在每种用法中,const修饰符出现的位置都不同,而在第三种用法中,它会出现两次。下图中,展示了const 对于指针的三种不同用法,随后就是对应的代码和解释。
|
|
在代码中,对于lp1 的声明,表明,它所指向的那个对象是常量,也就是说,在通过lp1 指针来访问"loc"对象时,该对象不可被改变。在示例代码中,使用lp1 来调用Xcoord()方法,是符合规则的,但使用lp1 来调用setX()方法,就是不符合规则的。同时,由于lp1本身并不是常量,所以,可以让它指向另一个Location对象(例如,other)。然而,通过lp1也无法修改other 对象。
对于lp2 的声明,表明,那个指针本身是常量,而那个对象并不是常量。因此,允许针对lp2 所指向的那个对象调用那些会对该个对象进行修改的方法。但是,不允许向lp2 本身赋予一个新的值。此处的 const,其意义是,指针本身的值是常量,不允许通过修改指针的值来让它指向另一个对象。
对于lp3 的声明,表明,指针和对象二者都是常量。因此,可以对该个对象调用那些常量(const)方法,例如Xcoord()。但是,不允许使用像setX()这样的修改方法来对该对象进行修改,也不允许修改指针的值让它指向另一个对象。
Location loc(100,100);
Location other(50,50);
const Location *lp1 = &loc; // 对象 是常量
Location const *lp2 = &loc; // 指针 是常量
const Location * const lp3 = &loc; // 二者 都是常量
lp1->Xcoord(); // 没问题
lp1->setX(10); // 错误
lp1 = &other; // 没问题
lp2->Xcoord(); // 没问题
lp2->setX(10); // 没问题
lp2 = &other; // 错误
lp3->Xcoord(); // 没问题
lp3->setX(10); // 错误
lp3 = &other; // 错误
下面,展示了Location 类的最终版本,它展示了针对参数所使用的常量修饰符(const)和指针。在这个例子中,加入了isInList()方法,它的作用是,检查以确认,该个对象是否是位于以参数的形式传入的Location 对象数组中。isInList 方法中对于那个参数的声明,表明了,在该个方法的执行过程中,既无法修改那个指针,也无法修改被那个指针所指向的数组。同时,isInList方法本身也被声明为常量方法(const),这就意味着,它无法修改执行了该方法的那个对象(也就是说,在列表中搜索自身坐标的那个对象)的数据。
Location 类中的常量指针 |
class Location { // 扩展3 private: int currentX, currentY; public: Location(int x, int y); // 指定位置 Location(); // 默认位置 int Xcoord() const; // 返回x轴坐标 int Ycoord() const; // 返回y轴坐标 int isInList(const Location * const list) const; // 检查,本个对象是否位于list所代表的列表中 }; // 以下是实现代码 Location::Location( int x, int y ) { currentX = x; currentY = y; } Location::Location () { currentX = -1; currentY = -1; } int Location::Xcoord() const { return currentX; } int Location::Ycoord() const { return currentY; } int Location::isInList(const Location * const list, const int length) const const int length { for( int i = 0; i<length; i++) if( (currentX == list[i].Xcoord()) && (currentY == list[i].Ycoord()) ) return 1; return 0; } |
为确保以上示例的完整性,以下给出了一段代码,它会对 isInList 方法进行使用。
Location finder(100,100);
Location list[10];
int length = 10;
//... 为数组list中的对象赋予具体的值
if (finder.isInList(list, 10))
{ finder 的坐标位于list中 }
else { finder 的坐标不位于list 中 }
在这段代码中,由Location 对象所组成的那个数组,会按照C 语言中传递数组的方式作为一个指针被传递。
任务
1.编写一个声明语句,它是一个常量(constant)的Location 对象,指定某个800 x 600 显示器上的中心位置。
2.重写Shape 类的声明,以引入对于常量(const)的使用。
3.检查Frame 类的接口,标识出所有的那些可被声明为常量参数的参数,以及所有那些可被声明为常量方法的方法。
4.检查TextBox 类的接口,标识出所有的那些可被声明为常量参数的参数,以及所有那些可被声明为常量方法的方法。
翁帆
Your opinionsHxLauncher: Launch Android applications by voice commands