《继承》
1.类的继承
继承的概念就不具体说明了。说明一些基类和派生类:如果B类是A类继承而来的,那么A类就称为基类,B类就称为派生类。
下面的demo是先创建一个box类,carton类继承box类。
box.h
#ifndef _BOX_H_ #define _BOX_H_ class box{ public: box(double lv=1.0, double wv=1.0, double hv=1.0); private: double length; double width; double height; }; #endif
box.cpp
#include "box.h" box::box(double lv, double wv, double hv) : length(lv), width(wv), height(hv) { ; }
carton.h
#ifndef _CARTON_H_ #define _CARTON_H_ #include "box.h" /* carton类派生于box类 */ class carton : public box{ public: carton(const char *pstr = "Cardboard"); ~carton(); private: char *pMaterial; }; #endif
carton.cpp
#include "carton.h" #include <cstring> carton::carton(const char *pstr) { pMaterial = new char[strlen(pstr) + 1]; std::strcpy(pMaterial, pstr); } carton::~carton() { delete[] pMaterial; }
main.cpp
#include <iostream> #include "box.h" #include "carton.h" using namespace std; int main() { box mybox(10.0, 20.0, 30.0); carton mycarton; carton candycarton("thin cardboard"); cout << endl << "mybox occupies " << sizeof mybox << " bytes" << endl; cout << "mycarton occupies " << sizeof mycarton << " bytes" << endl; cout << "candycarton occupies " << sizeof candycarton << " bytes" << endl; return 0; }
在我们创建carton对象时,会先调用box的构造函数,再调用carton的构造函数。
2.继承下的控制访问(protected)
如果在candycarton对象访问数据成员lenth,这个操作是不允许的。因为lenth是box类的私有成员。candycarton只能通过从box类继承下来的函数成员访问box类中的私有数据成员,而不能通过candycarton直接访问或者它本身的函数访问。
那么如果在派生类也能访问到基类的私有成员呢?
可以使用protected来替换private。在基类中如果把private替换成protected,不会对基类的数据成员有影响。这些受保护的类还是不能在类外访问,只能通过成员函数、友元类和类的友元函数访问。唯一的区别就是基类的protected的数据成员在派生类中也可以直接访问。
3.派生类成员的访问级别
public继承:不改变基类成员的访问控制。
private继承:派生类所继承的基类成员的访问控制都变为private。
protected继承:基类中的private成员的访问控制不变,其余的都变为protected。
可以使用using改变某个基类成员免受protected或private的影响。
class box{ public: box(double lv=1.0, double wv=1.0, double hv=1.0); double volume() const; protected: double length; double width; double height; }; class package : private box{ public: using box::volume; };
如上demo,即使package类是通过private继承了box类,但是通过using box::volume,volume函数就变成public(声明的时候不用传递返回值和参数)。当然也可以使用这样的方式来using数据成员。
但是不能用using声明基类的私有成员,因为私有成员在派生类中不能访问。
4.派生类中显式调用基类的构造函数
在最上面的1中最后一句话说到:在我们创建carton对象时,会先调用box的构造函数,再调用carton的构造函数。这个时候是直接默认调用无参的box类构造函数。那么如果要在派生类中传递参数到box类的构造函数呢?
demo:
carton.h
#ifndef _CARTON_H_ #define _CARTON_H_ #include "box.h" /* carton类派生于box类 */ class carton : public box{ public: carton(const char *pstr = "Cardboard"); ~carton(); carton(double lv, double wv, double hv, const char *pstr = "cardboard"); private: char *pMaterial; }; #endif
carton.cpp
carton::carton(double lv, double wv, double hv, const char *pstr) : box(lv, wv, hv) { /* 略 */ }
box.h
#ifndef _BOX_H_ #define _BOX_H_ class box{ public: box(double lv, double wv, double hv); double volume() const; protected: double length; double width; double height; }; #endif
main.cpp
carton candycarton(10.0, 20.0, 30.0, "thin cardboard");
需要注意:虽然基类中非私有数据成员可以在派生类中访问,但它们不能在派生类构造函数的初始化列表中初始化。
carton::carton(double lv, double wv, double hv, const char *pstr) : length(lv), width(wv), height(hv) { /* 略 */ }
这样是不被允许的,会提示length等数据成员不是carton类的成员。
这是因为在初始化时,这些成员还不存在。初始化列表是在调用基类的构造函数,对象的主要部分创建之前处理。
如果要显式初始化继承的数据成员,就必须在构造函数体中进行。
carton::carton(double lv, double wv, double hv, const char *pstr) { length = lv; width = wv; height = hv; pMaterial = new char[strlen(pstr) + 1]; std::strcpy(pMaterial, pstr); }
5.派生类中的副本构造函数
考虑一下以下情况:
box mybox(2.0, 3.0, 4.0); box copybox(mybox);
第二个语句中,对于这种初始化,编译器会自动调用副本构造函数,如果没有自定义副本构造函数,编译器会提供一个默认的版本,逐个复制原对象成员,创建一个新对象来。
那么对于派生类来讲,使用副本构造函数时,基类会复制吗?
box.cpp和box.h加入:
/* box.cpp */ box::box(const box &abox) : length(abox.length), width(abox.width), height(abox.height) { ; } /* box.h */ box(const box &abox);
carton.cpp和carton.h加入:
/* carton.h */ carton(const carton &acarton); /* carton.cpp */ carton::carton(const carton &acarton) { pMaterial = new char[strlen(acarton.pMaterial) + 1]; std::strcpy(pMaterial, acarton.pMaterial); }
主要注意:对于副本构造函数中存在指针的数据成员。必须复制指针指向的数据,而不是指针。否则一旦其中一个指针被删除,对应的数据也会被删除,剩下的一个指针就没有作用了。
main.cpp调用:
carton candycarton(20.0, 30.0, 40.0, "glassine board"); carton copycarton(candycarton);
运行后会发现,copycarton中box类的数据成员都是默认值1.0,而不是candycarton中的20、30、40。
这是因为编译器在复制candycarton的box子对象的时候,调用的是默认的box构造函数。
因此,我们需要在carton类的副本构造函数中调用box副本构造函数。
carton::carton(const carton &acarton) : box(acarton) { pMaterial = new char[strlen(acarton.pMaterial) + 1]; std::strcpy(pMaterial, acarton.pMaterial); }
6.重复的数据成员名和函数成员名
6.1 基类和派生类重复数据成员名
class base{ public: base(int num = 10) { value = num; } protected: int value; }; class derived : public base{ public: derived(int num = 20) {value = num;} int total() const; protected: int value; }; int derived::total const{ return value + base::value; };
需要使用基类名和作用域解析运算符限定成员名base::value。
6.2 基类和派生类重复函数成员名
相同函数名,但是参数不同:
使用using 声明,在派生类的作用域中引入基类成员函数的限定名。
class base{ public: void dothat(int arg); }; class derived : public base{ public: void dothat(double arg); using base::dothat; }; derived object; object.dothat(2); object.dothat(2.5);
相同函数名,相同参数:
derived object; object.base::dothat(2);
使用类名和作用域解析运算符来调用基类函数。
7. 多重继承