继承 通过继承联系在一起的类构成一种层次关系。通常在层次关系的根部有一个基类,其它类则直接或间接从基类继承而来,这些继承得到的类称为派生类。基类负责定义在层次关系中所有类共同拥有的成员,而每个派生定义各自的成员。继承实现了代码的复用。
1.派生类对象的内存分布
class Base
{
public:
int ma;
};
class Derive : public Base
{
public:
int mb;
};存在如上的继承关系,在构造一个派生类Derive对象时,根据定义的顺序,肯定是先构造基类成员ma,再构造派生类对象的成员mb,内存分布如下:
很明显,在继承的时候,派生类将基类的作用域也一并继承来了,那这样的话,派生类成员中如果定义一个成员"ma",成员的话是可以的。
2.派生类里面成员的访问限定
继承关系有三种,分别为public,protected,private,那么基类成员在定义的时候就已经限定了访问限定,那么在被继承后会受到影响吗?答案是肯定的,面向对象的思想中很重要的一个就是封装,要是能通过继承就能访问到,那0.0,尴尬了,所以不存在的。所以就有必要知道这些访问限定了。
上表基本说明了各种限定符下的继承情况,有几种得解释下,比如:基类成员的限定符是private,通过public的继承后,在派生类中,很明显这个成员是不能被访问的,不可见的,那么派生类对象内存分布中,会不会还有这个成员呢?当然,,,有;你只是看不到,访问不到这个成员,与人家存在不存在没什么关系吧。而且在继承情况中,成员的访问限定下,继承的访问限定是高于成员定义的访问限定的,比如上面的那个例子。*^_^*
3.派生类把基类的什么东西复用了?
基类的所有成员变量和成员方法,当然,,,构造和析构的话,不存在的,再说就算把继承给派生类,你也用不了啊,你的类名和基类的都不一致,怎么用,析构也一样的么;那么在构造一个派生类对象时构造顺序和析构顺序是怎样的呢?
class Base
{
public:
Base(int data):ma(data){cout<<"Base()"<<endl;}
~Base(){cout<<"~Base()"<<endl;}
int ma;
};
class Device : public Base
{
public:
Device(int data):mb(data),Base(data){cout<<"Device"<<endl;}
~Device(){cout<<"~Device()"<<endl;}
private:
int mb;
};
int main()
{
Device de(10);
return 0;
}我还有什么说的呢,别说话,看图。(派生类对象在初始化列表的构造顺序只与它们的定义顺序有关,而且在定义基类部分只能调用基类的构造方法构造,且写到初始化列表中)
4.通过派生类对象来调用基类继承来的成员
(1)基类和派生类对象的成员名字相同时,可以通过添加"Base::"来访问基类成员;
(2)基类和派生类同名成员方法的三种关系:
重载
同一作用域下,函数名相同,参数列表不同的方法可以构成重载,但是返回类型不能作为参考标准(此处不举栗子了);
隐藏
基类和派生类的同名方法/成员,直接构成隐藏关系,以下例子构成隐藏关系:
class Base
{
public:
Base(int data):ma(data){cout<<"Base()"<<endl;}
~Base(){cout<<"~Base()"<<endl;}
int show(int data=0)
{
cout<<"Base::show()"<<endl;
return 0;
}
int ma;
};
class Device : public Base
{
public:
Device(int data):mb(data),Base(data){cout<<"Device"<<endl;}
~Device(){cout<<"~Device()"<<endl;}
void show()
{
cout<<"Device::show()"<<endl;
}
private:
int mb;
};覆盖派生类中存在与基类中返回值、函数名、参数列表相同的方法,构成覆盖关系;以下关系构成覆盖:
class Base
{
public:
Base(int data):ma(data){cout<<"Base()"<<endl;}
~Base(){cout<<"~Base()"<<endl;}
void show()
{
cout<<"Base::show()"<<endl;
}
int ma;
};
class Device : public Base
{
public:
Device(int data):mb(data),Base(data){cout<<"Device"<<endl;}
~Device(){cout<<"~Device()"<<endl;}
void show()
{
cout<<"Device::show()"<<endl;
}
private:
int mb;
};5.继承结构中基类对象和派生类对象的相互转换
基类对象-->派生类对象 X
派生类对象-->基类对象 Y
举个栗子:比如,基类代表一个人,派生类代表一个大学生,老板说,去,给我找个能做微积分的来,你倒好,在街上随便拽一个就来复命了,这一定能帮老板解决问题吗?万一是个小学生,你该收拾东西了;如果要找一个人来,你拽了个大学生来,有问题吗?当然没有。说白了,对象的转换主要还得看它的能力符不符合条件。
基类指针/引用-->派生类对象指针/引用 Device pa; Base*pb= &pa; Y
把派生类对象的地址交给基类指针,没问题的,虽然说是除过基类的部分,其他都丢失了,无所谓啊,既然选择将派生类对象交给基类指针处理,那么就已经限定了它的处理范围,安全!
派生类指针/引用-->基类对象指针/引用 X 可能发生内存的越界访问,指针权限太大,容易对未定义的内存进行非法访问。
把基类对象的地址交给一个派生类的指针,基类只能帮它把基类的那部分初始化,属于派生类的那部分谁处理?基类不会,派生类也没做,极度不安全好吧!
6.静态绑定(编译时期的绑定)和动态绑定(运行时绑定)
(1)静态绑定
Device de(10);
Device* pd = &de;
pd->show();例如,在执行上述代码时,在编译时期就已经知道了要调用哪个函数的地址(基类或者是派生类),通过查看汇编代码,如下:
pd->show();
01081CFC mov ecx,dword ptr [ebp-24h]
01081CFF call Device::show (1081285h) 很明显,编译阶段编译器就知道要调用的函数地址。(2)动态绑定
动态绑定,讲的是C++中的多态,这里就要涉及到虚函数的概念(此处不详细介绍了),简单说“一个接口,多种方法”,即在运行时才能知道要调用的具体方法的地址。
class Base
{
public:
Base(int data):ma(data){cout<<"Base()"<<endl;}
~Base(){cout<<"~Base()"<<endl;}
virtual void show(){cout<<"Base::show()"<<endl;}
virtual void show(int i){cout<<"Base::show(int)"<<endl;}
protected:
int ma;
};
class Derive : public Base
{
public:
Derive(int data):mb(data), Base(data){cout<<"Derive()"<<endl;}
~Derive(){cout<<"~Derive()"<<endl;}
void show(){cout<<"Derive::show()"<<endl;}
private:
int mb;
};
int main()
{
Base b(20);
Derive *pd = (Derive*)&b;
pd->show();
return 0;
}
输出结果:
实例说明了,编译器在编译阶段并不知道应该调用哪个地址,这里体现了多态。
对于C++继承这一块,暂时就先总结这些,后续继续总结,例如菱形继承、虚函数、纯虚函数、抽象类总结持续更新
*^_^*
1480

被折叠的 条评论
为什么被折叠?



