<<从0到1学C++>> 第6篇 继承和派生

本篇要学习的内容和知识结构概览


继承和派生的概念

派生

经过特殊化已有的类来创建新类的过程, 叫作”类的派生”, 原有的类叫作”基类”, 新创建的类叫作”派生类”. 从类的成员角度看, 派生类自动地将基类的全部成员做为本身的成员, 这叫作”继承”. 基类和派生类也能够叫作”父类”和”子类”, 也能够叫作”通常类”和”特殊类”.bash

继承

类的继承是指派生类继承基类的数据成员和成员函数. 继承用来表示类属关系, 不能将继承理解为构成关系函数

继承派生的做用

  • 增长新的成员(数据成员和成员函数)
  • 从新定义已有的成员函数
  • 改变基类成员的访问权限

单一继承

通常形式

class 派生类名: 访问控制 基类名 {	private:		成员声明列表	protected:		成员声明列表	public:		成员声明列表}复制代码

"冒号"表示新类是哪一个基类的派生类学习

"访问控制"指继承方式. 三个方式: public, protected, privateui

派生类的构造函数和析构函数

// 基类
class Point {
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
        cout << "init Point" << endl;
    }
    void showPoint() {
        cout << "x = " << x << ", y = " << y << endl;
    }
    ~Point() {
        cout << "delete Point" << endl;
    }
};

// 派生类
class Rect: public Point {
    int w;
    int h;
    
    public:
    // 调用基类的构造函数对基类成员进行初始化
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
        cout << "init Rect" << endl;
    }
    void showRect() {
        cout << "w = " << w << ", h = " << h << endl;
    }
    ~Rect() {
        cout << "delete Rect" << endl;
    }
};

int main() {
    Rect r(3, 4, 5, 6);
    r.showPoint();
    r.showRect();
    
    /** 输出结果
     init Point // 当定义一个派生类的对象时, 首先调用基类的构造函数, 完成对基类成员的初始化
     init Rect // 而后执行派生类的构造函数, 完成对派生类成员的初始化
     x = 3, y = 4 // 调用基类成员函数showPoint();
     w = 5, h = 6 // 调用派生类成员函数showRect();
     delete Rect // 构造函数的执行顺序和构造函数的执行顺序相反, 首先调用派生类的析构函数
     delete Point // 其次调用基类的析构函数
     */
}
复制代码

类的保护成员

若是但愿Rect中的showRect()函数能够一次显示x, y , w, h. 咱们直接修改showRect()函数是不行的.spa

void showRect() {
	cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
}复制代码

报错 error: 'x' is a private member of ‘Point' 'y' is a private member of ‘Point', x, y为Point类的私有成员, 公有派生时, 在Rect类中是不可访问的指针

咱们还须要将基类Point中的两个成员声明为protected的属性code

像这样cdn

// 基类
class Point {
    // 公有数据成员
    protected:
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
        cout << "init Point" << endl;
    }
    void showPoint() {
        cout << "x = " << x << ", y = " << y << endl;
    }
};

// 派生类
class Rect: public Point {
    int w;
    int h;
    
    public:
    // 调用基类的构造函数对基类成员进行初始化
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
        cout << "init Rect" << endl;
    }
    
    // 公有派生, Point类中的受保护数据成员, 在Rect类中也是受保护的, 因此能够访问  // 而经过公有继承的基类私有的成员, 在派生类中是不可被访问的    void showRect() {
        cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
    }
};

int main() {
    Rect r(3, 4, 5, 6);
    r.showPoint();
    r.showRect();
}复制代码

访问权限和赋值兼容规则

在根类中, 对于成员的访问级别有三种, public, protected, private对象

在派生类中, 对于成员的访问级别有四种, public(公有), protected(受保护), private(私有), inaccessible(不可访问)blog

  • 公有派生和赋值兼容规则

在公有派生状况下, 基类成员的访问权限在派生类中基本保持不变

  1. 基类的公有成员在派生类中仍然是公有的
  2. 基类的保护成员在派生类中仍然是受保护的
  3. 基类的不可访问的成员在派生类中仍然是不可访问的
  4. 基类的私有成员在派生类中变成了不可访问的

总结: 在公有派生的状况下, 经过派生类本身的成员函数能够访问继承过来的公有和保护成员, 可是不能访问继承来的私有成员, 由于继承过程的私有成员, 变成了第四个级别, 不可访问的.

赋值兼容规则:

在公有派生的状况下, 一个派生类的对象能够做为基类的对象来使用的状况. 

像这样

// 基类
class Point {
    // 这里声明成员属性为受保护的
    protected:
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
    }
    
    void show() {
        cout << "x = " << x << ", y = " << y << endl;
    }
};

// 派生类
class Rect: public Point {
    int w;
    int h;
    
    public:
    // 调用基类的构造函数对基类成员进行初始化
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
    }
    
    void show() {
        cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
    }
};

int main() {
    Point a(1, 2);
    Rect b(3, 4, 5, 6);
    a.show();
    b.show();
    
    Point & pa = b; // 派生类对象初始化基类的引用
    pa.show(); // 实际调用基类的show()函数
    
    Point * p = &b; // 派生类对象的地址赋值给指向基类的指针
    p -> show(); // 实际也是调用基类的show()函数
    
    Rect * pb = &b; // 派生类指针
    pb -> show(); // 调用派生类的show()函数
    
    a = b; // 派生类对象的属性值, 更新基类对象的属性值
    a.show(); // 调用基类的show()函数
    /**
     x = 1, y = 2
     x = 3, y = 4, w = 5, h = 6
     x = 3, y = 4
     x = 3, y = 4
     x = 3, y = 4, w = 5, h = 6
     x = 3, y = 4
     */
}
复制代码

  • “isa”和”has-a“的区别

继承和派生 isa

好比一个Person类, 派生出一个Student类, 咱们能够说Student就是Person, 也就是 Student isa Person, 而反过来则不行.

一个类用另外一个类的对象做为本身的数据成员或者成员函数的参数 has-a

像这样

// 地址类
class Address {};
class PhoneNumber {};

// 职工类
class Worker {
    String name;
    Address address;
    PhoneNumber voiceNumber;
};
复制代码

表示一个Worker对象有一个名字, 一个地址, 一个电话号码, has-a的关系, 包含的关系

  • 私有派生

经过私有派生, 基类的私有和不可访问成员在派生类中是不可访问的, 而公有和保护成员这里就成了派生类的私有成员

// 基类
class Point {
    int x;
    int y;
    
    public:
    Point(int a, int b) {
        x = a;
        y = b;
    }

    void show() {
        cout << "x = " << x << ", y = " << y << endl;
    }
};

// 派生类
class Rect: private Point {
    int w;
    int h;
    
    public:
    Rect(int a, int b, int c, int d):Point(a, b) {
        w = c;
        h = d;
    }
    
    void show() {
        Point::show(); // 经过私有继承, Point类中的公有成员show(), 在Rect中为私有
        cout << "w = " << w << ", h = " << h << endl;
    }
};

class Test: public Rect {
    
    public:
    Test(int a, int b, int c, int d):Rect(a, b, c, d) {
        
    }
    void show() {
        Rect::show();
//        Point::show();
        /** error: 'Point' is a private member of ‘Point’
         标明: 不可访问基类Point中的成员
         Rect类私有继承自Point类, 因此Point中的私有成员x, 私有成员y, 在Rect类中为不可访问: Point类中公有成员show(), 在Rect中变私有
         Test类公有继承自Rect类, 因此在Rect中成员x, 成员y, 仍然是不可访问, Rect::show()仍是public, 可是Point::show()不可访问 */
    }
};
复制代码

由于私有派生不利于进一步派生, 于是实际中私有派生用得并很少

  • 保护派生

保护派生使原来的权限都降一级使用, 即private变为不可访问, protected变为private, public变为protected. 限制了数据成员和成员函数的访问权限, 所以在实际中保护派生用得也很少.

好比: 咱们在上个例子中, Rect类保护派生于Point, 则在Test类中Point::show();就可使用啦!

多重继承

一个类从多个基类派生

格式

class 派生类名: 访问控制 基类名1, 访问控制 基类名2, … {
	定义派生类本身的成员
}复制代码

像这样

// 基类A, 也叫根类
class A {
    int a;
    
    public:
    void setA(int x) {
        a = x;
    }
    
    void showA() {
        cout << "a = " << a << endl;
    }
};

// 基类B, 也叫根类
class B {
    int b;
    
    public:
    void setB(int x) {
        b = x;
    }
    
    void showB() {
        cout << "b = " << b << endl;
    }
};

// 多重继承, 公有继承自类A, 私有继承自类B
class C: public A, private B {
    int c;
    
    public:
    void setC(int x, int y) {
        c = x;
        setB(y);
    }
    
    void showC() {
        showB();
        cout << "c = " << c << endl;
    }
};

int main() {
    C c;
    c.setA(53); // 调用基类setA()函数
    c.showA(); // 调用基类showA()函数
    
    c.setC(55, 58); // 调用派生类C的setC()函数
    c.showC(); // 调用派生类C的showC()函数
    
    // 派生类C私有继承自基类B, 因此基类B中私有成员b, 在派生类C中不可访问, 基类B中公有成员setB(), showB()在派生类C中变私有. 在main()函数中不可访问
//    c.setB(60); // error: 'setB' is a private member of 'B'
//    c.showB(); // 'showB' is a private member of 'B'
    /**
     a = 53
     b = 58
     c = 55
     */
}
复制代码

二义性及其支配规则

对基类成员的访问必须是无二义性的, 若是一个表达式的含义能够解释为能够访问多个基类中的成员, 则这种对基类成员的访问就是不肯定的, 称这种访问具备二义性

做用域分辨符和成员名限定

// 基类A, 也叫根类
class A {
    public:
    void func() {
        cout << "A func" << endl;
    }
};

// 基类B, 也叫根类
class B {
    public:
    void func() {
        cout << "B func" << endl;
    }
    
    void gunc() {
        cout << "B gunc" << endl;
    }
};

// 多重继承
class C: public A, public B {
    public:
    void gunc() {
        cout << "C gunc" << endl;
    }
    
    void hunc() {
        /**
         这里就具备二义性, 它便可以访问A类中的func(), 也能够访问类B中的func()
         */
//        func(); // error: Member 'func' found in multiple base classes of different types
    }
    
    void hunc1() {
        A::func();
    }
    
    void hunc2() {
        B::func();
    }
};

int main() {
    C c;
//    c.func(); // 具备二义性
    c.A::func();
    c.B::func();
    c.B::gunc();
    c.C::gunc();
    
    c.gunc();
    c.hunc1();
    c.hunc2();
    
    /** 输出结果
     A func
     B func
     B gunc
     C gunc
     
     C gunc // 若是基类中的名字在派生类中再次声明, 则基类中的名字就被隐藏. 若是咱们想要访问被隐藏的基类中的成员则使用做用域分辨符B::gunc();
     A func
     B func
     */
}复制代码

格式: 类名::标识符

:: 为做用域分辨符, "类名"能够是任一基类或派生类名, “标识符”是该类中声明的任一成员名

派生类支配基类的同名函数

若是派生类定义了一个同基类成员函数同名的新成员函数(具备相同参数表的成员函数), 派生类的新成员函数就覆盖了基类的同名成员函数.

在这里, 直接使用成员名只能访问派生类中的成员函数, 使用做用域运算符, 才能访问基类的同名成员函数.

派生类中的成员函数名支配基类中的同名的成员函数名, 这称为名字支配规则.

若是一个名字支配另外一个名字, 则两者之间不存在二义性, 当选择该名字时, 使用支配者的名字.

例如上个例子中

c.gunc() // 输出”C gunc”, 基类B中的gunc成员函数被支配了
c.B::gunc(); // 加上做用域分辨符, 来使用被支配的成员复制代码

来自一张表的总结


总结

C++中的多重继承可能更灵活, 而且支持三种派生方式, 咱们在学习一门语言的时候, 更应该把精力放在它的特性上面, 不该该用什么语言, 都用本身所擅长语言的思考方式, 实现方式等, 要学会发挥该语言的优点所在. 

本系列文章会持续更新! 你们踊跃的留下本身的脚印吧!

👣👣👣👣👣👣👣👣