C++ Primer Plus 第14章 虚基类和类模板等

1.成员对象的列表初始化和继承列表初始化的区别

派生类对象的构造函数初始化,构造函数在成员初始化列表中使用基类类名来调用特定的基类构造函数

V2(int &w,double &q):V1(w){}	//v1是基类,这种情况初始化列表中是用基类名称

而一个类中包含其他类对象作为成员的话,构造函数在成员初始化列表中使用成员名,比如

Student(string &s,valaray<double> &a):name(s),scores(a){}	//name和scores是私有成员类对象

2.列表初始化顺序

当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。当一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要,所以编写一定要规范。

3.如果成员变量是一个对象,应该怎么调用该对象的类方法

一种方法是类对象调用自己的方法,然后在方法中使用包含的私有成员对象来调用该对象所属的类方法。
另外就是如果该对象是一个公共成员,那么可以通过(.)调用
比如:

class Test
{
private:
	string s1;
public:

	Test(const string& s) :s1(s) { cout << s1 << endl; }
	unsigned int  len()
			{
				return s1.length();
			}
};
int main()
{
	string s = "string";
	Test t1("stringasasd");
	cout<<t1.len();
	//t1.s1.length;
}

4.私有继承

私有继承不继承公共接口,这相当于将基类设置为派生类的私有对象成员,只不过这种对象没有名称,具体的代码实现大概如下所示:

class Student:private string,private valarray<double>
{
private:
....
public:
Student(const *str,const double *pd,int n):string(str),valarray<double>(pd,n);//构造函数
}

以上我们需要注意的有两点:
1.继承的时候,类名称后面跟的是private关键字,多重继承几个类就有几个private,中间用逗号隔开。但是不添加关键字private在私有继承来说也是可以的,因为编译器默认继承是私有派生,但是对于公有继承和保护继承,则必须使用public或protected来限定每一个基类。
2.派生类构造函数初始化的时候,因为继承的两个类并没有实际的名称,因此它使用类名初始化而不是成员名来标识构造函数。所以,对于构造函数,包含私有变量为类对象的情况相当于成员变量初始化,而对于私有继承,相当于调用基类构造函数。

5.如果私有继承,应该怎么调用基类的类方法

与公有继承一样,可以在方法内用作用域解析运算符来访问基类方法

double Student::Average()const
{
	if(valarray::size() > 0 )
		return valarray::size::sum / valarray::size::size();
	else
		return 0;
}

6.访问基类对象

作用域解析符可以访问基类的方法,但是如果要使用基类对象本身,可以使用强制转换,即把派生类类型转换为基类类型,并且返回*this,就可以返回基类对象了。

7.访问基类的友元函数

友元函数不能继承,但是和公有继承一样,可以通过显式强制转换为基类类型来调用正确的函数,但是这里需要注意两点:
1.无论是公有继承还是私有继承,友元函数要访问基类对象,都需要显式地转换类型,不然会导致递归调用。
2.如果不显式转换类型,编译器不会自动将派生类引用或指针赋值给基类引用或指针。

8.使用using关键字来重新定义访问权限

class Student:private string,private valarray<double>
{
private:
..
public:
	using valarray<double>::min;
	using valarray<double>::max;
}

这样就可以将私有继承而来的对象方法变为公有方法,需要注意的是using声明只使用成员名——没有圆括号、函数特征和返回类型。

9.什么是虚基类,为什么要用虚基类?

1.当一个派生类多重继承了两个基类,而这两个基类中刚好有相同的部分,即又都继承了一个相同的基类,那么这个时候就会出现二义性问题。比如当这个派生类要访问两个基类相同的部分,这个时候编译器检测到两个地址可供选择,当然我们可以通过显式转换来指定我们需要访问的对象,但是无疑这样做是麻烦的。
所以为了解决这种问题,c++引入了一种新技术——虚基类,虚基类指的是从多个类(它们的基类相同)派生出的对象只继承一个基类对象,该技术是使用关键是virtual来实现的,比如:

class singer : virtual public Worker{...};	//virtual 和public 不分先后
class Waiter : virtual public Worker{...};
class SingerWaiter : public singer,public Waiter{...};	//SingerWaiter对象就只包含Worker对象的一个副本,而不是两个。

2.当我们并不需要基类的多个拷贝,而只是需要继承一个基类对象的的特征的时候,就需要虚基类,但是虚基类会要求额外的运算时间,而且有缺点。

10.虚基类的构造规则

一般的派生类C构造函数只能调用它的基类B构造函数,它的基类B构造函数又将调用B的基类A的构造函数,这是一种自动传递的层次设计,但是在虚基类中,这种自动传递将失效,C++在基类是虚时,禁止信息通过中间类自动传递给基类。但是我们知道派生类构造函数生效之前必须要构造基类的对象组件,所以在基类为虚的时候,编译器将使用基类的默认构造函数。
如果不希望默认构造函数来构造基类对象,则需要显式地调用所需的基类构造函数。比如:

SingerWaiter(const Worker &wk,int p = 0,int v = Singer::other)::Worker(wk),Waiter(wk,p),Singer(wk,v){}	
//worker为Waiter和Singer的基类,SingerWaiter 是Waiter和Singer的派生类

对于虚基类,这种方式调用是可以的,但是对于非虚基类则是非法的。

11.虚基类中对祖先基类方法的调用

对于单继承,派生类如果没有重新定义一个方法,那么调用将会是最近祖先的方法定义,但是在多重继承中,每个直接祖先都有一个同名函数,这将导致调用二义性。
有两种方法可以解决这个问题:
1.用作用域解析符指定要使用哪个祖先基类的同名方法。
2.重新定义一个同名方法,其中包含基类方法,然后添加自己需要的代码,最后将这些组件组合起来。

12.混合使用虚基类和非虚基类将会产生什么结果?

只需要记住使用虚基类只有一个祖先对象,而每一个非虚派生祖先都会继承一个基类祖先,比如B是X、Y的虚基类,而是A、C的非虚基类,那么当有一个M派生类继承了XYAC这四个基类,那么它会有3个B类子对象。

13.类模板的定义

1.模板类函数定义之前需要加上以下开头

template <class Type>
或者
template <typename Type> 

2.类限定符需要加上,比如Stack::
3.类模板的使用,比如新建一个模板类对象:Stack nets;需要添加类型,如果是Stack将使用int替换掉模板中所有的Type
4.最重要的一点是不能将模板成员函数放在独立的实现文件中,这是因为模板不是函数,它们不能单独编译。为此,最简单的方法就是将所有模板信息都放在头文件中。

14.模板类中的表达式参数(非类型参数)的注意事项

template <class T,int n>	//其中的int类型参数就叫做表达式参数也称为非类型参数

表达式参数有一些限制,表达式参数可以是整型、枚举、引用或指针,因此double n不合法,但是double *n,double &n合法。
模板代码不能修改参数的值,也不能使用参数的地址。

15.模板类继承

模板类可以作为基类,也可用作派生类,比如:

template <typename T>
class Array
{
private:
	T entry;
	...
public:
	...
}

template <typename Type>
class GrowArray : public Array<Type>{...}	//继承

template <typename Tp>
class Stack
{
	Array<Tp> ar;	//将Array<>用作组件类
	...
}

16.默认类型模板参数

类模板的另一项新特性是,可以为类型参数提供默认值

template<class T1,class T2 = int>
class Tim{...};

这样,可以省略T2的初始化,编译器将使用int

17.模板的具体化

1.隐式实例化
在.cpp文件中定义一个对象时,该对象指出所需的类型,而编译器使用通用的模板提供的方案生成具体的类定义,也就是说,编译器在需要对象之前,不会生成类的隐式实例化。
2.显示实例化
当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显示实例化。声明必须位于模板定义所在的名称空间中,例如:

template class ArrayTp<string,100>;

在这种情况下,虽然没有创建ArrayTp对象,但是编译器也将生成类声明,虽然尚未请求这个类的对象

18.显式具体化

有时候,我们可能需要对某一种或者多种类型进行专门的类代码编写,使其与一般类型行为不同。在这种情况下,可以创建显式具体化。

template<>class ClassName<specialized-type-name>{...};

19.模板类和友元

1.模板类的非模板友元函数
该友元函数将是模板所有实例化的友元,不管typename是什么类型。
2.如果要为友元函数提供模板类参数,必须要声明明确的typename类型
比如:

friend void report(HashFriend &);	//这种是错误的
friend void report(HashFriend<T> &)	//这种是正确的

为什么需要具体化呢,这是因为友元函数不是模板函数,它是一个实实在在的函数,编译器在运行它的时候是需要明确指定模板类的类型的,这意味着必须要为使用的友元定义显示具体化。
2.模板类的约束模板友元函数
这种情况分为三步
第一步:首先在类定义之前声明每个模板函数
第二步:然后再在类中将模板声明为友元
第三步:为友元提供模板定义
2.模板类的非约束模板友元函数
这种情况就是每个函数具体化都是每个类具体化的友元,比如:

template<typename C,typename D>friend void show2(C& ,d &);	//友元函数声明

你可能感兴趣的:(C++,Primer,Plus)