06 继承与派生

继承与派生

1.定义

继承

  • 在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)。

派生

  • 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。

  • 派生类一经定义后,可以独立使用,不依赖于基类。

  • 派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public

  • 在派生类的各个成员函数中,不能访问 基类中的private成员。

派生类写法:

1
2
3
4
class 派生类名:public 基类名
{

};

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>

using namespace std;

// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{width = w;}
void setHeight(int h)
{height = h;}
protected:
int width;
int height;
};

// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{return area * 70;}
};

// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{ return (width * height); }
};

int main(void)
{
Rectangle Rect;
int area;

Rect.setWidth(5);
Rect.setHeight(7);

area = Rect.getArea();

// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;

// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;

return 0;
}
2. 派生类对象的内存空间

派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前

3.类的两种关系——继承关系与复合关系

继承:“是”关系。

  • 基类 A,B是基类A的派生类。

  • 逻辑上要求:“一个B对象也是一个A对象”。

复合:“有”关系。(可能算下表中的组合关系?吧)

  • 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系

  • 一般逻辑上要求:“D对象是C对象的固有属性或组成部分”

1
2
3
4
5
6
7
8
9
10
11
class CPoint
{
double x, y;
friend class CCircle;
//便于Ccirle类操作其圆心
};
class CCircle
{
double r;
CPoint center;
};

复合关系的另一种写法:(可能算下表中的关联关系吧?)

例题:为“狗”类设一个“业主”类的对象指针;为“业主”类设一个“狗”类的对象指针数组。

1
2
3
4
5
6
7
8
9
10
class CMaster; //CMaster必须提前声明,不能先
//写CMaster类后写Cdog类
class CDog
{
CMaster *pm;
};
class CMaster
{
CDog *dogs[10];
};

3. 派生类覆盖基类成员

派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;
class base
{
public:
int j;int i;
void func(){cout<<"fuck"<<endl;};
};
class derived : public base
{
public:
int i;
void access();
void func(){cout<<"fucku"<<endl;};
};
void derived::access()
{
j = 5; //引用的是基类的j(只有一个j)
i = 5; //引用的是派生类的 i
base::i = 5; //引用的是基类的 i
func(); //派生类的
base::func(); //基类的
}

int main()
{
derived obj;
obj.i = 1;
obj.base::i = 1;
obj.access();
cout<<obj.j<<endl;//obj.j改成obj.base::j输出一样的结果
return 0;
}

输出结果:

1
2
3
fucku
fuck
5

注:若在基类中加入int k;并且在access方法中加入k=3;则报错,因为派生类的成员函数无权访问基类的私有变量。

4. 类的保护成员
  • 基类的private成员:可以被下列函数访问

    • 基类的成员函数

    • 基类的友元函数

  • 基类的public成员:可以被下列函数访问

    • 基类的成员函数

    • 基类的友元函数

    • 派生类的成员函数

    • 派生类的友元函数

    • 其他的函数

  • 基类的protected成员:可以被下列函数访问

    • 基类的成员函数

    • 基类的友元函数

    • 派生类的成员函数可以访问当前对象和其它对象的基类的保护成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;
class Father
{
private:
int nPrivate; //私有成员
public:
int nPublic; //公有成员
protected:
int nProtected; // 保护成员
};
class Son : public Father
{
void AccessFather()
{
nPublic = 1; // ok;
nPrivate = 1; // wrong
nProtected = 1; // OK,访问从基类继承的protected成员
Son f;
f.nProtected = 1; // ok
}
};

int main()
{
Father f;
Son s;
f.nPublic = 1; // Ok
s.nPublic = 1; // Ok
f.nProtected = 1; // error
f.nPrivate = 1; // error
s.nProtected = 1; // error
s.nPrivate = 1; // error
return 0;
}
5. 派生类的构造函数

例如,我们写了以下 “昆虫”类 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Bug
{
private:
int nLegs;
int nColor;

public:
int nType;
Bug(int legs, int color);
void PrintBug(){};
};
class FlyBug : public Bug // FlyBug是Bug的派生类
{
int nWings;

public:
FlyBug(int legs, int color, int wings);
};
  • 继承关系派生类的构造函数的写法:

对于类FlyBug而言,只能使用 初始化列表构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Bug的构造函数没有关系,跟之前一样的
Bug::Bug(int legs, int color)
{
nLegs = legs;
nColor = color;
}
//错误的FlyBug构造函数
FlyBug::FlyBug(int legs, int color, int wings)
{
nLegs = legs; // 不能访问
nColor = color; // 不能访问
nType = 1; // ok
nWings = wings;
}
//正确的FlyBug构造函数:
FlyBug::FlyBug(int legs, int color, int wings) : Bug(legs, color)
{
nWings = wings;
}

使用时,即初始化对象时写法:FlyBug fb ( 2,3,4);

注意:

  • 在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数

  • 调用基类构造函数的两种方式

    • 显式方式:在派生类的构造函数中,为基类的构造函数提供参数.

    derived::derived(arg_derived-list):base(arg_base-list)

    • 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的无参构造函数
  • 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。

考虑以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
using namespace std;

class Base
{
public:
int n;
Base(int i) : n(i)
{cout << "Base " << n << " constructed" << endl;}
~Base()
{cout << "Base " << n << " destructed" << endl;}
};
class Derived : public Base
{
public:
Derived(int i) : Base(i)
{cout << "Derived constructed" << endl;}
~Derived()
{cout << "Derived destructed" << endl;}
};
int main()
{
Derived Obj(3);
return 0;
}

输出结果:

1
2
3
4
Base 3 constructed
Derived constructed
Derived destructed
Base 3 destructed
6. 包含成员对象的派生类的构造函数写法

即:第一类复合关系的派生函数的构造函数的写法(关键在最后一行啊前面是铺垫)

也是使用初始化列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Bug
{
private:
int nLegs;
int nColor;

public:
int nType;
Bug(int legs, int color);
void PrintBug(){};
};
class Skill
{
public:
Skill(int n) {}
};
class FlyBug : public Bug
{
int nWings;
Skill sk1, sk2;

public:
FlyBug(int legs, int color, int wings);
};
FlyBug::FlyBug(int legs, int color, int wings) : Bug(legs, color), sk1(5), sk2(color), nWings(wings){}
7.封闭派生类对象的构造函数和析构函数的执行顺序

在创建派生类的对象时:

  1. 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;

  2. 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。

  3. 最后执行派生类自己的构造函数

在派生类对象消亡时:

  1. 先执行派生类自己的析构函数

  2. 再依次执行各成员对象类的析构函数

  3. 最后执行基类的析构函数

析构函数的调用顺序与构造函数的调用顺序相反。

8.public继承的赋值兼容规则

以public方式继承时,考虑以下程序段:

1
2
3
4
5
6
class base
{};
class derived : public base
{};
base b;
derived d;

1) 派生类的对象可以赋值给基类对象

b = d;

2) 派生类对象可以初始化基类引用

base & br = d;

3) 派生类对象的地址可以赋值给基类指针

base * pb = & d;

如果派生方式是 private或protected,则上述三条不可行。

protected继承和private继承:

1
2
3
4
5
6
class base
{};
class derived : protected base
{};
base b;
derived d;
  • protected继承时,基类的public成员和protected成员成为派生类的protected成员。

  • private继承时,基类的public成员成为派生类的private成员,基类的protected成员成为派生类的不可访问成员。

  • protected和private继承不是“是”的关系。

9. 基类与派生类的指针强制转换

公有派生的情况下,派生类对象的指针可以直接赋值给基类指针:

Base * ptrBase = &objDerived;

因为基类指针指向基类对象的首地址,也就是指向派生类对象,而派生类的所有变量地址中前几个是基类变量所拥有的地址,但是因为指针是指向基类的指针,所有只能访问的内存是基类的那几个变量。

  • ptrBase指向的是一个Derived类的对象;

  • *ptrBase可以看作一个Base类的对象,访问它的public成员直接通过ptrBase即可,但不能通过ptrBase访问objDerived对象中属于Derived类而不属于Base类的成员

  • 即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。

但是,通过强制指针类型转换,可以把ptrBase转换成Derived类的指针:

1
2
Base * ptrBase = &objDerived;
Derived *ptrDerived = (Derived * ) ptrBase;

程序员要保证ptrBase指向的是一个Derived类的对象,否则很容易会出错。

10.直接基类和间接基类

在声明派生类时,只需要列出它的直接基类

派生类的成员包括:

  • 派生类自己定义的成员

  • 直接基类中的所有成员

  • 所有间接基类的全部成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
using namespace std;
class Base
{
public:
int n;
Base(int i) : n(i)
{
cout << "Base " << n << " constructed"
<< endl;
}
~Base()
{
cout << "Base " << n << " destructed"
<< endl;
}
};
class Derived : public Base
{
public:
Derived(int i) : Base(i)
{
cout << "Derived constructed" << endl;
}
~Derived()
{
cout << "Derived destructed" << endl;
}
};
class MoreDerived : public Derived
{
public:
MoreDerived() : Derived(4)
{
cout << "More Derived constructed" << endl;
}
~MoreDerived()
{
cout << "More Derived destructed" << endl;
}
};
int main()
{
MoreDerived Obj;
return 0;
}

输出结果:

1
2
3
4
5
6
Base 4 constructed
Derived constructed
More Derived constructed
More Derived destructed
Derived destructed
Base 4 destructed
11. 多继承

多继承即一个子类可以有多个父类,它继承了多个父类的特性。

C++ 类可以从多个类继承成员,语法如下:

1
2
3
4
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};

// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};

// 派生类
class Rectangle: public Shape, public PaintCost//多继承
{
public:
int getArea()
{
return (width * height);
}
};

int main(void)
{
Rectangle Rect;
int area;

Rect.setWidth(5);
Rect.setHeight(7);

area = Rect.getArea();

// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;

// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;

return 0;
}

输出结果:

1
2
Total area: 35
Total paint cost: $2450

附录:Python面向对象程序设计菜鸟教程链接Python 面向对象 | 菜鸟教程 (runoob.com)