C++提供了下面几种代码重用的手段:
1. 继承(公有继承(is-a)、私有继承和保护继承(has-a));
2. 包含/层次化/组合(has-a);
3. 多重继承
4. 类模板
继承了实现就是继承了数据成员,继承了接口就是继承了成员函数。
接口(派生类对象可以使用基类的方法)和实现(派生类对象存储了基类的数据成员,实现即为可以对数据成员进行操作(通过成员函数))
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不获得接口是has-a关系的组成部分。
虽然has-a关系的继承没有继承基类的接口,但可以在派生类的成员函数内部使用基类名和作用域运算符来访问基类方法。
私有继承
使用私有继承,基类的私有成员、保护成员都将成为派生类的私有成员。这意味着,基类方法不会成为派生类对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
‘包含‘ 将对象作为一个命名的成员对象添加到类中;而私有继承将对象作为一个未被命名的继承对象添加到类中。
私有继承的技术:
1. 初始化基类组件
构造函数使用成员初始化列表,使用类名而不是成员名来表示构造函数。
每一个继承的基类只对应一个成员对象。
2. 访问基类的方法
可以通过类名和作用域解析运算符来调用基类的方法。
3. 访问基类对象
通过私有继承的派生类如何访问基类对象?答案是使用强制类型转换。
4. 访问基类的友元函数
同样通过强制类型转换
当需要简历has-a关系时,使用基类还是私有继承
包含的优点:1. 易于理解;2. 继承会引起许多问题(多重继承时);3. 包含能够包括多个同类子对象。
继承的优点:1. 在派生类中可以使用基类的保护成员,而包含不能访问对象成员的保护成员;2. 当需要重定义虚函数时。
保护继承
保护继承是私有继承的变体,使用保护继承时,基类的公有和保护成员都将成为派生类的保护成员。
私有继承和保护继承之间的主要区别在第三代类中体现。私有继承的第三代类不能使用基类的方法(因为私有继承的基类的公有和保护成员将会成为第二代类的私有成员),而保护继承可以。
隐式向上类型转换意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类。
私有继承不能隐式向上类型转换,公有继承可以,保护继承在派生类中可以(?)。
使用using重新定义访问权限
使用保护或私有派生时,想让基类的方法在派生类外面可用,可将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员。
class Student : std::valarray<double> { ...... public: using std::valarray<double>::min; //必须使用类名进行限定 ...... }
多重继承(MI)
MI主要给程序员带来两个问题:
1. 从两个不同的基类继承同名方法(基类之间有同名的方法)(在派生类中使用类限定符来区分它们);
2. 从多个相关基类那里继承同一个类的多个实例(有多个基类继承于一个祖先时,将会在对象中产生一个类的多个实例)。
解决问题2.
多重派生对象将会产生各个基类对象,及基类的各级祖先类对象。若有多个基类继承与一个祖先,将会产生这个祖先的多个实例,产生二义问题。
C++引入了虚基类,在类声明中使用关键字virtual,使被限定的类成为虚基类。虚基类使多重派生对象只有一个(同时来自多个基类的)祖先类副本。
1 //祖先类(在Singer和Waiter类声明中被限定为虚基类) 2 class Worker { ...... }; 3 4 //基类Singer 5 class Singer : virtual public Worker 6 { ...... }; 7 //基类Waiter 8 class Waiter: virtual public Worker 9 { ...... }; 10 11 //多重派生类SingerWaiter 12 class SingerWaiter: public Worker, public Waiter 13 { ...... };
引入虚基类时,必须修改代码:(以下的祖先类表示多个类共同的(虚)基类)
引入虚基类前,在构造函数中用列表初始化基类成员对象时,一个祖先类对象将通过多个途径传递到多个基类构造函数。
为避免这个问题C++在祖先类是虚的时,禁止信息通过中间类自动传递给祖先类,默认调用祖先类的默认构造函数。
也可以显式的在成员列表初始化中调用祖先类的构造函数。
问题1.
如果从不同基类中继承了两个或多个同名成员,使用该成员名时,如果没有用类名进行限定将导致二义性。
但如果使用的是虚基类,这样做不一定会导致二义性。如果某个名称优先于其它所有名称则使用它,即便没有用类名限定,也不会导致二义性。
如何判断名称优先?
派生类中的名称优先于直接或间接祖先类中相同的名称。
虚二义性规则与访问规则无关:
即使同名函数是私有的,也不影响其优先顺序。
A同时是B和C的虚基子类,B派生出E,C和E派生出F。F的方法中调用show()方法将产生两个show()版本,即E的父类B中的show()和C的祖先基类A中的show()。由名称优先规则可知B类中的show()优先于A中的show(),因为B是由A派生而来。故F的类方法将隐式调用类B中的show(),而不用类名限定。
1 #include<iostream> 2 3 using std::cout; 4 using std::endl; 5 6 class A 7 { 8 public: 9 void show(){cout << "A" << endl;} 10 }; 11 // 12 class B : virtual public A 13 { 14 public: 15 void show(){cout << "B" << endl;} 16 void showw(){cout << "Bw" << endl;} 17 }; 18 // 19 class C : virtual public A 20 { 21 public: 22 void showw(){cout << "C" << endl;} 23 }; 24 // 25 class E : public B 26 { 27 public: 28 29 }; 30 // 31 class F : public E, public C 32 { 33 public: 34 void show0(){show();} 35 }; 36 37 //程序接口 38 int main() 39 { 40 F s; 41 s.show0(); 42 system("pause"); 43 return 0; 44 }
MI的复杂性主要是由派生类通过多条途径继承同一个基类引起的。
补充:
私有派生类有访问基类公有和保护成员的权限(在多重继承派生类中,为防止不同基类的同名成员产生二义性,通常使用类名和作用域解析运算符对此成员进行限定)。
模板类valarray简介
valarray类对象用于存储数字,提供了丰富的算术支持。
valarray<double> v1; //空数组
valarray<int> v2(8); //8个int元素的数组
valarray<float> v3(10.1, 8); //8个float元素的数组,每个元素的值都为10.1
int gpa[5] = {1, 2, 3, 4, 5};
valarray<int> v4(gpa, 4); //v4被初始化为gpa的前4个元素
在C++11中,可以对valarray对象进行列表初始化。
常用方法:
size();
sum();
min();
max().