博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
23.C++- 继承的多种方式、显示调用父类构造函数、父子之间的同名函数、virtual虚函数...
阅读量:6719 次
发布时间:2019-06-25

本文共 3358 字,大约阅读时间需要 11 分钟。

 上章链接:  


 

继承方式

继承方式位于定义子类的”:”后面,比如:

class Line : public Object             //继承方式是public{ };

继承方式默认为private

在C++中,继承方式共有3种:

public继承

-指父类的成员(变量和函数)访问级别,在子类中保持不变

private继承

-指父类的成员,在子类中变为private私有成员.

-也就是说子类无法访问父类的所有成员

protected继承

-指父类的public成员 ,在子类中变为protected保护成员,其它成员级别保持不变

如下图所示:

 

 

注意: protected继承只针对子类有效

比如当父类是protected继承时,则子类的子类就无法访问父类的所有成员

一般而言,C++项目只用到public继承

 

显示调用父类构造函数

  • 当我们创建子类对象时,编译器会默认调用父类无参构造函数
  • 若有子类对象,也会默认调用子类对象的无参构造函数。

比如以下代码:

class  StrA{public:          StrA()          {             cout<<"StrA()"<

 编译运行:

StrA()                    //父类无参构造函数StrB(int i):123

 

也可以通过子类构造函数的初始化列表来显示调用

接下来,修改上面子类的StrB(string s)函数,通过初始化列表调用StrA(string s)父类构造函数

改为:

StrB(string s): StrA(s){  cout<<"StrB(int i):"<<

运行打印:

StrA(string s):123StrB(int i):123

 

 

父子间的同名成员和同名函数

  • 子类可以定义父类中的同名成员和同名函数
  • 子类中的成员变量和函数将会隐藏父类的同名成员变量和函数
  • 父类中的同名成员变量和函数依然存在子类中
  • 通过作用域分辨符(::)才可以访问父类中的同名成员变量和函数

比如:

class Parent{public:       int mval;       Parent()       {              mval=1000;       } void add(int i)       {              mval+=i;       } };class Child : public Parent{public:       int mval;        Child()       {              mval=100;       }       void add(int i,int j)       {              mval+=i+j;       }};

 

在main()函数执行:

Child c;       //c. add(10);        //该行会报错,由于子类有add函数,所以编译器会默认在子类里寻找add(int i);       c.Parent::add(10);   //该行正确,执行父类的成员函数       c.add(2,3);       cout<<"Child.mval="<
<

 

打印:

Child.mval=105Parent.mval=1010

从打印结果看到,父类和子类之间的作用域是不同的, 所以执行父类的同名成员变量和函数需要作用域分辨符(::)才行

 

父子间的兼容

以上示例的Parent父类Child子类为例

  • 子类对象可以直接赋值给父类对象使用,比如: Parent p; Child c;   p=c;
  • 子类对象可以初始化父类对象,比如: Parent p1(c);
  • 父类引用可以直接引用子类对象,比如: Parent& p2 =c;    //p2是c对象的别名
  • 父类指针可以直接指向子类对象,比如: Parent* p3=&c;

其实是编译器是将子类对象退化为了父类对象, 从而能通过子类来赋值初始化父类

所以上述的父类对象(包括指针/引用)也只能访问父类中定义的成员.

 

如果父类对象想访问子类的成员,只能通过强制转换,将父类对象转为子类类型

示例1,通过C方式转换:

Child c;Parent* p3=&c;Child *c2 = (Child*)p3;

 

示例2,通过static_cast转换:

Child c;Parent* p3=&c;Child *c2 = (static_cast*)
(p3);

 

 

虚函数

实现多态性,通过指向子类的父类指针或引用,可以访问子类中同名覆盖成员函数

首先参考下面,没有虚函数的示例:

class Parent{    int i; public:           void example()        {            cout<<"class Parent"<
example();}int main(){ Parent t; Child c; print(&t); print(&c); cout<<"SIZEOF Parent:"<
<

运行打印:

class Parentclass ParentSIZEOF Parent:4SIZEOF Child:8

从结果看出,即使example函数指针p指向了Child c,也只能调用父类的example(),无法实现多态性.

 

所以C++引入了虚函数概念,根据指针指向的对象类型,执行不同类的同名覆盖成员函数,实现不同的形态

定义: 在父类成员函数的返回值前面,通过virtual关键字声明,这样便能访问子类中的同名成员函数了

接下来将上个示例的父类成员函数example()改写为虚函数:

virtual void print()        //将父类的成员函数定为虚函数{cout<<"class Parent"<

 

运行打印:

class Parentclass ChildSIZEOF Parent:8SIZEOF Child:12

可以发现,父类和子类的长度都增加了4字节,这4个字节就是用来指向“虚函数表”的指针,编译器便会更据这个指针来执行不同类的虚函数,实现多态性.

 

虚析构函数

-在使用基类指针指向派生类对象时用到

-通过基类析构函数可以删除派生类对象 

示例

#include 
using namespace std;class Base{public: Base() { cout << "Base()" << endl; } virtual ~Base() { cout << "~Base()" << endl; }};class Derived : public Base{public: Derived() { cout << "Derived()" << endl; } ~Derived() { cout << "~Derived()" << endl; }};int main(){ Base* p = new Derived(); // ... delete p; return 0;}

运行打印:

Base()Derived()~Derived()~Base()

可以发现,由于基类的析构函数是虚函数,所以我们delete基类指针时,派生类也跟着调用了析构函数,从而避免了内存泄漏,也能满足使用dynamic_cast强制转换

一般而言,虚构造函数只有在继承下才会被使用,单个类是不会使用虚构函数的,因为虚函数表会产生额外的空间

注意:构造函数不能成为虚函数,因为虚函数表是在构造函数执行后才会进行初始化

转载地址:http://facmo.baihongyu.com/

你可能感兴趣的文章
Express cookie-parser
查看>>
scp命令
查看>>
MySQL数据库性能优化之存储引擎选择
查看>>
前端面试大全(一)
查看>>
类加载过程的原理分析
查看>>
Day1_HTML_排版标签
查看>>
基本分词
查看>>
系统提示不能打开文件langbar.chm
查看>>
混合云工作负载5个安全问题
查看>>
对于上一篇文章的补充,关于String类型的比较
查看>>
固定边栏滚动特效
查看>>
学习英文之社区,博客及源码
查看>>
Git备忘
查看>>
Lvs+keepalived+httpd+NFS搭建高可用
查看>>
配置浏览器来显示基于WebGL的动画
查看>>
python 知识点小结
查看>>
CentOS7.4 yum 安装 Apache php5.6 或者 php7
查看>>
avalon2问题总结
查看>>
spring boot 集成quartz 2.0 实现前端动态配置(获取spring上下文)的两种方式,启动数据库中已开启定时任务...
查看>>
linux下进程、端口号相互查看方法
查看>>