《c++基础教案》PPT课件

上传人:ni****g 文档编号:584015051 上传时间:2024-08-30 格式:PPT 页数:139 大小:471KB
返回 下载 相关 举报
《c++基础教案》PPT课件_第1页
第1页 / 共139页
《c++基础教案》PPT课件_第2页
第2页 / 共139页
《c++基础教案》PPT课件_第3页
第3页 / 共139页
《c++基础教案》PPT课件_第4页
第4页 / 共139页
《c++基础教案》PPT课件_第5页
第5页 / 共139页
点击查看更多>>
资源描述

《《c++基础教案》PPT课件》由会员分享,可在线阅读,更多相关《《c++基础教案》PPT课件(139页珍藏版)》请在金锄头文库上搜索。

1、目录目录n第一节第一节c+概述概述n第二节第二节函数函数n第三节第三节类和对象类和对象n第四节第四节友元和重载友元和重载n第五节第五节模板模板n第六节第六节继承继承1第一节第一节c+概述概述nmain()函数函数n标准输入输出流标准输入输出流nexit语句语句n数据类型数据类型n标准库标准库string类型类型n引用类型引用类型n指针和指针和const限定符限定符n动态内存分配动态内存分配21.1main()函数函数nintmain()nnreturn0;nn每个每个c+程序必须含有程序必须含有main()函数,且函数,且main函数函数是唯一被操作系统显式调用的函数是唯一被操作系统显式调用的

2、函数n定义函数必须制定定义函数必须制定4个元素:返回类型、函数名、个元素:返回类型、函数名、形参表、函数体。形参表、函数体。n操作系统通过操作系统通过main的返回值来确定程序是否成的返回值来确定程序是否成功执行完毕,返回功执行完毕,返回0表示程序成功执行完毕,通表示程序成功执行完毕,通常非常非0表示有错误出现表示有错误出现31.2标准输入输出流标准输入输出流nC+没有直接定义进行输入没有直接定义进行输入/输出的任何语句,输出的任何语句,这个功能由标准库这个功能由标准库iostream.h提供。包含两个类:提供。包含两个类:输入流输入流istream和输出流和输出流ostream.n#incl

3、udenusingnamespacestd;n标准库中的四个标准库中的四个IO对象对象ncin标准输入(如键盘),为标准输入(如键盘),为istream对象对象ncout标准输出(如显示屏标准输出(如显示屏),为),为ostream对象对象ncerr标准错误,用于输出警告和错误信息,为标准错误,用于输出警告和错误信息,为ostream对象对象nclog用于产生程序执行的一般信息,为用于产生程序执行的一般信息,为ostream对象对象4cin读入流读入流(由键盘输入)(由键盘输入)n作用作用从键盘取得数据送至内存,与从键盘取得数据送至内存,与一起使用一起使用结合方向为自左向右结合方向为自左向右例

4、如:例如:intv1,v2;cinv1v2;从流中读取信息时,输入流缓冲指针跟踪最后从流中读取信息时,输入流缓冲指针跟踪最后一个读入到流的字符,每次尝试从流获取信一个读入到流的字符,每次尝试从流获取信息时,都从指针的当前位置开始息时,都从指针的当前位置开始qcin自动跳过空白字符(自动跳过空白字符(whitespace)q返回值为左操作数返回值为左操作数5用用cout写入到流(写入到流(输出到屏幕输出到屏幕)ncout必须与输出操作符必须与输出操作符一起使用,结合方向为自左一起使用,结合方向为自左向右向右n例如:例如:coutEntertwonumbersendl;coutdecx;(hex/

5、oct)n作用作用将右操作数插入到将右操作数插入到cout中,可同时接受不同类型的数据中,可同时接受不同类型的数据输出,所以可有多个输出,所以可有多个操作符,把信息写入流时,把信息操作符,把信息写入流时,把信息添加到流的末尾,相当于从左到右输出添加到流的末尾,相当于从左到右输出nendl(endofline)为操纵符,具有换行效果,并刷新与设备相为操纵符,具有换行效果,并刷新与设备相关联的缓冲区,刷新后用户可立即看到写入到流中的输出。关联的缓冲区,刷新后用户可立即看到写入到流中的输出。注:忘记刷新可能会造成输出停留在缓冲区,建议在程序员注:忘记刷新可能会造成输出停留在缓冲区,建议在程序员调试过

6、程中,这些语句都应刷新输出流调试过程中,这些语句都应刷新输出流niomanip.h中,中,setw(n):为后面的输出项预留为后面的输出项预留n列列61.3exit()语句语句n形式:形式:exit(interger_value);n执行执行exit语句时,程序立即终止。一般来说,如语句时,程序立即终止。一般来说,如果因为一个错误而调用果因为一个错误而调用exit,就使用就使用1,其他情,其他情况使用况使用0。n该函数在头文件该函数在头文件cstdlib中,所有要有预编译命令中,所有要有预编译命令#includeusingnamespacestd;71.4内置数据类型内置数据类型n常量常量n宏

7、常量:宏常量:#definePI3.1415926系统不为其分配内存,只是简单的字符串替换系统不为其分配内存,只是简单的字符串替换nconst常量:常量:const类型类型常量标识符常量标识符=值;值;常量定义后不能修改,所以必须初始化常量定义后不能修改,所以必须初始化例如:例如:constdoublePI=3.1415926;系统为系统为PI分配内存单元分配内存单元两种常量的比较两种常量的比较const常量有数据类型,而宏常量没有数据类型;编译常量有数据类型,而宏常量没有数据类型;编译器可以对器可以对const常量进行类型合法性检查,而对宏常常量进行类型合法性检查,而对宏常量仅仅是字符的替换

8、,没有合法性检查,并且在字量仅仅是字符的替换,没有合法性检查,并且在字符替换的时候可能产生意想不到的错误符替换的时候可能产生意想不到的错误c+中,中,const常量完全可以取代宏常量常量完全可以取代宏常量8q布尔类型布尔类型bool,它的值只有两个它的值只有两个true和和false可以将算术类型的任何值赋给可以将算术类型的任何值赋给bool对象。对象。0代表代表false非非0代表代表trueq初始化:创建对象并给它赋初值(赋值指擦除初始化:创建对象并给它赋初值(赋值指擦除对象的当前值并用新值代替)对象的当前值并用新值代替)有两种形式:有两种形式:复制初始化复制初始化:用等号用等号intva

9、l=1024;直接初始化直接初始化:初始化式放在括号中初始化式放在括号中intval(1024);通常在一个对象首次使用的地方定义该对象通常在一个对象首次使用的地方定义该对象91.5标准库标准库string类型类型#includeusingnamespacestd;nstring对象的定义和初始化对象的定义和初始化nstrings2(s1);/s1为为string对象或字符串字面值对象或字符串字面值nstrings3(n,c);/s3为为n个个cn注意:字符串字面值和标准库注意:字符串字面值和标准库string类型不是同一类型类型不是同一类型nstring对象的读写对象的读写n输入:输入:ci

10、ns;/从第一个非空字符读至下一个空白字符从第一个非空字符读至下一个空白字符读入一行读入一行getline(cin,line);两个参数:输入流对象和两个参数:输入流对象和string对象对象功能:功能:从输入流的中读取内容到从输入流的中读取内容到line中,换行符是该行的结束中,换行符是该行的结束标志标志注:注:getline()不忽略开头的换行符,但不忽略开头的换行符,但line并不保存换行符,即并不保存换行符,即若开头遇换行符,若开头遇换行符,line为空为空stringq输出:输出:coutsendl;10#include#includeusingnamespacestd;intmai

11、n(intargc,char*argv)stringstr;coutInputastringstr;/输入输入str的时候,只要遇到了空格,就会结束输入的时候,只要遇到了空格,就会结束输入coutstrendl;coutInputalineusinggetlineendl;getline(cin,str);coutstrstr;输入了一次回车,这个语句遇输入了一次回车,这个语句遇到回车就结束,这样,回车符也跟着到了内存,到用到回车就结束,这样,回车符也跟着到了内存,到用getline()()再输入时,内存里第一个字符就是回车,所以再输入时,内存里第一个字符就是回车,所以getline()一读到

12、()一读到这个回车就结束了,所以这个回车就结束了,所以getline(cin,str);不起作用。不起作用。解决:解决:处理掉处理掉cinstr;遗留的回车符,可以在该语句下加一句遗留的回车符,可以在该语句下加一句:getchar();/吸收内存里的回车符吸收内存里的回车符11nstring对象的操作对象的操作ns.empty();若字符串为空,返回若字符串为空,返回truens.size();返回返回s中字符的个数中字符的个数qsn;返回返回s中位置为中位置为n的字符的字符qs1=s2;比较,所有的比较运算符都可以使用比较,所有的比较运算符都可以使用qs1+=s2;连接连接qs1=s2;赋值

13、赋值,string类型可以和字符串字面值连接,类型可以和字符串字面值连接,赋给赋给string类型,但是,类型,但是,+操作符的左右操作数必须操作符的左右操作数必须至少有一个至少有一个string类型类型strings=hello+,+s1;121.6引用类型引用类型n引用就是对象的另一个名字,主要用于函数的形参引用就是对象的另一个名字,主要用于函数的形参n引入引入nvoid swap(int a , int b)n int temp=a; a=b; b=temp;nint main()n int x=10, y=20;nnswap(x,y);nq形形参参与与实实参参有有各各自自不不同同的的内

14、内存存空空间间,若若实实参参是是一一个个复复杂对象,重新分配内存会引起程序执行效率大大下降杂对象,重新分配内存会引起程序执行效率大大下降q形形参参对对实实参参为为值值传传递递,对对形形参参的的任任何何改改变变不不会会引引起起实实参值的改变参值的改变131.6.1非非const引用引用n引用就是某一变量(目标)的一个别名,对引用的操引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。作与对变量直接操作完全一样。n声明:声明:类型标识符类型标识符&引用名引用名=目标变量名;目标变量名;inta;int&ra=a;/定义引用定义引用ra,它是变量它是变量a的引用的引用(别别名)

15、名)(1)&在此不是求地址运算,而是起标识作用。在此不是求地址运算,而是起标识作用。(2)声明引用时,必须同时对其进行初始化。)声明引用时,必须同时对其进行初始化。(3)引用声明完毕后,相当于目标变量名有两个名称,)引用声明完毕后,相当于目标变量名有两个名称,不能再把该引用名作为其他变量名的别名。不能再把该引用名作为其他变量名的别名。(4)声明一个引用,不是新定义了一个变量,它只表示)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元。故:对引用数据类型,因此引用本身不占存储

16、单元。故:对引用求地址,就是对目标变量求地址。求地址,就是对目标变量求地址。&ra与与&a相等。相等。14非非const引用引用intival=1024;1.引用必须在定义时初始化,引用必须在定义时初始化,int&refval=ival;一旦绑定到某对象不可以将引用一旦绑定到某对象不可以将引用int&refval1;绑定到另一对象绑定到另一对象int&refval2=refval;2.不能定义引用类型的引用,不能定义引用类型的引用,int&ref1=4;必须用与该变量同类型对象初始化必须用与该变量同类型对象初始化int&ref2=ival+5;3.引用对应的值必须具有相应的引用对应的值必须具有

17、相应的内存空间,以便对这个空间进行引用。常量、表达式、内存空间,以便对这个空间进行引用。常量、表达式、引用不能赋给引用引用不能赋给引用例:例:inti=5;intj=6;int&k=i;k=j;/k和和i的值都变成了的值都变成了6;151.6.2const引用引用n格式:格式: const const 类型标识符类型标识符 & &引用名引用名= =目标变量名;目标变量名;用这种方式声明的引用,不能通过引用对目标用这种方式声明的引用,不能通过引用对目标变量的值进行修改变量的值进行修改, ,从而使引用的目标成为从而使引用的目标成为constconst,达到了引用的安全性。达到了引用的安全性。nin

18、tint a ; a ;const const intint & &rara=a;=a;rara=1; /=1; /错误错误a=1; /a=1; /正确正确n引用型参数应该在能被定义为引用型参数应该在能被定义为const的情况下,的情况下,尽量定义为尽量定义为const。16对对const对象的引用对象的引用const int ival=1024; int i=34;const int &refval=ival; refval=512; int &ref2=ival; const int &ref3=42; const int &ref=i;const int &ref4=ref3+3;v r

19、efvalrefval是对是对constconst型的引用,所以任何对型的引用,所以任何对refvalrefval的赋值都是非法的赋值都是非法vconstconst对象必须用对象必须用constconst引用引用vconstconst引用可以绑定到相关的类型的对象,或绑引用可以绑定到相关的类型的对象,或绑定到右值定到右值171.6.3指针和引用的比较指针和引用的比较n指针和引用都可间接访问另一个值,但是指针和引用都可间接访问另一个值,但是n1、引用总是指向某个对象,定义时必须初始化。、引用总是指向某个对象,定义时必须初始化。指针则可以在任何时候被初始化指针则可以在任何时候被初始化n2、引用一旦

20、被初始化,就不能改变引用的关系、引用一旦被初始化,就不能改变引用的关系而指针则可以随时改变所指向的对象而指针则可以随时改变所指向的对象n3、赋值:引用即为别名,给引用赋值修改的是、赋值:引用即为别名,给引用赋值修改的是该引用所关联的对象的值该引用所关联的对象的值(非非const引用引用)而指针而指针可以更改其指向的对象,也可以更改所指向的可以更改其指向的对象,也可以更改所指向的对象的值对象的值n4、不能有、不能有NULL引用,引用必须与合法的存储引用,引用必须与合法的存储单元关联,而指针则可以是单元关联,而指针则可以是NULL181.7指针和指针和const限定符限定符n使用使用const修饰

21、修饰指针指针时,由于时,由于const的位置不同,的位置不同,而含意不同。而含意不同。n1.7.1指向指向const对象的指针对象的指针指向指向constconst的指针,不可以通过该指针修改对象,的指针,不可以通过该指针修改对象,但可以其他方式修改但可以其他方式修改double double dvaldval=3.14,pi=3.1415;=3.14,pi=3.1415;const const doubledouble * *ptrptr = & = &dvaldval; ;/const/const限定了指针所指的对象类型,非限定了指针所指的对象类型,非ptrptr* *ptrptr = =

22、 3.14153.1415; / ; / 不合法不合法 指针所指的对象不能改指针所指的对象不能改 ptrptr = & = &pipi; /; /合法合法 , 指针值可变指针值可变dval=3.1415;/合法合法19n若一个指针是指向若一个指针是指向const对象,则该指针必须具对象,则该指针必须具有有const特性。例如特性。例如constdoublepi=3.14159;constdouble*ptr=π/正确正确double*ptr1=π/错误,错误,const对象要用指向对象要用指向const的指针来指向,这样的指针来指向,这样可以保证既不能通过可以保证既不能通过*ptr

23、,也不能通过也不能通过pi修改其值修改其值指向指向const的指针常用作函数的形参,这样可以确保函的指针常用作函数的形参,这样可以确保函数的实参在函数调用过程中不被修改数的实参在函数调用过程中不被修改voiduse_ptr(constint*p)201.7.2const指针指针n固定指向一个对象的指针,即指针本身是常量固定指向一个对象的指针,即指针本身是常量char * const ptchar * const ptr r1 = stringpt1 = stringptr r1;1;/constconst放在类型说明和变量之间放在类型说明和变量之间ptr1 = stringptptr1 = s

24、tringptr r2;2;/非法,指针本身的值不可改变非法,指针本身的值不可改变* *ptr1 = m; ptr1 = m; /合法合法 指针所指的变量的值可以改变指针所指的变量的值可以改变n若指针及指针所指向的变量的值都不可以更改若指针及指针所指向的变量的值都不可以更改constchar*constptr=stringptr;211.8动态内存分配动态内存分配n一一内存分配有三种方式内存分配有三种方式1.从静态存储区域分配。内存在程序编译的时候就已经分从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全配好,这块内存在程序的整个运行期间都存在。例

25、如全局变量,局变量,static变量。变量。2.栈,就是那些由编译器在需要的时候分配,在不需要的栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。栈内存分配运算内置于处理器的指变量、函数参数等。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。令集中,效率很高,但是分配的内存容量有限。3.堆,亦称动态内存分配。程序在运行的时候用堆,亦称动态内存分配。程序在运行的时候用malloc或或new申请任意多少的内存,程序员自己负责在何时用申请任意多少的内存,程序员自己负

26、责在何时用free或或delete释放内存。动态内存的生存期由我们决定如释放内存。动态内存的生存期由我们决定如果程序员没有释放掉,那么在程序结束后,操作系统会果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。自动回收。221.8.2单个对象的动态分配与释放单个对象的动态分配与释放n动态分配:由关键字动态分配:由关键字new及其后面的类型指示符构成。及其后面的类型指示符构成。该类型指示符可以是内置类型或该类型指示符可以是内置类型或class类型。类型。n例:例:newint;从堆中分配了一个从堆中分配了一个int型的对象。型的对象。newStudent; 分配了一个分配了一个Studen

27、t类对象。类对象。需要注意的是堆中分配的对象没有名字。需要注意的是堆中分配的对象没有名字。new表达式返回了一表达式返回了一个指向该对象的个指向该对象的指针指针,对该对象的全部操作都要通过这个指,对该对象的全部操作都要通过这个指针间接完成。针间接完成。例如:例如:pint*pi=newint;该该new表达式创建了一个表达式创建了一个int型的对象,由型的对象,由pi指向它。指向它。n初始化初始化int*pi=newint(0);该语句表示该语句表示pi指向一个指向一个int型的对象,该对象的初始值为型的对象,该对象的初始值为0。括号。括号中的表达式被称作初始化式中的表达式被称作初始化式23动

28、态内存的释放动态内存的释放n与静态分配内存的对象不同,编译器不会自动释放它与静态分配内存的对象不同,编译器不会自动释放它们所占的内存们所占的内存-除非整个程序结束。所以当动态分除非整个程序结束。所以当动态分配内存的对象完成它的使命,需要被销毁的时候不能配内存的对象完成它的使命,需要被销毁的时候不能依赖编译器,而要靠程序员用依赖编译器,而要靠程序员用delete释放。释放。n格式:格式:deletepi;/释放释放pi所指向的内存空间所指向的内存空间n指针指针pi本身是个在全局域中声明的全局对象,它的本身是个在全局域中声明的全局对象,它的生命期由编译器控制。生命期由编译器控制。pi的存储区在程序

29、开始之前的存储区在程序开始之前就被分配,一直保持到程序结束。就被分配,一直保持到程序结束。n而而pi指向的对象的生命期是由程序员控制的,它是指向的对象的生命期是由程序员控制的,它是在程序执行过程中遇到在程序执行过程中遇到new表达式时才被创建,遇表达式时才被创建,遇到到delete表达式时被销毁并收回存储区表达式时被销毁并收回存储区24动态内存的释放动态内存的释放ndelete只能用在指向内存是用只能用在指向内存是用new动态分配的指针上,如动态分配的指针上,如果将其在指向堆以外内存的指针上,会使程序运行期间果将其在指向堆以外内存的指针上,会使程序运行期间出现未定义的行为。唯一的例外是,当指针

30、指向出现未定义的行为。唯一的例外是,当指针指向NULL时,不管指针指向的对象是如何分配内存的,都不会引时,不管指针指向的对象是如何分配内存的,都不会引发麻烦。发麻烦。nvoidf()ninti;nchar*str=asdd;nint*pi=&i;nshort*ps=NULL;ndouble*pd=newdouble(123.3);ndeletestr;/危险!危险!str指向的不是动态对象指向的不是动态对象ndeletepi;/危险!危险!pi指向的对象指向的对象i是一个局部对象是一个局部对象ndeleteps;/安全!安全!ps指向指向NULLndeletepd;/安全!安全!pd指向一个动

31、态分配的对象指向一个动态分配的对象n25常见错误常见错误n1忘记了释放内存,造成内存泄露。忘记了释放内存,造成内存泄露。含有这种错误的函数每被调用一次就丢失一块内存。刚开始时含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存泄漏统出现提示:内存泄漏(memoryleak)。函数体内的局部变量在函数结束时自动消亡。例如函数体内的局部变量在函数结束时自动消亡。例如p是局部的指是局部的指针变量,它消亡的时候会让它所指的动态内存一起消亡。这是针变量,它消亡的时候会让它所指的动

32、态内存一起消亡。这是错觉!错觉!voidFunc(void)char*p=(char*)malloc(100);/动态内存会自动释放吗?动态内存会自动释放吗?指针消亡了,并不表示它所指的内存会被自动释放指针消亡了,并不表示它所指的内存会被自动释放n2对同一内存区应用了多次对同一内存区应用了多次delete表达式。这通常发生在多个表达式。这通常发生在多个指针指向同一个动态分配对象的时候。若多个指针指向同一对指针指向同一个动态分配对象的时候。若多个指针指向同一对象,当通过某一个指针释放该对象时就会发生这种情况。象,当通过某一个指针释放该对象时就会发生这种情况。n3内存被释放了,并不表示指针会消亡或

33、者成了内存被释放了,并不表示指针会消亡或者成了NULL指针,指针,形成野指针形成野指针26野指针野指针n因此,当程序执行因此,当程序执行deletepi;语句时,语句时,pi指向对象的内存被释放,指向对象的内存被释放,但指针但指针pi本身的内存并没有受到本身的内存并没有受到delete表达式的影响。在表达式的影响。在deletepi;之后,之后,pi被称作空悬指针(俗称野指针),即指向无效内存被称作空悬指针(俗称野指针),即指向无效内存的指针。空悬指针是错误的根源,它很难被检测到,如果对它的指针。空悬指针是错误的根源,它很难被检测到,如果对它进行操作将会产生无法预测的结果。一个比较好的办法是在

34、指进行操作将会产生无法预测的结果。一个比较好的办法是在指针所指的对象被释放后,马上将该指针设置为针所指的对象被释放后,马上将该指针设置为NULL,这样可以,这样可以清楚地表明该指针不再指向任何对象清楚地表明该指针不再指向任何对象nchar*p=(char*)malloc(100);strcpy(p,“hello”);free(p);/p所指的内存被释放,但是所指的内存被释放,但是p所指的地址仍然不变所指的地址仍然不变if(p!=NULL)/没有起到防错作用没有起到防错作用strcpy(p,“world”);/出错出错27野指针野指针n指针操作超越了变量的作用范围。这种情况让人防不指针操作超越了

35、变量的作用范围。这种情况让人防不胜防,示例程序如下:胜防,示例程序如下:classApublic:voidFunc(void)cout“FuncofclassA”Func();/p是是“野指针野指针”281.8.3数组的动态分配与释放数组的动态分配与释放nnew表达式也可以在堆中分配数组。在这种情况表达式也可以在堆中分配数组。在这种情况下下new表达式中的类型指示符后面必须有一对方表达式中的类型指示符后面必须有一对方括号,里面的值代表数组的长度,而且该数值括号,里面的值代表数组的长度,而且该数值可以是一个复杂的表达式。可以是一个复杂的表达式。new表达式返回指向表达式返回指向数组第一个元素的指

36、针。数组第一个元素的指针。n动态分配数组只需指定类型和长度动态分配数组只需指定类型和长度格式:格式: new new typesizetypesize; /size /size为数组元素的长为数组元素的长度,可以是任意表达式。度,可以是任意表达式。newnew返回指向新分配返回指向新分配数组的第一个元素的指针。数组的第一个元素的指针。例如:例如: intint *p2; p2=new *p2; p2=new intnintn;qdelete delete 释放释放newnew分配的存储空间分配的存储空间delete p;delete p;/表明表明p指向的是动态存储区的数组,指向的是动态存储区

37、的数组,如果遗漏,编译器无法发现错误如果遗漏,编译器无法发现错误29int*pi=newint(1024);/分配单个分配单个int型的对象,用型的对象,用1024初始初始化化nint*pia=newint1024;/分配一个含有分配一个含有1024个元素的个元素的int型数组,型数组,未被初始化未被初始化30第二节第二节函数函数n参数传递参数传递n函数返回值函数返回值n内联函数内联函数n函数的重载函数的重载312.1参数传递参数传递nC+语言中,函数的参数和返回值的传递方式有语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。在调用三种:值传递、指针传递和引用传递。在调用

38、函数时,对于每一个实参,其类型必须与对应函数时,对于每一个实参,其类型必须与对应的形参类型相同,或可被转换为该形参类型的形参类型相同,或可被转换为该形参类型2.1.1普通的非引用形参普通的非引用形参为传值调用,调用时将实参的值赋给形参,而形参为传值调用,调用时将实参的值赋给形参,而形参的任何改变不会改变实参的任何改变不会改变实参voidswap(intp1,intp2)inttemp=p1;p1=p2;p2=temp;调用时:调用时:inta=5,b=9;swap(a,b);/a、b并没有互换并没有互换32指针形参指针形参n指针传递传递的是地址,函数可以通过指针赋指针传递传递的是地址,函数可以

39、通过指针赋值,修改指针所指向的对象的值值,修改指针所指向的对象的值void void swap(intswap(int *p1, *p1, intint *p2) *p2) intint temptemp=*p1; *p1=*p2; *p2=*p1; *p1=*p2; *p2=temptemp; ; 调用时怎么写?调用时怎么写?q1、若要保护指针指向的对象的值,则形参需定、若要保护指针指向的对象的值,则形参需定义为指向义为指向const对象的指针对象的指针voiduse_ptr(constint*p)在函数体中,不能通过在函数体中,不能通过*p修改修改p指向的对象的值指向的对象的值实参既可以为

40、实参既可以为int*,也可以为也可以为constint*类型类型33n2、如果输入参数采用如果输入参数采用“值传递值传递”,由于,由于函数函数将自动产将自动产生临时变量用于复制该参数,调用结束释放所以该参生临时变量用于复制该参数,调用结束释放所以该参数本来就无需保护,没有必要加数本来就无需保护,没有必要加const修饰。修饰。例如不要例如不要将函数将函数voidFunc1(intx)写成写成voidFunc1(constintx)。同理不要将函数同理不要将函数voidFunc2(Aa)写成写成voidFunc2(constAa)。其中。其中A为用户自定义的数据类型。为用户自定义的数据类型。n对

41、于非内部数据类型的参数而言,象对于非内部数据类型的参数而言,象voidFunc(Aa)这这样声明的函数注定效率比较低。因为函数体内将产生样声明的函数注定效率比较低。因为函数体内将产生A类型的临时对象用于复制参数类型的临时对象用于复制参数a,而临时对象的构造、,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为将函数声明改为voidFunc(A&a),因为,因为“引用传递引用传递”仅借用一下参数的别名而已仅借用一下参数的别名而已342.1.2引用形参引用形参n引用的一个重要作用就是作为引用的一个重要作用就是作为函数函数的参

42、数。的参数。c c语语言中当有大块数据作为参数传递的时候,采用言中当有大块数据作为参数传递的时候,采用的方案往往是的方案往往是指针指针,在,在C+中又增加了一种同样中又增加了一种同样有效率的选择(在某些特殊情况下又是必须的有效率的选择(在某些特殊情况下又是必须的选择),就是引用选择),就是引用void void swap(intswap(int &p1, &p1, intint &p2)/ &p2)/形参形参p1, p2p1, p2都是引用都是引用 intint p; p=p1; p1=p2; p2=p; p; p=p1; p1=p2; p2=p; 当调用该函数,在相应的主调函数的调用点处,直

43、接当调用该函数,在相应的主调函数的调用点处,直接以以变量名变量名作为实参进行调用即可,而不需要实参变作为实参进行调用即可,而不需要实参变量有任何的特殊要求。如:对应上面定义的量有任何的特殊要求。如:对应上面定义的swapswap函函数,相应的主调数,相应的主调函数函数可写为:可写为:35nmain( )main( ) intint a,b; a,b;cincinab; /ab; /输入输入a,ba,b两变量的值两变量的值swap(a,b); /swap(a,b); /直接以变量直接以变量a a和和b b作为实参调用作为实参调用swapswap函数函数 coutcouta b; /a b; /输

44、出结果输出结果 n(1)被调函数的形参就成为原来主调函数中的实参)被调函数的形参就成为原来主调函数中的实参变量或对象的一个变量或对象的一个别名别名来使用,所以在被调函数中对来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调形参变量的操作就是对其相应的目标对象(在主调函函数数中)的操作。中)的操作。(2)使用引用传递函数的参数,在内存中并没有产)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;在传值调用中生实参的副本,它是直接对实参操作;在传值调用中若传递的是对象,将调用拷贝构造若传递的是对象,将调用拷贝构造函数函数。因此,当参。因此,当参数传递的数

45、据较大时,用引用比用一般变量传递参数数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。的效率和所占空间都好。36三种传递方式的比较三种传递方式的比较n值传递值传递voidFunc1(intx)x=x+10;/形参的使用形参的使用intn=0;Func1(n);/调用调用cout“n=”nendl;/n=0n指针传递指针传递voidFunc2(int*x)(*x)=(*x)+10;/形参的使用形参的使用intn=0;/调用调用Func2(&n);cout“n=”nendl;/n=1037n引用传递:引用传递:voidFunc3(int&x)x=x+10;intn=0;Func3

46、(n);cout“n=”nendl;/n=10对比上述三个示例程序,会发现对比上述三个示例程序,会发现“引用传递引用传递”的性质的性质象象“指针传递指针传递”,而书写方式象,而书写方式象“值传递值传递”。实际上。实际上“引用引用”可以做的任何事情可以做的任何事情“指针指针”也都能够做,为也都能够做,为什么还要什么还要“引用引用”这东西?这东西?答案是答案是“用适当的工具做恰如其分的工作用适当的工具做恰如其分的工作”。38const引用引用n若不希望在函数体中通过引用来修改实参,可若不希望在函数体中通过引用来修改实参,可以使用以使用const引用引用intsearch(constT&x)/x为实

47、参的别名,不用重新开辟空间,且不允为实参的别名,不用重新开辟空间,且不允许对许对x进行修改进行修改boolisShorter(conststring&s1,conststring&s2)returns1.size()s2.size();一般将不需要修改的引用形参都定义为一般将不需要修改的引用形参都定义为const引引用,而且可以增强其灵活性。(对于非用,而且可以增强其灵活性。(对于非const形形参,实参不能为参,实参不能为const对象,也不能为字面值常对象,也不能为字面值常量)量)39传递指向指针的引用传递指向指针的引用void void swap(intswap(int* &* &v v

48、1, 1, intint* &* &v v2) 2) /v1是一个引用是一个引用 ,与指向,与指向intint型对象相关联型对象相关联 intint * *v v; ; v v= =v v1; 1; v v1=1=v v2; 2; v v2=2=v v; ; 调用:调用: intint *pa=&a; *pa=&a; intint * *pbpb=&b;=&b; swap(pa,pbswap(pa,pb););402.2函数返回值函数返回值n在系统调用处将函数的返回值放在一个临时空间,在系统调用处将函数的返回值放在一个临时空间,在调用的地方使用该返回值(赋给一个变量或进行在调用的地方使用该返回

49、值(赋给一个变量或进行运算),调用语句执行完,释放临时空间运算),调用语句执行完,释放临时空间n1)返回引用:没有复制返回值,相反返回的是对返回引用:没有复制返回值,相反返回的是对象本身象本身格式:格式:类型标识符类型标识符&函数函数名(形参列表)名(形参列表)函数体体n非非const引用引用Chain&Chain:delete(intk)nconst引用引用:不能更改返回值:不能更改返回值conststring&shorterString(conststring&s1,conststring&s1)returns1.size()s2.size()?s1:s2;一般来说,函数返回的引用是对函数

50、的某个参数的引用,一般来说,函数返回的引用是对函数的某个参数的引用,而这个参数本身也是引用类型。而这个参数本身也是引用类型。41n引用作为返回值,必须遵守以下规则:引用作为返回值,必须遵守以下规则:不能返回局部变量的引用。主要原因是局部变不能返回局部变量的引用。主要原因是局部变量会在量会在函数函数返回后被销毁,因此被返回的引用返回后被销毁,因此被返回的引用就成为了就成为了无所指无所指的引用,程序会进入未知状的引用,程序会进入未知状态。态。conststring&manip(conststring&s)stringret=s;returnret;422.3内联函数内联函数n在在程程序序执执行行函

51、函数数调调用用时时,要要求求在在转转去去前前要要保保护护现现场场保保存存地地址址,转转回回后后先先要要恢恢复复现现场场,并并按按原原来来保保存存地地址址继继续续执执行行。因因此此,函函数数调调用用要要有有一一定定的的时时间间和和空空间间开开销销,影影响响其其效效率率。特特别别是是对对于于一一些些函函数数体体代代码码不不是是很很大大,但但又又频频繁繁地地被被调调用用的的函函数数来来讲讲,解解决决其其效效率率问问题题更更为为重重要要。因因此引入内联此引入内联函数函数。n内联内联函数函数的定义方法的定义方法定义内联函数的方法很简单,只要在函数定义内联函数的方法很简单,只要在函数定义定义的头前加上关键

52、字的头前加上关键字inline即可。即可。43inlineisNumber(charch);/函数函数原型声明原型声明inline即内联函数即内联函数voidmain()charch;cin.get(ch);if(isNumber(ch)cout是数字字符是数字字符 endl;elsecout不是数字字符不是数字字符 =0&ch=0&ch=9)?1:0.该函数在编译该函数在编译时被替代时被替代,这样就避免了频繁调用这样就避免了频繁调用函数函数对栈内存重复开辟对栈内存重复开辟所带来的消耗。但是会增加目标程序代码量,进而增加空所带来的消耗。但是会增加目标程序代码量,进而增加空间开销,可见它是以空间

53、换取时间。间开销,可见它是以空间换取时间。44n说明:说明:n(1)定义内联函数只是一种请求,而不是命令)定义内联函数只是一种请求,而不是命令编译器一定这么做编译器一定这么做n(2)内联函数应该在头文件中定义)内联函数应该在头文件中定义n(3)inline只适合函数体内代码简单的函数使用,只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能直接调用递归并且内联函数本身不能直接调用递归函数函数(自己内部还调用自己的自己内部还调用自己的函数函数)。452.4函数重载函数重载n重载可分为函数重载和运算符重载重载可

54、分为函数重载和运算符重载n函数重载函数重载在在C+程程序序中中,可可以以将将语语义义、功功能能相相似似的的几几个个函函数数用用同同一一个个名名字字表表示示,即即函函数数重重载载。这这样样便便于于记记忆忆,提提高高了了函函数数的的易易用用性性。例例如如函函数数EatBeef,EatFish,EatChicken可可以以用用同同一一个个函函数数名名Eat表表示示,用用不不同同类类型型的的参参数数加加以以区别。区别。voidEatBeef();/可以改为可以改为voidEat(Beef);voidEatFish();/可以改为可以改为voidEat(Fish);voidEatChicken(); /

55、 /改为改为voidEat(Chicken);46n例如:例如:intsum(inta,intb)doublesum(doublea,doubleb)调用重载函数时,编译器会根据实参的类型、个调用重载函数时,编译器会根据实参的类型、个数、顺序去调用相应的函数。数、顺序去调用相应的函数。intsum1=sum(5,6);doublesum2=sum(3.4,5.6);注:仅仅返回类型不同是不行的注:仅仅返回类型不同是不行的47第三节第三节类和对象类和对象n类的定义和实现类的定义和实现n类的静态成员类的静态成员n类的对象的定义和使用类的对象的定义和使用n类的成员函数类的成员函数n构造函数与析构函数

56、构造函数与析构函数48第三节第三节类和对象类和对象n类是一种复杂的数据类型,它是将不同类类是一种复杂的数据类型,它是将不同类型的型的数据数据和与这些数据相关的和与这些数据相关的操作操作封装在封装在一起的集合体。这有点像一起的集合体。这有点像C语言语言中的结构,中的结构,唯一不同的就是结构没有定义所说的唯一不同的就是结构没有定义所说的“数数据相关的操作据相关的操作”,即,即“方法方法” n封装n外部用户只能看到定义抽象行为的操作集合外部用户只能看到定义抽象行为的操作集合n程序员通过数据变量维护对象内部的状态程序员通过数据变量维护对象内部的状态493.1类的定义和实现类的定义和实现n类的定义一般地

57、分为说明部分和实现部分。类的定义一般地分为说明部分和实现部分。说明部分说明部分是用来说明该类中的成员,包含数据成员是用来说明该类中的成员,包含数据成员的说明和成员函数的说明。的说明和成员函数的说明。“干什么干什么”实现部分实现部分是用来对成员是用来对成员函数函数的定义。的定义。“怎么干怎么干”。n类的一般定义格式如下:类的一般定义格式如下:nclass类名类名/class是定义类的关键字,是定义类的关键字,是种标识是种标识符,通常用字母符,通常用字母C开头开头npublic:公有成员公有成员函数函数或数据成员的说明或数据成员的说明private:私有数据成员或成员私有数据成员或成员函数函数的说

58、明的说明;/注意:分号不可缺注意:分号不可缺50类的定义类的定义n访问权限修饰符或访问控制修饰符:公有的访问权限修饰符或访问控制修饰符:公有的(public)、私有的私有的(private)和保护的和保护的(protected)npublic:公有部分往往是一些操作公有部分往往是一些操作(成员函数成员函数),它,它是提供给用户的是提供给用户的接口接口功能。这部分成员不仅可以在功能。这部分成员不仅可以在类内被访问,也可以在类外被访问。类内被访问,也可以在类外被访问。nprivate:私有部分通常是一些数据成员,描述该类私有部分通常是一些数据成员,描述该类中的对象的中的对象的属性属性,用户是无法访

59、问它们的,只有成,用户是无法访问它们的,只有成员函数或经特殊说明的员函数或经特殊说明的函数函数才可以引用它们,它们才可以引用它们,它们是被用来隐藏的部分。是被用来隐藏的部分。nprotected:可以被本类及其继承类使用可以被本类及其继承类使用51一个日期类定义的例子一个日期类定义的例子/类的说明部分,一般放在类的说明部分,一般放在date.h文件中文件中classCdatepublic:voidsetDate(inty,intm,intd);intIsLeapYear();voidPrint();private:intyear,month,day;/类的实现部分类的实现部分一般放在一般放在d

60、ate.cpp文件中文件中#include“date.h”voidCdate:setDate(inty,intm,intd)这里出现的作用域运算符这里出现的作用域运算符:year=y;month=m;day=d;是用来标识某个成员是用来标识某个成员函数函数是属于哪是属于哪intCdate:IsLeapYear()个类个类,如果成员函数定义在类体外如果成员函数定义在类体外要使用作用域运算符要使用作用域运算符:return(year%4=0&year%100!=0)|(year%400=0);voidCdate:Print();coutyearmontbdayendl;52类的定义说明类的定义说明

61、n定义类时应注意的事项定义类时应注意的事项1)在类体中不允许对所定义的数据成员进行初始)在类体中不允许对所定义的数据成员进行初始化。化。2)一般地,在类体内先说明公有成员,它们是用)一般地,在类体内先说明公有成员,它们是用户所关心的,后说明私有成员,它们是用户不户所关心的,后说明私有成员,它们是用户不感兴趣的。在说明数据成员时,一般按数据成感兴趣的。在说明数据成员时,一般按数据成员的类型大小,由小至大说明,这样可提高时员的类型大小,由小至大说明,这样可提高时空利用率。空利用率。3)习惯地将类定义的说明部分放在头文件中)习惯地将类定义的说明部分放在头文件中.h。而实现部分放在而实现部分放在.cp

62、p文件中文件中533.1.2类的实现类的实现n类成员函数的实现部分一般放在类成员函数的实现部分一般放在.cpp文件中,文件中,如果要在类外使用类中的实例变量或者在类外如果要在类外使用类中的实例变量或者在类外定义函数体,要使用作用域运算符,用来标识定义函数体,要使用作用域运算符,用来标识某个成员是属于哪个类的。某个成员是属于哪个类的。n格式格式返回值类型返回值类型类名类名:函数名(参数列表)函数名(参数列表)例如:例如:voidCdate:setDate(inty,intm,intd)year=y;month=m;day=d;54作用域运算符作用域运算符classPlayingCardpubli

63、c:enumSuitsSpade,Diamond,Club,Heart;Suitssuit()returnsuitValue;intrank()returnrankValue;private:SuitssuitValue;intrankValue;booleanfaceUp;;如:在类定义之外使用表示花色的常量如:在类定义之外使用表示花色的常量if(aCard.suit()=PlayingCard:Diamond)553.2类的静态成员类的静态成员n被一个类的所有实例共享的公共数据成员。静态成员被一个类的所有实例共享的公共数据成员。静态成员(static)可以实现同一个类的多个对象之间的数据共

64、享。可以实现同一个类的多个对象之间的数据共享。静态数据成员对于类的所有对象只开辟了一块内存空间(在静静态数据成员对于类的所有对象只开辟了一块内存空间(在静态存储区),其中一个对象对其改变,保证所有对象存取的是态存储区),其中一个对象对其改变,保证所有对象存取的是更新后的相同的值。更新后的相同的值。n静态数据成员的使用方法和注意事项如下:静态数据成员的使用方法和注意事项如下:1、定义定义:静态数据成员在定义或说明时前面加关键字:静态数据成员在定义或说明时前面加关键字static。2、初始化初始化:格式如下:格式如下::=:u初始化在类体外进行,前面不加初始化在类体外进行,前面不加static和访

65、问权限控制符和访问权限控制符private,public等。等。u初始化时使用作用域运算符来标明它所属类,因为静态数据成员是类的初始化时使用作用域运算符来标明它所属类,因为静态数据成员是类的成员,而不是对象的成员。成员,而不是对象的成员。u静态数据成员是静态存储的,必须对它进行初始化。静态数据成员是静态存储的,必须对它进行初始化。3、引用引用:56nclassMyclasspublic:Myclass(inta,intb,intc);voidGetNumber();voidGetSum();private:intA,B,C;staticintSum;n实现实现intMyclass:Sum=0;

66、Myclass:Myclass(inta,intb,intc)A=a;B=b;C=c;Sum+=A+B+C;n调用调用MyclassM(3,7,10),N(14,9,11);573.3 类的对象的定义和使用u对象的定义对象的定义有两种方法有两种方法1.class类名类名对象名对象名;2.也可以先声明类,然后定义类的对象也可以先声明类,然后定义类的对象类名类名对象名;对象名;编译系统为对象分配内存并且将这段内存空间与对象名称进行编译系统为对象分配内存并且将这段内存空间与对象名称进行绑定,并进行必要的初始化操作绑定,并进行必要的初始化操作.变量声明只是创建了一个标识变量的名称,大部分面向对象语变量

67、声明只是创建了一个标识变量的名称,大部分面向对象语言通过言通过new操作符来创建对象,操作符来创建对象,c+语言可以将他们结合起来语言可以将他们结合起来如如PlayingCardaCard;/在在java中必须写成中必须写成PlayingCardaCard=newPlayingCard();58n使用对象使用对象:在调用成员函数时,必须指明:在调用成员函数时,必须指明对类的哪个对象进行操作。对类的哪个对象进行操作。利用成员运算利用成员运算符符.表明从属关系表明从属关系,用于类的对象用于类的对象例如:例如:Cdatedate,*p;date.setDate(2007,7,20);p=&date;

68、p-IsLeapYear();/等价于等价于date.IsLeapYear();593.4类的成员函数类的成员函数n成员函数的定义和声明成员函数的定义和声明:类的所有成员必须在:类的所有成员必须在类定义的类定义的内声明。类的成员函数既可以在类内声明。类的成员函数既可以在类内定义,以可以在类外定义,他可以访问该类内定义,以可以在类外定义,他可以访问该类的的private成员成员如:如:boolsame_isbn(constSalesItem&r)constreturnisbn=r.isbn;没有前缀的没有前缀的isbn指的是用于调用函数的对象的指的是用于调用函数的对象的isbnq调用调用调用函数

69、时,实际是使用对象来调用的调用函数时,实际是使用对象来调用的if(total.same_isbn(trans)/r是是trans的引的引用用60this指针指针n每个成员函数都有一个额外的、隐含的形参将每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象绑定在一起,该成员函数与调用该函数的类对象绑定在一起,这个形参为这个形参为this。调用成员函数时,形参调用成员函数时,形参this初初始化为调用函数的对象的地址。所以始化为调用函数的对象的地址。所以total.same_isbn(trans)可以理解为可以理解为same_isbn(&total,trans)/第一个参数为隐含

70、的,第一个参数为隐含的,它传递的是调用对象的地址它传递的是调用对象的地址n使用使用this在函数体中可以显式地使用在函数体中可以显式地使用boolsame_isbn(constSalesItem&r)constreturnthis-isbn=r.isbn;61当成员函数中函数参数与数据成员同名时,需当成员函数中函数参数与数据成员同名时,需要要this指针指针classPlayingCardPlayingCard(intsuit,intrank)this-rank=rank;this-suit=suit;this-faceUp=true;62const成员函数成员函数n使用使用const关键字进

71、行说明的成员函数为常成员函数。关键字进行说明的成员函数为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使只有常成员函数才有资格操作常量或常对象,没有使用用const关键字说明的成员函数不能用来操作常对象。关键字说明的成员函数不能用来操作常对象。n说明格式如下:说明格式如下:类型说明符类型说明符 函数函数名名(参数表参数表)const;其中,其中,const是加在函数说明后面的类型修饰符,它是是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也函数类型的一个组成部分,因此,在函数实现部分也要带要带const关键字。关键字。n任何不会修改数据成员的函数都应该

72、声明为任何不会修改数据成员的函数都应该声明为const类型。类型。如果在编写如果在编写const成员函数时,不慎修改了数据成员,成员函数时,不慎修改了数据成员,或者调用了其它非或者调用了其它非const成员成员函数函数,编译器将指出错误,编译器将指出错误,这无疑会提高程序的健壮性。这无疑会提高程序的健壮性。63class Stackclass Stack public:public:nvoid void Push(intPush(int elemelem););nintint Pop(void); Pop(void);nintint GetCount(voidGetCount(void) co

73、nst; / const) const; / const成员成员函数函数private:private:nintint m_num; m_num;nintint m_data100; m_data100;intint Stack:GetCount(voidStack:GetCount(void) const) const + m_num; / + m_num; / 编译错误,企图修改数据成员编译错误,企图修改数据成员m_numm_numnPop(); / Pop(); / 编译错误,企图调用非编译错误,企图调用非constconst函数函数nreturn m_num;return m_num;

74、 643.5 构造函数与析构函数n概述概述n构造函数的初始化表构造函数的初始化表n缺省的构造函数缺省的构造函数n缺省的拷贝构造函数缺省的拷贝构造函数n运算符重载运算符重载n缺省的赋值函数缺省的赋值函数n析构函数析构函数653.5.1概述概述n每个类只有一个每个类只有一个析构函数析构函数和一个和一个赋值函数赋值函数,但,但可以有多个可以有多个构造函数构造函数(包含一个拷贝构造函数,(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述如果不想编写上述函数函数,C+编译器将自动为编译器将自动为A产生四个缺省的产生四个缺省的函数函

75、数,如,如nA();/缺省的无参数构造缺省的无参数构造函数函数nA(constA&a);/缺省的拷贝构造缺省的拷贝构造函数函数nA();/缺省的析构缺省的析构函数函数nA&operate=(constA&a);/缺省的赋值缺省的赋值函数函数66n作用作用:把对象的初始化工作放在构造函数中,把清除:把对象的初始化工作放在构造函数中,把清除工作放在析构函数中。当对象被创建时,构造函数被工作放在析构函数中。当对象被创建时,构造函数被自动执行。当对象消亡时,析构自动执行。当对象消亡时,析构函数函数被自动执行。这被自动执行。这样可以自动执行对象的初始化和清除工作。样可以自动执行对象的初始化和清除工作。n

76、命名命名:构造函数、析构函数与类同名,由于析构函数:构造函数、析构函数与类同名,由于析构函数的目的与构造的目的与构造函数函数的相反,就加前缀的相反,就加前缀以示区别。以示区别。n构造、析构函数构造、析构函数没有返回值没有返回值,即使是,即使是void也不可以。也不可以。n构造、析构函数置于类定义的构造、析构函数置于类定义的public部分部分n析构函数析构函数不得带有任何参数不得带有任何参数n构造函数构造函数可以有一个或多个,一旦定义一个有参的构可以有一个或多个,一旦定义一个有参的构造函数,一定要显式定义一个无参构造函数造函数,一定要显式定义一个无参构造函数673.5.2构造构造函数函数的初始

77、化表的初始化表v和其他函数不同,构造函数有个特殊的初始化和其他函数不同,构造函数有个特殊的初始化方式叫方式叫“初始化表达式表初始化表达式表”(初始化表)。初(初始化表)。初始化表位于函数参数表之后,以冒号开始,后始化表位于函数参数表之后,以冒号开始,后面接数据成员列表,列表之间用逗号分隔,每面接数据成员列表,列表之间用逗号分隔,每一个变量后面用一对圆括号包含它的初始值一个变量后面用一对圆括号包含它的初始值。v初始化表放在构造函数的定义中初始化表放在构造函数的定义中PlayingCard:PlayingCard(Suitsis,intir):suit(is),rank(ir),faceUp(tr

78、ue)/函数体函数体 68(2)执行顺序执行顺序n首先执行初始化列表,然后执行该构造函数函数体中的语句首先执行初始化列表,然后执行该构造函数函数体中的语句n成员对象初始化的次序完全不受它们在初始化表中次序的影响,成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构

79、函数无法得到唯一的逆序。导致析构函数无法得到唯一的逆序。例例classxinti;intj;public:x(intval):j(val),i(j)/相当于相当于i=j;j=val;j还没有初值就被使用还没有初值就被使用;所以尽量避免使用成员来初始化其他成员所以尽量避免使用成员来初始化其他成员x(intval):j(val),i(val)69const数据成员数据成员nconst数据成员只在某个对象生存期内是常量,而对于整个类而数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员

80、的值可以不同。数据成员的值可以不同。不能在类声明中初始化不能在类声明中初始化const数据成员。以下用法是错误的,因数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道为类的对象未被创建时,编译器不知道SIZE的值是什么。的值是什么。classAconstintSIZE=100;/错误,企图在类声明中初始化错误,企图在类声明中初始化const数据成员数据成员intarraySIZE;/错误,未知的错误,未知的SIZE;nconst数据成员的初始化只能在类构造函数的初始化表中进行,数据成员的初始化只能在类构造函数的初始化表中进行,类的定义类的定义classAA(intsize);/构

81、造函数构造函数constintSIZE;类的实现类的实现A:A(intsize):SIZE(size)/构造函数的初始化表构造函数的初始化表n生成类的对象生成类的对象Aa(100);/对象对象a的的SIZE值为值为100Ab(200);/对象对象b的的SIZE值为值为20070(3)使用说明)使用说明大多数成员可以在构造函数的初始化表中初始化,也可以大多数成员可以在构造函数的初始化表中初始化,也可以在函数体中赋值。但是在函数体中赋值。但是n 类的类的constconst常量和引用成员只能在初始化表里被初始常量和引用成员只能在初始化表里被初始化,因为不能对他们赋值,所以要在执行构造函数的化,因为

82、不能对他们赋值,所以要在执行构造函数的函数体之前完成初始化。函数体之前完成初始化。classConstRefpublic:ConstRef(intii);private:inti;constintci;int&ri;71ConstRef:ConstRef(intii)i=ii;ci=ii;/不能对常量赋值不能对常量赋值ri=i;/此时是对此时是对ri所绑定的对象赋值,但是还不所绑定的对象赋值,但是还不知道知道ri是谁的别名是谁的别名ConstRef:ConstRef(intii):i(ii),ci(ii),ri(i)723.5.3缺省的构造函数缺省的构造函数n格式:格式:A:A()n当类没有定

83、义构造函数时,编译器才会自动生成一个当类没有定义构造函数时,编译器才会自动生成一个不带参数的缺省构造函数不带参数的缺省构造函数n在缺省构造函数中,对于类中的成员,若为类类型,在缺省构造函数中,对于类中的成员,若为类类型,调用默认构造函数来初始化;若为内置或复合类型调用默认构造函数来初始化;若为内置或复合类型(指针、数组),若对象定义在局部作用域中,必须(指针、数组),若对象定义在局部作用域中,必须对它赋初值对它赋初值n使用缺省构造函数创建对象使用缺省构造函数创建对象Complexc;注:注:Complexc();是错误的。编译器把他解释为一个函是错误的。编译器把他解释为一个函数的声明。数的声明

84、。c是函数名,返回类型为是函数名,返回类型为Complexq一旦定义一个有参的构造函数,一定要显式的定义一一旦定义一个有参的构造函数,一定要显式的定义一个无参构造函数个无参构造函数73Complex:Complex(double r , double i)Complex:Complex(double r , double i) real=r; image=i; real=r; image=i;Complex:Complex(double r)Complex:Complex(double r) real=r; image=0; real=r; image=0;Complex:Complex()

85、real=0; image=0;Complex:Complex() real=0; image=0; coutcout“Initializing 0 0Initializing 0 0”endlendl; ; q调用方式:调用方式:q创建对象时自动调用缺省的构造函数创建对象时自动调用缺省的构造函数nComplex c1;Complex c1;nComplexComplex*pc1=newComplexComplex;/注意,调用缺省构造函数时,没有括号注意,调用缺省构造函数时,没有括号n调用有参构造函数调用有参构造函数ComplexComplex c2c2( (4.54.5) ); ; 或或

86、Complex c2=new Complex(4.5); /Complex c2=new Complex(4.5); /一个参数一个参数Complexc3( (4.5,6.2) ); 或或Complexc3=newComplex(4.5,6.2);743.5.4缺省的拷贝缺省的拷贝(复制复制)构造函数构造函数n格式格式: : A(const A &a) A(const A &a) 他只有一个参数,且是所在类的对象的引用他只有一个参数,且是所在类的对象的引用n作作用用:利利用用编编译译器器提提供供的的拷拷贝贝构构造造函函数数创创建建新新的的对对象象,将将形形参参对对象象中中的的成成员员逐逐个个的

87、的拷拷贝贝到到新新的对象中。即新对象初始化为原对象的副本的对象中。即新对象初始化为原对象的副本 CpointCpoint: : Cpoint(constCpoint(const CpointCpoint & &o objbj) ) x= x=obj.xobj.x; y=; y=obj.yobj.y;若已经定义并初始化了若已经定义并初始化了CpointCpoint对象对象c1,c1,则可以定义则可以定义CpointCpoint c2=c1; c2=c1;相当于调用相当于调用c2.Cpoint(c1);c2.Cpoint(c1);75n在类中,初始化对象有两种方式在类中,初始化对象有两种方式n直接

88、初始化:用括弧直接初始化:用括弧直接调用与实参匹配的构造函数。如:直接调用与实参匹配的构造函数。如:stringdots(10,.);stringempty;n复制初始化复制初始化:用等号用等号首先使用指定构造函数创建一个临时对首先使用指定构造函数创建一个临时对象,然后使用复制构造函数将那个临时对象复制到正在创建象,然后使用复制构造函数将那个临时对象复制到正在创建的对象。的对象。stringempty_copy=string();n复制构造函数的应用场合复制构造函数的应用场合n定义一个对象并用一个同类型对象初始化时定义一个对象并用一个同类型对象初始化时n类的对象作为实参传递给形参时类的对象作为

89、实参传递给形参时n从函数返回时复制一个对象从函数返回时复制一个对象n即使是定义了其他的构造函数,这个缺省的复制构造即使是定义了其他的构造函数,这个缺省的复制构造函数也是存在的函数也是存在的763.5.5运算符重载运算符重载n对于基本数据类型,有许多相应的基本运算,如算术对于基本数据类型,有许多相应的基本运算,如算术运算的运算的+、-、*、/,但是对用户自定义类型,却不可以,但是对用户自定义类型,却不可以用这些运算符进行运算,为了使类类型也能使用这些用这些运算符进行运算,为了使类类型也能使用这些运算符,必须对这些运算符进行重载运算符,必须对这些运算符进行重载n重载操作符是具有特殊名称的函数,在重

90、载操作符是具有特殊名称的函数,在C+语言中,可语言中,可以用关键字以用关键字operator加上运算符来表示加上运算符来表示函数名函数名,叫做,叫做运算符重载。运算符重载。n遵循的规则:遵循的规则:*不能改变原运算符操作数的个数不能改变原运算符操作数的个数+*不能改变原运算符的自然含义不能改变原运算符的自然含义 *重载操作符必须具有至少一个类类型或枚举类型的操作数,重载操作符必须具有至少一个类类型或枚举类型的操作数,既不能对内置类型对象的操作符重载。既不能对内置类型对象的操作符重载。不能重载的运算符:成员运算符不能重载的运算符:成员运算符.作用域运算符作用域运算符:条件运算符条件运算符?:?:

91、一元运算符一元运算符*77如如果果运运算算符符被被重重载载为为类类的的成成员员函函数数,那那么么一一元元运运算算符符没没有有参参数数,二二元元运运算算符符只只有有一一个个参参数数,因因为为对对象象自自己己成成了了左左侧侧参参数数。从从语语法法上上讲讲,运运算算符符既既可可以以定定义义为为友友元元函数,也可以定义为成员函数,也可以定义为成员函数函数。n作为类的成员函数重载的格式:作为类的成员函数重载的格式:一元运算符一元运算符 返回值类型返回值类型类名类名:operator运算符()运算符() /operator运算符作为一个整体作为函数名运算符作为一个整体作为函数名二元运算符二元运算符 返回值

92、类型返回值类型类名类名:operator运算符(参数说明)运算符(参数说明)/参数说明只允许有一个参数,即右操作数参数说明只允许有一个参数,即右操作数78例如:对复数进行相加,通过例如:对复数进行相加,通过+ +号能直接实现号能直接实现类内声明类内声明:Complexoperator+(constComplex&a);类外定义类外定义:ComplexComplex:operator+(constComplex&a)nComplextemp(real+a.real,image+a.image);nreturntemp;n使用重载操作符使用重载操作符:与内置类型使用操作符的方式一样:与内置类型使用

93、操作符的方式一样intmain()Complexc1(1.0,1.0),C2(2.0,2.0),C;C=C1+C2;/C+编译器把他解释为编译器把他解释为C=C1.operator+(c2);793.5.6缺省的赋值函数缺省的赋值函数n格式格式:A&operator=(constA&a)n为成员函数,包含隐式的为成员函数,包含隐式的this形参,第一个形参为对应的左形参,第一个形参为对应的左操作数,第二个形参为对应的右操作数,右操作数一般为操作数,第二个形参为对应的右操作数,右操作数一般为const引用传递引用传递。n返回值为左操作数的引用返回值为左操作数的引用n通过重载通过重载=,可以对对象

94、整体赋值,可以对对象整体赋值缺省的赋值函数:缺省的赋值函数:右操作数对象的每个成员赋值给左操作数对象的每个成右操作数对象的每个成员赋值给左操作数对象的每个成员员,返回左操作数对象的引用返回左操作数对象的引用Complex&Complex:operator=(constComplex&a)nnreal=a.real;image=a.image;nreturn*this;n80拷贝构造函数与赋值函数的比较拷贝构造函数与赋值函数的比较n拷贝构造函数和赋值函数非常容易混淆,常导拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被致错写、错用。拷贝构造函数是在对象被创建创建时调用

95、的,而赋值函数只能时调用的,而赋值函数只能被已经存在了的对被已经存在了的对象象调用。调用。nString a(String a(“hellohello”););nString b(String b(“worldworld”););nString c = a; String c = a; n/ / 调用了拷贝构造调用了拷贝构造函数,最好写成,最好写成 c(a);c(a);nc = b; / c = b; / 调用了赋值调用了赋值函数81赋值和复制赋值和复制n内存分配方法影响赋值的含义:内存分配方法影响赋值的含义:1.复制语义复制语义:c+中使用,实际是调用的赋值函数,中使用,实际是调用的赋值函数

96、,右侧变量值赋给左侧变量,两个变量占用不同右侧变量值赋给左侧变量,两个变量占用不同存储空间,值是独立的存储空间,值是独立的2.指针语义指针语义:同一同一(Java中使用中使用)左侧变量的地址左侧变量的地址赋给了右侧变量的地址,两个变量是共用同一赋给了右侧变量的地址,两个变量是共用同一地址,其中一个变量值的改变会改变另一个变地址,其中一个变量值的改变会改变另一个变量量823.5.8析构函数析构函数n在对象脱离其作用域时,系统自动执行析构函数,作在对象脱离其作用域时,系统自动执行析构函数,作善后清理工作,主要用于资源回收善后清理工作,主要用于资源回收n析构函数与类同名,无参数,无返回值。只是在函数

97、析构函数与类同名,无参数,无返回值。只是在函数名前加名前加例:例:Complex:Complex()n编译器总会自动生成一个析构函数,他按成员在类中编译器总会自动生成一个析构函数,他按成员在类中的声明次序逆序撤销成员。默认析构函数释放对象生的声明次序逆序撤销成员。默认析构函数释放对象生命期内或构造函数中获取的资源。命期内或构造函数中获取的资源。n注:动态分配的对象只有在指向该对象的指针被删除注:动态分配的对象只有在指向该对象的指针被删除时才会撤销,若没有删除,不会运行该对象的析构函时才会撤销,若没有删除,不会运行该对象的析构函数,对象一直存在,会导致内存泄漏数,对象一直存在,会导致内存泄漏n当

98、对象的引用或指针超出作用域时,不会运行析构函当对象的引用或指针超出作用域时,不会运行析构函数。当删除指向动态分配对象的指针或实际对象超出数。当删除指向动态分配对象的指针或实际对象超出作用域时,才会运行析构函数作用域时,才会运行析构函数83第四节第四节友元和重载友元和重载n友元友元n友元函数友元函数n友元类友元类n友元关系与继承友元关系与继承n重载与友元重载与友元n重载重载n重载输出运算符重载输出运算符n重载输入运算符重载输入运算符n转换与类类型转换与类类型844.1 4.1 友元友元n我我们们已已知知道道类类具具有有封封装装和和信信息息隐隐藏藏的的特特性性。只只有有类类的的成成员员函函数数才才

99、能能访访问问类类的的私私有有成成员员。非非成成员员函函数数可可以以访访问问类类中中的的公公有有成成员员,但但是是如如果果将将数数据据成成员员都都定定义义为为公公有有的的,这这又又破破坏坏了了隐隐藏藏的的特特性性。另另外外,应应该该看看到到在在某某些些情情况况下下,特特别别是是在在对对某某些些成成员员函函数数多多次次调调用用时时,由由于于参参数数传传递递,类类型型检检查查和和安安全全性性检检查查等等都都需需要要时时间间开开销,而影响程序的运行效率。销,而影响程序的运行效率。n为了解决上述问题,提出一种使用友元的方案。友元为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的是一种定义

100、在类外部的普通函数普通函数,但它需要在类体内,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字时前面加以关键字friend。友元不是成员函数,但是它友元不是成员函数,但是它可以访问类中的私有成员可以访问类中的私有成员。友元的作用在于提高程序。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员使得非成员函数可以访问类的私有成员。可以访问类的私有成员。854.1.1友元函数友元函数n友元可以是一个函数,该函数被称为友元友元可以是一个函数,该函数被称为

101、友元函数函数;友元也可以是一个类,该类被称为友元类。友元也可以是一个类,该类被称为友元类。1、友元、友元函数函数友元函数的特点是他是能够访问类中的私友元函数的特点是他是能够访问类中的私有成员的非成员函数。友元函数从语法上看,有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普它与普通函数一样,即在定义上和调用上与普通函数一样。下面举一例子说明友元通函数一样。下面举一例子说明友元函数的应的应用。用。86n#includeusingnamespacestd;classPointpublic:Point(doublexx,doubleyy)x=xx;y=yy;voidGe

102、txy();frienddoubleDistance(Point&a,Point&b);/不是不是Point的成员函数而是友元函数,他不受类的访问控制的影响的成员函数而是友元函数,他不受类的访问控制的影响private:doublex,y;voidPoint:Getxy()cout(x,y)endl;doubleDistance(Point&a,Point&b)doubledx=a.x-b.x;/可以调用类的私有成员可以调用类的私有成员doubledy=a.y-b.y;returnsqrt(dx*dx+dy*dy);87nvoidmain()Pointp1(3.0,4.0),p2(6.0,8.

103、0);p1.Getxy();p2.Getxy();doubled=Distance(p1,p2);/友元函数被调用时与普通函数一样友元函数被调用时与普通函数一样coutDistanceisd只能重载为成员只能重载为成员函数函数二元操作符二元操作符+=-=/=*=&=|=%=建议重载为类成员建议重载为类成员函函数数所有其它运算符建议重载为友元所有其它运算符建议重载为友元函数函数914.2重载与输入输出操作符重载与输入输出操作符n支持支持I/O操作的类所提供的操作的类所提供的I/O操作接口,一般应操作接口,一般应该与标准库该与标准库iostream为内置类型定义的接口相同。为内置类型定义的接口相同

104、。因此,许多类都需要重载输入输出操作符因此,许多类都需要重载输入输出操作符n输出流库中对左移操作符的重载输出流库中对左移操作符的重载ostream&operator(ostream&des,constintsource);ostream&operator(ostream&des,constshortsource);ostream&operator(ostream&des,constlongsource);ostream&operator(ostream&des,constcharsource);ostream&operator(ostream&des,constchar*source);924

105、.2.1输出操作符的重载输出操作符的重载ostream&operator(ostream&des,constcharsource);v为了与为了与i/o标准库一致,操作符应接受标准库一致,操作符应接受ostream&作为第一作为第一个形参,该形参是一个引用,因为不能复制个形参,该形参是一个引用,因为不能复制ostream对象对象v对类类型对类类型const对象的引用作为第二个形参,这样避免复对象的引用作为第二个形参,这样避免复制实参,且输出一般不会改变对象。制实参,且输出一般不会改变对象。v返回对返回对ostream形参的引用形参的引用ostream&operator(ostream&os,c

106、onstClassType&object)os;returnos;934.2.1输出操作符的重载输出操作符的重载nIO操作符必须为非成员函数操作符必须为非成员函数如果如果为类的成员,则左操作数只能是该类的对象。但为类的成员,则左操作数只能是该类的对象。但是,我们的左操作数必须为是,我们的左操作数必须为ostream类型,类型,ostream类是类是标准库的组成部分,我们不能为标准库的类增加成员。标准库的组成部分,我们不能为标准库的类增加成员。所以,如果想使用该操作符,只能把它定义为非成员所以,如果想使用该操作符,只能把它定义为非成员函数,且他是某个使用它的类的友元函数,且他是某个使用它的类的友

107、元classNodefriendostream&operator(ostream&,constNode&);private:intscore;char*name;ostream&operator(ostream&out,constNode&x)outx.scorex.name0(istream&in,Node&x)inx.score;returnin;n与输出操作符不同,输入操作符必须处理读操与输出操作符不同,输入操作符必须处理读操作因为提供的值不正确而失败的错误和文件结作因为提供的值不正确而失败的错误和文件结束的可能性束的可能性nif(in)elsex=Node();954.2.3转换与类类

108、型转换与类类型nclass class SmallIntSmallInt n friend friend operator+(constoperator+(const SmallIntSmallInt &, &, intint);); friend operator-(const friend operator-(const SmallIntSmallInt &, &, intint);); friend friend operator+(intoperator+(int ,const ,const SmallIntSmallInt &); &); friend operator-( frie

109、nd operator-(intint ,const ,const SmallIntSmallInt &); &); public: public: SmallInt(intSmallInt(int ival):value(ivalival):value(ival) operator+(constoperator+(const SmallIntSmallInt &); &); operator-(const operator-(const SmallIntSmallInt &); &); private: private: intint value; value;n一个类可以定义自己的转换,应

110、用于它的对象一个类可以定义自己的转换,应用于它的对象例如:可以定义一个从例如:可以定义一个从SmallIntSmallInt到到intint类型的转换。如果定义了该类型的转换。如果定义了该转换,则无须再重载操作符。给定到转换,则无须再重载操作符。给定到intint的转换,的转换,SmallIntSmallInt对象对象可以用在任何可用可以用在任何可用intint值的地方值的地方96格式:格式:operatortype();vtype可以是内置类型名、类名等,但是,一般不可以是内置类型名、类名等,但是,一般不允许转换为数组或函数类型,转换为指针和引允许转换为数组或函数类型,转换为指针和引用类型是

111、可以的用类型是可以的v转换操作符是一种特殊的类转换操作符是一种特殊的类成员函数成员函数。它的功。它的功能是将类类型的值转换为其他类型的值。能是将类类型的值转换为其他类型的值。v转换操作符在类定义体内声明,在转换操作符在类定义体内声明,在operator后跟后跟转换的目标类型转换的目标类型v不能指定返回类型,形参表必须为空不能指定返回类型,形参表必须为空97classSmallIntfriendostream&operator(istream&os,SmallInt&)returnoss.value;public: SmallInt(intSmallInt(int ivalival=0):val

112、ue(ival) =0):value(ival) operatorint()constreturnvalue;/将将SmallInt类型转换为类型转换为int类型类型private:private: intint value; value;转换函数一般不应该改变被转换的对象,所以转换操转换函数一般不应该改变被转换的对象,所以转换操作符通常应被定义为作符通常应被定义为const成员成员98该转换是用户为类类型对象自定义的转换,但是调该转换是用户为类类型对象自定义的转换,但是调用时是由系统隐式调用用时是由系统隐式调用SmallIntsi1,si2;intmain()输入输入si1、si2的值;的值

113、;cout127)?”greaterthan”:(si1127)?”lessthan”:”equalto”);(b)?(a):(b)n但是在复杂调用下,它的行为是不可预期的。但是在复杂调用下,它的行为是不可预期的。n例如例如constintsize=10;nintiasize;nintmain()nintelem_cnt=0;nint*p=&ia0;nwhile(min(p+,&iasize)!=&iasize)n+elem_cnt;ncout“elem_cnt:”elem_cnt“texpecting:”endl;nreturn0;nn每次扩展每次扩展p=p+1都应用了两次。与函数不同,函数

114、调用之前确保都应用了两次。与函数不同,函数调用之前确保实参只被计算一次实参只被计算一次102函数模板函数模板n函数模板提供一种用来自动生成各种类型函数实例的算函数模板提供一种用来自动生成各种类型函数实例的算法,程序员对于函数接口参数化,而函数体保持不变。法,程序员对于函数接口参数化,而函数体保持不变。n函数模板的声明在关键字函数模板的声明在关键字template后跟随的尖括弧内可后跟随的尖括弧内可以有一个到多个参数。参数不能为空,模板参数可以是以有一个到多个参数。参数不能为空,模板参数可以是模板类型参数,他代表一种类型;也可以模板非类型参模板类型参数,他代表一种类型;也可以模板非类型参数,他代

115、表一个常量表达式。数,他代表一个常量表达式。n可以在某个头文件中定义模板。例如:可以在某个头文件中定义模板。例如:/filemax.h#ifndefMAX_INCLUDED#defineMAX_INCLUDEDtemplateclassTnTmax(Tt1,Tt2)return(t1t2)?t1:t2;/templateclassT为模板前缀,定义和声明都由此开始为模板前缀,定义和声明都由此开始n#endifn函数模板不是一个实在的函数,编译系统无法产生执行代码。函数模板不是一个实在的函数,编译系统无法产生执行代码。classT定义定义T作为模板类型参数。作为模板类型参数。max是函数名,是函

116、数名,t1和和t2是其参数,返回值的类型为是其参数,返回值的类型为T103n模板非类型参数是由一个普通的参数声明构成,模板模板非类型参数是由一个普通的参数声明构成,模板非类型参数的值代表了模板定义中的一个常量非类型参数的值代表了模板定义中的一个常量n例如,例如,size代表代表arr指向的数组的长度指向的数组的长度ntemplatenTypemin(Type(&arr)size)/模板类型参数使用模板类型参数使用nconstintloc_size=size;/非类型参数非类型参数nTypeloc_arrayloc_size;/模板类型参数使用模板类型参数使用nn模板定义的余下部分中模板定义的余

117、下部分中size可以出现在要求常量的地方可以出现在要求常量的地方n模板类型参数作为一个类型指示符,它的使用方式与模板类型参数作为一个类型指示符,它的使用方式与内置或用户自定义的类型完全一样内置或用户自定义的类型完全一样104模板实例化模板实例化n当实例化当实例化max()时(调用),由具体的数据类型替换。时(调用),由具体的数据类型替换。你可以像使用普通的你可以像使用普通的函数函数那样使用那样使用max()。编译器按照编译器按照所使用的数据类型自动产生相应的模板实例:所使用的数据类型自动产生相应的模板实例:nintn=10,m=16;nfloatf1=60.4,f2=23.2;inthighe

118、st=max(n,m);/编译器知道编译器知道n,m为为int型,用模板生成一个具体的函数定义,型,用模板生成一个具体的函数定义,并将并将T换成换成intnfloatfh=max(f1,f2):/发现该调用时,又生成一个定义,将发现该调用时,又生成一个定义,将T换成换成float,成为模成为模板函数板函数ncomplexdoublec1,c2;/.给给c1,c2赋值赋值complexdoublehigher=max(c1,c2);n/complex版本版本类模板类模板105显式模板实参显式模板实参ntemplateTmin(T,T)nunsignedintui;nintmain()nmin(u

119、i,1024);nnmin()的实参必须类型相同,此时实参推演失败,的实参必须类型相同,此时实参推演失败,模板实例化出现错误,可以采用显式模板实参。模板实例化出现错误,可以采用显式模板实参。显式模板实参被指定在逗号分隔的列表中,用显式模板实参被指定在逗号分隔的列表中,用尖括号括起来,紧跟在模板实例名的后面尖括号括起来,紧跟在模板实例名的后面n上例调用时可写成上例调用时可写成min(ui,1024);106模板编译模式模板编译模式n有两种:包含模式和分离模式有两种:包含模式和分离模式n包含模式包含模式n/model1.h中中templateclassTTmin(Tt1,Tt2)return(t1

120、 t2)?t1:t2;在每个使用在每个使用min()实例的文件中都包含该文件实例的文件中都包含该文件/model1.cpp中中#include“model1.h”inti=7,j=5;doubled=min(i,j);缺点:模板体对于用户来说应该是透明的缺点:模板体对于用户来说应该是透明的在多个文件之间编译相同的函数模板定义增加了不在多个文件之间编译相同的函数模板定义增加了不必要的编译时间必要的编译时间107n分离模式:函数模板的声明放在头文件中,而定义放分离模式:函数模板的声明放在头文件中,而定义放在在cpp文件中文件中/model2.h中中templateclassTTmin(Tt1,Tt

121、2);/model2.cpp中中exporttemplateclassT/在生成其他文件使用的模板在生成其他文件使用的模板实例化例化时可能要用到可能要用到这个定个定义Tmin(Tt1,Tt2)return(t1 t2)?t1:t2;/user.cpp中中#include“model2.h”inti=7,j=5;doubled=min(i,j);使用该模式可以很好的将定义和接口分开,但并非所有的编译使用该模式可以很好的将定义和接口分开,但并非所有的编译器都支持该模式器都支持该模式108类模板类模板(1)定义和声明)定义和声明templatenclassArraynnpublic:nArray(i

122、ntn);nT&move();nvoidadd(constT&);nprivate:nT*p;nintn;nintindex;n;n类模板的定义和声明都是以关键字类模板的定义和声明都是以关键字template开头,关键字后面是一个逗号分开头,关键字后面是一个逗号分隔的模板参数表,参数之间用隔的模板参数表,参数之间用括起来。括起来。n当有多个类型参数的时候,每个参数必须以关键字当有多个类型参数的时候,每个参数必须以关键字class开头开头template109n2)类模板中成员函数的实现)类模板中成员函数的实现ntemplatenArray:Array(intn)nn一般形式:一般形式:temp

123、late返回类型返回类型类模板名类模板名:成员函数名(形参)成员函数名(形参)函数体函数体n注:每一个成员函数前都要加模板前缀注:每一个成员函数前都要加模板前缀template,每一个成员函数名前都要加,每一个成员函数名前都要加类模板名类模板名:3)创建类模板的实例)创建类模板的实例n格式:类名格式:类名对象对象Arrayb(10);Arrayc(8.5);110n4)类模板和编译模式)类模板和编译模式n只有当编译器看到了实际的类模板的定义,而只有当编译器看到了实际的类模板的定义,而不仅仅是声明时,它才能实例化类模板,所以不仅仅是声明时,它才能实例化类模板,所以当程序使用一个类模板并要求其实例

124、时,程序当程序使用一个类模板并要求其实例时,程序首先必须提供类模板的定义首先必须提供类模板的定义n而类模板的成员函数与模板本身很相像,编译而类模板的成员函数与模板本身很相像,编译器只有看到成员函数并真正使用时,才实例化器只有看到成员函数并真正使用时,才实例化这个函数。所以一个成员函数被定义在类模板这个函数。所以一个成员函数被定义在类模板定义之外,那么这些定义应该被放在含有该类定义之外,那么这些定义应该被放在含有该类模板定义的头文件中。(包含编译模式)模板定义的头文件中。(包含编译模式)n分离编译模式分离编译模式111p/array.h中中exporttemplate/声明声明Array是一个可

125、导出的类模板是一个可导出的类模板classArray;parray.cpp中中#include“array.h”ntemplatenArray:Array(intn)nn/user.cpp中中#include“array.h”Arraya(5);112第六节第六节继承继承n关于继承的直观描述关于继承的直观描述nc+继承格式继承格式n改写、重定义与遮蔽改写、重定义与遮蔽n接口和抽象类接口和抽象类n继承和构造函数继承和构造函数n虚拟析构函数虚拟析构函数1136.1关于继承的直观描述关于继承的直观描述n继承可以使一个子类的实例存取和它的父类相关的数继承可以使一个子类的实例存取和它的父类相关的数据和行

126、为。也就是说用继承的方法可以自动为一个类据和行为。也就是说用继承的方法可以自动为一个类提供来自另一个类的属性和操作,进而使程序设计人提供来自另一个类的属性和操作,进而使程序设计人员在一个一般的类的基础上很快建立一个新的类,而员在一个一般的类的基础上很快建立一个新的类,而不必从零开始设计每个类。不必从零开始设计每个类。n当一个类被其他的类继承,被继承的类称为基类,又当一个类被其他的类继承,被继承的类称为基类,又称为父类。继承其他类的类称为派生类,又称为子类。称为父类。继承其他类的类称为派生类,又称为子类。n继承的含义继承的含义n子类具有父类的属性和方法(扩展)子类具有父类的属性和方法(扩展)n子

127、类可以声明新的属性和方法,也可以剔出不适用子类可以声明新的属性和方法,也可以剔出不适用其用途的父类操作(收缩)其用途的父类操作(收缩)n继承总是向下传递的,因此一个类可以从它上面的继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性多个超类中继承各种属性。114继承的含义继承的含义例:如果例:如果Dog是是Mammal的派生类,而的派生类,而Mammal又是又是Animal的派生类,则的派生类,则Dog不仅继承了不仅继承了Mammal的属性,的属性,同时也继承了同时也继承了Animal的属性。的属性。n派生类可以覆盖从基类继承来的行为。派生类可以覆盖从基类继承来的行为。n“是一个

128、是一个”检验:检验:检验两个类是否为继承关系时,存在一项规则,这项检验两个类是否为继承关系时,存在一项规则,这项规则称为规则称为“是一个是一个”(is-a)检验。例如,检验。例如,A是是B的子类,的子类,那么那么Ais-aB1156.2c+继承的格式继承的格式n从一个基类派生的继承称为单继承。单继承声明语句从一个基类派生的继承称为单继承。单继承声明语句的常用格式为:的常用格式为:nclass派生类名派生类名:访问控制关键字访问控制关键字基类名基类名数据成员和成员函数声明数据成员和成员函数声明;n从多个基类派生的继承称为多继承或多重继承,也就从多个基类派生的继承称为多继承或多重继承,也就是说,一

129、个派生类有多个直接基类。是说,一个派生类有多个直接基类。nclass派生类名派生类名:访问控制关键字访问控制关键字基类名基类名1,访问控制访问控制关键字关键字基类名基类名2,.数据成员和成员函数声明数据成员和成员函数声明;116继承方式继承方式基类特性基类特性派生类特性派生类特性说明说明公有继承公有继承publicpublicpublic基类中的非私有成员基类中的非私有成员在派生类中的访问属在派生类中的访问属性保持不变性保持不变protectedprotectedprivate不可见不可见保护继承保护继承protectedpublicprotected基类中的非私有成员基类中的非私有成员在派生

130、类中都为在派生类中都为protectedprotectedprotectedprivate不可见不可见私有继承私有继承privatepublicprivate基类中的非私有成员基类中的非私有成员都成为派生类中的私都成为派生类中的私有成员有成员protectedprivateprivate不可见不可见117例例nclassParentnprivate:intthree;nprotected:inttwo;npublic:nintone;nParent()one=two=three=42;nvoidinParent()ncoutonetwothree;/alllegaln;nclassChild:

131、publicParentnpublic:nvoidinChild()ncoutone;/legalncouttwo;/legalncoutthree;/error-notlegaln;nvoidmain()nChildc;ncoutc.one;/legalncoutc.two;/error-notlegalncoutc.three;/error-notlegaln1186.3改写、遮蔽与重定义改写、遮蔽与重定义n静态类和动态类静态类和动态类:n变量的静态类是指用于声明变量的类。变量的静态类是指用于声明变量的类。静态静态类在编译时就确定下来,并且再类在编译时就确定下来,并且再也不会改也不会改变变

132、n变量的动态类指与变量所表示的当前变量的动态类指与变量所表示的当前数值数值相关的类。动态类在程序的执行相关的类。动态类在程序的执行过程中,过程中,当对变量赋新值时可以改变当对变量赋新值时可以改变n对于静态类型面向对象编程语言,在编译时对于静态类型面向对象编程语言,在编译时消息传递表达式的消息传递表达式的合法性合法性不是基于接收器的不是基于接收器的当前动态数值,当前动态数值,而是基于接收器的静态类而是基于接收器的静态类来来决定的决定的119例例nclassAnimalpublic:virtualvoidspeak()cout“AnimalSpeak!”;nclassDog:publicAnima

133、lpublic:voidspeak()bark();voidbark()cout“Woof!”;nclassBird:publicAnimalpublic:voidspeak()coutspeak();/woof!pet-bark();/error!编译错误,编译错误,pet为指向为指向Animal的指针的指针1216.3.1多态多态n多态:如果方法所执行的消息绑定是由最近赋值给变多态:如果方法所执行的消息绑定是由最近赋值给变量的数值的类型来决定的,那么就称这个变量是量的数值的类型来决定的,那么就称这个变量是多态多态的。的。nJava变量都是多态的。变量都是多态的。nC+声明为简单类型的变量,

134、非多态。声明为简单类型的变量,非多态。n在在c+中,编译器不会自动将派生类类型对象转换为基中,编译器不会自动将派生类类型对象转换为基类类型对象类类型对象nAnimala;Dogb;b.speak();/woof!a=b;a.speak();/Animalspeak!/当接收器的类型为当接收器的类型为Animal时,时,speak所执行的方法处于静态所执行的方法处于静态类类Animal中,即使变量所包含的数值来自于不同类型的赋中,即使变量所包含的数值来自于不同类型的赋值值122实现多态要满足的条件实现多态要满足的条件na.使用指针或引用;使用指针或引用;nb.父类相关方法声明为父类相关方法声明为

135、virtual;Birdb;Animal*pet,&a=b;a.speak();Dogd;pet=&d;pet-speak();pet-bark();/error123Animal没有声明为没有声明为virtualnclassAnimalpublic:voidreply()cout“AnimalReply!”;nclassDog:publicAnimalpublic:voidreply()coutreply();/AnimalReply!d.reply();/Woofagain!1246.3.2改写和虚拟方法改写和虚拟方法n改写:改写:n语法上:子类定义一个与父类有着相同名称且类型签语法上:子

136、类定义一个与父类有着相同名称且类型签名相同的方法。名相同的方法。n运行时:变量声明为一个类,它所包含的值来自于子运行时:变量声明为一个类,它所包含的值来自于子类,与给定消息相对应的方法同时出现于父类和子类。类,与给定消息相对应的方法同时出现于父类和子类。n改写与替换结合时,想要执行的一般都是子类的方法。改写与替换结合时,想要执行的一般都是子类的方法。n改写机制改写机制nJava语言,只要子类通过同一类型签名改写父类的方语言,只要子类通过同一类型签名改写父类的方法,自然便会发生所期望的行为。法,自然便会发生所期望的行为。nC+中需要父类中使用关键字中需要父类中使用关键字Virtual来表明这一含

137、义。来表明这一含义。125n虚函数的定义与派生类中的重定义虚函数的定义与派生类中的重定义class类名类名public:virtual成员函数说明;成员函数说明;class类名:基类名类名:基类名public:virtual成员函数说明成员函数说明;/virtualoptional126例例classApublic: virtualvoidf()coutA:fendl;classB:publicApublic:virtualvoidf()coutB:fendl;127虚函数是成员函数,如果某类的一个成员函数被说明为虚虚函数是成员函数,如果某类的一个成员函数被说明为虚函数,就意味这该成员函数在派

138、生类中可能有不同的实函数,就意味这该成员函数在派生类中可能有不同的实现。现。在在c+中通过指向基类对象的指针来访问这些虚函数。中通过指向基类对象的指针来访问这些虚函数。classC:publicApublic:virtualvoidf()cout“C:ff();pa=&cobj;pa-f();1286.3.2重定义重定义n当子类定义了一个与父类具有相同名当子类定义了一个与父类具有相同名称但类称但类型签名不同的方法时,发生重定义。型签名不同的方法时,发生重定义。类型签名类型签名的变化是重定义区别于改写的主要依据。的变化是重定义区别于改写的主要依据。n在在c+中,在派生类作用域中派生类成员屏蔽基中

139、,在派生类作用域中派生类成员屏蔽基类成员,即使签名不同,基类成员也被屏蔽类成员,即使签名不同,基类成员也被屏蔽n129nclassParentpublic:voidexample(inta)cout“inparentmethod”endl;;classChild:publicParentpublic:voidexample(inta,intb)cout“inchildmethod”endl;;Parentp;Childc;p.example(10);c.example(10,20);c.example(20);/编译器查找名字编译器查找名字example,在,在Child类类中找到。一旦找到,

140、不再继续查找,但是与中找到。一旦找到,不再继续查找,但是与Child类中的类中的example参数不同,出错参数不同,出错130n所以说,如果派生类定义了重载成员,则通过所以说,如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。派生类型只能访问派生类中重定义的那些成员。如果要访问基类的成员如果要访问基类的成员nclassChild:publicParentpublic:voidexample(inta)Parent:example(a);voidexample(inta,intb)cout“inchildmethod”endl;;1316.3.3遮蔽遮蔽n类似于重载,改

141、写区别于遮蔽的最重要的特征就是,遮蔽是在类似于重载,改写区别于遮蔽的最重要的特征就是,遮蔽是在编译时基于静态类型解析的,并且不需要运行时机制。编译时基于静态类型解析的,并且不需要运行时机制。n例例4:classParentpublic:intx=12;nclassChild:publicParentnpublic:intx=42;nParentp;ncoutp.x;/12nChildc;ncoutc.x;/42np=c;ncoutp.x;/12132nc+中,若基类函数名前不加中,若基类函数名前不加virtual,将产生遮蔽。将产生遮蔽。nclassParentpublic:voidexamp

142、le()cout“inparent”endl;;classChild:publicParentpublic:voidexample()cout“inchild”example();Child*c=newChild;c-example();p=c;p-example();/inparent133改写、遮蔽和重定义改写、遮蔽和重定义n1.改写改写父类与子类的类型签名相同,并且在父父类与子类的类型签名相同,并且在父类中将方法声明为虚拟的。类中将方法声明为虚拟的。n2.遮蔽遮蔽父类与子类的类型签名相同,但是在父父类与子类的类型签名相同,但是在父类中并不将方法声明为虚拟的。类中并不将方法声明为虚拟的。n

143、3.重定义重定义父类与子类的类型签名不同父类与子类的类型签名不同1346.4接口和抽象类接口和抽象类n抽象方法:定义方法但不实现。创建实例前,抽象方法:定义方法但不实现。创建实例前,子类必须实现父类的抽象方法。子类必须实现父类的抽象方法。n纯虚函数:在纯虚函数:在c+中,用纯虚函数这个术语表示中,用纯虚函数这个术语表示抽象方法的概念。抽象方法的概念。n声明为纯虚函数的格式:声明为纯虚函数的格式:virtual类型说明符类型说明符函数名函数名(参数表参数表)=0;n纯虚函数是在基类中声明的一种函数,在基纯虚函数是在基类中声明的一种函数,在基类中只给出函数原型,要求任何派生类都类中只给出函数原型,

144、要求任何派生类都必必须提供这个函数的实现须提供这个函数的实现。n接口:类可以包含抽象方法和非抽象方法,所接口:类可以包含抽象方法和非抽象方法,所有都声明为抽象方法的类相当于有都声明为抽象方法的类相当于java中的接口中的接口1356.5继承和构造函数继承和构造函数n构造函数不能继承。构造函数不能继承。n当基类的构造函数没有参数或参数有缺省值时,当基类的构造函数没有参数或参数有缺省值时,基类的构造函数和子类的构造函数都会自动执基类的构造函数和子类的构造函数都会自动执行;当基类的构造函数有参数并且没有提供缺行;当基类的构造函数有参数并且没有提供缺省值,子类必须定义构造函数,将参数传递给省值,子类必

145、须定义构造函数,将参数传递给基类的构造函数。基类的构造函数。classChild:publicParentpublic:Child(intx):Parent(x+2);1366.6虚拟析构函数虚拟析构函数classParentpublic:Parent()cout“inparentn”;classChild:publicParentpublic:Child()cout“inchildn”;;Parent*p=newchild;/指向父类的指针变量指向子类指向父类的指针变量指向子类的一个实例的一个实例deletep;/对该指针变量进行释放,只调用父类的析构对该指针变量进行释放,只调用父类的析构函

146、数函数137n如果父类的析构函数声明为如果父类的析构函数声明为virtual,那么父类的那么父类的析构函数和子类的析构函数都将执行。在析构函数和子类的析构函数都将执行。在c+中,中,将析构函数声明为虚拟是一个良好习惯。将析构函数声明为虚拟是一个良好习惯。138参数的缺省值参数的缺省值n有一些参数的值在每次有一些参数的值在每次函数函数调用时都相同,书写这样的语句会调用时都相同,书写这样的语句会使人厌烦。使人厌烦。C+语言采用参数的缺省值使书写变得简洁(在编译语言采用参数的缺省值使书写变得简洁(在编译时,缺省值由编译器自动插入)。参数缺省值的使用规则:时,缺省值由编译器自动插入)。参数缺省值的使用

147、规则:nl、参数缺省值只能出现在、参数缺省值只能出现在函数函数的声明中,而不能出现在定义体的声明中,而不能出现在定义体中。中。n例如:例如:nvoidFoo(intx=0,inty=0);/正确,缺省值出现在正确,缺省值出现在函数函数的声明中的声明中nvoidFoo(intx=0,inty=0)/错误,缺省值出现在错误,缺省值出现在函数函数的定义体中的定义体中nn2、如果函数有多个参数,参数只能从后向前挨个儿缺省、如果函数有多个参数,参数只能从后向前挨个儿缺省n正确的示例如下:正确的示例如下:nvoidFoo(intx,inty=0,intz=0);n错误的示例如下:错误的示例如下:nvoidFoo(intx=0,inty,intz=0);139

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号