电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
适合具备 C 语言基础的 C++ 教程(六)
分 享
扫描二维码分享
适合具备 C 语言基础的 C++ 教程(六)
C++
wenzi 嵌入式软件
关注
发布时间: 2021-02-22
丨
阅读: 1473
# 前言 再上一则教程中,着重讲述了派生类继承于父类之后的一些访问控制,在本次教程中,将介绍如下几个点:**派生类扩展父类功能**,**派生类的空间分布**,以及**多重继承**的相关概念。 ## 派生类扩展父类的功能 在前文所述的 `Father`类我们通常也称之为父类或者说称之为基类,而 `Son`类我们则称之为子类或者是派生类,我们知道通过`public`继承的方式`Son`类可以继承到父类的 `it_skill`,那么我们可不可以将这个继承得到的 `it_skill`发扬光大呢?实际上是可以的,用更加专业的话来讲就是覆写,也就是 `override`,代码如下所示: ```cpp class Son : public Father { private: int toy; public: void paly_game(void) { cout << "son play game" << endl; } void it_skill(void) { cout << "son's it skill" << endl; } }; ``` 注意上述的`it_skill`和`Father`类的 `it_skill`是相同的一个函数,只是在 `Son`类里对这个函数进行了覆写,这个时候,如果向如下方式调用 `it_skill`,那么就会调用的是 `Son`类里面定义的 `it_skill`。 ```cpp int main(int argc, char **argv) { Son s; s.it_skill(); return 0; } ``` ## 派生类的空间分布(内存分布) 在讲述派生类的空间分布的时候,我们采用 `Person`类和 `Student`类进行阐述,首先 `Person`类具有如下的属性: ```cpp class Person { private: char *name; int age; public: int address; void setName(char *name) {/@@*省略*/} void setAge(int age) {/@@*省略*/} Person(char *name, int age) {/@@*省略*/} ~Person() {/@@*省略*/} }; ``` 然后,`Student`类以 `public`的方式从 `Person`类中继承而来,代码如下所示: ```cpp class Student : public Person { private: int grade; public: void setGrade(int grade) { this->grade = grade; } int getGrade(void) { return this->grade; } /@@*override*/ void printInfo(void) { cout << "Student"; person::printfInfo(); } }; ``` 上述就是`Student`类以 `public`方式继承自 `Person`类的一个例子,因为 `Student`类中也存在其自身的私有数据成员,所以,总的来说,`Person`类和`Student`类之间的关系如下所示: ![image-20210210215953484](https://gitee.com/wenzi_D/images4mk/raw/master/image-20210210215953484.png) 通过上述的示意图可以清楚地知晓`Student`类和 `Person`类之间的关系,那么假设现在有如下所示的一句代码: ```cpp Student s; Person &p = s; ``` 那么对于`p`来说,它引用的是 `s`里面继承于 `Person`里的那部分,为了更清楚地说明这个问题,我们编写如下所示地一个函数: ```cpp void test_func(Person &p) { p.printInfo(); } ``` 基于上述代码地基础上,我们继续来编写主函数代码: ```cpp int main(int argc, char **argv) { Person p("lisi", 16); Student s; s.setName("zhangsan"); s.setAge(16); test_func(p); test_func(s); s.printInfo(); return 0; } ``` 上述代码运行地结果如下图所示: ![image-20210210220743410](https://gitee.com/wenzi_D/images4mk/raw/master/image-20210210220743410.png) 通过上述地输出信息,也可以知道,在第十行代码中,`test_fun(s)` 实参传入地是 `Student`地实例化对象,但是在执行代码地时候,它执行的是`Person`类的 `printInfo`函数,也就是说,虽然传进去的是 `Student`的实例化对象,但是真正起作用的是实例化对象中继承自 `Person`类的那部分。 ## 多重继承 多重继承也就如字面意思一样,就是说派生类继承自多个父类,这就称之为是多重继承,简单来说,就是一个派生类可以有多个基类。基于上面的叙述,我们用一个例子来说明,比如我们现在有如下两个基类: ```cpp class Sofa { public: void watchTV(void) { cout<<"watch TV"<
weight = weight;} void getWeight(void) {return this->weight;} }; class bed { private: int weight; public: void sleep(void) {cout << "sleep" << endl;} void setWeight(int weight) {this->weight = weight;} void getWeight(void) {return this->weight;} }; ``` 上述代码我们又增添了两个基类的数据成员以及成员函数,这样也会出现一个问题。如果我们仍然像之前的那样继承的方式继承两个基类: ```cpp class Sofabed : public Sofa,public bed { }; ``` 这样子就会存在一个问题,如果我们在主函数这样子定义了一个实例化对象,并调用它的成员函数: ```cpp int main(int argc,char **argv) { Sofabed s; s.setWeight(100); /@@* error */ return 0; } ``` 这个时候`s`从 `Sofa`和`bed`两个类中继承而来,那么自然也就具备了`Sofa`和`bed`的属性,但是这个时候实例化的 `s`调用 `setWeight`的时候,并不知道操作的是从哪里继承而来的`weight`,因此也就造成了错误(注释表明了`error`)。如果在不更改继承方式的前提下,也可以这样书写避免错误: ```cpp s.Sofa::setWeight(100); ``` 但是这样书写一方面看着是存在冗余,一方面是实际上就多处来了一个无用的 `weight`。因此,为了解决上述的问题,就引入了虚拟继承的概念。 ### 虚拟继承 为了改进上述的代码,引入虚拟继承的概念,在这里,我们多引入一个类,`Furniture`类,然后,`Sofa`和`bed`都虚拟继承自`Furniture`,`Sofabed`从`Sofa`和`bed`中继承而来,下图是这几个类之间的一个关系图: ![image-20210219162331887](https://gitee.com/wenzi_D/images4mk/raw/master/image-20210219162331887.png) 从上图中我们看到`Sofa`和 `bed`都是虚拟继承自`Furniture`,下面是各个类的代码实现: ```cpp class Furniture { private: int weight; public: void setWeight(int weight) { this->weight = weight; } int getWeight(void) const { return weight; } }; ``` 然后是 `Sofa`的类的实现: ```cpp class Sofa : virtual public Furniture { private: int a; public: void watchTV(void) {cout << "watch TV" << endl;} }; ``` 紧接着是 `bed`的实现代码: ```cpp class bed : virtual public Furniture { private: int b; public: void sleep(void) {cout << "sleep" << endl;} }; ``` 紧接着是 `Sofabed`的代码实现: ```cpp class Sofabed : public Sofa, public Bed { private: int c; }; ``` 然后,我们紧接着我们看主函数的代码: ```cpp int main(int argc,char **argv) { Sofabed s; s.setweight(100); return 0; } ``` 上述代码就没有出错,为什么这样就没有出错呢,我们来看每个每个类以及实例化对象的空间分布: ![image-20210219163214150](https://gitee.com/wenzi_D/images4mk/raw/master/image-20210219163214150.png) 我们可以看到通过 `virtual(虚拟)`继承的方式,`Sofa`和`Bed`的`weight`公用的是一块内存空间,那么这个时候操作 `s`的时候也就不存在二义性了。 ## 再论构造函数的调用顺序 在前面的教程中,已经多次提及了构造函数的执行顺序,接下来也有必要就此问题继续谈一下当存在多重继承时,以及存在虚拟继承时,这个时候构造函数的调用顺序又是怎么样的?同样的,我们采用打印消息的方式来了解这个执行过程,为了更好地说明这个问题,我们引入如下几个类:`Furniture`类,`Vertification3c`类,`Sofa`类、`Bed`类、`SofaBed`类、`LeftRightCom`类以及`LeftRightSoftbed`类,这几个类之间地关系如下所示: ![image-20210219211646808](https://gitee.com/wenzi_D/images4mk/raw/master/image-20210219211646808.png) 下面是这几个类地代码实现,首先是`Furniture`类和`Vertifivation`类的代码实现: ```cpp class Furniture { private: int weight; public: void setWeight(int weight) { this->weight = weight; } int getWeight(void) const { return weight; } public: Furniture() { cout <<"Furniture()"<