继承和派生
三种继承方式
- 共有继承
- 私有继承
- 保护继承
公有继承(public)
继承的访问控制
基类的public成员和protected成员:访问属性在派生类中保持怖不变;
基类的private成员:不可直接访问访问权限
派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象:只能访问public成员。私有继承(private)
继承的访问控制
基类的public和protected成员:都以private身份出现在派生类中;
基类的private成员:不可直接访问。访问权限
派生类的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象:不能直接访问从基类继承的任何成员。保护继承
继承的访问控制
基类的public和protected成员:都以protected身份出现在派生类中
基类的private成员:不可以直接访问访问权限
派生类的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象:不能直接访问从基类继承的任何成员。protected成员的特点与作用
对建立其所在类对象的模块来说,它与private成员的性质相似。
对于其派生类来说,它与public成员的性质相同。
既实现了数据隐藏,又方便继承,实现代码重用。
class A { protected: int x; }; A a; a.x = 5; // 错误 class A { protected: int x; }; class B :public A { public: void function(); }; void B::function() { x = 5; // 正确 }
example:
#include <iostream> #include <cmath> #include "Rectangle.h" using namespace std; int main() { Rectangle rect; //定义Rectangle类的对象 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.h #ifndef _RECTANGLE_H #define _RECTANGLE_H #include "Point.h" 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; }; #endif //_RECTANGLE_H //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类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; }; #endif //_POINT_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; }; void A::setA(int x) { a = x; } void B::setB(int x) { b = x; } void C::setC(int x, int y, int z) { // 派生类成员直接访问基类的公有成员 setA(x); setB(y); c = z; } int main() { C obj; obj.setA(5); obj.showA(); // obj.setB(6); 错误 // obj.showB(); 错误 obj.setC(6, 7, 9); obj.showC(); return 0; }
类型转换
- 公有派生类对象可以被当作基类的对象使用,反之则不可。
派生类的对象可以隐含转换为基类对象;
派生类的对象可以初始化基类的引用;
派生类的指针可以隐含转换为基类的指针。 - 通过基类对象名、指针只能使用从基类继承的成员。
- 不要重新定义继承而来的非虚函数*
#include <iostream> using namespace std; class Base1 { public: void display() const { cout << "Base1::display()" << endl; } }; class Base2 :public Base1 { public: void display() const { cout << "Base2::display()" << endl; } }; class Derived:public Base2 { public: void display() const { cout << "Derived::display()" << endl; } }; void fun(Base1* ptr) { // 参数为指向基类对象的指针 ptr->display(); // “对象指针->成员名” } int main() { Base1 base1; // 声明Base1类对象 Base2 base2; // 声明Base2类对象 Derived derived; // 声明Derived对象 fun(&base1); // 用Base1对象的指针调用fun函数 fun(&base2); // 用Base2对象的指针调用fun函数 fun(&derived); // 用Derived对象的指针调用fun函数 return 0; } /* Base1::display() Base1::display() Base1::display() */
默认情况下
- 基类的构造函数不被继承;
- 派生类需要定义自己的构造函数。
若不继承基类的构造函数
- 派生类新增成员:派生类定义构造函数化;
- 继承来的成员:自动调用基类构造函数进行初始化;
- 派生类的构造函数需要给基类的构造函数传递参数。
单继承构造函数的定义语法:
- 派生类名::派生类名(基类所需的形参, 本类成员所需的形参):
基类名(参数表), 本类成员初始化列表
{
// 其他初始化;
};
多继承时构造函数的定义语法
- 派生类名::派生类名(参数表):
基类名1(基类1初始化参数表),
基类名2(基类2初始化参数表),
...
参数名n(基类n初始化参数表),
本类成员初始化列表
{
// 其他初始化;
};
派生类与基类的构造函数
- 当基类又默认构造函数时
派生类构造函数可以不向基类构造函数传递参数。
构造派生类对象时,基类的默认构造函数将被调用。 - 如果执行基类中带参数的构造函数
派生类构造函数应为基类构造函数提供参数。
构造函数执行顺序
- 调用基类构造函数。
顺序按照它们被继承时声明的顺序(从左到右)。 - 对初始化列表中的成员进行初始化。
顺序按照它们在类中定义的顺序。
对象成员初始化时自动调用其所属类的构造函数
由初始化列表提供参数。 - 执行派生类的构造函数体中的内容。
#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(); 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 constructor called." << endl; } C::~C() { cout << "C's destrutor called." << endl; } void C::print()const { B::print(); cout << c << endl; } int main() { C obj(5, 6); obj.print(); return 0; } /* B's constructor called. C's constructor called. 5 6 C's destrutor called. B's destructor called. */
#include <iostream> using namespace std; class Base1 { public: Base1(int i) { cout << "Constructing Base1 " << i << endl; } }; class Base2 { public: Base2(int j) { cout << "Construting Base2 " << j << endl; } }; class Base3 { public: Base3() { cout << "Construing Base3 *" << endl; } }; class Derived :public Base2, public Base1, public Base3 { public: Derived(int a, int b, int c,int d):Base1(a), member2(d), member1(c), Base2(b){} private: Base1 member1; Base3 member3; Base2 member2; }; int main() { Derived obj(1, 2, 3, 4); return 0; } /* Construting Base2 2 Constructing Base1 1 Construing Base3 * Constructing Base1 3 Construing Base3 * Construting Base2 4 */
派生类的复制构造函数
若派生类没有声明复制构造函数
- 编译器会在需要时生成一个隐含的复制构造函数;
- 先调用基类的复制构造函数;
- 再为派生类新增的成员执行复制。
若派生类定义复制构造函数 - 一般都为基类的复制构造函数。
- 复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。
- 基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用。
- 例如:
C::C(const C &c1):B(c1){...}
派生类的析构函数
- 析构函数不被继承,派生类如果需要,要自行声明析构函数
- 声明方法与无继承关系时类的析构函数相同。
- 不需要显式地调用基类的析构函数,系统会自动隐式调用。
- 先执行派生类析构函数的函数体,再调用基类的析构函数。
当派生类与基类中有相同成员时:
- 若未特别限定,则通过派生类对象使用的是派生类中的新定义的同名成员。
- 如果通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符(::)来限定
#include <iostream> using namespace std; class Base1 { //定义基类Base1 public: int var; void fun() { cout << "Member of Base1" << endl; } }; class Base2 { //定义基类Base2 public: int var; void fun() { cout << "Member of Base2" << endl; } }; class Derived : public Base1, public Base2 { //定义派生类Derived public: int var; //同名数据成员 void fun() { cout << "Member of Derived" << endl; } //同名函数成员 }; int main() { Derived d; Derived* p = &d; d.var = 1; //对象名.成员名标识 d.fun(); //访问D1类成员 d.Base1::var = 2; //作用域分辨符标识 d.Base1::fun(); //访问Base1基类成员 p->Base2::var = 3; //作用域分辨符标识 p->Base2::fun(); //访问Base2基类成员 return 0; } /* Member of Derived Member of Base1 Member of Base2 */
二义性问题
- 如果从不同基类继承了同名成员,但是在派生类中没有定义同名成员,“派生类对象或引用名.成员名”、“派生类指针->成员名”访问成员存在二义性问题
- 解决方式:用类名限定。
- 对象存储冗余了
1.空间浪费
2.不一致性
class A { public: void f(); }; class B { public: void f(); void g(); }; class C :public A, public B { public: void g(); void h(); }; int main() { C c1; c1.f(); // 有二义性 c1.g(); // 无二义性(同名隐藏) /* 解决方法一:用类名来限定 c1.A::f() 或 c1.B::f() 解决方法二:同名隐藏 在C中声明一个同名成员函数f(), f()再根据需要调用A::f() 或 B::f() */ }
#include <iostream> using namespace std; class Base0 { //定义基类Base0 public: int var0; void fun0() { cout << "Member of Base0" << endl; } }; class Base1 : public Base0 { //定义派生类Base1 public: //新增外部接口 int var1; }; class Base2 : public Base0 { //定义派生类Base2 public: //新增外部接口 int var2; }; class Derived : public Base1, public Base2 { //定义派生类Derived public: //新增外部接口 int var; void fun() { cout << "Member of Derived" << endl; } }; int main() { //程序主函数 Derived d; //定义Derived类对象d d.Base1::var0 = 2; //使用直接基类 d.Base1::fun0(); d.Base2::var0 = 3; //使用直接基类 d.Base2::fun0(); return 0; } /* d.var0 d.Base0::var0 有二义性 */