7.7 static 类成员
类的每个对象有自己的所有数据成员的副本,有时类的所有对象应共享变量的一个副本,因此可以使用 static 类变量。stattic 类变量表示的是类范围中所有对象共享的信息。static 类成员的声明以 static 关键字开始。
下面用一个视频游戏的例子说明 static 类共享数据的作用。假设视频游戏中有 Martian 和其他太空人。每个 Martian 都很勇敢,只要有5个 Martian 存在,就可以攻击其他太空人。如果 Martian 的人数不到5个,则不能进行攻击。因此每个 Martian 都要知道 martianCount。我们在 Martian 类中提供一个 martianCount 数据成员,这样,每个 Martian 有该数据成员的副本,每次生成新Martian时,都要更新每个 Martian 中的 martianCount,这样既浪费空间又浪费时间。
为此,我们将 martianCount 声明为 static,这样就使 martianCount 成为类中共享的数据。每个 Martian 都可以访问 martianCount,就像是自己的数据成员一样,但 C++ 只需维护 martianCount 的一个静态副本,这样可以节省空间。让 Martian 构造函数递增静态 martianCount 还能节省时间,因为只有一个副本,不需要对每个 Martian 对象递增 martianCount。
性能提示 7.4
如果一个数据副本就足够使用,用static数据成员可以节省存储空间。
数据成员 count 维护Employee类实例化的对象个数。Employee类的对象存在时,可以通过Employee对象的任何成员函数引用成员count,本例中,构造函数和析构函数都引用了count。
常见编程错误 7.10
在文件范围的static类变量定义中包括satic关键字是个语法错误。
1 // Fig. 7.9: employl.h
2 // An employee class
3 #ifndef EMPLOY1_H
4 #define EMPLOy1_H
5
6 class Employee {
7 public:
8 Employee( const char*, const char* ); // constructor
9 ~Employee(); // destructor
10 const char *getFirstName() const; // return first name
11 const char *getLastName() const; // return last name
12
13 // static member function
14 static int getCount(); // return # objects instantiated
15
16 private:
17 char *firstName;
18 char *lastName;
19
20 // static data member
21 static int count; // number of objects instantiated
22 };
23
24 #endif
25 // Fig. 7.9: employl.cpp
26 // Member function definitions for class Employee
27 #include <iostream.h>
28 #include <string.h>
29 #include <assert.h>
30 #include "employ1.h"
31
32 // Initialize the static data member
33 int Employee::count = O;
34
35 // Define the static member function that
36 // returns the number of employee objects instantiated.
37 int Employee::getCount() { return count; }
38
39 // Constructor dynamically allocates space for the
40 // first and last name and uses strcpy to copy
41 // the first and last names into the object
42 Employee::Employee( const char *first, const char *last )
43 {
44 firstName = new char[ strlen( first ) + 1 ];
45 assert( firstName != 0 ); // ensure memory allocated
46 strcpy( firstName, first );
47
48 lastName = new char[ strlen( last ) + 1 ];
49 assert( lastName ! = 0 ); // ensure memory allocated
50 strcpy( lastName, last );
52 ++count; // increment static count of employees
53 cout << "Employee constructor for" << firstName
54 << ' ' << lastName <<" called." << endl;
55 }
56
57 // Destructor deallocates dynamically allocated memory
58 Employee::~Employee()
59 {
60 cout << "~Employee() called for" << firstName
61 << ' ' << lastName << endl;
62 delete [] firstName; // recapture memory
63 delete [] lastName; // zecapture memory
64 --count; // decrement static count of employees
65 }
66
67 // Return first name of employee
68 const char *Employee::getFirstName() const
69 {
70 // Const before return type prevents client modifying
71 // private data. Client should copy returned string before
73 return firstName;
74 }
75
76 // Return last name of employee
77 const char *Employee::getLastName() const
78 {
79 // Const before return type prevents client modifying
80 // private data. Client should copy returned string before
81 // destructor deletes storage to prevent undefined pointer.
82 return lastName;
83 }
84 // Fig. 7.9: fig0709.cpp
85 // Driver to tast the employee class
86 #include <iostream.h>
83 #inelude "employ1.h"
89 int main()
90 {
91 cout << "Number of employees before instantiation is"
92 << Employee::getCount() << endl; // use class
93
94 Employee *e1Ptr = new Employee( "Susan", "Baker" );
95 Employee *e2Ptr = new Employee( "Robert", "Jones" );
96
97 cout << "Number of employees after instantiation is"
98 << e1Ptr->getCount();
99
100 cout << "\n\nEmployee 1:"
101 << e1Ptr ->getFirstName()
102 << " " << e1Ptr->getLastName()
103 << "\nEmployee 2:"
104 << e2Ptr->getFirstName()
105 << " "<< e2Ptr -> getLastName() << "\n\n";
106
107 delete e1Ptr; // recapture memory
108 e1Ptr = 0;
109 delete e2Ptr; // recapture memory
110 e2Ptr = 0;
111
112 cout << "Number of employees after deletion is"
113 << Employee::getCount() << endl;
114
115 return 0;
116 }
输出结果:
Number of employees before instantiation is 0
Employee constructor for Susan Baker called.
Employee constructor for Robert Jones called.
Number of employees after instantiation is 2
Employee 1: Susan Baker
Employee 2: Robert Jones
~ Employee() called for Susan Baker
~ Employee() called for Robert Jones
Number of employees after deletion is 0
图 7.9 用 static 数据成员维护类的对象个数
Employee 类的对象不存在时,仍然可以引用成员 count,但只能通过调用static成员函数 getCount:
Employee::getCount()
本例中,函数getCount确定当前实例化的Employee对象个数。注意,程序中没有实例化的对象时,发出employee::getCount()函数调用。但如果有实例化的对象,则可以通过一个对象调用函数getCount,见第97行和第98行的语句:
cout << "Number of employees after instantiation is "
<<elPtr—>getCount();
注意,调用 e2Ptr->getCount()
和 Employee::getCount()
也能使上述语句运行。
软件工程视点 7.13
有些公司的软件工程标准要求所有static成员函数只能对类名句柄调用,而不能对对象句柄调用。
如果成员函数不访问非 static 类数据成员和成员函数,则可以声明为 static。与非 static 成员函数不同的是,static 成员函数没有 this 指针,因为 static 类数据成员和成员函数是独立于类对象而存在的。
常见编程错误 7.11
在 static 成员函数中引用 this 指针是个语法错误。
常见编程错语 7.12
将 static 成员函数声明为 const 是个语法错误。
软件工程视点 7.14
即使在类没有实例化任何对象时,类的 static 数据成员和成员函数就已经存在并可使用。
第94行和第95行用运算符 new 动态分配两个 Employee 对象。分配每个 Employee 对象时调用其构造函数。第107行和第109行用 delete 释放两个 Employee 对象的内存空间时,调用其析构函数。
编程技巧 7.4
删除动态分配内存后,设置指向该内存的指针指向0,辽样就切断了指针与前面所分配内存的连接。
注意 Employee 构造函数中使用了 assert。assert 宏在 assert.h 头文件中定义,测试条件值。如果表达式值为 false,则 assert 发出错误消息,并调用 abort 函数(在一般实用程序头文件 stdlib.h 中)终止程序执行。这是个有用的调试工具,可以测试变量是否有正确值。在这个程序中,assert 确定 new 运算符能否满足动态分配内存的请求。例如,在 Employee 构造函数中,下列语句(也称为断言):
assert(firstName!=O);
测试指针 fi~tNme 以确定其是否不等于0。如果上述语句中的条件为 true,则程序继续执行,不被中断。如果上述语句中的条件为 false,则程序打印一个错误消息,包括行号、测试条件和断言所在的文件名,然后程序终止。程序员可以从这个代码区域找出错误。第13章 异常处理 中将介绍处理执行时错误的更好方法。
断言不一定要在调试完成后删除。程序不再用这个断言进行调试时,只要在程序文件开头插入下列语句即可:
#define NDEBUG
这时预处理程序忽略所有断言而不必由程序员手工删除每条断言。
注意函数 getFirstName 和 getLastName 的实现方法向类的客户返回常量字符指针。在这个实现方法中,如果客户要保留姓和名的副本,则客户要在取得对象的常量字符指针之后负责复制 Employee 对象的动态分配内存。注章,还可以使用 getFirstName 和 getlastName 让客户向每个函数传递字符数组和数组长度。然后函数可以将姓或名复制到客户提供的字符数组中。