在学习C++的过程中,继承和多态是所有人都绕不过的一个重头戏。这篇博客探讨的都是VS2013下,32位编译系统所产生的结果
目录
一、继承
1、概念
继承(inheritance)这一种机制是面向对象程序设计中,可以使代码复用的一种最重要手段。它可以使程序在保持原有类特性的基础上进行扩展,增加一些其他的功能。在继承中产生的类,我们称为派生类。
例如:对于集成来说,我们可以这样理解。简单的一句话,在现有的基础之上进行开发。就像智能手机,例如小米4,小米5,甚至于iPhone,还有电脑等。它们的每一代都是继承了上一代版本的各种优势,以及较为成熟的一些设计。总不可能,你每一次的产品推出都要从头开始设计,所有的程序,功能,框架都要从头设计?这样做的效率就会十分的低。所以c++产生了这一种机制,从而更快的提高程序员的开发效率。
2、作用
继承的主要作用,本就是实现代码的复用性。在生活中我们会遇见很多这样的情况——重复劳动。就好比三个计算器,都只能实现一类数据的加法。那既然都是加法为什么需要三个计算器呢?因此,就产生了多态的定义。而继承最主要的作用就是实现多态。
3、实现方式
class 派生类名 :继承权限 基类名;
class Base
{
public:
void SetBase(int pri, int pro, int pub)
{
_pri = pri;
_pro = pro;
_pub = pub;
}
void PrintBase()
{
cout << _pri << endl;
cout << _pro << endl;
cout << _pub << endl;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived : public Base
{};
int main()
{
// 可以验证:派生类将基类中的成员变量继承到子类中
cout << sizeof(Derived) << endl;//12
// 验证:基类中的成员函数释放被子类继承
Derived d;
d.SetBase(10, 20, 30);
d.PrintBase();
return 0;
}
4、继承权限
继承权限顾名思义,使用什么权限对基类中的成员进行继承
// public继承方式:
class Base
{
public:
void SetBase(int pri, int pro, int pub)
{
_pri = pri;
_pro = pro;
_pub = pub;
}
void PrintBase()
{
cout << _pri << endl;
cout << _pro << endl;
cout << _pub << endl;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
// 继承权限--public
// 基类中public/protected的成员在子类中权限不变
// 基类中private的成员在子类中不可见(但已被继承)
// 访问权限:限定该成员变量是否可以直接在类外进行调用
class Derived : public Base
{
public:
void SetDerived(int priD, int proD, int pubD)
{
_priD = priD;
_proD = proD;
_pubD = pubD;
_pro = 10;//基类中共有的成员变量在派生类中可以被访问
// _pri = 20; // 在派生类中不能访问基类中private的成员变量
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
//测试基类被public继承是否可以在类外访问protected
int main()
{
Derived d;
cout << sizeof(d) << endl;//24
d._pub = 10;
// d._pro = 10;//
return 0;
}
// 继承方式--protected
// 基类中public的成员在子类中访问权限已经变成protected
// 基类中protected的成员在子类中访问权限不变
// 基类中private的成员在子类中不可见(已被继承)
class Derived : protected Base
{
public:
void SetDerived(int priD, int proD, int pubD)
{
_priD = priD;
_proD = proD;
_pubD = pubD;
_pro = 10;//基类中受保护的成员变量在派生类中可以被访问
// _pri = 20; //在派生类中不能访问基类中私有的成员变量
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
class D : public Derived
{
public:
void Test()
{
_pub = 10;//基类中public权限已经成为protected
_pro = 20;//基类中protected权限不变
}
};
int main()
{
Derived d;
// d._pub = 10;//基类中public权限已经成为protected
//d._pro = 20;//基类中protected权限不变
return 0;
}
// 继承方式--private
// 基类中public的成员在子类中访问权限已经变成private
// 基类中protected的成员在子类中访问权限已经变成private
// 基类中private的成员在子类中不可见(已被继承)
class Derived : private Base
{
public:
void SetDerived(int priD, int proD, int pubD)
{
_priD = priD;
_proD = proD;
_pubD = pubD;
_pro = 10;//基类中受保护的成员变量在派生类中被访问
// _pri = 20; //在派生类中不能访问基类中私有的成员变量
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
class D : public Derived
{
public:
void Test()
{
// _pub = 10;//子类被公有继承,私有的成员变量不可访问
//_pro = 20;//子类被公有继承,私有的成员变量不可访问
}
};
int main()
{
Derived d;
//d._pub = 10;//私有的成员变量不可在类外被访问
return 0;
}
我们知道在C++中,struct和class都可以当做类来使用。那么在继承机制中,struct和class有什么区别呢?
struct Derived : Base
{
public:
void SetDerived(int priD, int proD, int pubD)
{
_priD = priD;
_proD = proD;
_pubD = pubD;
_pro = 10;//基类中的受保护的成员变量可以在类中被访问
// _pri = 20; // 在派生类中不能访问基类中私有的成员变量
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
class D : public Derived
{
public:
void Test()
{
_pub = 10;//基类中的公有的成员变量可以在类中被访问
}
};
int main()
{
D d;
d._pub = 10;//基类中的公有的成员变量可以在类外访问
//d._pro=20;//基类中的受保护的成员变量不可以在类外访问
return 0;
}
---------------------------------------------------------------------------
class Derived : Base
{
public:
void SetDerived(int priD, int proD, int pubD)
{
_priD = priD;
_proD = proD;
_pubD = pubD;
_pro = 10;//基类中受保护的成员变量在派生类中可以访问
// _pri = 20; //在派生类中不能访问基类中私有的成员变量
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
class D : public Derived
{
public:
void Test()
{
// _pub = 10;//子类被公有继承,私有的成员变量不可访问
//_pro = 20; //子类被公有继承,私有的成员变量不可访问
}
};
int main()
{
Derived d;
//d._pub = 10;//私有的成员变量不可在类外被访问
return 0;
}
所以我们可以得出struct在继承机制中,默认是public继承权限。
class在继承机制中,默认是private继承权限。
二、基类和派生类的赋值转化
在继承机制中,赋值和普通类又有什么区别呢?
赋值兼容规则:前提是public继承,因为只有public继承才使整个类中的成员可以被访问。
public继承方式:基类和子类是is-a的关系
is-a:可以将一个子类对象看成是一个基类对象(所有使用到基类对象的位置,都可以使用子对象来代替)
这个问题我们用对象模型来具体观察
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
string _name="Tom";
string _gender="man";
int _age=19;
};
class Student :public Person
{
public:
int _StuId=1000;
};
int main()
{
Person p;
Student s;
return 0;
}
其中person是基类,student是学生类。
int main()
{
Person p;
p.SetPerson("Tom","man",19);
Student s;
s.SetStudent("Jack","man",20,1000);
// 可以用子类对象来给基类对象进行赋值
p= s;
// 一个基类指针可以指向子类对象,
// 一个子类的指针不能直接指向一个基类的对象
Person* pp = &s;
Student * ps = (Student *)&p;
return 0;
}
子类对象可以给基类对象赋值,基类对象不可以给子类对象赋值
基类指针可以指向子类对象,子类对象不能直接指向基类对象,需要进行强转类型
原因:1、子类对象之所以能给基类对象赋值,因为子类中的空间是大于等于基类的,所以不会出现无法赋值的情况。
但是基类给子类对象赋值,就无法给子类中新增加的成员变量赋值,从而出现基类越界,不安全。
2、基类指针之所以能够指向子类对象,是因为基类指针类型相较于子类指针类型所能读取的空间不一样。
例如,基类指针可以访问到子类对象的前三个成员变量,而子类指针在访问基类成员变量的第四个时就会发生越界。
所以需要对子类指针类型进行强转为基类指针类型,才可以指向基类。
三、继承体系中的作用域
1、在继承体系中基类和派生类都有独立的作用域。
2、子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫做同名隐藏,也叫作重定义。
(在子类成员中可以使用 基类::基类成员 来进行显示访问)
3、如果同名隐藏的是函数,只需要函数名相同就会构成同名隐藏
#include<iostream>
using namespace std;
class Base
{
public:
void PrintFunc()
{
cout << "Base::PrintFunc()" << endl;
}
public:
int _pub=20;
};
class Derived :public Base
{
public:
void PrintFunc()
{
cout << "Derived::PrintFunc()" << endl;
}
public:
int _pub = 10;
};
int main()
{
Derived d;
d.PrintFunc();
cout << d._pub << endl;
d.Base::PrintFunc();
cout << d.Base::_pub << endl;
return 0;
}
在我们代码中,调用派生类中的同名函数,出现的是派生类中定义的函数,但是基类中的同名函数确实是被继承了。
在我们进行显示调用后,发现在派生类中确实存在了基类中的成员函数以及成员变量。只不过是因为同名,派生类将从基类中继承的同名变量和函数都进行了隐藏。
注意:1、如果同名成员变量同名--->与成员变量类型是否相同无关
2、如果成员函数同名---->与函数的原型是否相同无关
四、继承体系中的默认成员函数
我们都知道在类中有6个默认的成员函数,那么在继承体系中,默认的成员函数又是怎样的情况呢?
1、派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2、派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。
3、派生类的operator=必须调用基类的operator=完成基类的赋值
4、派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。只有这样才能保证派生类对象先清理派生类成员,再清理基类成员的顺序
5、派生类对象初始化先调用派生类构造,再掉基类构造。先执行基类构造,再执行派生类构造。
6、派生类对象析构先调用派生类析构再调用基类析构。
注意:之所以在基类没有默认构造函数时要在派生类中显示调用,是因为,派生类中的构造函数只提供对派生类中的成员变量初始化,而被继承的基类成员变量是需要被基类中的构造函数初始化。
#include<iostream>
using namespace std;
class Base
{
public:
Base(int b)
: _b(b)
{
cout << "Base::Base(int)" << endl;
}
~Base()
{
cout << "Base::~Base()" << endl;
}
protected:
int _b;
};
class Derived : public Base
{
public:
Derived(int b, int d)
: Base(b)
, _d(d)
{
cout << "Derived::Derived(int,int)" << endl;
}
~Derived()
{
cout << "Derived::~Derived()" << endl;
// call Base::~Base();
}
protected:
int _d;
};
void TestDerived()
{
Derived d(10, 20);
}
int main()
{
TestDerived();
return 0;
}
注意:这只是执行过程!!!派生类首先调用的是自己的构造函数,在构造函数的函数列表中,需要对基类数据进行初始化,所以再滴啊用了基类构造函数,在构造结束后返回派生类构造函数继续执行。
五、菱形继承与菱形虚拟继承
我们到目前为止讨论的都是单继承,但是大家有没有想过,一个派生类真的就只有一个基类吗?一个基类就真的只能有一个派生类吗?显然这是不可能的。所以就有了多继承以及菱形继承。


我们重点来讨论一下菱形继承
//菱形继承
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D d;
return 0;
}

通过代码和监视窗口,我们可以看到菱形继承对象的对象模型。我们可以看到由于B,C都继承自A,所以在B,C中都有A类中的成员变量_a,而在d对象中B,C类的数据也是根据继承顺序的先后进行布局的。诚然看到这里大家也可能想到一个问题,既然一个类中有两个同名变量,那么调用的到底是哪一个呢?
我们发现这样直接赋值时不行的,编译器无法确定_a到底是B类中的,还是C类中的。至此菱形继承的缺陷也被无限放大!那就是使用菱形继承必定会产生“二义性”。
那么我们要如何解决这种二义性问题呢?-------菱形虚拟继承
在C++中,已经有了明确解决这个问题的方法。那就是菱形虚拟继承以及virtual关键字的出现。
//菱形虚拟继承
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D d;
d._a = 10;
return 0;
在B类和C类前加上关键字virtual,我们在监视窗口看到的是这样一种情况。

在内存和监视窗口中我们通过成员变量的地址对比,从而得出其大概的对象模型

可以推断出虚拟继承中d的对象模型是这样的。
那么问题又出现了,0x0075FD40和0x0075FD48位置到底是什么呢?为什么会多出这8个字节呢?
通过这张图我们能清楚的看到当我们给_a赋值时,编译器首先将d对象的地址赋值给了eax寄存器,那么eax寄存器也就是对象的前4个字节到底存了什么呢?
可以看到的是在对象的前四个地址中存放的是一串地址,我们把这四个字节叫做虚基表指针,而其指向为一张虚基表,虚基表中则存放的是被虚拟继承类中成员变量的偏移量。所以在对_a赋值时,ecx寄存器等于0x00 00 00 14。
同样我们可以看到C对象中因为虚拟继承,其位置也存在了虚基表指针而它的虚基表中的偏移量就是12了
到目前为止,有关菱形的虚拟继承就已经讨论完成了。
什么类型的成员不可以被继承?
友元函数不可被继承。其实很好理解,有员函数本事不属于类中,它只是被允许访问某个类中的成员而已。例如基类的友元函数,就无法访问子类中的私有成员以及受保护的成员。
class Base
{
friend void Print();
public:
Base(int b)
: _b(b)
{}
int GetB()
{
return _b;
}
protected:
int _b;
};
class Derived : public Base
{
public:
Derived(int b, int d)
: Base(b)
, _d(d)
{}
protected:
int _d;
};
void Print()
{
Base b(10);
cout << b.GetB() << endl;
cout << b._b << endl;
Derived d(10, 20);
//cout << d._d << endl;
}
如何写一个不能被继承的类?
class Base
{
private:
Base(int b)
: _b(b)
{}
protected:
int _b;
};
#if 0
class Derived : public Base
{
};
endif
因为基类构造函数访问权限是private,其在子类中就不能直接被调用,因此派生类的构造函数无法生成。
在c++11中提供了final关键字来限制类的继承
class Base final
{
public:
Base(int b)
: _b(b)
{}
protected:
int _b;
};
class Derived : public Base
{};
int main()
{
return 0;
}
继承机制中的static类型的变量,不论有多少个子类,就都只有一个static成员。