0

0

C++继承实现方式 基类派生类关系建立

P粉602998670

P粉602998670

发布时间:2025-08-29 13:32:01

|

865人浏览过

|

来源于php中文网

原创

C++中基类与派生类关系通过继承语法建立,1. 使用class Derived : public Base声明实现“is-a”关系;2. 编译器安排内存布局,派生类对象包含基类子对象,形成连续内存结构;3. 构造时先调用基类构造函数再调用派生类构造函数,析构时顺序相反;4. public继承保持基类成员访问权限,支持代码复用与多态;5. 虚函数引入vptr和vtable机制,实现运行时多态;6. 基类析构函数应声明为virtual,防止资源泄漏;7. 派生类可直接访问基类public成员,体现功能扩展性。

c++继承实现方式 基类派生类关系建立

C++中基类与派生类关系的建立,核心在于通过派生类声明时的特定语法,明确指出其继承自哪个基类。这种声明不仅定义了类型之间的“is-a”关系,更在编译器层面安排了内存布局和成员访问规则,使得派生类能够复用基类的功能并扩展自身。

解决方案

C++的继承机制,说到底就是一种代码复用和类型体系构建的手段。当我们写下

class Derived : public Base { /* ... */ };
这样的代码时,我们不仅仅是告诉编译器
Derived
是一种
Base
,更是启动了一系列幕后操作。

首先,

:
符号后的
public Base
明确了继承的类型和访问权限。
public
意味着基类的
public
成员在派生类中依然是
public
protected
成员依然是
protected
。如果换成
protected
private
,那访问权限就会相应收紧。这就像给派生类设定了一个“看基类家底”的权限级别。

其次,编译器会为

Derived
类生成一个包含
Base
类子对象的内存布局。这意味着,一个
Derived
类的实例,内部会完整地包含一个
Base
类的实例所需的所有数据成员。你可以把它想象成一个俄罗斯套娃,外层的
Derived
里面包裹着一个
Base
。这种物理上的包含,是“is-a”关系在内存中的具象化。

立即学习C++免费学习笔记(深入)”;

再者,继承还涉及到构造函数和析构函数的调用顺序。当你创建一个

Derived
对象时,会先调用
Base
的构造函数,再调用
Derived
的构造函数。销毁时则反过来,先
Derived
析构,再
Base
析构。这个顺序是语言强制的,确保了基类部分在派生类使用前被正确初始化,并在派生类清理完毕后才被清理。

#include 

class Base {
public:
    int base_data;
    Base(int bd = 0) : base_data(bd) {
        std::cout << "Base constructor, base_data: " << base_data << std::endl;
    }
    void showBase() {
        std::cout << "Base data: " << base_data << std::endl;
    }
    ~Base() {
        std::cout << "Base destructor" << std::endl;
    }
};

class Derived : public Base {
public:
    int derived_data;
    // 派生类构造函数需要显式或隐式调用基类构造函数
    Derived(int bd = 0, int dd = 0) : Base(bd), derived_data(dd) {
        std::cout << "Derived constructor, derived_data: " << derived_data << std::endl;
    }
    void showDerived() {
        showBase(); // 可以访问基类的public成员
        std::cout << "Derived data: " << derived_data << std::endl;
    }
    ~Derived() {
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Derived d(10, 20);
    d.showDerived();
    // d.base_data; // 可以直接访问public基类成员
    return 0;
}

这段代码展示了基类和派生类的基本构造,以及构造和析构的顺序。派生类通过

Base(bd)
语法显式调用了基类的构造函数,这是建立关系的关键一步。

为什么我们需要继承?它解决了什么核心问题?

继承,在我看来,是C++面向对象编程中一个非常基础但又极其强大的概念。它解决的核心问题无非是两点:代码复用构建类型层次结构以支持多态。想想看,如果我们要设计一个图形系统,有圆形、矩形、三角形,它们都有颜色、位置,都能被绘制。如果没有继承,你可能会在每个类里都写一遍设置颜色、获取位置、绘制这些代码,这显然是重复且低效的。

继承提供了一个优雅的解决方案:我们可以定义一个

Shape
基类,把所有图形共有的属性(如颜色、位置)和行为(如
draw()
)放进去。然后,
Circle
Rectangle
Triangle
作为
Shape
的派生类,它们自然就“拥有”了这些共性,只需要实现自己特有的部分(比如圆的半径,矩形的宽高等)。这极大地减少了代码量,提高了开发效率,也让代码更容易维护。

更深层次的,继承为多态奠定了基础。通过基类指针或引用操作派生类对象,我们可以在运行时根据对象的实际类型执行不同的行为。比如,一个

Shape*
指针可以指向
Circle
也可以指向
Rectangle
,调用
draw()
方法时,编译器会根据实际指向的对象类型来决定调用哪个
draw()
。这让我们的程序设计变得异常灵活,能够应对复杂多变的需求。它不仅仅是代码的共享,更是对现实世界“is-a”关系的一种编程模型映射,让软件结构更加清晰和富有表现力。

Giiso写作机器人
Giiso写作机器人

Giiso写作机器人,让写作更简单

下载

C++中基类与派生类内存布局的奥秘

当我们谈论C++继承时,内存布局是一个绕不开的话题,它直接决定了对象在内存中是如何存在的。一个派生类对象,其实是其基类子对象和派生类自身新增成员的组合。这并不是说派生类对象里有一个基类对象的指针,而是基类对象的数据成员是派生类对象内存空间的一部分。

具体来说,一个

Derived
类的实例,它的内存通常会首先包含
Base
类的数据成员,然后才是
Derived
类自身的数据成员。如果基类或派生类中有虚函数,那么通常会在对象内存的某个位置(通常是开头)插入一个虚函数表指针(vptr),这个指针指向一个虚函数表(vtable)。这个 vptr 才是实现运行时多态的关键。

举个例子:

class Base {
public:
    int b1;
    virtual void func() {} // 引入虚函数,会产生vptr
    int b2;
};

class Derived : public Base {
public:
    int d1;
    void func() override {} // 重写虚函数
    int d2;
};

一个

Derived
对象的内存布局可能看起来像这样(具体实现依赖编译器和平台,这里是概念性的):
[vptr (指向 Derived 的 vtable)]
[Base::b1]
[Base::b2]
[Derived::d1]
[Derived::d2]

这个布局解释了为什么我们可以将派生类指针隐式转换为基类指针,因为基类部分就位于派生类对象的起始地址。但反过来就不行,因为基类指针无法知道派生类额外的数据成员在哪里。理解这一点对于避免诸如对象切片(object slicing)这样的问题至关重要,对象切片发生在将派生类对象赋值给基类对象时,派生类特有的部分会被“切掉”,只保留基类部分。这揭示了内存管理的深层逻辑,远非表面那么简单。

构造与析构:基类与派生类生命周期的交织

基类与派生类的生命周期,在构造和析构阶段呈现出一种严格而有序的交织。这不仅仅是语法规定,更是为了确保对象状态的完整性和资源的正确释放。

当一个派生类对象被创建时,其构造函数的执行流程是这样的:

  1. 首先,调用基类的构造函数。 这一步至关重要,因为派生类要使用基类提供的功能,基类部分必须先被正确初始化。如果基类有多个,它们会按照继承列表中出现的顺序被构造。
  2. 然后,初始化派生类自己的成员变量。
  3. 最后,执行派生类构造函数体内的代码。

这种“基类先于派生类”的构造顺序,保证了派生类在执行自己的初始化逻辑时,其基类部分已经是一个有效且可用的状态。如果基类构造函数需要参数,派生类构造函数必须通过初始化列表显式地传递这些参数,例如

Derived(int a, int b) : Base(a), member(b) {}

而在对象销毁时,析构函数的调用顺序则完全相反:

  1. 首先,执行派生类析构函数体内的代码。 派生类有机会清理它自己特有的资源。
  2. 然后,调用基类的析构函数。 基类负责清理它自己的资源。

这种“派生类先于基类”的析构顺序,确保了在基类部分被销毁之前,派生类仍然可以访问基类提供的资源。如果基类析构函数是虚函数,那么通过基类指针删除派生类对象时,就能正确调用到派生类的析构函数,从而避免内存泄漏。这是一个非常重要的设计模式,尤其是在多态场景下。忘记将基类析构函数声明为

virtual
,是C++中一个常见的错误源,会导致派生类特有的资源无法被正确释放,最终酿成泄漏。理解并遵循这个顺序,是编写健壮C++代码的基础。

相关专题

更多
go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

50

2025.11.27

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

15

2025.11.27

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

338

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

542

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

53

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

465

2024.01.03

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

1

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号