华为C++培训资料CHMWord下载.docx
《华为C++培训资料CHMWord下载.docx》由会员分享,可在线阅读,更多相关《华为C++培训资料CHMWord下载.docx(34页珍藏版)》请在冰豆网上搜索。
4、学习要求:
每章标题下面为本章的学习要求,以明确本章要掌握的要点。
文字为楷体、小四。
5、思考题:
每章最后要有思考题,以便帮助学员复习、思考。
6、参考资料和相关网站:
有参考资料和相关网站的要附在课程后面,以便帮助学员查阅。
内部资料,注意保密
C++中级培训教程
员工培训中心编辑
2005年6月V1.0
华为技术有限公司
前言
C++语言中级教材讲授C++语言的运用技术,包括:
类、对象之间的关系、对象的存储与布局、运算符重载、智能指针、仿函数、泛型编程,C++模式设计基本思想。
NE002009cV1.0
1
业务与软件C++语言项目
C++进阶
第一章类、接口…………………………………………………………………7
1.1Handle-Body与接口、抽象接口…………………………………………7
1.2多继承、与菱形缺陷、this跳转等………………………………………13
1.3C++多态的两种多态形式和区别……………………………………………18
第二章重载………………………………………………………………………18
2.1函数重载………………………………………………………………………19
2.2运算符重载…………………………………………………………………20
第三章模板………………………………………………………………………29
3.1模块函数……………………………………………………………………29
3.2模块类……………………………………………………………………………31
3.3STL标准模板库…………………………………………………………………34
附录:
参考资料………………………………………………………………………39
前言
我们在C++基础课程中已经了解了C++的一些基本概念,知道了什么是类什么是对象。
也了解了继承、封装、多态等C++面向对象的基本特征,本课程主要是更进一步探讨一下C++一些基本模型的应用,加深对概念的理解,由于课程时间有限,C++,模型和内容又如此之多,对任何一个模型都无法深入进去,所以只能泛泛而谈。
第一章类、接口
学习要求:
1、了解类的继承、封装等概念之间的关系
2、了解什么是接口,什么是虚函数,它有什么样的特点。
学会使用接口编程的思想
本章节主要介绍C++中的类、接口。
类,包涵了一组数据和一组基于数据上的一组方法。
它描述了一个对象的属性、状态和行为;
接口,它只是描述了一个对象的简单的行为。
有关类的基本概念:
Classnames
Classmembers
MemberFunctions
StaticMemberFunctions
Unions
C++BitFields
NestedClassDeclarations
TypeNamesinClassScope
MultipleBaseClasses
VirtualFunctions
AbstractClasses
ControllingAccesstoClassMembers
privateMembers
protectedMembers
publicMembers
AccessSpecifiersforBaseClasses,priavte,public、protected
Friends
Constructors
Destructors
ConversionFunctions
thenewoperatorandthedeleteoperator
CopyingConstructorFunctions
Interface
1.1Handle-Body与接口、抽象接口
在C++中封装的概念是把一个对象的外观接口同实际工作方式(实现)分离开来,但是C++的封装是不完全的,编译器必须知道一个对象的所有部分的声明,以便创建和管理它。
我们可以想象一种只需声明一个对象的公共接口部分的编程语言,而将私有的实现部分隐藏起来。
C++在编译期间要尽可能多地做静态类型检查。
这意味着尽早捕获错误,也意味着程序具有更高的效率。
然而这对私有的实现部分来说带来两个影响:
一是即使程序员不能轻易地访问实现部分,但他可以看到它;
二是造成一些不必要的重复编译。
然而C++并没有将这个原则应用到二进制层次上,这是因为C++的类既是描述了一个接口同时也描述了实现的过程,示例如下:
classCMyString
{
private:
constintm_cch;
char*m_psz;
public:
CMyString(constchar*psz);
~CMyString();
intLength()const;
intIndex(constchar*psz)const;
}
CMyStirng对外过多的暴露了内存布局实现的细节,这些信息过度的依赖于这些成员变量的大小和顺序,从而导致了客户过度依赖于可执行代码之间的二进制耦合关系,这样的接口不利于跨语言跨平台的软件开发和移植。
1.1.1Handle-Body模式
解决这个问题的技术有时叫句柄类(handleclasses)或叫“CheshireCat”[1]。
有关实现的任何东西都消失了,只剩一个单一的指针“m_pThis”。
该指针指向一个结构,该结构的定义与其所有的成员函数的定义一样出现在实现文件中。
这样,只要接口部分不改变,头文件就不需变动。
而实现部分可以按需要任意更动,完成后只要对实现文件进行重新编译,然后再连接到华为项目中。
这里有这项技术的简单例子。
头文件中只包含公共的接口和一个简单的没有完全指定的类指针。
classCMyStringHandle
classCMyString;
CMyString*m_pThis;
CMyStringHandle(constchar*psz);
~CMyStringHandle();
};
CMyStringHandle:
:
CMyStringHandle(constchar*psz)
:
m_pThis(newCMyString(psz));
~CMyStringHandle()
deletem_pThis;
intCMyStringHandle:
Length()
returnm_pThis->
Length();
Index(constchar*psz)
returnm_pThis->
Index(psz);
这是所有客户程序员都能看到的。
这行
classCMyString;
是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)。
它告诉编译器,cheshire是一个结构的名字,但没有提供有关该结构的任何东西。
这对产生一个指向结构的指针来说已经足够了。
但我们在提供一个结构的主体部分之前不能创建一个对象。
在这种技术里,包含具体实现的结构主体被隐藏在实现文件中。
在设计模式中,这就叫做Handle-Body模式,Handle-Body只含有一个实体指针,服务的数据成员永远被封闭在服务系统中。
Handle-Body模式如下:
Handle-Body模式(句柄类做为接口)
Handle-Body的布局结构永远不会随着实现类数据成员的加入或者删除或者修改而导致Handle-Body的修改,即Handle-Body协议不依赖于C++实现类的任何细节。
这就有效的对用户的编译器隐藏了这些斜街,用户在使用对这项技术时候,Handle-Body接口成了它唯一的入口。
然而Handle-Body模式也有自己的弱点:
1、接口类必须把每一个方法调用显示的传递给实现类,这在一个只有一个构造和一个析构的类来说显然不构成负担,但是如果一个庞大的类库,它有上百上千个方法时候,光是编写这些方法传递就有可能非常冗长,这也增加了出错的可能性。
2、对于关注于性能的应用每一个方法都得有两层的函数调用,嵌套的开销也不理想
3、由于句柄的存在依然存在编译连接器兼容性问题。
接口和实现分离的Handle-Body。
1.1.2抽象接口
使用了“接口与实现的分离”技术的Handle-Body解决了编译器/链接器的大部分问题,而C++面向对象编程中的抽象接口同样是运用了“接口与实现分离”的思想,而采用抽象接口对于解决这类问题是一个极其完美的解决方案。
1、抽象接口的语言描述:
classIMyString
virtualintLength()const=0;
//这表示是一个纯虚函数,具有纯虚函数的接口
virtualintIndex(constchar*psz)const=0;
2、抽象接口的内存结构:
抽象接口的内存布局
3、抽象接口的实现代码:
接口:
classIMyString
{
virtualintLength()const=0;
//这表示是一个纯虚函数,具有纯虚//函数的接口
virtualintIndex(constchar*psz)const=0;
};
实现:
classCMyString:
publicIMyString
virtual~CMyString();
从上面采用抽象接口的实例来看,抽象接口解决了Handle-Body所遗留下来的全部缺陷。
抽象接口的一个典型应用:
抽象工厂(AbstractFactroy)
抽象工厂模式
1.2多继承与菱形缺陷、this跳转等
多重继承是C++语言独有的继承方式,其它几乎所有语言都秉承了单一继承的思想。
这是因为多重继承致命的缺陷导致的:
1.2.1菱形缺陷
当继承基类时,在派生类中就获得了基类所有的数据成员副本。
假如类B从A1和A2两个类多重继承而来,这样B类就包含A1、A2类的数据成员副本。
考虑如果A1、A2都从某基类派生,该基类称为Base,现在继承关系如下:
菱形继承关系
我们C++语言来描述这种继承关系:
classBase{……};
classA1:
publicBase{……};
classA2:
classB:
publicA1,publicA2{……};
那么A1、A2都具有Base的副本。
这样B就包含了Base的两个副本,副本发生了重叠,不但增加了存储空间,同时也引入了二义性。
这就是菱形缺陷,菱形缺陷时间是两个缺陷:
1、子对象重叠
2、向上映射的二义性。
菱形缺陷的其中一种解决办法将
在C++世界里最广泛的使用虚拟继承解决菱形缺陷的应用便是标准C++的输入/输出iostream;
标准C++的输入/输出
1.2.2多重接口与方法名冲突问题(Siamesetwins)
对继承而来的虚函数改写很容易,但是如果是在改写一个“在两个基类都有相同原型”的虚函数情况就不那么容易了。
提出问题:
假设汽车最大速度的接口为ICar,潜艇最大速度的接口为IBoat,有一个两栖类的交通工具它可以奔跑在马路上,也可以航行在大海中,那么它就同时拥有ICar、IBoat两种交通工具的最大速度特性,我们定义它的接口为ICarBoat;
classICar
virtualintGetMaxSpeed()=0;
classIBoat
virtualintGetMaxSpeed()=0;
我们先对ICarBoat的接口做一个尝试:
classCCarBoat
virtualintGetMaxSpeed();
//既完成ICar的GetMaxSpeed()接口方法又//完成IBoat的接口方法?
显然不能够
};
解决问题:
显然上面这个尝试根本就无法成功,只用一个实现方法,怎么能够求出这个ICarBoat交通工具奔跑在马路上的最高时速,同时也能够求出航行在大海上的最大航行速度呢。
上面这一问题矛盾就在一一个方法,却需要两个答案。
看来ICarBoat要返回两个答案就必须有两个方法了,我们假设一个方法是求在陆地上奔跑的速度,名称为GetCarMaxSpeed();
另一个方法是求在大海上航行的最大速度,名称为GetBoatMaxSpeed();
那这两个方法又怎么和GetMaxSpeed()接口方法联系起来呢;
幸运的是,我们找到了解决办法,而且解决办法有很多种,下面介绍一下继承法。
classIXCar:
publicICar
virtualintGetMaxSpeed()
GetCarMaxSpeed();
}
virtualintGetCarMaxSpeed()=0;
classIXBoat:
publicIBoat
GetBoatMaxSpeed();
virtualintGetBoatMaxSpeed()=0;
classCCarBoat:
publicIXCar,publicIXBoat
virtualintGetCarMaxSpeed()
……
virtualintGetBoatMaxSpeed()
多重接口与方法名冲突问题
1.2.3this跳转
this跳转是指的“对象同一性”问题。
在单一继承的世界内,无论继承关系怎么复杂,针对于同一对象,无论它的子类或者父类的this指针永远相等。
即如果有下面的模型:
B从A继承的关系图
那么对于一个已经实例化B类的对象bObject,永远有(B*)&
bObject==(A*)&
bObject成立。
但是在多继承的世界内,上面的等式就不能恒成立,对象的同一性受到了挑战。
特别的是,在多继承世界内如果图四的菱形关系存在情况下,如果对于已经实例化B类的对象bObject;
(Base*)(A1*)&
bObject!
=(Base*)(A2*)&
bObject成立,当这种事情发生的时候我们就只能特殊处理了。
这种情况在COM应用中处处都会发生。
1.3C++多态的两种多态形式和区别
C++有两种多态多态形式:
1、编译时刻多态,编译时刻多态依靠函数重载或者模板实现
2、运行时刻多态。
运行时刻多态依靠需函数虚接口实现
第二章重载
1、了解什么是函数重载,什么是运算符重载
2、学会运用智能指针,仿函数
在C++的世界里,有两种重载:
函数重载和运算符重载,函数重载就采用采用参数匹配的原则,进行重载的,它是一种编译时刻的多态。
而运算符重载,使采用改写或者说重新定义C++的内嵌运算符的方法。
有关重载的基本概念:
OverloadedFunctions
OverloadedOperators
DeclarationMatching
ArgumentMatching
ArgumentTypesMatching
ArgumentCountsMatching
C++UnaryOperators
BinaryOperators
SmartPointer
Functionobjects
1.1函数重载
函数重载方法是在当前范围内选择一个最佳匹配的函数声明供调用该方法者使用。
如果一个适合的函数被找到后,这个函数将会被调用,在这里“适合的”是指按下列顺序匹配的符合下面条件的:
1、一个精确匹配的函数被找到
2、一个参数只有细微的差别,几乎可以忽略不计的。
3、象类似通过子类向父类转化达到参数匹配的
4、通过正常转化函数进行类型转换,能够达到参数匹配到的。
5、通过用户自定义的转化函数(如转化运算符或者构造函数)达到参数匹配的
6、参数是采用省略符号
函数重载的方法基本上有:
1、根据函数参数数据类型的不同进行的重载;
2、根据参数个数的不同进行的重载;
3、缺省参数上的重载
我们在这里把缺省参数也称为一种函数重载,实际上它并不是严格意义上的重载。
在使用缺省参数时必须记住两条规则。
第一,只有参数列表的后部参数才可是缺省的,也就是说,我们不可以在一个缺省参数后面又跟一个非缺省的参数。
第二,一旦我们开始使用缺省参数,那么这个参数后面的所有参数都必须是缺省的。
第三,缺省参数只能放在函数声明中。
第四,缺省参数可以让声明的参数没有标识符。
4、返回值重载
特别注意,在C++中并没有根据返回返回值的不同进行重载的,即我们不能定义这样的函数:
voidf();
intf();
在C++中这样的函数声明方法是被禁止的,但是我们有时间可能又需要这样的重载方法,我们又怎么实现呢,其实很简单,jiang函数的参数进行扩展,将这个函数返回值的数据类型,做为扩展参数的数据类型来。
如下:
voidf(void);
voidf(int);
此时这个例子中的参数列表的数据,只在编译时刻起到分练函数的作用,在运行时刻并不起到传值作用,模板中经常都应用到了这种方法。
1.2运算符重载
你可以重新定义C++绝大多数内嵌运算符的实现方法和功能,这些重定义的或者说重载的运算符,有可能全局作用的,也有可能作用在类基础之上的,运算符重载的实现可能以类的成员函数的形式出现,也有可能以全局性的函数的身份出现。
在C++中重载运算符的名字为operatorx,在这里x是一个可重载的运算符,如:
重载加法运算符,你需要定义一个名为operator+的函数,然后实现他,其它的类似定义就可以了,例如:
Classcomplex//verysimplifiedcomplex
{
doublere,im;
complex(doubler,doublei):
re(r),im(i){};
complexoperator+(complex);
complexoperator*(complex);
定义了complex这个复数的一个简单的实现概念模型。
一个复数是由一对double类型的数据组成,并定义了这个复数的两个方法,加法运算complex:
operartor+()和乘法运算complex:
operator*().现在我们就能够实现下面的复数表达式了:
voidf()
complexa=complex(1,3.1);
complexb=complex(1.2,2);
complexc=b;
a=b+c;
b=b+c*a;
c=a*b+complex(1,2);
1.3.1C++可重载的和C++不可重载的运算符
可重载运算符表:
Operator
Name
Type
Comma
Binary
–>
*
Pointer-to-memberselection
!
LogicalNOT
Unary
/
Division
=
Inequality
/=
Division/assignment
%
Modulus
<
Lessthan
%=
Modulus/assignment
Leftshift
&
BitwiseAND
Leftshift/assignment
Address-of
Lessthanorequalto
LogicalAND
Assignment
BitwiseAND/assign
==
Equality
Bi