派生类的构造函数和复制控制

因为派生类是从基类继承而来的,所以包含了基类的一些成员,所以在写派生类的构造函数和复制控制函数时,必须考虑基类的影响。

先说构造函数,派生类的构造函数中,并不直接初始化基类的成员,而是调用基类的构造函数初始化基类的部分:

class Item_base
{
public:
	//构造函数
	Item_base(const std::string &book = "",double sales_price = 0.0):
		isbn(book),price(sales_price){ std::cout<<"基类构造函数"<<std::endl;}
	//返回isbn号
	std::string book(){return isbn;}
	//基类不需要折扣策略
	virtual double net_price(std::size_t n)const{return n * price;}
	//析构函数
	virtual ~Item_base(){std::cout<<"基类析构函数"<<std::endl;};
	//复制控制函数
	Item_base (const Item_base&);
	//赋值操作
	Item_base& operator=(const Item_base&);

private:
	std::string isbn;
protected:
	double price;
};


class Bulk_item:public Item_base
{
public:
	//构造函数
	Bulk_item(const std::string& book = "",double sales_price = 0.0,std::size_t qty = 0,double disc_rate = 0.0):
		Item_base(book,sales_price),quantity(qty),discount(disc_rate){ std::cout<<"派生类构造函数"<<std::endl;}
	~Bulk_item(){std::cout<<"派生类析构函数"<<std::endl;}
	double net_price(std::size_t)const;
	//复制控制
	Bulk_item (const Bulk_item&);
	//赋值操作符
	Bulk_item& operator=(const Bulk_item&);

private:
	std::size_t quantity;
	double discount;
};

而且每次都是先初始化基类部分,在初始化派生类的成员。还有·一点需要注意,就是派生类只能初始化自己的基类,并不需要考虑基类的基类怎么初始化。

对于复制控制函数,我们一个一个看:

先说复制构造函数,它的作用是规定当用一个派生类对象复制给另一个派生类对象时,会发生什么。它需要完成的工作也是两部分:调用基类的复制构造函数完成基类部分的复制,然后再复制派生类的部分。

//基类复制控制
Item_base::Item_base(const Item_base& ib)
{
	isbn = ib.isbn;
	price = ib.price;
	std::cout<<"基类复制构造函数"<<std::endl;
	
}


//派生类复制构造函数
Bulk_item::Bulk_item (const Bulk_item& b)
{
	Item_base::Item_base(b);
	quantity = b.quantity;
	discount = b.discount;
	std::cout<<"派生类复制构造函数"<<std::endl;
}


注意一下作用域操作符:如果不加作用域标示符,会提示你形参b重定义。因为Item_base(b)相当于新建了一个Item_base的对象b,所以是重定义。使用作用域操作符以后,等于显示调用了基类的复制构造函数Item_base,用派生类直接给基类复制,所以不会出现重定义。

同理还有赋值操作符:

//基类的赋值操作
Item_base& Item_base::operator=(const Item_base& rhs)
{
	isbn = rhs.isbn;
	price = rhs.price;
	std::cout<<"基类赋值操作符"<<std::endl;
	return *this;
}
//赋值操作符
Bulk_item& Bulk_item::operator=(const Bulk_item& rhs)
{
	if(this != &rhs)
		Item_base::operator=(rhs);
	quantity = rhs.quantity;
	discount = rhs.discount;
	std::cout<<"派生类赋值操作符"<<std::endl;
	return *this;	
}


这里要注意的是,我们增加了左右操作数是否相等的判断,只有在不等时,才调用基类的操作。

在析构函数中,我们什么也没有做,却把它定义成了虚函数,这是为什么呢?

通过前面的学习我们知道,如果一个类中有指针成员,那么应该在这个类的析构函数中删除指针成员。我们又知道:由于动态绑定的缘故,我们完全可以让一个静态类型为基类的指针指向一个派生类的对象。当通过基类的指针去删除派 生类的对象,而基类又没有虚析构函数时,会出现问题,举一个例子:

class A
{
public:
	A()
	{
		ptr = new int[10];
		cout<<"A构造函数"<<endl;
	}
	virtual ~A()
	{
		delete ptr;
		cout<<"A析构函数"<<endl;
	}
private:
	int *ptr;
};

class B:public A
{
public:
	B()
	{
		ptr = new long[10];
		cout<<"B构造函数"<<endl;
	}
	~B()
	{
		delete ptr;
		cout<<"B析构函数"<<endl;
	}
private:
	long *ptr;


};

int main()
{
    A *a=new B();
    delete a;
    return 0;
}


程序运行依次打印:A构造函数,B构造函数,A析构函数。

可以看出,B构造函数中创建的指针并没有删除,这是因为,当执行delete a时,由于a的静态类型是指向A类的指针,所以会调用A的析构函数删除A构造函数中新建的对象。当我们把A类构造函数设定为虚函数时,由于虚函数是动态绑定的,所以会调用派生类的析构函数,而在派生类的析构函数中,调用基类的析构函数。这样,就能完全删除对象了。

这里还要强调下一构造函数与析构函数的执行顺序:建立一个派生类时,会调用派生类构造函数,然后在这个构造函数中(显示或者隐式的)调用基类构造函数,然后再初始化派生类的其他部分;析构函数中先调用派生类析构函数,先析构派生类自己的成员,最后在这个析构函数中隐式的调用基类的析构函数析构基类的成员。

最后的一点内容是对这一小节内容的总结以及上一小结的补充:

上一小节中并没有给出相关内容的说明程序,下面通过测试程序来具体说明:

首先是定义了3个函数:

void func1(Item_base obj){}
void func2(Item_base& obj){}
Item_base func3()
{
	Item_base obj;
	return obj;
}


这三个函数看起来并没有做什么实质性的工作,但他们很有代表新:前两个接受的分别是基类的对象和基类对象的引用,第三个的返回值是基类的对象,下面我们看看主程序:


int main()
{
	
	Item_base iobj;					//调用基类构造函数,整个函数结束时释放
	
	func1(iobj);					//调用基类构造函数创建一个临时对象,整个函数释放
	
	func2(iobj);					//使用引用时,并不创建对象
	
	iobj = func3();					//新建一个Item_base对象时调用基类构造函数
									//函数返回时调用复制构造函数
									//然后调用基类析构函数撤销局部对象
									//然后调用基类赋值操作符
									//整个函数结束时释放

	
	Item_base *p = new Item_base;	//调用基类构造函数创建Item_base对象
	delete p;						//删除指针时调用析构函数
	
	Bulk_item bobj;					//因为构造函数先执行列表,后执行函数体
									//打印先执行基类构造函数,然后打印构造派生类
	
	func1(bobj);					//由于func1函数的形参是基类,所以先将派生类隐式转化成基类对象
									//然后调用基类复制构造函数将实参传给形参
									//调用基类析构函数撤销对象
	
	func2(bobj);					//引用不存在临时对象的创建
	
	Bulk_item *q = new Bulk_item;	//派生类构造函数会调用基类构造函数
	delete q;						//调用析构函数
	
	Item_base obj(bobj);			//派生类转化为基类,然后调用基类复制构造函数


	return 0;
}


程序的注释部分说明了具体的功能。值得注意的是:

1.如果将用派生类实参调用形参为基类引用的函数,并不会发生派生类到基类的类型转化,因为引用直接绑定到派生类上,对象并没有“类型转化”,只是将派生类的基类部分的地址传递给基类型的引用。

2.如果将派生类实参传递给一个基类形参的函数,先将派生类隐式转化成基类对象,然后调用基类复制构造函数将实参传给形参。

3.用派生类初始化基类时,派生类将转化为基类,然后调用基类的复制构造函数处理。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值