`
weihe6666
  • 浏览: 429909 次
  • 性别: 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++ 编写String 的构造函数、拷贝构造函数、析构函数和赋值函数

    详解C++ 编写String 的构造函数、拷贝构造函数、析构函数和赋值函数  编写类String 的构造函数、析构函数和赋值函数,已知类String 的原型为: class String { public: String(const char *str = NULL); // 普通...

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

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

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

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

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

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

    C++中复制构造函数与重载赋值操作符的深入分析

    在C++中复制控制是一个比较重要的话题,主要包括复制构造函数、重载赋值操作符、析构函数这三部分,这三个函数是一致的,如果需要手动定义了其中了一个,那么另外的两个也需要定义,通常在存在指针或者前期相关操作...

    典型的String类实现C++

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

    C++Primer视频(高级)下载地址

    10.13章 复制构造函数和赋值操作符 11.13章 析构函数 12.13章 深复制、浅复制 13.13章 管理指针成员 14.14章 重载操作符的定义 15.14章 重载输入输出操作符 16.14章 重载算术操作符 17.14章 重载关系...

    极客班C++面向对象高级编程测试题(8月8日)1

    《C++面向对象高级编程》测试题为下面的 Rectangle 类实现构造函数,拷贝构造函数,赋值操作符,析构函数。注:希望大家在一周内上传答案到极客班 gith

    effective C++ .txt 格式

    格式 .txt,方便阅读,编辑,打印 01.第一章 从C转向C++.txt 02.第二章 内存管理.txt 03.第三章 构造函数,析构函数和赋值操作符.txt ... 有空多去去 http://meooo.download.csdn.net/ 也许有你喜欢东西和书籍......

    C++之继承和动态内存分配

     这将不需要在派生类中显式的定义析构函数,复制构造函数,赋值操作符。如果在派生类中没有定义析构函数,编译器将定义一个不执行任何操作的默认构造函数,实际上,派生类的默认构造函数总要进行一些操作:执行...

    Effective.C++.中文第二版.50条款doc文档.chm

    第三章 构造函数,析构函数和赋值操作符 条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符 条款12: 尽量使用初始化而不要在构造函数里赋值 条款13: 初始化列表中成员列出的顺序和它们在类中声明...

    C++ 中继承与动态内存分配的详解

    派生类是否需要为显示定义析构函数,复制构造函数和赋值操作符呢? 不需要! 首先,来看是否需要析构函数,如果没有定义析构函数,编译器将定义一个不执行任何操作的默认构造函数。实际上,派生类的默认构造函数...

    C++编译器帮我们做了些什么

     ●赋值操作符,如果没有定义  ●地址操作符 class Stack { private: char *str; public: Stack(); //默认构造函数 ~Stack();//析构函数 Stack (const Stack &); //复制构造函数 Stack (char *str ) /...

    C++语言中std::array的用法小结(神器用法)

    std::array的构造函数、析构函数和赋值操作符都是编译器隐式声明的……这让很多用惯了std::vector这类容器的程序员不习惯,觉得std::array不好用。 但实际上,std::array的威力很可能被低估了。在

    c++智能指针的实现

    对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数减至0,则删除对象),并增加右操作数所指对象的引用计数; 调用析构函数时,减少引用计数(如果引用计数减至0,则删除基础对象); ...

    C++ Primer第四版【中文高清扫描版】.pdf

    5.2 关系操作符和逻辑操作符 131 5.3 位操作符 134 5.3.1 bitset对象或整型值的使用 135 5.3.2 将移位操作符用于IO 137 5.4 赋值操作符 137 5.4.1 赋值操作的右结合性 138 5.4.2 赋值操作具有低优先级 138 5.4.3 ...

    C++智能指针实现

    智能指针是存储指向动态分配(堆)对象指针的类, 用于生存期控制, 能够确保自动正确的销毁动态分配的对象,防止内存泄露。...调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

    C#中类和结构的区别 -- 示例详解

    2.虽然结构的初始化也使用了New 操作符可是结构对象依然分配在堆栈上而不是堆上,如果不使用“新建”(new),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用 2.继承性 结构:不能从另外一个结构...

Global site tag (gtag.js) - Google Analytics