`
java-mans
  • 浏览: 11415533 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

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

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

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.用派生类初始化基类时,派生类将转化为基类,然后调用基类的复制构造函数处理。



分享到:
评论

相关推荐

    按以下描述和要求建立两个类:基类 Rectangle(矩形类) 和派生类 Cube(正方体)

    按以下描述和要求建立两个类:基类 Rectangle(矩形类) 和派生类 Cube(正方体) 1. Rectangle 私有成员:  double x1, y1; //左下角的坐标  double x2, y2; //右上角的坐标 公有成员:  带缺省值的构造...

    类的继承与多态性

    由圆和高多重派生掌握类的继承与派生类关系以及实现方法,理解类的层次结构;...掌握派生类构造函数初始化基类成员和对象成员的方法;理解赋值兼容规则,掌握派生类的复制构造函数和赋值运算符的定义

    详谈C++何时需要定义赋值/复制构造函数

    假设基类使用了动态内存分配,而且定义了析构函数、复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数、复制构造函数和赋值函数。 当基类和派生类采用动态内存...

    中国大学MOOC西工大C++课程PPT

    第28讲 默认构造函数和复制构造函数 第29讲 析构函数 第30讲 对象数组和对象指针 第31讲 类作用域、对象生命期、const限定 第32讲 静态成员和友元 第33讲 类的继承与派生 第34讲 派生类成员的访问 第35讲 派生类的...

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

    如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将如何影响派生类的实现呢?这取决于派生类的属性,如果派生类也使用动态内存分配,这将如何实现呢?这种大致分为两种情况,  第一种情况:派生类不...

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

    28.15章 派生类的构造函数和析构函数 29.15章 转换与继承 30.15章 友元与继承 31.15章 静态成员与继承 32.15章 纯虚函数与抽象类 33.16章 模板与泛型编程 34.16章 类模板 - 顺序队列 35.16章 类模板 ...

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

    15.4.2 派生类构造函数 490 15.4.3 复制控制和继承 494 15.4.4 虚析构函数 495 15.4.5 构造函数和析构函数中的虚函数 497 15.5 继承情况下的类作用域 497 15.5.1 名字查找在编译时发生 498 15.5.2 名字冲突与继承 ...

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

    例如,如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现呢?这个问题的答案取决于派生类的属性。如果派生类也使用动态内存分配,那么就需要学习几个新的小技巧。下面来看看这两种...

    小型图形处理系统(VC++ GDI)

    (3)演示构造函数、复制构造函数、析构函数的作用和调用顺序 (4)利用MFC类库在图形用户界面上绘制及修改这些图形,采用对话框输入输出图形的基本属性 (5)用文件保存图形信息,且能够从文件中读取并作相应处理。

    C++的概念/解释,可打印,华南师范大学C++考过。

    20. 派生类的构造函数和析构函数的构造规则 7 21. 虚函数及其作用 7 22. 静态关联和动态关联 7 23. 函数重载与虚函数的不同 7 24. 虚析构函数 8 25. 纯虚函数 8 26. 抽象类 8 27. 抽象类与接口的区别 8 28. 32.输入...

    自编自用的c++语法快速参考

    23.构造函数: 7 24.析构函数: 8 25.复制类的对象: 8 26.初始化和赋值的区别: 8 27.友元(friend): 8 28.常量成员和常量函数: 9 29.类型转换函数: 9 30.内联函数(inline): 9 31.提前不完全声明: 9 ...

    C++复习资料之系列

    (a) 3和3 (b) 3和5 (c) 5和3 (d) 5和5 18. 在下列运算符中,( d )优先级最高。 (a) (b)*= (c)+ (d)* 19. 在下列运算符中,( d )优先级最低。 (a) ! (b)&& (c)!= (d)?: 20.设i=1...

    C++入门指南-v2.4.pdf

    4.5 基类和派生类的构造函数 82 4.6 基类和派生类的析构函数 83 4.7 多继承 85 4.8 虚继承和虚基类 88 五、C++多态与抽象类 91 5.1 多态概念介绍 91 5.2 虚函数 92 5.3 纯虚函数和抽象类 95 六、C++运算符重载 97 ...

    面向对象与C++试题.doc

    (1)从Base类派生圆类(Circle)、正方形类(Square),圆类新增数据成员半径(radius),正方形类新增数据成员边长(a),圆类和正方形类都有构造函数,修改、显示数据成员值的函数,求面积函数。 (2)写出main( )...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    3.4.3 用getchar和putchar函数进行字符的输入和输出 3.4.4 用scanf和printf函数进行输入和输出 3.5 编写顺序结构的程序 3.6 关系运算和逻辑运算 3.6.1 关系运算和关系表达式 3.6.2 逻辑常量和逻辑变量 3.6.3 逻辑...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    3.4.3 用getchar和putchar函数进行字符的输入和输出 3.4.4 用scanf和printf函数进行输入和输出 3.5 编写顺序结构的程序 3.6 关系运算和逻辑运算 3.6.1 关系运算和关系表达式 3.6.2 逻辑常量和逻辑变量 3.6.3 逻辑...

    Absolute C++中文版(原书第2版)-完美的C++教程,文档中还包含英文版

    14.2.1 派生类中的赋值操作符和复制 构造函数 426 14.2.2 派生类的析构函数 426 14.2.3 保护继承和私有继承 436 14.2.4 多继承 437 第15章 多态与虚函数 442 15.1 虚函数基础 442 15.1.1 后绑定 442 15.1.2 ...

    C++ 人事管理系统(实验)

    ⑴设计一个用于人事管理的“People(人员类)”基类。考虑到通用性,仅抽象出各类人员都具有的属性:编号、姓名、性别、出生日期(Date对象)、身份证号等; ⑵从People(人员类)派生出Student(学生...有复制构造函数

    零起点学通C++多媒体范例教学代码

    10.3.4 构造函数和new运算符 10.3.5 再谈默认构造函数 10.4.析构函数和delete运算符 10.4..1 默认析构函数 10.4.2 调用构造函数进行类型转换 10.5 浅层复制构造函数 10.6 深层复制构造函数 第11章 运算符重载 11.1 ...

Global site tag (gtag.js) - Google Analytics