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

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 重载和多态

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505451.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑按f8无法进入安全模式_自已有电脑的人,都会遇到系统死机问题,教大家实用一招自已解决...

其实我们电脑死机蓝屏重启并不可怕&#xff0c;如果只要懂得最基本的一些查找方法就可以解决这些问题&#xff0c;因为电脑是一个完整的系统&#xff0c;既然是系统工程必须由硬件与软件共同合作才能完成出色的任务&#xff0c;如果电脑出现死机蓝屏等问题可以参考以下方式进行…

台式电脑键盘字母乱了_键盘侠的育儿经利用键盘引导学龄前儿童正确使用电脑、学习英文字母和拼音...

点击上方“总想做自己”关注我可以订阅哦一点资讯邀约作者&#xff1a;方游专注探讨个人成长与正向激励的话题&#xff0c;有干货&#xff0c;不再错过&#xff0c;快来点击关注吧&#xff01;微信公众号&#xff1a;apple_seeworld难得机会与孩子在家里有长达半月的相处&#…

C++模板的注意事项

函数模板 template <typename T> //把typename换成class也可以 函数模板调用方法 使用过程中, 有两种方法调用, 如下: 自动类型推导:#include <iostream>template <class T> void swap(T &first, T &second) {T temp;temp first;first se…

excel教程自学网_Excel自学教程:万能查找函数Lookup的神应用和技巧

提起查找函数&#xff0c;大家第一时间想到的肯定是Vlookup&#xff0c;其实大多数人不知道&#xff0c;Lookup才是查找函数之王&#xff0c;它几乎能高效地实现Vlookup函数的所有功能&#xff0c;部分功能是Vlookup函数无法比拟的。一、语法结构和基本使用方法。应用场景&…

C++ STL 容器的一些总结

1 C STL类型及实现原理 1.1 顺序容器 容器中的元素为有序排列,可以指定元素插入位置. 1.1.1 vector 顺序存储, 初始化过程会分配一定量空间, 在尾部插入会很快, 但是在中间插入元素, 会把之后所有元素向后平移, 所以较慢(中间删除元素同理). 如果元素个数超过当前限制, 会重…

java电商项目简历_一文解析从写简历,到面试、谈薪酬技巧和防坑指南

点击上方“码农沉思录”&#xff0c;选择“设为星标”优质文章&#xff0c;及时送达读者大大们好&#xff0c;好几天没更新了。一方面因为这几天工作忙&#xff0c;占了写作的时间。另一方面是在准备这篇文章各种素材&#xff0c;今年是最难求职年&#xff0c;我希望通过这篇文…

qq浏览器极速版_安卓手机QQ轻聊版大升级,极速版正式上线:无广告/省内存

8月28日&#xff0c;安卓手机QQ极速版推出了4.0正式版&#xff0c;采用了全新的界面设计&#xff0c;仅保留了基本聊天功能和QQ空间、小程序、钱包、文件等少量QQ主推功能&#xff0c;现已开放下载&#xff0c;QQ极速版安装之后会覆盖QQ轻聊版。QQ极速版4.0界面焕新升级&#x…

C++ STL 容器 vector

1 vector简介 顺序存储, 初始化过程会分配一定量空间, 在尾部插入会很快, 但是在中间插入元素, 会把之后所有元素向后平移, 所以较慢(中间删除元素同理). 如果元素个数超过当前限制, 会重新分配更大空间, 再把原容器中所有元素都拷贝到新的容器中. 优点: 支持随机访问(用下标…

3dmax卸载工具_3dmax软件如何彻底卸载?

近期有很多学员遇到关于3dmax软件卸载的问题&#xff0c;有的是想安装更高版本但不知道如何卸载更安全&#xff0c;有的是自己卸载了之后再安装其他版本却总是无法成功&#xff0c;这对此类问题&#xff0c;今天我做一个详细讲解&#xff1b;根据自己所遇问题&#xff0c;找到最…

华为app安装失败与已安装签名_手机APP为什么总是安装失败

整天“机不离手”的我们每个人手机里都装有好几十个APP但安装的时候总会出现跳出手机APP无法安装或安装失败的页面这究竟是怎么回事呢&#xff1f;小翼带你瞅瞅一、手机安全认证在安装或下载应用程序时&#xff0c;如果提示失败可能是因为系统“未知来源”没有开启。可以通过打…

C++ STL 中提供的算法

1 算法 1.1 for_each() 参数有三个: 首迭代器尾迭代器执行的函数 例如如下代码: #include <algorithm> //必须包含 #include <vector> using namespace std;int main() {vector<int> tmp;tmp.push_back(10);tmp.push_back(20);tmp.push_back(30);tmp.pu…

arma模型_GARCH模型应用:以国泰君安为例

1.下载国泰君安股票数据&#xff0c;计算对数收益率(1)首先安装包"quantmod"&#xff0c;这个包可以从雅虎财经的下载股票数据&#xff0c;具体包的解释见"【量化基础】R语言获取金融数据之quantmod包"。install.packages("quantmod")#安装包qua…

C++ STL 容器 string

1 string string内部含有一个char*字符串 2 string构造方式 无参构造 string str; 字符串构造 string str("abcd"); 拷贝构造n个相同字符 string str(10, k); //初始化为10个k 3 string赋值操作 可以有以下操作: void string_test() {string str1;str1 &qu…

小程序webview不全屏_小程序不在小(深度)

原标题&#xff1a;小程序不在小(深度)你问&#xff1a;“微信小程序适合哪些行业?”&#xff0c;回答是&#xff1a;“所有行业!”你可以想一下那些做过APP的公司&#xff0c;不管是任何行业的公司都可以拥有属于自己的APP&#xff0c;而从来不会有人问他们你们用的APP是否适…

leetcode 4 --- 寻找两个有序数组的中位数

1 题目 给定两个大小为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。 进阶&#xff1a;设计一个时间复杂度为 O(log (mn)) 的算法. 2 解法 这个题如果mn是偶数, 就是找到第(mn)/2以及第(mn)/2 1个数, 如果…

leetcode 142 --- linked-list-cycle-ii

1 题目&#xff1a; 对于一个给定的链表&#xff0c;返回环的入口节点&#xff0c;如果没有环&#xff0c;返回null 拓展&#xff1a; 你能给出不利用额外空间的解法么&#xff1f; 代码&#xff1a; class Solution { public:ListNode *detectCycle(ListNode *head) {} …

百度搜索引擎优化指南3.0_深圳网站搜索引擎排名优化电话,百度优化排名费用_华阳网络...

天津华阳在线科技有限公司为您详细解读深圳网站搜索引擎排名优化电话,百度优化排名费用的相关知识与详情&#xff1a;网站的主页标题是百度SEO的关键。你想要的主要关键词应该反映在标题中。如果标题写得好&#xff0c;百度很快就收录进去了。但要记住&#xff0c;有一点&#…

C++ STL 容器的一些总结 --- set(multiset)和map(multimap)

1 set和multiset 1.1 插入元素方式 set只能用insert插入数据. insert返回值是一个pair<iterator, bool>, 即插入数据的迭代器以及是否插入成功, multiset返回的只有迭代器, 因为不会插入失败. 1.2 删除 set只能用erase, 可以传迭代器或者是值. 1.3 注意事项 不允许…

苹果自带相册打马赛克_剪映app怎么给视频局部打马赛克

剪映app怎么给视频局部打马赛克呢&#xff1f;很多用户对此还不是很清楚&#xff0c;小编这里就给大家带来有关剪映app怎么给视频局部打马赛克的回答&#xff0c;希望能够对大家有所帮助。1、首先打开剪映app&#xff0c;进入首页后点击开始创作选项&#xff0c;2、这时选择需要…

excel表格如何转换成word表格_如何将excel转换成pdf?excel表格可以变成pdf文件吗?...

文字使用word&#xff0c;数据使用Excel&#xff0c;这应该是咱们日常生活中的一个规律了吧&#xff1f;不过不管是word文档还是Excel文档&#xff0c;都是可以被编辑修改的&#xff0c;那么我们怎么才能让它变得不能被编辑修改呢&#xff1f;小编这里还真有一个好方法&#xf…