• 141.84 KB
  • 2022-04-21 发布

c++基础教程完整版(含目录)

  • 177页
  • 当前文档由用户上传发布,收益归属用户
  1. 1、本文档由用户上传,淘文库整理发布,可阅读全部内容。
  2. 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,请立即联系网站客服。
  3. 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细阅读内容确认后进行付费下载。
  4. 网站客服QQ:403074932
目录C++基础教程简介2第一章C++基础知识(BasicsofC++)31.1C++程序结构(Structureofaprogram)41.2变量和数据类型(VariablesandDatatypes)81.3常量(Constants)171.4操作符/运算符(Operators)21第二章控制结构和函数382.1控制结构(ControlStructures)382.2函数I(FunctionsI)482.3函数II(FunctionsII)532.4参数的默认值(Defaultvaluesinarguments)562.5函数重载(Overloadedfunctions)572.6Inline函数(inlinefunctions)582.7递归(Recursivity)592.8函数的声明(Declaringfunctions)60第三章高级数据类型(AdvancedData)623.1数组(Arrays)623.2字符序列(CharacterSequences)693.3指针(Pointers)763.4动态内存分配(Dynamicmemory)873.5数据结构(DataStructures)923.6自定义数据类型(Userdefineddatatypes)100第四章面向对象编程1044.1类(Classes)1054.2操作符重载(Overloadingoperators)1164.3类之间的关系(Relationshipsbetweenclasses)123177/177n4.4多态(Polymorphism)132第五章C++高级1405.1模板(Templates)1405.2名空间(Namespaces)1495.3出错处理(Exceptionhandling)1545.4类型转换高级(AdvacnedClassType-casting)1585.5预处理指令(PreprocessorDirectives)163第六章C++标准函数库168177/177nC++基础教程Beta版原作:JuanSoulié翻译:JingXu(aqua)英文原版本教程根据JuanSoulie的英文版C++教程翻译并改编。本版为最新校对版,尚未定稿。如有不明或错误之处,请参考英文原版,并敬请在本站留言指正。版权归作者所有,欢迎链接,请勿转载。本教程对C++语言进行了深入浅出的介绍,从基础知识到ANSI-C++标准的最新功能,内容涵盖了从数组,类等基本概念到多态、模板等高级概念。教程本着实用的原则,每一小节都结合了可以工作的程序实例,以便读者从第一课开始就可以上手实习。本翻译版本对许多C++概念中的关键词保留了中英文对照,以便读者增强理解,并方便日后阅读英文原版教材C++基础教程简介怎样使用本教程读者范围本教程面向所有希望学习C++语言的读者。如果读者有其他编程语言背景或计算机相关基本知识可以帮助更好的理解教程内容,但这并非必须条件。对于C语言熟悉的读者可将前三章(1.1到3.4)当作复习,因为这部分内容主要介绍C++中的C部分。不过某些C++的语法与C还是有些差别,所以建议还是快速的读一下这部分。第四章讲述面向对象编程。第五章主要介绍ANSI-C++标准中的新增的功能。本教程结构教程共分6章,每章分若干小节。你可以直接从主目录进入任意小节,并循每页底部的链接向后浏览。很多小节含有一页例题介绍该章节主要知识点的使用。建议在进入下一章学习之前最好先阅读这些例题,理解每行代码。177/177n学习和练习一种编程语言的最好办法是自己修改书中例题程序,设法在程序中增加新的功能。不要不敢修改这些例题程序,这正是学习的方法。兼容性备注ANSI-C++标准近几年来被接受为国际标准。尽管C++语言从二十世纪80年代即存在,ANSI-C++在1997年才被发表,2003年又被修订过。因此很多编译器不支持ANSI-C++中的部分新功能,特别是那些在此标准发表前即被发布的编译器。在本教程中,那些ANSI-C++中新增的而老一代C++编译器大多不支持概念将备用如下标志标出:ANSIC++新增的概念同样对于C和C++在实现上有明显不同的概念,将备用如下标志标出:C与C++不同的地方编译器本教程中所有例题程序均为console程序(控制台程序)。此类程序以文本形式与用户交换信息,显示结果。所有C++编译器均支持console程序的编译。要了解更多关于如何编译的说明,请查询你的编译器用户使用手册。C++编译器和开发环境推荐很多读者询问编译器和开发环境的问题。除了常用的商用收费的MSVisualStudio,VC++,BorlandC++等工具外,还有很多免费的工具也是很好用的。这里推荐两种免费的C++开发软件:1、Eclipse的CDT开发工具,官方网站在http://www.eclipse.org/cdt/2、开源工具Dev-C++和wxDev-C++第一章C++基础知识(BasicsofC++)1.C++程序结构Structureofaprogram177/177n2.变量和数据类型VariablesandDatatypes3.常量Constants4.操作符/运算符Operators5.控制台交互Communicationthroughconsole1.1C++程序结构(Structureofaprogram)下面我们从一个最简单的程序入手看一个C++程序的组成结构。//myfirstprograminC++#includeusingnamespacestd;intmain(){cout<<“HelloWorld!”;return0;}HelloWorld!上面左侧显示了我们的第一个程序的源代码,代码文件名称为hellowworld.cpp。右边显示了程序被编译执行后的输出结果。编辑和编译一个程序的方法取决于你用的是什么编译器,根据它是否有图形化的界面及版本的不同,编译方法也有可能不同,具体请参照你所使用的编译器的使用说明。以上程序是多数初学者学会写的第一个程序,它的运行结果是在屏幕上打出”HelloWorld!”这句话。虽然它可能是C++可写出的最简单的程序之一,但其中已经包含了每一个C++程序的基本组成结构。下面我们就逐个分析其组成结构的每一部分://myfirstprograminC++177/177n这是注释行。所有以两个斜线符号(//)开始的程序行都被认为是注释行,这些注释行是程序员写在程序源代码内,用来对程序作简单解释或描述的,对程序本身的运行不会产生影响。在本例中,这行注释对本程序是什么做了一个简要的描述。#include以#标志开始的句子是预处理器的指示语句。它们不是可执行代码,只是对编译器作出指示。在本例中这个句子#include告诉编译器的预处理器将输入输出流的标准头文件(iostream.h)包括在本程序中。这个头文件包括了C++中定义的基本标准输入-输出程序库的声明。此处它被包括进来是因为在本程序的后面部分中将用到它的功能。usingnamespacestd;C++标准函数库的所有元素都被声明在一个名空间中,这就是std名空间。因此为了能够访问它的功能,我们用这条语句来表达我们将使用标准名空间中定义的元素。这条语句在使用标准函数库的C++程序中频繁出现,本教程中大部分代码例子中也将用到它。intmain()这一行为主函数(mainfunction)的起始声明。mainfunction是所有C++程序的运行的起始点。不管它是在代码的开头,结尾还是中间–此函数中的代码总是在程序开始运行时第一个被执行。并且,由于同样的原因,所有C++程序都必须有一个mainfunction。main后面跟了一对圆括号(),表示它是一个函数。C++中所有函数都跟有一对圆括号(),括号中可以有一些输入参数。如例题中显示,主函数(mainfunction)的内容紧跟在它的声明之后,由花括号({})括起来。cout<<“HellowWorld!”;这个语句在本程序中最重要。cout是C++中的标准输出流(通常为控制台,即屏幕),这句话把一串字符串(本例中为”HelloWorld”)插入输出流(控制台输出)中。cout在的声明在头文件iostream.h中,所以要想使用cout必须将该头文件包括在程序开始处。注意这个句子以分号(;)结尾。分号标示了一个语句的结束,C++的每一个语句都必须以分号结尾。(C++程序员最常犯的错误之一就是忘记在语句末尾写上分号)。return0;177/177n返回语句(return)引起主函数main()执行结束,并将该语句后面所跟代码(在本例中为0)返回。这是在程序执行没有出现任何错误的情况下最常见的程序结束方式。在后面的例子中你会看到所有C++程序都以类似的语句结束。你可能注意到并不是程序中的所有的行都会被执行。程序中可以有注释行(以//开头),有编译器预处理器的指示行(以#开头),然后有函数的声明(本例中main函数),最后是程序语句(例如调用cout<<),最后这些语句行全部被括在主函数的花括号({})内。本例中程序被写在不同的行中以方便阅读。其实这并不是必须的。例如,以下程序intmain(){cout<<"HelloWorld";return0;}也可以被写成:intmain(){cout<<"HelloWorld";return0;}以上两段程序是完全相同的。在C++中,语句的分隔是以分号(;)为分隔符的。分行写代码只是为了更方便人阅读。以下程序包含更多的语句://mysecondprograminC++#includeintmain(){cout<<"HelloWorld!";cout<<"I'maC++program";return0;}HelloWorld!I'maC++program177/177n在这个例子中,我们在两个不同的语句中调用了cout<<函数两次。再一次说明分行写程序代码只是为了我们阅读方便,因为这个main函数也可以被写为以下形式而没有任何问题:intmain(){cout<<"HelloWorld!";cout<<"I'mtoC++program";return0;}为方便起见,我们也可以把代码分为更多的行来写:intmain(){cout<<"HelloWorld!";cout<<"I'maC++program";return0;}它的运行结果将和上面的例子完全一样。这个规则对预处理器指示行(以#号开始的行)并不适用,因为它们并不是真正的语句。它们由预处理器读取并忽略,并不会生成任何代码。因此他们每一个必须单独成行,末尾不需要分号(;)注释(Comments)注释(comments)是源代码的一部分,但它们会被编译器忽略。它们不会生成任何执行代码。使用注释的目的只是使程序员可以在源程序中插入一些说明解释性的内容。C++支持两中插入注释的方法://linecomment/*blockcomment*/第一种方法为行注释,它告诉编译器忽略从//开始至本行结束的任何内容。第二种为块注释(段注释),告诉编译器忽略在/*符号和*/符号之间的所有内容,可能包含多行内容。在以下我们的第二个程序中,我们插入了更多的注释。177/177n/*mysecondprograminC++withmorecomments*/#includeintmain(){cout<<"HelloWorld!";//saysHelloWorld!cout<<"I'maC++program";//saysI'maC++programreturn0;}HelloWorld!I'maC++program如果你在源程序中插入了注释而没有用//符号或/*和*/符号,编译器会把它们当成C++的语句,那么在编译时就会出现一个或多个错误信息。1.2变量和数据类型(VariablesandDatatypes)你可能觉得这个“HellowWorld”程序用处不大。我们写了好几行代码,编译,然后执行生成的程序只是为了在屏幕上看到一句话。的确,我们直接在屏幕上打出这句话会更快。但是编程并不仅限于在屏幕上打出文字这么简单的工作。为了能够进一步写出可以执行更有用的任务的程序,我们需要引入变量(variable)这个的概念。让我们设想这样一个例子,我要求你在脑子里记住5这个数字,然后再记住2这个数字。你已经存储了两个数值在你的记忆里。现在我要求你在我说的第一个数值上加1,你应该保留6(即5+1)和2在你的记忆里。现在如果我们将两数相减可以得到结果4。所有这些你在脑子里做的事情与计算机用两个变量可以做的事情非常相似。同样的处理过程用C++来表示可以写成下面一段代码:a=5;b=2;a=a+1;result=a-b;177/177n很明显这是一个很简单的例子,因为我们只用了两个小的整数数值。但是想一想你的电脑可以同时存储成千上万这样的数值,并进行复杂的数学运算。因此,我们可以将变量(variable)定义为内存的一部分,用以存储一个确定的值。每一个变量(variable)需要一个标识,以便将它与其他变量相区别,例如,在前面的代码中,变量标识是a,b,和result。我们可以给变量起任何名字,只要它们是有效的标识符。标识(Identifiers)有效标识由字母(letter),数字(digits)和下划线(_)组成。标识的长度没有限制,但是有些编译器只取前32个字符(剩下的字符会被忽略)。空格(spaces),标点(punctuationmarks)和符号(symbols)都不可以出现在标识中。只有字母(letters),数字(digits)和下划线(_)是合法的。并且变量标识必须以字母开头。标识也可能以下划线(_)开头,但这种标识通常是保留给为外部连接用的。标识不可以以数字开头。必须注意的另一条规则是当你给变量起名字时不可以和C++语言的关键字或你所使用的编译器的特殊关键字同名,因为这样与这些关键字产生混淆。例如,以下列出标准保留关键字,他们不允许被用作变量标识名称:asm,auto,bool,break,case,catch,char,class,const,const_cast,continue,default,delete,do,double,dynamic_cast,else,enum,explicit,extern,false,float,for,friend,goto,if,inline,int,long,mutable,namespace,new,operator,private,protected,public,register,reinterpret_cast,return,short,signed,sizeof,static,static_cast,struct,switch,template,this,throw,true,try,typedef,typeid,typename,union,unsigned,using,virtual,void,volatile,wchar_t,while另外,不要使用一些操作符的替代表示作为变量标识,因为在某些环境中它们可能被用作保留词:and,and_eq,bitand,bitor,compl,not,not_eq,or,or_eq,xor,xor_eq你的编译器还可能包含一些特殊保留词,例如许多生成16位码的编译器(比如一些DOS编译器)把far,huge和near也作为关键字。非常重要:C++语言是“大小写敏感”(“casesensitive”)的,即同样的名字字母大小写不同代表不同的变量标识。因此,例如变量RESULT,变量result和变量Result分别表示三个不同的变量标识.177/177n基本数据类型(FundamentalDatatypes)编程时我们将变量存储在计算机的内存中,但是计算机要知道我们要用这些变量存储什么样的值,因为一个简单的数值,一个字符,或一个巨大的数值在内存所占用的空间是不一样的。计算机的内存是以字节(byte)为单位组织的。一个字节(byte)是我们在C++中能够操作的最小的内存单位。一个字节(byte)可以存储相对较小数据:一个单个的字符或一个小整数(通常为一个0到255之间的整数)。但是计算机可以同时操作处理由多个字节组成复杂数据类型,比如长整数(longintegers)和小数(decimals)。以下列表总结了现有的C++基本数据类型,以及每一类型所能存储的数据范围:数据类型(DATATYPES)名称字节数*描述范围*char1字符(character)或整数(integer),8位(bits)长有符号(signed):-128到127无符号(unsigned):0到255shortint(short)2短整数(integer)16位(bits)长有符号(signed):-32768到32767无符号(unsigned):0到65535longint(long)4长整数(integer)32位(bits)长有符号(signed):-2147483648到2147483647无符号(unsigned):0到4294967295int4整数(integer)有符号(signed):-2147483648到2147483647无符号(unsigned):0到4294967295float4浮点数(floatingpointnumber)3.4e+/-38(7个数字(7digits))double8双精度浮点数(doubleprecisionfloatingpointnumber)1.7e+/-308(15digits)longdouble8长双精度浮点数(longdoubleprecisionfloatingpointnumber)1.7e+/-308(15digits)177/177nbool1布尔Boolean值。它只能是真(true)或假(false)两值之一。true或falsewchar_t2宽字符(Widecharacter)。这是为存储两字节(2bytes)长的国际字符而设计的类型。一个宽字符(1widecharacters)*字节数一列和范围一列可能根据程序编译和运行的系统不同而有所不同。这里列出的数值是多数32位系统的常用数据。对于其他系统,通常的说法是整型(int)具有根据系统结构建议的自然长度(即一个字oneword的长度),而4中整型数据char,short,int,long的长度必须是递增的,也就是说按顺序每一类型必须大于等于其前面一个类型的长度。同样的规则也适用于浮点数类型float,double和longdouble,也是按递增顺序。除以上列出的基本数据类型外,还有指针(pointer)和void参数表示类型,我们将在后面看到。变量的声明(Declarationofvariables)在C++中要使用一个变量必须先声明(declare)该变量的数据类型。声明一个新变量的语法是写出数据类型标识符(例如int,short,float...)后面跟一个有效的变量标识名称。例如:inta;floatmynumber;以上两个均为有效的变量声明(variabledeclaration)。第一个声明一个标识为a的整型变量(intvariable),第二个声明一个标识为mynumber的浮点型变量(floatvariable)。声明之后,我们就可以在后面的程序中使用变量a和mynumber了。如果你需要声明多个同一类型的变量,你可以将它们缩写在同一行声明中,在标识之间用逗号(comma)分隔。例如:inta,b,c;以上语句同时定义了a、b、c3个整型变量,它与下面的写法完全等同:inta;intb;intc;整型数据类型(char,short,long和int)可以是有符号的(signed)或无符号的(unsigned),这取决于我们需要表示的数据范围。有符号类型(signed)可以表示正177/177n数和负数,而无符号类型(unsigned)只能表示正数和0。在定义一个整型数据变量时可以在数据类型前面加关键字signed或unsigned来声明数据的符号类型。例如:unsignedshortNumberOfSons;signedintMyAccountBalance;如果我们没有特别写出signed或unsigned,变量默认为signed,因此以上第二个声明我们也可以写成:intMyAccountBalance;因为以上两种表示方式意义完全一样,因此我们在源程序通常省略关键字signed。唯一的例外是字符型(char)变量,这种变量独立存在,与signedchar和unsignedchar型均不相同。short和long可以被单独用来表示整型基本数据类型,short相当于shortint,long相当于longint。也就是说shortyear;和shortintyear;两种声明是等价的。最后,signed和unsigned也可以被单独用来表示简单类型,意思分别同signedint和unsignedint相同,即以下两种声明互相等同:unsignedMyBirthYear;unsignedintMyBirthYear;下面我们就用C++代码来解决在这一节开头提到的记忆问题,来看一下变量定义是如何在程序中起作用的。//operatingwithvariables#includeusingnamespacestd;intmain(){//declaringvariables:inta,b;intresult;177/177n//process:a=5;b=2;a=a+1;result=a-b;//printouttheresult:cout<177/177nusingnamespacestd;intmain(){inta=5;//初始值为5intb(2);//初始值为2intresult;//不确定初始值a=a+3;result=a-b;cout<,并且使用usingnamespace语句来使用标准名空间(std),如下面例子所示://C++字符串例题#include#includeusingnamespacestd;intmain(){177/177nstringmystring="Thisisastring";cout<#includeusingnamespacestd;intmain(){stringmystring;mystring="Thisistheinitialstringcontent";cout<usingnamespacestd;intmain(){inta,b;//a:?,b:?a=10;//a:10,b:?b=4;//a:10,b:4a=b;//a:4,b:4b=7;//a:4,b:7cout<<"a:";cout<>=,<<=,&=,^=,|=)177/177nC++以书写简练著称的一大特色就是这些组合运算符compoundassignationoperators(+=,-=,*=和/=及其他),这些运算符使得只用一个基本运算符就可改写变量的值:value+=increase;等同于value=value+increase;a-=5;等同于a=a-5;a/=b;等同于a=a/b;price*=units+1;等同于price=price*(units+1);其他运算符以此类推。例如://组合运算符例子#includeusingnamespacestd;intmain(){inta,b=3;a=b;a+=2;//相当于a=a+2cout<,<,>=,<=)我们用关系运算符来比较两个表达式。如ANSI-C++标准中指出的,关系预算的结果是一个bool值,根据运算结果的不同,它的值只能是真true或false。例如我们想通过比较两个表达式来看它们是否相等或一个值是否比另一个的值大。以下为C++的关系运算符:==相等Equal!=不等Different>大于Greaterthan177/177n<小于Lessthan>=大于等于Greaterorequalthan<=小于等于Lessorequalthan下面你可以看到一些实际的例子:(7==5)将返回false.(5>4)将返回true.(3!=2)将返回true.(6>=6)将返回true.(5<5)将返回false.当然,除了使用数字常量,我们也可以使用任何有效表达式,包括变量。假设有a=2,b=3和c=6,(a==5)将返回false.(a*b>=c)将返回true因为它实际是(2*3>=6)(b+4>a*c)将返回false因为它实际是(3+4>2*6)((b=2)==a)将返回true.注意:运算符=(单个等号)不同于运算符==(双等号)。第一个是赋值运算符(将等号右边的表达式值赋给左边的变量);第二个(==)是一个判断等于的关系运算符,用来判断运算符两边的表达式是否相等。因此在上面例子中最后一个表达式((b=2)==a),我们首先将数值2赋给变量b,然后把它和变量a进行比较。因为变量a中存储的也是数值2,所以整个运算的结果为true。在ANSI-C++标准出现之前的许多编译器中,就像C语言中,关系运算并不返回值为真true或假false的bool值,而是返回一个整型数值最为结果,它的数值可以为0,代表"false"或一个非0数值(通常为1)来代表"true"。177/177n逻辑运算符Logicoperators(!,&&,||)运算符!等同于boolean运算NOT(取非),它只有一个操作数(operand),写在它的右边。它做的唯一工作就是取该操作数的反面值,也就是说如果操作数值为真true,那么运算后值变为假false,如果操作数值为假false,则运算结果为真true。它就好像是说取与操作数相反的值。例如:!(5==5)返回false,因为它右边的表达式(5==5)为真true.!(6<=4)返回true因为(6<=4)为假false.!true返回假false.!false返回真true.逻辑运算符&&和||是用来计算两个表达式而获得一个结果值。它们分别对应逻辑运算中的与运算AND和或运算OR。它们的运算结果取决于两个操作数(operand)的关系:第一个操作数a第二个操作数b结果a&&b结果a||btruetruetruetruetruefalsefalsetruefalsetruefalsetruefalsefalsefalsefalse例如:((5==5)&&(3>6))返回false(true&&false).((5==5)||(3>6))返回true(true||false).条件运算符Conditionaloperator(?)条件运算符计算一个表达式的值并根据表达式的计算结果为真true或假false而返回不同值。它的格式是:177/177ncondition?result1:result2(条件?返回值1:返回值2)如果条件condition为真true,整个表达式将返回esult1,否则将返回result2。7==5?4:3返回3,因为7不等于5.7==5+2?4:3返回4,因为7等于5+2.5>3?a:b返回a,因为5大于3.a>b?a:b返回较大值,a或b.//条件运算符例子#includeusingnamespacestd;intmain(){inta,b,c;a=2;b=7;c=(a>b)?a:b;cout<b)运算值为假(false),所以整个表达式(a>b)?a:b要取分号后面的值,也就是b的值7。因此最后输出c的值为7。逗号运算符(,)177/177n逗号运算符(,)用来分开多个表达式,并只取最右边的表达式的值返回。例如有以下代码:a=(b=3,b+2);这行代码首先将3赋值给变量b,然后将b+2赋值给变量a。所以最后变量a的值为5,而变量b的值为3。位运算符BitwiseOperators(&,|,^,~,<<,>>)位运算符以比特位改写变量存储的数值,也就是改写变量值的二进制表示:opasmDescription&AND逻辑与LogicAND|OR逻辑或LogicOR^XOR逻辑异或LogicalexclusiveOR~NOT对1取补(位反转)Complementtoone(bitinversion)<>SHR右移ShiftRight变量类型转换运算符Explicittypecastingoperators变量类型转换运算符可以将一种类型的数据转换为另一种类型的数据。在写C++中有几种方法可以实现这种操作,最常用的一种,也是与C兼容的一种,是在原转换的表达式前面加用括号()括起的新数据类型:inti;floatf=3.14;i=(int)f;以上代码将浮点型数字3.14转换成一个整数值(3)。这里类型转换操作符为(int)。在C++中实现这一操作的另一种方法是使用构造函数constructor的形式:在要转换的表达式前加变量类型并将表达式括在括号中:177/177ni=int(f);以上两种类型转换的方法在C++中都是合法的。另外ANSI-C++针对面向对象编程(objectorientedprogramming)增加了新的类型转换操作符(参考Section5.4,Advancedclasstype-casting).sizeof()这个运算符接受一个输入参数,该参数可以是一个变量类型或一个变量自己,返回该变量类型(variabletype)或对象(object)所占的字节数:a=sizeof(char);这将会返回1给a,因为char是一个常为1个字节的变量类型。sizeof返回的值是一个常数,因此它总是在程序执行前就被固定了。其它运算符在本教程后面的章节里我们将看到更多的运算符,比如指向指针的运算或面向对象编程特有的运算,等等,我们会在它们各自的章节里进行详细讨论。运算符的优先度Precedenceofoperators当多个操作数组成复杂的表达式时,我们可能会疑惑哪个运算先被计算,哪个后被计算。例如以下表达式:a=5+7%2我们可以怀疑它实际上表示:a=5+(7%2)结果为6,还是a=(5+7)%2结果为0?正确答案为第一个,结果为6。每一个运算符有一个固定的优先级,不仅对数学运算符(我们可能在学习数学的时候已经很了解它们的优先顺序了),所有在C++中出现的运算符都有优先级。从最从最高级到最低级,运算的优先级按下表排列:优先级Level操作符Operator说明177/177nDescription结合方向Grouping1::范围从左到右2()[].->++--dynamic_caststatic_castreinterpret_castconst_casttypeid后缀从左到右3++--~!sizeofnewdelete一元(前缀)从右到左*&指针和取地址+-一元符号4(type)类型转换从右到左5.*->*指向成员的指针从左到右6*/%乘、除、取模从左到右7+-加减从左到右8<<>>位移从左到右9<><=>=关系操作符从左到右10==!=等于、不等于从左到右11&按位与运算从左到右12^按位异或运算从左到右13|按位或运算从左到右14&&逻辑与运算从左到右15||逻辑或运算从左到右16?:条件运算从右到左17=*=/=%=+=-=>>=<<=&=^=|=赋值运算从右到左18,逗号从左到右177/177n结合方向Grouping定义了当有同优先级的多个运算符在一起时,哪一个必须被首先运算,最右边的还是最左边的。所有这些运算符的优先级顺序可以通过使用括号parenthesissigns(和)来控制,而且更易读懂,例如以下例子:a=5+7%2;根据我们想要实现的计算的不同,可以写成:a=5+(7%2);或者a=(5+7)%2;所以如果你想写一个复杂的表达式而不敢肯定各个运算的执行顺序,那么就加上括号。这样还可以使代码更易读懂。1.5控制台交互(Communicationthroughconsole)控制台(console)是电脑的最基本交互接口,通常包括键盘(keyboard)和屏幕(screen)。键盘通常为标准输入设备,而屏幕为标准输出设备。在C++的iostream函数库中,一个程序的标准输入输出操作依靠两种数据流:cin给输入使用和cout给输出使用。另外,cerr和clog也已经被实现――它们是两种特殊设计的数据流专门用来显示出错信息。它们可以被重新定向到标准输出设备或到一个日志文件(logfile)。因此cout(标准输出流)通常被定向到屏幕,而cin(标准输入流)通常被定向到键盘。通过控制这两种数据流,你可以在程序中与用户交互,因为你可以在屏幕上显示输出并从键盘接收用户的输入。输出Output(cout)输出流cout与重载(overloaded)运算符<<一起使用:cout<<"Outputsentence";//打印Outputsentence到屏幕上cout<<120;//打印数字120到屏幕上cout<>)来实现的。它后面必须跟一个变量以便存储读入的数据。例如:intage;cin>>age;声明一个整型变量age然后等待用户从键盘输入到cin并将输入值存储在这个变量中。cin只能在键盘输入回车键(RETURN)后才能处理前面输入的内容。因此即使你只要求输入一个单独的字符,在用户按下回车键(RETURN)之前cin将不会处理用户的输入的字符。在使用cin输入的时候必须考虑后面的变量类型。如果你要求输入一个整数,extraction(>>)后面必须跟一个整型变量,如果要求一个字符,后面必须跟一个字符型变量,如果要求一个字符串,后面必须跟一个字符串型变量。//i/oexample#includeintmain(){inti;cout<<"Pleaseenteranintegervalue:";cin>>i;cout<<"Thevalueyouenteredis"<>a>>b;等同于:cin>>a;cin>>b;在以上两种情况下用户都必须输入两个数据,一个给变量a,一个给变量b。输入时两个变量之间可以以任何有效的空白符号间隔,包括空格,跳跃符tab或换行。cin和字符串我们可以像读取基本类型数据一样,使用cin和>>操作符来读取字符串,例如:cin>>mystring;但是,cin>>只能读取一个单词,一旦碰到任何空格,读取操作就会停止。在很多时候这并不是我们想要的操作,比如我们希望用户输入一个英文句子,那么这种方法就无法读取完整的句子,因为一定会遇到空格。要一次读取一整行输入,需要使用C++的函数getline,相对于是用cin,我们更建议使用getline来读取用户输入。例如://读取字符串例子#include#include177/177nusingnamespacestd;intmain(){stringmystr;cout<<"What'syourname?";getline(cin,mystr);cout<<"Hello"<定义了一个叫做 stringstream的类,使用这个类可以对基于字符串的对象进行像流(stream)一样的操作。这样,我们可以对字符串进行抽取和插入操作,这对将字符串与数值互相转换非常有用。例如,如果我们想将一个字符串转换为一个整数,可以这样写:stringmystr("1204");intmyint;stringstream(mystr)>>myint;这个例子中先定义了一个字符串类型的对象mystr,初始值为"1204",又定义了一个整数变量myint。然后我们使用stringstream类的构造函数定义了这个类的对象,并以字符串变量mystr为参数。因为我们可以像使用流一样使用stringstream的对象,所177/177n以我们可以像使用cin那样使用操作符>>后面跟一个整数变量来进行提取整数数据。这段代码执行之后变量myint存储的是数值1204。//字符串流的使用示例#include#include#includeusingnamespacestd;intmain(){stringmystr;floatprice=0;intquantity=0;cout<<"Enterprice:";getline(cin,mystr);stringstream(mystr)>>price;cout<<"Enterquantity:";getline(cin,mystr);stringstream(mystr)>>quantity;cout<<"Totalprice:"<0)cout<<"xispositive";elseif(x<0)177/177ncout<<"xisnegative";elsecout<<"xis0";记住当我们需要执行多条语句时,必须使用花括号{}将它们括起来以组成一个语句块blockofinstructions。重复结构Iterationstructures或循环loops循环Loops的目的是重复执行一组语句一定的次数或直到满足某种条件。while循环格式是:while(表达式expression)语句statement它的功能是当expression的值为真true时重复执行statement。例如,下面我们将用while循环来写一个倒计数程序://customcountdownusingwhile#includeintmain(){intn;cout<<"Enterthestartingnumber>";cin>>n;while(n>0){cout<88,7,6,5,4,3,2,1,FIRE!177/177n程序开始时提示用户输入一个倒计数的初始值。然后while循环开始,如果用户输入的数值满足条件n>0(即n比0大),后面跟的语句块将会被执行一定的次数,直到条件(n>0)不再满足(变为false)。以上程序的所有处理过程可以用以下的描述来解释:从main开始:1.用户输入一个数值赋给n.2.while语句检查(n>0)是否成立,这时有两种可能:otrue:执行statement(到第3步)ofalse:跳过statement.程序直接执行第5步.3.执行statement:cout<intmain(){unsignedlongn;do{cout<<"Enternumber(0toend):";cin>>n;cout<<"Youentered:"<intmain(){for(intn=10;n>0;n--){cout<intmain(){intn;for(n=10;n>0;n--){cout<intmain(){177/177nfor(intn=10;n>0;n--){if(n==5)continue;cout<intmain(){intn=10;loop:cout<0)gotoloop;cout<<"FIRE!";return0;}10,9,8,7,6,5,4,3,2,1,FIRE!exit函数exit是一个在cstdlib(stdlib.h)库中定义的函数。exit的目的是一个特定的退出代码来结束程序的运行,它的原型(prototype)是:177/177nvoidexit(intexitcode);exitcode是由操作系统使用或被调用程序使用。通常exitcode为0表示程序正常结束,任何其他值表示程序执行过程中出现了错误。选择结构TheselectiveStructure:switchswitch语句的语法比较特殊。它的目标是对一个表达式检查多个可能常量值,有些像我们在本节开头学习的把几个if和elseif语句连接起来的结构。它的形式是:switch(expression){caseconstant1:blockofinstructions1break;caseconstant2:blockofinstructions2break;...default:defaultblockofinstructions}它按以下方式执行:switch计算表达式expression的值,并检查它是否与第一个常量constant1相等,如果相等,程序执行常量1后面的语句块blockofinstructions1直到碰到关键字break,程序跳转到switch选择结构的结尾处。如果expression不等于constant1,程序检查表达式expression的值是否等于第二个常量constant2,如果相等,程序将执行常量2后面的语句块blockofinstructions2直到碰到关键字break。依此类推,直到最后如果表达式expression的值不等于任何前面的常量(你可以用case语句指明任意数量的常量值来要求检查),程序将执行默认区default:后面的语句,如果它存在的话。default:选项是可以省略的。177/177n下面的两段代码段功能相同:switchexampleif-elseequivalentswitch(x){case1:cout<<"xis1";break;case2:cout<<"xis2";break;default:cout<<"valueofxunknown";}if(x==1){cout<<"xis1";}elseif(x==2){cout<<"xis2";}else{cout<<"valueofxunknown";}前面已经提到switch的语法有点特殊。注意每个语句块结尾包含的break语句。这是必须的,因为如果不这样做,例如在语句块blockofinstructions1的结尾没有break,程序执行将不会跳转到switch选择的结尾处(}),而是继续执行下面的语句块,直到第一次遇到break语句或到switch选择结构的结尾。因此,不需要在每一个case域内加花括号{}。这个特点同时可以帮助实现对不同的可能值执行相同的语句块。例如:switch(x){case1:case2:case3:cout<<"xis1,2or3";177/177nbreak;default:cout<<"xisnot1,2nor3";}注意switch只能被用来比较表达式和不同常量的值constants。因此我们不能够把变量或范围放在case之后,例如(case(n*2):)或(case(1..3):)都不可以,因为它们不是有效的常量。如果你需要检查范围或非常量数值,使用连续的if和elseif语句。2.2函数I(FunctionsI)通过使用函数(functions)我们可以把我们的程序以更模块化的形式组织起来,从而利用C++所能提供的所有结构化编程的潜力。一个函数(function)是一个可以从程序其它地方调用执行的语句块。以下是它的格式:typename(argument1,argument2,...)statement这里:?type是函数返回的数据的类型?name是函数被调用时使用的名?argument是函数调用需要传入的参量(可以声明任意多个参量)。每个参量(argument)由一个数据类型后面跟一个标识名称组成,就像变量声明中一样(例如,intx)。参量仅在函数范围内有效,可以和函数中的其它变量一样使用,它们使得函数在被调用时可以传入参数,不同的参数用逗号(comma)隔开.?statement是函数的内容。它可以是一句指令,也可以是一组指令组成的语句块。如果是一组指令,则语句块必须用花括号{}括起来,这也是我们最常见到情况。其实为了使程序的格式更加统一清晰,建议在仅有一条指令的时候也使用花括号,这是一个良好的编程习惯。下面看一下第一个函数的例子://functionexample#include177/177nintaddition(inta,intb){intr;r=a+b;return(r);}intmain(){intz;z=addition(5,3);cout<<"Theresultis"<intsubtraction(inta,intb){intr;r=a-b;return(r);}intmain()177/177n{intx=5,y=3,z;z=subtraction(7,2);cout<<"Thefirstresultis"<usingnamespacestd;177/177nvoidprintmessage(){cout<<"I'mafunction!";}intmain(){printmessage();return0;}I'mafunction!void还可以被用在函数参数位置,表示我们明确希望这个函数在被调用时不需要任何参数。例如上面的函数printmessage也可以写为以下形式:voidprintmessage(void){cout<<"I'mafunction!";}虽然在C++中void可以被省略,我们还是建议写出void,以便明确指出函数不需要参数。你必须时刻知道的是调用一个函数时要写出它的名字并把参数写在后面的括号内。但如果函数不需要参数,后面的括号并不能省略。因此调用函数printmessage的格式是printmessage();函数名称后面的括号就明确表示了它是一个函数调用,而不是一个变量名称或其它什么语句。以下调用函数的方式就不对:printmessage;2.3函数II(FunctionsII)参数按数值传递和按地址传递(Argumentspassedbyvalueandbyreference)177/177n到目前为止,我们看到的所有函数中,传递到函数中的参数全部是按数值传递的(byvalue)。也就是说,当我们调用一个带有参数的函数时,我们传递到函数中的是变量的数值而不是变量本身。例如,假设我们用下面的代码调用我们的第一个函数addition:intx=5,y=3,z;z=addition(x,y);在这个例子里我们调用函数addition同时将x和y的值传给它,即分别为5和3,而不是两个变量:这样,当函数addition被调用时,它的变量a和b的值分别变为5和3,但在函数addition内对变量a或b所做的任何修改不会影响变量他外面的变量x和y的值,因为变量x和y并没有把它们自己传递给函数,而只是传递了他们的数值。但在某些情况下你可能需要在一个函数内控制一个函数以外的变量。要实现这种操作,我们必须使用按地址传递的参数(argumentspassedbyreference),就象下面例子中的函数duplicate://passingparametersbyreference#includevoidduplicate(int&a,int&b,int&c){a*=2;b*=2;c*=2;}intmain(){intx=1,y=3,z=7;duplicate(x,y,z);cout<<"x="<。这就是为什么上面的程序中,主程序main中的三个变量x,y和z在调用函数duplicate后打印结果显示他们的值增加了一倍。如果在声明下面的函数:voidduplicate(int&a,int&b,int&c)时,我们是按这样声明的:voidduplicate(inta,intb,intc)也就是不写地址符ampersand(&),我们也就没有将参数的地址传递给函数,而是传递了它们的值,因此,屏幕上显示的输出结果x,y,z的值将不会改变,仍是1,3,7。这种用地址符ampersand(&)来声明按地址"byreference"传递参数的方式只是在C++中适用。在C语言中,我们必须用指针(pointers)来做相同的操作。按地址传递(Passingbyreference)是一个使函数返回多个值的有效方法。例如,下面是一个函数,它可以返回第一个输入参数的前一个和后一个数值。//morethanonereturningvalue#includevoidprevnext(intx,int&prev,int&next){prev=x-1;next=x+1;177/177n}intmain(){intx=100,y,z;prevnext(x,y,z);cout<<"Previous="<intdivide(inta,intb=2){intr;r=a/b;return(r);}intmain(){cout<intdivide(inta,intb){return(a/b);}floatdivide(floata,floatb){return(a/b);}intmain(){177/177nintx=5,y=2;floatn=5.0,m=2.0;cout<longfactorial(longa){if(a>1)return(a*factorial(a-1));elsereturn(1);}intmain(){longl;cout<<"Typeanumber:";cin>>l;cout<<"!"<voidodd(inta);voideven(inta);intmain(){inti;do{cout<<"Typeanumber:(0toexit)";177/177ncin>>i;odd(i);}while(i!=0);return0;}voidodd(inta){if((a%2)!=0)cout<<"Numberisodd.n";elseeven(a);}voideven(inta){if((a%2)==0)cout<<"Numberiseven.n";elseodd(a);}Typeanumber(0toexit):9Numberisodd.Typeanumber(0toexit):6Numberiseven.Typeanumber(0toexit):1030Numberiseven.Typeanumber(0toexit):0Numberiseven.这个例子的确不是很有效率,我相信现在你已经可以只用一半行数的代码来完成同样的功能。但这个例子显示了函数原型(prototypingfunctions)是怎样工作的。并且在这个具体的例子中,两个函数中至少有一个是必须定义原型的。这里我们首先看到的是函数odd和even的原型:voidodd(inta);voideven(inta);这样使得这两个函数可以在它们被完整定义之前就被使用,例如在main中被调用,这样main就可以被放在逻辑上更合理的位置:即程序代码的开头部分。177/177n尽管如此,这个程序需要至少一个函数原型定义的特殊原因是因为在odd函数里需要调用even函数,而在even函数里也同样需要调用odd函数。如果两个函数任何一个都没被提前定义原型的话,就会出现编译错误,因为或者odd在even函数中是不可见的(因为它还没有被定义),或者even函数在odd函数中是不可见的。很多程序员建议给所有的函数定义原型。这也是我的建议,特别是在有很多函数或函数很长的情况下。把所有函数的原型定义放在一个地方,可以使我们在决定怎样调用这些函数的时候轻松一些,同时也有助于生成头文件。第三章高级数据类型(AdvancedData)1.数组Arrays2.字符序列CharactersSequences3.指针Pointers4.动态内存分配Dynamicmemory5.数据结构DataStructures6.自定义数据类型Userdefineddatatypes3.1数组(Arrays)数组(Arrays)是在内存中连续存储的一组同种数据类型的元素(变量),每一数组有一个唯一名称,通过在名称后面加索引(index)的方式可以引用它的每一个元素。177/177n也就是说,例如我们有5个整型数值需要存储,但我们不需要定义5个不同的变量名称,而是用一个数组(array)来存储这5个不同的数值。注意数组中的元素必须是同一数据类型的,在这个例子中为整型(int)。例如一个存储5个整数叫做billy的数组可以用下图来表示:这里每一个空白框代表数组的一个元素,在这个例子中为一个整数值。白框上面的数字0到4代表元素的索引(index)。注意无论数组的长度如何,它的第一个元素的索引总是从0开始的。同其它的变量一样,数组必须先被声明然后才能被使用。一种典型的数组声明显示如下:typename[elements];这里type是可以使任何一种有效的对象数据类型(objecttype),如int,float...等,name是一个有效地变量标识(identifier),而由中括号[]引起来的elements域指明数组的大小,即可以存储多少个元素。因此,要定义上面图中显示的billy数组,用一下语句就可以了:intbilly[5];备注:在定义一个数组的时候,中括号[]中的elements域必须是一个常量数值,因为数组是内存中一块有固定大小的静态空间,编译器必须在编译所有相关指令之前先能够确定要给该数组分配多少内存空间。初始化数组(Initializingarrays)当声明一个本地范围内(在一个函数内)的数组时,除非我们特别指定,否则数组将不会被初始化,因此它的内容在我们将数值存储进去之前是不定的。如果我们声明一个全局数组(在所有函数之外),则它的内容将被初始化为所有元素均为0。因此,如果全局范围内我们声明:intbilly[5];那么billy中的每一个元素将会被初始化为0:177/177n另外,我们还可以在声明一个变量的同时把初始值付给数组中的每一个元素,这个赋值用花括号{}来完成。例如:intbilly[5]={16,2,77,40,12071};这个声明将生成如下数组:花括号中我们要初始化的元素数值个数必须和数组声明时方括号[]中指定的数组长度相符。例如,在上面例子中数组billy声明中的长度为5,因此在后面花括号中的初始值也有5个,每个元素一个数值。因为这是一种信息的重复,因此C++允许在这种情况下数组[]中为空白,而数组的长度将有后面花括号{}中数值的个数来决定,如下例所示。intbilly[]={16,2,77,40,12071};存取数组中数值(AccesstothevaluesofanArray)在程序中我们可以读取和修改数组任一元素的数值,就像操作其他普通变量一样。格式如下:name[index]继续上面的例子,数组billy有5个元素,其中每一元素都是整型int,我们引用其中每一个元素的名字分别为如下所示:例如,要把数值75存入数组billy中第3个元素的语句可以是:billy[2]=75;又例如,要把数组billy中第3个元素的值赋给变量a,我们可以这样写:a=billy[2];因此,在所有使用中,表达式billy[2]就像任何其他整型变量一样。注意数组billy的第3个元素为billy[2],因为索引(index)从0开始,第1个元素是billy[0],第2个元素是billy[1],因此第3个是billy[2]。同样的原因,最后一个元素是billy[4]。如果我们写billy[5],那么是在使用billy的第6个元素,因此会超出数组的长度。177/177n在C++中对数组使用超出范围的index是合法的,这就会产生问题,因为它不会产生编译错误而不易被察觉,但是在运行时会产生意想不到的结果,甚至导致严重运行错误。超出范围的index之所以合法的原因我们在后面学习指针(pointer)的时候会了解。学到这里,我们必须能够清楚的了解方括号[]在对数组操作中的两种不同用法。它们完成两种任务:一种是在声明数组的时候定义数组的长度;另一种是在引用具体的数组元素的时候指明一个索引号(index)。我们要注意不要把这两种用法混淆。intbilly[5];//声明新数组(以数据类型名称开头)billy[2]=75;//存储数组的一个元素其它合法的数组操作:billy[0]=a;//a为一个整型变量billy[a]=75;b=billy[a+2];billy[billy[a]]=billy[2]+5;//arraysexample#includeintbilly[]={16,2,77,40,12071};intn,result=0;intmain(){for(n=0;n<5;n++){result+=billy[n];}cout<#defineWIDTH5#defineHEIGHT3intjimmy[HEIGHT][WIDTH];177/177nintn,m;intmain(){for(n=0;n#defineWIDTH5#defineHEIGHT3intjimmy[HEIGHT*WIDTH];intn,m;intmain(){for(n=0;nvoidprintarray(intarg[],intlength){for(intn=0;n#includeintmain(){charszMyName[20];strcpy(szMyName,"J.Soulie");cout<voidsetstring(charszOut[],charszIn[]){intn=0;do{szOut[n]=szIn[n];}while(szIn[n++]!='');}intmain(){177/177ncharszMyName[20];setstring(szMyName,"J.Soulie");cout<intmain(){charmybuffer[100];cout<<"What'syourname?";cin.getline(mybuffer,100);cout<<"Hello"<>)来直接从标准输入设备接收数据。这个方法也同样可以被用来输入字符串,例如,在上面的例子中我们也可以用以下代码来读取用户输入:cin>>mybuffer;这种方法也可以工作,但它有以下局限性是cin.getline所没有的:?它只能接收单独的词(而不能是完整的句子),因为这种方法以任何空白符为分隔符,包括空格spaces,跳跃符tabulators,换行符newlines和回车符arriagereturns。?它不能给buffer指定容量,这使得程序不稳定,如果用户输入超出数组长度,输入信息会被丢失。因此,建议在需要用cin来输入字符串时,使用cin.getline来代替cin>>。字符串和其它数据类型的转换(Convertingstringstoothertypes)鉴于字符串可能包含其他数据类型的内容,例如数字,将字符串内容转换成数字型变量的功能会有用处。例如一个字符串的内容可能是"1977",但这一个5个字符组成序列,并不容易转换为一个单独的整数。因此,函数库cstdlib(stdlib.h)提供了3个有用的函数:?atoi:将字符串string转换为整型int?atol:将字符串string转换为长整型long?atof:将字符串string转换为浮点型float所有这些函数接受一个参数,返回一个指定类型的数据(int,long或float)。这三个函数与cin.getline一起使用来获得用户输入的数值,比传统的cin>>方法更可靠://cinandato*functions#include#include177/177nintmain(){charmybuffer[100];floatprice;intquantity;cout<<"Enterprice:";cin.getline(mybuffer,100);price=atof(mybuffer);cout<<"Enterquantity:";cin.getline(mybuffer,100);quantity=atoi(mybuffer);cout<<"Totalprice:"<intmain(){intvalue1=5,value2=15;int*mypointer;mypointer=&value1;*mypointer=10;mypointer=&value2;*mypointer=20;cout<<"value1=="<intmain(){intvalue1=5,value2=15;int*p1,*p2;p1=&value1;//p1=addressofvalue1p2=&value2;//p2=addressofvalue2177/177n*p1=10;//valuepointedbyp1=10*p2=*p1;//valuepointedbyp2=valuepointedbyp1p1=p2;//p1=p2(valueofpointercopied)*p1=20;//valuepointedbyp1=20cout<<"value1=="<intmain(){intnumbers[5];int*p;p=numbers;*p=10;p++;*p=20;p=&numbers[2];*p=30;p=numbers+3;*p=40;p=numbers;*(p+4)=50;for(intn=0;n<5;n++)cout<voidincrease(void*data,inttype){switch(type){casesizeof(char):(*((char*)data))++;break;casesizeof(short):(*((short*)data))++;break;casesizeof(long):(*((long*)data))++;break;}}intmain(){chara=5;shortb=9;longc=12;increase(&a,sizeof(a));increase(&b,sizeof(b));increase(&c,sizeof(c));cout<<(int)a<<","<intaddition(inta,intb){return(a+b);}intsubtraction(inta,intb){return(a-b);}int(*minus)(int,int)=subtraction;intoperation(intx,inty,int(*functocall)(int,int)){intg;g=(*functocall)(x,y);return(g);}intmain(){intm,n;m=operation(7,5,addition);177/177nn=operation(20,m,minus);cout<title,50);cout<<"Enteryear:";cin.getline(buffer,50);pmovie->year=atoi(buffer);cout<<"nYouhaveentered:n";cout<title;cout<<"("<year<<")n";return0;}Entertitle:MatrixEnteryear:1999Youhaveentered:Matrix(1999)上面的代码中引入了一个重要的操作符:->。这是一个引用操作符,常与结构或类的指针一起使用,以便引用其中的成员元素,这样就避免使用很多括号。例如,我们用:pmovie->title来代替:(*pmovie).title以上两种表达式pmovie->title和(*pmovie).title都是合法的,都表示取指针pmovie所指向的结构其元素title的值。我们要清楚将它和以下表达区分开:177/177n*pmovie.title它相当于*(pmovie.title)表示取结构pmovie的元素title作为指针所指向的值,这个表达式在本例中没有意义,因为title本身不是指针类型。下表中总结了指针和结构组成的各种可能的组合:表达式描述等价于pmovie.title结构pmovie的元素titlepmovie->title指针pmovie所指向的结构其元素title的值(*pmovie).title*pmovie.title结构pmovie的元素title作为指针所指向的值*(pmovie.title)结构嵌套(Nestingstructures)结构可以嵌套使用,即一个结构的元素本身又可以是另一个结构类型。例如:structmovies_t{chartitle[50];intyear;}structfriends_t{charname[50];charemail[50];movies_tfavourite_movie;}charlie,maria;friends_t*pfriends=&charlie;因此,在有以上声明之后,我们可以使用下面的表达式:charlie.name177/177nmaria.favourite_movie.titlecharlie.favourite_movie.yearpfriends->favourite_movie.year(以上最后两个表达式等价)本节中所讨论的结构的概念与C语言中结构概念是一样的。然而,在C++中,结构的概念已经被扩展到与类(class)相同的程度,只是它所有的元素都是公开的(public)。在后面的章节4.1“类”中,我们将进一步深入讨论这个问题。3.6自定义数据类型(Userdefineddatatypes)前面我们已经看到过一种用户(程序员)定义的数据类型:结构。除此之外,还有一些其它类型的用户自定义数据类型:定义自己的数据类型(typedef)C++允许我们在现有数据类型的基础上定义我们自己的数据类型。我们将用关键字typedef来实现这种定义,它的形式是:typedefexisting_typenew_type_name;这里existing_type是C++基本数据类型或其它已经被定义了的数据类型,new_type_name是我们将要定义的新数据类型的名称。例如:typedefcharC;typedefunsignedintWORD;typedefchar*string_t;typedefcharfield[50];在上面的例子中,我们定义了四种新的数据类型:C,WORD,string_t和field,它们分别代替char,unsignedint,char*和char[50]。这样,我们就可以安全的使用以下代码:Cachar,anotherchar,*ptchar1;WORDmyword;string_tptchar2;177/177nfieldname;如果在一个程序中我们反复使用一种数据类型,而在以后的版本中我们有可能改变该数据类型的情况下,typedef就很有用了。或者如果一种数据类型的名称太长,你想用一个比较短的名字来代替,也可以是用typedef。联合(Union)联合(Union)使得同一段内存可以被按照不同的数据类型来访问,数据实际是存储在同一个位置的。它的声明和使用看起来与结构(structure)十分相似,但实际功能是完全不同的:unionmodel_name{type1element1;type2element2;type3element3;..}object_name;union中的所有被声明的元素占据同一段内存空间,其大小取声明中最长的元素的大小。例如:unionmytypes_t{charc;inti;floatf;}mytypes;定义了3个元素:mytypes.cmytypes.imytypes.f每一个是一种不同的数据类型。既然它们都指向同一段内存空间,改变其中一个元素的值,将会影响所有其他元素的值。177/177nunion的用途之一是将一种较长的基本类型与由其它比较小的数据类型组成的结构(structure)或数组(array)联合使用,例如:unionmix_t{longl;struct{shorthi;shortlo;}s;charc[4];}mix;以上例子中定义了3个名称:mix.l,mix.s和mix.c,我们可以通过这3个名字来访问同一段4bytes长的内存空间。至于使用哪一个名字来访问,取决于我们想使用什么数据类型,是long,short还是char。下图显示了在这个联合(union)中各个元素在内存中的的可能结构,以及我们如何通过不同的数据类型进行访问:匿名联合(Anonymousunion)在C++我们可以选择使联合(union)匿名。如果我们将一个union包括在一个结构(structure)的定义中,并且不赋予它object名称(就是跟在花括号{}后面的名字),这个union就是匿名的。这种情况下我们可以直接使用union中元素的名字来访问该元素,而不需要再在前面加union对象的名称。在下面的例子中,我们可以看到这两种表达方式在使用上的区别:unionanonymousunionstruct{chartitle[50];charauthor[50];union{floatdollars;intyens;}price;}book;struct{177/177nchartitle[50];charauthor[50];union{floatdollars;intyens;};}book;以上两种定义的唯一区别在于左边的定义中我们给了union一个名字price,而在右边的定义中我们没给。在使用时的区别是当我们想访问一个对象(object)的元素dollars和yens时,在前一种定义的情况下,需要使用:book.price.dollarsbook.price.yens而在后面一种定义下,我们直接使用:book.dollarsbook.yens再一次提醒,因为这是一个联合(union),域dollars和yens占据的是同一块内存空间,所以它们不能被用来存储两个不同的值。也就是你可以使用一个dollars或yens的价格,但不能同时使用两者。枚举Enumerations(enum)枚举(Enumerations)可以用来生成一些任意类型的数据,不只限于数字类型或字符类型,甚至常量true和false。它的定义形式如下:enummodel_name{value1,value2,value3,..}object_name;177/177n例如,我们可以定义一种新的变量类型叫做color_t来存储不同的颜色:enumcolors_t{black,blue,green,cyan,red,purple,yellow,white};注意在这个定义里我们没有使用任何基本数据类型。换句话说,我们创造了一种的新的数据类型,而它并没有基于任何已存在的数据类型:类型color_t,花括号{}中包括了它的所有的可能取值。例如,在定义了colors_t列举类型后,我们可以使用以下表达式:colors_tmycolor;mycolor=blue;if(mycolor==green)mycolor=red;实际上,我们的枚举数据类型在编译时是被编译为整型数值的,而它的数值列表可以是任何指定的整型常量。如果没有指定常量,枚举中第一个列出的可能值为0,后面的每一个值为前面一个值加1。因此,在我们前面定义的数据类型colors_t中,black相当于0,blue相当于1,green相当于2,后面依此类推。如果我们在定义枚举数据类型的时候明确指定某些可能值(例如第一个)的等价整数值,后面的数值将会在此基础上增加,例如:enummonths_t{january=1,february,march,april,may,june,july,august,september,october,november,december}y2k;在这个例子中,枚举类型months_t的变量y2k可以是12种可能取值中的任何一个,从january到december,它们相当于数值1到12,而不是0到11,因为我们已经指定january等于1。第四章面向对象编程Object-orientedProgramming1.类,构造函数和析构函数,类的指针Classes.ConstructorsandDestructors.Pointerstoclasses.2.操作符重载,this,静态成员OverloadingOperators.this.Staticmembers177/177n3.类之间的关系Relationshipsbetweenclasses:friend.Inheritance4.虚拟成员,抽象,多态VirtualMembers.Abstraction.Polymorphism4.1类(Classes)类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class,其功能与C语言中的struct类似,不同之处是class可以包含函数,而不像struct只能包含数据元素。类定义的形式是:classclass_name{permission_label_1:member1;permission_label_2:member2;...}object_name;其中class_name是类的名称(用户自定义的类型),而可选项object_name是一个或几个对象(object)标识。Class的声明体中包含成员members,成员可以是数据或函数定义,同时也可以包括允许范围标志permissionlabels,范围标志可以是以下三个关键字中任意一个:private:,public:或protected:。它们分别代表以下含义:?private:class的private成员,只有同一个class的其他成员或该class的“friend”class可以访问这些成员。?protected:class的protected成员,只有同一个class的其他成员,或该class的“friend”class,或该class的子类(derivedclasses)可以访问这些成员。?public:class的public成员,任何可以看到这个class的地方都可以访问这些成员。177/177n如果我们在定义一个class成员的时候没有声明其允许范围,这些成员将被默认为private范围。例如:classCRectangle{intx,y;public:voidset_values(int,int);intarea(void);}rect;上面例子定义了一个classCRectangle和该class类型的对象变量rect。这个class有4个成员:两个整型变量(x和y),在private部分(因为private是默认的允许范围);以及两个函数,在public部分:set_values()和area(),这里只包含了函数的原型(prototype)。注意class名称与对象(object)名称的不同:在上面的例子中,CRectangle是class名称(即用户定义的类型名称),而rect是一个CRectangle类型的对象名称。它们的区别就像下面例子中类型名int和变量名a的区别一样:inta;int是class名称(类型名),而a是对象名objectname(变量)。在程序中,我们可以通过使用对象名后面加一点再加成员名称(同使用Cstructs一样),来引用对象rect的任何public成员,就像它们只是一般的函数或变量。例如:rect.set_value(3,4);myarea=rect.area();但我们不能够引用x或y,因为它们是该class的private成员,它们只能够在该class的其它成员中被引用。晕了吗?下面是关于classCRectangle的一个复杂的例子://classesexample#includeclassCRectangle{intx,y;177/177npublic:voidset_values(int,int);intarea(void){return(x*y);}};voidCRectangle::set_values(inta,intb){x=a;y=b;}intmain(){CRectanglerect;rect.set_values(3,4);cout<<"area:"<classCRectangle{intx,y;public:voidset_values(int,int);intarea(void){return(x*y);}};voidCRectangle::set_values(inta,intb){x=a;y=b;}intmain(){CRectanglerect,rectb;rect.set_values(3,4);rectb.set_values(5,6);cout<<"rectarea:"<classCRectangle{intwidth,height;public:CRectangle(int,int);intarea(void){return(width*height);}};CRectangle::CRectangle(inta,intb){177/177nwidth=a;height=b;}intmain(){CRectanglerect(3,4);CRectanglerectb(5,6);cout<<"rectarea:"<177/177nclassCRectangle{int*width,*height;public:CRectangle(int,int);~CRectangle();intarea(void){return(*width**height);}};CRectangle::CRectangle(inta,intb){width=newint;height=newint;*width=a;*height=b;}CRectangle::~CRectangle(){deletewidth;deleteheight;}intmain(){CRectanglerect(3,4),rectb(5,6);cout<<"rectarea:"<ClassCRectangle{intwidth,height;public:CRectangle();CRectangle(int,int);intarea(void){return(width*height);}};CRectangle::CRectangle(){width=5;height=5;}CRectangle::CRectangle(inta,intb){width=a;height=b;}intmain(){CRectanglerect(3,4);CRectanglerectb;cout<<"rectarea:"<。这里是一个例子,显示了几种可能出现的情况://pointertoclassesexample#includeclassCRectangle{intwidth,height;public:voidset_values(int,int);intarea(void){return(width*height);}};voidCRectangle::set_values(inta,intb){width=a;height=b;}intmain(){177/177nCRectanglea,*b,*c;CRectangle*d=newCRectangle[2];b=newCRectangle;c=&a;a.set_values(1,2);b->set_values(3,4);d->set_values(5,6);d[1].set_values(7,8);cout<<"aarea:"<area()<area()<,[]):?*x读作:pointedbyx(由x指向的)?&x读作:addressofx(x的地址)?x.y读作:memberyofobjectx(对象x的成员y)?(*x).y读作:memberyofobjectpointedbyx(由x指向的对象的成员y)?x->y读作:memberyofobjectpointedbyx(同上一个等价)?x[0]读作:firstobjectpointedbyx(由x指向的第一个对象)?x[1]读作:secondobjectpointedbyx(由x指向的第二个对象)177/177n?x[n]读作:(n+1)thobjectpointedbyx(由x指向的第n+1个对象)在继续向下阅读之前,一定要确定你明白所有这些的逻辑含义。如果你还有疑问,再读一遍这一笑节,或者同时参考小节"3.3,指针(Pointers)"和"3.5,数据结构(Structures)".由关键字struct和union定义的类类不仅可以用关键字class来定义,也可以用struct或union来定义。因为在C++中类和数据结构的概念太相似了,所以这两个关键字struct和class的作用几乎是一样的(也就是说在C++中struct定义的类也可以有成员函数,而不仅仅有数据成员)。两者定义的类的唯一区别在于由class定义的类所有成员的默认访问权限为private,而struct定义的类所有成员默认访问权限为public。除此之外,两个关键字的作用是相同的。union的概念与struct和class定义的类不同,因为union在同一时间只能存储一个数据成员。但是由union定义的类也是可以有成员函数的。union定义的类访问权限默认为public。4.2操作符重载(Overloadingoperators)C++实现了在类(class)之间使用语言标准操作符,而不只是在基本数据类型之间使用。例如:inta,b,c;a=b+c;是有效操作,因为加号两边的变量都是基本数据类型。然而,我们是否可以进行下面的操作就不是那么显而易见了(它实际上是正确的):struct{charproduct[50];floatprice;}a,b,c;a=b+c;177/177n将一个类class(或结构struct)的对象赋给另一个同种类型的对象是允许的(通过使用默认的复制构造函数copyconstructor)。但相加操作就有可能产生错误,理论上讲它在非基本数据类型之间是无效的。但归功于C++的操作符重载(overload)能力,我们可以完成这个操作。像以上例子中这样的组合类型的对象在C++中可以接受如果没有操作符重载则不能被接受的操作,我们甚至可以修改这些操作符的效果。以下是所有可以被重载的操作符的列表:+-*/=<>+=-=*=/=<<>><<=>>===!=<=>=++--%&^!|~&=^=|=&&||%=[]()newdelete要想重载一个操作符,我们只需要编写一个成员函数,名为operator,后面跟我们要重载的操作符,遵循以下原型定义:typeoperatorsign(parameters);这里是一个操作符+的例子。我们要计算二维向量(bidimensionalvector)a(3,1)与b(1,2)的和。两个二维向量相加的操作很简单,就是将两个x轴的值相加获得结果的x轴值,将两个y轴值相加获得结果的y值。在这个例子里,结果是(3+1,1+2)=(4,3)。//vectors:overloadingoperatorsexample#includeclassCVector{public:intx,y;CVector(){};CVector(int,int);CVectoroperator+(CVector);};CVector::CVector(inta,intb){x=a;y=b;177/177n}CVectorCVector::operator+(CVectorparam){CVectortemp;temp.x=x+param.x;temp.y=y+param.y;return(temp);}intmain(){CVectora(3,1);CVectorb(1,2);CVectorc;c=a+b;cout<==!=<=>=<<>>&&||,A::operator@(B)operator@(A,B)a@b=+=-=*=/=%=^=&=|=<<=>>=[]A::operator@(B)-177/177na(b,c...)()A::operator()(B,C...)-a->b->A::operator->()-*这里a是classA的一个对象,b是B的一个对象,c是classC的一个对象。从上表可以看出有两种方法重载一些class操作符:作为成员函数(memberfunction)或作为全域函数(globalfunction)。它们的用法没有区别,但是我要提醒你,如果不是class的成员函数,则不能访问该class的private或protected成员,除非这个全域函数是该class的friend(friend的含义将在后面的章节解释)。关键字this关键字this通常被用在一个class内部,指正在被执行的该class的对象(object)在内存中的地址。它是一个指针,其值永远是自身object的地址。它可以被用来检查传入一个对象的成员函数的参数是否是该对象本身。例如://this#includeclassCDummy{public:intisitme(CDummy¶m);};intCDummy::isitme(CDummy¶m){if(¶m==this)return1;elsereturn0;}intmain(){CDummya;CDummy*b=&a;if(b->isitme(a))177/177ncout<<"yes,&aisb";return0;}yes,&aisb它还经常被用在成员函数operator=中,用来返回对象的指针(避免使用临时对象)。以下用前面看到的向量(vector)的例子来看一下函数operator=是怎样实现的:CVector&CVector::operator=(constCVector¶m){x=param.x;y=param.y;return*this;}实际上,如果我们没有定义成员函数operator=,编译器自动为该class生成的默认代码有可能就是这个样子的。静态成员(Staticmembers)一个class可以包含静态成员(staticmembers),可以是数据,也可以是函数。一个class的静态数据成员也被称作类变量"classvariables",因为它们的内容不依赖于某个对象,对同一个class的所有object具有相同的值。例如,它可以被用作计算一个class声明的objects的个数,见以下代码程序://staticmembersinclasses#includeclassCDummy{public:staticintn;CDummy(){n++;};~CDummy(){n--;};};177/177nintCDummy::n=0;intmain(){CDummya;CDummyb[5];CDummy*c=newCDummy;cout<classCRectangle{intwidth,height;public:voidset_values(int,int);intarea(void){return(width*height);}friendCRectangleduplicate(CRectangle);};voidCRectangle::set_values(inta,intb){width=a;height=b;}CRectangleduplicate(CRectanglerectparam){CRectanglerectres;rectres.width=rectparam.width*2;177/177nrectres.height=rectparam.height*2;return(rectres);}intmain(){CRectanglerect,rectb;rect.set_values(2,3);rectb=duplicate(rect);cout<classCSquare;classCRectangle{intwidth,height;177/177npublic:intarea(void){return(width*height);}voidconvert(CSquarea);};ClassCSquare{private:intside;public:voidset_side(inta){side=a;}friendclassCRectangle;};voidCRectangle::convert(CSquarea){width=a.side;height=a.side;}intmain(){CSquaresqr;CRectanglerect;sqr.set_side(4);rect.convert(sqr);cout<ClassCPolygon{177/177nprotected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;}};classCRectangle:publicCPolygon{public:intarea(void){return(width*height);}};classCTriangle:publicCPolygon{public:intarea(void){return(width*height/2);}};intmain(){CRectanglerect;CTriangletrgl;rect.set_values(4,5);trgl.set_values(4,5);cout<classmother{public:mother(){cout<<"mother:noparametersn";}mother(inta){cout<<"mother:intparametern";}177/177n};classdaughter:publicmother{public:daughter(inta){cout<<"daughter:intparameternn";}};classson:publicmother{public:son(inta):mother(a){cout<<"son:intparameternn";}};intmain(){daughtercynthia(1);sondaniel(1);return0;}mother:noparametersdaughter:intparametermother:intparameterson:intparameter观察当一个新的daughterobject生成的时候mother的哪一个构造函数被调用了,而当新的sonobject生成的时候,又是哪一个被调用了。不同的构造函数被调用是因为daughter和son的构造函数的定义不同:daughter(inta)//没有特别制定:调用默认constructorson(inta):mother(a)//指定了constructor:调用被指定的构造函数177/177n多重继承(Multipleinheritance)在C++中,一个class可以从多个class中继承属性或函数,只需要在子类的声明中用逗号将不同基类分开就可以了。例如,如果我们有一个特殊的classCOutput可以实现向屏幕打印的功能,我们同时希望我们的类CRectangle和CTriangle在CPolygon之外还继承一些其它的成员,我们可以这样写:classCRectangle:publicCPolygon,publicCOutput{classCTriangle:publicCPolygon,publicCOutput{以下是一个完整的例子://multipleinheritance#includeclassCPolygon{protected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;}};classCOutput{public:voidoutput(inti);};voidCOutput::output(inti){cout<b//指针和对象pointersandobjects(Section4.2)classa:publicb;//类之间的关系Relationshipsbetweenclasses(Section4.3)基类的指针(Pointerstobaseclass)继承的好处之一是一个指向子类(derivedclass)的指针与一个指向基类(baseclass)的指针是type-compatible的。本节就是重点介绍如何利用C++的这一重要特性。例如,我们将结合C++的这个功能,重写前面小节中关于长方形rectangle和三角形triangle的程序://pointerstobaseclass#includeclassCPolygon{protected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;}};classCRectangle:publicCPolygon{public:intarea(void){return(width*height);}};classCTriangle:publicCPolygon{public:intarea(void){return(width*height/2);}177/177n};intmain(){CRectanglerect;CTriangletrgl;CPolygon*ppoly1=▭CPolygon*ppoly2=&trgl;ppoly1->set_values(4,5);ppoly2->set_values(4,5);cout<classCPolygon{177/177nprotected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;}virtualintarea(void){return(0);}};classCRectangle:publicCPolygon{public:intarea(void){return(width*height);}};classCTriangle:publicCPolygon{public:intarea(void){return(width*height/2);}};intmain(){CRectanglerect;CTriangletrgl;CPolygonpoly;CPolygon*ppoly1=▭CPolygon*ppoly2=&trgl;CPolygon*ppoly3=&poly;ppoly1->set_values(4,5);ppoly2->set_values(4,5);ppoly3->set_values(4,5);cout<area()<area()<area()<classCPolygon{protected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;177/177n}virtualintarea(void)=0;};classCRectangle:publicCPolygon{public:intarea(void){return(width*height);}};classCTriangle:publicCPolygon{public:intarea(void){return(width*height/2);}};intmain(){CRectanglerect;CTriangletrgl;CPolygon*ppoly1=▭CPolygon*ppoly2=&trgl;ppoly1->set_values(4,5);ppoly2->set_values(4,5);cout<area()<area()<177/177nclassCPolygon{protected:intwidth,height;public:voidset_values(inta,intb){width=a;height=b;}virtualintarea(void)=0;voidprintarea(void){cout<area()<set_values(4,5);ppoly2->set_values(4,5);177/177nppoly1->printarea();ppoly2->printarea();return0;}2010记住,this代表代码正在被执行的这一个对象的指针。抽象类和虚拟成员赋予了C++多态(polymorphic)的特征,使得面向对象的编程object-orientedprogramming成为一个有用的工具。这里只是展示了这些功能最简单的用途。想象一下如果在对象数组或动态分配的对象上使用这些功能,将会节省多少麻烦。第五章C++高级Advancedconcepts1.模板Templates2.名空间Namespaces3.出错处理Exceptionhandling4.类型转换高级AdvacnedClassType-casting5.预处理指令PreprocessorDirectives5.1模板(Templates)模板(Templates)是ANSI-C++标准中新引入的概念。如果你使用的C++编译器不符合这个标准,则你很可能不能使用模板。177/177n函数模板(Functiontemplates)模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。这在一定程度上实现了宏(macro)的作用。它们的原型定义可以是下面两种中的任何一个:templatefunction_declaration;templatefunction_declaration;上面两种原型定义的不同之处在关键字class或typename的使用。它们实际是完全等价的,因为两种表达的意思和执行都一模一样。例如,要生成一个模板,返回两个对象中较大的一个,我们可以这样写:templateGenericTypeGetMax(GenericTypea,GenericTypeb){return(a>b?a:b);}在第一行声明中,我们已经生成了一个通用数据类型的模板,叫做GenericType。因此在其后面的函数中,GenericType成为一个有效的数据类型,它被用来定义了两个参数a和b,并被用作了函数GetMax的返回值类型。GenericType仍没有代表任何具体的数据类型;当函数GetMax被调用的时候,我们可以使用任何有效的数据类型来调用它。这个数据类型将被作为pattern来代替函数中GenericType出现的地方。用一个类型pattern来调用一个模板的方法如下:function(parameters);例如,要调用GetMax来比较两个int类型的整数可以这样写:intx,y;GetMax(x,y);因此,GetMax的调用就好像所有的GenericType出现的地方都用int来代替一样。这里是一个例子://functiontemplate#includetemplateTGetMax(Ta,Tb){Tresult;177/177nresult=(a>b)?a:b;return(result);}intmain(){inti=5,j=6,k;longl=10,m=5,n;k=GetMax(i,j);n=GetMax(l,m);cout<中的类型。在这个具体的例子中,通用类型T被用作函数GetMax的参数,不需要说明,编译器也可以自动检测到传入的数据类型,因此,我们也可以这样写这个例子:inti,j;GetMax(i,j);因为i和j都是int类型,编译器会自动假设我们想要函数按照int进行调用。这种暗示的方法更为有用,并产生同样的结果://functiontemplateII177/177n#includetemplateTGetMax(Ta,Tb){return(a>b?a:b);}intmain(){inti=5,j=6,k;longl=10,m=5,n;k=GetMax(i,j);n=GetMax(l,m);cout<中指明具体数据类型的。编译器自动决定每一个调用需要什么数据类型。因为我们的模板函数只包括一种数据类型(classT),而且它的两个参数都是同一种类型,我们不能够用两个不同类型的参数来调用它:inti;longl;k=GetMax(i,l);上面的调用就是不对的,因为我们的函数等待的是两个同种类型的参数。我们也可以使得模板函数接受两种或两种以上类型的数据,例如:templateTGetMin(Ta,Ub){return(a(j,l);或者,简单的用i=GetMin(j,l);虽然j和l是不同的类型。类模板(Classtemplates)我们也可以定义类模板(classtemplates),使得一个类可以有基于通用类型的成员,而不需要在类生成的时候定义具体的数据类型,例如:templateclasspair{Tvalues[2];public:pair(Tfirst,Tsecond){values[0]=first;values[1]=second;}};上面我们定义的类可以用来存储两个任意类型的元素。例如,如果我们想要定义该类的一个对象,用来存储两个整型数据115和36,我们可以这样写:pairmyobject(115,36);我们同时可以用这个类来生成另一个对象用来存储任何其他类型数据,例如:pairmyfloats(3.0,2.18);在上面的例子中,类的唯一一个成员函数已经被inline定义。如果我们要在类之外定义它的一个成员函数,我们必须在每一函数前面加template<...>。//classtemplates#includetemplateclasspair{Tvalue1,value2;public:177/177npair(Tfirst,Tsecond){value1=first;value2=second;}Tgetmax();};templateTpair::getmax(){Tretval;retval=value1>value2?value1:value2;returnretval;}intmain(){pairmyobject(100,75);cout<Tpair::getmax()所有写T的地方都是必需的,每次你定义模板类的成员函数的时候都需要遵循类似的格式(这里第二个T表示函数返回值的类型,这个根据需要可能会有变化)。模板特殊化(Templatespecialization)模板的特殊化是当模板中的pattern有确定的类型时,模板有一个具体的实现。例如假设我们的类模板pair包含一个取模计算(moduleoperation)的函数,而我们希望这177/177n个函数只有当对象中存储的数据为整型(int)的时候才能工作,其他时候,我们需要这个函数总是返回0。这可以通过下面的代码来实现://Templatespecialization#includetemplateclasspair{Tvalue1,value2;public:pair(Tfirst,Tsecond){value1=first;value2=second;}Tmodule(){return0;}};template<>classpair{intvalue1,value2;public:pair(intfirst,intsecond){value1=first;value2=second;}intmodule();};template<>intpair::module(){returnvalue1%value2;}intmain(){177/177npairmyints(100,75);pairmyfloats(100.0,75.0);cout<classclass_name这个特殊化本身也是模板定义的一部分,因此,我们必须在该定义开头写template<>。而且因为它确实为一个具体类型的特殊定义,通用数据类型在这里不能够使用,所以第一对尖括号<>内必须为空。在类名称后面,我们必须将这个特殊化中使用的具体数据类型写在尖括号<>中。当我们特殊化模板的一个数据类型的时候,同时还必须重新定义类的所有成员的特殊化实现(如果你仔细看上面的例子,会发现我们不得不在特殊化的定义中包含它自己的构造函数constructor,虽然它与通用模板中的构造函数是一样的)。这样做的原因就是特殊化不会继承通用模板的任何一个成员。模板的参数值(Parametervaluesfortemplates)除了模板参数前面跟关键字class或typename表示一个通用类型外,函数模板和类模板还可以包含其它不是代表一个类型的参数,例如代表一个常数,这些通常是基本数据类型的。例如,下面的例子定义了一个用来存储数组的类模板://arraytemplate#includetemplateclassarray{Tmemblock[N];public:voidsetmember(intx,Tvalue);Tgetmember(intx);177/177n};templatevoidarray::setmember(intx,Tvalue){memblock[x]=value;}templateTarray::getmember(intx){returnmemblock[x];}intmain(){arraymyints;arraymyfloats;myints.setmember(0,100);myfloats.setmember(3,3.1416);cout<//最常用的:一个class参数。template//两个class参数。template//一个class和一个整数。template//有一个默认值。template//参数为一个函数。模板与多文件工程(Templatesandmultiple-fileprojects)177/177n从编译器的角度来看,模板不同于一般的函数或类。它们在需要时才被编译(compiledondemand),也就是说一个模板的代码直到需要生成一个对象的时候(instantiation)才被编译。当需要instantiation的时候,编译器根据模板为特定的调用数据类型生成一个特殊的函数。当工程变得越来越大的时候,程序代码通常会被分割为多个源程序文件。在这种情况下,通常接口(interface)和实现(implementation)是分开的。用一个函数库做例子,接口通常包括所有能被调用的函数的原型定义。它们通常被定义在以.h为扩展名的头文件(headerfile)中;而实现(函数的定义)则在独立的C++代码文件中。模板这种类似宏(macro-like)的功能,对多文件工程有一定的限制:函数或类模板的实现(定义)必须与原型声明在同一个文件中。也就是说我们不能再将接口(interface)存储在单独的头文件中,而必须将接口和实现放在使用模板的同一个文件中。回到函数库的例子,如果我们想要建立一个函数模板的库,我们不能再使用头文件(.h),取而代之,我们应该生成一个模板文件(templatefile),将函数模板的接口和实现都放在这个文件中(这种文件没有惯用扩展名,除了不要使用.h扩展名或不要不加任何扩展名)。在一个工程中多次包含同时具有声明和实现的模板文件并不会产生链接错误(linkageerrors),因为它们只有在需要时才被编译,而兼容模板的编译器应该已经考虑到这种情况,不会生成重复的代码。5.2名空间(Namespaces)通过使用名空间(Namespaces)我们可以将一组全局范围有效的类、对象或函数组织到一个名字下面。换种说法,就是它将全局范围分割成许多子域范围,每个子域范围叫做一个名空间(namespaces).使用名空间的格式是:namespaceidentifier{namespace-body}这里identifier是一个有效的标示符,namespace-body是该名空间包含的一组类、对象和函数。例如:177/177nnamespacegeneral{inta,b;}在这个例子中,a和b是名空间general中的整型变量。要想在这个名空间外面访问这两个变量,我们必须使用范围操作符::。例如,要想访问前面的两个变量,我们需要这样写:general::ageneral::b名空间(namespaces)的作用在于全局对象或函数很有可能重名而造成重复定义的错误,名空间的使用可以避免这些错误的发生。例如://namespaces#includenamespacefirst{intvar=5;}namespacesecond{doublevar=3.1416;}intmain(){cout<namespacefirst{intvar=5;}namespacesecond{doublevar=3.1416;}intmain(){usingnamespacesecond;cout<namespacefirst{intvar=5;}namespacesecond{doublevar=3.1416;}intmain(){{usingnamespacefirst;cout<intmain(){std::cout<<"HelloworldinANSI-C++n";return0;}HelloworldinANSI-C++更常用的方法是使用usingnamespace,这样我们就不必在所有标准空间中定义的函数或对象前面总是使用范围操作符::了://ANSI-C++complianthelloworld(II)#includeusingnamespacestd;177/177nintmain(){cout<<"HelloworldinANSI-C++n";return0;}HelloworldinANSI-C++对于STL用户,强烈建议使用ANSI-compliant方式来包含标准函数库。5.3出错处理(Exceptionhandling)本节介绍的出错处理是ANSI-C++标准引入的新功能。如果你使用的C++编译器不兼容这个标准,则你可能无法使用这些功能。在编程过程中,很多时候我们是无法确定一段代码是否总是能够正常工作的,或者因为程序访问了并不存在的资源,或者由于一些变量超出了预期的范围,等等。这些情况我们统称为出错(例外),C++新近引入的三种操作符能够帮助我们处理这些出错情况:try,throw和catch。它们的一般用法是:try{//codetobetriedthrowexception;}catch(typeexception){//codetobeexecutedincaseofexception}它们所进行的操作是:?try语句块中的代码被正常执行。如果有例外发生,代码必须使用关键字throw和一个参数来扔出一个例外。这个参数可以是任何有效的数据类型,它的类型反映了例外的特征。177/177n?如果有例外发生,也就是说在try语句块中有一个throw指令被执行了,则catch语句块会被执行,用来接收throw传来的例外参数。例如://exceptions#includeintmain(){charmyarray[10];try{for(intn=0;n<=10;n++){if(n>9)throw"Outofrange";myarray[n]='z';}}catch(char*str){cout<<"Exception:"<177/177nintmain(){try{char*mystring;mystring=newchar[10];if(mystring==NULL)throw"Allocationfailure";for(intn=0;n<=100;n++){if(n>9)thrown;mystring[n]='z';}}catch(inti){cout<<"Exception:";cout<<"index"<#include#includeclassA{virtualvoidf(){};};intmain(){try{A*a=NULL;typeid(*a);}catch(std::exception&e){cout<<"Exception:"<classCDummy{inti;};classCAddition{intx,y;public:CAddition(inta,intb){x=a;y=b;}intresult(){returnx+y;}};intmain(){CDummyd;CAddition*padd;padd=(CAddition*)&d;cout<result();return0;}虽然以上程序在C++中是没有语法错误的(多数编译器甚至不会产生警告信息),但这段程序没有什么实际的逻辑意义。我们使用了CAddition的成员函数result而没有定义一个相应的该类的对象:padd并不是一个对象,它只是一个指针,被我们赋值指向177/177n一个毫无关系的对象的地址。当在程序运行到访问它的result成员函数时,将会有一个运行错误(run-timeerror)产生,或生成一个意外的结果。为了控制这种类之间的转换,ANSI-C++标准定义了4种新的类型转换操作符:reinterpret_cast,static_cast,dynamic_cast和const_cast。所有这些操作符都是同样的使用格式:reinterpret_cast(expression)dynamic_cast(expression)static_cast(expression)const_cast(expression)这里new_type是要转换成的目标类型,expression是要被转换的内容。为了便于理解,模仿传统转换操作符,它们的含义是这样的:(new_type)expressionnew_type(expression)reinterpret_castreinterpret_cast可以将一个指针转换为任意其它类型的指针。它也可以用来将一个指针转换为一个整型,或反之亦然。这个操作符可以在互不相关的类之间进行指针转换,操作的结果是简单的将一个指针的二进制数据(binarycopy)复制到另一个指针。对指针指向的内容不做任何检查或转换。如果这种复制发生在一个指针到一个整数之间,则对其内容的解释取决于不同的系统,因此任何实现都是不可移植(nonportable)的。一个指针如果被转换为一个能够完全存储它的足够大的整数中,则是可以再被转换回来成为指针的。例如:classA{};classB{};A*a=newA;B*b=reinterpret_cast(a);reinterpret_cast对所有指针的处理与传统的类型转换符所作的一模一样。177/177nstatic_caststatic_cast可以执行所有能够隐含执行的类型转换,以及它们的反向操作(即使这种方向操作是不允许隐含执行的)。用于类的指针,也就是说,它允许将一个引申类的指针转换为其基类类型(这是可以被隐含执行的有效转换),同时也允许进行相反的转换:将一个基类转换为一个引申类类型。在后面一种情况中,不会检查被转换的基类是否真正完全是目标类型的。例如下面的代码是合法的:classBase{};classDerived:publicBase{};Base*a=newBase;Derived*b=static_cast(a);static_cast除了能够对类指针进行操作,还可以被用来进行类中明确定义的转换,以及对基本类型的标准转换:doubled=3.14159265;inti=static_cast(d);译者注:如果你对这部分看不太懂,请结合下面的dynamic_cast一起看,也许会帮助理解。dynamic_castdynamic_cast完全被用来进行指针的操作。它可以用来进行任何可以隐含进行的转换操作以及它们被用于多态类情况下的方向操作。然而与static_cast不同的是,dynamic_cast会检查后一种情况的操作是否合法,也就是说它会检查类型转换操作是否会返回一个被要求类型的有效的完整的对象。这种检查是在程序运行过程中进行的。如果被转换的指针所指向的对象不是一个被要求类型的有效完整的对象,返回值将会是一个空指针NULL。classBase{virtualdummy(){};};classDerived:publicBase{};177/177nBase*b1=newDerived;Base*b2=newBase;Derived*d1=dynamic_cast(b1);//succeedsDerived*d2=dynamic_cast(b2);//fails:returnsNULL如果类型转换被用在引用(reference)类型上,而这个转换不可能进行的话,一个bad_cast类型的例外(exception)将会被抛出:classBase{virtualdummy(){};};classDerived:publicBase{};Base*b1=newDerived;Base*b2=newBase;Derivedd1=dynamic_cast(b1);//succeedsDerivedd2=dynamic_cast(b2);//fails:exceptionthrownconst_cast这种类型转换对常量const进行设置或取消操作:classC{};constC*a=newC;C*b=const_cast(a);其他3种cast操作符都不可以修改一个对象的常量属性(constness)。typeidANSI-C++还定义了一个新的操作符叫做typeid,它检查一个表达式的类型:typeid(expression)这个操作符返回一个类型为type_info的常量对象指针,这种类型定义在标准头函数中。这种返回值可以用操作符==和!=来互相进行比较,也可以用来通过name()函数获得一个描述数据类型或类名称的字符串,例如:177/177n//typeid,typeinfo#include#includeclassCDummy{};intmain(){CDummy*a,b;if(typeid(a)!=typeid(b)){cout<<"aandbareofdifferenttypes:n";cout<<"ais:"<b?a:bintx=5,y;y=getmax(x,2);这段代码执行后y的值为5。#undef#undef完成与#define相反的工作,它取消对传入的参数的宏定义:#defineMAX_WIDTH100charstr1[MAX_WIDTH];#undefMAX_WIDTH#defineMAX_WIDTH200charstr2[MAX_WIDTH];#ifdef,#ifndef,#if,#endif,#elseand#elif这些指令可以使程序的一部分在某种条件下被忽略。#ifdef可以使一段程序只有在某个指定常量已经被定义了的情况下才被编译,无论被定义的值是什么。它的操作是:#ifdefname//codehere#endif177/177n例如:#ifdefMAX_WIDTHcharstr[MAX_WIDTH];#endif在这个例子中,语句charstr[MAX_WIDTH];只有在宏定义常量MAX_WIDTH已经被定义的情况下才被编译器考虑,不管它的值是什么。如果它还没有被定义,这一行代码则不会被包括在程序中。#ifndef起相反的作用:在指令#ifndef和#endif之间的代码只有在某个常量没有被定义的情况下才被编译,例如:#ifndefMAX_WIDTH#defineMAX_WIDTH100#endifcharstr[MAX_WIDTH];这个例子中,如果当处理到这段代码的时候MAX_WIDTH还没有被定义,则它会被定义为值100。而如果它已经被定义了,那么它会保持原值(因为#define语句这一行不会被执行)。指令#if,#else和#elif(elif=elseif)用来使得其后面所跟的程序部分只有在特定条件下才被编译。这些条件只能够是常量表达式,例如:#ifMAX_WIDTH>200#undefMAX_WIDTH#defineMAX_WIDTH200#elsifMAX_WIDTH<50#undefMAX_WIDTH#defineMAX_WIDTH50#else#undefMAX_WIDTH#defineMAX_WIDTH100#endif177/177ncharstr[MAX_WIDTH];注意看这一连串的指令#if,#elsif和#else是怎样以#endif结尾的。#line当我们编译一段程序的时候,如果有错误发生,编译器会在错误前面显示出错文件的名称以及文件中的第几行发生的错误。指令#line可以使我们对这两点进行控制,也就是说当出错时显示文件中的行数以及我们希望显示的文件名。它的格式是:#linenumber"filename"这里number是将会赋给下一行的新行数。它后面的行数从这一点逐个递增。filename是一个可选参数,用来替换自此行以后出错时显示的文件名,直到有另外一个#line指令替换它或直到文件的末尾。例如:#line1"assigningvariable"inta?;这段代码将会产生一个错误,显示为在文件"assigningvariable",line1。#error这个指令将中断编译过程并返回一个参数中定义的出错信息,例如:#ifndef__cplusplus#errorAC++compilerisrequired#endif这个例子中如果__cplusplus没有被定义就会中断编译过程。#include这个指令我们已经见到很多次。当预处理器找到一个#include指令时,它用指定文件的全部内容替换这条语句。声明包含一个文件有两种方式:#include"file"177/177n#include两种表达的唯一区别是编译器应该在什么路经下寻找指定的文件。第一种情况下,文件名被写在双引号中,编译器首先在包含这条指令的文件所在的目录下进行寻找,如果找不到指定文件,编译器再到被配置的默认路径下(也就是标准头文件路径下)进行寻找。如果文件名是在尖括号<>中,编译器会直接到默认标准头文件路径下寻找。#pragma这个指令是用来对编译器进行配置的,针对你所使用的平台和编译器而有所不同。要了解更多信息,请参考你的编译器手册。如果你的编译器不支持某个#pragma的特定参数,这个参数会被忽略,不会产生出错。预定义的宏名称(Predefinedmacronames)以下宏名称在任何时候都是定义好的:macrovalue__LINE__整数值,表示当前正在编译的行在源文件中的行数。__FILE__字符串,表示被编译的源文件的文件名。__DATE__一个格式为"Mmmddyyyy"的字符串,存储编译开始的日期。__TIME__一个格式为"hh:mm:ss"的字符串,存储编译开始的时间。__cplusplus整数值,所有C++编译器都定义了这个常量为某个值。如果这个编译器是完全遵守C++标准的,它的值应该等于或大于199711L,具体值取决于它遵守的是哪个版本的标准。例如://标准宏名称#includeusingnamespacestd;177/177nintmain(){cout<<"Thisisthelinenumber"<<__LINE__;cout<<"offile"<<__FILE__<<".n";cout<<"Itscompilationbegan"<<__DATE__;cout<<"at"<<__TIME__<<".n";cout<<"Thecompilergivesa"<<"__cplusplusvalueof"<<__cplusplus;return0;}Thisisthelinenumber7offile/home/jay/stdmacronames.cpp.ItscompilationbeganNov12005at10:12:29.Thecompilergivesa__cplusplusvalueof1第六章C++标准函数库C++StandardLibrary文件的输入输出(Input/Outputwithfiles)C++通过以下几个类支持文件的输入输出:?ofstream:写操作(输出)的文件类(由ostream引申而来)?ifstream:读操作(输入)的文件类(由istream引申而来)?fstream:可同时读写操作的文件类(由iostream引申而来)打开文件(Openafile)177/177n对这些类的一个对象所做的第一个操作通常就是将它和一个真正的文件联系起来,也就是说打开一个文件。被打开的文件在程序中由一个流对象(streamobject)来表示(这些类的一个实例),而对这个流对象所做的任何输入输出操作实际就是对该文件所做的操作。要通过一个流对象打开一个文件,我们使用它的成员函数open():voidopen(constchar*filename,openmodemode);这里filename是一个字符串,代表要打开的文件名,mode是以下标志符的一个组合:ios::in为输入(读)而打开文件ios::out为输出(写)而打开文件ios::ate初始位置:文件尾ios::app所有输出附加在文件末尾ios::trunc如果文件已存在则先删除该文件ios::binary二进制方式这些标识符可以被组合使用,中间以”或”操作符(|)间隔。例如,如果我们想要以二进制方式打开文件"example.bin"来写入一些数据,我们可以通过以下方式调用成员函数open()来实现:ofstreamfile;file.open("example.bin",ios::out|ios::app|ios::binary);ofstream,ifstream和fstream所有这些类的成员函数open都包含了一个默认打开文件的方式,这三个类的默认方式各不相同:类参数的默认方式ofstreamios::out|ios::truncifstreamios::infstreamios::in|ios::out只有当函数被调用时没有声明方式参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。177/177n由于对类ofstream,ifstream和fstream的对象所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open函数,并拥有同样的参数。这样,我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作:ofstreamfile("example.bin",ios::out|ios::app|ios::binary);两种打开文件的方式都是正确的。你可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了:boolis_open();它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假(false)则相反。关闭文件(Closingafile)当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close(),它负责将缓存中的数据排放出来并关闭文件。它的格式很简单:voidclose();这个函数一旦被调用,原先的流对象(streamobject)就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程(process)所有访问了。为防止流对象被销毁时还联系着打开的文件,析构函数(destructor)将会自动调用关闭函数close。文本文件(Textmodefiles)类ofstream,ifstream和fstream是分别从ostream,istream和iostream中引申而来的。这就是为什么fstream的对象可以使用其父类的成员来访问数据。一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin和cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<://writingonatextfile#includeintmain(){ofstreamexamplefile("example.txt");177/177nif(examplefile.is_open()){examplefile<<"Thisisaline.n";examplefile<<"Thisisanotherline.n";examplefile.close();}return0;}fileexample.txtThisisaline.Thisisanotherline.从文件中读入数据也可以用与cin的使用同样的方法://readingatextfile#include#include#includeintmain(){charbuffer[256];ifstreamexamplefile("example.txt");if(!examplefile.is_open()){cout<<"Erroropeningfile";exit(1);}while(!examplefile.eof()){examplefile.getline(buffer,100);cout<#includeconstchar*filename="example.txt";intmain(){177/177nlongl,m;ifstreamfile(filename,ios::in|ios::binary);l=file.tellg();file.seekg(0,ios::end);m=file.tellg();file.close();cout<<"sizeof"<>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。文件流包括两个为顺序读写数据特殊设计的成员函数:write和read。第一个函数(write)是ostream的一个成员函数,都是被ofstream所继承。而read是istream的一个成员函数,被ifstream所继承。类fstream的对象同时拥有这两个函数。它们的原型是:write(char*buffer,streamsizesize);read(char*buffer,streamsizesize);这里buffer是一块内存的地址,用来存储或读出数据。参数size是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。//readingbinaryfile#include#includeconstchar*filename="example.txt";intmain(){177/177nchar*buffer;longsize;ifstreamfile(filename,ios::in|ios::binary|ios::ate);size=file.tellg();file.seekg(0,ios::beg);buffer=newchar[size];file.read(buffer,size);file.close();cout<<"thecompletefileisinabuffer";delete[]buffer;return0;}Thecompletefileisinabuffer缓存和同步(BuffersandSynchronization)当我们对文件流进行操作的时候,它们与一个streambuf类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流,每次成员函数put(写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:?当文件被关闭时:在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。?当缓存buffer满时:缓存Buffers有一定的空间限制。当缓存满时,它会被自动同步。?控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush和endl。177/177n?明确调用函数sync():调用成员函数sync()(无参数)可以引发立即同步。这个函数返回一个int值,等于-1表示流没有联系的缓存或操作失败。8177/177

相关文档