1. 一些继承中的问题

1.1 多继承中父类含有重名成员问题

如下:

#include <iostream>
#include <string>
using namespace std;class father1 {
public:father1() {class_name = "father1";}string class_name;
};class father2 {
public:father2() {class_name = "father2";}string class_name;
};class public_inherited_son : public father1, public father2 {
public:
};int main() {public_inherited_son public_inherited_son_instance1;cout << public_inherited_son_instance1.class_name << endl;return 0;
}

此时father1, father2都含有class_name成员, 而子类不含, 所以子类实例化的对象中如果要使用class_name, 必须要加上作用域, 否则以上代码编译后就会如下:

修改后代码如下:

#include <iostream>
#include <string>
using namespace std;class father1 {
public:father1() {class_name = "father1";}string class_name;
};class father2 {
public:father2() {class_name = "father2";}string class_name;
};class public_inherited_son : public father1, public father2 {
public:
};int main() {public_inherited_son public_inherited_son_instance1;cout << public_inherited_son_instance1.father1::class_name << endl;cout << public_inherited_son_instance1.father2::class_name << endl;return 0;
}

结果为:

或者让子类用同名成员覆盖该成员, 代码如下:

#include <iostream>
#include <string>
using namespace std;class father1 {
public:father1() {class_name = "father1";}string class_name;
};class father2 {
public:father2() {class_name = "father2";}string class_name;
};class public_inherited_son : public father1, public father2 {
public:public_inherited_son() {class_name = "public_inherited_son";}string class_name;
};int main() {public_inherited_son public_inherited_son_instance1;cout << public_inherited_son_instance1.class_name << endl;return 0;
}

结果如下:

1.2 菱形继承的问题

也叫钻石继承, 即两个派生类同时继承于同一个基类, 又有一个派生类同时继承于以上两个派生类.

如下代码所示:

#include <iostream>
#include <string>
using namespace std;class grandfather {
public:int member_value;
};class father1 : public grandfather {
public:
};class father2 : public grandfather{
public:
};class public_inherited_son : public father1, public father2 {
public:
};

这时候father1, father2类都继承了grandfaher类的member_value属性, 那么public_inherited_son在多继承father1和father2类的时候就会不清楚用谁的成员, 用以下测试代码:

int main() {public_inherited_son public_inherited_son_instance1;public_inherited_son_instance1.member_value = 10;cout << public_inherited_son_instance1.member_value << endl;return 0;
}

编译发现果然有二义性:

这种情况当然可以通过作用域来区分, 但是如果实际情况是这份数据只需要一份的话, 菱形继承之后会造成资源浪费, 那么就可以用virtual继承来解决这样的问题.

2 虚继承

2.1 普通继承的内存模型

当不加虚继承关键字的时候, 继承中内存的情况如下图:

2.2 虚继承实现

复用1.2中的例子, 如果修改继承方式如下:

#include <iostream>
#include <string>
using namespace std;class grandfather {
public:int member_value;
};class father1 : virtual public grandfather {
public:
};class father2 : virtual public grandfather{
public:
};class public_inherited_son : public father1, public father2 {
public:
};int main() {public_inherited_son public_inherited_son_instance1;public_inherited_son_instance1.member_value = 10;cout << "public_inherited_son_instance1.member_value = 10" << endl;cout << "current public_inherited_son_instance1.member_value is" << public_inherited_son_instance1.member_value << endl;cout << "current public_inherited_son_instance1.father1::member_value is" << public_inherited_son_instance1.father1::member_value << endl;cout << "current public_inherited_son_instance1.father2::member_value is" << public_inherited_son_instance1.father2::member_value << endl << endl;public_inherited_son_instance1.father1::member_value = 20;cout << "change public_inherited_son_instance1.father1::member_value to 20" << endl;cout << "current public_inherited_son_instance1.member_value is" << public_inherited_son_instance1.member_value << endl;cout << "current public_inherited_son_instance1.father1::member_value is" << public_inherited_son_instance1.father1::member_value << endl;cout << "current public_inherited_son_instance1.father2::member_value is" << public_inherited_son_instance1.father2::member_value << endl << endl;public_inherited_son_instance1.father2::member_value = 30;cout << "change public_inherited_son_instance1.father1::member_value to 30" << endl;cout << "current public_inherited_son_instance1.member_value is" << public_inherited_son_instance1.member_value << endl;cout << "current public_inherited_son_instance1.father1::member_value is" << public_inherited_son_instance1.father1::member_value << endl;cout << "current public_inherited_son_instance1.father2::member_value is" << public_inherited_son_instance1.father2::member_value << endl << endl;return 0;
}

这样编译就没问题了, 运行结果为:

原因是, 当使用虚继承之后, 该数据成员便只有一份, 此时该内存模型为:

虚继承后, 子类继承的是一个指针, 这个指针指向的是一个虚继承表, 表内记录了继承的数据的位置与本身的偏移量(即用的是基类的数据, 这样保证了数据的唯一性), 这样做就解决了菱形问题.

3 多态

3.1 静态多态和动态多态

函数重载, 运算符重载等等, 地址早绑定, 以下是一个静态继承的例子:

#include <iostream>
#include <string>
using namespace std;class father {
public:void printClassName() {cout << "this is class father" << endl;}
};class son1 : public father {
public:void printClassName() {cout << "this is class son1" << endl;}
};void PrintClassName(father &fatherInstance) {fatherInstance.printClassName();
}void test1() {son1 son1Instance1;PrintClassName(son1Instance1);
}int main() {test1();return 0;
}

运行时发现:


这就是因为静态多态早绑定的性质, 因为在编译阶段, 其father中的printClassName函数就已经确定了函数地址了, 就是father实例化对象的printClassName, 哪怕是通过子类进行类型转换的, 其作为参数传进PrintClassName中时, 也会退化为传入其父类的空间.

如果我们按照以下方法做:

#include <iostream>
#include <string>
using namespace std;class father {
public:virtual void printClassName() {cout << "this is class father" << endl;}
};class son1 : public father {
public:void printClassName() {cout << "this is class son1" << endl;}
};void PrintClassName(father &fatherInstance) {fatherInstance.printClassName();
}void test1() {son1 son1Instance1;PrintClassName(son1Instance1);
}int main() {test1();return 0;
}

运行发现:

即在father类的printClassName函数前面加virtual, 这样的话就会实现与静态多态早绑定相对应的晚绑定, 即在运行时才给函数赋值. 如需动态多态, 子类需要重写父类的虚函数(区别于重载, 重写为函数返回值类型, 函数名称, 参数列表需完全相同). 这样做就能达到父类的指针或引用指向子类对象时, 调用其虚函数会调用子类对象对应的重写函数.

3.2 动态多态的实现原理

当在父类的函数前加上virtual关键字, 其就会作为一个函数指针存储在类中, 指向虚函数表, 当运行时, 从虚函数表中获取出当前的函数地址并运行(如果发生重写, 会换成继承后的地址).

3.3 多态各类情况总结

用以下测试类:

class father {
public:void printFuncName() {cout << "this is father's function" << endl << endl;}virtual void virtualPrintFuncName() {cout << "this is father's virtual function" << endl << endl;}
};class virtualInheritedSon : virtual public father {
public:void printFuncName() {cout << "this is virtualInheritedSon's function" << endl << endl;}void virtualPrintFuncName() {cout << "this is virtualInheritedSon's virtual function" << endl << endl;}
};class normalInheritedSon : public father {
public:void printFuncName() {cout << "this is normalInheritedSon's function" << endl << endl;}void virtualPrintFuncName() {cout << "this is normalInheritedSon's virtual function" << endl << endl;}
};

3.3.1 普通继承

如下代码测试:

void normalInheritedTest() {normalInheritedSon normalInheritedSonInstance;cout << "now normalInheritedSonInstance printFuncName" << endl << endl;normalInheritedSonInstance.printFuncName();cout << "now normalInheritedSonInstance virtualPrintFuncName" << endl << endl;normalInheritedSonInstance.virtualPrintFuncName();cout << "now fatherInstance = normalInheritedSonInstance" << endl << endl;father fatherInstance = normalInheritedSonInstance;cout << "now fatherInstance printFuncName" << endl << endl;fatherInstance.printFuncName();cout << "now fatherInstance virtualPrintFuncName" << endl << endl;fatherInstance.virtualPrintFuncName();cout << "now rFatherInstance = normalInheritedSonInstance" << endl << endl;father &rFatherInstance = normalInheritedSonInstance;cout << "now rFatherInstance printFuncName" << endl << endl;rFatherInstance.printFuncName();cout << "now rFatherInstance virtualPrintFuncName" << endl << endl;rFatherInstance.virtualPrintFuncName();
}

结果为:

总结如下:

  • 普通继承的子类实例化的对象无论是调用普通成员函数或者是虚成员函数, 都会调用子类自己的.
  • 用父类等号取出其子类实例化对象中的父类调用普通成员函数或者是虚成员函数, 都会调用父类自己的.
  • 用父类引用取出其子类实例化对象中的父类调用普通成员函数, 会调用父类自己的, 如果是虚函数, 会调用子类的.

3.3.2 虚继承

用如下代码测试:

void normalInheritedTest() {normalInheritedSon normalInheritedSonInstance;cout << "now normalInheritedSonInstance printFuncName" << endl << endl;normalInheritedSonInstance.printFuncName();cout << "now normalInheritedSonInstance virtualPrintFuncName" << endl << endl;normalInheritedSonInstance.virtualPrintFuncName();cout << "now fatherInstance = normalInheritedSonInstance" << endl << endl;father fatherInstance = normalInheritedSonInstance;cout << "now fatherInstance printFuncName" << endl << endl;fatherInstance.printFuncName();cout << "now fatherInstance virtualPrintFuncName" << endl << endl;fatherInstance.virtualPrintFuncName();cout << "now rFatherInstance = normalInheritedSonInstance" << endl << endl;father &rFatherInstance = normalInheritedSonInstance;cout << "now rFatherInstance printFuncName" << endl << endl;rFatherInstance.printFuncName();cout << "now rFatherInstance virtualPrintFuncName" << endl << endl;rFatherInstance.virtualPrintFuncName();
}

结果为:

总结如下:

  • 虚继承的子类实例化的对象无论是调用普通成员函数或者是虚成员函数, 都会调用子类自己的.
  • 用父类等号取出其子类实例化对象中的父类调用普通成员函数或者是虚成员函数, 都会调用父类自己的.
  • 用父类引用取出其子类实例化对象中的父类调用普通成员函数, 会调用父类自己的, 如果是虚函数, 会调用子类的.

3.3.3 虚析构

如下例子:

#include <iostream>
using namespace std;class father {
public:father() {cout << "calling father's constructor" << endl;}~father() {cout << "calling father's destructor" << endl;}virtual void printClassName() = 0;
};class son : public father {
public:son(int value) {cout << "calling son's constructor" << endl;m_pvalue = new int(value);}~son() {cout << "calling son's destructor" << endl;if (m_pvalue != NULL) {delete m_pvalue;m_pvalue = NULL;}}void printClassName() {cout << "this is class son" << endl;cout << "my value is " << *m_pvalue << endl;}int *m_pvalue;
};void test1() {father* pfatherInstance = new son(10);pfatherInstance->printClassName();delete pfatherInstance;
}int main() {test1();return 0;
}

这样运行结果为:

发现并没有调用子类的析构函数, 也就是说当用父类去引用或用父类指针指向子类对象时, 父类本身析构过程并不会调用子类析构函数, 这样容易导致内存泄漏(以上son的构造过程中m_pvalue指向了堆区申请的空间而没有释放掉).

我们把父类的析构函数设置为虚析构函数, 如下代码:

class father {
public:father() {cout << "calling father's constructor" << endl;}virtual ~father() {cout << "calling father's destructor" << endl;}virtual void printClassName() = 0;
};

这样就可以避免此问题, 且虚析构函数相较于其他虚函数, 其本身父类的函数也会走, 如下结果:

如果父类的析构函数声明为纯虚析构, 如下代码:

class father {
public:father() {cout << "calling father's constructor" << endl;}virtual ~father() = 0;virtual void printClassName() = 0;
};

那么编译会出现如下错误:

因为父类的纯虚函数是必须要走的, 所以其可以在类的实现文件中实现一下, 加入如下代码:

father::~father() {cout << "calling father's destructor" << endl;
}

这样编译就没问题了, 运行结果也和上面一样.

3.3.4 构造函数不可以是虚函数

同析构函数不同的是, 构造函数不可以设置为虚函数, 因为构造函数调用时虚函数表还没有初始化. 而且虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。 而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数, 没有这个必要.

3.3.5 静态函数不可以是虚函数

虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable. 对于静态成员函数,它没有this指针,所以无法访问vptr. 这就是为何static函数不能为virtual.

4 重载和多态

主要是重载, 覆盖, 隐藏的区别:

重载: 同一个类中(两个函数都在父类, 或者都在子类, 这样用父类指针或者用子类调用不会有什么争议)

覆盖: 父类虚函数被子类同名, 同参数函数覆盖, 这就是简单的多态,需要注意,覆盖需要子类的方法的返回值小于等于父类的返回值,访问权限大于父类的访问权限。

隐藏: 父类与子类存在同名, 参数不同函数, 无论父类函数是否为虚函数, 都会被隐藏.

父类与子类存在同名, 参数相同参数, 父类函数不是虚函数, 也会被隐藏.

隐藏函数可以通过父类作用域调用.

C++中类和对象的一些注意事项 --- 多态相关推荐

  1. C++中类和对象的一些注意事项

    1. struct和class的区别 默认的访问权限不同, struct默认访问权限是public, 而class的默认访问权限是private. 2. 构造析构函数 2.1 注意事项 匿名构造函数在 ...

  2. C++中类和对象的一些注意事项 ---继承

    1 继承中的访问权限问题 所有继承方式, 子类都无法访问父类的private成员. 那么用如下测试代码尝试一下: #include <iostream> using namespace s ...

  3. web前端培训分享:面向对象中类和对象的定义是什么?

    在学习web前端技术的时候,我们接触的最多的便是面向对象这一块,其实很多编程技术都有用到这个现象,下面我们就为大家详细的介绍一下面向对象中类和对象的定义是什么? web前端培训分享:面向对象中类和对象 ...

  4. Vue:对象更改检测注意事项

    还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除: var vm = new Vue({data: {a: 1} }) // `vm.a` 现在是响应式的vm.b = 2 ...

  5. java 对象的定义是_浅析Java编程中类和对象的定义

    1,什么是类? 答:类是客观存在的,抽象的,概念的东西. 2,什么事对象? 答:对象是具体的,实际的,代表一个事物.例如:车是一个类,汽车,自行车就是他的对象. 关于类与对象的描述:类是对象的模版,对 ...

  6. php中的类 对象的方法的区别,php中类和对象的区别是什么

    php中类和对象的区别:类是对象的抽象,对象是类的具体实例:类是抽象的,不占用内存,而对象是具体的,占有内存空间.打个比方:类就是水果,对象就是苹果. 本教程操作环境:windows7系统.PHP7. ...

  7. 学习笔记之——Python中类和对象的理解

    学习笔记之--Python中类和对象的理解 面向对象的含义和特性 类 Python中类的定义.结构.创建 Python类的定义 Python类的结构 类的创建 类的属性 类的方法 对象 对象的创建 参 ...

  8. Java语言中类与对象的创建

    Java语言中类与对象的创建 文章目录 Java语言中类与对象的创建 一.实验目的: 二.实验要求: 三.实验内容: 一.实验目的: 1.掌握类.对象的概念: 2.掌握对象的创建过程: 3.理解对象的 ...

  9. python中对象的特性_python中类与对象之继承,python类和对象理解,面对对象的三大特性之...

    python中类与对象之继承,python类和对象理解,面对对象的三大特性之 面对对象的三大特性之继承 1.什么是继承? 在程序中,继承指的是class与class之间的关系 继承是一种关系,必须存在 ...

最新文章

  1. Android - Manifest 文件 详解
  2. Android工程目录
  3. Qt中TCP服务端编程
  4. MyBatisPlus条件构造器带条件查询selectList使用
  5. python excel整合_如何整合100张excel表到一张excel表
  6. Microsoft WPF VS Adobe Apollo
  7. set的用法及短语_专升本英语易考短语搭配+常考句型
  8. CVPR最佳作者新作!无监督学习可变形3D对象
  9. 根据当前登录域账号 获取AD用户姓名和所在OU目录
  10. CCNA题库大换血,考生纷纷落马!(转)
  11. underscore.js 964 --- 1103行
  12. 部门换届推文文字_宿委会换届表彰大会!!!
  13. IPP简介及windows下安装说明
  14. 下载Android App的历史版本
  15. Mock server是什么
  16. 解决导出CSV文件乱码的问题
  17. ZOJ 3717 二分+2-sat判定。
  18. CS5266BN说明书|CS5266BN QFN48封装规格书|CS5266BN设计资料
  19. 集成经验模态(EEMD)原理详解与python实现
  20. NAVION-2019年的神作与VIO-ASIC化的隐秘面纱

热门文章

  1. oracle磁带的使用期限,rman删除磁带库过期备份问题
  2. MATLAB xlswrite函数出现“错误: 服务器出现意外情况”
  3. Android之ScrollView
  4. 利用github page搭建博客
  5. 汇编中各寄存器的作用(16位CPU14个,32位CPU16个)和 x86汇编指令集大全(带注释)
  6. Exchanger及其用法
  7. C++学习之路 | PTA乙级—— 1024 科学计数法 (20 分)(精简)
  8. android 通知灯 测试,Android灯光系统通知灯【转】
  9. java第一阶段知识_第一阶段 Java语言(下)
  10. nlp cs224n 学习笔记1 Introduction and Word Vectors