5. 软件重用的方式

本文最后更新于 2024年1月27日 下午

软件重用的方式

这是浙江大学翁恺老师的公开课,《面向对象设计C++》
视频地址:
https://www.bilibili.com/video/BV1yQ4y1A7ts/?spm_id_from=333.337.search-card.all.click&vd_source=3074f6f6ab43a114c5af8727fa4f7255

本节对应视频14-16部分。

对象组合

面向对象的三大特性是封装、继承和多态性。继承和组合是面向对象对软件重用的实现的关键方式。

组合(composition)是通过已有的对象组合起来来创造出新的对象,可以用“has-a”的语言来描述新对象和原来对象的关系:比如已经有“轮胎”和“引擎”两个对象,就可以组合起来创造出新的对象“车”。

面向对象的五条基本原则中就指出“一个对象的内部是其他对象”。
从C++代码实现的角度来看,组合指的是在设计一个类的时候,这个类的成员变量可以是其他类的对象。
组合的实现方式有两种,一种叫Fully,另一种叫By reference。Fully是指一个对象在另一个对象内部,即成员变量是其他对象。 By reference是指一个对象通过指针引用另一个对象,成员变量是指向其他对象的指针。根据类的设计决定使用哪种方法。
比如一个雇员的信息包括了:名字、住址、岗位和直属领导,这里直属领导就是另一个雇员。
下面展示了一个Fully的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class Person {...};
class Currency {...};
class SavingAccount {
public:
SavingAccount(const char* name, const char* address, int cents);
~SavingAccount();
void print();
private:
Person m_saver; //Fully
Currency m_balance; // Fully
};

SavingAccount中包括了CurrencyPerson两个类,SavingAccount的构造函数需要在初始化时调用CurrencyPerson的构造函数:
1
2
3
4
5
6
SavingAccount(const char* name, const char* address, int cents): m_saver(name,address),m_balance(0,cents) {...}
void SavingsAccount::print() {
m_saver.print();
m_balance.print();
// 假设Person 和 Currency都提供了print方
}

如果类当中有成员变量是其他类的对象,它就应该在初始化列表中被初始化。

继承

继承(inheritance)将现有的类作为基础,通过改造得到一个新的类,它可以使得类之间共享成员数据、成员函数和接口(成员函数public的部分)的设计。
继承是用一个类来得到一个新的类的设计方法,新的类是原来的类的超集(superset),比如下图可以说“student”继承了“person”。也就是说,继承的类必须是原来的类的扩充,比如“student”所拥有的描述/数据/功能要比“person”更多。

在继承方法中,新类和老的类的关系可以描述为“is-A”,原来的类称为父类(parent class),从这个类继承的新类称为这个类的子类(child class)或者派生类(derived class)。

继承的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
class A {
public:
A():i(0) { cout << "A::A()" << endl;}
~A() { cout << "A::~A()" << endl;}
void print() {cout<<"A::print()"<< i << endl;}
void set(int ii) { i = ii;}
private:
int i;
};

class B : public A { //class B继承了A的设计,public是必须的

};

int main()
{
B b; //类B继承了类A中全部的内容,因此对象b具有类A的对象的全部内容
b.set(10); //B也具有A的set函数
b.print();
return 0;
};

这个程序在运行的过程中,在b被创建的过程也会调用类A的构造函数;此外,b也会拥有setprint这两个函数,这表明子类继承了父类包括父类的构造函数、析构函数、公共部分和私有部分等全部内容
如果这时候对类B进行一些修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
class B : public A { //class B继承了A的设计,public是必须的
public:
void f() {set(20); print();} //B中新增加的部分
};

int main()
{
B b; //类B继承了类A中全部的内容,因此对象b具有类A的对象的全部内容
b.set(10); //B也具有A的set函数
b.print();
b.f();
return 0;
};
程序也可以通过,表明子类的增加的新的函数可以调用父类中的公共成员。【但是不能调用父类的私有部分。】

编译时,父类的构造函数先被调用,再构造子类。析构的时候,子类先被析构,父类后被析构。

protected

protected是一种访问权限,它允许父类和子类允许使用protected的内容,但是程序的其他部分(包括主函数main)都不能调用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
#include<iostream>
using namespace std;
class A {
public:
A():i(0) { cout << "A::A()" << endl;}
~A() { cout << "A::~A()" << endl;}
void print() {cout<<"A::print()"<< i << endl;}
protected:
void set(int ii) { i = ii;} //set()是受保护的
private:
int i;
};
class B : public A {
public:
void f() {set(20); print();}
};

int main()
{
B b;
b.set(10); //此时main函数不能够访问set()函数,该程序会出错
b.print();
b.f();
return 0;
};
该程序会在编译时报错:
1
2
3
4
5
$ vi a.cpp
$ g++ a.cpp
>> a.cpp: In function 'int main()':
>> a.cpp:11:error:'void A::set(int)' is protected
>> a.cpp:29:error: within this context

总结在类的设计中的几类权限:private用于保护父类的所有的数据,public用于父类向子类和程序的其他部分提供服务,protected的接口用于让父类向子类能够操作数据。


5. 软件重用的方式
https://l61012345.top/2024/01/04/学习笔记/C++面向对象设计/5.类的继承/
作者
Oreki Kigiha
发布于
2024年1月4日
更新于
2024年1月27日
许可协议