C++程序设计教程(第二版)课件清华大学出版社 钱能

上传人:M****1 文档编号:589878797 上传时间:2024-09-11 格式:PPT 页数:362 大小:958.50KB
返回 下载 相关 举报
C++程序设计教程(第二版)课件清华大学出版社 钱能_第1页
第1页 / 共362页
C++程序设计教程(第二版)课件清华大学出版社 钱能_第2页
第2页 / 共362页
C++程序设计教程(第二版)课件清华大学出版社 钱能_第3页
第3页 / 共362页
C++程序设计教程(第二版)课件清华大学出版社 钱能_第4页
第4页 / 共362页
C++程序设计教程(第二版)课件清华大学出版社 钱能_第5页
第5页 / 共362页
点击查看更多>>
资源描述

《C++程序设计教程(第二版)课件清华大学出版社 钱能》由会员分享,可在线阅读,更多相关《C++程序设计教程(第二版)课件清华大学出版社 钱能(362页珍藏版)》请在金锄头文库上搜索。

1、C + 程序设计教程(第二版)本书第一章非常简短,不再敷述!清华大学出版社 钱 能9/11/20241C + 程序设计教程(第二版)第二章 基本编程语句 Chapter 2 Basic Programming Statements清华大学出版社 钱 能9/11/20242第二章内容1.说明语句说明语句 ( Declarative Statements ) 2.条件语句条件语句 (Condition Statements ) 3.循环语句循环语句 ( Loop Statements ) 4.循环设计循环设计(Loop Designs )5.输入输出语句输入输出语句( Input/Output S

2、tatements ) 6.转移语句转移语句 ( Move Statements ) 7.再做循环设计再做循环设计(More Loop Designs ) 9/11/202431. 说明语句说明语句 ( Declarative Statements )数据说明:求解问题所使用的数据是什么性质,进行什么运算,表达范围如何,必须预先说明说明方式: 既要指明其名字,也要指明其是什么类型,还可以顺便初始化如: int a; double d = 3.5;说明数据的另一个目的是创建一个所需大小的实体空间给该名字,以便存储所用的数据值若数据名字没有说明,使用其便是非法的9/11/20244过程(函数)说明

3、:求解中需要通过函数调用来实施求解时,便要对函数的性质进行说明,说明其返回类型,参数类型,参数个数函数说明分函数声明和函数定义两种: 函数声明是说明函数的名字,函数的返回类型,以及函数的参数和个数如: double area(double ra); 函数定义是在函数声明的基础上,对整个实现过程进行详细定义如: double area(double ra) return ra*ra*3.14; 9/11/20245调用函数就是使用函数名字,使用名字前必须清楚名字的性质,所以必须先对函数进行声明运行程序中,会涉及到被调用函数的执行,所以凡是被调用的函数都必须有函数定义,不管该定义在程序的什么位置如

4、: double sphere(); / 声明 int main() double result = sphere(); / 调用 cout“area: ”result“n”; void sphere() / 定义 coutr; return r*r*3.14; 9/11/20246. 条件语句条件语句(Condition Statements )if语句的两种形态:语句1条件语句1条件语句2是是否否9/11/20247对应语句: if(ab) coutaendl; if(a=b) coutaendl; else coutb0) if(x50) cout”x is ok.n”; else co

5、ut0) if(x 50) cout”OKn”; else cout0) if(x 50) cout”OKn”; else cout”NOT OKn”;9/11/20249条件表达式:对于对于 if(x) a = 327981; else b = 327981;可表示为:可表示为: x ? a=327981 : b=327981;如果如果a和和b为同类型,则还可以:为同类型,则还可以: (x?a:b) = 327981;9/11/202410switch多分支语句: switch(整数表达式) case value1: 语句1; break; case value2: 语句2; break;

6、default: 语句n; 等价于:等价于: if(整数表达式整数表达式=value1) 语句语句1; else if(整数表达式整数表达式=value2) 语句语句2; else 语句语句n; 9/11/202411虽然switch有等价的复合if表示, 而且,分支判断值只能是整数,显得应用范围狭窄, 但是switch在使用上的直观和灵活形式, 使得其仍具有编程价值. 如:break可选,甚至case可以重叠: case value1: 语句1; case value2: 语句2; case v1: case v2: case v3: 语句;9/11/2024123. 循环语句循环语句 (

7、Loop Statements )for循环结构: 开 始循环初始状态循环体状态修正条件判断未结束结束结 束9/11/202413对应语句为:对应语句为: for(int i=1; i=10; i+) cout”hello.n”;循环初始状态条件判断状态修正循环体9/11/202414每次循环体执行,都改变循环状态,直每次循环体执行,都改变循环状态,直到条件不满足而终止到条件不满足而终止. .如,设置求和的初始值,交给循环计算,完成如,设置求和的初始值,交给循环计算,完成循环后,输出求和结果:循环后,输出求和结果:nint sum = 0;nfor(int i=1; i=100; i+)nn

8、sum = sum+i;nncoutsumendl;循环开始循环结束9/11/202415因为并不是所有循环都有明显的循环初因为并不是所有循环都有明显的循环初始状态和状态修正的,所以,始状态和状态修正的,所以,whilewhile循环是一种循环是一种forfor循环的简洁形式循环的简洁形式. .如,同样的求和:如,同样的求和:nint sum=0, i=1;nwhile(i=100)n sum += i+;ncoutsumendl;循环开始循环结束9/11/2024164. 循环设计循环设计 ( Loop Designs )(1)简单字符图形的双重循环MMMMMMMMMMMMMMMMMMMMM

9、MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM9/11/202417分析方法:该图形一共10行,每一行增加一个字符,所以,应循环10次,每次输出一行,其循环模式为:for(int i=1; i=10; +i) 输出第i行(循环) 换行n行 i M个数n 1 1 1n 2 2 2n 3 3 3n 4 4 4n.n10 10 109/11/202418for(int i=1; i=10; +i) for(int j=1; j=i; +j) cout”M”; coutendl;9/11/202419(2)判断素数:(利用数学定律)nmij 假定ij, n则 i2ijmj2 n即

10、 i2mj2 n即 imj bool isPrime(int m) double sqm=sqrt(m*1.0); for(int i=2; i=sqm; +i) if(m%i=0) return false; return true; 9/11/2024205. 输入输出语句输入输出语句( Input/Output Statements ) 标准输出流:可以控制输出格式ncoutshowpos12; / +12ncouthex18” “showbase18; / 12 0x12ncouthex255” “uppercase255; / ff FFncout123.0” “showpoint1

11、23.0; / 123 123.000ncout3)” “boolalpha3); / 0 falsencoutfixed12345.678; / 12345.678000ncoutscientific123456.678; / 1.234568e+059/11/202421控制宽度和填充字符的操作是带参数的,注意width(n)为一次性操作,即第二次显示时将不再有效。默认为width(0),表示仅显示数值。例如:ncout.width(5);ncout.fill(S);ncout2323; / 输出: SSS2323在头文件iomanip的支持下,还可以直接由操作:n#includennco

12、utsetw(6)setfill($)27endl; / 输出: $279/11/202422文件流的输入出操作与标准输入出基本相同,只是需要以一定方式打开和关闭如,将文件a.in打开,逐行读入字符,输出到文件a.out:n ifstream in(“a.in”);n ofstream out(“a.out”);n for(string s; getline(in, s); )n outsendl;其中文件流读入操作总是伴随着状态返回,判断状态就可以确定文件是否正常读入,如getline(in,s)当读到文件尾时,便返回false,以使循环结束9/11/202423. 转移语句转移语句 ( M

13、ove Statements ) break除了用在switch之外,主要的是用在终结本次循环for(int i; ; ) for( ; ; ) / . if(i=1) break; / . a=1; / .break跳到此处9/11/202424ncontinue一般是用条件判断执行的,通过反条件,可以免去continue的使用,所以,它用来构筑良好的程序风格 for(int n=100; n=200; +n) if(n%3=0) continue; coutnendl; n免去continuecontinue的情形: for(int n=100; n=200; +n) if(n%3!=0)

14、 coutnendl; 9/11/202425ngoto的程序段除了系统跟踪和架构困难外,阅读也相对复杂: int a; goto Init; Forward: a = a + 1; Print: coutaendl; goto Down; Init: a = 1; goto Print; Down: if(a100) goto Forward;n等价于: for(int i=1; i=100; +i) couti“n”;9/11/202426nbreak语句的可取之处:/ 用break语句的代码段bool flag=false; / 用于做退出记号for(int i=1; i100; +i)

15、 for(int j=1; j100; +j) if(i*j=651) flag=true; break; else / . if(flag) break;/ goto语句的代码段for(int i=1; i100; +i)for(int j=1; j100; +j) if(i*j=651) goto End; / .End:9/11/2024277. 再做循环设计再做循环设计( More Loop Designs ) 逻辑判断类的语句控制结构: for(所有可能情况) / 可为多重循环 if(条件1不满足) continue; if(条件2不满足) continue; / if(条件n不满足

16、) continue; 输出所要的结果 9/11/202428百鸡问题的例子: for(int c=1; c=13; +c) for(int h=1; h=18; +h) for(int s=1; s=96; +s) if(7*c+5*h+s/3-100) continue; if(c+h+s-100) continue; if(s%3) continue; coutCock:c , Hens:h , Chicks:100-c-h1e-6; +n) item *= (-1.0)*(2*n-3)/(2*n-1); sum += item;coutPi = “ setiosflags(ios:fi

17、xed) sum*4endl;9/11/202431C + 程序设计教程(第二版)第三章 数据类型 Chapter 3 Data Types清华大学出版社 钱 能9/11/202432n数据类型:u一定的数据在计算机的内部表示;u该数据所表示的值的集合;u在该数据上的一系列操作。n内部数据类型: 1.整型长整型,短整型,字符型,布尔型 2.浮点型单精度,双精度9/11/202433第三章内容1. 整型整型 ( int Types ) 2. 整型子类整型子类 ( sub-int Types ) 3. 浮点型浮点型 ( Floating-Point Type ) 4. C-串与串与string (

18、 C-string & string )5. 数组数组 ( Arrays ) 6. 向量向量 ( vectors ) 7. 指针与引用指针与引用 ( Pointer & References ) 9/11/2024341. 整型整型 ( int Types )整型数的内部表示:二进制补码 位数既定的二进制补码运算没有加减的区别;对于溢出,只是简单的舍弃而不是错误整型数的表示范围:取决于二进制位数整型数的操作:+,-,*,/,%,=,!,=,=,=,&,|,&=,|=,&,|,&=,|=,!=,=,+=,-=,*=,/=,%=,+,-,,,? :9/11/202435编译器的机器字长总是与整型的

19、位长有关如: 32位编译器的整型数一定为32位长整型字面值分八进制,十进制和十六进制不同表示如: 0123 / 8进制 0x12af3 / 16进制 12345 / 10进制 超过表示范围的整型数其值不可预料或者出错如: int a = 12345678912345678912345; / 错9/11/202436. 整型子类整型子类( Sub-int Types )字符型:表示范围: 有符号:-128127 无符号:0255 输出形式与整型数不同: int a = 65; char b = 65; couta“n”; coutb“n”; 结果为: 65 A 9/11/202437枚举型: 自

20、定义整数区间,甚至列举单个整数值 enum Week Mon, Tue, Wed, Thu, Fri, Sat, Sun ; 最大特点是可以给每个值指定一个在程序中直接使用的标记(枚举符) 编程中将其当作整数常量用如: int a = 7; if ( a = Sun) cout “Sundayn”; 9/11/202438布尔型:表示范围仅含整数和1,也可以表示成true和false,相当于: enum bool false, true ;因为条件表达式、逻辑运算的结果都是因为条件表达式、逻辑运算的结果都是或,所以,相当大数量的表达式的值与或,所以,相当大数量的表达式的值与布尔型对应布尔型对应

21、9/11/2024393. 浮点型浮点型 ( Floating-Point Types )浮点数的内部表示: 国际标准IEEE754浮点表示法,它与编程所用的浮点数字面量以及输出的十进制浮点数之间有一个转换关系浮点数的表示范围:32位浮点数3.41038 64位浮点数1.810308浮点数的操作: 常规的加、减、乘、除等操作9/11/2024404. C-串与串与string ( C-string & string )C-串结构每个字符占据1个字节一个C-串是一个字符序列,用来表示各种名字或者文字说明C-串的字符序列的最后总是添加有一个结束标志.即在6个字符的字串(“Hello!”)其空间存储

22、有7个字节左边三图是不同细节的同一空间结构描述Hello!0 72 101 108 108 111 33 0010010000110010101101100011011000110111100100001000000009/11/202441知道了知道了C-C-串首地址串首地址, ,即可知道整个串即可知道整个串, ,所所以可以藉字符首址以可以藉字符首址( (字符指针字符指针) )来操来操作作C-C-串串, ,但要注意,串的第一个字符但要注意,串的第一个字符与整个串的操作不同与整个串的操作不同, ,如如,C-,C-串的输串的输出操作出操作: : char* str = ”Hello”; cout

23、 *str endl; / 显示显示H cout str endl; / 显示显示Hello9/11/202442C-串不能直接比较,因为字符指针的比较只是地址值的比较而不是C-串的字典序比较: cout(“join”=”join” ? ” : ”not “)”equaln”; / 字面值比较 char* str1=”good”; char* str2=”good”; cout(str1=str2 ? ” : ”not “)”equaln”; / 字符指针比较 char buffer16=”Hello”; char buffer26=”Hello”; cout(buffer1=buffer2

24、? ” : ”not “)”equaln”; / 字符数组比较 结果:结果:not equal not equal not equal9/11/202443不得不配备专门操作C-串的库函数:nstrcpy(s1, s2); /从从s2拷贝到拷贝到s1nstrcmp(s1, s2); /比较比较s1与与s2nstrcat(s1, s2); /连接连接s2到到s1nstrrev(s); /将将s倒排倒排 nstrset(s, c); /将将s全置为全置为cnstrstr(s, “ell”); /查找查找s中的子串中的子串nstrchr(s,c); /查找查找s中的字符中的字符 等等等等9/11/2

25、02444但字符指针操作C-串的安全性受到质疑:char* str1;char* str2 = new char5;strcpy(str2, ”ugly”);strcpy(str1,str2); / 错: str1没有空间可储strcpy(str2, ”Hello”); / 错: str2空间不够大str2 = ”Hello”; / 错:原来的”ugly”空间脱钩,导致内存泄漏根源:复制操作须以足够的目的地空间为前提,而所有C-串操作的空间调配都是人为安排的,C-串库函数一概不管9/11/202445类串string串类自定义串对应字符指针的C-串操作: string a, s1 = Hell

26、o ; string s2 = 123; a = s1; / copy cout(a=s1 ? : not)equaln; / compare couta+s2endl; / concatenate reverse(a.begin(), a.end(); coutaendl; / reverse couta.replace(0,9,9,c)endl; / set cout(s1.find(ell)!= -1 ? : not )foundn;/ find string cout(s1.find(c)!= -1 ? : not )的读入方式总是将前导的空格(所谓空格,即包括空格、回车、水平或垂直制

27、表符等)滤掉,将单词读入,在遇到空格时结束本次输入ngetline总是将行末的回车符滤掉,将其整行输入对字串”Hello, How are you?”的两种输入方式 for ( string s; cins; ) couts” “; coutendl; string s; getline(cin, s); couts a ; sum += a ) ; cout sum “n” ; 9/11/2024485. 数组数组( Arrays ) 数组是同类元素的集合,它的元素排列在连续的空间中,按下标来标记描述数组必须给出元素类型,元素个数元素个数必须在编程时确定,任何变量都不允许 int aa ;

28、/ 表示int a97; int n = 100 ; int an ; / 错: 元素个数必须预知 const int n = 100 ; int an ; / ok int a ; / 错: 无元素个数 int a = 1, 2, 3, 4, 5 ; / ok:通过初始化确定元素个数9/11/202449数组初始化可选,但须遵循语法无初始化的数组按规定取默认值 nint array15 = 1, 2, 3, 4, 5, 6 ; / 错: 初始值个数超元素个数nint array25 = 1, , 2, 3, 4 ; / 错: 不能以逗号方式省略nint array35 = 1, 2, 3,

29、; / 错: 同上nint array45 = ; / 错: 初始值不能为空nint array55 = 1, 2, 3 ; / ok: 后面元素取0nint array65 = 0 ; / ok: 元素全为0nint array75 ; / ok: 元素值不确定nint a35 = 1, 2, 3, 4, 5 , 2, 3, 4, 5, 6 , 3, 4, 5, 6, 7 ; 9/11/202450数组有诸多缺陷,造成编程艰难和不安全 int a5 = 1,2,3,4,5, c5; int b5 = a; / 错:无法拷贝创建 c = a; / 错:无法整体拷贝和局部拷贝 a8 = 10;

30、/ 错:无法动态扩容和随意增减元素 for(int i=0; i=5; +i) / 错:无法防范下标溢出 ai = i+1; if(a=c) a0 = 2; / 错:不可比较 int a5 = 1; / 初始化呆板,无法获得全初值9/11/202451二维数组的初始化,下标访问及输出 int array123=1,2,3,4,5; int array223=1,2,4; coutarray1: ; for(int i=0; i2; +i) for(int j=0; j3; +j) coutarray1ij,; coutnarray2: ; for(int i=0; i2; +i) for(in

31、t j=0; j3; +j) coutarray2ij,; coutn;结果为:结果为: array1: 1,2,3,4,5,0, array2: 1,2,0,4,0,0,9/11/202452.向向量量( vector ) 向量与数组的共同特征是元素的排列在逻辑上是线性序列结构,可以用下标进行访问 向量可以按需创建,拷贝创建,局部拷贝创建,异类拷贝和创建 灵活的初始化 随意扩容和元素增减 可通过异常来进行下标溢出追踪和处理 可比较 等等9/11/202453nint n=10;nint t5=1,2,3,4,5;nvector a(n); /按需创建nvector b(10, 1); /元素

32、赋全,灵活的初始化nvector c(b); / 整体拷贝创建nvector f(t, t+5); /异类拷贝创建nvector d(b.begin(), b.begin()+3);/局部拷贝创建d为b的前个元素na.assign(100); /动态扩容至100个元素9/11/202454向量常用操作na.assign(b.begin(), b.begin()+3); / b的前3个元素赋给ana.assign(4,2); / a向量含4个元素,全初始化为2nint x = a.back(); / a的最后一个元素赋给变量xna.clear(); / a向量清空(不再有元素)nif(a.emp

33、ty() cout”empty”; / a判空操作nint y = a.front(); / a的第一个元素赋给变量yna.pop_back(); / 删除a的最后一个元素na.push_back(5); / a最后插入一个元素,其值为5na.resize(10); / a元素个数调至10。多删少补,其值随机na.resize(10,2);/a元素个数调至10。多删少补,新添元素初值为2nif(a=b) cout”equal”; / a与b的向量比较操作9/11/202455向量操作尤其适合于函数参数传递(-D以上的数组参数的传递十分丑陋):传递一个矩阵,无论其每行中的元素个数不同传递一个矩阵

34、,无论其每行中的元素个数不同. .输出之输出之: :typedef vectorvector Mat;void print(const Mat& a) for(int i=0; ia.size(); +i) for(int j=0; jai.size(); +j) coutaij ; coutendl; 9/11/2024567. 指针与引用指针与引用 ( Pointers & Reference ) 指针指向存放数据的地址指针必须初始化或者赋值(指向了数据)后,才能进行间接访问(间访)操作nint* ip;nint iCount = 18;nint* iPtr = &iCount; / 初始

35、化nip = &iCount; / 赋值n*ip = 8; / 间访操作9/11/202457指针操作与指向数据的类型密切相关 float f = 34.5; int* ip = reinterpret_cast(&f); cout“fAddr: ”&f”f“n”; cout“iAddr: ”ip”*ip“n”; *ip = 100; cout“ int: ”*ip“n”; cout“float: ”f34.5 iAddr: 1245064=1107951616 int: 100 float: 1.4013e-439/11/202458指针加减整数的操作表示空间位置上的挪动但是挪动的字节数与其

36、数据类型相关:对float指针加6实际增加了24个字节对long int指针加5实际增加了20个字节对char指针减7实际减少了7个字节对double指针减2实际减少了16个字节9/11/202459数组名本身就是表示元素集合的首地址数组名本身就是表示元素集合的首地址可以将数组名赋给指针可以将数组名赋给指针 int a3; for(int i=0; i3; +i) ai = i*2; for(int* iP=a; iPa+3; iP+=1) coutiP“: ”*iP“n”; 结果为: 1245036: 0 1245040: 2 1245044: 49/11/202460指针限定指针限定con

37、st int a = 78;int b = 10;int c = 18;const int* ip = &a; / const修饰指向的实体类型常量指针int* const cp = &b; / const修饰指针*cp指针常量int const* dp = &b; / 等价于上一句指针常量const int* const icp = &c; / 常量指针常量*ip = 87; / 错:常量指针不能修改指向的常量,*ip只能做右值ip = &c; / ok:常量指针可以修改指针值*cp = 81; / ok:指针常量可以修改指向的实体cp = &b; / 错:指针常量不能修改指针值,即使是同一

38、个地址*icp = 33; / 错:常量指针常量不能修改指向的常量icp = &b; / 错:常量指针常量不能修改指针值int d = *icp; / ok9/11/202461引用必须初始化,因为引用总是附属于某引用必须初始化,因为引用总是附属于某个实体个实体int someInt = 5;int& rInt = someIne; /初始化修改引用的值,即是修改了附属的实体值修改引用的值,即是修改了附属的实体值int a = 5;int& ra = a;ra = 8;couta“n”;结果为:8引用多用在函数参数的传递上引用多用在函数参数的传递上9/11/202462C + 程序设计教程(第

39、二版)第四章 计算表达 Chapter 4 Computational Expressing清华大学出版社 钱 能9/11/202463n计算表达: 表达计算使用一系列操作,它依赖于特定语言的操作符功能,关乎数据类型的内在特性,故计算表达目的在于深入剖析数据类型对于编程的影响,从而准确使用操作符n学习方法: 1.掌握操作符的功能和相互关系(优先级和结合性) 2.针对内部数据类型,对一些典型的操作中的典型问题留下深刻印象9/11/202464第四章内容1. 名词解释名词解释( Name Explainations ) 2. 算术运算问题算术运算问题 ( Arithmetic Problems )

40、 3. 相容类型的转换相容类型的转换 ( Cast Campatible Type ) 4. 关系与逻辑操作关系与逻辑操作 ( Relation & Logic Operations )5. 位操作位操作 ( Bit Operations ) 6. 增量操作增量操作 ( Increment Operations ) 7. 表达式副作用表达式副作用 ( Expressions Side Effects ) 9/11/2024651. 操作符操作符 ( Operators )单目操作符:在一个操作数上施加的操作,如:-3双目操作符:在二个操作数上施加的操作,如:- 故有些操作符既是单目操作符,又是

41、双目操作符表达式:若干个操作数和操作符按语法规则构成的操作,如: a = -3-5+6*7/-89/11/202466优先级:表达式中多个操作符的执行顺序的规定性,如: *x+; / 先做x+结合性:同级操作符的执行顺序的规定性,如: a=b=6; / 先做b=69/11/202467. 算术运算问题算术运算问题( Arithmetic Problems )整型数表示范围有限,如: 不能用整型变量累计的一般循环方法来解: int sum = 0; for(int i=1; i=10000; +i) sum += i; coutsum“n”;9/11/202468整型数的周而复始性,如: uns

42、igned int a = 2000000000; unsigned int b = 3000000000; cout a+b“n”; 结果为: 705032704 超过表示范围的整型数不是报错,而是表示成一个去掉进位后的余数9/11/202469中间结果溢出导致计算错误,如: int a = 100000; int b = 100000; int c = 1000; couta*b/cn; couta*(b/c)n; 结果为: 1410065 100000009/11/202470浮点数的精度和有效位 影响比较的正确性,如: float f1 = 7.123456789; float f2

43、= 7.123456785; if ( f1=f2 ) cout“f1 equal to f2n”; float g = 1.0/3.0; double d = 1.0/3.0; if ( g=d ) cout“g not equal to dn”;结果为:结果为: f1 equals to f2 g not equals to d9/11/202471浮点数计算的近似性 使精确性比较失败,如: double d1 = 123456789.9*9; double d2 = 1111111109.1;if ( d1!=d2 ) cout “Not samen” ;else cout “Samen

44、” ;if ( abs ( d1-d2 ) 1e-05 ) cout “Samen” ;else cout “Not samen” ;结果为:结果为:Not sameSame9/11/2024723. 相容类型转换相容类型转换 ( Cast Compatible Type ) 隐式转换:整型和浮点型都是数值型,所以它们是相容类型指针与整型不相容,如: 7.0 / 3 = 7.0 / 3.0 /将隐式转换成浮点 = 2.33333333 int a = 9; int* ap = &a; 3 + ap /错9/11/202473从表达能力弱的类型到强的类型的转换是安全的,反之,会引起精度丢失如:

45、float f = 7.0/3; / doublefloat int a = 7.0/3; / doubleint cout.precision(9); cout fixed 7.0/3 “n” ; cout f “n” a “n” ; 结果为:结果为: 2.333333333 2.333333254 29/11/202474可以用显式转换的方法,人为控制运算可以用显式转换的方法,人为控制运算在一定的数据类型下工作,如:在一定的数据类型下工作,如: double d = sqrt ( 123456.0 ) ;int a = static_cast(d) * 8 + 5;int b = d *

46、8 + 5; / 隐式转换为浮点couta“n”b“n”;结果为:281328159/11/2024754. 关系与逻辑操作关系与逻辑操作 (Relations & Logic Operations )=与=的区别int x = 9;if ( x = 0 ) cout “test 1 okn” ;if ( x = 5 ) cout “test 2 okn” ;if ( x = 0 ) cout “test 3 okn” ;结果为:test 2 ok9/11/202476!=!=是操作符,是操作符,=!=!不是操作符不是操作符 int x = 3; if ( x!=9 ) cout “not 9

47、n” ; if ( x=!9 ) cout “impossiblen” ;条件表达式(x!=0)与(x)等同 int x = 3; if ( x != 0 ) cout x ; if ( x ) cout x ;9/11/202477不等式连写的错误: int a = -1, b = 0, c = 1; if ( abc ) cout “ok1n” ; if ( ab & bc ) cout 2 ) cout “okn” ; 避免不必要的求值 if ( a=0 | b=func() ) cout”uselessn”; 9/11/2024795. 位操作位操作( Bit Operations )

48、 左移操作将整数最高位挤掉,在右端补0。如: int a = 12; / a为:00000000000000000000000000001100 a = a在在整整数数的的高高位位挤挤一一个个0或或1进进去去(有有符符号号数数挤挤符符号号位位,无无符符号号数数挤挤 ),而而整整数数最最低低位位被被挤挤掉掉。如:如: short int a = -2; / 1111111111111110 a = a1; / a=-1 即1111111111111111 unsigned short int b = 65535; / 1111111111111111 b = b1; / b=32767即0111

49、1111111111119/11/202481位与操作&将两个操作数每一位做与操作,如: int a = 12; / a为: 00000000000000000000000000001100 int b = 6; / b为: 00000000000000000000000000000110 / a & b为: 00000000000000000000000000000100 int cbit = a & b; int clogic = a & b; cout cbit “n” clogic “n” ; 结果为:( 比较 & 与 & 的区别 ) 4 19/11/202482位与操作 |将两个操作

50、数每一位做或操作,如:将两个操作数每一位做或操作,如: int a = 12; / a为: 00000000000000000000000000001100 int b = 6; / b为: 00000000000000000000000000000110 / a | b为: 00000000000000000000000000001110 int cbit = a | b; int clogic = a | b; cout cbit “n” clogic m? m:n)*10; / 保证运输次数最少9/11/202496参数传递:形参是对实参的克隆,克隆必须遵守类型匹配规则void f(Ty

51、pe a); /a为形参void g() Type x; f(x); /x为实参a实体x实体复制Type类型Type类型9/11/202497. 指针参数指针参数 ( Pointer Parameters )传递指针:指针参数也是值传递的,指针值的真正用途是进行数据间访,以达到操作数据块(大小由之)的目的传递引用:引用参数本质上也是值传递的,它表现为名字传递,即以形参的名字来代替实参名字如果实参不是实体名而是表达式,那么其表达式所对应的临时实体取名为形参,并要求其为常量引用意义:指针和引用参数的存在,使函数实际上可以访问非局部的数据区,函数的黑盒性便名存实亡但这并非一定坏事,指针是一把双刃剑,

52、或灵巧或邪恶引用是为了防范指针非安全的无意操作9/11/202498void mySort(int* b, int size);void f() int a = 3, 5, 7, 1, 8, 4, 9; mySort(a, sizeof(a)/sizeof(a0);传递指针须附带传递单位数据的个数元素个数传指针9/11/202499限制无意操作带来的意外副作用vector add( / 向量加法 const vector& a, const vector& b) vector c(a.size(); for(unsigned i=0; ia.size(); +i) ci = ai + bi;

53、return c;9/11/20241003. 栈机制栈机制 ( Stack Mechanism )运行时内存布局栈区进程空间代码区全局数据区堆区9/11/2024101未初始化局部数据的不确定性#includevoid f() int b; / 未初始化 std:cout”b“n”;/-int main() int a; / 未初始化 std:cout”a“n”; f();/-/ 8804248/ 27880489/11/2024102#includeint a=5;int b=6;int main() int* ap=(int*)4202660; *ap=8; std:couta“n”;

54、std:coutint(&b)“n”;/ 8/ 4202664 指针的无约束性5642026604202664ab4202660ap9/11/20241034. 函数指针函数指针 ( Function Pointers )函数类型:函数类型因参数类型、个数和排列顺序的不同而不同,也因返回类型的不同而不同函数指针:指向代码区中函数体代码的指针.不同的函数类型,其函数指针也不同用法:函数指针经常用作函数参数,以传递连函数本身都不知道的处理过程(函数)9/11/2024104不同的函数指针,不能相互赋值int g(int);int (*gp)(int) = g;void f();void (*fp)

55、();fp = f;gp = fp; / error不同的函数9/11/2024105函数指针作为参数传递 (函数名看作是函数指针)bool lessThanBitSum(int a, int b) int suma=0, sumb=0; for(int x=a; x; x/=10) suma += x%10; for(int x=b; x; x/=10) sumb += x%10; return suma sumb;int main() int a = 33, 61, 12, 19, 14, 71, 78, 59; sort(aa, aa+8, lessThanBitSum); for(in

56、t i=0; i8; +i) coutaai ; coutn;/ 12 14 33 61 71 19 59 789/11/2024106指定函数指针类型,定义函数指针数组typedef void (*MenuFun)();void f1() coutgood!n; void f2() coutbetter!n; void f3() coutbest!n; MenuFun fun=f1,f2,f3;指针类型名9/11/20241075. main参数参数 ( The mains Parameters )程序运行:操作系统读入命令以启动程序重定向命令:操作系统读入命令后,识别并自我消化的参数mai

57、n函数参数:操作系统读入命令后,不能识别参数,将其直接传递给所启动的程序9/11/2024108命令重定向/ f0509.cpp#includeusing namespace std;int main() for(int a,b; cinab;) couta+bf0509 abc.txt17213578 99 1212 345abc.txt9/11/2024109main函数参数/ f0510.cpp#includeusing anmespace std;int main(int argc, char* argv) for(int i=0; iargc; +i) coutargvif0510

58、a1 a2 a3f0510a1a2a39/11/20241106. 递归函数递归函数 ( Recursive Functions )形式上:一个正在执行的函数调用了自身(直接递归).或者,一个函数调用了另一个函数,而另一个函数却调用了本函数(间接递归)本质上:程序在运行中调用了相同代码实体的函数,却在函数栈中重新复制了该函数的整套数据,由于每套数据中的参数也许不同,导致了计算条件发生变化,使得函数得以逐步逼近终极目标而运行9/11/2024111递归函数可以转换为非递归函数例如,求最大公约数long gcd1(int a, int b) / 递归版 if(a%b=0) return b; re

59、turn gcd(b, a%b);/-long gcd2(int a, int b) / 非递归版 for(int temp; b; a=b, b=temp) temp = a%b; return a;/-9/11/20241127. 函数重载函数重载 ( Function Overload )函数重载:一组概念相同,处理对象(参数)不同的过程,出于方便编程的目的,用同一个函数名字来命名的技术称为函数重载参数默认:一个函数,既可以严谨和地道的调用,也可以省略参数,轻灵地调用,达到此种方便编程目的的技术称为参数默认重载与参数默认:它们都是通过参数的变化来分辨处理任务的不同如果参数决定了不同的处理

60、过程,则应重载,否则参数默认更简捷一些9/11/2024113重载是不同的函数,以参数的类型,个数和顺序来分辨void print(double);void print(int);void func() print(1); / void print(int); print(1.0); / void print(double); print(a); / void print(int); print(3.1415f); / void pirnt(double); 9/11/2024114参数默认是通过不同参数来分辨一个函数调用中的行为差异void delay(int a = 2); / 函数声明时

61、函数声明时int main() delay(); / 默认延迟秒默认延迟秒 delay(2); / 延迟秒延迟秒 delay(5); / 延迟秒延迟秒void delay(int a) / 函数定义时函数定义时 int sum=0; for(int i=1; i=a; +i) for(int j=1; j3500; +j) for(int k=1; k100000; +k) sum+;9/11/2024115C+程序设计教程(第二版)第六章 性能 Chapter 6 Performance 清华大学出版社 钱 能9/11/2024116n提高性能的意义: 性能对提高编程能力举足轻重n如何提高性

62、能? 以合理使用资源为前提,尽快完成任务的能力称为效率效率影响性能,提高效率就能提高性能n学习目标: 1. 扩展视野,对同一问题的不同要求,模仿各种编程技巧与空间布局策略,予以应对从而对各种不同的问题,亦能应变自如 2. 掌握测试性能的方法,学会测算时/空交换的代价,客观评估自身的编程能力9/11/2024117第六章内容1. 内联函数内联函数 ( Inline Functions ) 2. 数据结构数据结构 ( Data Structures ) 3. 算法算法 ( Algorithms ) 4. 数值计算数值计算 ( Numerical Computation )5. STL算法算法 (

63、STL Algorithms ) 6. 动态内存动态内存 ( Dynamic Memory ) 7. 低级编程低级编程 ( Lower Programming ) 9/11/20241181. 内联函数内联函数 ( Inline Functions )n做法:将一些反复被执行的简单语句序列做成小函数n用法:在函数声明前加上inline关键字n作用:不损害可读性又能提高性能9/11/20241191./=2.#include3.3.boolbool isDigit(charchar); / 小函数4.4.intint main( )5. forfor(charchar c; cinc & c!=

64、n; )6. ifif(isDigit(c) 7. std:cout“Digit.n;8. elseelse std:cout=0 & ch=9 ? 1 : 0;12./=频繁调用的函数:用昂贵的开销换取可读性9/11/20241201./=2.#include3.3.intint main( )4. forfor(charchar c; cinc & c!=n; )5. ifif(ch=0 & ch=9 ? 1 : 0)6. std:cout“Digit.n;7. elseelse 8. std:cout“NonDigit.n;9./=内嵌代码:开销虽少,但可读性差9/11/2024121内

65、联方式:开销少,可读性也佳1./=2.#includeninline inline boolbool isDigit(charchar); / 小函数nintint main( )n forfor(charchar c; cinc & c!=n; )n ifif(isDigit(c) n std:coutDigit.n;n elseelse n std:cout=0 & ch=9 ? 1 : 0;n/=内联标记放在函数声明的前面9/11/2024122内联函数的使用经验:n函数体适当小,且无循环或开关语句,这样就使嵌入工作容易进行,不会破坏原调用主体如:排序函数不能内联n程序中特别是在循环中反

66、复执行该函数,这样就使嵌入的代码利用率较高如:上例中的isDigit函数n程序并不多处出现该函数调用,这样就使嵌入工作量相对较少,代码量也不会剧增 9/11/20241231./=2.#include3.#include4.4.using namespaceusing namespace std;5./-6.6.intint calc1(intint a, intint b) returnreturn a+b; 7.7.inline inline intint calc2(intint a, intint b) returnreturn a+b; 8./-9.9.intint main()10

67、. intint x1000, y1000, z1000;11. clock_t t = clock();12. forfor(intint i=0; i1000*1000*1000; +i)13. zi = calc1(xi%1000, yi%1000);14. cout(clock()-t)/CLK_TCK“without inlinen;15. t = clock();16. forfor(intint i=0; i1000*1000*1000; +i)17. zi = calc2(xi%1000, yi%1000);18. cout(clock()-t)/CLK_TCKf0605 8.

68、281 without inline2.437 with inline9/11/20241252. 数据结构数据结构 ( Data Structures )n揭示:将数据结构实现为数据类型是编程的高级境界,STL容器就是系统提供的现成数据结构n做法:充分和合理使用STL容器,简化复杂问题,提高编程效率与程序性能9/11/2024126问题:有许多序列,每个序列都等待验证是否可从顺序数列经过栈操作而得 例如:顺序数列 1,2,3,4,5 待验证序列 3,2,1,5,4 待验证序列 5,3,4,2,19/11/2024127采用不同的数据结构#include#include#include/ 栈方

69、式: #include/ 向量方式:#includeusing namespace std;9/11/2024128栈方式/=intint main() ifstream in(rail.txt); forfor(intint n,line=0; inn & n & in.ignore(); ) cout(line+ ? n:); forfor(string s; getline(in, s) & s!=0; ) istringstream sin(s); stack st; forfor(intint last=0,coach; sincoach; st.pop() forfor(intin

70、t p=last+1; p=coach; +p) st.push(p); if if(lastcoach) last=coach; if if(st.top()!=coach) break; coutn & n & in.ignore(); ) cout(line+ ? n:); forfor(string s; getline(in, s) & s!=0; ) istringstream sin(s); vector st; forfor(intint last=0,coach; sincoach; st.pop_back() forfor(intint p=last+1; p=coach;

71、 +p) st.push_back(p); if if(lastcoach) last=coach; if if(st.back()!=coach) break; cout(!sin ? Yesn : Non); /=模仿栈操作9/11/2024130结论n不同的数据结构有不同的操作和性能n应学习使用不同数据结构的经验9/11/20241313. 算法算法 ( Algorithms )揭示:确定了数据结构之后,所采用的算法将直接决定程序的性能;任何语言都有个性,算法的选择使用是灵巧运用语言的艺术,充分发挥语言的优势是活用算法的根本做法:培养经验,用测试的办法对算法进行选择9/11/202413

72、2问题:已知不太大的正整数n(n46),求Fibonacci数列 0 1 1 2 3 5 8 13 21 34 的第n项的值对于后面的四种算法: unsigned fibo1 (unsigned n); unsigned fibo2 (unsigned n); unsigned fibo3 (unsigned n); unsigned fibo4 (unsigned n);如何选择其中之一 第0项第1项第2项9/11/2024133算法:递归法 优点:算法简单,容易编程 缺点:栈空间负担过重,调用开销过大1.unsigned fibo1 (unsigned n)2.3. if (n=1) re

73、turn n;4. return fibo1(n-1) + fibo1(n-2);5.n=0或19/11/2024134算法:迭代法 优点:节省空间,节省时间缺点:编程相对复杂1.unsigned fibo2 (unsigned n)2.3. int c=n;4. for (int a=0,b=1,i=2; i=n; +i)5. c=a+b, a=b, b=c;6. return c;7.9/11/2024135算法3:向量迭代法 优点:节省时间 缺点:n越大,占用堆空间越多1.unsigned fibo3 (unsigned n)2.3. vector v(n+1, 0); 4. v1 =

74、1;5. for (unsigned i=2; i=n; +i)6. vi = vi-1 + vi-2;7. return vn;8.9/11/2024136算法4:直接计算法 优点:节省时间 缺点:引入了浮点计算1.unsigned fibo4 (unsigned n)2.3. return 4. ( pow ( (1+ sqrt ( 5.0 ) ) / 2, n)5. pow ( (1 sqrt ( 5.0 ) ) / 2, n) )6. / sqrt ( 5.0 ) ;7.9/11/2024137fibo1:只在示意性编程中使用,但并不是否定一切递归法fibo2:在讲究性能的场合中使用,

75、它省空间省时间,但在n很大的场合中,性能比不上fibo4fibo3:可以数组代替向量,提高低级编程的性能,它易编易用,还可以读取中间项的值,但在一味追求性能的场合中,比不上fibo2fibo4:在n不太大时,与fibo2相当,在n趋向很大时,其性能优势便充分体现9/11/20241384. 数值计算数值计算 ( Numerical Computation )揭示:在近似计算中,除了计算范围与终止计算条件外,还涉及逼近的快慢,这与算法有很大关系,选择成熟的算法具有决定性作用做法:了解各种数值计算算法的特点及使用场合,有的放矢解决实际问题9/11/2024139数值计算的参数描述template

76、/ T为赖以计算的数系 T method ( / method为某种算法T a, / 左边界T b, / 右边界 const T Epsilon, / 终止条件 T ( * f ) ( T ) / 求值数学函数);9/11/2024140矩形法double rectangle(double a, double b, const double Eps, double (*f ) (double) ) double w=b-a, sN = w*( f (a) + f (b) ) / 2, sO=0; for ( int n=1; abs ( sN - sO ) = Eps; n*=2 ) sO =

77、 sN; sN = 0; for ( int i=0; i Eps; n+=n, h/=2.0 ) In=I2n; Tn=T2n; / In老积分值 double sigma=0; for ( int k=0; kn; k+ ) sigma += f ( a+(k+0.5)*h ); T2n=(Tn+h*sigma)/2; I2n=(4*T2n-Tn)/3; / I2n新积分值 return I2n;小矩形求和辛普生处理前后两次辛普生值的差9/11/2024142性能比较求同样的数学函数,区间和精度矩阵法比辛普生法多循环许多次9/11/20241435. 标准标准+ 算法算法 ( Standa

78、rd C+ Algorithms )揭示:标准C+提供了诸多算法,这些算法的组合构成了许多问题的解,对算法的准确了解是编程能力的一大体现做法:吃透标准+算法,灵活运用之9/11/2024144容器的区间表示vector a (10);/ 下面表示待处理的元素vector b (a.begin ()+1, a.begin ()+7 ); 0123456789首尾待处理的元素9/11/2024145逐一读入两个字串,比较是否含有相同元素int main ( ) ifstream in ( string.txt ) ; for (string s,t; inst; ) 比较 输出 yes 或 no

79、9/11/2024146分别排序,直接加以字串比较是直截了当的思路:for ( string s,t; inst; ) sort ( s.begin ( ), s.end ( ) ) ; sort ( t.begin ( ), t.end ( ) ) ; coutst; ) int s1=count(s.begin(), s.end(), 1); int s0=count(s.begin(), s.end(), 0); int t1=count(t.begin(), t.end(), 1); int t0=count(t.begin(), t.end(), 0); coutst; ) int

80、s1=count ( s.begin(), s.end(), 1) ; int t1=count ( t.begin(), t.end(), 1) ; cout (s1=t1 & s.length()=t.length() ? yesn : non);C+标准算法9/11/20241496. 动态内存动态内存 ( Dynamic Memory )揭示:许多问题不知道数据量的大小,需要所运用的数据结构具有扩容能力;许多问题要求时间性能甚于空间占用充分利用堆空间(动态内存)是解决这些问题的关键做法:理解堆空间的使用场合,学习堆空间的使用方法9/11/2024150使用容器,便是自动使用堆内存例如,

81、从abc.txt中读取诸段落: ifstream in ( abc.txt ) ; vector ps ; / ps.reserve(1100) ; 可以预留 for ( string s ; getline ( in, s ) ; ) ps.push_back(s) ;预留是减小频繁扩容造成的数据移动开销9/11/2024151若每个数据的处理,都要用到已经处理的数据时,保存历史数据,则可以改善时间性能例如,统计一亿之内的素数个数(原始版): bool isPrime(int n) int sqrtn=sqrt(n*1.0); for(int i=2; i=sqrtn; +i) if(n%i

82、=0) return false; return true;/-int main() int num=0; for(int i=2; i=100000000; +i) if(isPrime(i) num+; coutnumendl;/-9/11/2024152空间换时间版int main() bitset* p = new bitset; p-set(); for(int i=2; itest(i) for(int j=i*i; jsize(); j+=i) p-reset(j); int num=0; for(int i=2; itest(i) num+; coutnum0 & scanf(

83、%s,b)0) printf(%s, strlen(a)=strlen(b) & cnt(a)=cnt(b)? yesn : non);9/11/2024155STL容器是为方便编程设计的,它当然也考虑了性能,但与直接操纵堆空间相比还是间接了些例如,一亿之内的筛法求素数个数(版): int sieveSTL() bitset& p = *new bitset; p.set(); int num=100000000-2; for(int i=2; i=10000; +i) if(p.test(i) for(int j=i*i; jp.size(); j+=i) if(p.test(j) & nu

84、m-) p.reset(j); delete &p; return num;9/11/2024156一亿之内的筛法求素数个数(低级编程版): int sieve() unsigned int* p=(unsigned int*)malloc(12500000); memset(p,-1,12500000); int num = 100000000-2; for(int i=2; i=10000; +i) if(pi/32&(1i%32) for(int j=i*i; j100000000; j+=i) if(pj/32&(1j%32) & num-) pj/32 &= (1j%32); fre

85、e(p); return num;9/11/2024157低级编程与高级编程的差异低级编程与高级编程的差异在代码量上能够一定程度地反映出来,但根本的差异在于使用编程语句的抽象性程度代码越抽象,其内在的数据与操作的安全性考虑得越多,因而额外开销就越大反之,代码越低级,对数据的操作越直接,越需要程序员亲自驾驭程序的整体安全控制9/11/2024158C+程序设计教程(第二版)第七章 程序结构 Chapter 7 Program Structure 清华大学出版社 钱 能9/11/2024159n程序结构: 使程序得以运行的框架组织便是程序结构,对程序结构的研究,是为了更好地表达算法思想,使其符合编

86、译逻辑,又具有更好的可读性和可维护性n学习目标: 1. 从简单的函数层层调用,初步理解+程序结构 2. 学习合理组织程序的规则与经验,掌握扩展程序规模的基本方法9/11/2024160第七章内容1. 函数组织函数组织( Function Organization ) 2. 头文件头文件 ( Header Files ) 3. 全局数据全局数据 ( Global Data ) 4. 静态数据静态数据 ( Static Data )5. 作用域与生命期作用域与生命期 ( Scope & Lifetime ) 6. 名空间名空间 ( namespace ) 7. 预编译预编译 ( Pre-Compi

87、lation ) 9/11/20241611. 函数组织函数组织 ( Function Organization )函数:对输入参数负责,埋头做自己的事,最终返回结果函数组织:通过在函数中进行函数调用来扩展运行的规模,层层叠叠的函数构成树结构做法:将若干个函数组织成文件,又将若干个文件构成程序的办法来进行编程分工9/11/2024162对如下的函数调用关系,进行文件划分mainf2f1f3g1g2hp文件文件文件9/11/2024163. 头文件头文件 ( Header Files )原始头文件:作为共同开发的项目,为了共享彼此的过程资源(函数),将全体函数声明放在一个共用的头文件中界面头文件

88、:界定模块可用资源(函数,数据,类型等)(可由一个或几个头文件组合,其实现由他人提供),或提供他人使用的模块资源它是由软件工程师分发的、以规范项目开发为目的的资源文件做法:练习划分函数组,模仿学习构造头文件,并注意头文件的应含内容9/11/2024164/ abc.hvoid f1 ( );void f2 ( );void f3 ( );void g1 ( );void g2 ( );void p ( );void h ( );原始头文件 (包含age5的图中的一切函数声明)9/11/2024165头文件的使用:使函数调用免于声明/ a1.cpp#include”abc.h”void f1()

89、 if() p(); g1(); else g2(); h(); 9/11/2024166头文件的使用:使函数调用免于声明/ a2.cpp#include”abc.h”int main() f1(); f2(); f3();/-void f3() f1();/-void p() f3();/-9/11/2024167头文件的使用:使函数调用免于声明/ a3.cpp#include”abc.h”void h() void f2() g1(); g2();/-void g1() void g2() 9/11/2024168界面头文件/ a1.h a1.cpp提供的资源void f1();/ a2.

90、h a2.cpp提供的资源void p();/ a3.h a3.cpp提供的资源void g1();void g2();void f2();void h(); 9/11/2024169/ a1.cpp#include”a2.h”#include”a3.h”void f1() if() p(); g1(); else g2(); h(); 使用界面头文件9/11/2024170/ a2.cpp#include”a1.h”#include”a3.h”static void f3();int main() f1(); f2(); f3();void f3() f1();void p() f3();使

91、用界面头文件9/11/2024171/ a3.cpp#include”a3.h”void h() void f2() g1(); g2();void g1() void g2() 使用界面头文件9/11/20241723. 全局数据全局数据 ( Global Data )全局数据:使若干个模块在程序范围内共享(读与写)数据,是若干程序文件沟通数据的一种形式意义:模块的独立性由数据的封闭性来支持全局数据破坏了数据的封闭性,因而对小程序简单而对规范化程序则不登大雅之堂做法:练习函数之间用参数传递数据的常规形式,尽量避免使用全局数据9/11/2024173例如:对于矩阵的输入、处理和输出vector

92、vector a; / global Datavoid input ( );void transpose ( );void print ( );int main ( ) input ( ); / using a transpose ( ); / using a print ( ); / using a9/11/2024174消去全局数据:前一个过程的输出作为后一个过程的输入typedef vectorvector Mat ; Mat input ( ) ;Mat transpose ( const Mat& a) ;void print ( const Mat& a ) ;int main (

93、 ) print ( transpose ( input ( ) ) ) ;9/11/2024175在多个程序文件组成的程序中共享数据,要遵守一次定义规则/ item1.cpp#includeusing namespace std ;int n = 8 ; / define void f ( ) ;int main ( ) coutn”n”; f ( ) ;/ item2.cpp#includeusing namespace std ;extern int n ; / declarevoid f ( ) coutn”n”; 9/11/20241764. 静态数据静态数据 ( Static Da

94、ta )静态全局数据:在一个程序文件中共享的数据注意:全局数据则在多个程序文件中共享数据静态局部数据:在屡次调用的同一个函数中共享的数据9/11/2024177演示静态局部变量void func() static int a=2; a+; int b=5; b+; couta=a, b=bendl;int main() func(); func();/ a=3, b=6/ a=4, b=69/11/20241785. 作用域与生命期作用域与生命期 ( Scope & Lifetime )作用域:有很多种,变化最多的是局部作用域作用域遵守就近原则,它总是取用最贴近的名字,除非名字加前缀,则指特定

95、区域的名字生命期:实体一旦产生(定义)后,存活时间的度量作用域与生命期:作用域是编程规范,用于编译时的语法检查,生命期是程序运行中的实体存活度量,体现运行程序的内在规律名字访问遵守作用域规则,而作用域以实体存活为前提9/11/2024179处于生命期中的实体总是能被安全访问到,即使违背模块的黑盒准则int* f() static int a=1; a+; return &a;int main() int* pa = f(); *pa = 8; /ok,但偷越到了函数f的局部区 cout*pa“n”;/ 89/11/2024180生命期消亡的实体,其空间位置上的访问,得不到安全保证int* f(

96、) int a=1; int* p=&a; return p; / a的生命期在此处消亡int main() int* pa = f(); cout*pa“n”; cout*pa“n”;/ 1/ 41988309/11/2024181. 名空间名空间 ( namespace )名空间:解决名字冲突的方法所有名字都有空间归属,在一定的空间中,名字是不允许冲突的引用一个名字时,加上空间归属的前缀,就可以唯一确定该名字所对应的实体无前缀名字:很多时候,名字都是无前缀的,这是因为事先已经指定了默认名字空间如果默认名空间在两个以上,则必须注意名字冲突的可能性9/11/2024182名空间的使用/ 局部名

97、空间默认#includeusing std:cout;using std:endl;/-int abs ( int a ) return a0 ? a : -a; /-int main ( ) int a = abs ( -5 ); / 使用自定义函数 int b = std:abs ( -5 ); / 使用标准库函数 coutaendlbset(2005,12,5); / delete dp;9/11/2024193常成员函数与函数的常量参数常成员函数常成员函数 对捆绑的对象对捆绑的对象, 不允许写操作不允许写操作函数的常量参数函数的常量参数 对传递的参数对传递的参数, 不允许写操作不允许写

98、操作例如例如: bool Date:comp(const Date& a)const year = 2005; / error: 常成员函数捆绑的对象 a.year = 2003; / error: 常量对象 return year=b.yaer & month=a.month & day=a.day; 9/11/20241943. 操作符操作符 ( Operators )操作符定义就是函数定义,调用操作符就是调用函数,例如: Point operator+(const Point& a, const Point& b) Point s; s.set(a.x + b.x, a.y + b.y)

99、; return s; Point p, q; p.set(3, 2); q.set(1, 5); Point r = p + q; / p + q 等价于 operator+(p, q)一切自定义操作符都是语言中操作符的重载.操作符作用:让编译器理解人性化编程代码.从正确性上讲并不是必要的.例如,Java不允许操作符重载9/11/2024195值返回 返回临时表达式的值,例如:Point operator+(const Point& a, const Point& b) Point s; s.set(a.x+b.x, a.y+b.y); return s;引用返回 函数将处理结果放入参数中时

100、,如果有将返回值作连续操作的需要,便需要返回参数,即引用返回,例如:ostream& operator(ostream& o, const Point& d) return o”(“d.x”,“d.y”)n”;值返回与引用返回值返回与引用返回9/11/2024196成员操作符与普通操作符普通操作符: Point operator+(const Point& d)const Point s; s.set(x+d.x, y+d.y); return s; 成员操作符: Point Point:operator+(const Point& a, const Point& b)const Point

101、s; s.set(a.x+b.x, a.y+b.y); return s; p + q等价于成员操作符p.operator+(q),或者普通操作符operator(p, q) 两者必居其一.9/11/20241974. 再论程序结构再论程序结构 ( Program Structure Restatement )访问控制成员函数一般为公有public公有的成员函数在类的外部可以被使用,即外界可以调用成员函数数据成员一般为私有private私有的数据成员在外部不能被访问,即外界不能访问对象的数据分量,而只能由成员函数内部去处理公有和私有可任意设定访问控制public和private是语言提供给程序

102、员的功能9/11/2024198程序结构类定义作为头文件,如:point.h类的实现作为独立编译单元,如:point.cpp使用类的程序作为另一独立编译单元,如:f0809.cpp类的头文件和类的实现可以作为一个独立的资源提供给编程者内联的成员函数定义一般放在头文件中头文件中必须使用头文件卫士技术9/11/2024199类作用域与类定义作用域类定义作用域:从类定义结束开始,到从外面包围类定义的块结束(若类定义外无包围块,则结束于文件)使用类的程序员在类定义作用域下编程类作用域:类定义内部及成员函数定义内部实现类的程序员在类作用域下编程9/11/20242005. 屏蔽类的实现屏蔽类的实现 (

103、Shield Class Implementations )使用类的应用程序只需要类定义头文件编程实现类,也只需要类定义头文件,不需要使用类的程序细节确定了类定义(头文件),便可以从事两方面的编程而互不干涉类定义成功地屏蔽了类的实现,是类机制的技术体现9/11/20242016. 静态成员静态成员 ( Static Members )静态数据成员 在类作用域中声明,在类定义作用域中定义(一般是在main函数启动之前定义),每个该类对象都共享描述任何对象的共性状态 在多文件程序结构中,一般放在类的实现编译单元中9/11/2024202例如:例如:class Student string name

104、;public: static int num; void set(string& str) name = str; +num; ;int Student:num = 0; /静态数据成员初始化int main() Student s1, s2; s1.set(Smith); s2.set (“John”) ; couts1.num“n”; / 可行,非标准 coutStundent:num“n”;/ 访问静态成员9/11/2024203静态成员函数调用时,不捆绑对象,所以,不能直接操作对象和其成员,若需访问该类对象,必须以参数传递之.静态成员函数一般设计为公有的,以访问私有静态数据成员为目的

105、.调用方式是以类名加域操作符:后跟静态成员函数9/11/2024204例如:例如:class Student static int num; / 静态私有成员 /.public: / . static void showNum() coutnum“n”; / 访问静态私有成员 ;int Student:num = 0; / 静态数据成员初始化int main() Student s; s.showNum(); / 可行,但非标准 Student:showNum(); / 静态成员函数调用9/11/20242057. 友元友元 ( Friends )友元的作用:弥补访问控制符的不足,在外部频繁操

106、作对象(即调用成员函数),引起调用开销的增加时,可以通过直接访问对象的成员(而不是调用成员函数),而使性能明显提高.9/11/2024206例如:矩阵乘向量函数friend Vector multiply(Matrix& m, Vector& v) Vector r ( v.size(), 0); / 整型向量初始值0 r.set ( m.szl ); / 直接访问私有数据成员szl for ( int i=0; im.szl; i+ ) for ( int j=0; jm.szr; j+ ) r.vi += m.mi*m.szr+j * v.vj; return r;若以普通函数的身份实现,

107、则要大量调用成员函数去访问私有数据成员,而用友元之后,可以直接访问之9/11/2024207友元的另一作用:用在无法成员化的操作符重载中例如:class Point / friend ostream& operator(ostream& o, const Point& d ) return o(d.x,d.y“)n”; ;对于Point型对象a,由于语句couta;中的第一操作数为cout,不是Point对象,所以无法在Point类中将操作符成员化,但又想长驱直入地访问Point中的私有数据成员故用友元9/11/2024208C+程序设计教程(第二版)第九章 对象生灭 Chapter 9 Ob

108、ject Birth & Death 清华大学出版社 钱 能9/11/2024209第九章内容1.构造函数设计构造函数设计 ( Constructor Design ) 2.构造函数重载构造函数重载 ( Constructor Overload ) 3.类成员初始化类成员初始化 ( Class Member Initializations ) 4.构造顺序构造顺序 ( Constructing Order )5.拷贝构造函数拷贝构造函数 ( Copy Constructors ) 6.析构函数析构函数 ( Destructors ) 7.转型与赋值转型与赋值 ( Conversion & As

109、signment ) 9/11/20242101. 构造函数设计构造函数设计 ( Constructor Design ) 初始化要求:对象与变量的不同在于对象对应于事物,要求从诞生之时起便有明确的意义.封装性要求:初始化不是简单的参数与成员对应,而是联系参数到成员的过程.构造函数名:该过程产生对象,而不是捆绑对象的成员函数调用,因而它是特殊的成员函数形式:与变量的定义形式保持一致.构造函数原则上不能失败,也没有返回形式例外:一次性对象构造,没有对象名,与强制转换的形式一致,因而它是一个特定类型的对象.9/11/20242112. 构造函数重载构造函数重载 ( Constructor Over

110、load ) 构造函数可以重载,也可以参数默认:class Datepublic: Date(const string& s); Date(int y=2003, int m=12, int d=1); / .;int main() Date d(“2006-12-26”); Date e(2000, 12, 25); Date f(2001, 10); Date g(2002); Date h(); / .9/11/2024212若类中没有定义构造函数,则系统会默认定义一个无参空函数:class Datepublic: / 相当于定义了Date();int main() Date d; /

111、ok / .9/11/2024213任何其他的构造函数定义,都将阻止默认无参空函数的产生:class Datepublic: Date(int y, int m, int d) / .;int main() Date d; / error / .9/11/20242143. 类成员初始化类成员初始化 ( Class Member Initializations ) class StudentID int a;public: StudentID() a = 1; cout“StudentId: an; ;class Student string name; StudentID id;public

112、: Student(string n=noName) cout“Student: + n +n; name = n; ;int main() Student s(Randy);数据成员的空间分配是在构造函数被调用和其过程被执行之间的刹那间完成,在类中有对象成员时,那个刹那间便是调用对象所在类的构造函数,以创建对象空间的时机,左边的程序得到下列运行结果:StudentId: 1Student: Randy说明先成员构造,后自身构造成员构造不见显式调用,而是悄悄调用无参构造函数9/11/2024215class StudentID int a;public: StudentID(int id=0)

113、 a=id; cout“StudentId: a“n”; ;class Student string name; StudentID id;public: Student(string n=noName, int ssID=0) :id(ssID),name(n) cout“Student: nDate e 对象创建的运行顺序为: Date e9/11/2024217同一工程不同代码文件全局对象的创建没有明确顺序规定对策:不要让不同文件的全局对象互为依赖因为依赖具有先后性,而其全局对象的创建不能保证该依赖性发挥作用全局对象在main函数启动之前生成,而调试则在main函数启动之后对策:调试时,

114、应先将全局对象作为局部对象来运行观察或者,在构造函数中添加输出语句来观察运行过程9/11/2024218成员对象的构造顺序按类定义的出现顺序,最后执行自身构造函数: class A B b; C c; D d; public: A() / . ; int main() A a; 则构造顺序为bcd,然后执行A的构造函数的花括号体9/11/2024219全局数据区:全局对象,静态全局对象,静态局部对象,常对象类的静态数据成员也存放在该数据区栈区:局部对象(根据不同编译器的实现方法,临时对象可能在栈区,也可能在动态存储区,也可能一部分在栈区,一部分在动态存储区)动态存储区(也称堆区):用new申请

115、的对象除此之外,还可以指定特殊地址空间,存放对象构造位置9/11/20242205. 拷贝构造函数拷贝构造函数 ( Copy Constructors ) 对象本体与对象实体:对象本体也是对象主体,对象实体则还包括属于对象的衍生物,如,某个人体是人类对象的主体,然而某人还拥有父母,房产等属于某人的世系或资产,描述人的属性不仅仅只是人体数据从形式上看,对象除了包括数据成员,还包括指向数据的指针9/11/2024221拷贝构造函数:以本类对象为常量引用参数的构造函数:class Datepublic: Date(); Date(const Date& d); / . . .;Date x; /调用

116、无参构造函数Date y(x); /调用拷贝构造函数9/11/2024222默认拷贝构造函数:若类中没有定义拷贝构造函数,则系统会悄悄定义一个默认空拷贝构造函数: Date(const Date& d)默认拷贝构造函数体一定是空的空拷贝构造函数负责将传递的对象到新创的对象做对象本体的位对位拷贝(甚至连指针值都相等,即与参数对象拥有共同的资源)拷贝构造函数体的工作不负责位对位对象复制,一般来说,它负责资源分配和由此而来的指针修改9/11/2024223拷贝构造函数体的工作不负责位对位对象复制,一般来说,它负责资源分配和由此而来的指针修改class Person char* pName;publi

117、c: Person(char* pN=noName) pName = new charstrlen(pN)+1; if(pName) strcpy(pName,pN); Person(const Person& s) pName = new charstrlen(s.pName)+1; if(pName) strcpy(pName, s.pName); Person() delete pName; ;9/11/20242246. 析构函数析构函数 ( Destructors )对象结束其生命时,会被系统悄悄地销毁(析构).即对象本体空间与名字脱离关系.对象结束生命时,若对象本体与对象实体不同,

118、则需要人为地进行资源释放,以保证对象本体失效之前,资源被收回9/11/2024225定义析构函数的目的:定义析构函数的目的:由于对象本体与实体不同,所以要进由于对象本体与实体不同,所以要进行对象占有资源的释放工作行对象占有资源的释放工作一般来说,一个类,若有人为定义的一般来说,一个类,若有人为定义的拷贝构造函数,则也应该定义析构拷贝构造函数,则也应该定义析构函数因为对象创建中有资源要获函数因为对象创建中有资源要获得分配,则对象失效前必应先释放得分配,则对象失效前必应先释放资源资源9/11/20242267. 转型与赋值转型与赋值 ( Conversion & Assignment ) 对象转型

119、一个构造函数,含有一个其他数据类型的参数,显然其意义为,用该参数类型的值可以创建本对象.从另一方面看,参数类型的值可以转换为本对象. class Student public: Student(const string& n); / . ; void fn(Student& s); int main() string t=“jenny”; fn(t); / 参数为string,却能匹配Student类型 9/11/2024227对象转型的规则:n只会尝试含有一个参数的构造函数只会尝试含有一个参数的构造函数n如果有二义性,则会放弃尝试如果有二义性,则会放弃尝试n推导是一次性的,不允许多步推导推导

120、是一次性的,不允许多步推导 fn(“Jenny”)不能匹配不能匹配 void fn(const Student& s); 因为:因为:”Jenny” - string - Student 经历了两步经历了两步.9/11/2024228对象赋值即对象拷贝:两个已经存在的对象之间的复制Person d, g;d = g; / 对象赋值对象赋值便是使用类中的赋值操作符如果类中没有定义赋值操作符,则系统悄悄地定义一个默认的赋值操作符: Person& operator=(const Person& p) memcpy(*this, *p, sizeof(p); 9/11/2024229当对象本体与对象

121、实体不同时,则对象赋值操作符与拷贝构造函数一样,必须自定义:class Person char* pName;public: Person(char* pN=noName); Person(const Person& s); Person& operator=(const Person& s) if(this=&s) return s; delete pName; pName = new charstrlen(s.pName)+1; if(pName) strcpy(pName,s.pName); return *this; Person() delete pName; ;定义赋值操作符:排除

122、客体对象与本对象同一的情况释放本对象的资源申请客体对象相同大小的资源空间拷贝客体对象的资源到本对象9/11/2024230C+程序设计教程(第二版)第十章 继承 Chapter 10 Inheritance 清华大学出版社 钱 能9/11/2024231第十章内容1.继承结构继承结构 ( Inheritance Structure ) 2.访问父类成员访问父类成员 ( Access Fathers Member ) 3.派生类的构造派生类的构造 ( Constructing Derived Classes ) 4.继承方式继承方式 ( Inheritance Mode )5.继承与组合继承与组

123、合 ( Inheritance & Composition ) 6.多继承概念多继承概念 ( Multi-Inheritance Concept ) 7.多继承技术多继承技术 ( Multi-Inheritance Technology ) 9/11/20242321. 继承结构继承结构 ( Inheritance Structure ) 宇宙万事万物都是分类分层的,解决问题可以从事物之间的上下关系中着手这是继承引入程序设计的前提例如:已知鸟的属性,鸭子是什么的描述便可以在鸟的基础上进行:除了是鸟之外,还会一种区别于其他鸟的特殊的嘎嘎叫 因为鸭子不会飞,于是就在继承鸟的属性中去掉会飞的属性9/

124、11/2024233派生类对象结构派生类对象结构 对于下面的继承关系:对于下面的继承关系:class Father int a,b;public:/ 成员函数;class Son:public Father int c;public: / 成员函数;基类对象子类对象子类对象空间总是不小于基类对象cabab基类部分子类添加部分9/11/20242342. 访问父类成员访问父类成员 ( Access Fathers Member ) 捆绑子类对象可以访问父类成员函数和自身成员函数;捆绑基类对象只能访问基类成员函数,不能访问子类成员函数,这是自然的: Student ds(“Jenny); Grad

125、uateStudent gs(“Smith”); ds.addCourse(3, 2.5); ds.display(); gs.addCourse(3, 3.0); gs.display(); gs.getQualifier(); ds.getQualifier(); / error9/11/2024235子类也是基类的用户,其成员函数不能访问基类的私有成员但子类可以区别于外来用户,让基类protected成员允许子类对象访问而不许外来对象访问. 例如,对于基类: class Father int a; protected: void fp() couta; public: void prin

126、t() couta; ;外来用户:外来用户:void fn() Son d; d.print(); / ok d.disp(); / ok d.fp(); / error Father f; f.print(); / ok f.fp(); / error子类用户:子类用户:class Son : public Father int b;public: void disp() fp(); / ok print(); / ok void ed()a+; / error;9/11/20242363.构造子类对象构造子类对象 ( Constructing Objects of SubClass ) 默

127、认构造:如果子类没有构造函数,则调用默认构造函数,默认构造函数转而先调用默认父类构造函数,完成父类对象部分的构造如果父类的上面还有父类,则依次递归9/11/2024237自定义构造:为了规定父类构造函数的调用方式而不是默认调用,需要自定义子类构造函数,并且,在构造函数定义体的初始化列表中描述父类构造函数的调用形式描述形式与对象成员构造的描述一致GraduateStudent ( const string& pN, Advisor& adv ) : Student(pN), advisor(adv), qualifierGrade(0) 9/11/2024238覆盖(overlap):子类定义了

128、与祖先类(父类,或者父类的父类.)名字相同的成员class Studentpublic: void display(); / .;class GraStudent:public Studentpublic: void display(); /overlap / .;void fn() GraStudent gs; gs.display(); /call GraStudent:display()捆绑子类对象访问成员函数,则首先匹配子类,然后父类,再父类的父类,依此类推9/11/2024239拷贝构造:子类若没有定义拷贝构造函数,则子类对象在拷贝创建时先调用父类的拷贝构造函数,再完成自己的位对位拷

129、贝父类若没有定义拷贝构造函数,则子类对象在拷贝创建中调用父类默认的拷贝构造函数赋值操作符原理相似9/11/20242404. 继承方式继承方式 ( Inheritance Mode )继承可以公有继承,保护继承和私有继承公有继承是普通继承,基类可以为大多数应用服务也可以重复继承保护继承是“单传”继承,只继承给自己的后代,应用是以子孙的公有成员函数来对外展开服务的私有继承是“绝版”继承,该基类只继承直接的子类,而不考虑让子类再继承下去9/11/2024241继承体系中,子类可以在祖先类成员可见的范围中调整其访问控制属性class A int a1;public: int a2;class B :

130、 private Apublic: using A:a2; / a2从私有转为公有 using A:a1; / 错: a1不可见;int main() B d; d.a2 = 1; / ok9/11/20242425. 继承与组合继承与组合 ( Inheritance & Composition ) 组合:类中含有对象成员,称为组合式包含继承:子类继承了父类,称为子类对象对父类对象的继承式包含继承和组合都重用了类设计继承重用场合,父类对象就在自己家里,无须捆绑父类对象便能对其操作但是操作受到了父类访问控制属性设定的制约组合重用场合,使用对象成员的操作需捆绑对象成员,而且只能使用对象的公有成员继

131、承部分派生部分其他数据成员Student对象Advisor对象研究生对象组合式包含继承式包含9/11/2024243继承型的Circle类头文件:#includepoint.hclass Circle : public Point double radius;public: /成员函数;组合型的Circle类头文件:#includepoint.hclass Circle Point point; double radius;public: /成员函数;公有成员函数实现不同,但可以让界面相同,从而不影响编程者使用继承与组合在于实现技术不同9/11/2024244使用含有继承和组合的子类:只要外界

132、不直接或无法直接使用该子类的祖先类成员或对象成员,仅提供公有的成员函数,则对外界来说,无所谓该子类的继承式包含还是组合式包含(包含组合或继承的哪种头文件都可):#include”point.h”#include“circle.h” /组合或继承int fn() Circle c(Point(2.3, 5.6), 7); c.moveTo(1, 2); c.modifyRadius(3); / 9/11/20242456. 多继承概念多继承概念 ( Multi-Inheritance Concept ) 多继承:一个实体,来自多个类对象的组合因而它同时也可以继承多个基类来实现9/11/20242

133、46多继承的主要技术问题:由由于于子子类类可可以以访访问问多多个个基基类类,而而基基类类之之间间没没有有专专门门的的协协调调,所所以以,基基类类可可能能出出现现相相同同的的名名字字,这这于于子子类类来来说说,要要访访问问这这种种名名字字增增加加了了编编程程的的复复杂杂性性,不不得得不不要要在在名字前加上前缀名字前加上前缀然而,这种同名也许意义相同,操作这种名然而,这种同名也许意义相同,操作这种名字本身便是一种分别性操作,不合逻辑字本身便是一种分别性操作,不合逻辑于是便寻求一种分离共性,统一基类的解决于是便寻求一种分离共性,统一基类的解决办法办法(见见CH12.5).但是,不同的父类拥有共性基类

134、,访问基类但是,不同的父类拥有共性基类,访问基类成员仍然存在相同名字冲突问题成员仍然存在相同名字冲突问题9/11/20242477. 多继承技术多继承技术 ( Multi-Inheritance Technology ) 解决多继承基类名字冲突问题将多个父类看成同一基类下的不同子类,而所需要派生的子类来自于这些不同子类于是形成一个棱形结构Bed sleep()Sofa watchTV()SleeperSofa FoldOut()Furniture weightgetWeight()setWeight()9/11/2024248多个不同子类(如,床、沙发)在继承基类的方式上采取虚拟继承,它的作用

135、是,当对象创建上产生基类重叠时,略去重复产生基类对象空间的行为: class Bed : virtual public Furniture public: void sleep()const coutSleeping.n; ;多继承这种性质的子类: class SleeperSofa:public Bed,public Sofa public: void foldOut()constcoutFoldout.n; ;9/11/2024249C+程序设计教程(第二版)第十一章 基于对象编程 Chapter 11 Object-Based Programming清华大学出版社 钱 能9/11/202

136、4250第十一章内容1.抽象编程抽象编程 ( Abstract Programming )2.编程质量编程质量 ( Programming Quality ) 3.分析分析Joses问题问题 ( Analysis the Joses Problem )4.基于过程的实现基于过程的实现 ( Procedure-Based Solving )5.基于对象的实现基于对象的实现 ( Object-Based Solving )6.程序维护程序维护 ( Program Maintenance )7.程序扩展程序扩展 ( Program Extension )9/11/20242511. 抽象编程抽象编程

137、 ( Abstract Programming )抽象分行为抽象和数据抽象两种行为抽象:通俗地说便是将一个行为序列归并(抽象)为一个行为的过程.例如:将取碗筷、盛饭、盛菜,扒一口饭、夹一筷菜、再扒一口饭、再夹一筷菜的若干重复,然后放下碗筷的过程归并为吃饭.数据抽象:通俗地说,就是将事物归类,或者说,将事物看成是一定型号、规格的数据,然后将性质接近的数据归纳(抽象)为一类.例如:将圆、三角形、长方形归为形状类.9/11/2024252数据结构数据结构 一系列性质相同的数据一系列性质相同的数据, 组织成一定的逻辑结组织成一定的逻辑结构构, 并带有自身的一系列操作并带有自身的一系列操作例如:整型向量

138、例如:整型向量 不同整型值是一系列性质相同的数据;其数不同整型值是一系列性质相同的数据;其数据集合存放在向量中,便是组织成线性存储据集合存放在向量中,便是组织成线性存储结构;向量自身有创建、复制、扩建、增删、结构;向量自身有创建、复制、扩建、增删、修改等操作,外加排序、查找等算法可以调修改等操作,外加排序、查找等算法可以调用用.所以,整型向量在语言中是一种具体的所以,整型向量在语言中是一种具体的数据结构。数据结构。9/11/2024253抽象编程抽象编程 通过抽象的方法来减少编程工作量或有效地减轻通过抽象的方法来减少编程工作量或有效地减轻编程难度称为编程难度称为抽象编程抽象编程将问题通过功能分

139、解,各个击破的编程方法将问题通过功能分解,各个击破的编程方法(过过程化编程程化编程)是一种是一种以行为抽象为主的抽象编程以行为抽象为主的抽象编程将问题通过实体分析,分层分类地实现抽象数据将问题通过实体分析,分层分类地实现抽象数据类型,从而进行简单应用编程类型,从而进行简单应用编程(基于对象编程基于对象编程)是一种是一种以数据抽象为主的抽象编程以数据抽象为主的抽象编程,这种抽,这种抽象编程,通过数据类型复用,方便编程,方便象编程,通过数据类型复用,方便编程,方便维护和扩展,其效果比过程化编程更好维护和扩展,其效果比过程化编程更好9/11/2024254编程编程 语言中没有许多具体的数据类型,要解

140、决实际问语言中没有许多具体的数据类型,要解决实际问题,很大部分工作是要建立数据模式与实际问题,很大部分工作是要建立数据模式与实际问题的对应,也就是建立抽象数据类型的过程题的对应,也就是建立抽象数据类型的过程对象化编程就是基于分层分类的抽象数据类型之对象化编程就是基于分层分类的抽象数据类型之具体编程,它能更好地实现数据结构和算法,具体编程,它能更好地实现数据结构和算法,便是将便是将N.Wirth的程序公式:的程序公式: 程序程序=算法算法+数据结构数据结构具体化为:具体化为: 程序程序=算法算法+抽象数据类型抽象数据类型9/11/20242552. 编程质量编程质量 ( Programming

141、Quality ) 可读性:通过使用更好的编程方法可以从本质上改进可读性通过使用定义良好的算法和语句控制结构,可以局部改进算法的可理解性通过学习和使用优秀程序员的编码习惯,采用一贯的编码风格,可以增强可读性注意:高级程序员与初学者对可读性的把握分寸是不同的 9/11/2024256易编程性:只是对采用好的编程方法或更高级语言而言,抽象程度越大,越易编程对象化编程在抽象数据类型的创建上做了大量的工作,因而赢得了应用程序编写的方便与快捷9/11/2024257安全性:编程在算法设计上是挖空心思的劳动过程,而在运用语言对之描述上应该潇洒自在:容易表达,不容易出错,运行上更安全。好的编程方法能够帮助程

142、序员实现潇洒编程抽象数据类型中可以将大量安全代码嵌入其中,从而使应用编程潇洒自在实现抽象数据类型的过程本身也是使用其他抽象数据类型的应用编程,同样也充满着潇洒与自在9/11/2024258可维护性:指局部修改不影响系统全局的总体性能,而系统产生的问题通常可以通过局部维护(修改或更换部件)来解决。模块化编程使程序可拆装,可局部修改,而不影响整体性能与工作,因而可维护性强,过程化编程是将过程模块化,具有一定的可维护性;对象化编程是将数据类型也模块化,从而导致更方便的维护性能9/11/2024259可扩充性:指系统扩展时,只增加扩展代码,而对原系统的正常运作只作很少的修改甚至不修改功能扩展的影响:过

143、程化编程,会涉及多处扩展代码与原系统不和谐所带来的修改对象化编程,如果是抽象数据类型的功能扩展,则不会影响原系统的正常运行,而只是支持应用程序中增加的一些扩展代码而已;如果是应用程序功能扩展,那是使用抽象数据类型的抽象代码扩展,其修改量比之低级代码要少9/11/2024260效率:前提:代码量大不等于运行量大效率除了看速度,还要看损耗,既要考虑编程方便,又要考虑运行性能(时空效率)1过程化程序代码量少,但对象化程序的代码量相对较多,但不占系统空间2过程化程序编写安全代码代价大,因而不系统,对象化程序可以方便地在抽象数据类型中嵌入安全代码,从而导致了代码量大的问题,如果让过程化程序达到其安全指标

144、,为此增加的代码量比对象化程序更多9/11/20242613. 分析分析Joses问题问题 ( Analysis the Joses Problem )过程化分析:按实现过程分析,功能划分,从而得到几个算法步骤:获得小孩数n,开始位置s,间隔数m创建环链表循环数数,排除n-1个小孩输出剩下的小孩编号(胜利者)善后工作(清除环链表)9/11/2024262对每个算法步骤,分而治之:例如,环链表操作的复杂性,在循环数数中体现初始化和善后处理都必须由程序员一人承担,工作量大,容易产生错误9/11/2024263对象化分析:先考虑一些能对应抽象数据类型的实体,如,以小孩为元素的链表类,问题本身也是一个

145、类,然后脱离问题,先来定制类或者重用类,最后基于类型来实现算法(简单得多)链表类:它有创建,增加,减少,修改,搜索等操作问题(Josephus)类:它有创建,获得胜利者操作9/11/20242646. 程序维护程序维护 ( Program Maintenance )维护要求:数个数m,m若改为根据当前小孩的序号与m的和来确定个数m,则在进行数个数前,先要进行表达式计算,这一切,都是在Josephus类中暗中完成的,所以只要修改Josephus类的 getWinner 无须修改应用程序,便可维护好系统9/11/20242657. 程序扩展程序扩展 ( Program Extension )扩展要

146、求既要求直接数m个数,最后所获得的1个胜利者,也要求按维护要求的数法所获得的胜利者此时,便要保留原来的求胜利者的成员函数,再设计实现一个成员函数,作为功能扩展再在原系统中增加调用该成员函数的语句由于小孩转圈的结构没有变,所以该扩展要求就不涉及链表修改9/11/2024266C+程序设计教程(第二版)第十二章 多态 Chapter 12 Polymorphism 清华大学出版社 钱 能9/11/2024267第十章内容1.继承召唤多态继承召唤多态 (Inheritance Summon up Polymorphism) 2.抽象编程的困惑抽象编程的困惑(Abstract Programming

147、Perplexing) 3.虚函数虚函数(Virtual Function) 4.避免虚函数误用避免虚函数误用(Avoiding Misusing Virtual Function) 5.精简共性的类精简共性的类 (Simplify Class with Generality) 6.多态编程多态编程(Polymorphic Programming) 7.类型转换类型转换(Type Conversions ) 9/11/20242681. 继承召唤多态继承召唤多态 (Inheritance Summon up Polymorphism) 祖孙互易的说明:晚辈是前辈的后继,晚辈是前辈的一种反之不确

148、,前辈不是晚辈,也不是晚辈的一种这一概念也影响到类的继承操作,甚至其指针操作:Student是GraduateStudent的祖先则:Student s, *pS;GraduateStudent gs, *pGS;s = gs; / okgs = s; / errorpS = static_cast(&gs); / okpGS = static_cast(&s); / error9/11/2024269同化效应同化效应 class Studentpublic: / . . . void display() cout“UnderGraduaten”; ;class GraduateStudent

149、 :public Studentpublic: / . . . void display() coutdisplay();ps = static_cast(&gs);ps-display();结果:UnderGraduateGraduate这意味着子类对象就是父类对象的性质是一句空话因为操作起来原来是将子类对象粗暴的同化,根本扼杀了子类对象的个性于是,子类对象与父类对象共存的容器便失去了意义,因为容器中没有子类对象,全部同化成一种父类对象了,世界失去了丰富多彩性!9/11/2024271关键技术关键技术 void fn(Student& x) / . int main() Student s;

150、 GraduateStudent gs; fn(s); / 显示大学生信息 fn(gs); / 显示研究生信息结果:UnderGraduateGraduate子类对象赋值给父类对象,使得子类对象不复存在,这没有异议Student s = gs;否则,让父类对象行使子类职能就太离谱了当子类对象的地址赋给父类对象指针(或引用)时,应让子类对象维持其状态的完整性,并且还要显露其子类的特征即多态性:9/11/20242722. 抽象编程的困惑抽象编程的困惑 ( Abstract Programming Perplexing )类型域方案可以做到,即实现fn函数如下:void fn(Student& x

151、) switch(x.type) case Student:STUDENT: x.calcTuition(); break; case Student:GRADUATESTUDENT: GraduateStudent& rx = static_cast(x); rx.calcTuition(); break; 但不敢恭维这种方法,因为它导致类编程与应用编程互相依赖,因而破坏了只关注局部细节的抽象编程9/11/2024273破坏抽象编程的后果是:可维护性,可扩展性受到伤害若增加一个博士类,则类代码与应用程序代码都得改,而这本来不是应用程序的份内事因而呼吁从语言内部来支持这种多态性9/11/202

152、42743. 虚函数虚函数 ( Virtual Function ) 于是应用程序便有多态,而且fn函数实现非常简捷:void fn(Student& x) x.display() int main() Student s; GraduateStudent gs; fn(s); / 显示大学生信息 fn(gs); / 显示研究生信息结果:UnderGraduateGraduate类中采用虚函数:class Studentpublic: virtual void display() cout“UnderGraduaten”; ;class GraduateStudent :public Stud

153、entpublic: virtual void display() cout“Graduaten”; ;注:子类同名函数上的virtual可省9/11/2024275多态性使得应用程序使用类体系中的祖孙对象共存的复杂局面达到了一种编程自在境界程序员从使用孤立的类(抽象数据类型),到使用分层的类,并且让各种对象“同场竞技”,充分展现其个性.尝到了对象化编程的真正乐趣+类机制的虚函数就是冲着让类编程实质性地支持应用编程中对家族化对象操作依赖的目的,从而面向对象来分析、设计和解决问题9/11/20242764. 避免误用虚函数避免误用虚函数 ( Avoiding Misusing Virtual F

154、unction ) class Basepublic: virtual void fn(int x) coutBasen; ;class Sub : public Basepublic: virtual void fn(double x) coutSubn; ;void test(Base& b) b.fn(3.5);int main() test(Base(); test(Sub();n子类重载父类成员函数不能传播“虚”性 右边程序的运行结果说明了这一点: Base Base9/11/2024277函数重载对于编译识别来说,返回类型是不起作用的void fn(int);int fn(int)

155、;对于fn(2);无法判断应该调用哪个函数而使调用遭遇编译失败对于虚函数来说,声明:virtual Base* fn();virtual Sub* fn();应看作不能分辨其多态调用的虚函数,但却引编译认为是同一个虚函数而获得通过这是语言技术上的一种处理,事实上,如果一个多态函数正在处理Sub类的对象,则它仍可以通过返回的Sub对象指针,继续处理Sub对象,似乎更自然9/11/2024278注意事项:多态性是通过成员函数捆绑不同类型的对象来体现的,所以,虚函数一定是成员函数,而且,静态成员函数都不行,因为它不捆绑对象,同样,构造函数也不行,因为它只产生对象,也不捆绑对象可是,析构函数却可以是虚

156、函数,事实上,鼓励类继承体系中的每个类最好其析构函数都是虚函数一旦设置了虚函数,就与编译器达成了滞后联编的协议,函数必定分离于当前运行的模块,因而就不可能是内联函数了9/11/20242795. 精简共性的类精简共性的类 ( Simplify Class with Generality )孤立的类之间有一些共性,希望减少一些冗余代码通常不能因此而构成上下级关系的类层次,因为会产生不良反应:基类如果扩展功能,势必连累子类9/11/20242806. 多态编程多态编程 ( Polymorphic Programming )将共性的类通过继承由共性构成的基类来实现或许会比较明智因为两者的扩展彼此之间

157、不会带来干扰而且还可任意派生子类类的继承体系决定之后,其基类的多态成员函数也可确定通常可以在子类之间进行比较,同名不同功能的函数往往可以置成虚函数class Account protected: / . . .public: virtual void display ( ) const; virtual void withdrawal ( double amount ) ;9/11/2024281多态编程中,会遇到各种子类混在一个集合中的情形,这正是多态大展身手的时候针对左边的类体系,有多态的应用代码:vector a;/ . . .for(int i=0; iarea();class Sha

158、pepublic: / . . . virtual void area();class Circle:public Shapepublic: void area() / . . .;class Triangle:public Shapepublic: void area() / . . .;/ . . .9/11/20242827. 类型转换类型转换 ( Type Conversions ) 调用虚函数是一种多态的方法,它是将运行中无法知道的对象交给系统去自动辨认类型从而作出准确的操作使用动态转型策略:另一种实现是在运行中,判断对象的类型,从而可以准确的捆绑调用确定的成员函数,这是一种主动辨认

159、对象类型的编程策略9/11/2024283辨认对象的类型,首先应该知道其属于哪个类系然后确定要判断的子类名称再行编码例如,针对每个对象进行操作:nSavings类对象,余额增加以1%计算的利息;nChecking类对象,余额增加以0.05%计算的利息 vector a;/ .Checking* pC;Savings* pS;for(int i=0; ia.size(); +i) if(pC = dynamic_cast(ai) pC-deposit(pC-getBalan()*0.05); else if(pS = dynamic_cast(&ai) pS-deposit(pS-getBala

160、n()*0.1); ai-display();动态转型9/11/2024284动态转型只限于多态类,而静态转型适合更一般的类对象例如,没有多态性的学生与研究生之间的转换: Student* ps=static_cast(&gs);例如:无类型指针到某类型指针的转换: void fn(void* dd) Student* pp=static_cast(&dd); /. 静态转型9/11/2024285许多标准类库函数的声明为了适应大多数编程场合,做了完美的设计例如,max:const char* max(const char* s1, const char* s2) return strcmp(

161、s1, s2)0 ? s1 : s2;但是,编程总是调用标准库函数和自处理交替进行的,当返回但是,编程总是调用标准库函数和自处理交替进行的,当返回的对象要进行写操作时,由于类型的限制,便不能再正确的对象要进行写操作时,由于类型的限制,便不能再正确编码下去了然而,事实上,从一开始,对象完全是可修编码下去了然而,事实上,从一开始,对象完全是可修改的,只是由于函数调用的结果,而使对象性质被迫发生改的,只是由于函数调用的结果,而使对象性质被迫发生了变化为了将这种本质上可以修改的对象回复可修改的了变化为了将这种本质上可以修改的对象回复可修改的状态,使用常量转型状态,使用常量转型int fn() char

162、* p = max(hello,world); / error char* p = const_cast(max(“hello”,”world”);/ok / 常量转型9/11/2024286C+程序设计教程(第二版)第十三章 抽象类 Chapter 13 Abstract Class 清华大学出版社 钱 能9/11/2024287第十三章内容1.抽象基类抽象基类(Abstract Base-Class) 2.抽象类与具体类抽象类与具体类(Abstract & Concrete Classes) 3.深度隔离的界面深度隔离的界面 (Interface Which Deeply Parted)

163、4.抽象类作界面抽象类作界面(Abstract Class As Interface) 5.演绎概念设计演绎概念设计(Deducting Concept Design) 6.系统扩展系统扩展(System Extension) 7.手柄手柄(Handle) 9/11/20242881. 抽象基类抽象基类 ( Abstract Base-Class ) 继承体系的多态问题:n继承体系反映的是事物的分层分类,它是倒树状,顶端是基类越顶端越抽象,越底端越具体n基类往往是一种概念表达,或者像Account类那样,仅仅提取了各个子类的共性,本身并不构成有意义的实体这种基类的成员都是为子类提供的特别是虚函

164、数,不同的子类有不同的实现,于基类中的定义版本并无意义class Account / .public: virtual void withdrawal(double amount) return; / 无意义 ;9/11/2024289虚函数都是从基类传播的,靠基类指针来掀动多态因而,为多态性之故,非得在基类设置虚函数不可: class A; / 基类中无fn()成员class B : public Apublic: virtual void fn();class C : public Apublic: virtual void fn();void f(A* pa) pa-fn(); / 编译

165、错void g() f(&B(); f(&C();9/11/2024290编译器的语法规定,如果一个函数被调用了,则该函数若只有声明而没有定义是万万不能的 class Apublic: virtual void fn(); / 无定义; class B : public Apublic: void fn();class C : public Apublic: void fn();void f(A* pa) pa-fn(); / 链接错void g() f(&B(); f(&C();9/11/2024291纯虚函数纯虚函数 class Account/抽象类public: virtual voi

166、d withdrawal(double amount)=0;Account a(“3”, 30); / 错:创建对象之故前提:不同的子类表现不同的行为多态,而基类并不产生对象只是摆设目的:为了安全性,将基类抽象化,仅用来继承,不准许产生对象手法:设置纯虚函数。即在基类虚函数声明后面加上”=0”,不须提供定义体,表明为抽象类任何抽象类若有创建对象操作,则是非法的9/11/20242922. 抽象类与具体类抽象类与具体类 ( Abstract & Concrete Classes )运行下列程序: void g(Display* d) d-init(); d-write();int main()

167、g(&Monochrome(); g(&SVGA();结果为:MonochromeColorAdapterclass Displaypublic: virtual void init() = 0; virtual void write() = 0;class Monochrome : public Display virtual void init() virtual void write() cout“Monochromen”; ;class ColorAdapter : public Displaypublic: virtual void write() cout“ColorAdapter

168、n”; ;class SVGA : public ColorAdapterpublic: virtual void init();如果要解决的问题涉及单一的类对象,无须继承.如果涉及许多相关的类对象,则需建立一个具有多态的继承体系.也许该继承体系的基类只是用来继承,别无目的,但抽象基类却足以将问题中的概念描述清楚.9/11/20242933. 深度隔离的界面深度隔离的界面 ( Interface Which Deeply Parted ) 类定义头文件若有修改,将引起类的实现和类的应用程序重新编译.界面不变是指外界可以访问的公有成员不变,而不是类定义头文件不变.类的实现细节可能涉及私有成员的变

169、更.例如:下列两个类界面相同,但类定义不同,头文件自然就不同了其类的实现也不会相同日期的年月日版日期的年月日版class Date int year, month, day;public: Date(const string& s); Date(int n=1); Date(int y, int m, int d); Date operator+(int n)const; Date& operator+=(int n); Date& operator+(); friend ostream& operator( ostream& o, const Date& d );日期的天数版日期的天数版cl

170、ass Date int absDay;public: Date(const string& s); Date(int n=1); Date(int y, int m, int d); Date operator+(int n)const; Date& operator+=(int n); Date& operator+(); friend ostream& operator( ostream& o, const Date& d );9/11/2024294设法将界面和类定义分离,来实现深度界面隔离.该类作为界面,便不会影响应用编程该类作为界面,便不会影响应用编程class Date Date

171、mid* m_p;public: Date(const string& s); Date(int n=1); Date(int y, int m, int d); Date operator+(int n)const; Date& operator+=(int n); Date& operator+(); friend ostream& operator( ostream& o, const Date& d );DateMid类即为以前的类即为以前的Date类类:class DateMid int year, month, day;public: DateMid(const string& s

172、); DateMId(int n=1); DateMid(int y, int m, int d); DateMid operator+(int n)const; DateMid& operator+=(int n); DateMid& operator+(); friend ostream& operator( ostream& o, const DateMid& d );9/11/2024295界面类的实现,便是Date到DateMid的转换#include”date.h”#include”datemid.h”Date:Date(const string s):m_p(new DateMi

173、d(s)Date:Date(int n):m_p(new DateMid(n)Date:Date(int y, int m, int d):m_p(new DateMid(y,m,d)Date:Date(const DateMid& d):m_p(new DateMid(d)Date:Date() delete m_p; Date Date:operator+(int n)const return *m_p + n; Date& Date:operator+=(int n) *m_p += n; return *this; Date& Date:operator+() *m_p += 1; r

174、eturn *this; ostream& operator(ostream& o, const Date& d) return o*(d.m_p);这样一来,类DateMid的实现也不影响界面Date.以Date类作为分界线,便可以进行充分的抽象编程了9/11/20242964. 抽象类作界面抽象类作界面 ( Abstract Class As Interface )抽象类抽象类IDate作界面作界面class IDatepublic: virtual IDate() virtual IDate& operator+(int n) = 0; virtual IDate& operator+=

175、(int n) = 0; virtual IDate& operator+() = 0; virtual void print(ostream& o)const=0;IDate& createDate(int y, int m, int d);IDate& createDate(int n);IDate& createDate(const string s);inline ostream& operator( ostream& o, const IDate& d) d.print(o); return o;作为界面的Date类转而去调用DateMid类的对应成员,何不将界面Date类做成抽象类

176、呢?!这样一来,应用程序可以通过类体系的多态性来自在使用Date类另一方面,DateMid的实现可以作为继承界面类Date的具体类.9/11/2024297可以还具体类Date以本来面貌,但这次是从IDate类继承而来:class Date:public IDate int year, month, day;public: Date(const string& s); Date(int n=1); Date(int y, int m, int d); Date operator+(int n)const; Date& operator+=(int n); Date& operator+();

177、friend ostream& operator() return sp; SonyHandle(Sony* pp) : sp(pp);对象的析构是自动的让对象指针做成对象,便可以免遭人工释放所带来的误操作之苦“外套”就是指针的类形式9/11/2024305class SonyHandle Sony* sp; int* count;public: SonyHandle(Sony* pp); SonyHandle(const SonyHandle& sh); Sony* operator-(); SonyHandle& operator=(const SonyHandle& sh); SonyH

178、andle();再让手柄类具有一些操作,使得该类能够进行普通的指针操作将其实用起来9/11/2024306C+程序设计教程(第二版)第十四章 模板 Chapter 14 Template 清华大学出版社 钱 能9/11/2024307n思考角度 C+程序是一些类型和函数,编程就是设计类型和函数,然后按C+的程序结构来组织n模板编程 世界上万事万物都具有相似性,许多类型和函数尽管处理的数据不同,但其行为也具有相似性,将相似的类型归为类型族以及相似的函数归为函数族的编程,就是模板编程n编程方法的侧重点 面向对象编程解决类体系中的不同对象行为表现 模板编程解决独立类之间的不同对象行为表现 多个独立类

179、可以是多个类继承体系,因而,面向对象编程与模板编程是融合的 9/11/2024308第十四章内容1.函数模板函数模板(Function Template) 2.函数模板参数函数模板参数(Function Template Parameter) 3.类模板类模板(Class Template) 4.实例化与定做实例化与定做(Instantiation & Specialization) 5.程序组织程序组织(Program Organization) 6.模板的多态模板的多态(Template Polymorphism) 7.高级编程高级编程(Advanced Programming) 9/11

180、/20243091. 函数模板函数模板 ( Function Template ) 理想的函数重载是针对不同的参数做不同的事而形如:void swap(Type& a, Type& b) Type t=a; a=b; b=t;的重载函数系所定义的行为序列却相同这种形式的重载意义不大9/11/2024310templatevoid swap(T& a, T& b) T t=a; a=b; b=t;定义函数模板来表示重载函数系:模板形参函数模板名数据形参函数模板定义体9/11/2024311templatevoid swap(T& a, T& b) T t=a; a=b; b=t;int fn()

181、 int ix=6, iy=7, ia=3, ib=5; swap(ix, iy); /产生函数定义体 swap(ia, ib);/不产生函数定义体 /.函数模板用法:以函数模板名作调用的函数名,以数据实参作参数传递9/11/20243122. 函数模板参数函数模板参数 ( Function Template Parameter ) 数据参数是按值的性质匹配的,所以相容类型之间可以转换类型参数是按名字匹配的,更为苛刻templatevoid swap(T& a, T& b) T t=a; a=b; b=t;int add(double a, double b) return a+b;int f

182、n() int ia=3; double db=5.0; char s1=good, s2=better; int x = add(ia, db); / ok swap(ia, db); / error swap(s1, s2); / error9/11/2024313数据形参分:引用型参数(提倡用本项)非引用型参数引用型参数分:引用型参数常量引用型参数9/11/2024314常量引用型形参其数据形参可以是临时对象,所以,通过显式转换可以规定模板的产生形式但是,不能被隐式转换的数据形参,其显式模板类型指定失效:templateT const& max(T const& a, T const&

183、b) return a b ? b : a;int main() int ia=3; double db=6.7; db = max(ia, db); db = max(static_cast(ia), db); db = max(&ia, db); /error9/11/2024315引用型形参其数据形参与数据实参捆绑,故数据实参应为左值表达式:templatevoid swap(T& a, T& b) T t=a; a=b; b=t;int main() int ia=3; const int cb=5; swap(ia, 7); / error:7不是左值不是左值 swap(ia, 7)

184、; / error:哪怕类型指定也不济哪怕类型指定也不济 swap(cb, 7); / error:const int会引起操作错误会引起操作错误 swap(ia, 7); / error:同上同上9/11/2024316函数模板重载参数有不同的形式(是否引用,是否常量,甚至是否特殊类型),不同形式的参数,其行为不同,这是模板重载的前提例如:字串比较很特殊,指针比较与对象比较亦不同,则:templateT const& max(T const& a, T const& b) return a b ? b : a;templateT* const& max(T* const& a, T* con

185、st& b) return *a *b ? b : a;const char* const& max(const char* const& a, const char* const& b) return strcmp(a,b) 0 ? b : a;int main() int ia=3, ib=7; char *s1=”hello”, *s2=”hell”; cout*max(&ia, &ib)”n”; / match the second template coutmax(s1, s2)”n”; / match the max function coutmax(ia, ib)”n”; / m

186、atch the first template9/11/20243173. 类模板类模板 ( Class Template ) 有些类,比如容器类,处理一种类型的对象与处理另一种类型的对象方法一样,但是就因为处理的对象类型不同,而使该类必须重新定义例如:CatList, DogList, RabbitList等9/11/2024318通过模板类定义,可以解决代码冗繁问题:templateclass Nodepublic: Node(const T& d):c(d),next(0),pref(0) T c; Node *next, *pref;9/11/2024319又例如下面的模板类定义,含有

187、模板类的成员:templateclass List Node *first, *last;public: List(); void add(const T& c); void remove(const T& c); Node* find(const T& c)const; void print()const; List();9/11/2024320与类定义相似,成员函数的定义一般放在类定义的外部,与类定义分开以有利于程序的组织放在模板类定义外部的成员函数的定义形式为:templateList:List():first(0),last(0)templatevoid List:add(const

188、T& n) Node* p = new Node(n); p-next = first; first = p; (last ? p-next-pref : last) = p;List构造函数9/11/2024321类模板(class template):侧重于模板的描述(声明或定义),例如:Templateclass List;/类模板声明template /类模板定义class List Node *first, *last;public: List(); void add(const T& c); void remove(const T& c); Node* find(const T&

189、c)const; void print()const; List();模板类(template class):侧重于模板的使用形式例如:List /T为类型形参List /Dog是类型实参形如List形式的类,不管是形参还是实参,本书都称之为为模板类9/11/2024322高级模板概念中,类模板声明,如:Templateclass List等同于带有形式类型参数的模板类,如:List并将其作为一种类型看待所以本质上不区分类模板与模板类9/11/2024323模板类的使用将直接透入对象的构造,因此,允许模板类带有值参便理所当然了templateclass bitset;但是类型值参将导致不同的类

190、模板描述,因而决定了不同的值参值,生成不同的模板类bitset a;bitset b;a = b; / error值参值应为编译能识别的常量,值参多为整型.9/11/20243244. 实例化与定做实例化与定做 ( Instantiation & Specialization ) 实例化遵循一次定义原则第一次用特定的类型实参使用模板类时,将引起类模板的实例化(产生类定义)List dList;/产生实例化List eList;/不再实例化第一次用特定的类型实参使用模板函数时,将引起函数模板的实例化(产生函数定义)9/11/2024325实例化与实施的操作有关,构造对象所触发的实例化,只实例化其

191、构造函数,不实例化类模板的其他成员函数但可以通过显式请求,强制整体实例化template List;/实例化整个模板类List dList;/实例化构造函数dList.add(3.6);/实例化add成员dList.add(5.8);/不再实例化,只是简单调用List iList;/不再实例化iList.add(5);/不再实例化9/11/2024326对于特定的类型实参,希望其行为不同于类模板所规定的操作,可以根据该实参来定做,定做的模板称为模板铸件(或称特制模板),如:templateclass List Node *first, *last;public: List(); void ad

192、d(const Cat& c); void remove(const Cat& c); Node* find(const Cat& c)const; void print()const; List();9/11/2024327模板铸件可为类,亦可为模板若为模板,则其定做称为局部定做 templateclass A . ;局部定做1templateclass A ; /A为模板局部定做2templateclass A ; /A为模板使用时要注意:A dCat; /按A匹配A dd;/按A匹配A cCat; /错:A还是A?9/11/20243285. 程序组织程序组织 ( Program Org

193、anization ) 包含方式模板使用的方式,不但创建了模板函数定义或模板类定义,还实施了函数调用,或者创建了对象,实施了对象操作.因此,除了需要函数模板声明或类模板定义之外,还需要函数模板定义和类模板的实现.也就是说,任何使用模板之前,编译应该能够事先看到整个模板的说明.这就是包含方式的由来.9/11/2024329模板使用/x.cpp#include”tlist.h”int main() List dList; dList.add(3.6); dList.print(); List iList; iList.add(5); iList.add(8); iList.print();模板说明

194、/ tlist.h#ifndef TLIST#define TLISTtemplatestruct Node . ;template /类模板定义class List . ;template /类模板成员函数实现List:List():first(0),last(0)templatevoid List:add(const T& n) . / .#endif / TLIST9/11/2024330分离方式通过关键字export来规定编译和链接模板的方式,分离模板定义和模板实现,让使用模板者无须与模板实现见面9/11/2024331模板实现/ tlist.cpp#include”tlist.h”e

195、xport templateList:List():first(0),last(0)export templatevoid List:add(const T& n) . / .模板定义/ tlist.h#ifndef TLIST#define TLISTtemplatestruct Node . ;export template /类模板定义class List . ;#endif / TLIST模板使用(与包含方式同)/x.cpp#include”tlist.h”int main() List dList; dList.add(3.6); dList.print(); List iList;

196、 iList.add(5); iList.add(8); iList.print();9/11/20243326. 模板的多态模板的多态 ( Template Polymorphism ) 不同类型的对象,作相同名字的操作,却表现为不同的行为,便是多态虚函数所表现的多态是基于类继承体系的,而模板的多态却适合于任何孤立的类由对象捆绑同名操作,以求识别不同的行为,得等到运行过程中获得传递的对象之后动多态由实例化模板类,进而创建对象,来规定对象的操作,便可以在对象创建中,确定对象未来将要进行捆绑的同名操作的行为静多态9/11/2024333静多态class Dog;class Cat;templat

197、eclass List;templatevoid fn(list& a) a.print(); /其行为取决于a的类型 int main() List dlist; dlist.add(); List clist; clist.add(); fn(dlist);/定义void fn(list) fn(clist);/定义fn的重载函数9/11/20243347. 高级编程高级编程 ( Advanced Programming ) 静多态无须动态联编,而同样可以实现多态/ application1.cpp#include”interfacetv.h”Templatevoid operating(

198、TV& tv) tv.adjustVolume(); tv.switchChannel();/ interfacetv.h#ifndef HEADER_INTERFACETV#define HEADER_INTERFACETVtemplateclass InterfaceTV TV tv;public: void adjustVolume() tv.adjustVolume(); void switchChannel() tv.switchChannel(); ;#endif / HEADER_INTERFACETV9/11/2024335抽象编程代码/ application2.cpp#in

199、clude”interfacetv.h”#include”sony.h”#include”natinal.h”void f() InterfaceTV ts; ts.operating(); InterfaceTV tn; tn.operating();sony类定义与实现代码/ sony.h/ sony.cppNational类定义与实现代码/ national.h/ national.cpp9/11/2024336泛型编程模板编程是归纳类族和函数族的编码工作泛型编程是解决具体问题的编程,包括应用编程与面向对象编程相呼应,是一种利用静多态技术来展示对象行为的编程方法由于实际情况中,充斥着类的

200、继承体系与别的类继承体系甚至孤立类的交互作用,所以,问题解决方案中往往面向对象编程与泛型编程两种方法并用动多态与静多态并存,模板机制与类机制特别是虚函数机制技术并施9/11/2024337C+程序设计教程(第二版)第十五章 异常 Chapter 15 Exception 清华大学出版社 钱 能9/11/2024338n异常是一种程序控制机制,与函数机制独立和互补 函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.n异常设计目的: 栈机制是一种高度节律性控制机制,面向对象编程却要求对

201、象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。异常设计出来之后,却发现在错误处理方面获得了最大的好处9/11/2024339第十五章内容1.错误处理的复杂性错误处理的复杂性(Error Proccessing Complexity) 2.使用异常使用异常(Using Exception) 3.捕捉异常捕捉异常(Catching Exception) 4.异常申述异常申述(Exception Description) 5.异常继承体系异常继承体系(Exception Inheritance System)

202、 6.异常应用异常应用 (Exception Applications) 7.非错误处理非错误处理(Non-Error Proccessing)9/11/20243401. 错误处理的复杂性错误处理的复杂性 ( Error Proccessing Complexity ) 传统的错误处理方式:1 遇到错误,终止运行,低级粗暴2 遇到错误,循调用返回给上层函数一个错误信息,忽略了模块体系3 遇到错误,改变全局错误变量的值,并函数返回,破坏了程序结构4 遇到错误,调用事先设计好的下层错误处理函数,可惜错误往往不是自己所能解决的了的!9/11/2024341程序运作实情n可以将程序运行看作是诸多模块

203、的工作及相互往来n一个模块发生的错误,需要借助于另一个模块进行修复n每个模块包含一个层层调用的函数体系n发生错误后,如果层层函数返回,则不但动作缓慢,而且错误信息丢失,失去处理的针对性n发生错误,说明模块服务失败,需要在主体模块中找说法给予必要的处理.n不同的系统,其主体模块是不同的,决定了“说法”也是不同的计算元素位置if(vi溢出) 说明元素不存在if(i=size)下标溢出接受外来申请if(vi溢出) 说明资源不足应用模块2应用模块1向量模块9/11/2024342函数机制,本质上是一种过程控制机制对面向对象程序来说,进行从发现错误到处理错误的设计,是一个超出过程控制能力的庞大控制体系9

204、/11/20243432. 使用异常使用异常 ( Using Exception ) n错误处理示意:放弃一棵子树,循调用链跳到祖先函数发现错误处9/11/2024344异常处理的语法:1 框定异常发生的可能范围2 定义异常处理3 抛掷异常int main(int argc, char* argv) ifstream in(argv1); try if(!in) throw string(argv1); catch(string s) couts+File Opening Error.n; return 1; for(string s; getline(in, s); ) couts“n”;9

205、/11/2024345异常擅长于在函数调用链中腾挪void f0() throw string(“请处理n”); void f1() f0(); void f2() f1(); void f3() f2(); void f4() f3(); void f5() f4(); int main() try f5(); catch(string) cout“处理中n”; 9/11/2024346系统中提供了标准异常在标准模块中若发生标准错误,会自动抛掷标准异常,无须程序员指定throw地点int* f() return new int1000000000;int main() int* sp; tr

206、y sp=f(); catch(bad_alloc) coutBad Allocation.n; return 1; /using sp;9/11/20243473. 捕捉异常捕捉异常 ( Catching Exception ) 类型匹配异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到例如下列代码不会输出“int exception.”,从而也不会输出“Thats ok.”intint main() main() try try throwthrow H; catchcatch(intint) coutint exc

207、eption.n; coutThats ok.n;9/11/2024348异常可以函数为依托,层层布网:下层捕捉失败后,抛掷会继续上传,触动上层捕捉直至系统最后一道防线,例如,下列代码将输出“int exception.”:void f() try throw 12; catch(char) cout“char exception.n”; int main() try f(); catch(int) coutint exception.n; 9/11/20243494. 异常申述异常申述 ( Exception Description ) 布网是按类型,以便捕捉申述也是按类型,规定抛掷的类型范

208、围申述是函数对抛掷的过滤网格没有函数的异常类型申述,抛掷是穿透不出函数的9/11/2024350没有申述的函数,默认为任何抛掷都可穿透该函数class A;class B;void f1()throw(A,B);void f2();void f3()throw();对于函数g中的调用,可能捕捉到函数f1的和函数f2的异常抛掷,但捕捉不到函数f3中的任何抛掷注:同一个函数,其声明与定义中的申述应一致class A;class B;void f1()throw(A,B) if(.) throw A;void f2() if(.) throw B;void f3()throw() if(.) thr

209、ow A;void g() try f1(); f2(); f3(); catch(A) cout“exception in An”; catch(B) cout“exception in Bn”; 9/11/2024351捉不住处理:抛掷而无布网捕捉的异常将直逼系统的最后一道防线void f() if(.) throw A;void g() try f(); catch(B) cout“exception Bn”; int main() g();throw A将穿透函数f,g和main,抵达系统的最后一道防线激发terminate函数该函数调用引起运行终止的abort函数最后一道防线的函数可

210、以由程序员设置从而规定其终止前的行为9/11/20243525. 异常继承体系异常继承体系 ( Exception Inheritance System ) 异常抛掷的捕捉是类型匹配的.关乎类型的匹配自然纳入类机制的范畴.因而,异常的多态是必不可少的.有了异常的多态,就不但可以按函数调用链层撒网,捕捉抛掷,还可以按类的层次进行体系化异常捕捉9/11/2024353抛掷子类可被基类捕捉 try if(.) throw OtherErr; catch(MyException& me) if(string(me.what()=HardwareErr). / 停机 if(string(me.what(

211、)=PerformErr). / 加工件离机,停机 if(string(me.what()=OtherErr). / 粗暴停机 catch(exception& e) coutStandardExceptionn; / 停机 9/11/20243546. 异常应用异常应用 ( Exception Applications ) 异常特别适合于一种封闭的模块想要设法把出乎意料的信息传出去作为封闭的模块,其输出信息的状态模式是规定的,但当出现出乎意料的情况,无法完成模块任务的时候,就谈不上输出信息了用异常指引到另一个状态处理器是明智的9/11/2024355构造函数:正象一个封闭的模块,输出状态是一

212、个新创对象任何创建过程中的错误(例如,动态内存申请失败等)都会导致模板的出乎意料这时候的状态是不能接续后继操作的,如:捆绑对象的操作因为没有对象而招致失败如果敢于正常返回,则又招致荒谬的结果class Date int year, month, day; void init(int y, int m, int d) if(y1|m12|d31) return; /? public: Date(int y=2000, int m=1, int d=1) :year(y),month(m),day(d) init(); void print() coutyear“-”month“-” day“n”

213、; ;int main() Date ad(2000,13,1); ad.print(); / 荒谬:月份为139/11/2024356异常操作应该恢复到对类对象进行创建和使用以前的状态void f() Date(2000,13,1); ad.print();int main() try f(); /其他操作 catch(out_of_range) cout“对象创建失败,改换门庭”; class Date int year, month, day; void init(int y, int m, int d) if(y1|m12|d31) throw out_of_range; public

214、: Date(int y=2000, int m=1, int d=1) :year(y),month(m),day(d) init(); void print() coutyear“-”month“-” day“n”; ;9/11/2024357动态转型:有虚函数的类也称多态类动态转型针对多态类多态类的多态是以指针来表现的动态转型也主要针对转变多态类的指针(引用)若多态类的指针不是指向动态转型的类型对象,则转型结果为空指针以此作为判断指针是否指向所期待对象的依据int main() Circle* cp; cp = new Circle; Rectangle* rp; rp = new Re

215、ctangle; f(cp); /由虚函数导出的多态 f(rp); g(cp); /由动态转型导出的多态 g(rp); delete cp,rp;class Shapepublic: virtual double area()=0; ;class Circle :public Shapepublic: double area() void getRadius();class Rectangle :public Shapepublic: double area() void rotate();void f(Shape* sp) coutarea(); /同名操作void g(Shape* sp)

216、 /动态转型 if(dynamic_cast(sp) sp-getRadius(); else sp-rotate();9/11/2024358动态转型:对引用的动态转型,其结果不是指针,而是一个对象,因此无法以指针值判断也无法根据结果值(对象)来得出对象的类型因而,即使转型失败也无法逆转后继执行次序但是引用动态转型如果失败,将会抛掷一个标准异常bad_cast,这就给程序员提供了实施多态的手段int main() Circle c; Rectangle r; h(c); /引用的动态转型 h(r);class Shapepublic: virtual double area()=0; ;cl

217、ass Circle :public Shapepublic: double area() void getRadius();class Rectangle :public Shapepublic: double area() void rotate();void h(Shape& rs) try dynamic_cast(rs).getRadius(); catch(bad_cast) rs.rotate(); 9/11/20243597. 非错误处理非错误处理 ( Non-Error Proccessing ) 循环控制n多重循环控制中,当某个条件满足时,需要立刻退出所有循环体时,一般用g

218、oto语句反而比较现实否则一重一重地退,编程复杂,性能也连累这是在一个函数中时的做法n在一个循环中,遇到一个函数调用链,当某个条件满足时,需要立刻退出函数调用链所在的循环时,就不能用goto语句,因为不在同一个函数中此时,程序员渴望函数能奇迹般地跳跃用异常控制结构便能满足编程要求9/11/2024360class A;void f3() if(.) throw A; / 退出for循环void f2() f3();void f1() f2();int main() try for(.; .; .) f1(); catch(A) /输出循环结果 9/11/2024361递归控制递归函数本身就是一个深不可测的调用链,要立刻从调用链中彻底退出,若走函数控制这条线,那么,必须逐个进行函数返回而异常控制可以实现瞬间跳跃class A.;void f(int n) if(.) throw A; f(n-1);int main() int x=12; try f(x); catch(A) /输出结果 9/11/2024362

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

最新文档


当前位置:首页 > 商业/管理/HR > 营销创新

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