因为派生类是从基类继承而来的,所以包含了基类的一些成员,所以在写派生类的构造函数和复制控制函数时,必须考虑基类的影响。
先说构造函数,派生类的构造函数中,并不直接初始化基类的成员,而是调用基类的构造函数初始化基类的部分:
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(正方体) 1. Rectangle 私有成员: double x1, y1; //左下角的坐标 double x2, y2; //右上角的坐标 公有成员: 带缺省值的构造...
由圆和高多重派生掌握类的继承与派生类关系以及实现方法,理解类的层次结构;...掌握派生类构造函数初始化基类成员和对象成员的方法;理解赋值兼容规则,掌握派生类的复制构造函数和赋值运算符的定义
假设基类使用了动态内存分配,而且定义了析构函数、复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数、复制构造函数和赋值函数。 当基类和派生类采用动态内存...
第28讲 默认构造函数和复制构造函数 第29讲 析构函数 第30讲 对象数组和对象指针 第31讲 类作用域、对象生命期、const限定 第32讲 静态成员和友元 第33讲 类的继承与派生 第34讲 派生类成员的访问 第35讲 派生类的...
如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将如何影响派生类的实现呢?这取决于派生类的属性,如果派生类也使用动态内存分配,这将如何实现呢?这种大致分为两种情况, 第一种情况:派生类不...
28.15章 派生类的构造函数和析构函数 29.15章 转换与继承 30.15章 友元与继承 31.15章 静态成员与继承 32.15章 纯虚函数与抽象类 33.16章 模板与泛型编程 34.16章 类模板 - 顺序队列 35.16章 类模板 ...
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 名字冲突与继承 ...
例如,如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现呢?这个问题的答案取决于派生类的属性。如果派生类也使用动态内存分配,那么就需要学习几个新的小技巧。下面来看看这两种...
(3)演示构造函数、复制构造函数、析构函数的作用和调用顺序 (4)利用MFC类库在图形用户界面上绘制及修改这些图形,采用对话框输入输出图形的基本属性 (5)用文件保存图形信息,且能够从文件中读取并作相应处理。
20. 派生类的构造函数和析构函数的构造规则 7 21. 虚函数及其作用 7 22. 静态关联和动态关联 7 23. 函数重载与虚函数的不同 7 24. 虚析构函数 8 25. 纯虚函数 8 26. 抽象类 8 27. 抽象类与接口的区别 8 28. 32.输入...
23.构造函数: 7 24.析构函数: 8 25.复制类的对象: 8 26.初始化和赋值的区别: 8 27.友元(friend): 8 28.常量成员和常量函数: 9 29.类型转换函数: 9 30.内联函数(inline): 9 31.提前不完全声明: 9 ...
(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...
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 ...
(1)从Base类派生圆类(Circle)、正方形类(Square),圆类新增数据成员半径(radius),正方形类新增数据成员边长(a),圆类和正方形类都有构造函数,修改、显示数据成员值的函数,求面积函数。 (2)写出main( )...
3.4.3 用getchar和putchar函数进行字符的输入和输出 3.4.4 用scanf和printf函数进行输入和输出 3.5 编写顺序结构的程序 3.6 关系运算和逻辑运算 3.6.1 关系运算和关系表达式 3.6.2 逻辑常量和逻辑变量 3.6.3 逻辑...
3.4.3 用getchar和putchar函数进行字符的输入和输出 3.4.4 用scanf和printf函数进行输入和输出 3.5 编写顺序结构的程序 3.6 关系运算和逻辑运算 3.6.1 关系运算和关系表达式 3.6.2 逻辑常量和逻辑变量 3.6.3 逻辑...
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 ...
⑴设计一个用于人事管理的“People(人员类)”基类。考虑到通用性,仅抽象出各类人员都具有的属性:编号、姓名、性别、出生日期(Date对象)、身份证号等; ⑵从People(人员类)派生出Student(学生...有复制构造函数
10.3.4 构造函数和new运算符 10.3.5 再谈默认构造函数 10.4.析构函数和delete运算符 10.4..1 默认析构函数 10.4.2 调用构造函数进行类型转换 10.5 浅层复制构造函数 10.6 深层复制构造函数 第11章 运算符重载 11.1 ...