上章链接: 22.C++- 继承与组合,protected访问级别


继承方式

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

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

};

继承方式默认为private

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

public继承

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

private继承

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

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

protected继承

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

如下图所示:

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

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

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

显示调用父类构造函数

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

比如以下代码:

class  StrA
{
public:
StrA()
{
cout<<"StrA()"<<endl;
}
StrA(string s)
{
cout<<"StrA(string s):"<<s<<endl;
}
}; class StrB : public StrA
{
public:
StrB(string s)
{
cout<<"StrB(int i):"<<s<<endl;
}
}; int main()
{
StrB b("");
return ;
}

编译运行:

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

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

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

改为:

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

运行打印:

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

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

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

比如:

class Parent{

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

在main()函数执行:

       Child c;

       //c. add(10);        //该行会报错,由于子类有add函数,所以编译器会默认在子类里寻找add(int i);

       c.Parent::add();   //该行正确,执行父类的成员函数

       c.add(,);

       cout<<"Child.mval="<<c.mval<<endl;

       cout<<"Parent.mval="<<c.Parent::mval<<endl;

打印:

Child.mval=
Parent.mval=

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

 

父子间的兼容

以上示例的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*)<Child*>(p3);

虚函数

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

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

class Parent
{
int i;
public:
void example()
{
cout<<"class Parent"<<endl;
} }; class Child : public Parent
{
int j;
public:
void example()
{
cout<<"class Child"<<endl;
}
}; void print(Parent* p)
{
p->example();
}
int main()
{
Parent t;
Child c; print(&t);
print(&c); cout<<"SIZEOF Parent:"<<sizeof(t)<<endl;
cout<<"SIZEOF Child:"<<sizeof(c)<<endl;
}

运行打印:

class Parent
class Parent
SIZEOF Parent:
SIZEOF Child:

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

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

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

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

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

运行打印:

class Parent
class Child
SIZEOF Parent:
SIZEOF Child:

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

虚析构函数

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

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

示例

#include <iostream>

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 ;
}

运行打印:

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

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

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

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

23.C++- 继承的多种方式、显示调用父类构造函数、父子之间的同名函数、virtual虚函数的更多相关文章

  1. C++入门经典-例8.3-子类显示调用父类构造函数

    1:当父类含有带参数的构造函数时,创建子类的时候会调用它吗?答案是通过显示方式才可以调用. 无论创建子类对象时调用的是那种子类构造函数,都会自动调用父类默认构造函数.若想使用父类带参数的构造函数,则需 ...

  2. 基础3:js实现继承的多种方式

    js实现继承的多种方式 1. 原型链继承 function Parent() { this.name = 'xwk' } Parent.prototype.getName = function() { ...

  3. c++继承构造子类调用父类构造函数的问题及关于容器指针的问题及当容器里储存指针时,记得要手动释放

    看下面的一个问题: class Person { private: string name; public: Person(const string& s=""){ nam ...

  4. 虚函数&纯虚函数&抽象类&虚继承

    C++ 虚函数&纯虚函数&抽象类&接口&虚基类   1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过 ...

  5. C++ 子类继承父类纯虚函数、虚函数和普通函数的区别

    C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...

  6. python基础之继承实现原理、子类调用父类的方法、封装

    继承实现原理 python中的类可以同时继承多个父类,继承的顺序有两种:深度优先和广度优先. 一般来讲,经典类在多继承的情况下会按照深度优先的方式查找,新式类会按照广度优先的方式查找 示例解析: 没有 ...

  7. Python开发基础-Day20继承实现原理、子类调用父类的方法、封装

    继承实现原理 python中的类可以同时继承多个父类,继承的顺序有两种:深度优先和广度优先. 一般来讲,经典类在多继承的情况下会按照深度优先的方式查找,新式类会按照广度优先的方式查找 示例解析: 没有 ...

  8. C# 构造函数如何调用父类构造函数或其他构造函数

    class C : B{    C() : base(5)      // call base constructor B(5)    {  }    C(int i) : this()  // ca ...

  9. 在C++中子类继承和调用父类的构造函数方法

    构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法).因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需 ...

随机推荐

  1. 【webpack】-- 自动刷新与解析

    前端需要频繁的修改js和样式,且需要根据浏览器的页面效果不断的做调整:而且往往我们的开发目录和本地发布目录不是同一个,修改之后需要发布一下:另外一点就是并不是所有的效果都可以直接双击页面就能看到,我们 ...

  2. 字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8

    原作者:阮一峰(ruanyifeng.com),现重新整理发布,感谢原作者的无私分享. 1.引言 今天中午,我突然想搞清楚 Unicode 和 UTF-8 之间的关系,就开始查资料. 这个问题比我想象 ...

  3. IntelliJ IDEA 的下载和安装

    下载 官网地址:https://www.jetbrains.com/idea/ 直接点击 DOWNLOAD 下载 接下来跳转到一个页面,可以看到第一个红框中是选择操作系统的,IDEA分为收费的旗舰版和 ...

  4. C 单向链表就地逆转

    1.问题描述 给定一个单链表L,设计函数Reverse将L就地逆转.即不需要申请新的节点,将第一个节点转换为最后一个结点,第二个节点转换为倒数第二个结点,以此类推. 2.思路分析 循环处理整个链表.将 ...

  5. eclipse如何使用log4j详解,你get了吗???

    1.下载log4j    jar包 log4j下载地址 http://logging.apache.org/log4j/2.x/download.html 2.log4j   jar包引入项目 接下来 ...

  6. Android获取手机号码

    今天来说一下Android系统中怎么获取手机号 一.SIM卡存储的数据可分为四类,它们分别是: 第一类是固定存放的数据.这类数据在移动电话机被出售之前由SIM卡中心写入,包括国际移动用户识别号(IMS ...

  7. HBase之HRegionServer启动(含与HMaster交互)

    在我的博文<HBase——HMaster启动之一>.<HBase——HMaster启动之二>中已经详细介绍过HMaster在启动过程中调用的各种方法.下面,单就HRegionS ...

  8. [原创]K8Cscan插件之多种方式系统版本探测

    [原创]K8 Cscan 大型内网渗透自定义扫描器 https://www.cnblogs.com/k8gege/p/10519321.html Cscan简介:何为自定义扫描器?其实也是插件化,但C ...

  9. Java之单元测试工具(Junit)

    Junit是Java单元测试框架,一般Eclipse里面会集成这个Junit4测试工具 既然是测试工具,虽然开发用得比较多,但作为测试人员也需要具备会Junit测试的思想,况且技多不压身 这里简单介绍 ...

  10. 关于mysql的update、delete、和insert into能否使用别名问题

    在工作中遇到这样一个问题,就是mysql在insert into时能不能使用别名,大家会很奇怪为什么insert into使用别名呢?原因在于原来的项目中使用了user表,新项目要将user表拆分为u ...