大家都知道C#和C++重要的区别之一就是,C#支持单一继承和多接口继承,而C++则允许多继承。至于哪一个比较好,在这里就不去评价了。今天所要说的重点内容就是C++的多重继承以及虚基类。
1.前一节我介绍了有关单继承的内容,实际上,一个派生类可能会继承于两个或多了基类,那么这样的派生方法称为多基派生或多重继承。它声明的一般格式:
class 派生类名:继承方式1 基类名1, 继承方式2 基类名2, 继承方式3 基类名3, ...,继承方式n 基类名n
{
派生类新增的数据成员和成员函数;
}
在多重继承中,公有继承、保护继承以及私有继承对于基类成员在派生类中的访问属性与单继承的规则是相同的。同样的多重继承派生类的构造函数和析构函数又是如何定义呢?一般形式:
派生类名(参数总表):基类名1(参数子表1), 基类名2(参数子表2),...,基类名n(参数子表n)
{
派生类新增成员的初始化语句;
}
与单继承派生类构造函数一样,多继承下的派生类构造函数必须同时负责该派生类的所有基类构造函数的参数传递。且构造函数和析构函数的调用顺序和单继承是一样的(可参考C++之继承与派生(2) ),先调用基类构造函数,再调用对象成员的构造函数,最后调用派生类的构造函数。那么处于同一层次的各个基类构造函数的调用顺序是取决于声明派生类时所指定的各个基类的顺序。我们通过以下这个示例说明:
(1).组件基类
<span style="font-size:14px;">class ComponentBase
{
private:
int width;
int height;
protected:
std::string name;
public:
ComponentBase(std::string name);
void setWidth(int width);
void setHeight(int height);
void showSize();
void showName();
};
ComponentBase::ComponentBase(std::string name)
{
this->name=name;
}
void ComponentBase::setWidth(int width)
{
this->width=width;
}
void ComponentBase::setHeight(int height)
{
this->height=height;
}
void ComponentBase::showSize()
{
std::cout<<"组件"<<this->name<<"高:"<<this->height<<" 宽:"<<this->width<<std::endl;
}
void ComponentBase::showName()
{
std::cout<<"组件名:"<<this->name<<std::endl;
}</span>
(2)窗体类
class Window:public ComponentBase
{
private:
std::string icon;
public:
Window(std::string name):ComponentBase(name)
{
std::cout<<"构造组件名为"<<this->name<<"窗体对象"<<std::endl;
}
void setIcon(std::string icon);
void showIcon();
~Window();
};
void Window::setIcon(std::string icon)
{
this->icon=icon;
}
void Window::showIcon()
{
std::cout<<"窗体图标"<<this->icon<<std::endl;
}
Window::~Window()
{
std::cout<<"清理组件名为"<<this->name<<"窗体对象"<<std::endl;
}
(3)滚动条类
class ScrollBar:public ComponentBase
{
private:
std::string orientation;
public:
ScrollBar(std::string name):ComponentBase(name)
{
std::cout<<"构造组件名为"<<this->name<<"滚动条对象"<<std::endl;
};
void setorientation(std::string orientation);
void showOrientation();
~ScrollBar();
};
void ScrollBar::setorientation(std::string orientation)
{
this->orientation=orientation;
}
void ScrollBar::showOrientation()
{
std::cout<<"滚动条方向为"<<this->orientation<<std::endl;
}
ScrollBar::~ScrollBar()
{
std::cout<<"清理组件名为"<<this->name<<"滚动条对象"<<std::endl;
}
(4)带有滚动条的窗体类
class ScrollBarWindow:public Window,public ScrollBar
{
private:
std::string name;//与ComponentBase同名数据成员name
public:
ScrollBarWindow(std::string windowName,std::string scrollBarName):Window(windowName),ScrollBar(scrollBarName)
{
this->name=windowName;
std::cout<<"构造名为"<<this->name<<"带有滚动条的窗体对象"<<std::endl;
}
~ScrollBarWindow()
{
std::cout<<"清理名为"<<this->name<<"带有滚动条的窗体对象"<<std::endl;
}
void setWidth(int width,int scrollBarWidth);//与ComponentBase同名成员函数setWidth
void setHeight(int height,int scrollBarHeight);//与ComponentBase同名成员函数setHeight
void showSize();//与ComponentBase同名成员函数showSize
void showName();//与ComponentBase同名成员函数showName
};
void ScrollBarWindow::showName()
{
std::cout<<"带有滚动条的窗体名:"<<this->name<<std::endl;
}
void ScrollBarWindow::setWidth(int width,int scrollBarWidth)
{
Window::setWidth(width);
ScrollBar::setWidth(scrollBarWidth);
}
void ScrollBarWindow::setHeight(int height,int scrollBarHeight)
{
Window::setHeight(height);
ScrollBar::setHeight(scrollBarHeight);
}
void ScrollBarWindow::showSize()
{
Window::showSize();
}
(5)main主函数实现部分
int main()
{
{
ScrollBarWindow scrollBarWindow("form1","scrollBar1");//设置窗体名和滚动条名
scrollBarWindow.setHeight(400,400);//设置窗体和滚动条高
scrollBarWindow.setWidth(200,20);//设置窗体和滚动条宽
scrollBarWindow.setIcon("kawayi");//设置窗体图标
scrollBarWindow.setorientation("Vertical");//设置滚动条方向
scrollBarWindow.showName();//显示窗体名称
scrollBarWindow.showSize();//显示窗体大小
scrollBarWindow.showIcon();//显示窗体图标
scrollBarWindow.showOrientation();//显示滚动条的方向
}
return0;
}
结果:
从结果看也验证了之前所说的多重继承派生类中处于同一层次的各个基类构造和析构的调用顺序。
2.看了上一个示例代码,也许有读者会问,为什么在ScrollBarWindow类中,有那么多和ComponentBase类同名的成员,既然ScrollBarWindow继承ScrollBar类和Window类,而ScrollBar类和Window类又继承ComponentBase类,那ScrollBarWindow类应该就拥有ComponentBase类中的这些同名成员了,为什么还要在重新声明,可以把这些同名的成员全部删掉,在main()主函数里声明对象直接调用这些同名的成员函数不就OK了么,这样重复声明不就完全失去继承的所体现的代码复用的特点了吗?读者可以尝试一下,你会发现会提示有关于成员XX不明确的错误,即产生了二义性,为什么会出现这个问题呢?单继承是不会有出现这样的问题的,原因在多重继承上。
上述程序代码中,类ScrollBarWindow由ScrollBar类和Window类派生出来,而ScrollBar类和Window类由ComponentBase类派生出来,虽然ScrollBar类和Window类没有定义数据成员name,但是它们分别从ConponentBase类继承了数据成员name,这样ScrollBar类和Window类就同时存在数据成员name,虽然ScrollBar类和Window类中的数据成员name具有不同的存储单元,可以存放不同数据。但是派生出的类ScrollBarWindow中就有两份数据成员name,其他同名成员也是一个道理,这是如果我们在直接声明类ScrollBarWindow的对象调用这些同名成员函数,那就会出现上述所说的二义性的问题。为了解决这个问题,C++中引入了虚基类的概念。
虚基类声明的一般格式:
class 派生类名:virtual 继承方式 类名{}//或class 派生类名:继承方式 virtual 类名{}
现在我们对上一个示例代码做一次瘦身:
(1).组件基类不变
(2).窗体类
class Window:publicvirtual ComponentBase //声明ComponentBase为Window的虚基类
{
private:
std::string icon;
public:
Window(std::string name):ComponentBase(name)
{
std::cout<<"构造组件名为"<<this->name<<"窗体对象"<<std::endl;
}
void setIcon(std::string icon);
void showIcon();
~Window();
};
void Window::setIcon(std::string icon)
{
this->icon=icon;
}
void Window::showIcon()
{
std::cout<<"窗体图标"<<this->icon<<std::endl;
}
Window::~Window()
{
std::cout<<"清理组件名为"<<this->name<<"窗体对象"<<std::endl;
}
(3).滚动条类
class ScrollBar:publicvirtual ComponentBase //声明ComponentBase为Window的虚基类
{
private:
std::string orientation;
public:
ScrollBar(std::string name):ComponentBase(name)
{
std::cout<<"构造组件名为"<<this->name<<"滚动条对象"<<std::endl;
};
void setorientation(std::string orientation);
void showOrientation();
~ScrollBar();
};
void ScrollBar::setorientation(std::string orientation)
{
this->orientation=orientation;
}
void ScrollBar::showOrientation()
{
std::cout<<"滚动条方向为"<<this->orientation<<std::endl;
}
ScrollBar::~ScrollBar()
{
std::cout<<"清理组件名为"<<this->name<<"滚动条对象"<<std::endl;
}
(4).带有滚动条的窗体类
class ScrollBarWindow:public Window,public ScrollBar
{
public:
ScrollBarWindow(std::string windowName,std::string scrollBarName):Window(windowName),ScrollBar(scrollBarName),ComponentBase(windowName)
{
std::cout<<"构造名为"<<this->name<<"带有滚动条的窗体对象"<<std::endl;
}
~ScrollBarWindow()
{
std::cout<<"清理名为"<<this->name<<"带有滚动条的窗体对象"<<std::endl;
}
};
<span style="color: rgb(0, 128, 0); font-family: verdana, 'ms song', Arial, Helvetica, sans-serif; font-size: 14px; line-height: 21px; background-color: rgb(250, 247, 239);">(5).main()主函数实现部分</span>
<span style="font-family: verdana, 'ms song', Arial, Helvetica, sans-serif; font-size: 14px; line-height: 21px; background-color: rgb(250, 247, 239);"></span><pre name="code" class="cpp">int main()
{
{
ScrollBarWindow scrollBarWindow("form1","scrollBar1");//设置窗体名和滚动条名
scrollBarWindow.setHeight(400);//设置窗体高
scrollBarWindow.setWidth(200);//设置窗体宽
scrollBarWindow.setIcon("kawayi");//设置窗体图标
scrollBarWindow.setorientation("Vertical");//设置滚动条方向
scrollBarWindow.showName();//显示窗体名称
scrollBarWindow.showSize();//显示窗体大小
scrollBarWindow.showIcon();//显示窗体图标
scrollBarWindow.showOrientation();//显示滚动条的方向
}
return0;
}
结果:
发现结果和上一个示例一样,但是类ScrollBarWindow中的代码却大大减少了,从该示例中可以看到虚基类在多重继承中所体现的重要性。
其实把ComponentBase声明为类ScrollBar和类Window的虚基类后,从类ScrollBar和类Window派生出来的类ScrollBarWindow只继承了类ComponentBase一次。最后对于虚基类我想补充几个注意点:(1)如果在虚基类中定义了带有参数的构造函数,且没有定义默认形式的构造函数,则在整个继承过程中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用;(2)建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的,该派生类的其他基类对虚基类的调用构造函数则被忽略;(3)若在同一层次中同时包含虚基类和非虚基类,那么先调用虚基类的构造函数,在调用非虚基类的构造函数,最后调用派生类的构造函数,析构则相反;(4)对于多个虚基类,则构造函数执行顺序从左到右;(5)对于多个非虚基类来说,构造函数的执行顺序也是如此;(6)若虚基类由非虚基类派生而来,那么仍然先调用基类构造函数,再调用派生类的构造函数;