has-a关系
-
包含对象成员的类
- 易于理解及使用
- 可包含多个基类,易于区分。
- 构造函数初始化的是成员对象,而不是继承的对象,所以在初始化列表中使用的是成员名,而不是类名。
- 当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。一般来说,初始化顺序并不重要,但如果代码使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要了。
-
私有继承
-
提供的特性比包含多,如基类的保护成员在派生类中可以使用,但包含(继承层级结构外)不可用。
-
需要重新定义虚函数,包含类不能。使用私有继承,重新定义的函数只能在类中使用,而不是公有的。
-
通常,应使用包含来建立
has-a
关系,如果新类需要访问原有类的保护成员,或者需要重新定义虚函数,则应该使用私有继承。 -
基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数调用它。
-
私有继承使用类名和作用域解析运算符来调用基类的方法
double Student::Average()const { if(ArrayDb::size()>0) return ArrayDb::sum()/ArrayDb::size(); else return 0; }
-
访问基类对象时可使用强制类型转换
const string & Student::Name()const { return (const string &)*this; }
-
-
保护继承
-
基类的公有成员和保护成员都将成为派生类的保护成员
-
在第三代继承时,保护继承和私有继承的区别就呈现出来了。使用私有继承,基类的成员都将成为第二代的私有成员,所以第三代派生类无法使用基类的公有成员和保护成员。但使用保护继承则可以。
-
重新定义访问权限
- 如果在类外面使用基类方法,有两种选择。
- 在派生类中重新声明一个方法。
double Student::sum() const { return std::valarray<double>::sum(); }
- 使用一个using声明,只使用成员名——没有圆括号,函数特征标和返回类型。
class Student:private std::string,private std::valarray<double> { ... public: using std::valarray<double>::min; using std::valarray<double>::max; }
-
-
各种继承方式
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
多重继承(MI)
-
一个例子(以公有继承举例)
class Worker { ... }; class Waiter:public Worker { ... }; class Singer:public Worker { ... }; class SingerWaiter:public Waiter,public Singer { ... }; int main() { SingerWaiter ed; Worker *pw=&ed;//invalid }
-
通常,这种赋值将把基类指针设置为派生对象中的基类对象地址。但ed中包含两个Worker对象,有两个地址可供选择,故会有二义性。
-
正确写法
Worker *pw1=(Waiter *)&ed; Worker *pw2=(Singer *)&ed;
-
当只需要一个Worker对象时,C++引入了一个新技术——虚基类。
-
虚基类
-
虚基类的声明
class Singer : virtual public Worker{...}; class Waiter : public virtual Worker{...};//public和virtual的顺序无关紧要 class SingerWaiter : public Singer, public Waiter{...};
-
新构造函数规则
SingerWaiter(const Worker &wk,int p=0,int v=0) : Waiter(wk,p),Singer(wk,v){}//invalid SingerWaiter(const Worker &wk,int p=0,int v=0) :Worker(wk),Waiter(wk,p),Singer(wk,v){}//valid
注意:上述代码显示调用了构造函数Worker(const Worker &).请注意,这种用法对于虚基类来说,这种用法是合法的,必须这样做。但对于非虚基类来说,则是非法的。
- 使用基类方法
-
按照普通的使用的话,那么会造成二义性。所以这里得使用作用域解析运算符来澄清编程者的意图:
SingerWaiter newhire("Elise Hawks",2005,6,3); newhire.Singer::Show();
-
重新定义函数
void SingerWaiter::Show() { Singer::Show(); }
-
-
-
MI的其他问题
-
混合使用虚基类和非虚基类
当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。 -
虚基类和支配
-
一般情况下,如果类从不同的类那里继承了两个或多个的同名成员(数据或方法),则使用该成员名时,如果没有用类名进行限定,将导致二义性。
-
使用虚基类后,如果某个名称优先于其他名称,则使用它时,即使不使用限定符,也不会导致二义性。优先级:派生类中的名称优先于直接或间接祖先类中的相同名称。
class B { public: short q(); ... }; class C : virtual public B { public: long q(); int omg(); ... }; class D : public C { ... }; class E : virtual public B { private: int omg(); ... }; class F : public D,public E { ... };
-
类C中的q()定义优先于类B的q()定义,因此F中的方法可以使用q()来表示C::q().另一方面,任何一个omg定义都不优先于其他omg()定义。因为C和E都不是对方的基类。所以在F中使用非限定的omg()将导致二义性。
-
虚二义性规则与访问规则无关。也就是说,即使E::omg()是私有的,不能在F类中直接访问,但使用omg()仍将导致二义性。同样,即使C::q()是私有的,它也将优先于D::q()。在这种情况下,可以在类F中调用B::q(),但如果不限定q(),则将意味着要调用不可访问的C::q()
-
类模板
-
类模板以下面的代码开头
template<class Type>
-
模板成员函数用一下代码定义
template<class Type> bool Stack<Type>::push(const Type &item) { ... }
如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。
-
不能将定义放在独立实现文件中,最简单的方法即将所有模板信息放在一个头文件中,并在要使用这些模板文件中包含该头文件即可。
-
模板的具体化
-
隐式实例化
ArrayTP<int,100>stuff;
-
显式实例化
template class ArrayTP<string,100>;
-
显式具体化
template<>class SortedArray<const char*> { ...//details omitted }
重新定义以
const char *
进行实例化的类 -
部分具体化,即只限制模板的通用性
template<class T1,class T2>class Pair{...}//general template template<class T1>class Pair<T1,int>{...};
如果有多个模板可供选择,编译器将使用具体化程度最高的模板。
Pair<double,double>p1;//use general Pair template Pair<double,int>p2;//use Pair<T1,int>partial specialization Pair<int,int>p3;//use Pair<int,int> explicit specialization
也可以通过为指针提供特殊版本来部分具体化现有的模板
template<class T> class Feeb{...}; template<class *T> class Feeb{...}; Feeb<char>fb1;//use general Feeb template,T is char Feeb<char *>fb2;//use Feeb T* specialization,T is char
-
将模板用作参数
template <template <typename T>class Thing> class Crab { private: Thing<int> s1; Thing<double>s2; ... }
也可以将模板参数和常规参数混用
template <template<typename T>class Thing,typename U,typename V> class Crab { private: Thing<U>s1; Thing<V>s2; ... }
-
-
模板类与友元
-
模板类的非模板友元函数
- 不是通过对象调用
- 它可访问全局对象,或者独立于对象的模板类的静态数据成员
- 只有一个友元函数,被所有的类所共用。
#include<bits/stdc++.h> using namespace std; template <class T> class ABC { private: T item; static int ct; public: ABC(const T &i) : item(i){ct++;} ~ABC(){ct--;} friend void counts(); friend void reports(ABC<T> &); friend void counts2(); }; template<class T> int ABC<T>::ct=0; /*template<class T> void counts2() { cout<<ABC<int>::ct<<endl; } */ void counts() { cout<<"int count: "<<ABC<int>::ct<<"; "; cout<<"double count: "<<ABC<double>::ct<<endl; } void reports(ABC<int> &hf) { cout<<"int: "<<hf.item<<endl; } void reports(ABC<double> &hf) { cout<<"double: "<<hf.item<<endl; } int main() { counts(); //counts2(); ABC<int>a1(10); counts(); //counts2(); ABC<int>a2(20); counts(); //counts2(); ABC<double>a3(10.5); counts(); //counts2(); reports(a1); reports(a2); reports(a3); return 0; //tmp.show(); }
-
模板类的约束模板友元函数
- 类定义前声明每个模板函数
template <typename T>void counts(); template <typename T>void report(T &);
- 在函数中再次将模板声明为友元
template<typename TT> class HasFriendT { ... friend void counts<TT>(); friend void report<>(HasFriendT<TT>&); //same as friend void reprot<HasFriendT<TT> >(HasFriendT<TT>&)
-
模板类的非约束模板友元函数
- 友元模板类型参数与模板类类型参数不同
template<typename T> class ManyFriend { ... template<typename C,typename D> friend void show2(C &,D &); };
-
模板别名(C++11)
-
可使用
typedef
为模板具体化指定别名typedef std::array<double,12>arrd; arrd a1;
-
使用
using
指定别名template<typename T> using arrtype=std::array<T,12>; arrtype<double>a1;
-
C++11允许将语法
using=
用于非模板。用于非模板时,这种语法与常规typedef
等价typedef const char *pc1; using pc2=const char *; typedef const int *(*pa1)[10]; using pa2=const int *(*)[10];