当前位置: 首页 > 文档资料 > C++大学教程 >

7.2 const 常量 对象与 const 成员函数

优质
小牛编辑
142浏览
2023-12-01

我们一直强调,最低权限原则(principle of least privilege)是良好软件工程的最基本原则之一。下面介绍这个原则如何应用于对象。

有些对象需要修改,有些不需要。程序员可以用关键字const指定对象不能修改,且修改时会产生语法错误。例如:

const Time noon(12,0,0);

声明 Time 类对象 noon 为 const,并将其初始化为中午12时。

软件工程视点 7.1
将对象声明为const有助于实现最低权限原则,这样试图修改就会产生编译时错误不是执行时错误。

软件工程视点 7.2
使用 const 是正确的类设计、程序设计与编码的关键。

性能提示 7.1
声明变量和对象为 const 不仅是有效的软件工程做法,而且能提高性能,因为如今复杂的优化编译器能对常量进行某些无法对变量进行的优化。

C++ 编译器不允许任何成员函数调用const对象,除非该成员函数本身也声明为 const,即使get成员函数不修改对象时也是这样。声明const的成员函数不能修改对象,因为编译器不允许其修改对象。

函数在原型和定义中指定为const,即在函数参数表和函数定义的左花括号之间插入 const 关键字。例如,下列类A的成员函数:
 int A::getValue() const{ reture privateDataMember) 只是返回一个对象的数据成员值,可以声明为 const。

常见编程错误 7.1
定义修改对象数据成员的 const 成员函数是个语法错误。

常见编程错误 7.2
定义调用同一类实例的非 const 成员函数的 const 成员函数是个语法错误。

常见编程错误 7.1
对 const 对象调用非 consc 成员函数是个语法借误。

软件工程视点 7.3
const 成员函数可以用非 const 版本重载。编译器根据对象是否为 const 自动选择所用的重载版本。

这里对构造函数和析构函数产生了一个有趣的问题,两者都经常需要修改对象。const对象的构造函数和析构函数不需要const声明。构造函数应允许修改对象,才能正确地将对象初始化。析构函数应能在对象删除之前进行清理工作。

常见编程错误 7.4
将构造函数和析构函数声明为 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对象时编译器产生的消息。

编程技巧 7.1
将所有不需要修改当前对象的成员函数声明为 const,以便在需要时调用 const 对象。

1 // Fig. 7.1: time5.h
2 // Declaration of the class Time.
3 // Member functions defined in time5.cpp
4 #ifndef TIME5_H
5 #define TIME5_H
6
7 class Time {
8 public:
9 Time(int = 0,int = 0,int = 0); // default constructor
10
11 // set functions
12 void setTime( int, int, int ); // set time
13 void setHour( int ); // set hour
14 void setMinute( int ); // set minut
15 void setSecond( int ); // set second
16
17 // get functions (normally declared const)
18 int getHour() Const; // return hour
19 int getMinute() const; // return minute
20 int getSecond() const; // return second
21
22 // print functions (normally declared const)
23 void printMilitary() const; // print military time
24 void printStandard(); // print standard time
25 private:
26 int hour; // 0 - 23
27 int minute; // 0 - 59
28 int second; // 0 - 59
29 };
30
31 #endif
32 // Fig. 7.1: time5.cpp
34 #include <iostream.h>
35 #include "time5.h"
37 // Constructor function to initialize private data.
38 // Default values are 0 (see class definition).
39 Time::Time( int hr, int min, int sec )
40 { setTime{ hr, min, sec ); }
42 // Set the values of hour, minute, and second.
43 void Time::setTime( int h, int m, int s )
44 {
45 setHour( h );
46 setMinute( m );
47 setSecond( s );
48 }
49
50 // Set the hour value
51 void Time::setHour( int h )
52 { hour = ( h >= 0 && h < 24 ) ? h : 0; )
53
54 // Set the minute value
55 void Time::setMinute( int m )
56 { minute = ( m >= 0 && m < 60 ) ? m : 0; }
57
58 // Set the second value
59 void Time::setSecond( int s )
60{ second = ( s >= 0 && s < 60 ) ? s : 0; }
61
62 // Get the hour value
63 int Time::getHour() const {return hour;}
64
65 // Get the minute value
66 int Time::getMinute() const { return minute;}
67
68 // Get the second value
69 int Time::getSecond() const { return second;}
7O
71 // Display military format time: HH:MM:
72 void Time::printMilitary() const
73 {
74 cout << ( hour < 10 ? "0": "") << hour <<":"
75 << ( minute < 10 ? "0" : "") << minute; ) << minute;
76 }
77
78 // Display standard format time: HH:MM:SS AM (or PM)
79 void Time::printStandard{)
80 {
81 cout << ( ( hour == 12 ) ? 12 : hour % 12 ) << ":"
82 << ( minute < 10 ? "0" : "" ) << minute << ":"
83 << ( second < 10 ? "0" : "" ) << second
84 << ( hour < 12 ? "AM" : "PM" );
85 }
86 // Fig. 7.1:fig07 01.cpp
87 // Attempting to access a const object with
88 // non-const member functions.
89 #include <iostream.h>
90 #include "time5.h"
91
92 int main()
93 {
94 Time wakeUp{ 6, 45, 0 ); // non-constant object
95 const Time noon( 12, 0, 0 ); // constant object
96
97 // MEMBER FUNCTION OBJECT
98 wakeUp.setHour( 18 ); // non-const non-const
99
100 noon.setHour( 12 ); // non-const const
101
102 wakeUp.getHour(); // const non-const
103
104 noon.getMinute(); // const const
105 noon.p,intMilitary(); // const const
106 noon.printStandard(); // non-const const
107 return 0;
108 }

输出结果:

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 成员函数时是合法的。

软件工程视点 7.4
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章学习继承时,会介绍派生类的基类部分也要用这种方法进行初始化。

测试与调试提示 7.1
如果成员函数修改对象,则将其声明为 const,这样可以减少许多错误。

1 // Fig. 7.2:fig07 02.cpp
2 // Using a member initializer to initialize a
3 // constant of a built-in data type.
4
5 #include <iostream.h>
6
7 class Increment {
8 public:
9 Increment( int c = 0, int i = 1 );
10 void addIncrement() { count += increment; }
11 void print() const;
12
13 private:
14 int count;
15 const int increment;
16 };
17
18 // Constructor for class Increment
19 Increment::Increment( int c, int i )
20 : increment( i ) // initializer for const member
21 {count = c;}
22
23 // Print the data
24 void Increment::print() const
25 {
26 cout << "count = "<< count
27 << ", increment = "<< increment << endl;
28 }
30 int main()
31 {
32 Increment value( 10, 5 );
33
34 cout << "Before incrementing: ";
35 value.print();
36
37 for ( int j = 0; j < 3; j++ ) {
28 value.addIncrement();
39 cout << "After increment "<< j << ": ";
40 value.print();
41 }
42
43 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++ 编译器产生的编译错误。

1 // Fig. 7.3: fig07_03.cpp
2 // Atempting to initialize a costant of
3 // a built-in data type with an assignment.
4 #include <iostream.h>
6 class Increment {
7 public:
8 Increment( int c = 0, int i = 1 );
9 void addIncrement() { count += increment; }
10 void print() const;
11 private:
12 int Count;
13 const int increment;
14 };
15
16 // Constructor for class Increment
17 Increment::Increment( int c, int i)
18 { // Constant member ~ncrement' is not initialized
19 count = c;
20 increment = i; // ERROR: Cannot modify a const object
21 }
22
23 // Print the data
24 void Increment::print() const
25 {
26 cout << "count =" << count
27 << ", increment =" << increment << endl;
28 }
29
30 int main{)
31 {
32 Increment value( 10, 5 );
33
34 cout << "Before incrementing: ";
35 value.print();
36
37 for ( int j = 0; j < 3: j++ } {
38 value.addIncrement();
39 cout << "After increment "<< j << ": ";
40 value.print{);
41 }
42
43 return 0;
44 }

输出结果:

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 用赋值语句初始化内部数据类型的常量时产生的编译错误

常见编程错误 7.5
不为 const 数据成员提供成员初始化值是个语法错误。

软件工程视点 7.5
常量类成员(const 对象和 const 变量)要用成员初始化值的语法初始化,而不能用赋值语句。
注意第24行将 print 函数声明为 const,但不会有 const 类型的 Inerement 对象。

软件工程视点 7.6
如果成员函数不修改对象,最好将其声明 const。如果不需要生成该类的 const 类型对象,则这样做是没有必要的。但将这种成员函数声明为const有一个好处,如果不小心修改了这个成员函数中的对象,则编泽器全产生一个语法错误的消息。

测试与调试提示 7.2
C++ 之类的语言是不断演变的,新的关键字不断出现。不要用 object 之类的标识符。尽管 object 目前还不是 C++ 中的关键字,但将来很可能变成关键字,新的编译器可能不能接受现有代码。

C++ 提供了新的关键字 mutable,能够对程序中 const 对象进行处理。第21章将介绍关键字 mutable。