C++ 继承


引入

在软件开发中,我们常常会遇到多个类拥有相似的成员或功能的情况。比如:

class Cat {
public:
    void eat() { cout << "Cat is eating" << endl; }
    void sleep() { cout << "Cat is sleeping" << endl; }
};

class Dog {
public:
    void eat() { cout << "Dog is eating" << endl; }
    void sleep() { cout << "Dog is sleeping" << endl; }
};

可以看到,Cat 和 Dog 中有大量重复代码。为了避免代码冗余、提升可维护性,我们可以将共同的部分提取出来,放到一个更通用的类中(比如 Animal),然后让 Cat 和 Dog 去继承这个基类。

继承是 C++ 面向对象编程三大特性之一(封装、继承、多态),它的主要作用包括:
代码复用:派生类自动拥有基类的成员函数和成员变量。
建立层次关系:通过 is-a 关系组织类结构。
实现多态基础:继承 + 虚函数 实现运行时多态(如虚函数表机制)。

使用继承,我们可以从通用到具体地构建系统,例如:

class Animal {
public:
    void eat() { cout << "Animal is eating" << endl; }
};

class Cat : public Animal {
public:
    void meow() { cout << "Meow!" << endl; }
};

class Dog : public Animal {
public:
    void bark() { cout << "Bark!" << endl; }
};

现在,Cat 和 Dog 不再重复 eat(),并且还能定义各自特有的行为,结构更清晰、扩展更方便。


一、继承的基本语法:

#include <iostream>
class Base {
public:
    void show() { std::cout << "Base" << std::endl; }
};

class Derived : public Base {
    // 派生类没有定义任何成员,但自动继承了 Base 的成员函数
};

int main() {
    Derived d;
    d.show(); // 调用的是 Base 中的 show()
    return 0;
}

语法结构解释:

class 派生类名 : 继承方式 基类名 {
    // 派生类定义
};

Base 是基类(父类)
Derived 是派生类(子类)
public Base 表示 公有继承,也就是 继承方式是 public

二、继承方式和基类成员

继承的类型:公有继承 public、保护继承 protected、私有继承 private

在这里插入图片描述

解释:protected 是对子类开放的封装,private 是只对当前类开放的封装。

class Base {
public:
    int x = 1;
protected:
    int y = 2;
};

class Derived : protected Base {
public:
    void test1() {
        x = 10; // x 变成 protected,可以访问
        y = 20; // y 本来就是 protected,可以访问
    }
};

class SubDerived : public Derived {
public:
    void test2() {
        x = 30; // x 是 protected,可以访问
        y = 40; // y 是 protected,可以访问
    }
};

上面是 protected 继承,子类 SubDerived 能访问 x 和 y,虽然它们对外部是隐藏的。

class Derived2 : private Base {
public:
    void test1() {
        x = 10; // x 变成 private,Derived2 自己能访问
        y = 20; // OK
    }
};

class SubDerived2 : public Derived2 {
public:
    void test2() {
        // x = 30; //  错误,x 是 private,不可访问
        // y = 40; //  错误
    }
};

在这里插入图片描述

上面是 private 继承,子类 SubDerived2 无法访问 x 和 y,它们被“锁死”在 Derived2 中。

(2)基类和派生类对象赋值转换

2.1 基类对象赋值给派生类对象(不允许)

基类和派生类之间的赋值通常是不允许的,因为派生类包含了基类之外的成员,直接赋值可能会丢失派生类中的信息

#include<iostream>
using namespace std;

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int v1, int v2) : Base(v1), derivedValue(v2) {}
};

int main() {
    Base b(10);
    Derived d1(20, 30);

    // 这行代码会报错
    // d1 = b; // 错误:不能将基类对象赋值给派生类对象

    return 0;
}

在这里插入图片描述

基类 Base 和派生类 Derived 在内存布局上不同。派生类 Derived 除了继承自基类 Base 外,还可能有额外的数据成员。
直接赋值基类对象到派生类对象,会丢失派生类的额外成员,因此编译器不允许这么做。

2.2派生类对象赋值给基类对象(允许,但只拷贝基类部分)

派生类对象赋值给基类对象(允许,但只拷贝基类部分)

#include<iostream>
using namespace std;

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int v1, int v2) : Base(v1), derivedValue(v2) {}
};

int main() {
    Derived d(20, 30);  // 派生类对象
    Base b(0);

    b = d;  // 派生类对象赋值给基类对象

    cout << "Base value: " << b.baseValue << endl;
    // 基类部分被赋值了,输出: Base value: 20

    return 0;
}

在这里插入图片描述

在这个例子中,d 是 Derived 类型的对象,它被赋值给 b,即基类类型的对象。
赋值后,b.baseValue 被正确地赋值为 d.baseValue,但是 d.derivedValue 不能拷贝给 b,因为 b 并不包含 derivedValue 成员。

2.3基类指针或引用指向派生类对象(允许)(多态章节讲)

基类的指针或引用可以指向派生类对象,这种赋值称为 多态,在运行时通过虚函数实现动态绑定。这样可以使基类指针或引用操作派生类的对象。

#include<iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base class" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class" << endl;
    }
};

int main() {
    Base* basePtr;
    Derived d;

    // 基类指针指向派生类对象
    basePtr = &d;

    basePtr->show();  // 调用的是 Derived 类中的 show() 方法,输出: Derived class

    return 0;
}

在这里插入图片描述

基类的指针 basePtr 可以指向派生类的对象 d。
通过基类指针调用虚函数 show() 时,实际上调用的是派生类 Derived 中的重写函数,因为虚函数实现了动态绑定。

三、继承中名字隐藏

作用域是独立的:
父类和子类有自己各自的成员变量和函数,即使重名也互不影响,但子类优先访问自己的作用域。

函数名相同就构成隐藏:
子类一旦定义了一个同名函数,哪怕参数不同,也会屏蔽所有父类同名函数,想要使用需要指定类域。

#include<iostream>
using namespace std;

class Base {
public:
    int x = 1;

    void func() {
        cout << "Base::func()" << endl;
    }

    void func(int n) {
        cout << "Base::func(int): " << n << endl;
    }
};

class Derived : public Base {
public:
    int x = 2;  // 与 Base::x 同名,隐藏

    void func() {  // 同名函数,隐藏了 Base::func 和 Base::func(int)
        cout << "Derived::func()" << endl;
    }

    void test() {
        cout << "x = " << x << endl;            // 访问的是 Derived::x
        cout << "Base::x = " << Base::x << endl; // 显示访问基类成员

        func();             // 调用 Derived::func()
        Base::func();       // 显示调用 Base::func()
        Base::func(100);    // 调用重载版本 Base::func(int)
    }
};

int main() {
    Derived d;
    d.test();

    return 0;
}

在这里插入图片描述

四、派生类的六个默认成员函数

(4.1)派生类的构造函数必须调用基类构造函数

默认情况下,派生类的构造函数会自动调用基类的默认构造函数
如果基类没有默认构造函数,必须显式调用某个构造函数

#include<iostream>
using namespace std;

class Base {
public:
    Base(int x) {
        cout << "Base构造函数: " << x << endl;
    }
};

class Derived : public Base {
public:
    // Derived 默认构造函数,必须显式初始化 Base
    Derived() : Base(100) {
        cout << "Derived构造函数" << endl;
    }
};

int main() {
    Derived d;
}

在这里插入图片描述

(4.2)拷贝构造函数自动调用基类的拷贝构造函数

基类如果有显示的拷贝构造:
编译器不会自动调用基类的拷贝构造函数
你必须显式地在初始化列表中调用
否则只会默认使用基类的默认构造函数,或者报错(如果没有默认构造函数)。

#include <iostream>
using namespace std;

class Base {
public:
    Base() {}
    Base(const Base&) {
        cout << "Base 拷贝构造函数" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {}
    
    // 显式调用 Base 的拷贝构造
    Derived(const Derived& d) : Base(d) {
        cout << "Derived 拷贝构造函数" << endl;
    }
};

int main() {
    Derived d1;
    Derived d2 = d1;  // 输出两个拷贝构造函数
}

在这里插入图片描述

没有手动调用 Base 的拷贝构造

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "默认构造" << endl;
    }
    Base(const Base&) {
        cout << "Base 拷贝构造函数" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {}
    Derived(const Derived& d) {
        cout << "Derived 拷贝构造函数" << endl;
    }
};


int main() {
    Derived d1;
    Derived d2 = d1;  
}

在这里插入图片描述

(4.3)为什么派生类重载了 operator= 后,需要手动调用基类的 operator=

在 C++ 中,如果我们在派生类中重载了 operator=,很多人以为编译器会自动帮我们调用基类的赋值运算符,其实并不会

#include <iostream>
using namespace std;

class Base {
public:
    int a = 10;  // 有数据成员
};

class Derived : public Base {
public:
    int b = 20;

    Derived& operator=(const Derived& d) {
        b = d.b;
        cout << "Derived::operator= 被调用" << endl;
        return *this;
    }

    void show() {
        cout << "Base::a = " << a << ", Derived::b = " << b << endl;
    }
};

int main() {
    Derived d1;
    d1.a = 111;
    d1.b = 222;

    Derived d2;
    d2 = d1;

    d2.show();  // Base::a 没有被赋值
}

在这里插入图片描述

此时的基类到底有没有编译器生成的operator=? 有,调试的时候虽然是没有直接进入这个基类,因为你在 Derived 中手动写了 operator=,编译器不会自动帮你调用 Base::operator=,这是你自己的职责。所以你必须手动调用。

看下一个测试用例:我们手动调用基类的operator =

Derived& operator=(const Derived& d) {
    Base::operator=(d);  // 手动调用
    b = d.b;
    return *this;
}

在这里插入图片描述

此时就是明显的观察到调用了基类的operator= ,并且编译器是生成了operator= 这个功能。

(4.4)析构函数:先析构派生类,再析构基类

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base 构造" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived 构造" << endl;
    }
};

int main() {
    Derived d;
}

在这里插入图片描述

(4.5)构造顺序:先构造基类,再构造派生类

class Base {
public:
    ~Base() {
        cout << "Base 析构" << endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "Derived 析构" << endl;
    }
};

int main() {
    Derived d;
}

在这里插入图片描述

五、友元与继承

子类不能访问父类私有成员,除非父类主动“邀请”子类成为友元;同时,友元关系是不传递、不继承的

(5.1)子类不是父类的友元

class Base {
private:
    int a = 1;
};

class Derived : public Base {
public:
    void func() {
        // a = 10; //  编译错误,子类不能访问 Base 的私有成员
    }
};

(5.2)友元关系不能继承

如果 A 是 B 的友元,那么 A 的子类或 B 的子类都不会自动拥有这个友元关系。

class A {
    friend class B;
private:
    int x = 10;
};

class B {
public:
    void f(A& a) {
        a.x = 20;  //B 是 A 的友元
    }
};

class B_Derived : public B {
public:
    void f2(A& a) {
        // a.x = 30; //错误,友元关系不会继承
    }
};

(5.3)友元类可以是派生类,也可以是基类

class Derived;

class Base {
    friend class Derived; //Derived 可访问 Base 的私有成员
private:
    int x = 1;
};

class Derived : public Base {
public:
    void f() {
        x = 100;  //友元访问私有成员
    }
};

(5.4)解决办法:声明

#include <iostream>
using namespace std;

class Derived; // 提前声明

class Base {
    friend void friendFunc(Base& b); // friendFunc 是 Base 的友元
protected:
    int base_protected = 1;
private:
    int base_private = 2;
};

class Derived : public Base {
protected:
    int derived_protected = 3;
private:
    int derived_private = 4;
};

// 定义友元函数
void friendFunc(Base& b) {
    cout << b.base_protected << endl;  // Base 的保护成员
    cout << b.base_private << endl;    // Base 的私有成员
    // cout << b.derived_protected << endl; // 错误:friendFunc 不是 Derived 的友元
}

Derived 类也加上 friend class FriendClass;,FriendClass 能访问 Derived 的私有和受保护成员

在这里插入图片描述

六、静态变量与继承

(6.1)子类会继承静态变量(但不是拷贝)

静态变量只有一份存储空间,在继承中不会为每个子类再创建新的副本
子类可以访问父类的静态变量(只要权限允许:public 或 protected)

#include<iostream>

using namespace std;


class Base {
public:
    static int count;
};

int Base::count = 0;

class Derived : public Base {};

int main() {
    Base::count = 1;
    Derived::count = 2;

    cout << Base::count << endl;    // 输出 2
    cout << Derived::count << endl; // 输出 2
}

在这里插入图片描述

(6.2)如果子类定义了同名静态变量

#include<iostream>

using namespace std;

class Base {
public:
    static int count;
};
int Base::count = 0;

class Derived : public Base {
public:
    static int count; // 隐藏了 Base::count
};
int Derived::count = 100;

int main() {
    cout << Base::count << endl;    // 输出 0
    cout << Derived::count << endl; // 输出 100
}

那么这个时候,子类会隐藏基类的静态变量。

在这里插入图片描述

七、菱形继承及菱形虚拟继承

(7.1)什么是菱形继承

      A
     / \
    B   C
     \ /
      D

类 B 和 C 都从 A 派生;

类 D 同时继承自 B 和 C;

这就导致:D 继承了两份 A 的成员。

(7.2)引发二义性

#include<iostream>

using namespace std;
class A {
public:
    int x;
};

class B : public A {};
class C : public A {};
class D : public B, public C {};

int main() {
    D d;
    d.x = 10;// 不明确是 B::A::x 还是 C::A::x
    d.B::x = 10; //  可以用作用域限定符解决,但不优雅
}

在这里插入图片描述

(7.3)虚拟继承(Virtual Inheritance)解决重复继承问题

通过在继承时加上 virtual 关键字,告诉编译器:这个基类只保留一份副本

#include<iostream>

using namespace std;


class A {
public:
	int x;
};

class B : virtual public A {};  // 注意 virtual
class C : virtual public A {};
class D : public B, public C {}; 

int main() {
    D d;
    d.x = 10;
}

B 和 C 虚继承了 A;
D 只包含一份 A 的子对象;
这个机制通过 虚基表指针(vptr) 保证只构造一份基类子对象

(7.4)虚拟继承的底层原理

上段代码,B类里面有基类A吗?
类 B 虽然写了 : virtual public A,但它:
不包含 A 的独立子对象(子对象指内存中独立的一份 A);
它只是声明了“我依赖一个虚基类 A”,这意味着它知道将来某个派生类会真正提供这份 A 的实体。
所以:B 有继承关系上的 A,但没有真正的 A 成员对象

#include <iostream>
using namespace std;

class A {
public:
    int x;
};

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.x = 1;
    d.b = 2;
    d.c = 3;
    d.d = 4;

    cout << "x = " << d.x << endl;
    cout << "b = " << d.b << endl;
    cout << "c = " << d.c << endl;
    cout << "d = " << d.d << endl;

    return 0;
}

在这里插入图片描述

地址        值
0x008FF754  48 9b 4b 00    ; 偏移0:B的vbptr(虚基表指针)或其它继承指针
0x008FF758  02 00 00 00    ; 偏移4:B::b = 2
0x008FF75C  54 9b 4b 00    ; 偏移8:C的vbptr(虚基表指针)
0x008FF760  03 00 00 00    ; 偏移12:C::c = 3
0x008FF764  04 00 00 00    ; 偏移16:D::d = 4
0x008FF768  01 00 00 00    ; 偏移20:A::x = 1(虚基类成员)

把 48 9b 4b 00 转成地址 0x004B9B48

0x004B9B48  00 00 00 00  
0x004B9B4C  14 00 00 00   存储的B的偏移量

14 00 00 00转换为十进制就是20

在这里插入图片描述

D 对象内存布局:
+---------------------+
| B::vbptr ---------->|
| B::b                |
+---------------------+
| C::vbptr ---------->|
| C::c                |
+---------------------+
| D::d                |
+---------------------+
| 共享的 A::x         |  <-- 只存在一份!由 D 初始化
+---------------------+

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值