继承与派生
一、相关概念:
继承:保持已有类的特性而构造新类的过程
派生:在已有类的基础上新增自己的特性而产生新类的过程
小结:同一过程从不同角度看
好处:
代码的可重用性和可扩充性以及基类的可靠性
二、访问控制
从基类继承的成员,其访问属性由继承方式控制。类的继承方式有public(公有继承)、protected(保护)、private(私有继承)三种。不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
公有继承
当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问。
例如:
#include<iostream> #include<cmath> using namespace std; class Point { //基类:点类 public: void initPoint(float x = 0, float y = 0) { this->x = x; this->y=y; } void move(float offX, float offY) { x += offX; y += offY; } float getX()const { return x; } float getY()const { return y; } private: float x, y; }; class Rectangle :public Point { //点类的派生类,对其公有继承 public: void initRectangle(float x, float y, float w, float h) { initPoint(x, y); //公有继承,可以使用基类的成员 this->w = w; this->h = h; } float getH()const { return h; } float getW()const { return w; } private: float w, h; }; int main() { Rectangle rect; rect.initRectangle(2, 3, 20, 10); rect.move(3, 2); cout << "The data of rect(x,y,w,h):" << endl; cout << rect.getX() << "," << rect.getY() << "," << rect.getW() << "," << rect.getH() << endl; return 0; }
运行结果:
从结果中可以看出,派生类Rectangle成功调用了基类Point类的成员函数而构造了一个矩形对象。说明了公有继承时,基类的共有成员和保护成员被继承到派生类中访问属性不变,仍作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问它们。
私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。
下面用一个例子来说明:
//header.h class A { public: void setA(int); void showA()const; private: int a; }; class B { public: void setB(int); void showB()const; private: int b; }; class C :public A, private B { public: void setC(int, int, int); void showC()const; private: int c; }; //caroline.cpp #include"header.h" #include<iostream> using namespace std; void A::setA(int x) { a = x; } void A::showA()const { cout << a << endl; } void B::setB(int x) { b = x; } void B::showB()const { cout << b << endl; } void C::setC(int x, int t, int z) { setA(x); setB(y); c = z; } void C::showC const() { cout << c << endl; } //wybie.cpp #include<iostream> #include"header.h" using namespace std; int main() { C obj; obj.setA(5); obj.showA(); obj.setC(6,7,9); obj.showC(); obj.setB(6); obj.showB(); return 0; }
运行结果:
从图片中可以看出,程序出现了错误,错误的原因是类C对类B是私有继承过来的,类B的公有成员被继承后作为C的私有成员,所以C的对象obj不能直接访问它们,因此报错。如果删掉这两句就可以正常运行了。
保护继承
保护继承中,基类的公有成员和保护成员都以保护成员的身份出项在派生类中,而基类的私有成员不可直接访问,这点与私有成员类似。但可以被派生类的成员函数访问
例如:
//基类A: class A{ protected: int x; }; //主函数: int main(){ A a; a.x=5; }
如果这样写的话,那么程序在编译的时候就会报错,因为类A建立的对象a企图访问本类的保护成员,这是不允许的,正是因为保护成员的访问规则和私有成员的访问规则是一样的。但上文说过可以被派生类的成员函数访问
class A{ protected : int x; }; calss B: public A{ public: void function(); }; void B::function(){ x=5; } int main(){ B b; b.function(); return 0; }
这样的话就是实现了对基类保护成员的访问。
三、派生类的构造函数和析构函数
构造派生类的对象时,要对基类的成员对象和新增成员对象进行初始化。基类的构造函数并没有继承下来,对基类成员对象的初始化工作需要通过调用基类的构造函数。派生类的构造函数的参数一部分要传递给基类的构造函数,用于初始化相应的成员,另一些参数要用于对派生类新增的成员对象进行初始化。
派生类构造函数的一般语法形式为:
派生类名::派生类名(参数表):基类名1(基类1初始化参数表),、、、,基类名n(基类n初始化参数表),成员对象名1(成员对象1初始化参数表),、、、,成员对象名m(成员对象m初始化参数表) { 派生类构造函数的其他初始化操作 }
派生类构造函数执行的一般次序如下:
- 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
- 执行派生类的构造函数体中的内容。
细节:构造函数初始化列表中的基类名、对象名之间的次序无关紧要,他们各自出现的顺序可以是随意的,无论它们的顺序怎么样安排,基类构造函数的调用和各个成员对象的初始化顺序都是确定的。
例如:
#include<iostream> using namespace std; class B { public: B(); //默认构造函数 B(int i); //有参数的构造函数 ~B(); //析构函数 void print()const; private: int b; }; B::B() { b = 0; cout << "B's default constructor called." << endl; } B::B(int i) { b = i; cout << "B's constructor called." << endl; } B::~B() { cout << "B's destructor called." << endl; } void B::print()const { cout << b << endl; } class C :public B { public: C(); //默认构造函数 C(int i, int j); //有参数 ~C(); //C的析构函数 void print()const; private: int c; }; C::C() { c = 0; cout << "C's default constructor called." << endl; } C::C(int i, int j):B(i),c(j) { cout << "C's destructor called." << endl; } C::~C() { cout << "C's destructor called." << endl; } void C::print()const { B::print(); cout << c << endl; } int main() { C obj; //不带参数,自动调用B::B()、C::C() obj.print(); return 0; }
运行结果:
从前两行结果(default constructor)可以看出调用的是基类B和类C的默认构造函数,私有成员的值都是0。
如果将主函数换成:
int main() { C obj(5, 6); //对象有两个参数,自动调用C::C(5,6),进而首先调用B::B(5),然后执行C::C(5,6)的初始化列表和函数体 obj.print(); return 0; }
运行结果:
从图中可以看出执行的是有参数的构造函数,私有成员的值发生改变。
从这两此运行结果中还可以看出对于析构函数的调用规则是,先构造的先析构、后构造的后析构