一.继承的概念
二.继承的继承方式与作用域
继承和访问方式都有public,protected和private这三类.
其关系如下图:
总结:
1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它.
2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能 访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.class默认继承方式是private,struct默认继承方式是public,一般最好显示写出继承方式
三.基类与派生类对象赋值转换
切片:派生类对象给基类对象/指针/引用赋值.也叫做切割
基类对象不能赋值给派生类对象.基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的.
四.继承中的作用域
五.派生类中的默认成员函数
总之:派生类必须对基类进行初始化,派生类的拷贝构造和赋值运算符重载必须完成对基类的对应函数的调用,构造函数和析构函数顺序问题(先构造基类,后析构基类).
#include<iostream>
using namespace std;
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name, int num)
: Person(name)
, _num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
: Person(s)
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);
_num = s._num;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
void main()
{
Student s1("jack", 18);
Student s2(s1);
}
注意:
友元关系不能被继承.比如你父亲的朋友不一定是你的朋友.也就是说基类友元不能访问子类私有和保护成员
基类中有静态成员,继承后也只有一份静态成员
菱形继承会有数据冗余和二义性的问题.
六.菱形继承问题与虚继承
C++中继承有单继承和多继承.
多继承里面会出现菱形继承比如:
菱形继承会出现数据二义性,如上图,Assistant类中会存储两份Person中的数据,这样会造成数据的二义性,解决的方法就是虚拟继承.
#include<cstdio>
#include<iostream>
using namespace std;
class A {
public:
int _a;
};
//class B : virtual public A {
class B : public A{
public:
int _b;
};
// class C : public A
class C : 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;
d._b = 3;
d._c = 4;
d._d = 5;
printf("B->a: %p\n", &(d.B::_a));
printf("C->a: %p\n\n", &(d.C::_a));
printf("D->b: %p\n", &(d._b));
printf("D->c: %p\n", &(d._c));
printf("D->d: %p\n", &(d._d));
return 0;
}
#include<cstdio>
#include<iostream>
using namespace std;
class A {
public:
int _a;
};
// class B : public A
class B : virtual public A {
public:
int _b;
};
// class C : public A
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;
d._b = 3;
d._c = 4;
d._d = 5;
printf("B->a: %p\n", &(d.B::_a));
printf("C->a: %p\n\n", &(d.C::_a));
printf("D->a: %p\n", &(d._a));
printf("D->b: %p\n", &(d._b));
printf("D->c: %p\n", &(d._c));
printf("D->d: %p\n", &(d._d));
return 0;
}
D对象中将A放到的了对象组成的最下面,这个A同时属于B和C
这里通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A.
七.组合和继承问题
继承是一种代码复用的手段,但是多继承会出现菱形继承,因此我们在使用集成时尽量避免菱形继承,少用多继承.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。
组合与继承类似却又不同,在我们平常程序设计中,也可以使用继承和组合一起设计,不要一味使用继承,而继承在一定程度上破坏了封装性,增加了程序的耦合.
继承优缺点:
优点:容易去复用代码,适合扩展,增加程序复杂程度
缺点:
(1)菱形继承是继承的缺点之一,并且程序耦合性较高
(2)代码白盒复用,父类实现暴露给了子类,破坏封装
(3)父类代码修改时,子类可能也得修改,维护难度增加
(4)不支持动态扩展,父类在编译期就已经确定
组合:
优点:
(1)代码黑盒复用,耦合度低,代码维护性好
(2)整体类与局部类之间松耦合,相互独立且,支持扩展
(3)支持动态扩展,可在运行时根据具体对象选择不同类型的组合对象
缺点:在设计类时需要设计所有局部类,导致系统的对象很多