`
weihe6666
  • 浏览: 437202 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

构造函数,析构函数和赋值操作符

    博客分类:
  • C++
C++ 
阅读更多
构造函数,析构函数和赋值操作符


几乎所有的对象都需要构造函数、拷贝构造函数、析构函数和赋值构造函数,但问题是何时需要自己定义这些函数,何时调用默认的这些函数?这也是所谓的“浅拷贝”和“深拷贝”的问题。

当类的数据成员有指针,需要动态的为指针申请内存时,这时就需要自己定义拷贝构造函数和赋值构造函数,这就是所谓的“深拷贝”。

当类的数据成员没有指针,不需要动态的为指针申请内存时,不需要定义自己版本的拷贝和赋值构造函数,直接调用默认的即可,这就是所谓的“浅拷贝”。

为何这种情况需要定义自己的拷贝构造函数和赋值构造函数?看下面的例子:
#include <iostream>
using namespace std;

// 一个很简单的TString类
class TString {
public:
	TString(const char *value);
	~TString();
	void Print();
private:
	char *data;
};

TString::TString(const char *value)
{
	if (value) {
		data = new char[strlen(value) + 1];
		strcpy(data, value);
	}
	else {
		data = new char[1];
		*data = '\0';
	}
	cout << "Call Constructor !\n";
}

inline TString::~TString() { delete [] data; cout << "Call Destructor !\n";}

void TString::Print()
{
	cout << "The content is :" << data << endl;
}

void donothing(const TString &localstring) { cout << "Call donothing function!\n";}

int main()
{
  TString a("hello");
  TString b("world");
 //b = a;
    b.Print();
    a.Print();
}

输出为:
Call Constructor !
Call Constructor !
The content is :world
The content is :hello
Call Destructor !
Call Destructor !

但是如果定义:b = a; 就会提示错误,为何呢?类本身不是提供默认的赋值构造函数吗,为何还会出错?

如果b = a;而没有定义自己的赋值构造函数,TString b("world");为b对象申请的内存段将丢失,也即没有任何指针指向这个内存段,这就是所谓的内存泄露。而b = a;会通过默认的赋值构造函数直接把指针a所指向的地址赋值给b,那么b和a指向同一段地址,当a和b离开生存空间时会调用类的析构函数来删除申请的地址,比如当b删除了b.data所指向的地址后,a对象也会调用析构函数删除a.data指向的地址,但此地址已经被b对象删除,用delete删除一个已经被删除的指针,其结果是不可预料的。

这就是为什么当类的数据成员是指针时,需要字定义拷贝构造函数和赋值构造函数。

又如:
TString s = "the truth is out there";
  donothing(s);
如果没有定义自己的拷贝构造函数,会出现同样的问题,说明如下:

一切好象都很正常。但因为被传递的localstring是一个值,它必须从s通过(缺省)拷贝构造函数进行初始化。于是localstring拥有了一个s内的指针的拷贝。当donothing结束运行时,localstring离开了其生存空间,调用析构函数。其结果也将是:s包含一个指向localstring早已删除的内存的指针。

解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 m29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。

在看下面的例子:
#include <iostream>
using namespace std;

class CSomething
{
public:
	int a;
	int b;
public:
	CSomething(int a, int b)
	{
		cout << "Call CSomething Constructor !" << endl;
		this->a = a;  this->b = b;
	}
	~CSomething()
	{
		cout << "Call the CSomething Destructor!" << endl;
	}
};

class CA
{
private:
	CSomething* sth;              // 以指针形式存在的成员变量
public:

	CA(CSomething* sth)
	{
		cout << "Call CA Constructor!" << endl;
		this->sth = new CSomething(sth->a, sth->b);
	}
	~CA()
	{
		cout << "In the destructor of class CA..." << endl;
		if (NULL != sth) delete sth;
	}
	void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;}
	void setValue(int a, int b){sth->a = a; sth->b = b;}
	void getSthAddress()
	{
		cout << sth << endl;
	}
};

int main(void)
{
	CSomething sth(1, 2);
	CA ca(&sth);
	ca.Show();
	CA cb(ca);                                      // 调用缺省的隐式拷贝构造函数
	cb.Show();
	cb.setValue(2, 3);
	ca.Show();
	cb.Show();

	ca.getSthAddress();
	cb.getSthAddress();

	return 0;

}

没有显示定义拷贝构造函数,其结果输出如下:
Call CSomething Constructor !
Call CA Constructor!
Call CSomething Constructor !
(1, 2)
(1, 2)
(2, 3)
(2, 3)
00396560
00396560
In the destructor of class CA...
Call the CSomething Destructor!
In the destructor of class CA...
Call the CSomething Destructor!

由输出结果可以看出:ca和cb的sth是同一个。
运行时会出错,其出错原因时程序结束时调用析构函数delete sth,由于ca.sth地址:00396560 和cb.sth地址:00396560相同,当ca.sth删除掉后,cb.sth再删除时就会出现问题。


下面是加上CA的拷贝构造函数:
	CA( const CA & ob)
	{
		sth = new CSomething((ob.sth)->a,(ob.sth)->b);
		cout << "Call the Copy Constructor!" << endl;
	}


其输出为:
Call CSomething Constructor !
Call CA Constructor!
Call CSomething Constructor !
(1, 2)
Call CSomething Constructor !
Call the Copy Constructor!
(1, 2)
(1, 2)
(2, 3)
00396560
00396610
In the destructor of class CA...
Call the CSomething Destructor!
In the destructor of class CA...
Call the CSomething Destructor!
Call the CSomething Destructor!

由输出结果:
修改值后的ca.sth->a ca.sht->b
(1, 2)
修改值后的cb.sth->a cb.sht->b
(2, 3)
ca.sth:00396560
cb.sth00396610

可以看出达到了”深拷贝“的效果。
分享到:
评论

相关推荐

    C++构造函数_析构函数和赋值操作符学习小结

    C++构造函数、析构函数和赋值操作符学习小结 C++ 构造函数、析构函数和赋值操作符是 C++ 编程语言中的基本组件,它们提供了对象的初始化、销毁和赋值操作。这些函数的正确性对整个类的正确性至关重要。 构造函数 ...

    C++构造函数析构函数

    **析构函数**是与构造函数相对的,它在对象生命周期结束时,即对象销毁前被调用,用于清理对象可能占用的资源。虽然在提供的内容中没有直接提到析构函数,但在实际编程中,析构函数通常用来释放动态分配的内存或其他...

    string 类实现,构造函数、析构函数、操作符重载等

    通过以上分析,我们可以看到`String`类的设计和实现涵盖了构造函数、析构函数以及多种操作符重载等功能,使得该类能够有效地处理字符串数据。对于学习C++语言的新手来说,这是一个很好的实践项目,可以帮助他们更好...

    深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结

    而当调用函数中有一个接受对象时,就将返回对象赋值给接收对象,这个返回对象在调用函数结束时调用析构函数。3. 当类有一个带有一个参数的构造函数时,可以用这个参数同类型的数据初始化这个对象,默认会调用这个...

    构造函数和复制构造函数

    在上面的代码中,我们定义了一个名为classA的类,其中包括一个默认构造函数、一个带参数的构造函数、一个复制构造函数、一个赋值操作符和一个析构函数。在main函数中,我们使用了多种方式来调用这些函数,例如语句1...

    C++程序设计课件:3 构造函数、析构函数.ppt

    总之,构造函数、析构函数、拷贝构造函数、初始化列表、静态成员、常量、友元和运算符重载等是C++中构建复杂类结构的关键要素,理解并熟练掌握这些知识点对于编写高效、健壮的C++代码至关重要。

    析构或构造函数声明为保护或私有成员

    在上面的代码中,类 A 的拷贝构造函数和 operator=(赋值操作符重载)被声明为私有成员,以禁止外部用户对对象进行复制动作。 将构造函数和析构函数声明为私有或保护成员是一种常用的设计模式,可以用于实现 ...

    C++中复制构造函数和重载赋值操作符总结

    我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数、析构函数、复制构造函数和重载赋值操作;即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数。例如以下类: 代码如下: class CTest { ...

    编写类String的构造函数

    通过以上实现,我们可以看到构造函数、析构函数、拷贝构造函数和赋值操作符是管理动态内存的关键成员函数。它们不仅确保了内存的正确分配和释放,还保证了对象状态的一致性和安全性。对于开发人员来说,理解并正确...

    C++ 赋值构造函数注意点介绍

    初始化和赋值问题详解C++ 拷贝构造函数和赋值运算符详解C++中对构造函数和赋值运算符的复制和移动操作C++中复制构造函数和重载赋值操作符总结深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结...

    c++构造函数和析构函数,拷贝,重载

    根据给定的文件标题“C++构造函数和析构函数,拷贝,重载”以及描述“一道很好的习题代码,包含构造、析构、拷贝、重载”,我们可以了解到这段代码主要涉及到了C++中类的设计与实现,特别是构造函数、析构函数、拷贝...

    c++初阶 类和对象 中级篇

    本篇文章讲解了C++中类和对象的中级知识,涵盖了类的6个默认成员函数、构造函数、析构函数、拷贝构造函数、赋值运算符重载、日期类的实现等内容。 一、类的6个默认成员函数 在C++中,如果一个类中什么成员都没有,...

    C++构造函数.pdf

    此外,还提供拷贝赋值操作符重载和析构函数来管理字符串的内存。 对象的创建可以使用new关键字动态分配内存,也可以在栈上直接声明。栈上的对象在声明周期结束时会自动调用析构函数,而动态分配的对象则需要程序员...

    C++规定与类同名的函数就是拷贝构造函数

    实现深拷贝通常需要在拷贝构造函数中手动复制这些资源,并在析构函数中相应地释放它们。这是因为编译器默认提供的浅拷贝构造函数并不知道如何处理这样的情况,因此程序员必须自己处理。 除了拷贝构造函数,课程内容...

    C++实验三.docx

    实验三旨在深入理解C++中的类和对象的概念,特别是构造函数、析构函数、数据成员的访问控制、对象的赋值与复制、以及静态成员的相关知识。实验内容包括以下几个部分: 1. **类的定义与实现**: 首先,我们需要定义...

    典型的String类实现C++

    一个典型的String类实现,C++描述,里面包括String的构造函数,赋值构造函数,析构函数,赋值操作符的实现等

    拷贝构造函数导致指针挂起的研究

    具体表现为两个或多个对象的指针成员变量指向相同的内存地址,当其中一个对象被销毁时,其析构函数会释放这块内存,而其他对象仍然持有指向这块已被释放内存的指针,从而导致指针挂起。 #### 3. 指针挂起的原因分析...

Global site tag (gtag.js) - Google Analytics