目录
(一)继承的概念及定义
1.1继承的概念
继承(inheritance)机制是面向对象程序设计使代码可复用的最重要手段。类层次上的复用。相比于面向对象的封装,继体现类与类之间的相互联系。继承中,我们将学习如何处理类与类的关系和数据冲突与成员复用。
1.2继承的定义
1.2.1定义:
class A : public B , public C
派生类(子类) 继承方式 基类 基类(父类)
1.格式说明:派生类A,在基类的基础上选择性继承,先继承B,再继承C。
2.多继承中间要加,且每个继承都要注明继承方式。class类如果没注明继承方式就默认是私有,struct类没注明继承方式默认是共有。(这与访问修饰符的限定是一致的)
1.2.2继承方式和访问限定符
1.继承后使用权限=访问限定权限&&继承方式 首先是考虑访问限定,然后考虑继承方式
比如:private访问,public继承->子类继承基类所有成员,但是private限定只能在基类内部访问,在子类中不可见。即不能使用。所以继承后,派生类可使用基类public和protected的成员。
protected访问限定,public继承->子类继承父类部分成员,其实这种方式是更被期待的。父类实现对子类变量的管理。
2.继承方式:也分成3种,共有,私有,保护(protected)
3.实际使用中:一般使用public方式继承,几乎很少使用protected/private.
继承关系中,父类想给子类访问就设置成保护,不想给子类访问就设置成私有。
(二)基类和派生类对象赋值转换
1.派生类对象 可以赋值给基类对象,基类引用,基类对象;反之则不能,因为这本身就与继承的定义相悖。
2。可以使用基类指针或者引用指向派生类对象,反之不能,但是,对于指针来讲,本身就是动态的定义,所以可以强制转。
(三)继承中的作用域
1.在继承体系中基类和派生类中都有独立的作用域。
2.同名隐藏:在子类和父类中,具有相同名称的成员(成员函数或成员变量),需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
代码
class A
{
public:
void S()
{
cout << "S()" << endl;
}
};
class B
{
public:
void S(string str)
{
/*A::S();*/加上此句 error C2352: “A::S”: 非静态成员函数的非法调用
cout << "S(string str)" << endl;
}
};
int main()
{
B bb;
bb.S("wyt");
}
加上A::s()编译时语法报错,说明子类隐藏了父类,故访问父类是非法的。
(四)派生类的默认成员函数
这一模块与面向对象是一致的。6个默认的成员函数,“默认”是说我们不写,编译器会自动生成一个。在派生类中,我们多了“基类”的成员,要进行单独处理。
1.父类的成员由父类处理。才体现复用。“子类的构造函数必须调用父类的构造函数来初始化从父类继承来的一部分成员”。如果父类没有默认的构造函数可用,就必须采用子类构造函数的初始化列表显示调用。
class Father
{
public:
Father(int b,int n)
:_b(b)
,_n(n)
{}
protected:
int _b;
int _n;
};
//子类
class Son:public Father
{
public:
Son(int b,int n,string m)
:Father(b, n) //父类在子类中初始化
, _m(m)
{
cout << "Son()" << endl;
}
protected:
string _m;
};
2.构造函数系列的设置,要跟着类的特性走。类和它的构造函数是根据需求设计的,故也要配套使用。所以拷贝构造和赋值重载函数也必须要调用父类的拷贝构造完成拷贝初始化,和赋值重载完成赋值。
class Father
{
public:
Father(const Father& s)
:_b(s._b)
,_n(s._n)
protected:
int _b;
int _n;
};
//子类
class Son:public Father
{
public:
Son(const Son& s)
:Father(s)
, _m(s._m)
protected:
string _m;
};
//测试函数
void main()
{
Son s1(1, 2,"wyt");
Son s2(s1);
s1.print();
s2.print();
}
3.子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。
4.子类对象初始化先调用父类的构造,再调用子类构造。
5.子类对象清理先调用子类析构再调用父类析构。
(五)继承与友元,静态函数
1.友员函数和静态函数都没有*this指针,存储方式与成员函数也不同。所以对他们的理解也是特殊的。
2.友员函数不能被继承。友元函数是普通函数,用于与类私有成员建立联系。成员变量表已改变。
3.静态成员变量,可以被继承,但属于整个继承体系。
(六)复杂的菱形继承以及虚拟继承
6.1单继承
继承是相对子类说的。单继承:子类中只有一个基类。
效果:理论上继承了基类的所有成员,但不意味着都可以使用。
根据基类的访问修饰符限制,获取相应的成员变量和成员函数使用权。
6.2多继承
多继承:子类中至少有两个基类
注意:如果是多继承,基类中成员在子类中的排列次序与继承列表中的基类的先后次序一致。
6.3菱形继承与“虚”类成员变量的引入
1. 菱形继承:用类的角度类比:A是助教 B是老师类 C是学生类 D是研究生类(可以带入理解一下)
2.虚拟继承的作用是在菱形继承中,解决了菱形继承二义性的问题。抽象来讲就是说,当两个类(B,C)中有着相似的同源基类(A),另一个类(D)对它们进行管理时,就需要对(B,C)类进行管理即合并,注意这个合并,并非去重,而是统一到一个变量名下存储两份数据。数据并不冲突,冲突是因为其存储冲突而造成的访问冲突。
3.事物正因其抽象,才具有共性。 问题抽象:如何通过一个类管理两个类甚至多个类的数据和函数?我们可以引入“虚”类概念。通过一个去映射多个。
4.实现过程:虚基类指针和虚基表实现,每个虚继承的子类都有一个虚基表指针(内存中指针大小是4字节)和虚基表。虚指针(virtual base table pointer)指向虚基表(virtul table),虚基表中记录了虚基类与本类的偏移地址,通过偏移地址,找到虚基类成员。
5.虚指针占内存空间,虚基表存储的是虚基类相对直接继承类的偏移,不占类的内存空间。
6.代码
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.B::_a = 1;
d.C::_a = 2;
return 0;
}
(七)继承的总结
7.1继承
1.继承解决了什么问题:继承解决了类与类关系的问题。通过继承提高代码的复用率。但是多继承中菱形继承也反映了缺陷:高耦合性。处理高耦合性的问题,底层复杂,同时带来性能的损失!
2.从一个程序出发,代码应该尽可能地模块化。即“低耦合,高类聚”,从而提升代码的可维护性。
7.2继承与组合
public继承是一种is-a关系。也就是说每个派生类对象都是一个基类对象,组合是一种has-a的关系。B组合了A,每个B对象中都有一个A对象。实际尽量多去用组合。组合的耦合性低,代码维护性高。组合的底层虽然不复杂,关系线性。当描述类之间的关系时,可以用组合就用组合。不过继承也有自己的专用之处,描述复杂的类与类关系时,就得用多继承去处理。多态也必须要用到继承。
7.3思维启示
1.删繁从简,理解复杂的事物要从简单的角度。从简单的角度出发,要有合理的控制,如果不能控制则会使问题变得复杂。这时可以考虑替代,或者少用。总之尽量使代码的可读性高,维护成本低!