本文主要是介绍浅论继承与派生,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
都说继承派生是c++的精髓,就如同指针是c的灵魂一样。
最近刚好学到这些知识,就写一个博客分享下学习体会吧。
继承与派生
啥叫继承呢,就比如你爸爸在外面拼搏开了个厂子,等你老爸退休不做让位给你了,那么你就是继承着你老爸的厂子。什么叫派生呢?好比你自己长大了有出息了,自己赚了钱买了个车,这车就是你派生出来的东西,说个不恰当的,就是你爸爸的钱,厂子,车子你全继承下来了,但是车型号太老了你不喜欢,你自己买了个新的,那么你比你爸爸就多了个新的车子。
继承和派生就好比是这样,你爸爸的东西你继承下来了,你自己还新派生了些东西,换句话说,你爸爸有的你都有,你爸爸没有的你也有。
那么接下来举个例子吧。
#include <iostream>
#include "string.h"using namespace std;class Parent
{
public:int money;
public:Parent(int _p){cout << "Parent 构造函数" << endl;money = _p;}void showp(){cout << "this is parent :" << money << "万" << endl;}
};class Child : public Parent
{
public:char *car;
public:Child(int _p, char *n) : Parent(_p){cout << "Child 构造函数" << endl;car = new char [100];strcpy(car,n);}void showc(){cout << "this is child :" << car << endl;}
};int main()
{Child ch(100, "Audi");cout << "*************************" << endl;ch.showp();ch.showc();return 0;
}
访问控制权限问题
上述例子中,假设你大名是ch,那么你show出来的东西既有你继承你爸爸的100万,还有一辆属于你的奥迪车。
这边顺带就接着讲一讲一些权限吧,你父亲的东西有个权限,而你继承过来之后,也有一个权限,就比如,你父亲用作打麻将的小金库,在你父亲那边应该是private私有权限,你父亲是不会让你继承的吧,你连这个都继承来了你父亲过年拿什么坐庄呢?还有一个权限,是你自己继承来之后的权限,比如你父亲的车子,有句话叫老婆与车恕不外借,对吧,那么你父亲的车你可以继承过来,但是在你这儿就是你私有的,不外借。关于权限,public,protected,private,三种,两层关系,组合起来细分就是九种,按照我的想法,就是取其权限最小者作为最终的权限。
class Father
{
private:int money; //你老爸打麻将的
protected:int card; // 你爸爸的银行卡,只能你家人用,外人不能用
public:int fangchan; // 你家的房产,你亲戚来了可以借助
};//继承权限
class MR_Wang : public Father //你继承来之后的权限
{
private: char your_car; //你的车,私有的权限,不外借
protected:int computer; // 你的电脑,别人可以玩,但是不能带出去玩
public:int You_Hui_Quan;//你的兰博基尼五元代金券,可以直接送你朋友用
};
这里举的例子可能不恰当,但是有助于理解,即在你父亲的权限,你的继承权限中最小值作为你的最终的权限。
接下来再来谈谈继承派生中构造函数和析构函数吧。还是以你爸爸的资产和你继承与派生的资产为例子。你爸爸去申请的办理的银行卡,肯定得你爸爸本人的身份证,那么当你爸爸想要提出大笔金额时,还得你爸爸带着证件去银行,在这里就可以把你爸爸的身份证当作是一个构造函数,那么当你结婚买房需要大额money时候,还需要你爸爸 带着证件去银行取钱,参数初始化列表Child(int money, char *name, int x) : Parent(monet, name);
就好比你带着你爸爸去银行,取出来钱给你用。这就是析构函数,你没办法直接取银行卡里的钱,但是你可以喊上你爸爸带着证件去取钱。
这个过程是需要你参与的,但是析构函数就不用你再去过问了,换句话说析构函数,不要人为干预了,就好比你爸爸的一双鞋子破旧不想要了,你爸爸带你妈妈shopping一圈自己去买一双就ok了。
继承时的名字遮蔽
接下来再来讨论一下一个问题,比如你朋友来你家做客,看见你家有个大鱼缸,里面的鱼甚是好看,而里面的鱼既有你爸爸垂钓得来,也有你买来的,你朋友对其饶有兴趣,那么默认情况下,你的朋友就会以为这里面的鱼都是你弄来的,如果你给你朋友做了介绍说明这里面有的鱼是你父亲垂钓所得,那么你朋友才会知道真相并夸赞你的父亲。
#include <iostream>using namespace std;class Parent
{
public:char *s1;Parent(){s1 = new char [100];strcpy(s1, "fish");}void fish(){cout << s1 << " by father" << endl;}
};class Child : public Parent
{
public:char *s2;Child(){s2 = new char [100];strcpy(s2, "fish");}void fish(){cout << s2 << " by myself" << endl;}
};int main()
{ Child f;f.fish();cout << "*************" << endl;f.Parent::fish();return 0;
}
这个例子实际上说的就是 继承时候的名字遮蔽问题,就如同这些鱼,你朋友默认是你掉来的,只有在你说明之后,你朋友才会知道有些是你父亲掉来的。
继承中的static关键字
还是接上面的某个例子来说吧,比如你爸爸银行卡里面的钱,你爸爸取钱或者你去存钱,里面的数据修改了会被记录下来,这个时候就需要一个全局静态变量, 比如 static int money;虽然他是在Parent类里面定义的,但是Child类继承过来可以对其修改,且可以将修改之后的值存储下来。
值得注意的是,static修饰的变量初始化,必须在类的外面,就好像办理银行卡只能在银行柜台一样,而之后的动作可以在任意一个类里面操作,就如同存钱取钱可以找一个ATM机器一样。
#include <iostream>
#include "string.h"using namespace std;class Parent
{
public:static int money;
public:Parent(int _p){cout << "Parent 构造函数" << endl;money = _p;}void show(){cout << "this is parent :" << money << "万" << endl;}~Parent(){cout << "Parent析构函数" << endl;}
};int Parent::money = 10;class Child : public Parent
{
public:char *car;
public:Child(int mon, char *n) : Parent(mon) {cout << "Child 构造函数" << endl;car = new char [100];strcpy(car,n);}void show(){cout << "this is child :" << car << endl;} ~Child(){cout << "child 析构函数" << endl; }
};int main()
{Child ch(100, "Audi");cout << "*************************" << endl;ch.show();ch.Parent::show();return 0;
}
这里假如你爸爸初始办卡存进去十万,后续你存了90万所以加起来就是100万。第32行代码就如同你爸爸去银行申请银行卡并初始存入10万,这就是static修饰的变量,需要在类外面做初始化,任何一个地方对其修改,都会被记录存储下来 。
继承中对象存储模型
先祭出代码吧,代码是上面关于 鱼 的代码,只是main函数末尾加了三句话,打印出对象的内存首地址。
cout << "&f : "<< &f << endl;cout << "&f.s1 : " << &f.s1 << endl; cout << "&f.s2 : " << &f.s2 << endl;
这里可以看出,父类对象首地址在前,子类首地址在后面。内存地址呢,也可以举一个牵强一点的例子,比如你亲戚朋友来你家做客,那么他们首先应该是给你父亲打招呼问好,然后才会谈论你的事情,比如谈恋爱啥的,对吧?这样理解一下,然后就得记住,父类对象地址在前,子类在后。
继承中的类型兼容性原则
子类对象可以当作父类对象使用
接着上面的例子,比如说你继承了你爸爸的工厂,你爸爸可以指挥个人做事情,你当然也有这个权限
#include <iostream>using namespace std;class Parent
{
public:virtual void Work(){cout << "working" << endl;}
};class Child : public Parent
{
public:void fun(){cout << "洗碗" << endl;}
};int main()
{Child c;c.Work();return 0;
}
运行结果会显示work。
基类的引用直接引用派生类 & 基类指针指向派生类
在这里,不得不提及virtual这个关键词了,百度翻译是虚拟,虚拟的 等意思。顾名思义,这个关键词所修饰的函数可以称呼为虚函数,对, 就是那个肾虚的虚。virtual type fun();
, 其中virtual算作关键字,type表示函数类型,括号里面可以有参数,这里没写。
这儿也没什么合适的例子了,可能会很晦涩,就算是记住吧,当基类对象指向或者引用派生类对象时候,基类与派生类种必须包含函数原型相同的函数,且基类中函数必须加上virtual修饰。最重要的一点别忘了,继承关系不能丢!话不多说,上代码
#include <iostream>using namespace std;class Parent
{
public://关键字virtual并不能丢virtual void fun() //与派生类Child中fun原型相同{cout << "working" << endl;}
};class Child : public Parent
{
public:void fun(){cout << "洗碗" << endl; //函数原型相同,内容不同}
};int main()
{//父类引用子类对象Child c;Parent &p = c;p.fun();//父类指针指向子类对象Parent *ptr = NULL;ptr = new Child;ptr->fun(); return 0;
}
这里的fun函数,子类父类都有这个相同类型的函数,但是不同的是,父类前面有个virtual修饰。
多继承导致的二义性
先来举个例子说说多继承吧。比如过年的时候,你爷爷给你爸爸压岁钱,也给你大伯压岁钱,然后你爸爸和你大伯又给了你压岁钱,那么问题就是过段时间后,你还会记得你的某几张红票子是你爷爷给你爸爸再给你的,还是你爷爷给了你大伯你大伯再给你的呢?
这样的问题,就叫做多继承导致的二义性问题。话不多说,继续上代码
#include <iostream>using namespace std;class YeYe //爷爷的钞票
{
public :int money;
};class BaBa : public YeYe //给了爸爸一部分
{};class DaBo : public YeYe //给了大伯一部分
{};class My : public BaBa, public DaBo //爸爸和大伯又都给你压岁钱
{};int main()
{My m;m.money;return 0;
}
由此可以看出,编译阶段就会报错,书上称之为二义性。实质上,这个问题我们可以溯源,就能解决,也不必疑惑,那就是直接把这个钱理解是爷爷给的,爸爸和大伯仅仅是转了下手。对此,c++提出了一个解决方案,即加virtual关键字。
#include <iostream>using namespace std;class YeYe //爷爷的钞票
{
public :int money;
};class BaBa : virtual public YeYe //给了爸爸一部分
{};class DaBo : virtual public YeYe //给了大伯一部分
{};class My : public BaBa, public DaBo //爸爸和大伯又都给你压岁钱
{};int main()
{My m;m.money;return 0;
}
这里的代码比之前的,仅仅是继承时在public前加了个关键字virtual,加上之后,编译的时候便不会报错。这两段代码写的都比较抽象,删繁就简突出了virtual关键字及虚继承的概念。
虚继承时的构造函数
在c++中,规定了由最终派生类来初始化虚基类。接着上面的例子,也就意味着,你爷爷之听你的话,不听你爸爸和你大伯的话,毕竟隔代亲嘛,对吧?
#include <iostream>using namespace std;class YeYe //爷爷的钞票
{
public :int money;
public :YeYe(int a ){money = a;}
};class BaBa : virtual public YeYe //给了爸爸一部分
{
public :int b;
public :BaBa(int a, int b) : YeYe(a){this->b = b;}
};class DaBo : virtual public YeYe //给了大伯一部分
{
public :int d;
public :DaBo(int a, int b) : YeYe(a){d = b;}
};class My : public BaBa, public DaBo //爸爸和大伯又都给你压岁钱
{
public :int m;
public :My(int a, int b, int c, int d) : YeYe(a), BaBa(10, b), DaBo(20, c){m = d;}void show(){cout << money << endl;cout << b << endl;cout << d << endl;cout << m << endl;}};int main()
{My m(1, 2, 3, 4);m.show();return 0;
}
在派生类My的构造函数中,调用了三个父类的构造函数,可以看出,BaBa给YeYe的初始值是10 ,DaBo给爷爷的赋值是20,而My给YeYe的初始值是1 ,运行后不难发现,
最终YeYe的money是1,这也就验证了基类的构造函数只能由最终的派生类来对其初始化。
写的有点啰嗦,例子可能也有点牵强,算是写出了自己对于这两天学习继承与派生的一点心得体会吧。
这篇关于浅论继承与派生的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!