第15章 面向对象程序设计 03<构造函数与拷贝控制、容器、文本查询再探>

目录

一  构造函数与拷贝控制

     1 虚析构函数

二 :合成拷贝控制与继承

三:派生类的拷贝构造


一  构造函数与拷贝控制

     1 虚析构函数

继承关系对基类拷贝控制最直接的影响是基类通常定义一个虚析构函数,这样我们就能动态分配继承体系中的对象。

和其他虚函数一样,析构函数的虚函数也会被继承。因此,无论Quote的派生类使用合成的虚构函数还是定义自己的析构函数,都将是虚析构函数。只要基类的析构函数是虚函数,就能确保当我们delete基类指针时将运行正确的析构函数。如果基类的析构函数不是虚函数,则析构一个指向派生类对象的积累指针将产生未定义的行为。

例如:

class Quote{
public:
//如果我们删除的是一个指向派生类对象的基类指针,则需要虚析构函数
    virtual ~Quote()=default;  //动态绑定析构函数
};


Quote *itemp=new Quote; //静态类型与动态类型一致
delete itemp;            //调用Quote的析构函数
itemp =new Bulk_quote;    //静态类型与动态类型不一致
delete itemp;            //调用Bulk_quote的析构函数

二 :合成拷贝控制与继承

三:派生类的拷贝构造

语法如下 :基类A .派生类B;派生类拷贝构造 语法 B(const B & b):A(b)

demo.h

//文件 demo.h

#include <iostream>
using namespace std;
#include <string>

class Student
{
public:
    Student(string sn = "wangwu", char cs = 's'
    , float fs = 60)
        :name_(sn), sex_(cs), score_(fs) 
    {}
    
    void dis()
    {
        cout << "name_:" << name_ << endl;
        cout << "sex_:" << sex_ << endl;
        cout << "score_:" << score_ << endl;
    }

    Student(const Student& another)
    {
        this->name_ = another.name_;
        this->score_ = another.score_;
        this->sex_ = another.sex_;
    }
    
private:
    string name_;
    char sex_;
    float score_;
};

class Graduate : public Student
{
public:
    Graduate(string sn,char cs,float fs,float fsa)
        :Student(sn,cs,fs)
    {
        salary_ = fsa;
    }

    void dump()
    {
        dis();
        cout << "salary:" << salary_ << endl;
    }

    Graduate(const Graduate & another)
    {
        this->salary_ = another.salary_;
    }
    
private:  
    float salary_;
};

拷贝构造顺序.h

#include <iostream>
using namespace std;


/*
问题一:
 类C 是否存在自实现拷贝构造器 和 子类(E)的拷贝构造不含内嵌子对象的显示调用(结果是否有区别  可以试验)

 结果如下 :
存在内嵌子对象的时候,如果子类的拷贝构造没有自实现是默认拷贝构造,那么在调用内嵌子对象类的时候默认调用内嵌类的拷贝构造器。

如果子对象自实现了拷贝构造器但是没有显示的调用内嵌子类的拷贝构造器,那么会直接调用内嵌子类的构造器。

如果子类的拷贝构造器自实现,那么如果显示的调用内嵌子类的拷贝构造器,那就直接调用内嵌子类的拷贝构造器。

*/
class C
{
public:

	C(int y = 10)
	{
		c = y;
		cout << "C()" << endl;
	}
	C(const C & another){ //自实现拷贝构造器
		this->c = another.c;
		cout << "C(const C & another)" << endl;
	}
	int c;
};

class D
{
public:
	D(int x = 22)
	{
		d = x;
		cout << "D()" << endl;
	}

	D(const D & another)
	{
		this->d = another.d;
		cout << "D(const D & another)" << endl;
	}
	int d;
};

class E: public D
{
public:
	E(int x,int y,int z)
		:D(x),c(z)
	{
		e = y;
		cout << "E()" << endl;
	}

	//E(const E & another)
	//	:D(another)   //在这里显示的调用父类的拷贝构造器
	//{
	//	this->e = another.e;
	//	cout << "E(const E & another)" << endl;
	//}
	E(const E & another)
		:D(another),c(another.c)   //在这里显示的调用父类的拷贝构造器 和内嵌子对象的拷贝构造
	{
		this->e = another.e;
		cout << "E(const E & another)" << endl;
	}
	C c;
	int e;
};

main.cpp

#include <iostream>
using namespace std;
#include "demo.h"
#include "拷贝构造顺序.h"
/*  前提知识点
一般情况下,当类中成员有指针变量、类中有动态内存分配时常常需要用户自己定义拷贝构造函数。

在什么情况下系统会调用拷贝构造函数:(三种情况)

(1)用类的一个对象去初始化另一个对象时

(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用

(3)当函数的返回值是类的对象或引用时
*/

class A
{
public:
	A(int x = 22)
	{
		a=x;
		cout << "A()" << endl;
	}

	A(const A & another)	//自实现拷贝构造器
	{
		this->a = another.a;
		cout << "A(const A & another)" << endl;
	}
	int a;

};

class B : public A
{
public:
	B(int z,int y):A(z)
	{
		b=y;
		cout << "B()" << endl;
	}
	B(const B & another)	//自实现拷贝构造器
	{
		this->b = another.b;
		cout << "B(const B & another)" << endl;
	}
/* method no.2
	B(const B & another)
		:A(another)   //在这里显示的调用父类的拷贝构造器
	{
		this->b = another.b;
		cout << "B(const B & another)" << endl;
	}
*/
	int b;
};


int main()
{
	B b(666,100);
	B bb(b);		//调用系统默认提供的拷贝构造器 


	cout <<"bb.a="<<bb.a<<endl;
	cout <<"bb.b="<<bb.b<<endl;
	cout << ""<<endl;
/*问题一 :解决派生类的拷贝构造问题

1: 父类和子类都是默认拷贝构造函数时:
cout "A()"  "B()"

2:父类和子类的拷贝构造都自实现拷贝构造 (在类A和类B完成构造之后,会继续调用A的构造,然后调用B的拷贝构造,整个过程没有调用A的拷贝构造。)
cout "A()"  "B()" "A()" ""B(const B & another)"

3在父类自实现拷贝构造,子类使用默认的拷贝构造 
(子类没有自实现拷贝构造的时候,会调用父类的拷贝构造,而不会去调用父类的构造器,然后默认调用子类的拷贝构造。)
cout "A()"  "B()" "A(const A & another)"

4:在父类使用默认的拷贝构造,子类使用自实现的拷贝构造
cout "A()"  "B()" "A()" "B(const B & another)"

5:实例总结
B b(666,100);
cout "A()"  "B()" "A()" "B(const B & another)" "bb.a=22"  "bb.b=100" 
此时 666没有拷贝过来 没有实际意义
我们可以看到,我们给b对象传递的参数是666和100但是经过拷贝构造之后到bb对象的时候就直接变成了22和100
我们通过代码进行分析就可以看出来,在进行完父类A和子类B的构造之后,子类B调用父类A的构造器,并没有调用父类A的拷贝构造,
然后执行B类的拷贝构造,所以最终的结果并没有实现父类的拷贝构造。
那么这个时候的拷贝构造就是没有意义的,因为拷贝出来的对象和被拷贝的对象不一样。


引例总结  

当子类不自实现拷贝构造的时候会默认调用父类的拷贝构造。

如果子类自实现拷贝构造不作特殊处理此时只会调用父类的构造器。不会调用父类的拷贝构造器,这种情况就失去了拷贝的意义。
*/


//解决方式如下
/*
1:
method no.01:
采用上述第一种方式(全部使用默认拷贝构造)  
cout "A()"  "B()" "bb.a=600"  "bb.b=100"
:但是上面的方法系统默认提供的拷贝构造只能用于浅拷贝。那么如果涉及深拷贝的时候父类和子类都是默认的拷贝构造将不能实现派生类的拷贝构造。
https://blog.csdn.net/caoshangpa/article/details/79226270
浅拷贝只是对指针的拷贝,浅拷贝后两个指针指向同一个内存空间;
深拷贝不仅对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
当对一个已知对象进行拷贝时,编译系统会自动调用一种构造函数——拷贝构造函数,如果用户未定义拷贝构造函数,则会调用默认拷贝构造函数。
当拷贝一个基类指针到派生类时,如果调用系统默认的拷贝构造函数,这时只是对指针进行拷贝,两个指针指向同一个地址,这就会导致指针被分配了一次内存,但内存被释放了两次(两次调用析构函数),造成程序崩溃。所以在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。

2
:method no.02: 派生类固定语法:派生类对拷贝构造固定语法
B(const B & another)
		:A(another)   //在这里显示的调用父类的拷贝构造器
	{
		this->b = another.b;
		cout << "B(const B & another)" << endl;
	}
cout "A()"  "B()" "bb.a=600" "A(const A & another)" "B(const B & another)" "bb.b=100"
						
通过显式的调用父类的拷贝构造器,那么我们就实现了派生类的拷贝构造。上面代码在子类中:A(another) //在这里显示的调用父类的拷贝构造器
的another是子类B类型,但是赋值给了A类型的父类。
子类对象赋值给父类的引用这种现象叫赋值兼容。
语法

派生类::派生类(const 派生类& another)
	:基类(another),派生类新成员(another.新成员)
{
	//派生类新成员(another.新成员)
}

*/



    cout <<"demo 实例输出如下***********************!!!"<<endl;
    cout<<"父类和子类都自实现拷贝构造,子类中的拷贝构造没有进行特殊处理,那么就会出现子类的                             拷贝构造调用父类的构造器。"<<endl;
    Graduate g("zhaosi", 's', 99, 1000);
    g.dump();
    cout << "拷贝对象结果如下======================" << endl;
    Graduate gg(g);
    gg.dump();

/* cout 如下
demo 实例输出如下***********************!!!
父类和子类都自实现拷贝构造,子类中的拷贝构造没有进行特殊处理,那么就会出现子类的拷贝构造调用父类的构造器。
name_:zhaosi
sex_:s
score_:99
salary:1000
拷贝对象结果如下======================
name_:wangwu
sex_:s
score_:60
salary:1000
*/



//当存在子类存在内嵌子对象时  他的构造顺序以及 拷贝顺序 
	/*
	存在内嵌子对象的时候,
	1:如果子类的拷贝构造没有自实现是默认拷贝构造,那么在调用内嵌子对象类的时候默认调用内嵌类的拷贝构造器。

	2:如果子对象自实现了拷贝构造器但是没有显示的调用内嵌子类的拷贝构造器,那么会直接调用内嵌子类的构造器。

	3:如果子类的拷贝构造器自实现,那么如果显示的调用内嵌子类的拷贝构造器,那就直接调用内嵌子类的拷贝构造器。
	
	在构造的过程中先实现父类的构造,再实现内嵌子对象的构造,再实现子类构造。(1,2,3都是如此)
	那么在拷贝构造的过程中先实现父类的拷贝构造,再实现内嵌子对象的拷贝构造,
	但是由于子类的拷贝构造自实现,显式调用只调用了父类的拷贝构造和显式调用内嵌子对象的拷贝构造,所以会直接调用内嵌子对象的拷贝构造,之后再调用子类的拷贝构造。(3)
	
	 拷贝构造顺序.h  */
		
	cout << ""<<endl;
	cout <<"拷贝构造顺序 子类显示调用内嵌子类的构造器 实例输出如下***********************!!!"<<endl;
	E e(666, 100, 200);
	E ee(e);
	cout << ee.d << endl;
	cout << ee.e << endl;
	cout << ee.c.c << endl;
	/*情况二:子类显式调用内嵌子类的构造器的情况:
	结果如下:
	拷贝构造顺序 子类显示调用内嵌子类的构造器 实例输出如下***********************!!!
	D()
	C()
	E()
	D(const D & another)
	C(const C & another)
	E(const E & another)
	666
	100
	200
	*/
	/*
	在构造的过程中先实现父类的构造,再实现内嵌子对象的构造,再实现子类构造。

	那么在拷贝构造的过程中先实现父类的拷贝构造,再实现内嵌子对象的拷贝构造,
	但是由于子类的拷贝构造自实现,显式调用只调用了父类的拷贝构造和显式调用内嵌子对象的拷贝构造,所以会直接调用内嵌子对象的拷贝构造,之后再调用子类的拷贝构造。
*/
	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值