C++类和动态数组


析构函数

在类属性中使用动态变量有一个问题。在函数中即使使用局部指针变量创建动态变量,且局部指针变量在函数调用结束时离去,除非调用 delete,否则动态变量仍会保存在内存中。不调用 delete 销毁动态变量,动态变量就会一直占用内存空间,这会导致程序因耗尽自由存储而终止。此外,将动态变量嵌入类的实现中,由于使用该类的程序员并不知道动态变量的存在,所以不能指望他们帮你调用 delete。事实上,由于数据成员通常是私有成员,所以程序员通常不能访问所需的指针变量,所以根本不能为这些指针变量调用 delete。为了解决这个问题,C++ 提供了称为 析构函数 的特殊成员函数。

析构函数(destructor) 是成员函数,在类的对象离开作用域时自动调用。换言之,如函数包含局部变量,而且这个局部变量是提供了析构函数的对象,那么函数调用终止时会自动调用析构函数。如果正确定义了析构函数,析构函数就会调用 delete 销毁由对象创建的所有动态变量。为达到 “析构” 的目的,可能只需要调用一次 delete,也可能需要调用多次。可让析构函数执行其他清理工作,但将内存回收到自由存储是析构函数的主职。

析构函数的定义为 ~ + 类名,其与构造函数的定义类似,只是前面多出一个 ~ 符号,比如 StringVar 类的析构函数就为 ~StringVar。析构函数不能指定返回值类型,无参数,所以每个类只能有一个析构函数,不能为类重载析构函数。除了这些区别,析构函数的定义方式与其他成员函数相同。

下面借助下面的示例进行说明:

#include <iostream>
#include <cstring>
#include <cstddef>
#include <cstdlib>
using namespace std;

class StringVar
{
public:
    StringVar(const char a[]);
    // 前条件:数组 a 包含以 '\0' 终止的一组字符
    // 初始化对象,使它的值成为a中存储的字符串
    // 并使其以后能设置成最大长度为strlen(a)的字符串值

    ~StringVar();
    // 析构函数

    friend ostream& operator <<(ostream& outs, const StringVar& theString);

private:
    char *value; //指向容纳字符串值的动态数组的指针
};
void conversation();
// 开始与用户的对话
int main()
{
    conversation();
    return 0;
}

StringVar::StringVar(const char a[])
{
    value = new char[strlen(a)+1];
    strcpy(value, a);
}

StringVar::~StringVar()
{
    delete [] value;
}
ostream& operator <<(ostream& outs, const StringVar& theString)
{
    outs << theString.value;
    return outs;
}

void conversation()
{
    StringVar ourname("Borg");
    cout <<"We are "<< ourname <<endl;
}

上面定义的析构函数 ~StringVar() 调用 delete 销毁成员指针变量 value 指向的动态数组。分析 conversation 函数,局部变量 ourName 会创建动态数组。如果类没有析构函数,在对 conversation 的调用结束后,动态数组仍会占用内存,即使它们对于程序来说已完全无用。对于这个示范程序,似乎不是很严重的问题,因为在对 conversation 的调用结束之后,程序会马上终止。但如果写程序反复调用 conversation 这样的函数,而且 StringVar 类没有合适的析构函数,函数调用就会不断消耗自由存储中的内存,直至所有内存都消耗殆尽,造成程序不得不异常终止。


拷贝构造函数

**拷贝构造函数(copy constructor)**要求获取一个参数,该参数具有与类相同的类型。该参数必须传引用,而且通常要在前面附加 const 参数修饰符,使它成为常量参数。除此之外,拷贝构造函数的定义和用法与其它任何构造函数完全相同。

例如,我们在上面的代码中使用拷贝构造函数。

StringVar ourname("Brog");
StringVar myname(ourname);

成员变量 myname.value 不能简单地设置成与 ourname.value 相同的值,那样会造成两个指针指向同一个动态数组,即浅拷贝。

StringVar::StringVar(const StringVar& stringObject)
{
    value = new char[10];
    strcpy(value, stringObject.value);
}

而应该像上面的代码新建一个动态数组,并将一个动态数组的内容拷贝到另一个动态数组。这就是深拷贝。

具体地说,拷贝构造函数会在三种情况下自动调用:

  • 声明类的对象,并由同类型的另一个对象初始化。
  • 函数返回类类型的值。
  • 在传值形参的位置“插入”类类型的实参。

下面举一个例子说明没有拷贝构造函数会出现什么问题。

void showString(StringVar theString)
{
	cout<<"The string is: "
		<< theString << endl;
}

再给定以下代码,其中包括一个函数调用:

StringVar greeting("Hello");
showString(greeting);
cout << "After call: " << greeting << endl;

假定没有拷贝构造函数,那么具体过程是:执行函数调用时,greeting 的值复制给局部变量 theString,所以 theString.value 被设置成与 greeting,value 相等。但是,这些是指针变量,所以在函数调用期间,theString.valuegreeting.value 指向同一个动态数组。

函数调用结束之后,会调用 StringVar 的析构函数,将 theString 使用的内存返回给自由存储。由于 greeting.valuetheString.value 指向同一个动态数组,所以删除 theString.value 就是删除 greeting.value。那么之和执行 cout<<"After call:"<<greeting<<endl 就是未定义的。如果 greeting 对象是某些函数的局部变量,就会出现重大问题。在这种情况下,析构函数调用等价于:

delete [] greeting.value

重复调用 delete 来删除同一个动态数组,可能会造成严重的系统错误,并导致程序崩溃。


重载赋值操作符

假设 string1string2 像下面这样声明:

StringVar string1("abc"), string2("xyz");

下面的赋值函数,会将 string2 的属性拷贝给 string1

string1 = string2;

当然这里为浅拷贝,如果需要深拷贝,要重载赋值操作符。重载赋值操作符与重载其他操作符不同,重载赋值操作符必须是类的成员,而不能是类的友元。

class StringVar
{
public:
	void operator =(const StringVar& rightSide);
	// 重载赋值操作符=,将字符串从一个对象复制到另一个

函数定义如下:

void StringVar::operator =(const StringVar& rightSide)
{
	for(int i=0; i<strlen(rightSide.value); ++i)
		value[i] = rightSide.value[i];
	value[strlen(rightSide.value)] = '\0';
}

Big Three

拷贝构造函数、操作符= 以及 析构函数统称为 Big Three。专家认为,如果需要定义其中一个,就必须定义全部三个。缺少任何一个,编译器都会帮你创建它,只是可能达不到你预期的效果。所以,有必要每次都自己定义。假如所有成员变量都具有预定义了类型(比如 intdouble),那么编译器生成的拷贝构造函数和重载的 = 能很好的工作。但假如类中包含类或指针成员变量,它们就可能表现失常,对于使用指针和操作符 new 的任何类,最保险的做法就是定义自己的拷贝构造函数、重载的操作符=以及析构函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值