教学目标
●动态生成与删除对象
●指定const对象与const成员函数
●了解友元函数与友元类的用途
●了解如何使用static数据成员和成员函数
●了解容器类的概念
●了解遍历容器类元素的迭代类概念
●了解this指针的用法
本章继续介绍类与数据抽象。我们要介绍更高级的课题并为第8章介绍类与运算符重载奠定基础。第6章到第8章的讨论鼓励程序员使用对象,我们称之为基于对象编程(object-based programming,OBP)。然后,第9章和第10章介绍继承与多态,这是真正面向对象编程(object-oriented programming,OOP)的技术。本章和后面几章要使用第5章介绍的C语言式字符串,帮助读者掌握C语言指针的复杂课题.以便在工作中处理近二十年来积累的遗留代码。第19章将介绍新的字符串样式,将字符串作为完全成熟的类对象。这样,读者将熟悉C++中生成和操作字符串的两种最主要方法。
我们一直强调,最低权限原则(principle of least privilege)是良好软件工程的最基本原则之一。下面介绍这个原则如何应用于对象。
有些对象需要修改,有些不需要。程序员可以用关键字const指定对象不能修改,且修改时会产生语法错误。例如:
const Time noon(12,O,O);
声明Time类对象noon为const,并将其初始化为中午12时。
将对象声明为const有助于实现最低权限原则,这样试图修改就会产生编译时错误不是执行时错误。
使用const是正确的类设计、程序设计与编码的关键。
声明变量和对象为const不仅是有效的软件工程做法,而且能提高性能,因为如今复杂的优化编译器能对常量进行某些无法对变量进行的优化。
C++编译器不允许任何成员函数调用const对象,除非该成员函数本身也声明为const,即使get成员函数不修改对象时也是这样。声明const的成员函数不能修改对象,因为编译器不允许其修改对象。
函数在原型和定义中指定为const,即在函数参数表和函数定义的左花括号之间插入const关键字。例如,下列类A的成员函数:
int A::getValue() const{ reture privateDataMember);只是返回一个对象的数据成员值,可以声明为const。
定义修改对象数据成员的const成员函数是个语法错误。
定义调用同一类实例的非const成员函数的const成员函数是个语法错误。
对const对象调用非consc成员函数是个语法借误。
const成员函数可以用非const版本重载。编译器根据对象是否为const自动选择所用的重载版本。
这里对构造函数和析构函数产生了一个有趣的问题,两者都经常需要修改对象。const对象的构造函数和析构函数不需要const声明。构造函数应允许修改对象,才能正确地将对象初始化。析构函数应能在对象删除之前进行清理工作。
将构造函数和析构函数声明为const是个语法错误。
图7.1的程序实例化两个Time对象,一个非const对象和一个const对象。程序想用非const成员函数setHour(第100行)和printStandard(第106行)修改const对象noon。程序还演示了另外三个成员函数调用对象的组合,一个非Const成员函数调用非const对象(第98行)、一个const成员函数调用非const对象(第102行)和一个const成员函数调用const对象(第104与第105行)。输出窗口中显示了一个非const成员函数调用const对象时编译器产生的消息。
将所有不需要修改当前对象的成员函数声明为const,以便在需要时调用const对象。
// Fig. 7.1: time5.h // Declaration of the class Time. // Member functions defined in time5.cpp #ifndef TIME5_H #define TIME5_H class Time { public: Time(int = 0,int = 0,int = 0); // default constructor // set functions void setTime( int, int, int ); // set time void setHour( int ); // set hour void setMinute( int ); // set minut void setSecond( int ); // set second // get functions (normally declared const) int getHour() Const; // return hour int getMinute() const; // return minute int getSecond() const; // return second // print functions (normally declared const) void printMilitary() const; // print military time void printStandard(); // print standard time private: int hour; // 0 - 23 int minute; // 0 - 59 int second; // 0 - 59 }; #endif // Fig. 7.1: time5.cpp #include < iostream.h> #include "time5.h" // Constructor function to initialize private data. // Default values are 0 (see class definition). Time::Time( int hr, int min, int sec ) { setTime{ hr, min, sec ); } // Set the values of hour, minute, and second. void Time::setTime( int h, int m, int s ) { setHour( h ); setMinute( m ); setSecond( s ); } // Set the hour value void Time::setHour( int h ) { hour = ( h >= 0 && h < 24 ) ? h : 0; ) // Set the minute value void Time::setMinute( int m ) { minute = ( m >= 0 && m < 60 ) ? m : 0; } // Set the second value void Time::setSecond( int s ) { second = ( s >= 0 && s < 60 ) ? s : 0; } // Get the hour value int Time::getHour() const {return hour;} // Get the minute value int Time::getMinute() const { return minute;} // Get the second value int Time::getSecond() const { return second;} // Display military format time: HH:MM: void Time::printMilitary() const { cout << ( hour < 10 ? "0": "") << hour <<":" << ( minute < 10 ? "0" : "") << minute; ) << minute; } // Display standard format time: HH:MM:SS AM (or PM) void Time::printStandard{) { cout << ( ( hour == 12 ) ? 12 : hour % 12 ) << ":" << ( minute < 10 ? "0" : "" ) << minute << ":" << ( second < 10 ? "0" : "" ) << second << ( hour < 12 ? "AM" : "PM" ); } // Fig. 7.1:fig07 01.cpp // Attempting to access a const object with // non-const member functions. #include < iostream.h> #include "time5.h" int main() { Time wakeUp{ 6, 45, 0 ); // non-constant object const Time noon( 12, 0, 0 ); // constant object // MEMBER FUNCTION OBJECT wakeUp.setHour( 18 ); // non-const non-const noon.setHour( 12 ); // non-const const wakeUp.getHour(); // const non-const noon.getMinute(); // const const noon.p,intMilitary(); // const const noon.printStandard(); // non-const const return 0; }
输出结果:
Compiling Fig07_01.cpp
Fig07_01.cpp(15):error: 'setHour':
cannot convert 'this' pointer from
'const class Time' to 'class Time &'
Conversion loses qualifiers
Fig07_01.cpp(21) : error: 'printStandazd' :
cannot convert 'this' pointer from
'const class Time' to 'class Time &'
Conversion loses qualifiers
注意,尽管构造函数应为非const成员函数,但仍然可以对const对象调用构造函数。Time构造函数的定义在第39行和第40行 Tlme:Time( int hr, int min,int sec )
{ setTime(hr, min, sec); }
其中Time构造函数调用另一个非const成员函数setTime进行Time对象的初始化。在const对象的构造函数调用中调用非const成员函数时是合法的。
const对象不能用赋值语句修改,因此应初始化。类的数据成员声明为const时.要用成员初始化值向构造函数提供类对象数据成员的初始值。
另外注意第106行(源文件中第21行):
noon.printStandard();// noon—const const
尽管Time类的成员函数printStandard不修改所调用的对象,但仍然产生一个编译错误。
图7.2演示用成员初始化值初始化Increment类的const数据成员increment。Increment的构造函数修改后如下所示:
Increment::Increment( int c,int i ) :increment( i ) { count = c;}
符号:increment(i)将increment初始化为数值i。如果需要多个成员初始化值,则可以将其放在冒号后面以逗号分隔的列表中。所有数据成员都可以用成员初始化值的语法进行初始化,但const和引用必须用这种方式进行初始化。本章稍后会介绍,成员对象也要用这种方法进行初始化。第9章学习继承时,会介绍派生类的基类部分也要用这种方法进行初始化。
如果成员函数修改对象,则将其声明为const,这样可以减少许多错误。
// Fig. 7.2:fig07 02.cpp // Using a member initializer to initialize a // constant of a built-in data type. #include < iostream.h> class Increment { public: Increment( int c = 0, int i = 1 ); void addIncrement() { count += increment; } void print() const; private: int count; const int increment; }; // Constructor for class Increment Increment::Increment( int c, int i ) : increment( i ) // initializer for const member {count = c;} // Print the data void Increment::print() const { cout << "count = "<< count << ", increment = "<< increment << endl; } int main() { Increment value( 10, 5 ); cout << "Before incrementing: "; value.print(); for ( int j = 0; j < 3; j++ ) { value.addIncrement(); cout << "After increment "<< j << ": "; value.print(); } return 0;
输出结果:
Before incrementing: count = 10, increment = 5
After increment 1: count = 15, increment = 5
After increment 2: count = 20, increment= 5
After increment 3: count = 30,increment = 5
图7.2 用成员初始化值初始化内部数据类型的常量
图7.3显示的是用赋值语句而不用成员初始化值初始化increment时C++编译器产生的编译错误。
// Fig. 7.3: fig07_03.cpp // Atempting to initialize a costant of // a built-in data type with an assignment. #include <iostream.h> class Increment { public: Increment( int c = 0, int i = 1 ); void addIncrement() { count += increment; } void print() const; private: int Count; const int increment; }; // Constructor for class Increment Increment::Increment( int c, int i) { // Constant member ~ncrement' is not initialized count = c; increment = i; // ERROR: Cannot modify a const object } // Print the data void Increment::print() const { cout << "count =" << count << ", increment =" << increment << endl; } int main{) { Increment value( 10, 5 ); cout << "Before incrementing: "; value.print(); for ( int j = 0; j < 3: j++ } { value.addIncrement(); cout << "After increment "<< j << ": "; value.print{); } return 0; }
输出结果:
Compiling...
FigT_3.cpp
Fig7_3.cpp(18) : error: 'increment':
must be initialized in constructor base/member
initializer list
Fig7 3.cpp(20) : error: 1-value specifies const object
图7 3 用赋值语句初始化内部数据类型的常量时产生的编译错误
不为const数据成员提供成员初始化值是个语法错误。
常量类成员(const对象和const“变量”)要用成员初始化值的语法初始化,而不能用赋值语句。
注意第24行将print函数声明为const,但不会有const类型的Inerement对象。
如果成员函数不修改对象,最好将其声明const。如果不需要生成该类的const类型对象,则这样做是没有必要的。但将这种成员函数声明为const有一个好处,如果不小心修改了这个成员函数中的对象,则编泽器全产生一个语法错误的消息。
C++之类的语言是不断演变的,新的关键字不断出现。不要用“object'’之类的标识符。尽管”object”目前还不是C++中的关键字,但将来很可能变成关键字,新的编译器可能不能接受现有代码。C++提供了新的关键字mutable,能够对程序中const对象进行处理。第21章将介绍关键字mutable。