C++ 温习

1.前言

以前分析Android源码分析道native层,一些简单的C++知识还够我流畅的阅读分析源码,最近分析到hal层,发现C++的知识不够用了,这里打算花段时间去温习下C++,知道的温习,不知道的补习。
ps:本文以及后续温习C++的文章若没具体明示,都是参照阅读《C++ Primer Plus(第6版)_中文版_带书签_超清》。

2.语法

2.1 头文件
包含头文件有如下几种方式:
C++ 温习_第1张图片
2.2 名称空间
C++ 温习_第2张图片
2.3 位与字节
C++ 温习_第3张图片
2.4 运算符重载
C++ 温习_第4张图片
2.5 auto关键字
C++ 温习_第5张图片
2.6 指针定义注意点
C++ 温习_第6张图片
2.7 复合类型
数组、指针和结构

2.8 内联函数 inline
内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数直接的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。
理解代码运行对时间、空间消耗。
无非就是空间换取时间,或者时间换取空间。

如何使用inline函数?
C++ 温习_第7张图片
内联与宏的关系
C++ 温习_第8张图片
2.9 引用变量
引用变量是一种伪装指针,它允许变量创建别名。引用变量主要被用作处理结构和类对象的函数的参数。
2.9.1 创建引用变量

int rats;
int & rodents = rats; // int & 指的是指向intd的引用

ps:经测试 rodents 和 rats 地址也是一样。
可以理解引用为const 指针,但并不是。

如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。

如:double refcube(const double &ra);

2.9.2 使用const
C++ 温习_第9张图片
2.9.3 const 引用返回
C++ 温习_第10张图片
2.9.4 何时使用引用?

3.1 默认参数
函数默认参数的默认值的设置必须通过函数原型。
即,将值赋给原型中的参数。

char * left(const char * str,int n = 1);

4.1 函数重载(多态)
同名同类型返回值函数,参数类型,个数不同。

void print(const char * str, int width);
void print(double d, int width);
void print(long l, int width);

4.1.1 何时使用函数重载?
C++ 温习_第11张图片
5.1 函数模板
函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时候也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。
关键字template和typename是必需的。除非可以使用关键字class代替typename。必须使用尖括号。

//函数模板原型
template 
void Swap(T & a, T &b);
//函数模板定义
template 
void Swap(T & a, T &b)
{
	T temp;
	temp = a;
	a = b;
	b = temp;
}

若如下使用编译器则会生成对应的函数定义。

int a = 0;
int b = 1;
Swap(a,b);

void Swap(int & a, int &b) //该函数程序员不能看到,但编译器确实生成并使用了它们
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

5.1.2 重载解析
C++ 温习_第12张图片
6.1 头文件
C++ 温习_第13张图片
6.1.2 头文件管理
C++ 温习_第14张图片
C++ 温习_第15张图片
7.1 存储持续性
C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。
C++ 温习_第16张图片
7.1.1 全局变量和局部变量
C++ 温习_第17张图片
8.1 对象和类
8.1.1 类和结构
C++ 温习_第18张图片
8.1.2 内联方法
内联方法其定义位于类声明中的函数都将自动成为内联函数。类声明常将短小的成员函数作为内联函数。
内联函数的特殊规则要去在每个使用它们的文件中都对其进行定义。确保内敛定义对多文件程序中的所有文件都是可用的、最简便的方法是:将内联定义放在定义类的头文件心中。

9.1 客户/服务器模型(C/S)
C++ 温习_第19张图片
10.1 构造函数和析构函数
构造函数是一种特殊的类成员函数,在创建类对象时被调用。构造函数的名称和类名相同,但通过函数重载,可以创建多个同名的构造函数。
构造函数没有声明类型。
构造函数通常用于初始化类对象的成员,初始化应与构造函数的参数列表匹配。

用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特俗的成员函数,即析构函数。
每个类只能有一个析构函数。
析构函数没有返回类型(连void都没有),也没有参数。

11.1 const成员函数
C++ 温习_第20张图片
12.1 this 指针
每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式this。在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值。然而,要返回的并不是this,因为this是对象的地址,而是对象本身,即this(将解除引用运算符*用与指针,将得到指针指向的值)。
C++ 温习_第21张图片
13.1 对象数组:声明对象数组的方法与声明标准类型数组相同。

14.1 类作用域:在类中定义的名称(如类数据成员名和类成员函数名)的作用于都是整个类。
使用类成员名时,必须根据上下文使用直接成员运算符(.)、简介成员运算符(->)或作用域解析运算符(:?。

Ik * pik = new Ik;
pik->ViewIk();

Ik ee = Ik(8);
ee.ViewIk();

14.1.2 作用域为类的常量:使用static关键字
14.1.3 作用域内枚举:所有对象中都不包含枚举
eg错误示例:

class Bakery
{
private:
	enum {Months = 12};
	double costs[Months];
}

需要使用枚举,可以使用作用域内枚举。
C++ 温习_第22张图片
15.1 抽象数据类型

16.1 oop总结
C++ 温习_第23张图片
17.1 友元
通常公有类方法提供唯一对象私有部分的访问途径。但是有时候太过严格,以至于不适合特定的编程问题。C++提供了另外一种形式的访问权限-友元
17.1.2 友元函数
如何创建友元函数?
C++ 温习_第24张图片
C++ 温习_第25张图片
17.1.3 常用的友元:重载<<运算符
一个很有用的类特性是,可以对<<运算符进行重载,使之能与cout一起来显示对象的内容。
只有在类声明中的原型中才能使用friend关键字。除非函数定义也是原型,否则不能在函数定义中使用该关键字。

18.1 重载运算符:作为成员函数还是非成员函数?
C++ 温习_第26张图片
因为运算符重载是通过函数来实现的,所以只要运算符函数的特征标不同,使用的运算符数量与相应的内置C++运算不同,就可以多次重载同一个运算符。

19.1 类的自动转换和强制类型转换
只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字explicit限定了这种构造函数,则它只能用于显示转换,否则也可以用于隐式转换。
19.1.1 转换函数:构造函数只能用于从某种类型到类类型的转换。eg:

Stonewt incognito = 275;

但不能用构造函数进行相反转换,要进行相反的转换,必须使用特殊的C++运算符函数–转换函数。
转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。eg:

Stonewt wolfe(285.7);
double host = double (wolfe);
double thinker = (doube) wolfe;

如何创建转换函数
C++ 温习_第27张图片
C++ 温习_第28张图片
20.1 特殊成员函数
20.1.2 默认构造函数:如果没有提供任何构造函数,C++将创建默认构造函数。
20.1.3 复制构造函数:复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。
函数原型如下:

Class_name(const Class_name &);
eg: StringBad(const StringBad &);

对于复制构造函数,需要知道两点:何时调用和有何功能。
何时调用复制构造函数?
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
默认的复制构造函数的功能?
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。

21.1 C++11 空指针
C++ 温习_第29张图片
22.1 静态类成员函数
C++ 温习_第30张图片
23.1 在构造函数中使用new时应注意的事项
1.如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete。
2.new和delete必须相互兼容。new对应于delete,new[]对应于delete[]。
3.如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。 然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr),这是因为delete(无论待中括号还是不带中括号)可以用于空指针。
在这里插入图片描述
4.应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。通常,这种构造函数与下面类似。

String::String(const String & st)
{
	num_string++;		//hanle static member update if necessary
	len = st.len;			//sam length as copied string
	str = new char[len + 1];	//allot space
	std::strcpy(str,st.str);		//copy string to new location
}

具体的说,复制构造函数应分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址。另外,还应该更新所有受影响的静态类成员。
5.应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。通常,该类方法与下面类似:

String & String::operator=(const String & st)
{
	if(this == &st)			//object assigned to itself
		return *this;			//all done
	delete [] str;				//free old string
	len = st.len;
	str = new char[len + 1];	//get space for new string
	std::strcpy(str,st.str);		//copy the string
	return *this;						return reference to invoking object
}

具体的说,该方法应完成这些操作:检查自我赋值的情况,释放成员指针以前指向的内存,复制数据而不仅仅是数据的地址,并返回一个指向调用对象的引用

24.1 返回对象的说明。
当成员函数或独立的函数返回对象时,有如下几种返回方式可供选择。
1.返回指向对象的引用。
2.返回指向对象的const引用。
3.返回const对象。
24.1.1 返回指向const对象的引用
返回对象将调用复制构造函数,而返回引用不会。
引用指向的对象应该在调用函数执行时存在。
返回的对象尽量和函数传入的参数类型相对应。
24.1.2 返回指向非const对象的引用
常见的两种返回非const对象情形是,第一:重载赋值运算符。第二:重载与cout一起使用的<<运算符。
24.1.3 返回对象
如果返回的对象是被调用函数中的局部变量,则不应按引用的方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数。因此,当控制权回到调用函数时,引用指向的对象将不再存在。在这种情况下,应返回对象而不是引用。
C++ 温习_第31张图片
总之,如果方法或者函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或者函数要返回一个没有公有复制构造函数的类的对象,它必须返回一个指向这种对象的引用。最后,有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为其效率更高。

25.1 使用new初始化对象
C++ 温习_第32张图片
26.1 析构函数的调用时机
C++ 温习_第33张图片
27.1 指针和对象小结
使用对象指针时,需要注意以下几点:
图示:

1.使用常规表示法来声明指向对象的指针:

String * glamour;

2.可以将指针初始化为指向已有的对象:

String * first = &sayings[0];

3.可以使用new来初始化指针,这将创建一个新的对象(有关使用new初始化指针的细节):

String * favorite = new String(saying[choice]);

图示:
C++ 温习_第34张图片
4.对类使用new将调用相应的类构造函数来初始化新创建的对象:

//invokes defalut constructor
String * gsleep = new String;
//invokes the String(const chat *) constructor
String * glop = new String("my my my");
//invokes the String(const String &) constructor
String * favorite = new String(saying[choice]);

5.可以使用->运算符通过指针访问类方法:

if(saying[i].length() < shortest->length())

6 可以对对象指针应用解除引用运算符(*)来获得对象:

if (sayings[i] < *first)		//compare object values
	first = &sayings[i];		//assign object address

28.1 队列特征
C++ 温习_第35张图片
29.1 部分总结

C++ 温习_第36张图片
30.1 类构造函数特殊初始化数据成员的特殊语法
C++为类构造函数提供了一种可用来初始化数据成员的特殊语法。这种语法包括冒号和由逗号分隔的初始化列表,都放在构造函数参数的右括号后,函数体的左括号之前。
每一个初始化器都由被初始化的成员的名称和包含初始值的括号组成。
从概念来说,这些初始化操作是在对象创建时进行的,此时函数体中语句还没有执行。语法如下:

queue(int qs) : qsize(qs), item(0), front(NULL), rear(NULL) {}

如果数据成员是非静态const成员或引用,则必须采用这种格式,但可将C++11新增的类内初始化用于非静态初始化const成员。
C++11允许类内初始化,即在类定义中进行初始化:

class Queue
{
private:
...
	Node * front = NULL;
	enum {Q_SIZE = 10};
	Node * rear = NULL;
	int items = 0;
	const int qszie = Q_SZIE;
...
};

这与使用成员初始化列表等价。
然而,使用成员舒适化列表的构造函数将覆盖相应的类内初始化。

31.1 类继承
可以通过集成完成的工作:
1.可以在已有类的基础上添加功能。例如:对于数组类,可以添加数学运算。
2.可以给类添加数据。例如,对于字符串类,可以派生出一个类,并添加指定字符串显示颜色的数据成员。
3.可以修改类方法的行为。例如,对于代表提供给飞机乘客的服务的Passenger类,可以派生出提供更高级别服务的FirstClassPassenger类。
31.1.1 基类
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类
为说明继承,首先需要一个基类。
派生一个类

//RatedPlayer derives from the TableTennisPlayer base class
class RatedPlayer : public TableTennisPlayer
{
...
};

冒号指出RatedPlayer类的基类是TableTennisplayer类。public声明头表明TableTennisPlayer是一个公有基类,这被称为公有派生。派生类对象包含基类对象。
使用公有派生,基类的公有成员将称为派生类的公有成员。
基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。
C++ 温习_第37张图片
需要在继承特性中添加:
1.派生类需要自己的构造函数
2.派生类可以根据需要添加额外的数据成员和成员函数。
ps:派生类的构造函数必须给新成员(如果有的话)和继承的成员提供数据。
派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。
有关派生类的构造函数的要点如下:

1.首先创建基类对象;
2.派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
3.派生类构造函数应初始化派生类新增的数据成员。

C++ 温习_第38张图片
派生类和基类成员初始化列表:
派生类构造函数可以使用初始化器列表机制将值传递给基类构造函数。
eg:

derived::derived(type1 x, type2 y) : base(x,y) //initializer list
{
	...
}

其中deriver是派生类,base是基类,x和y是基类构造函数使用的变量。
如果派生类构造函数接收到参数10和12,则这种机制将把10和12传递给被定义为接受这些类型的参数的基类构造函数。除虚基类外,类只能将值传递回相邻的基类,但后者可以使用相同的机制将信息传递相邻的基类,以此类推。如果没有在成员初始化列表中提供基类构造函数,程序将使用默认的基类构造函数。
派生类和基类之间的特殊关系:

1.派生类对象可以使用基类的方法,条件是方法不是私有的;
2.基类指针可以在不进行显示类型转换的情况下指向派生类对象;
3.基类引用可以在不进行显示类型转换的情况下引用派生类对象。

eg:

RatedPlayer rplayer1(1140, "Mallort", "Duck", true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rt.Name();	//invoke Name() with reference
pt->Name();	//invoke Name() with pointer

继承:C++有三种继承方式:公有继承、保护继承和私有继承
is-a关系(还有has-a、is-implemented-as-a、uses-a)
笔者第一次接触这种,详情百度,谷歌。

多态公有继承派生类对象使用基类的方法,而未做任何修改。
但希望同一个方法在派生类和基类中的行为是不同的,换句话说,方法的行为应取决于调用该方法的对象。这种行为叫做多态–具有多种形态。
以下两种重要的机制可用于实现多态公有继承:

1.在派生类中重新定义基类的方法;
2.使用虚方法。

ps:如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。这样,程序将根据对象类型而不是引用或指针的类型来选择方法版本。为基类声明一个虚析构函数也是一种惯例(这样做是为了确保释放派生对象时,按正确的顺序调用析构函数。)。
C++ 温习_第39张图片
32.1 静态联编和动态联编
将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。
在编译过程中进行联编被称为静态联编(static binding),又称为早期联编(early binding)。
因为虚函数的出现,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编(dynamic binding),又称为晚期联编(late binding)。

32.1.2 指针和引用类型的兼容性
在C++中,动态联编与通过指针和引用调用方法相关,从某种程度上说。这是由继承控制。公有继承建立is-a关系的一种方法是如何处理指向对象的指针和引用。
通常,C++不允许将一种类型的地址赋值给另一种类型的指针,也不允许一种类型的引用指向另一种类型。
但是,指向基类的引用或指针可以引用派生类对象,而不必进行显示类型转换。
向上强制转换:将派生类引用或指针转换为基类引用或者指针。向上强制转换是可传递的,也就是说,如果A是基类,B是派生类,B派生C类,A指针或者引用可以引用A对象,B对象或者C对象。
向下强制转换:与向上强制转换相反,将基类指针或者引用转换为派生类指针或者引用。如果不使用显示类型转换,则向下强制转换时不允许的。因为is-a关系通常是不可逆的。

32.1.3 虚成员函数
如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置非虚方法。
另外,使用虚函数时,在内存和执行速度方面有一定的成本,包括:

1.每个对象都将增大,增大量为存储地址的空间;
2.对于每个类,编译器都创建一个虚函数地址表(数组);
3.对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。

虽然非虚函数的效率比虚函数高,但不具备动态联编功能。

32.1.4 虚函数注意事项

1.在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的;
2.如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象;
3.如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。

对于虚方法,还有如下几点需要注意:

1.构造函数
构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类的构造函数,所以将类构造函数声明为虚的没什么意义。
2.析构函数
析构函数应该是虚函数,除非类不用做基类。通常应该基类提供一个虚析构函数,即使它并不需要析构函数。原因是关于派生类对象释放的原因。
3.友元
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数。
4.没有重新定义
如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本。例外的情况是基类版本是隐藏的。
5.重新定义将隐藏方法
派生类重新定义不会生成函数的两个重载版本,而是隐藏了基类的函数版本。重新定义继承的方法并不是重载。如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管参数特征标如何。
如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或者指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变,因为允许返回类型随类类型的变化而变化。这种例外只适用于返回值,而不适用于参数。
如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果派生类只重新定义一个版本,则基类其他的版本将被隐藏,派生类对象将无法使用它们。注意,如果派生类不需要修改,则定义可只调用基类版本。

33.1 访问控制: protected
访问控制还有public、private。
public是公有的,而protected和private的区别只有在基类派生的类中才会表现出来。
派生类的成员可以直接访问基类的保护成员(protected),但不能直接访问基类的私有成员(private)。
因此,对于外部世界来说,保护成员的行为与私有成员相似,但对于派生类来说,保护成员的行为与公有成员相似。
注意: 最好对类数据成员采用私有访问控制,不要使用保护访问控制,同时通过基类方法使派生类能够访问基类数据。(因为基类的设计是只能基类的成员函数才能够修改基类的成员变量)
然而,对于成员函数来说,保护访问控制很有用,它让派生类能够访问公众不能使用的内部函数。

34 抽象基类
C++通过使用纯虚函数(pure virtual function)提供未实现的函数。纯虚函数声明的结尾处为=0。
eg:

virtual double Area() const = 0; //a pure virtual function

当类声明中包含纯虚函数时,则不能创建该类的对象。这里的理念是,包含纯虚函数的类只用作基类。
ps:至少有一个虚函数是纯虚函数,这样的类才是抽象类。

35 编译器生成的成员函数
编译器会自动生成一些公有成员函数–特殊成员函数。

1.默认构造函数
默认构造函数要么没有参数,要么所有的参数都有默认值。如果没有定义任何构造函数,编译器将定义默认构造函数,让程序员能够创建对象。
自动生成的默认构造函数的另一项功能是,调用基类的默认构造函数以及调用本身是对象的成员所属类的默认构造函数。
如果派生类构造函数的成员初始化列表中没有显式调用基类构造函数,则编译器将使用基类的默认构造函数来构造派生类对象的基类部分。在这种情况下,如果基类没有构造函数,将导致编译阶段错误。
如果定义了某种构造函数,编译器将不会定义默认构造函数。在这种情况下,如果需要默认构造函数,则必须自己提供。
提供构造函数的动机之一是确保对象总能够被正确的初始化。如果类包含指针成员,则必须初始化这些成员。因此,最好提供一个显式默认构造函数,将所有的类数据成员都初始化为合理的值。
2.复制构造函数
复制构造函数接受其所属类的对象作为参数。
eg: Star(const Star &);
在下述情况下,将使用复制构造函数:
2.1.将新对象初始化为一个同类对象;
2.2.按值将对象传递给函数;
2.3.函数按值返回对象;
2.4.编译器生成临时对象;
如果程序没有使用(显式或隐式)复制构造函数,编译器将提供原型,但不提供函数定义;否则。程序将定义一个执行成员初始化的复制构造函数。也就是说,新对象的每个成员都被初始化为原始对象相应成员的值。如果成员为类对象,则初始化该成员时,将使用相应类的复制构造函数。
ps:在某些情况下,成员初始化是不合适的。如,使用new初始化的成员指针通过要求执行深复制,或者类可能包含需要修改的静态变量。这就需要定义自己的复制构造函数。
3.赋值运算符
默认的赋值运算符用于处理同类对象之间的赋值。
默认赋值为成员赋值。如果成员为类对象,则默认成员赋值将使用相应类的赋值运算符。

36 定义类的其他注意点

1.构造函数
构造函数不同于其他类方法,因为它创建新的对象,而其他类方法只是被现有的对象调用。这是构造函数不能被继承的原因之一。继承意味着派生类对象可以使用基类的方法。然而,构造函数在完成其工作之前,对象并不存在。
2.析构函数
一定要定义显式析构函数来释放类构造函数使用new分配的所有内存,并完成类对象所需的任何特殊的清理工作。对于基类,即使它不需要析构函数,也应提供一个虚析构函数。
3.转换
使用一个参数就可以调用的构造函数定义了从参数类型到类类型的转换。
将可装换的类型传递给以类为参数的函数时,将调用转换构造函数。
在带一个参数的构造函数原型中使用explicit将禁止进行隐式转换,但仍允许显式转换。
要将类对象转换为其他类型,应定义转换类型。转换函数可以是没有参数的类成员函数,也可以是返回类型被声明为目标类型的类成员函数。即使没有声明返回类型,函数也应返回所需的转换值。
C++11支持将关键字explicit用于转换函数。与构造函数一样,explicit允许使用强制类型转换进行显式转换,但不允许隐式转换。
4.按值传递对象与传递引用
为了提高效率,通常编写使用对象作为参数的函数时,应按引用而不是按值来传递对象。
按值传递对象涉及到生成临时拷贝,即调用复制构造函数,然后调用析构函数。调用这些函数需要时间,复制大型对象比传递引用花费的时间要多得多。如果函数不修改对象,应将参数声明为const引用。
按引用传递对象的另一个原因是在继承使用虚函数时,被定义为接受基类引用参数的函数可以接受派生类。
5.返回对象和返回引用
在编码方面,直接返回对象与返回引用之间的唯一区别在于函数原型和函数头:
Star noval(const Star &);		//return a Star object
Star & nova2(const Star &);	//return a reference to a Star
何时返回对象何时返回引用,这里有个通用规则,但不排除其他情况。
如果函数返回在函数中创建的临时对象,则不要使用引用。
如果函数返回的是通过引用或指针传递给它的对象,则应按引用返回对象。
6.使用const
使用const时应特别注意。可以用它来确保方法不修改参数;
可以使用const来确保方法不修改调用它的对象;
通常,可以将返回引用的函数放在赋值语句的左侧,这实际上意味着可以将值赋给引用的对象。但可以使用const来确保引用或指针返回的值不能用于修改对象中的数据。
注意,如果函数将参数声明为指向const的引用或指针,则不能将该参数传递给另一个函数,除非后者也确保了参数不会被修改。

37 类函数总结
C++ 温习_第40张图片
38 类继承总结
继承通过使用已有的类(基类)定义新的类(派生类),使得能够根据需要修改编程代码。公有继承建立is-a关系,意味着派生类对象也应该是某种基类对象。作为is-a模型的一部分,派生类继承基类的数据成员和大部分方法,但不继承基类的构造函数、析构函数和赋值运算符。派生类可以直接访问基类的公有成员和保护成员,并能够通过基类的公有方法和保护方法访问基类的私有成员。可以在派生类中新增数据成员和方法,还可以将派生类用作基类,来做进一步的开发。每个派生类都必须有自己的构造函数。程序创建派生类对象时,将首先调用基类的构造函数,然后调用派生类的构造函数;程序删除对象时,将首先调用派生类的析构函数,然后调用基类的析构函数。
如果要将类用作基类,则可以将成员声明为保护的,而不是私有的,这样,派生类将可以直接访问这些成员。然而,使用私有成员通过可以减少出现编程问题的可能性。如果希望派生类可以重新定义基类的方法,则可以使用关键字virtual将它声明为虚的。这样对于通过指针或引用访问的对象,能够根据对象类型来处理,而不是根据引用或指针的类型来处理。具体的说,基类的析构函数通常应该是虚的。
可以考虑定义一个ABC(抽象类),只定义接口,而不涉及实现。ABC必须至少包含一个纯虚方法,可以在声明中的分号前面加上=0来声明纯虚方法。

virtual double area() const = 0;

不一定非得定义纯虚方法。对于包含纯虚成员的类,不能使用它来创建对象。纯虚方法用于定义派生类的通用接口。

39 C++中代码重用
C++的一个主要目标是促进代码重用。公有继承是实现这种目标的机制之一,但并不是唯一的机制。
1.使用这样的类成员:本身是另一个类的对象,这种方法称为包含(containment)、组合(composition)或层次化(layering)
2.使用私有或保护继承。通常,包含、私有继承和保护继承用于实现has-a关系,即新的类将包含另一个类的对象
多重继承使得能够使用两个或更多的基类派生出新的类,将基类的功能组合在一起。
39.1 包含对象成员的类
接口和实现:使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。或者接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
39.2 C++和约束
C++包含让程序员能够限制程序结构的特性–使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据,等待。这样做的根本原因是:在编译阶段出现错误优于在运行阶段出现错误。
39.3 初始化顺序
当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。

40 私有继承
C++还有另一种实现has-a关系的途径–私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用公有继承,基类的公有方法将成为派生类的公有方法。总之,派生类将继承基类的接口;这是is-a关系的一部分。使用私有继承,基类的公有方法将成为派生类的私有方法。总之,派生类不继承基类的接口。正如从被包含对象中看到的,这种不完全继承是has-a关系的一部分。
使用私有继承,类将继承实现。
包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。
因此,私有继承提供的特性与包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系。
要进行私有继承,要使用关键字private而不是public来定义类。(实际上,private是默认值,因此省略访问限定符也将导致私有继承
使用多个基类的继承被称为多重继承(multiple inheritance,MI)

40.1 使用包含还是私有继承
由于既可以使用包含,也可以使用私有继承来建立has-a关系,那么应使用何种方式呢?
使用包含的优点:

1.易于理解,类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。
2.继承会引起很多问题,尤其从多个基类继承时,可能必须处理很多问题。如包含同名方法的独立的基类或共享祖先的独立基类。
3.包含能够包括多个同类的子对象。

通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。

41 保护继承
保护继承是私有继承的变体。保护继承在列出基类时使用关键字protected:

class Student : protected std::string,
				protected std::valarray
{...};

使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。
使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
41.1 各种继承方式
C++ 温习_第41张图片
42 多重继承(MI)
MI描述的是有多个直接基类的类。与单继承一样,公有MI表示的也是is-a关系。例如,可以从Waiter类和Singer类派生出SingingWaiter类:

class SingingWaiter : public Waiter, public Singer {...};

注意,必须使用关键字public来限定每一个基类。这是因为,除非特别指出,否则编译器将认为是私有派生:

class SingingWaiter : public Waiter,Singer {...}; //Singer is a private base

MI可能带来如下主要问题:
1.从两个不同的基类继承同名方法;
2.从两个或者更多相关基类那里继承同一个类的多个实例。
解决方法:祖先相同的MI
定义一个抽象基类Worker,并使用它派生出Waiter类和Singer类。然后,便可以使用MI从Waiter类和Singer类派生出SingingWaiter类。
C++ 温习_第42张图片
43 虚基类
C++引入多重继承的同时,引入了一种新技术–虚基类(virtual base class),使MI成为可能。
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。
通过在类的声明中使用关键字virtual,可以使Worker被用作Singer和Waiter的虚基类(virtual和public的次序无关紧要):

class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};

然后,可以将SingingWaiter类定义为:

class SingingWaiter : public Singer, public Waiter {...};

ps:在基类不是虚的时候,C++允许信息通过中间类自动传递给基类。
eg:A是基类,B继承A,C继承B,C可以将信息通过构造传递给B,B可以将信息通过构造传递给A。
但是,在基类是虚基类的时候,C++禁止信息通过中间类自动传递给基类。
警告:
1.如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式的调用该虚基类的某个构造函数。
2.多重继承可能导致函数调用的二义性。(可以使用作用域解析运算符来说明或者在多重继承的派生类里重新定义该方法)

总之,在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。另外,如果在编写这些类时没有考虑到MI,则还可能需要重新编写它们。
有关MI的问题:

1.混合使用虚基类和非虚基类
如果基类是虚基类,派生类将包含基类的一个子对象;如果基类不是虚基类,派生类将包含多个子对象。当虚基类和非虚基类混合时(当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象)
2.虚基类和支配
使用虚基类将改变C++解析二义性的方式。使用非虚基类时,规则很简单。如果类从不同的类那里继承了两个或者更多的同名成员(数据或者方法),则使用该成员名时,如果没有用类名进行限定,将导致二义性。
如果使用的是虚基类(即使没有使用作用域限定符),派生类中的名称优先于直接或间接祖先类中相同名称。

44 MI小结(多重继承小结)
不适用虚基类的MI,如果一个类从两个不同的类那里继承了两个同名的成员,则需要在派生类中使用类限定符来区分它们。否则,编译器将指出二义性错误。如果一个类通过多种途径继承了一个非虚基类,则该类从每种途径分别继承非虚基类的一个实例。这样,多个基类实例都是问题。
使用虚基类的MI,当派生类使用关键字virtual来指示派生时,基类就称为虚基类:

class marketing : public virtual reality {...};

对于非虚基类的主要变化(也是使用虚基类的原因)就是,从虚基类的一个或多个实例派生而来的类将只继承了一个基类对象。为实现这种特性,必须满足以下要求:

1.有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的;
2.通过优先规则解决名称二义性。

MI增加编程的复杂性主要是由于派生类通过多条途径继承同一个基类引起的。避免这种情况后,唯一需要注意的就是:在必要时对继承的名称进行限定。

45 类模板
C++的类模板为生成通用的类声明提供了一种更好的方法(C++最初不支持模板,但模板被引入后,就一直在演化)。
模板提供参数化(parameterized)类型,即能够将类型名作为参数传递给接收方来建立类或函数。
45.1 定义类模板
采用模板类,将使用模板定义替换类声明,使用模板成员函数替换类的成员函数。和模板函数一样,模板类以下面的代码开头:

template 

关键字template告诉编译器,将要定义一个模板。尖括号中内容相当于函数的参数列表。可以把关键字class看作是变量的类型名,该变量接受类型作为其值,把Type看作是该变量的名称。
另外,这里使用class并不意味着Type必须是一个类;而只是表明Type是一个通用的类型说明符,在使用模板时,将使用实际的类型替换它。较新的C++实现允许使用不太容易混淆的关键字typename代替class

template 	//newer choice

可以使用自己的泛型名代替Type,其命名规则与其他标识符相同。当前流行的选项包括T和Type.
在模板定义中,可以使用泛型名来标识要存储在栈中的类型。
45.2 使用模板类
仅在程序包含模板并不能生成模板类,而必须请求实例化。为此,需要声明一个类型为模板类的对象,方法是使用所需的具体类型替换泛型名。
eg:

Stack kernels;	//create a stack of ints

泛型标识符–称为类型参数(type parameter),这意味着它们类似于变量,但赋给它们的不能是数字,而只能是类型。
注意,对于模板类,必须显式的提供所需的类型,这与常规的函数模板是不同的,因为编译器可以根据函数的参数类型来确定要生成哪种函数。
45.2 数组模板示例和非类型参数
模板常常用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型。

template 

关键字class(可替换为typename)指出T为类型参数,int指出n的类型为int。这种参数(指定特殊的类型而不是用作泛型名)称为非类型(non-type)或表达式(expression)参数。
eg:

ArrayTP eggweights;

这将导致编译定义名为ArrayTP的类,并创建一个类型为ArrayTP的eggweight对象。定义类时,编译器将使用double替换T,使用12替换n。
注意:表达式参数有部分限制表达式参数可以是整型、枚举、引用或者指针
模板代码不能修改参数的值也不能使用参数的地址
实例化模板时用作表达式参数的值必须式常亮表达式
45.3 模板多功能性
*.可以用于常规类的技术用于模板类。模板类可以用作基类or组件类or其他模板的类型参数。
*.可以递归使用模板。
eg:

ArrayTP ,10> twodee;
等价
int twodee[10][5];

*.模板可以包含多个类型参数。
*.可以为类型参数提供默认值。
eg:

template  class Topo {...};

45.4 模板的具体化
模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明

1.隐式实例化
声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义
2.显式实例化
当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化(explicit instantiation)。声明必须位于模板定义所在的名称空间中。
3.显式具体化
显示具体化(explicit specialization)是特定类型(用于替换模板中的泛型)的定义。在需要为特殊类型实例化时,对模板进行修改,使其行为不同。可以创建显式具体化。
4.部分具体化
C++还允许部分具体化(partial specialization),即部分限制模板的通用性。

45.5 将模板用作参数
模板可以包含类型参数(typenameT)和非类型参数(int n)。模板还可以包含本身就是模板的参数。
eg:

template