析构函数
如果类对象成员指向一块new分配的内存,则需要编写一个析构函数来释放内存。当删除类对象时,C++会释放对象本身占用的内存,但并不能自动释放对象成员所指向的内存,所以需要编写相应的析构函数来释放对象成员所指的内存。
例如以下类声明:
class Cowboy
{
private:
char* name;
public:
Cow();
Cow(const char* nm, double wt);
~Cow();
};
若构造函数中为name分配了内存,析构函数需要释放name所指的内存。
复制构造函数
下面用一个存在问题的类来说明复制构造函数的作用。
假设存在如下类:
class Cowboy
{
private:
static int num_cows;
char* name;
double weight;
public:
Cow();
Cow(const char* nm, double wt);
~Cow();
void showCow();
};
其中num_cows
为静态类成员,它的特点是无论创建了多少对象,程序都只创建一个静态类变量副本,所以可以用它来记录创建的类对象数量。
类方法实现如下:
#include <cstring>
#include <iostream>
#include "Cow.h"
using namespace std;
int Cow::num_cows = 0;
Cow::Cow() {
name = nullptr;
weight = 0;
num_cows++;
cout << num_cows << ": \"" << name << "\" created" << endl;
}
Cow::Cow(const char* nm, double wt) {
int len;
len = strlen(nm);
name = new char[len + 1];
strcpy_s(name,len+1, nm);
weight = wt;
num_cows++;
cout << num_cows << ": \"" << name << "\" created" << endl;
}
void Cow::showCow() {
cout << "Cow name: " << name << ", Cow weight: " << weight << endl;
}
Cow::~Cow() {
cout << "\"" << name << "\" deleted ";
--num_cows;
cout << num_cows << " left" << endl;
delete[] name;
}
void call1(Cow& c) {
cout << "Cow passed by reference:" << endl;
c.showCow();
}
void call2(Cow c) {
cout << "Cow passed by value:" << endl;
c.showCow();
}
call1和call2为两个测试函数,call1使用引用传参调用类对象,call2使用值传递。
现在让我们来测试这个类:
void CowTest() {
Cow Bob("Bob", 120);
Cow Alice("Alice", 150);
Cow Tom("Tom", 120);
cout << "Bob: ";
Bob.showCow();
cout << "Alice: ";
Alice.showCow();
cout << "Tom: ";
Tom.showCow();
cout << endl;
call1(Bob);
cout << "Bob: ";
Bob.showCow();
cout << endl;
call2(Alice);
cout << "Alice: ";
Alice.showCow();
cout << endl;
cout << "Initialize one object to another" << endl;
Cow Aim = Tom;
cout << "Aim: ";
Aim.showCow();
cout << endl;
cout << "Assign one object to another" << endl;
Cow Wu;
Wu = Bob;
cout << "Wu: ";
Wu.showCow();
}

我们可以发现明显的两个问题:
- 在创建Aim时,没有调用我们编写的构造函数和默认构造函数。
- 当调用call2函数时出现了一次未知的析构函数调用,使得再次访问Alice时出现了乱码。
出现问题1的原因:
Cow Aim = Tom;
上述句将转化为:
Cow Aim = Cow(Tom);
对应的构造函数形式为Cow(const Cow&) -- 这就是复制构造函数。
当我们没有声明复制构造函数时,C++将会提供默认的复制构造函数
出现问题2的原因:
当我们使用值传递,传递Alice对象时,会先调用复制构造函数,创建临时对象副本,当函数执行完毕后将释放此副本。
在上述类实现中,我们没有定义复制构造函数,所以这里将使用默认的复制构造函数。默认复制构造函数逐个复制非静态成员(成员赋值也叫浅复制),复制的是成员的值。
这里浅复制就出现了问题,临时对象的name与Alice.name值相同,是一个地址。当call2函数执行完成,释放临时对象时,调用临时对象的析构函数,释放name指向的内存,导致再次查看Alice时出现了乱码。
所以解决上述两个问题的方法为创建一个复制构造函数,进行深复制。复制构造函数如下:
class Cow
{
private:
static int num_cows;
char* name;
double weight;
public:
...
Cow(const Cow& c);
...
};
Cow::Cow(const Cow& c) {
int len;
len = strlen(c.name);
name = new char[len + 1];
strcpy_s(name, len + 1, c.name);
weight = c.weight;
num_cows++;
cout << num_cows << ": \"" << name << "\" created" << endl;
}
赋值符(=)重载
解决复制构造函数问题后,我们再次对上述类进行测试:

这次类对象的创建与析构数量已经对应上了,值传递也未出现问题。但在最后释放对象时还是出现了问题。
按照最先创建的对象,最后释放的原则,可以发现Wu和Bob释放了同一块内存导致程序出现错误。
产生该问题的原因:
Cow Wu;
Wu = Bob;
C++中类的赋值时通过自动重载赋值符=
实现的,出现该问题的原因与默认复制构造函数一样,使得Wu.name = Bob.name(name为char类型指针)。在调用析构函数释时,这两个对象释放了同一块内存。
解决此问题需要重载赋值运算符=
:
class Cow
{
private:
static int num_cows;
char* name;
double weight;
public:
...
Cow& operator=(const Cow& c);
...
};
Cow& Cow::operator=(const Cow& c) {
int len;
if (this == &c) {
return *this;
}
delete[] name;
len = strlen(c.name);
name = new char[len + 1];
strcpy_s(name, len + 1, c.name);
weight = c.weight;
return *this;
}

网友评论