
例如大街上有很多车,它们都有各自的品牌,颜色,价格,拥有者,所属省等等共有的属性,但是每辆车都有它们各自不同的功能和作用,假如用代码把这些车归类,使用继承就可以大大提高效率,让所有车都继承一个同一个父类,它们各自不同的地方,再在父类的基础上增加,共同的属性全部从父类继承下来。

继承关系有三类,分别为公有继承(public),保护继承(protected),私有继承(private),但是最最常用的只有公有继承,另外两种继承方式都很少用。
class 派生类名 : 继承方式 基类
其中访问方式为public 就是公有继承 protected 就是保护继承 private就是私有继承

这里的访问方式的意思是继承下来的成员变量的新的访问权限。

总结:
基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是
被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能
访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
基类的其他成员在子类的访问方式=min(成员再基类的访问限定符,继承方式)
public > protected > private
子类中会继承父类中的所有成员(私有成员也会继承下来,只是语法限制了其在子类中不可见),子类还有自己额外的成员,那么子类就具有父类中的所有成员,相反父类不具有子类中的所有成员。那么子类可以赋值给父类,但是父类不可以赋值给子类。
子类对象可以赋值给父类对象、父类指针(用子类地址赋值)、父类引用。 这个过程中就会产生切片或者叫切割,就是把子类中属于父类的那部分切出来赋值给父类。

基类的指针是可以通过强制类型转换为派生类指针的,但是当访问该指针指向对象的数据的时候,只有该基类指针指向的是派生类对象的时候才是安全的否则就会有越界(基类中不具有派生类中所有的成员)。
相关代码:
class Person
{
protected :
string _name;
string _sex;
int _age;
};
class Student : public Person
{
public :
int _score ;
};
void Test ()
{
Student sobj ;
// 子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//基类对象不能赋值给派生类对象
sobj = pobj;
//基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换可以
ps1->_score = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_score = 10;
}
在继承中,基类和子类都有自己独立的作用域。
class Person
{
public:
void func()//父类中的func函数
{
cout<<"Person::func()"<<endl;
}
protected:
string _name;
size_t _age;
string _sex;
}
class Student:public Person//继承自person
{
public:
void func()//子类中的func函数
{
cout<<"Student::func()"<<endl;
}
private:
size_t _socre;
}
解答:上面两个父子类中有同名函数,但是他们不是构成的重载,而是叫隐藏或者重定义。因为重载必须是同一作用域内的两个同名函数,但是彼此的参数列表不同(参数个数,顺序,类型),但是基类和派生类是有着自己独立的作用域的,因此他们不是同一作用域的同名函数,不构成重载关系!!!
注意:
1.成员函数的隐藏,只需要函数名相同即可,不需要参数列表也一模一样
在子类成员函数中,可以通过使用指定访问限定符的方式显式的访问父类中的成员变量或函数
派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函
数,则必须在派生类构造函数的初始化列表阶段显示调用
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
派生类的operator=必须要调用基类的operator=完成基类的复制
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
派生类对象初始化先调用基类构造再调派生类构造
派生类对象析构清理先调用派生类析构再调基类的析构

例:
//普通函数的继承是继承实现,虚函数的继承是继承接口,实现需要子类重写。
class Person
{
public:
Person()
{
cout << "Person()" << endl;
}
virtual ~Person()
{
cout << "~Person()" << endl;
delete[] p;
}
protected:
char* p = new char[10]{ 's','d' };
};
class Teacher :public Person
{
public:
//如果父类的析构函数是虚函数,此时只要子类的析构函数定义 无论是否加virtual都会与父类的析构函数构成重写,虽然两者的函数名
//不相同,不符合重写规则,但是实际上编译器对二者的析构函数函数名做了统一处理,将其全都命名为distructor 从而构成重写
Teacher()
{
cout << "Teacher" << endl;
}
virtual ~Teacher()
{
//不用在里面在调用基类的析构函数,因为派生类析构函调用完成后会自动地调用基类的析构函数
cout << "~Teacher()" << endl;
delete[] s;
}
private:
char* s = new char[10]{ 'f','g' };
};
void test()
{
//Person p;
Teacher t;
}

友元关系是不能被继承的。
class Student;//提前声明
class Person
{
public:
//友元关系是不可以继承的!!!
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
//下面这行注释掉,调用Display会报错, 解开注释正常运行!!!
//friend void Display(const Person& p, const Student& s);
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
class A
{
public:
A()
{
++_count;
}
void func()
{
cout << _count << endl;
}
protected:
static int _count;
};
int A::_count = 0;
class B :public A
{};
class C :public A
{};
void test()
{
A a;
a.func();
B b;
b.func();
B b1;
b.func();
C c;
c.func();
}

一个子类只有一个直接父类的继承就是单继承。

格式
class A{}
class B:public A{}
class C:public B{}
class D:public C{}
一个子类有两个或两个以上的直接父类的继承叫做多继承。

格式:
class D:public A,public B,public C
{
}
下面的结构就是菱形继承

菱形继承中B C 继承自A,那么B C 中的成员有继承自A的_a 和自己的成员变量,D又是继承自B C ,那么其内部就会有B C 中的成员(继承下来的和本身的)和自己的成员_d
A B C D 中的类的成员图解:

根据上图可见,D中会有两个_a变量,分别来自B继承A的 _a 和 C继承A的 _a ,那么就会存在数据冗余和二义性的问题。
数据冗余:在一个对象中会有两份相同的成员。
二义性:以上图为例,D中有两个_a ,继承自B和C,那么如果想访问D中的 _a,直接访问的话那么就会使得编译器分不清到底是访问的是继承自B中的 _a 还是继承自 C 中的 _a ,这就是由数据冗余导致的二义性问题。需要指定类域去显式访问。
错误访问方式:

相关代码:
//菱形继承问题
namespace zh6
{
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;
};
void test()
{
//菱形继承 就会导致最底下的一层里会有两个最顶层里的成员变量或函数,会有二义性和数据冗余
//二义性就是A里有成员变量_a ,D继承了B C ,B C 继承自A 那么会导致D中会有两个_a ,分别来自B和C
//如果D中想要访问_a 那么就会出现分不清是访问继承自B中的_a,还是继承自C中的_a,出现二义性。
// 数据冗余就是相同的数据会继承两份。 如果换成虚继承就不会有这些问题了
D d;
//d._a = 9;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
}
对象成员模型
菱形继承会继承B 和 C 中的成员_a ,那么d对象中就会有两块空间,分别是B C对象的 _a

上面的菱形继承会有数据冗余和二义性的问题,但是如果换成菱形虚拟继承就不会有上面的问题。因为公共的数据只会有一份。
//格式
class A
{...}
class B:virtual public A
{...}
class C:virtual public A
{...}
class D:public B,public C
{...}
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;
};
void test()
{
//菱形继承 就会导致最底下的一层里会有两个最顶层里的成员变量或函数,会有二义性和数据冗余
//二义性就是A里有成员变量_a ,D继承了B C ,B C 继承自A 那么会导致D中会有两个_a ,分别来自B和C
//如果D中想要访问_a 那么就会出现分不清是访问继承自B中的_a,还是继承自C中的_a,出现二义性。
// 数据冗余就是相同的数据会继承两份。 如果换成虚继承就不会有这些问题了
D d;
//d._a = 9;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
对象成员模型:
下图的内存2是对象d的成员模型,可以观察到其里面有数字2 3 4 5,和两个地址,这两个地址分别是对象B 和C的虚基表指针,分别指向一张虚基表,虚基基表中存的是两个偏移量,第一个偏移量是该虚基表指针和虚函数表指针的偏移量,第二个偏移量是该对象和公共对象的偏移量。

菱形虚拟继承解决了数据冗余问题,将两个对象中公共的数据从两个对象中剥离出来,另外再两个对象外用一块独立的内存存放该公共数据,省去了两个对象中都存着各自的公共数据,造成数据冗余和二义性。