ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

小学生学C++编程语法知识(如果想将某个类用作基类,为什么该类必须定义而并非声明)

小学生学C++编程语法知识(如果想将某个类用作基类,为什么该类必须定义而并非声明)


如果想将某个类用作基类,为什么该类必须定义而并非声明

对小学生来说,可以把它理解成:

继承就像“盖房子”,建新房子之前,必须知道老房子的完整结构,而不能只知道一个名字。

下面我们一步一步讲。


一、先认识两个概念:声明 vs 定义

在 C++ 中:

1. 声明(Declaration)

声明只是告诉编译器:

“有这么一个东西存在。”


例如:

class Animal;

这句话叫:

类的前向声明(forward declaration)


意思:

“以后会有一个叫 Animal 的类。”

但是编译器不知道:

  • Animal 有几个成员变量?

  • Animal 有哪些函数?

  • Animal 占多少内存?

它只知道:

有这么一个名字。


2. 定义(Definition)

定义就是完整写出类:

class Animal { public: int age; void eat() { cout << "吃东西"; } };

现在编译器知道:

Animal:

  • 有一个 int 类型的 age

  • 有一个 eat 函数

  • 大小是多少

  • 如何创建对象


二、什么叫“作为基类”?

例如:

class Dog : public Animal { };

意思:

Dog 继承 Animal。


可以理解:

Animal | | Dog

Dog 是 Animal 的孩子。


三、为什么基类必须定义?

因为创建 Dog 的时候:

Dog 身体里面包含 Animal 的部分。

例如:

class Animal { public: int age; }; class Dog : public Animal { };

实际上:

Dog对象 +----------------+ | Animal部分 | | | | age | +----------------+ | Dog自己的部分 | +----------------+

Dog 天生带有 Animal。

所以编译器必须知道:

Animal 到底有多大?


四、如果只有声明,会发生什么?

例如:

class Animal; // 只有声明 class Dog : public Animal { };

编译器看到:

Dog继承Animal

它会问:

“Animal是什么样?”

可是它只知道:

Animal这个名字存在

不知道:

Animal里面有什么

所以无法计算:

Dog对象占多少空间

例如:

Dog dog;

编译器需要分配:

多少字节?

但是:

Animal??? 大小未知

所以错误。


五、用盖房子的故事理解

假设:

爸爸 Animal 要给孩子 Dog 留房间。

爸爸房间:

+----------------+ | 爸爸的房间 | | 10平方米 | +----------------+

孩子:

Dog = 爸爸房间 + 自己房间

那么建 Dog 房子之前:

必须知道爸爸房间多大。

如果只听说:

“有一个爸爸。”

但是不知道:

  • 爸爸房间多大

  • 有几个房间

怎么建?

所以:

继承必须知道基类完整结构。


六、为什么普通指针可以只声明?

这里容易混淆。

例如:

class Animal; Animal* p;

可以。

为什么?

因为:

指针大小固定。

比如:

64位系统:

指针 = 8字节

编译器只需要知道:

“这是一个地址。”

不需要知道 Animal 有多大。


但是:

class Dog : public Animal { };

不行。

因为:

Dog包含Animal。

必须知道:

Animal大小

七、一个经典对比

情况1:指向对象

可以:

class Student; Student* p;

因为:

只需要保存地址。


情况2:继承

不可以:

class Student; class GoodStudent : public Student { };

因为:

GoodStudent需要包含Student。


八、还有一个重要原因:虚函数表

如果基类有虚函数:

class Animal { public: virtual void speak(); };

那么对象里面还有:

虚函数表指针 vptr

例如:

Animal对象 +-------------+ | vptr | +-------------+ | 成员变量 | +-------------+

派生类:

Dog对象 +-------------+ | Animal部分 | | vptr | +-------------+ | Dog部分 | +-------------+

如果不知道 Animal 定义:

连:

  • 有没有虚函数

  • 有没有vptr

  • 内存布局

都不知道。

所以更加不能继承。


九、总结成一句小学生能记住的话

为什么基类必须定义?

因为:

派生类像是在基类身体上继续添加东西,编译器必须先知道基类完整长什么样,才能计算派生类的大小和布局。


口诀:

声明: 知道名字,不知道身体 定义: 知道身体,知道大小 指针: 只需要知道名字 继承: 必须知道身体

十、和前面“虚析构函数”的知识联系起来

以后学习:

class Base { public: virtual ~Base() { } }; class Derived : public Base { };

这里:

Derived继承Base。

编译器必须知道:

Base完整定义。

因为:

Derived对象里面包含Base部分。

所以:

继承关系比指针关系要求更多的信息。


这也是为什么:

class Base; Base* p; // 可以 class Derived : public Base { }; // 不可以

这是 C++ 面向对象中非常核心的规则。


返回列表