文章目录
1 单一继承
在C++中,声明单一继承的一般形式如下:
class 派生类名:访问权限 基类名 {
private:
成员声明列表
protected:
成员声明列表
public:
成员声明列表
};
在派生类中继承的基类成员的初始化,需要由派生类的构造函数调用基类的构造函数来完成,这和初始化对象成员有类似之处。
定义派生类的构造函数的一般形式如下:
派生类名::派生类名(参数表):基类名(参数表) { // 函数体 }
构造函数(包括析构函数)是不被继承的,所以一个派生类只能调用它的直接基类的构造函数。当定义派生类的一个对象时,首先调用基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数,如果某个基类仍是一个派生类,则这个过程递归进行。当该对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反。
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x, int y):x(x),y(y) {
cout << "point construct" << endl;
}
void showXY() {
cout << "x = " << x << ", y = " << y << endl;
}
~Point() {
cout << "point destruct" << endl;
}
};
// 在派生类中,基类构造函数和析构函数是不被继承的
class Rectangle:public Point {
private:
int h, w;
public:
// 在派生类构造函数中初始化基类成员
Rectangle(int h, int w, int a, int b):Point(a, b) {
this->h = h;
this->w = w;
cout << "rectangle construct" << endl;
}
void show() {
cout << "h = " << h << ", w = " << w << endl;
}
~Rectangle() {
cout << "rectangle destruct" << endl; // 对象消失,会先执行派生类析构函数
}
};
int main() {
Rectangle r(3, 4, 5, 6);
r.showXY();
r.show();
return 0;
}
输出结果:
point construct
rectangle construct
x = 5, y = 6
h = 3, w = 4
rectangle destruct
point destruct
2 访问权限和赋值兼容规则
所谓赋值兼容规则是指在公有派生情况下,一个派生类的对象可以作为基类的对象来使用的情况。(简单说就是java的多态)
2.1 派生的对象可以赋给基类的对象
derived d;
base b;
b = d; // 基类拿的是派生类的对象
2.2 派生类的对象可以初始化类的引用
derived d;
base& br = d; // 初始化类引用
2.3 派生类的对象地址可以赋给指向基类的指针:
derived d;
base* pb = &d; // 拿的是派生类的对象地址
注意:静态成员可以被继承,这时基类对象和派生类的对象共享该静态成员。(就是父类定义了静态成员变量,子类也可以使用这些静态成员变量)
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x, int y) {
this->x = x;
this->y = y;
}
void show() {
cout << "x = " << x << ", y = " << y << endl;
}
};
class Rectangle:public Point {
private:
int w, h;
public:
Rectangle(int w, int h, int a, int b):Point(a, b) {
this->w = w;
this->h = h;
}
void show() {
cout << "w = " << w << ", h = " << h << endl;
}
};
// 简单说,就是左边是父类,右边赋什么值都是会调用父类的函数和成员
int main() {
Point a(1, 2);
Rectangle b(3, 4, 5, 6);
a.show();
b.show();
Point& ra = b; // 派生类对象初始化基类的引用
ra.show(); // 实际调用的是基类的show()
Point* p = &b; // 派生类对象的地址赋给指向基类的指针
p->show(); // 实际调用的是基类的show()
Rectangle* pb = &b;
pb->show(); // 调用派生类的show()
return 0;
}
输出结果:
x = 1, y = 2
w = 3, h = 4
x = 5, y = 6
x = 5, y = 6
w = 3, h = 4
3 私有派生
通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这时就成了派生类的私有成员,派生类的对象不能访问继承的基类成员,必须定义公有的成员函数作为接口。更重要的是,虽然派生类的成员函数可通过自定义的函数访问基类的成员,但将该派生类作为基类再继续派生时,这时即使使用公有派生,原基类公有成员在新的派生类中也将是不可访问的。
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x, int y) {
this->x = x;
this->y = y;
}
void show() {
cout << "x = " << x << ", y = " << y << endl;
}
};
// Rectangle私有派生Point
class Rectangle:private Point {
private:
int w, h;
public:
Rectangle(int w, int h, int a, int b):Point(a, b) {
this->w = w;
this->h = h;
}
void show() {
Point::show();
cout << "w = " << w << ", h = " << h << endl;
}
};
class Test:public Rectangle {
public:
Test(int w, int h, int a, int b):Rectangle(w, h, a, b) {}
void show() {
// Point::show(); // Rectangle私有派生Point,即使Rectangle再公有派生,Test类已经无法使用基类Point的函数
Rectangle:show();
}
};
私有派生的这一特点不利于进一步派生,因而实际中私有派生用得并不多。
4 多重继承
一个类从多个基类派生的一般形式如下:
class 派生类名:访问权限 基类1,访问权限 基类2,....访问权限 基类n {}
“基类1”继承了“基类2”到“基类n”的所有数据成员和成员函数,访问权限用于限制其派生类中的成员对象对基类的访问权限,其规则和单一继承情况一样。
4 二义性及其支配规则
#include <iostream>
using namespace std;
class A {
public:
void func() {
cout << "a.func" << endl;
}
};
class B {
public:
void func() {
cout << "b.func" << endl;
}
void gunc() {
cout << "b.gunc" << endl;
}
};
class C:public A,public B {
public:
void gunc() {
cout << "c.gunc" << endl;
}
void hun1() {
A::func();
}
void hun2() {
B::func();
}
void hunc() {
// func(); // 编译器不知道要调用哪个类的func()函数,具有二义性
}
};
int main() {
C obj;
obj.A::func(); // a.func()
obj.B::func(); // b.func()
obj.B::gunc(); // b.gunc()
obj.C::gunc(); // c.gunc()
obj.gunc(); // c.gunc()
obj.hun1(); // a.func()
obj.hun2(); // b.func()
return 0;
}
编译器是自类C开始沿继承树向上搜索的,所以 obj.gunc()
没有二义性,它使用离类C最近的gunc()版本。
如果基类中的名字在派生类中再次声明,则派生类中的名字就隐藏了基类中的相应名字。C++可以迫使编译器“看到”当前作用域的外层部分,存取那些被隐藏的名字。这一过程叫做作用域分辨。作用域分辨操作的一般形式如下:
类名::标识符
void main() {
C c;
C* c1 = new C;
c.C::gunc();
c1->C::gunc();
c.B::func();
c1->A::func();
}
6 派生类支配基类的同名函数
如果这时派生类定义了一个和基类成员函数同名的新成员函数(因为参数不同属于重载,所以这里是指具有相同参数表的成员函数),派生类的新成员函数就覆盖了外层的同名成员函数。在这种情况下,直接使用成员名只能访问派生类的成员函数,只有使用作用域分辨,才能访问基类的同名成员函数。
class A {
public:
void show() {}
};
class B:public A {
public:
void show() {}
};
A和B都有show()函数,如果直接使用对象B,则会调用B的show();如果要使用基类的show(),要添加作用域分辨。
B obj;
obj.show(); // 调用B类的show()
obj.A::show(); // 调用A类的show()