类的继承

基类与派生类之间的构造行为

派生类可以调用基类的公共成员,但无法调用基类的私有成员。所以派生类无法直接初始化基类中的私有成员变量。在派生类中初始化基类的私有成员变量需要显式的调用基类的构造函数,并以委托构造的方式将其初始化。

// 基类头文件 basic.h
#ifndef __BASIC__
#define __BASIC__ class Basic
{
private:
int basic_pri_val;
void basicPriFuc() const;
public:
Basic(int _pri_val, int _pub_val):basic_pri_val(_pri_val), basic_pub_val(_pub_val){}
int basic_pub_val;
void basicPubFuc() const;
void showBasicPriVal() const;
};
#endif
// 基类源文件 basic.cpp
#include <iostream>
#include "basic.h"
using std::cout;
void Basic::basicPriFuc() const
{
cout<< "Basic private function.\n";
} void Basic::basicPubFuc() const
{
cout<< "Baisc public function.\n";
} void Basic::showBasicPriVal() const
{
cout<< "Basic private value: "<< basic_pri_val<< '\n';
}
// 派生类头文件 derive.h
#ifndef __DERIVE__
#define __DERIVE__ #include "basic.h" class Derive:public Basic // 从 Basic 类派生
{
public:
Derive(int _basic_pri_val, int _basic_pub_val):Basic(_basic_pri_val, _basic_pub_val){} // 委托基类构造函数初始化基类成员变量
}; #endif
// 派生类源文件 derive.cpp
#include "derive.h"
// 主程序源文件 main.cpp
#include "derive.h" int main()
{
Derive derive = {1, 2}; // derive.basicPriFuc(); // 不可访问基类私有成员函数
derive.basicPubFuc(); // 可以访问基类公共成员函数
// derive.basic_pri_val; // 不可访问基类私有成员变量
derive.showBasicPriVal(); // 基类私有成员变量可以通过基类公共成员函数访问
derive.basic_pub_val; // 可以访问基类公共成员变量 return 0;
}

Baisc public function.

Basic private value: 1

另外,基类的引用或指针可以指向派生类,但这样的引用和指针只能调用来自于基类的方法。

在派生类中使用基类方法

在派生类中使用基类方法只需要在方法前加上域解析运算符,如下:

// 假定类 Basic 中已经定义了 myPrint 方法
class Derive:: public Basic
{
public:
void myPrint()
{
Basic::myPrint(); // 调用类 Basic 中的 myPrint 方法
myPrint(); // 调用类 Derive 中的 myPrint 方法
}
}

protected 的访问权限

对于类外而言,protected 的访问权限与 private 相似。对于派生类而言, protected 的访问权限与 public 相似。

多态公有继承

在进行本节之前需要知道:父类的指针/引用可以指向/引用派生类,但只能调用继承于父类的方法或重写在子类中的虚方法。

多态有静态多态和动态多态两个部分。

  • 静态多态相当于在派生类中重写了(不是重载)父类中的函数,函数的声明中,形参类型和数量可以与父类不一致。派生类只能调用重写后的函数,而不能够再使用被重写的父类中的函数。
  • 动态多态发生在使用父类指针或引用,调用或重写修函数时。下面将进行详细的演示。

关键字 virtual

  • 只有类的非静态成员函数可以成为虚函数,其他非类成员函数或类的静态成员函数不行。
  • 类中的虚函数,即使不被调用也必须被定义。而非虚函数在不被调用的情况下可以只声明不定义。
  • 派生类与父类中的虚函数声明必须完全一致,包括函数名与形参类型。
  • virtual 还可用于类的虚继承。

使用 virtual 修饰类的成员函数,其成员函数的调用行为只在下列情况下不同:

  • 对于一般的成员函数,如果使用类的指针或者引用来调用该函数,则函数的行为将取决于声明该指针/引用的类型。即如果是父类类型的的指针/引用,则将调用父类中的函数;如果是派生类类型的指针/引用,则将调用派生类中的函数。
  • 对于虚成员函数,如果使用类的指针或引用来调用该函数,则函数的行为取决于指针所指向的对象的类型。

示例

// 基类头文件 basic.h
#ifndef __BASIC__
#define __BASIC__ class Basic
{
private:
int basic_pri_val;
void basicPriFuc() const;
public:
Basic(int _pri_val, int _pub_val):basic_pri_val(_pri_val), basic_pub_val(_pub_val){}
// virtual ~Basic() {}; // 在包含虚函数时,应当包含一个虚析构函数
int basic_pub_val;
void basicPubFuc() const;
void showBasicPriVal() const; void myPrint() const; // 非虚函数,下面将在派生类中重写
};
#endif
// 基类源文件 basic.cpp
#include <iostream>
#include "basic.h"
using std::cout;
void Basic::basicPriFuc() const
{
cout<< "Basic private function.\n";
} void Basic::basicPubFuc() const
{
cout<< "Baisc public function.\n";
} void Basic::showBasicPriVal() const
{
cout<< "Basic private value: "<< basic_pri_val<< '\n';
} void Basic::myPrint() const
{
cout<< "Basic class print.\n";
}
// 派生类头文件 derive.h
#ifndef __DERIVE__
#define __DERIVE__ #include "basic.h" class Derive:public Basic // 从 Basic 类派生
{
public:
Derive(int _basic_pri_val, int _basic_pub_val):Basic(_basic_pri_val, _basic_pub_val){} // 委托基类构造函数初始化基类成员变量 void myPrint(); // 重写父类中的非虚函数 }; #endif
// 派生类源文件 derive.cpp
#include "derive.h"
using std::cout;
void Derive::myPrint() const
{
cout<< "Derive class print.\n";
}
// 主程序源文件 main.cpp
#include "derive.h" int main()
{
Derive derive = {1, 2};
Basic & basic = derive;
basic.myPrint(); return 0;
}

Basic class print.

当重写父类中的普通函数时,即使父类引用引用了派生类,但是该引用调用的却是父类中的函数。myPrint 函数的行为却决于引用类型。

如果我们在声明中为 myPrint() 函数加上 virtual 修饰,则程序结果为:

Derive class print

可以看到,myPrint() 函数的行为取决于引用所指向的类。

如果需要在派生类中重定义基类中的方法,则应尽量将该方法声明为虚。且需要在基类中声明一个虚析构函数。

当类被用作基类时,它的虚构函数应当被设置为虚函数,否则将会对派生类对象的内存释放造成麻烦。

抽象基类(ABC)

类中包含纯虚函数的类被称为抽象基类。纯虚函数的声明如下:

class ABC
{
public:
virtual void myPrint() const = 0;
}

纯虚函数可以在抽象基类中被定义。抽象基类的派生类必须使用重写虚函数的方法来重写纯虚函数。抽象基类强制派生类实现其纯虚函数。

私有继承和保护继承

  • 在私有继承中,基类的公有成员和保护成员都将成为派生类的私有成员。
  • 在保护继承中,基类的公有成员和保护成员都将成为派生类的保护成员。

多重继承

class Deriver : public Basic1, private Basic2 {···};

C++基础杂记(3)的更多相关文章

  1. java复习(2)---java基础杂记

    java命名规范: 参考:http://www.cnblogs.com/maowang1991/archive/2013/06/29/3162366.html 1.项目名小写 2.包名小写 3.类名每 ...

  2. Webpack系列-第一篇基础杂记

    前言 公司的前端项目基本都是用Webpack来做工程化的,而Webpack虽然只是一个工具,但内部涉及到非常多的知识,之前一直靠CV来解决问题,之知其然不知其所以然,希望这次能整理一下相关的知识点. ...

  3. python基础杂记

    一.编码 1.ACSII                        0000 0001           8位       一个字节 2. uncoide                     ...

  4. .Net基础杂记

    1.面向对象程序思想 面向对象是程序开发的一种机制,特征为封装.继承.多态.以面向对象方式编写程序时,将复杂的项目抽象为多个对象互相协作的模型,然后编写模型结构,声明或实现类型的成员,即描述对象的特征 ...

  5. Webpack系列-第三篇流程杂记

    系列文章 Webpack系列-第一篇基础杂记 Webpack系列-第二篇插件机制杂记 Webpack系列-第三篇流程杂记 前言 本文章个人理解, 只是为了理清webpack流程, 没有关注内部过多细节 ...

  6. webpack-插件机制杂记

    系列文章 Webpack系列-第一篇基础杂记 webpack系列-插件机制杂记 前言 webpack本身并不难,他所完成的各种复杂炫酷的功能都依赖于他的插件机制.或许我们在日常的开发需求中并不需要自己 ...

  7. 5天揭秘js高级技术-第一天

    一.基础杂记 1. document.write() <script type="text/javascript"> document.write('<h2> ...

  8. 面试基础知识集合(python、计算机网络、操作系统、数据结构、数据库等杂记)

    python python _.__.__xx__之间的差别 python中range.xrange和randrange的区别 python中 =.copy.deepcopy的差别 python 继承 ...

  9. java基础(杂记)

    java基础夯实(杂记):1:创建实例对象可以通过无参的构造函数然后调用成员变量去初始化属性,也可以自己定义有参构造方法直接初始化属性,当属性为private时我们可以通过getset方法间接访问:2 ...

  10. elasticsearch基础知识杂记

    日常工作中用到的ES相关基础知识和总结.不足之处请指正,会持续更新. 1.集群的健康状况为 yellow 则表示全部主分片都正常运行(集群可以正常服务所有请求),但是 副本 分片没有全部处在正常状态. ...

随机推荐

  1. 河南省CCPC大学生程序设计竞赛赛后总结yy

    这次的ccpc总体来说,取得的成绩并不理想,首先是题目解决的数量较少,其次是罚时太多了.开始也是找到了签到题,按理说应该不难拿下,虽然大家解决这道签到题都不是很快,但是我们小队在比赛已经过去两个小时左 ...

  2. 2021-8-5 Mysql个人练习题

    创建学校表格 CREATE TABLE `Student`( `s_id` VARCHAR(20), `s_name` VARCHAR(20) NOT NULL DEFAULT '', `s_birt ...

  3. C#.NET 国密SM4对称加解密 与JAVA互通 ver:20230731

    C#.NET 国密SM4对称加解密 与JAVA互通 ver:20230731 .NET 环境:.NET6 控制台程序(.net core). JAVA 环境:JAVA8,带maven 的JAVA控制台 ...

  4. VS Code 有哪些好用的插件呢?【持续更新】

    一.画图工具:vscode-drawio   功能:在 VSCode 中画流程图.数据流图等等.        使用方法:     创建一个后缀名为 .drawio 的文件,然后用 VSCode 打开 ...

  5. 自定义组件使用v-model

    场景描述 我们在一个系统中,会出现这样的情况, 有一个联系人的下拉框,这个下拉框中的数据是从服务端获取的. 在很多页面都需要使用这个联系人(下拉框). 我们通常是这样做的: 写一个下拉框组件然后调用接 ...

  6. 从壹开始前后端开发【.Net6+Vue3】

    项目名称:KeepGoing(继续前进) 1.1介绍 工作后,学习的脚步一直停停走走,希望可以以此项目为基础,可以不断的迫使自己不断的学习以及成长 将以Girvs框架为基础,从壹开始二次开发一个前后端 ...

  7. 《高级程序员 面试攻略 》rabitmq rcoketmq kafka的区别 和应用场景

    RabbitMQ.RocketMQ 和 Kafka 都是流行的消息中间件系统,用于实现分布式应用程序之间的异步通信.虽然它们都有类似的目标,但在设计和应用场景上存在一些区别. 1. RabbitMQ( ...

  8. 日志开源组件(六)Adaptive Sampling 自适应采样

    业务背景 有时候日志的信息比较多,怎么样才可以让系统做到自适应采样呢? 拓展阅读 日志开源组件(一)java 注解结合 spring aop 实现自动输出日志 日志开源组件(二)java 注解结合 s ...

  9. Navicat连接docker mysql出错

    一.启动容器 首先启动docker mysql: docker run -itd --name mysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 my ...

  10. 4.go语言复合类型简述

    目录 1. 本章前瞻 2.来自leetcode的例题 描述 分析 题解 3. 复合类型新版本的变化 3.1 string和[]byte的高效转化 3.2 内置函数clear 4. 复合类型概述 4.1 ...