浅论继承与派生

都说继承派生是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,这也就验证了基类的构造函数只能由最终的派生类来对其初始化。

写的有点啰嗦,例子可能也有点牵强,算是写出了自己对于这两天学习继承与派生的一点心得体会吧。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值