继承与派生概念:
派生是一种创建新类的方式,在原来已有被继承类的基础上,不影响原来的类,不改变原来类的代码,实现对于功能的扩展,在原有被继承类的基础上快速增加新的功能;新创建的类可以来源于一个类或多个类,即新类可以继承一个或多个类;
继承描述的是类与类之间的关系,新创建的类被称为派生类或子类,被继承的类称为基类或父类;
通常使用父类->子类或基类->派生类这两种固定组合保持风格统一,不要使用父类->派生类和基类->子类这2种组合,不便于阅读;
继承分为单继承和多继承,其语法格式如下:
单继承
class 基类名
{
};
class 派生类名 : 继承方式(权限) 基类名
{
};
多继承
class 基类名1
{
};
class 基类名2
{
};
class 基类名n
{
};
class 派生类名 : 继承方式1 基类名1, 继承方式2 基类名2, 继承方式n 基类名n
{
};
继承权限:
1.共有继承public
2.私有继承private
3.保护继承protected
在 C++ 中,protected 是一种访问控制修饰符,它用于指定类中的成员属性或方法的访问权限。使用 protected 来修饰的成员可以被其所在类及其派生类的成员访问,但是在类外部是不可以访问的。
具体来说,使用 protected 修饰的成员在类外部是不能直接访问的,但是可以通过其派生类来访问。也就是说,在派生类中可以访问基类的 protected 成员。而在基类中,其他类无法访问基类的 protected 成员,只能够访问 public 成员。
总之,protected 成员可以被派生类访问,是为了方便派生类继承并重用基类的代码。它可以使得基类的一些属性和方法只对其子类可见,保证了类的封装性和安全性。
注意:继承是子类继承父类全部的成员(即所有内存空间),但是访问权限要根据父类的成员属性和继承方式共同决定,见下文:
共有继承public | 私有继承private | 保护继承proteed | |
父类public成员 | 子类可以获得public访问权限 | 子类可以获得private访问权限 | 子类可以获得protected访问权限 |
父类private成员 | 子类可以获得不可访问 | 子类可以获得不可访问 | 子类可以获得不可访问 |
父类protected成员 | 子类可以获得protected访问权限 | 子类可以获得private访问权限 | 子类可以获得protected访问权限 |
规律:
父类的公有成员被子类继承后,子类对父类拥有成员的访问权限根据子类的继承方式设置与继承方式相同的访问权限;
父类的私有成员被子类继承后,不管子类用什么方式去继承都不可访问;
父类的保护成员被子类继承继承后,按照父类成员访问权限与子类继承权限种两者最严格的方式设置访问权限;
多级继承:分析直接父子类就可以,因为继承过程种数据不会丢失。
// 继承与派生该概念以及访问权限
#include <iostream>
#include <string>using namespace std;class Father {
public:Father() {};~Father() {};public:int pub;void pub_func() {};
private:int pri;void pri_func() {};
protected:// 在 C++ 中,protected 是一种访问控制修饰符,它用于指定类中的成员属性或方法的访问权限。// 使用protected修饰的成员可以被其所在类及其派生类的成员访问,但是在类外部是不可以访问的。// 具体来说,使用protected修饰的成员在类外部是不能直接访问的,但是可以通过其派生类来访问。// 也就是说,在派生类中可以访问基类的protected成员。而在基类中,其他类无法访问基类的protected成员,只能够访问public成员。// 总之,protected成员可以被派生类访问,是为了方便派生类继承并重用基类的代码。// 它可以使得基类的一些属性和方法只对其子类可见,保证了类的封装性和安全性。int pro;void pro_func() {};
};class Father1 {
public:Father1() {};~Father1() {};
public:int pub;void pub_func() {};
private:int pri;int pri1;void pri_func() {};
protected:int pro;void pro_func() {};
};// 最基本的继承关系写法
class Son1 : public Father {
public:Son1() {};~Son1() {};
public:
private:
protected:
};// 最基本的继承关系写法
class Son2 : public Father {
public:Son2() {};~Son2() {};
public:int num;void testFunc() {// 继承后访问权限测试// 父类的public成员可以在类里边和类外访问this->pub;this->pub_func();// 父类的protected成员可以在继承类里边访问,但是不能在类外访问this->pro;this->pro_func();// 父类的private成员不能访问// this->pri;// this->pri_func();}
private:
protected:
};class Son3 : public Father, public Father1
{};int main()
{cout << "sizoef(Father) = " << sizeof(Father) << endl;// 子类Son1继承了父类的一切,但是没有增加新的成员,所以内存大小与父类一致cout << "sizoef(Son1) = " << sizeof(Son1) << endl;// 子类Son2继承了父类的一切,自己有增加新的东西,内存大小在父类基础上增加了新增成员的大小cout << "sizoef(Son2) = " << sizeof(Son2) << endl;// 子类Son2继承了2个父类的一切,内存大小为2个父类之和cout << "sizeof(Son3) = " << sizeof(Son3) << endl;Son2 obj_son2;// public方式继承的子类对象可以访问父类public成员obj_son2.pub;obj_son2.pub_func();// public方式继承的子类对象不可以访问父类private成员// obj_son2.pri;// obj_son2.pri_func();// public方式继承的子类对象不可以在类外访问父类protected成员// obj_son2.pro;// obj_son2.pro_func();return 0;
}
继承与派生类的关系:
1.派生类的构成
构造(包括拷贝构造)和析构不会继承,因为任何类都默认会有构造和析构函数,其他的都会继承;
2.父类不会调用子类新增的成员;
3.如果子类新增成员名称和父类已有成员相同,那么子类会将父类成员隐藏,使用父类成员时通过对应父类的类名加作用域运算符的形式访问,如果子类有多个父类时访问方式同理;
子类是父类的对象,但是父类不是子类的对象;
也就是说派生类对象可以当作基类对象使用,因为派生类继承了基类的所有成员,基类有的派生类都有,直接用派生类就可以,即可以用父类的地方就可以用子类;
// 继承类与基类之间的成员访问关系
// 1.派生类的构成
// 构造(包括拷贝构造)和析构不会继承,因为任何类都默认会有构造和析构函数,其他的都会继承;
// 2.父类不会调用子类新增的成员;
// 3.如果子类新增成员名称和父类已有成员相同,那么子类会将父类成员隐藏,
// 使用父类成员时通过对应父类的类名加作用域运算符的形式访问,如果子类有多个父类时访问方式同理;
#include <iostream>
#include <string>using namespace std;class Father {
public:Father() {val = 888;num = 889;};~Father() {};
public:int val;int num;
private:
protected:
};class Mother {
public:Mother() {val = 666;num = 667;};~Mother() {};
public:int val;int num;
private:
protected:
};class Son1 : public Father
{
public:Son1() {num = 111;};~Son1() {};
public:int num;
private:
protected:
};class Son2 : public Father, public Mother
{
public:Son2() {num = 222;};~Son2() {};
public:int num;
private:
protected:
};int main()
{Son1 obj_son1;// 子类对象已经将父类的同名成员隐藏,子类直接访问时只能访问自己新增的同名成员cout << "obj_son1.num = " << obj_son1.num << endl;// 子类需要访问父类同名成员时需要加父类类名加作用域运算符加同名成员进行访问cout << "obj_son1.Fater::num = " << obj_son1.Father::num << endl;Son2 obj_son2;cout << "obj_son2.num = " << obj_son2.num << endl;// 子类继承自多个父类时,需要访问某个父类同名成员时需要加对应父类类名加作用域运算符加同名成员进行访问cout << "obj_son2.Fater::num = " << obj_son2.Father::num << endl;cout << "obj_son2.Mother::num = " << obj_son2.Mother::num << endl;return 0;
}
// 派生类与基类之间的关系
// 子类是父类的对象,但是父类不是子类的对象;
// 也就是说派生类对象可以当作基类对象使用,因为派生类继承了基类的所有成员,
// 基类有的派生类都有,直接用派生类就可以,即可以用父类的地方就可以用子类。
#include <iostream>
#include <string>using namespace std;class Father
{
public:Father() {};~Father() {};
private:
protected:
};class Son : public Father
{
public:Son() {};~Son() {};
};int main()
{Father obj_father;Son obj_son;// 子类对象可以给父类对象赋obj_father = obj_son;// 但是父类对象不能给子类对象赋值// obj_son = obj_father;Father *p_father;Son *p_son;// 父类指针指向父类对象okp_father = &obj_father;// 父类指针指向子类对象okp_father = &obj_son;// 子类指针指向子类对象okp_son = &obj_son;// 子类指针不可以指向父类对象// p_son = &obj_father;return 0;
}
基类与派生类构造析构顺序
// 继承类与父类之间构造与析构调用顺序
// 如果要创建子类对象就要先调用父类的构造函数
// 父类带参构造以及父类构造传参#include <iostream>
#include <string>
using namespace std;class Father {
public:// 没有参数默认val为设定值111Father() : val(111) {cout << "父类构造" << endl;};// 父类的带参构造, 将val的值设置为输入参数n// 由于构造函数不能被主动调用,由系统自动调用,需要使用成员初始化列表进行构造函数传参Father(int n) : val(n) {cout << "父类的带参构造" << endl;};~Father() {cout << "父类析构" << endl;};
public:const int val;
private:
protected:
};class Son : public Father
{
public:Son() {num = 666;cout << "子类构造" << endl;};// 子类的带参构造,由于构造函数不能被主动调用,由系统自动调用,所以需要使用成员初始化列表进行构造函数传参Son(int n, int v) : Father(v), num(n) {// 以下这种做法不是构造函数传参,而是创建了一个无名的Father对象// Father(v);cout << "子类的带参构造" << endl;}~Son() {cout << "子类析构" << endl;};
public:int num;
private:
protected:
};int main()
{Son obj_son;cout << "obj_son.val = " << obj_son.val << ", obj_son.num = " << obj_son.num << endl;// 创建子类对象时,先调用父类构造再调用子类构造// 销毁对象时,像调用子类析构在调用父类析构// 遵循的原则是:先构造的后析构// 执行程序后打印结果如下// 父类构造// 子类构造// obj_son.val = 111, obj_son.num = 666// 子类析构// 父类析构Son obj_son1(3, 4);cout << "obj_son1.val = " << obj_son1.val << ", obj_son.num = " << obj_son1.num << endl;return 0;
}
棱形继承
// 棱形继承
// 棱形继承是特殊的情况,也是很容易出现问题的一种情况
// 棱形继承的情况下,最终子类中只会有一份来自父类的内存
#include <iostream>
#include <string>using namespace std;class A
{
public:int num;
};class A1 : public A
{
public:int a1;
};class A2 : public A
{
public:int a2;
};// 棱形继承
class AA : public A1, public A2
{
public:int aa;
};// 在这里添加virtual描述,直接使用A3A4时不会产生任何影响
// virtual描述的虚继承通过虚指针标记A3A4从基类A中继承了那些成员
class A3 : virtual public A
{
public:int a3;
};class A4 : virtual public A
{
public:int a4;
};// A3A4通过virtual修饰,虚指针已经标记该类继承了基类A中的哪些成员
// AB使用棱形继承的方式继承A时只会得到一份A类的成员,不会造成重复继承导致内存浪费
// 使用虚继承后A3A4相比普通继承方式A1A2会多出4个字节,这4个字节用于存放虚指针,
// 在类A数据量较大时使用只有4字节的虚指针方式可以避免重复继承类A成员内存开销减小
// AB中也会有一个同样的虚指针
// 棱形继承的情况下,最终子类中只会有一份来自父类的内存
class AB : public A3, public A4
{
public:int ab;
};int main()
{AA obj_aa;// 访问权限obj_aa.aa;obj_aa.a1;obj_aa.a2;// AA没有直接继承A,不是AA的直接父类,所以无法直接访问A类中的成员// obj_aa.num;// aa继承了A1和A2,可以通过这2个直接父类访问A1和A2继承的基类A中的成员obj_aa.A1::num;obj_aa.A2::num;// aa没有直接继承A,所以无法直接访问A类中的成员// obj_aa.A::num;// virtual虚继承方式AB obj_ab;obj_ab.A3::num;obj_ab.A4::num;// 通过虚继承方式可以不需要再使用类名进行限定,指定访问类A中的成员obj_ab.num;obj_ab.A3::a3;obj_ab.A4::a4;obj_ab.a3;obj_ab.a4;return 0;
}