您希望下个月的早餐、午餐和晚餐吃些什么?在第三天的晚餐喝多少盎司的牛奶?在第15天的早餐中需要在谷类食品添加多少葡萄干?如果您和大多数人一样,就会等到进餐时再做决定。C++在分配内存时采取的部分策略与此相同,让程序在运行时决定内存分配,而不是在编译时决定。这样,可以根据程序的需要,而不是根据一系列严格的存储类型规则来使用内存。C++使用new和delete运算符来动态控制内存。遗憾的是,在类中使用这些运算符将导致许多新的编程问题。在这种情况下,析构函数是必不可少的,而不再是可有可无的。有时候,还必须重载赋值运算符,以保证程序正常运行。下面来看一看这些问题。
下面的程序中我们将使用new和delete。并且,引入了一种新的存储类型:静态类成员。首先设计一个StringBad类,然后设计一个功能稍强的MyString类。StringBad类对象将包含一个字符串指针和一个表示字符串长度的值。这里使用StringBad类,主要是为了深入了解new、delete和静态类成员的工作原理。因此,构造函数和析构函数调用时将显示一些信息,以便您能按照提示完成一些操作。
那么为什么将它命名为StringBad呢?这是为了表示提醒,StringBad是一个还没有开发好的示例。这是使用动态内存分配来开发的第一步,它正确地完成了一些显而易见的工作,例如,它在构造函数和析构函数中正确的使用new和delete。它其实不执行有害的操作,但省略了一些有益的功能,这些功能是必需的,但却不是显而易见的。通过说明这个类存在的问题,有助于稍后将它转换为一个功能更强的MyString类时,理解和牢记所做的一些并不明显的修改。
#ifndef STRINGBAD_H_
#define STRINGBAD_H_
#include <iostream>
using namespace std;
class StringBad
{
private:
char * str; // pointer to string
int len; // length of string
static int num_strings; // number of objects
public:
StringBad(const char * s); // constructor
StringBad(); // default constructor
~StringBad(); // destructor
// friend function
friend ostream & operator << (ostream & os, const StringBad & st);
};
#endif
对这个声明,需要注意的有两点:
首先,它使用char指针(而不是char数组)来表示姓名。这意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。这避免了在类声明中预先定义字符串长度。
其次,将num_strings成员声明为静态存储类。静态类成员有一个特点:无论创建多少个对象,程序都只创建一个静态类变量副本。也就是说,类的所有对象共享一个静态成员,就像家中的电话可供全体家族成员共享一样。这对于所有类对象都具有相同值的私有数据是非常方便的。例如,num_strings成员可以记录所创建的对象数目。
下面的程序清单中类方法的实现,它演示了如何使用指针和静态成员。
#include "stringbad.h"
#include <cstring>
int StringBad::num_strings = 0;
StringBad::StringBad(const char * s)
{
len = strlen(s);
str = new char [len+1];
strcpy(str, s);
num_strings++;
cout << num_strings << ": \"" << str << "\" object created.\n";
}
StringBad::StringBad()
{
len = 4;
str = new char [len+1];
strcpy(str, "C++");
num_strings++;
cout << num_strings << ": \"" << str << "\" default object created.\n";
}
StringBad::~StringBad()
{
cout << "\"" << str << "\" object deleted, ";
num_strings--;
cout << num_strings << " left.\n";
delete [] str;
}
ostream & operator << (ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
首先,请注意下面的语句:
int StringBad::num_strings = 0;
这条语句将静态成员num_strings的值初始化为0。请注意,不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但却并不分配内存。您可以使用这种格式来创建对象,从而分配和初始化内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。请注意,初始化语句指出了类型,并使用了作用域运算符,但没有使用关键字static。
初始化是在方法文件中,而不是在类的头文件中进行的,这是因为类声明位于头文件中,程序可能将头文件包括在其他几个文件中。如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误。
注意:静态数据成员在类声明中声明,在包含类的方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但如果静态成员是整型const或枚举型const,则可以在类声明中初始化