适合具备 C 语言基础的 C++ 教程(六)
前言
在上一则教程中,着重讲述了派生类继承于父类之后的一些访问控制,在本次教程中,将介绍如下几个点:派生类扩展父类功能,派生类的空间分布,以及多重继承的相关概念。
派生类扩展父类的功能
在前文所述的 Father
类我们通常也称之为父类或者说称之为基类,而 Son
类我们则称之为子类或者是派生类,我们知道通过public
继承的方式Son
类可以继承到父类的 it_skill
,那么我们可不可以将这个继承得到的 it_skill
发扬光大呢?实际上是可以的,用更加专业的话来讲就是覆写,也就是 override
,代码如下所示:
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
。
int main(int argc, char **argv)
{
Son s;
s.it_skill();
return 0;
}
派生类的空间分布(内存分布)
在讲述派生类的空间分布的时候,我们采用 Person
类和 Student
类进行阐述,首先 Person
类具有如下的属性:
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
类中继承而来,代码如下所示:
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
类之间的关系如下所示:
通过上述的示意图可以清楚地知晓Student
类和 Person
类之间的关系,那么假设现在有如下所示的一句代码:
Student s;
Person &p = s;
那么对于p
来说,它引用的是 s
里面继承于 Person
里的那部分,为了更清楚地说明这个问题,我们编写如下所示的一个函数:
void test_func(Person &p)
{
p.printInfo();
}
基于上述代码的基础上,我们继续来编写主函数代码:
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;
}
上述代码运行的结果如下图所示:
通过上述地输出信息,也可以知道,在第十行代码中,test_fun(s)
实参传入地是 Student
地实例化对象,但是在执行代码地时候,它执行的是Person
类的 printInfo
函数,也就是说,虽然传进去的是 Student
的实例化对象,但是真正起作用的是实例化对象中继承自 Person
类的那部分。
多重继承
多重继承也就如字面意思一样,就是说派生类继承自多个父类,这就称之为是多重继承,简单来说,就是一个派生类可以有多个基类。基于上面的叙述,我们用一个例子来说明,比如我们现在有如下两个基类:
class Sofa {
public:
void watchTV(void) { cout<<"watch TV"<<endl; }
};
class Bed {
public:
void sleep(void) { cout<<"sleep"<<endl; }
};
那我们现在有一个派生类从这两个基类继承而来,代码如下所示:
class Sofabed : public Sofa, public Bed {
};
既然是从两个基类中继承而来,自然也就满足在之前的教程里所述的访问控制的原则,并为继承所得到的派生类也满足基类所具备的一些特性:
int main(int argc, char **argv)
{
Sofabed s;
s.watchTV();
s.sleep();
return 0;
}
上述代码的执行结果也显而易见,执行结果如下所示:
存在的问题
在上述的例子中,我们使用多重继承的时候,那么这样会带来什么问题呢?我们将上述的两个基类的数据成员和成员函数进行补充:
class Sofa
{
private:
int weight;
public:
void WatchTV(void) { cout << "watch TV" << endl;}
void setWeight(int weight) {this->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;}
};
上述代码我们又增添了两个基类的数据成员以及成员函数,这样也会出现一个问题。如果我们仍然像之前的那样继承的方式继承两个基类:
class Sofabed : public Sofa,public bed
{
};
这样子就会存在一个问题,如果我们在主函数这样子定义了一个实例化对象,并调用它的成员函数:
int main(int argc,char **argv)
{
Sofabed s;
s.setWeight(100); /* error */
return 0;
}
这个时候s
从 Sofa
和bed
两个类中继承而来,那么自然也就具备了Sofa
和bed
的属性,但是这个时候实例化的 s
调用 setWeight
的时候,并不知道操作的是从哪里继承而来的weight
,因此也就造成了错误(注释表明了error
)。如果在不更改继承方式的前提下,也可以这样书写避免错误:
s.Sofa::setWeight(100);
但是这样书写一方面看着是存在冗余,一方面是实际上就多出来了一个无用的 weight
。因此,为了解决上述的问题,就引入了虚拟继承的概念。
虚拟继承
为了改进上述的代码,引入虚拟继承的概念,在这里,我们多引入一个类,Furniture
类,然后,Sofa
和bed
都虚拟继承自Furniture
,Sofabed
从Sofa
和bed
中继承而来,下图是这几个类之间的一个关系图:
从上图中我们看到Sofa
和 bed
都是虚拟继承自Furniture
,下面是各个类的代码实现:
class Furniture
{
private:
int weight;
public:
void setWeight(int weight) { this->weight = weight; }
int getWeight(void) const { return weight; }
};
然后是 Sofa
的类的实现:
class Sofa : virtual public Furniture
{
private:
int a;
public:
void watchTV(void) {cout << "watch TV" << endl;}
};
紧接着是 bed
的实现代码:
class bed : virtual public Furniture
{
private:
int b;
public:
void sleep(void) {cout << "sleep" << endl;}
};
紧接着是 Sofabed
的代码实现:
class Sofabed : public Sofa, public Bed {
private:
int c;
};
然后,我们紧接着我们看主函数的代码:
int main(int argc,char **argv)
{
Sofabed s;
s.setweight(100);
return 0;
}
上述代码就没有出错,为什么这样就没有出错呢,我们来看每个每个类以及实例化对象的空间分布:
我们可以看到通过 virtual(虚拟)
继承的方式,Sofa
和Bed
的weight
公用的是一块内存空间,那么这个时候操作 s
的时候也就不存在二义性了。
再论构造函数的调用顺序
在前面的教程中,已经多次提及了构造函数的执行顺序,接下来也有必要就此问题继续谈一下当存在多重继承时,以及存在虚拟继承时,这个时候构造函数的调用顺序又是怎么样的?同样的,我们采用打印消息的方式来了解这个执行过程,为了更好地说明这个问题,我们引入如下几个类:Furniture
类,Vertification3c
类,Sofa
类、Bed
类、SofaBed
类、LeftRightCom
类以及LeftRightSoftbed
类,这几个类之间的关系如下所示:
下面是这几个类的代码实现,首先是Furniture
类和Vertifivation
类的代码实现:
class Furniture
{
private:
int weight;
public:
void setWeight(int weight) { this->weight = weight; }
int getWeight(void) const { return weight; }
public:
Furniture() { cout <<"Furniture()"<<endl; }
};
class Vertification3C
{
public:
Vertification3C() { cout <<"Vertification3C()"<<endl; }
};
由上述框图可以知道,Sofa
和Bed
都是虚拟继承自Furniture
和Vertication3C
,那么代码实现如下所示:
class Sofa : virtual public Furniture , virtual public Vertification3C
{
private:
int a;
public:
void watchTV(void) { cout<<"watch TV"<<endl; }
public:
Sofa() { cout <<"Sofa()"<<endl; }
};
class Bed : virtual public Furniture, virtual public Vertification3C
{
private:
int b;
public:
void sleep(void) { cout<<"sleep"<<endl; }
public:
Bed() { cout <<"Bed()"<<endl; }
};
然后,我们又知道 Sofabed
是继承自Sofa
和Bed
,那么 Sofabed
的代码如下所示:
class Sofabed : public Sofa, public Bed
{
private:
int c;
public:
Sofabed() { cout <<"Sofabed()"<<endl; }
};
在上述基础上,我们继续来实现LeftRightCom
类以及 Data
类和Type
类:
class LeftRightCom
{
public:
LeftRightCom() { cout <<"LeftRightCom()"<<endl; }
};
class Date
{
public:
Date() { cout <<"Date()"<<endl; }
};
class Type
{
public:
Type() { cout <<"Type()"<<endl; }
};
最后,我们来实现 LeftRightSofabed
,代码实现如下所示:
class LeftRightSofabed : public Sofabed, public LeftRightCom {
private:
Date date;
Type type;
public:
LeftRightSofabed() { cout <<"LeftRightSofabed()"<<endl; }
};
最后,为了弄清楚构造函数的调用顺序,我们在主函数里进行实例化对象:
int main(int argc, char **argv)
{
LeftRightSofabed s;
return 0;
}
上述代码的执行结果如下所示:
在剖析构造函数的调用顺序之前,我们先明确如下几个原则:
1、先调用虚拟基类构造函数:按照继承顺序,只执行一次
2、然后调用非虚拟基类构造函数,按照继承顺序
3、然后是类的对象成员(按照声明的顺序)
4、最后是类自己的构造函数
按照上述这个分析原则,很容易就能够得出构造函数的执行顺序就是如运行结果所示,如果在 LeftRightSofabed
类的定义中,我们将其更改为如下方式:
class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {
private:
Date date;
Type type;
public:
LeftRightSofabed() { cout <<"LeftRightSofabed()"<<endl; }
};
那么按照先调用虚拟基类构造函数
的这个原则,构造函数的调用顺序如下所示:
小结
本次分享的内容就是这些,本节所涉及到的代码可以到百度云链接中获取:
链接:https://pan.baidu.com/s/1ReW7F-2RAGGlVnHIycCI1w
提取码:lhru