《从问题到程序》PPT课件.ppt

上传人:枫** 文档编号:575633990 上传时间:2024-08-18 格式:PPT 页数:108 大小:337.52KB
返回 下载 相关 举报
《从问题到程序》PPT课件.ppt_第1页
第1页 / 共108页
《从问题到程序》PPT课件.ppt_第2页
第2页 / 共108页
《从问题到程序》PPT课件.ppt_第3页
第3页 / 共108页
《从问题到程序》PPT课件.ppt_第4页
第4页 / 共108页
《从问题到程序》PPT课件.ppt_第5页
第5页 / 共108页
点击查看更多>>
资源描述

《《从问题到程序》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《从问题到程序》PPT课件.ppt(108页珍藏版)》请在金锄头文库上搜索。

1、从问题到程序从问题到程序第七章,指针第七章,指针程序执行中数据存于内存。程序执行中数据存于内存。在可用期间数据有确定存储位置,占据一些存储单元。在可用期间数据有确定存储位置,占据一些存储单元。内存单元的编号:内存单元的编号:地址地址。机器语言通过地址访问数据。高级语言用变量等作为机器语言通过地址访问数据。高级语言用变量等作为存储单元存储单元/地址的抽象。地址的抽象。建立变量就是安排存储。赋值时存入,用值时从中提取建立变量就是安排存储。赋值时存入,用值时从中提取外部变量外部变量/静态变量有全局存在期,程序执行前安排存储静态变量有全局存在期,程序执行前安排存储位置,保持到程序结束。位置,保持到程序

2、结束。自自动变量在函数调用时安排存储,至函数结束。再调用动变量在函数调用时安排存储,至函数结束。再调用时重新安排存储时重新安排存储。7.1 地址与指针地址与指针变量变量存在期存在期就是它占据所安排存储的期间。就是它占据所安排存储的期间。任何变量在存在期间总有确定存储位置,有固定任何变量在存在期间总有确定存储位置,有固定地址地址。寄存器变量可能放在寄存器,无地址。本章不考虑寄存器变量。寄存器变量可能放在寄存器,无地址。本章不考虑寄存器变量。变量存在时有地址,地址用二进制编码,因此可能成为变量存在时有地址,地址用二进制编码,因此可能成为程序处理的程序处理的数据数据。问题:地址作为数据有什么用?问题

3、:地址作为数据有什么用?若程序可以处理对象地址,就可通过地址处理相关对象。若程序可以处理对象地址,就可通过地址处理相关对象。对象(如变量)地址也被作为对象(如变量)地址也被作为数据数据,地址值地址值/指针值指针值。以地址为值的变量称为以地址为值的变量称为指针变量指针变量/指针指针(pointer)。指针是一种访问其他对象的手段指针是一种访问其他对象的手段,利用这种机制能更灵,利用这种机制能更灵活方便地实施对各种对象的操作。活方便地实施对各种对象的操作。指针的主要操作指针的主要操作指针赋值:将程序对象的地址存入指针变量。指针赋值:将程序对象的地址存入指针变量。间接访问:通过指针访问被指对象。间接

4、访问:通过指针访问被指对象。指针还能保存其他对象的地址。下面讨论以变量为例。指针还能保存其他对象的地址。下面讨论以变量为例。指针指针p保存着变量保存着变量x地址,也说指针地址,也说指针p指向指向x。图示:图示:在在C中使用指针常能写出更简洁有效的程序。有些问题中使用指针常能写出更简洁有效的程序。有些问题必须用指针处理。必须用指针处理。指针在大型复杂软件中使用广泛。指针使用的水平是评指针在大型复杂软件中使用广泛。指针使用的水平是评价人的价人的C程序设计能力的重要方面。程序设计能力的重要方面。C指针灵活指针灵活/功能强。掌握有难度,易用错,应特别注功能强。掌握有难度,易用错,应特别注意。应特别注意

5、使用指针的常见错误,意。应特别注意使用指针的常见错误,注意!注意!指针是变量,可赋值,其指向可以改变。指针是变量,可赋值,其指向可以改变。现在现在p指向指向x,以后可能指向以后可能指向y。通过通过p访问被指对象的访问被指对象的语句语句目前访问目前访问x,后来就访问后来就访问y。这种新的灵活性很有用。这种新的灵活性很有用。7.2 指针变量的定义和使用指针变量的定义和使用指针有类型,只能保存特定类型的变量的地址指针有类型,只能保存特定类型的变量的地址指向指向int的指针的指针p只能指向只能指向int变量。变量。p所指也看作所指也看作int,从,从p间接访问当作间接访问当作int。常说。常说int指

6、针指针p1等。等。定义指针需指明定义指针需指明指向类型指向类型。定义指向。定义指向int的的指针变量指针变量:int *p, *q;指针变量可以与其他变量一起定义:指针变量可以与其他变量一起定义:int *p, n, a10, *q, *p1, m;指指针针是是变变量量,可可赋赋值值取取值值,有有定定义义域域与与存存在在期期。应应赋赋给给类型正确的指针值,取出的值是特定类型的指针值。类型正确的指针值,取出的值是特定类型的指针值。用用(int*)表示整型指针的类型,其他类似。表示整型指针的类型,其他类似。指针操作指针操作取地址运算符取地址运算符& 和间接访问操作和间接访问操作 *。都是一元运算符

7、。都是一元运算符取地址运算取地址运算& 写在变量描述写在变量描述(如变量名)前(如变量名)前取取变量地址,是对应类变量地址,是对应类型的指针值,可赋给类型合适的指针。例:型的指针值,可赋给类型合适的指针。例:p = &n; q = p; p1 = &a1;多个指针可能同时指向同一变量。变量相等是值相等,多个指针可能同时指向同一变量。变量相等是值相等,两个指针变量相等说明它们指向程序里同一东西。两个指针变量相等说明它们指向程序里同一东西。间接运算间接运算间间接接运运算算得得到到被被指指针针所所指指的的变变量量,这这种种表表达达式式可可以以像像普普通变量一样使用。设通变量一样使用。设p指向指向n。

8、间接赋值:间接赋值:*p = 17;这里写这里写*p相当于直接写相当于直接写n。另一个赋值:另一个赋值:m = *p + *q * n; /* 访问访问n三次三次 */+ *p; /* 使变量使变量n的值加的值加1,变成,变成18 */(*p)+; /* 使变量使变量n的值再加的值再加1,变成,变成19。*/*p += *q + n; /* 变量变量n被赋以新值被赋以新值57 */q = &a0;/* 指针指针q指向了数组指向了数组a的元素的元素 */指针作为函数参数指针作为函数参数指针作为函数参数有特殊意义,利用这种参数可写出能修改调用指针作为函数参数有特殊意义,利用这种参数可写出能修改调用

9、时环境的函数。时环境的函数。函数调用处的环境函数调用处的环境指在调用函数的位置能访问的变量全体。指在调用函数的位置能访问的变量全体。前面函数的特点:可使用调用处环境中变量的值(通过参数),前面函数的特点:可使用调用处环境中变量的值(通过参数),但不能修改这些变量(数组参数除外)。但不能修改这些变量(数组参数除外)。在函数在函数f里调用里调用g,可将可将f的局部变量作为实参。但用的局部变量作为实参。但用g改变改变f局部局部变量的唯一方法是将变量的唯一方法是将g的返回值赋给的返回值赋给f的局部变量。的局部变量。这种方法局限性太强,例如无法在一个函数调用中修改两个局部这种方法局限性太强,例如无法在一

10、个函数调用中修改两个局部变量的值。利用变量的值。利用指针可以改变这种情况。指针可以改变这种情况。例例:定定义义函函数数swap,希希望望用用它它交交换换两两个个变变量量的的值值。因因为要改变两个变量,无法通过返回值解决。为要改变两个变量,无法通过返回值解决。下面定义不行:下面定义不行:void swap0 (int x, int y) int t = x; x = y; y = t;int f() int a = 5, b = 10; swap0(a, b); /*不行不行*/函数内修改形参,不会改变调用时的实参。函数内修改形参,不会改变调用时的实参。分析分析:要(在一函数里)通过:要(在一函

11、数里)通过调用函数调用函数g修改调用处的修改调用处的变量(如局部变量),必须在变量(如局部变量),必须在g里里掌握掌握这个这个变量。变量。用指针可以解决问题。用指针可以解决问题。把把m的地址(也是值)通过指针参数的地址(也是值)通过指针参数p传给传给g,函数内对函数内对p间接访问就能操作间接访问就能操作m,包括对包括对m赋值(改变赋值(改变m)。)。例,通过函数调用把变量值设置为例,通过函数调用把变量值设置为3:void set3(int * np) *np = 3;使用实例:使用实例:int main() int n, m; set3(&n); /* 设置设置n和和m */ set3(&m)

12、; printf(%d, %dn, n, m); return 0;请请回忆回忆scanf的情况。的情况。通过参数改变调用环境的方案通过参数改变调用环境的方案包括三方面:包括三方面:1.函数定义中用函数定义中用指针参数指针参数;2.函数内用函数内用间接操作间接操作实际变量;实际变量;3.调用时以被操作调用时以被操作变量的地址变量的地址作为实参。作为实参。函数函数swap可定义为:可定义为:void swap(int *p, int *q) int t = *p; *p = *q; *q = t;交换变量交换变量m和和n的值,调用形式是:的值,调用形式是: swap(&m, &n);swap的的

13、参参数数类类型型是是(int *),实实参参必必须须是是合合法法的的整整型型变变量的地址。量的地址。设有变量定义:设有变量定义:int a10, k;调用调用swap的实例:的实例:swap(&a0,&a5); swap(&a1,&k);介介绍绍标标准准库库函函数数scanf时时,强强调调在在接接受受输输入入的的变变量量前前必必须写须写&,就是将变量的地址传给,就是将变量的地址传给scanf。scanf采采用用与与swap一一样样的的技技术术,通通过过间间接接访访问问为为指指定定变量赋值,把输入的值赋给指定变量。变量赋值,把输入的值赋给指定变量。例:改造上章输入整数值并检查值范围的函数,引进指

14、例:改造上章输入整数值并检查值范围的函数,引进指针参数,使之能更好处理输入错误。针参数,使之能更好处理输入错误。前面实现里用特殊整数值指明输入出错,有很大缺点前面实现里用特殊整数值指明输入出错,有很大缺点(可能不存在合适的值)。(可能不存在合适的值)。指针参数提供了另一种方法:指针参数提供了另一种方法: 函数增加一个指针参数,通过它送回读入值。用函数返函数增加一个指针参数,通过它送回读入值。用函数返回值传递函数执行的状态信息。回值传递函数执行的状态信息。返回返回1表示输入成功,表示输入成功,0表示输入未能正常完成。表示输入未能正常完成。 新函数定义:新函数定义: int getnumber(c

15、har prompt, int imin, int imax, int repeat, int *np) int i; for (i = 0; repeat=0 | irepeat; +i) printf(%s, prompt); if (scanf(%d, np)!=1 | *npimax) printf(Correct range %d, %d.n, imin, imax); while (getchar() != n) ; else return 1; return 0;前面的调用现在可以重写为:前面的调用现在可以重写为:getnumber(Choose a range 0, n. In

16、put n: , 2, 32767, 5, &m);getnumber(Your guess: , 0, m-1, 5, &guess);更更合适的调用形式:合适的调用形式:if (getnumber(Your guess: , 0, m-1, 5, &guess) = 0) /* 处理输入出错的程序片段处理输入出错的程序片段 */这类函数的形参为指针,实参必须是合法变量地址。这类函数的形参为指针,实参必须是合法变量地址。 这里可以看到标准库函数这里可以看到标准库函数scanf的样子。通过返回值的样子。通过返回值表示函数的工作情况,是表示函数的工作情况,是一种一种常用技术。常用技术。 与指针有

17、关的一些问题与指针有关的一些问题空指针值:空指针值:一个特殊指针值,表示指针变量闲置(未一个特殊指针值,表示指针变量闲置(未指向任何变量)。唯一对任何指针类型都合法的值指向任何变量)。唯一对任何指针类型都合法的值空指针值用空指针值用0表示,标准库专门定义了符号常量表示,标准库专门定义了符号常量 NULLp = NULL; 和和p = 0; 相同相同前一写法易看到是指针,用时必须包含标准头文件。前一写法易看到是指针,用时必须包含标准头文件。指针初始化指针初始化指针变量定义时可用合法指针值初始化:指针变量定义时可用合法指针值初始化:int n, *p = &n, *q = NULL;若若没没有有初

18、初始始化化,外外部部指指针针和和局局部部静静态态指指针针自自动动初初始始化化为用空;局部自动指针不自动初始化。为用空;局部自动指针不自动初始化。指针使用中的常见错误指针使用中的常见错误使用指针的最常见错误是使用指针的最常见错误是非法间接访问非法间接访问:在指针未指:在指针未指向合法变量的情况下做间接。如:向合法变量的情况下做间接。如:int f (.) int *p, n = 3; *p = 2; .p没有初始化,没有初始化,没有指向合法变量。没有指向合法变量。“悬空指针悬空指针”指值不是(当时)合法的变量地址的指针指值不是(当时)合法的变量地址的指针变量,也常被称为变量,也常被称为“野指针野

19、指针”。间接访问悬空指针是严重错误,后果可能很严重。间接访问悬空指针是严重错误,后果可能很严重。常见错误写法(设常见错误写法(设p是悬空是悬空int指针,指针,n是是int变量):变量):swap(p, &n); scanf(., p); scanf(., n);编编译译程程序序不不能能发发现现scanf的的错错误误。有有些些系系统统可可能能对对第第一个例子(假设一个例子(假设p未初始化)给出警告未初始化)给出警告。间接访问空指针也同样无理,是非法的。间接访问空指针也同样无理,是非法的。通用指针通用指针 类型类型(void*),可以指向任何变量可以指向任何变量。声明:声明:int n, *p;

20、double x, *q; void *gp1,*gp2;任何指针值可以赋给通用指针(不必转换)。例:任何指针值可以赋给通用指针(不必转换)。例: gp1 = &n; / gp1指向指向n(值是值是n的地址)的地址) gp2 = &x; / gp2指向指向x若通用指针若通用指针gpt指向指向g,g类型是指针类型是指针pt的指向类型,将的指向类型,将gpt赋给赋给pt(要写强制转换)通过要写强制转换)通过pt保证正确访问保证正确访问g。gp1 = &n;p = (int*)gp1; /*合法,合法,p是是(int*) */q = (double*)gp1;/*不合法,不合法,q是是(double

21、*) */其他使用方式没有任何保证。其他使用方式没有任何保证。编译程序不能识别强制转换错误。编译程序不能识别强制转换错误。指针类型转换并不改变指针值指针类型转换并不改变指针值将整型变量将整型变量n的地址赋给通用指针的地址赋给通用指针gpt,gpt存的就是存的就是n的地址。赋回整型指针不会有问题。的地址。赋回整型指针不会有问题。将该地址赋给将该地址赋给double指针,就造成了混乱。指针,就造成了混乱。指针类型代表一种观点。被整型指针所指的变量总看成指针类型代表一种观点。被整型指针所指的变量总看成是整型的变量;被双精度指针指向是整型的变量;被双精度指针指向。指针转换是观点转换。从整型指针转换到通

22、用指针就是指针转换是观点转换。从整型指针转换到通用指针就是丢掉类型信息。保证恢复到原有类型(转回整型指针)丢掉类型信息。保证恢复到原有类型(转回整型指针),被指对象还可用。,被指对象还可用。通用指针通用指针不能做间接运算不能做间接运算被普通指针指向的变量的类型明确,间接后可以作为被普通指针指向的变量的类型明确,间接后可以作为该类型的变量使用该类型的变量使用通用指针可以指向任何变量,通过通用指针间接访问通用指针可以指向任何变量,通过通用指针间接访问的意义无法确定。通用指针没提供被指对象的类型信的意义无法确定。通用指针没提供被指对象的类型信息,所以不能通过它们直接使用被指对象息,所以不能通过它们直

23、接使用被指对象通用指针最无用,唯一用途就是提供指针值通用指针最无用,唯一用途就是提供指针值标准库的某些函数使用了通用指针(后面会看到)标准库的某些函数使用了通用指针(后面会看到)7.3 指针与数组指针与数组C指指针针与与数数组组关关系系密密切切,以以指指针针为为媒媒介介可可以以完完成成各各种种数数组操作。常能使程序更加简洁有效。组操作。常能使程序更加简洁有效。用指针做数组操作同样要用指针做数组操作同样要特别注意特别注意越界错误。越界错误。指指针针和和数数组组的的关关系系是是C语语言言特特有有的的,除除了了由由C派派生生出出的的语言(如语言(如C+),),一般语言中没有这种关系。一般语言中没有这

24、种关系。指向数组元素的指针指向数组元素的指针类型合适的指针可以指向数组元素。类型合适的指针可以指向数组元素。假定有定义:假定有定义:int *p1, *p2, *p3, *p4;int a10 = 1,2,3,4,5,6,7,8,9,10; 可以写:可以写:p1 = &a0; p2 = p1;p3 = &a5; p4 = &a10; p4没没指向指向a的元素,是指向的元素,是指向a最后元素向后一个位置。最后元素向后一个位置。C语言保证这个地址存在,但写语言保证这个地址存在,但写 * *p4 是错误的是错误的。 写数组名得到数组首元素地址,元素类型的指针值写数组名得到数组首元素地址,元素类型的指

25、针值。“p1 = &a0;”可简写为:可简写为: p1 = a;指针运算指针运算当指针当指针p指向数组元素时说指向数组元素时说p指到了数组里指到了数组里。这时由。这时由p可可以访问被以访问被p指的元素,还可访问数组的其他元素。指的元素,还可访问数组的其他元素。例:例:p1指向指向a首元素,值合法(首元素,值合法(a0的地址),的地址),p1+1也合法(也合法(a1的地址)。的地址)。p1+2、p1+3、也合法,分也合法,分别为别为a其他元素的地址。由它们可间接访问其他元素的地址。由它们可间接访问a各元素。各元素。例:例: *(p1 + 2) = 3;/* 给给a2赋值赋值 */p2 = p1

26、+ 5; /* 使使p2指向指向a5 */也可由指向非首元素的指针出发访问数组其他元素:也可由指向非首元素的指针出发访问数组其他元素: *(p2 + 2) = 5; /* 给给a7赋值赋值 */可用减法访问所指位置之前的元素:可用减法访问所指位置之前的元素: *(p2 - 2) = 4; /* 给给a3赋值赋值 */通过指针访问数组元素时必须保证不越界。通过指针访问数组元素时必须保证不越界。运运算算取取得得的的指指针针值值(即即使使不不间间接接访访问问)必必须须在在数数组组范范围围内(可过末元素一位置),否则无定义。内(可过末元素一位置),否则无定义。这类运算称为这类运算称为“指针运算指针运算

27、”。其他常用指针运算:。其他常用指针运算:用指针运算得到的值做指针更新:用指针运算得到的值做指针更新: p2 = p2 - 2; /* 这使这使p2改指向改指向a3 */用增用增/减量操作做指针更新(指针应指在数组里):减量操作做指针更新(指针应指在数组里): p3 = p2; +p3; -p2; p3 += 2;如如果果两两指指针针指指在在同同一一个个数数组组里里,可可以以求求差差,得得到到它它们们间的数组元素个数(带符号整数)。间的数组元素个数(带符号整数)。 n = p3 p2; /* 也可以求也可以求 p2 p3 */指在同一个数组里的指针指在同一个数组里的指针可以比较大小:可以比较大

28、小:if (p3 p2) .当当p3所所指指的的元元素素在在p2所所指指的的元元素素之之后后时时条条件件成成立立(值值为为1),否否则则不不成成立立(值值为为0)。两两个个指指针针不不指指在在同同一一数数组组里时,比较大小没有意义。里时,比较大小没有意义。两两个个同同类类型型指指针针可可用用 = 和和 != 比比较较相相等等或或不不等等;任任何何指指针针都都能能与与通通用用指指针针比比较较相相等等或或不不等等,任任何何指指针针可可与空指针值(与空指针值(0或或NULL)比较相等或不等。比较相等或不等。两指针指向同一数据元素,或同为空值时它们相等。两指针指向同一数据元素,或同为空值时它们相等。数

29、组写法与指针写法数组写法与指针写法如果一个如果一个指针指针指在一个数组里,通过指针访问数组元素指在一个数组里,通过指针访问数组元素的操作也可用下标形式写。的操作也可用下标形式写。设设p1指向数组指向数组a0,p3指向指向a5。可写:。可写: p13 = 5; p32 = 8;p13一一类类写写法法称称为为数数组组写写法法,*(p+3)一一类类写写法法称称为为指指针写法针写法。两类写法有等价效力,可以自由选用。两类写法有等价效力,可以自由选用。对数组名求值得到指向数组首元素的指针值对数组名求值得到指向数组首元素的指针值数数组组名名可可以以“看看作作”常常量量指指针针,可可参参与与一一些些指指针针

30、运运算算,与其他指针比大小,比较相等与不相等。与其他指针比大小,比较相等与不相等。通通过过数数组组名名的的元元素素访访问问也也可可以以采采用用指指针针写写法法。a3可可写为写为*(a+3)。注意:注意:数组名不是指针变量数组名不是指针变量,特别是不能赋值,不能更,特别是不能赋值,不能更改。改。若若a为数组,下面操作都是错误的:为数组,下面操作都是错误的:a+;a += 3;a = p;有有些些运运算算虽虽不不赋赋值值但但也也可可能能没没意意义义。如如 a3 不不可可能能得得到合法指针值,因其到合法指针值,因其结果超出数组界限结果超出数组界限指针运算原理指针运算原理当当一一个个指指针针指指向向某

31、某数数组组里里的的元元素素时时,为为什什么么能能算算出出下下一一元素位置?(这是指针运算的基础)元素位置?(这是指针运算的基础)指指针针有有指指向向类类型型,p指指向向数数组组a时时,由由于于p的的指指向向类类型型与与a的元素类型一致,数据对象的大小可以确定。的元素类型一致,数据对象的大小可以确定。p+1的的值值可可根根据据p的的值值和和数数组组元元素素大大小小算算出出。由由一一个个数数组组元元素素位位置置可可以以算算出出下下一一元元素素位位置置,或或几几个个元元素素之之后后的的元元素位置。指针运算的基础。素位置。指针运算的基础。通通用用指指针针即即使使指指到到数数组组里里,因因没没有有确确定

32、定指指向向类类型型,因因此此不能做一般指针计算,只能做指针比较。不能做一般指针计算,只能做指针比较。基于指针的数组程序设计基于指针的数组程序设计指针运算是处理数组元素的另一方式,有时很方便。设指针运算是处理数组元素的另一方式,有时很方便。设有有int数组数组a和指针和指针p1,p2,下面代码都下面代码都打印打印a的元素:的元素: for (p1 = a, p2 = a+10; p1 p2; +p1) printf(%dn, *p1);for (p1 = a; p1 a+10; +p1) printf(%dn, *p1);for (p1 = p2 = a; p1 - p2 10; +p1) pr

33、intf(%dn, *p1);for (p1 = a; p1 - a 10; +p1) printf(%dn, *p1);数组参数的意义数组参数的意义C规定,数组参数规定,数组参数就是就是相应的指针参数:相应的指针参数:int f(int n, int d) . .和和int f(int n, int *d) . . 意义相同。数组参数的作用就是这样实现的。意义相同。数组参数的作用就是这样实现的。对对应应d的的实实参参是是被被处处理理数数组组的的名名字字,求求值值得得到到指指针针值值,符合形参需要,使符合形参需要,使d指向该数组的指向该数组的“首元素首元素”。前前面面函函数数体体里里参参数数用

34、用数数组组写写法法(对对指指针针可可这这样样写写)。通通过过指指针针形形参参d访访问问的的相相应应实实参参数数组组里里的的各各元元素素。(数数组组参数就是利用指针实现的!参数就是利用指针实现的!)这也使采用数组参数的函数能修改实参数组。这也使采用数组参数的函数能修改实参数组。函数里也可用指针方式做元素访问。函数里也可用指针方式做元素访问。int intsum (int n, int a) int i, m = 0; for (i = 0; i n; +i) m += *(a+i); return m;函数里不能用函数里不能用sizeof确定数组实参大小确定数组实参大小:函数的数组:函数的数组形

35、参实际是指针,求形参实际是指针,求sizeof算出的是指针的大小。算出的是指针的大小。所有指针大小都一样,它们保存的都是地址值,各种类所有指针大小都一样,它们保存的都是地址值,各种类型的地址值采用同样表示方式。型的地址值采用同样表示方式。另一方面,另一方面,sizeof的计算是在编译中完成的。实参是的计算是在编译中完成的。实参是动态运行中确定的东西。动态运行中确定的东西。使用数组的一段元素使用数组的一段元素以数组为参数的函数可处理一段元素。求元素和:以数组为参数的函数可处理一段元素。求元素和:double sum(int n, double a);设有双精度数组设有双精度数组b,40个元素已有

36、值:个元素已有值:用用sum可求可求b所有元素之和所有元素之和/前一段元素之和:前一段元素之和:x = sum(40, b);y = sum(20, b);sum不不知知道道b的的大大小小,它它由由参参数数得得到到数数组组首首元元素素地地址址,从这里开始求连续从这里开始求连续40或或20个元素的和。个元素的和。也可用也可用sum求求b中中下标下标12到到24的一段元素之和。的一段元素之和。z = sum(13, b+12);指针与数组操作函数实例指针与数组操作函数实例例例1,用指针方式实现字符串长度函数。一种方式:,用指针方式实现字符串长度函数。一种方式:int strLength (cons

37、t char *s) int n = 0; /*通过局部指针扫描串中字符通过局部指针扫描串中字符*/ while (*s != 0) s+; n+; return n; 另一实现:另一实现:int strLength (const char *s) char *p = s; while (*p != 0) p+; return p - s;参数类型参数类型(char*),实参应是字符串或存字符串的数组实参应是字符串或存字符串的数组例例2,用指针实现字符串复制函数。直接定义:,用指针实现字符串复制函数。直接定义:void strCopy (char *s, const char *t) whil

38、e (*s = *t) != 0) s+; t+; 赋值表达式有值,赋值表达式有值,0就是就是0,函数可简化:,函数可简化:void strCopy (char *s, const char *t) while (*s = *t) s+; t+; 把指针更新操作也写在循环测试条件里,程序是:把指针更新操作也写在循环测试条件里,程序是:void strCopy (char *s, const char *t) while (*s+ = *t+) ; / 空语句空语句注意优先级与结合性,增量运算的作用与值等。注意优先级与结合性,增量运算的作用与值等。例例3 3,利用指针,输出,利用指针,输出int

39、数组里一段元素数组里一段元素:void prt_seq(int *begin, int *end) for (; begin != end; +begin) printf(%dn, *begin); prt_seq(a, a+10);prt_seq(a+5, a+10);prt_seq(a, a+3);prt_seq(a+2, a+6);prt_seq(a+4, a+4);prt_seq(a+10, a+10);最后两个调用对应空序列。序列为最后两个调用对应空序列。序列为“半闭半开半闭半开”。 还还可写出许多类似函数。可写出许多类似函数。“设置设置”函数:函数:void set_seq(int

40、 *b, int *e, int v) for (; b != e; +b) *b = v; 把序列中每个元素都用其平方根取代:把序列中每个元素都用其平方根取代:void sqrt_seq (double *b, double *e) for (; b!=e; +b) *b = sqrt(*b); 求平均值:求平均值:double avrg(double *b, double *e) double *p, x = 0.0; if (b = e) return 0.0; for (p = b; p != e; +p) x += *p; return x / (e - b);字符指针与字符数组字符

41、指针与字符数组常用字符指针指向字符数组元素。如指向常量字符串或常用字符指针指向字符数组元素。如指向常量字符串或存着字符串的字符数组,通常指向字符串开始。存着字符串的字符数组,通常指向字符串开始。也可指到字符串中间,把指的东西当字符串用。也可指到字符串中间,把指的东西当字符串用。定义字符指针时可用字符串常量初始化,如:定义字符指针时可用字符串常量初始化,如:char *p = Programming;1)定义了指针)定义了指针p2)建立了一个字符串常量,内容为)建立了一个字符串常量,内容为Programming3)令)令p指向该字符串常量。图指向该字符串常量。图(a)char a = Progr

42、amming; 1)定义了一个)定义了一个12个字符元素的数组个字符元素的数组2)用)用Programming各字符初始化各字符初始化a的元素,图的元素,图(b)1)指针指针p可重新赋值(数组不能赋值):可重新赋值(数组不能赋值):p = Programming Language C;2)p和和a类型不同,大小不同。类型不同,大小不同。a占占12个字符的空间。个字符的空间。3)a的元素可以重新赋值。如:的元素可以重新赋值。如:a8=e; a9=r; a10=0;a的内容现在变成的内容现在变成“Programmer”按按规定,字符串常量不得修改规定,字符串常量不得修改可定义字符指针变量并让它指向

43、已有字符数组。可定义字符指针变量并让它指向已有字符数组。C程序程序常用这种方式使用和操作字符数组内容。常用这种方式使用和操作字符数组内容。例如,输入一行到数组里例如,输入一行到数组里:enum NLINE = 256 ;char lineNLINE;int count; char *p;/*-*/p = line;while(plineNLINE-1 & (*p = gerchar()!=n) +p; *p = 0; /* 做成字符串做成字符串 */*-*/for (count = 0, p = line; *p != 0; +p) if (*p = e) +count; /* 统计统计e的个

44、数的个数 */7.4 指针数组指针数组复杂复杂C程序里常用到指针的数组。程序里常用到指针的数组。例:需要一组字符串,常用字符指针数组索引它们。如例:需要一组字符串,常用字符指针数组索引它们。如软件中错误信息常用一组字符串表示。分散管理不便。软件中错误信息常用一组字符串表示。分散管理不便。可定义指针数组,指针分别指向输出信息串常量。可定义指针数组,指针分别指向输出信息串常量。也可定义其他类型的指针数组,如指向整数或者其他类也可定义其他类型的指针数组,如指向整数或者其他类型的指针的数组,下面讨论以字符指针为例型的指针的数组,下面讨论以字符指针为例。定义字符指针数组:定义字符指针数组:char *p

45、a10;优优先先级级也也适适用用于于定定义义。优优先先级级高高,pa是是数数组组,其其元元素素是字符指针。是字符指针。定义字符指针数组时用字符串常量提供初始值。例:定义字符指针数组时用字符串常量提供初始值。例:char *days = Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday; 简单实例简单实例:printf(Work days: );for(i=1; i6; +i) printf(%s , daysi);printf(nWeekend: );printf(%s %sn, days6,days0);字符指针数组

46、实例字符指针数组实例:改改写写第第6章章的的C语语言言关关键键字字统统计计程程序序,把把原原来来的的两两维维字字符数组符数组keywords改为字符指针数组。改为字符指针数组。只只需需定定义义下下面面数数组组,并并用用(关关键键字字)字字符符串串对对各各指指针针做做初始化:初始化:char *keywords = auto, break, . . volatile, while;其他部分不需要改,程序可以正常工作。其他部分不需要改,程序可以正常工作。指针数组与两维数组指针数组与两维数组两维字符数组与字符指针数组不同两维字符数组与字符指针数组不同 。定义:。定义:char color16=RED

47、,GREEN,BLUE;char *color=RED,GREEN,BLUE;命令行参数的处理命令行参数的处理启启动动程程序序的的基基本本方方式式是是输输入入命命令令,要要求求OS装装入入程程序序代代码码文件并执行。文件并执行。命令行命令行:描述命令的字符行。:描述命令的字符行。在在图图形形用用户户界界面面系系统统(如如Windows)里里,命命令令行行存存在在于于图标图标/菜单的定义中。菜单的定义中。源文件源文件abcd.c得到可执行文件得到可执行文件abcd.exe。键入命令:键入命令:abcd该程序就会被装入执行。该程序就会被装入执行。除命令名外,命令行常包括其他信息。除命令名外,命令行

48、常包括其他信息。DOS命令:命令: copy a:file1.txt dir windowssystem /p附加信息也是字符序列,称为附加信息也是字符序列,称为命令行参数命令行参数。前面的程序都没有包含处理命令行的功能前面的程序都没有包含处理命令行的功能要要写写能能够够处处理理命命令令行行参参数数的的程程序序,需需要要用用C语语言言的的命命令令行参数机制。行参数机制。处处理理命命令令行行参参数数很很像像处处理理函函数数参参数数,写写程程序序时时要要考考虑虑和和处理程序启动时实际命令行提供的信息。处理程序启动时实际命令行提供的信息。命令行被看作空格分隔的字段,各个命令行被看作空格分隔的字段,各

49、个命令行参数命令行参数。命令名编号为命令名编号为0,其余依次编号。程序启动时把各命令,其余依次编号。程序启动时把各命令行参数做成字符串,程序里可按规定方式使用。行参数做成字符串,程序里可按规定方式使用。设有程序设有程序 prog1;设启动程序的命令行是:设启动程序的命令行是:prog1 there are five arguments这这时时prog1是是编编为为0的的命命令令行行参参数数,there是是编编号号1的的命命令行参数,令行参数,;共;共5个命令行参数。个命令行参数。通通过过main的的参参数数可可获获取取命命令令行行参参数数。main(void)表表示示不处理命令行参数,不处理命

50、令行参数,main的另一形式带两个参数:的另一形式带两个参数:int main (int argc, char *argv);main开始执行时:开始执行时:argc是命令行参数的个数是命令行参数的个数argv指向含指向含argc+1个指针个指针字符指针数组,前字符指针数组,前argc个指个指针指向各命令行参数串,最针指向各命令行参数串,最后有一个空指针后有一个空指针图为执行下面命令时图为执行下面命令时 main 函数里的现场情况函数里的现场情况prog1 there are five argumentsmain参数参数常用常用argc、argv作作为名字(实际上可以用其他名为名字(实际上可以

51、用其他名字)。参数类型确定。字)。参数类型确定。可可由由argc得到参数个数,通过得到参数个数,通过argv访问它们。访问它们。可以访问启动程序的命令名本身。在一些系统里,可以访问启动程序的命令名本身。在一些系统里,0号号参数还包括完整的目录路径。参数还包括完整的目录路径。例例:写写程程序序echo打打印印各各命命令令行行参参数数。写写程程序序时时不不知知道道调用时的命令行参数是什么,但可以打印它们:调用时的命令行参数是什么,但可以打印它们:#include int main (int argc, char *argv) int i; for (i = 0; i argc; +i) print

52、f(Args%d: %sn, i, argvi); return 0;书上有另一种定义方式,其中利用了最后的空指针书上有另一种定义方式,其中利用了最后的空指针用用IDE开开发发程程序序时时,编编辑辑/调调试试/执执行行等等工工作作都都在在环环境境里里完成,执行程序时如何提供命令行参数?完成,执行程序时如何提供命令行参数?集集成成开开发发环环境境都都有有专专门门机机制制为为启启动动命命令令行行提提供供参参数数(如如Turbo-C的的Options/Arguments)。)。可可转转到到IDE之之外外在在命命令令行行状状态态下下启启动动程程序序。在在图图形形用用户户界面系统里,有关命令行参数的讨论

53、同样有效。界面系统里,有关命令行参数的讨论同样有效。建建立立程程序序项项、命命令令菜菜单单项项等等也也要要写写出出实实际际命命令令行行,包包括括提供必需的命令行参数。提供必需的命令行参数。一一些些图图形形界界面面系系统统里里可可把把数数据据文文件件拖拖到到程程序序文文件件上上作作为为处理对象。此时将自动产生一个命令行。处理对象。此时将自动产生一个命令行。7.5 多维数组作为参数的通用函数多维数组作为参数的通用函数函数的两维或多维数组参数必须说明除第一维外各维的函数的两维或多维数组参数必须说明除第一维外各维的大小,这使函数失去了一般通用性。大小,这使函数失去了一般通用性。ANSI C 没提供定义

54、处理多维数组的通用函数的标准方没提供定义处理多维数组的通用函数的标准方法。可以通过技术解决。(法。可以通过技术解决。(C99 提供了标准方式)提供了标准方式)下面以两维数组为例,多维数组可以类似处理。下面以两维数组为例,多维数组可以类似处理。考虑:考虑:int fun1(int n, int mat10) . . matij . . 只能对第二维长只能对第二维长10的数组使用。不能处理其他数组的数组使用。不能处理其他数组mat的的指指向向类类型型是是int数数组组(两两维维数数组组的的元元素素是是一一维维数数组)。定义没给出一维数组大小,指针定义不完全组)。定义没给出一维数组大小,指针定义不完

55、全这这样样,编编译译程程序序虽虽然然知知道道mat0的的位位置置,但但却却无无法法算算出出mat1等等子子数数组组位位置置以以及及matij位位置置。因因此此编译工作无法完成。编译工作无法完成。实际中确实需定义处理多维数组的通用函数。实际中确实需定义处理多维数组的通用函数。改写为:改写为:int fun2(int n, int m, int mat) . . matij . . 这个定义错误,这个定义错误,无法通过编译。为什么?无法通过编译。为什么?解决方案:解决方案:考虑数组考虑数组 int a108;首元位置首元位置&a00。每行每行8元素,第元素,第1行开始位置是:行开始位置是:&a00

56、 + 8访问第访问第1行首元素用表达式:行首元素用表达式:*(&a00 + 8)访问第访问第i行首元素用表达式:行首元素用表达式:*(&a00 + i*8)访问访问aij:*(&a00 + i*8 + j)可见,有了一些信息后,可以算出元素的位置可见,有了一些信息后,可以算出元素的位置处理两维数组所需信息:处理两维数组所需信息:1)基本元素类型;)基本元素类型;2)数组两)数组两个维的长度;个维的长度;3)数组开始位置。)数组开始位置。通过参数可得到数组的首元素位置和各维长。通过参数可得到数组的首元素位置和各维长。输出两维整型数组的函数,每行输出在一行。输出两维整型数组的函数,每行输出在一行。

57、void prtMatrix (int m, int n, int *mp) int i, j; for (i = 0; i m; +i) for (j = 0; j n; +j) printf(%d , *(mp + i * n + j); putchar(n); 打印数组打印数组a和另一和另一2036的整型数组的整型数组mat的调用形式:的调用形式:prtMatrix(10, 8, &a00);prtMatrix(20, 36, &mat00);7.6 动态存储管理动态存储管理变量(简单变量变量(简单变量/数组等)用于保存数据,需安排存储数组等)用于保存数据,需安排存储(称为(称为存储分配

58、存储分配)。)。高级语言编程不需要考虑存储细节,有关工作由编译程高级语言编程不需要考虑存储细节,有关工作由编译程序完成。编程效率高。序完成。编程效率高。在在 C 语言里语言里外部变量外部变量/局部静态变量在编译的时候确定存储,开局部静态变量在编译的时候确定存储,开始执行前分配存储始执行前分配存储自动变量在执行进入定义函数时分配存储自动变量在执行进入定义函数时分配存储共同性质:共同性质:变量大小都是静态确定的变量大小都是静态确定的。例:函数中变量和参数决定了函数执行时所需要存储例:函数中变量和参数决定了函数执行时所需要存储空间量,空间量, C 语言要求自动数组的大小用静态表达式描语言要求自动数组

59、的大小用静态表达式描述。这样,函数需要的存储量就可在编译时确定。述。这样,函数需要的存储量就可在编译时确定。静态处理存储的优点是方便,效率高,执行中的工作静态处理存储的优点是方便,效率高,执行中的工作简单,速度快。简单,速度快。但对编程方式加了限制,有些问题不好解决。但对编程方式加了限制,有些问题不好解决。例:要处理学生成绩,需要用数组存放。但编程时并例:要处理学生成绩,需要用数组存放。但编程时并不知道运行时需要处理多少学生成绩,每次处理的成不知道运行时需要处理多少学生成绩,每次处理的成绩项数也可能不同。绩项数也可能不同。能否先通知数据项数,再建数据表示?能否先通知数据项数,再建数据表示?理想

60、方式(现在行不通):理想方式(现在行不通):int n;.scanf(%d, &n);double scoresn; /* 不行!不行!*/. /* 读入数据,然后处理读入数据,然后处理 */不能用变量说明不能用变量说明scores大小(必须静态确定)。大小(必须静态确定)。至今讨论的机制无法很好解决这类问题。至今讨论的机制无法很好解决这类问题。可能解决方案可能解决方案:1)分分析析问问题题,定定义义适适当当大大小小的的数数组组。若若分分析析正正确确,一一般都能处理。但数据很多时程序就不能用。般都能处理。但数据很多时程序就不能用。2)定定义义尽尽可可能能大大的的数数组组以以满满足足任任何何需需

61、要要。浪浪费费大大量量存存储储资资源源。如如有有多多个个这这种种数数组组就就更更难难办办。系系统统可可能能无无法法容容纳几个大数组,但实际上它们并不同时需要很大空间。纳几个大数组,但实际上它们并不同时需要很大空间。解解决决的的办办法法是是“动动态态存存储储分分配配”。在在程程序序运运行行中中做做存存储储分配工作。分配工作。这这里里的的问问题题:程程序序运运行行中中需需要要使使用用存存储储,有有时时程程序序对对存存储的需求量在写程序时不能确定。储的需求量在写程序时不能确定。动态存储分配与释放动态存储分配与释放根据运行中的需要分配存储,取得存储块使用,称为根据运行中的需要分配存储,取得存储块使用,

62、称为动动态存储分配态存储分配。在运行中根据需要动态进行。在运行中根据需要动态进行。程序里怎样使用分配的存储块?程序里怎样使用分配的存储块?程使用变量是通过名字。程使用变量是通过名字。动态分配的存储块没有名字,因此需要其他访问途径。动态分配的存储块没有名字,因此需要其他访问途径。借助于指针借助于指针。用指针指向存储块,间接使用被指存储。用指针指向存储块,间接使用被指存储。访问动态分配存储是指针的最重要用途访问动态分配存储是指针的最重要用途。与此对应:动态释放,不用的动态存储块应交还。与此对应:动态释放,不用的动态存储块应交还。动态分配动态分配/释放由释放由动态存储管理系统动态存储管理系统完成,这

63、是程序运完成,这是程序运行系统的子系统,管理着称作行系统的子系统,管理着称作堆堆(英文(英文heap)的存储区。的存储区。大部分常规语言都有这种机制。大部分常规语言都有这种机制。C语言的动态存储管理机制语言的动态存储管理机制用标准库函数实现,用标准库函数实现,/1)存储分配函数)存储分配函数malloc()。原型:原型:void *malloc(size_t n); /*size_t是某整型是某整型*/分分配配一一块块不不小小于于n的的存存储储,返返回回其其地地址址。无无法法满满足足时时返返回空指针值。回空指针值。int n; double *data;. scanf(%d, &n);data

64、=(double*)malloc(n*sizeof(double);if (data = NULL) . /* 分配未完成时的处理分配未完成时的处理 */ .datai.*(data+j)./*正常处理正常处理*/malloc的返回值(的返回值(void*)应通过类型强制转为特定应通过类型强制转为特定指针类型后赋给指针变量。指针类型后赋给指针变量。使用注意事项:使用注意事项:分配存储块大小应该用分配存储块大小应该用sizeof计算计算动态分配必须检查成功与否动态分配必须检查成功与否动态分配的块大小也是确定的。越界使用(尤其是动态分配的块大小也是确定的。越界使用(尤其是越界赋值)是严重错误,可能

65、导致程序或系统垮台越界赋值)是严重错误,可能导致程序或系统垮台2)带计数和清)带计数和清0的存储分配函数的存储分配函数calloc。原型:原型:void *calloc(size_t n, size_t size);size是元素大小,是元素大小,n是个数。是个数。分分配配一一块块存存储储,足足够够存存n个个大大小小为为size的的元元素素,并并把把元元素全部清素全部清0;无法分配时返回空指针值。;无法分配时返回空指针值。前面的存储分配问题也可用下面语句实现:前面的存储分配问题也可用下面语句实现:data = (double*)calloc(n, sizeof(double);主主要要差差别别

66、:malloc对对所所分分配配的的区区域域不不做做任任何何事事情情,calloc对整个区域自动清对整个区域自动清0。3)动态存储释放函数)动态存储释放函数free。原型:原型:void free(void *p);free释放释放p指的存储块。注意:指的存储块。注意:该块必须是通过动态存储分配得到的该块必须是通过动态存储分配得到的p值为空时什么也不做值为空时什么也不做执执行行free(p)后后p值值未未变变,被被指指块块可可能能已已变变。不不允允许许间接访问已释放存储块间接访问已释放存储块不要对并非指向动态分配块的指针用本操作不要对并非指向动态分配块的指针用本操作为为保保证证动动态态存存储储的

67、的有有效效使使用用,动动态态分分配配块块不不再再用用时时应应释放。动态存储块的释放只能通过调用释放。动态存储块的释放只能通过调用free完成。完成。程序例子:程序例子:int fun (.) int *p; . p = (int *)malloc(.); . free(p); return .; /*退出函数前应释放函数内分配且已无用的动态存储退出函数前应释放函数内分配且已无用的动态存储*/fun退出时退出时p存在期结束,若没有访问分配块的其他途存在期结束,若没有访问分配块的其他途径,将不可能再用到函数里分配的存储块。径,将不可能再用到函数里分配的存储块。动态存储的流失动态存储的流失。如程序长

68、期执行,存储流失就可能成为严重问题。对实如程序长期执行,存储流失就可能成为严重问题。对实际系统可能是很严重的问题。际系统可能是很严重的问题。4)分配调整函数)分配调整函数realloc。函数原型是:函数原型是:void *realloc(void *p, size_t n);更改已有分配。更改已有分配。p指原分配块,指原分配块,n是新大小要求。是新大小要求。返回大小至少为返回大小至少为n的存储块指针。新块与原块一致:新的存储块指针。新块与原块一致:新块小时保存原块块小时保存原块n范围内数据;新块大时原数据存在,范围内数据;新块大时原数据存在,新增部分不初始化。分配成功后原块可能改变。新增部分不

69、初始化。分配成功后原块可能改变。无法满足时返回空指针,原块不变。无法满足时返回空指针,原块不变。常用写法常用写法(防止分配失败导致原存储块丢失防止分配失败导致原存储块丢失) :q = (double*)realloc(p, m * sizeof(double);if (q = NULL) /*未成功,未成功,p仍指原块,特殊处理仍指原块,特殊处理*/ else p = q; /* 令令p指向新块,正常处理指向新块,正常处理 */ .例例1:修修改改筛筛法法程程序序,由由命命令令行行参参数数得得到到所所需需范范围围。如如无命令行参数则要求用户输入确定范围的整数值。无命令行参数则要求用户输入确定范

70、围的整数值。 先考虑整体设计。为了清晰,将筛法用函数实现。先考虑整体设计。为了清晰,将筛法用函数实现。命令行参数(字符串)需要转换到整数,定义函数:命令行参数(字符串)需要转换到整数,定义函数:int s2int(const char *s); 函数实现(标准库有类似函数函数实现(标准库有类似函数atoi):):int s2int(const char *s) int n; for (n = 0; isdigit(*s); +s) n = 10 * n + (*s - 0); return n;筛法计算包装为函数筛法计算包装为函数:void sieve(int lim, int an) int

71、 i, j, upb = sqrt(lim+1); an0 = an1 = 0; / 建立初始向量建立初始向量 for (i = 2; i = lim; +i) ani = 1; for (i = 2; i = upb; +i) if (ani = 1) / i是素数是素数 for (j = i*2; j = lim; j += i) anj = 0; / i的倍数不是素数的倍数不是素数main的工作:的工作:1)获取范围(由命令行或用户),)获取范围(由命令行或用户),2)分配空间及初始化,分配空间及初始化,3)执行筛法,)执行筛法,4)打印输出。)打印输出。enum LARG = 6553

72、5 ;int main(int argc, char *argv) int i, j, n, *ns; if (argc = 2) n = s2int(argv1); else getnumber(Largest: ,2,LARG,5,&n); if (n LARG) printf(Largest must in 2, %d,LARG); return 1; ns = (int*)malloc(sizeof(int)*(n+1) if (ns = NULL) printf(No enough memory!n); return 2; sieve(n, ns); . /* 输出略输出略 */ f

73、ree(ns); return 0;例例2,改造成绩直方图程序,使之能处理任意个数据。,改造成绩直方图程序,使之能处理任意个数据。如何处理事先无法确定数目的数据集合如何处理事先无法确定数目的数据集合。用数组限制了。用数组限制了能处理的项数,现在改用动态分配。能处理的项数,现在改用动态分配。让让getscores根据需要申请存储块,返回动态分配的根据需要申请存储块,返回动态分配的块和实际项数。函数原型:块和实际项数。函数原型:double* readscores(int* np); 由由np送回项数送回项数无法分配无法分配存储时返回存储时返回NULL如果已读了部分数据,但在扩大存储失败,就给出信

74、如果已读了部分数据,但在扩大存储失败,就给出信息并返回部分数据息并返回部分数据主函数:主函数:int main() int n; double *scores = readscores(&n); if(scores = NULL) return 1; . /* 其他不必修改其他不必修改 */由于原来的设计比较得当,现在由于原来的设计比较得当,现在只需做少许修改只需做少许修改由此可见良好设计的重要性由此可见良好设计的重要性readscores的定义:的定义:事先不知道数据项数,可以先分配一块,读入中发现不事先不知道数据项数,可以先分配一块,读入中发现不够用时扩大。够用时扩大。开始分配多大?采用什

75、么扩大策略?开始分配多大?采用什么扩大策略?下面采用开始分配一块,随后不够时加倍的策略。有关下面采用开始分配一块,随后不够时加倍的策略。有关存储分配和扩大策略的讨论见书。存储分配和扩大策略的讨论见书。这个定义主要显示分配调整技术,没有追求完善。读入这个定义主要显示分配调整技术,没有追求完善。读入中遇到错误数据就立即结束。中遇到错误数据就立即结束。数据检查和处理问题前面已讨论过,修改这个函数,使数据检查和处理问题前面已讨论过,修改这个函数,使之能合理处理输入数据错误,给出有用信息,或增加其之能合理处理输入数据错误,给出有用信息,或增加其他有用功能等都留作他有用功能等都留作 练习。练习。enum

76、INUM = 40 ;double* readscores(int* np) unsigned size = INUM, n; double *q, x, *p; if(p=(double*)malloc(INUM*sizeof(double)=NULL) printf(No memory. Stopn); *np = 0; return NULL; for(n=0; scanf(%lf, &x) = 1; +n) if (n = size) /* 块满了,需要重新分配块满了,需要重新分配 */ size *= 2; q = (double*)realloc(p, size*sizeof(do

77、uble); if (q = NULL) printf(Process only %d scores.n, n); break; p = q; pn = x; *np = n; return p;函数、指针和动态存储分配函数、指针和动态存储分配要用函数处理一组数据,得到处理结果,最好的方式是要用函数处理一组数据,得到处理结果,最好的方式是直接为函数提供数组位置和元素个数(或结束位置)。直接为函数提供数组位置和元素个数(或结束位置)。前面经常采用这种方式。前面经常采用这种方式。这时函数不必知道处理的是数组变量还是动态存储。这时函数不必知道处理的是数组变量还是动态存储。例如,完全可以用如下方式调用

78、筛法函数:例如,完全可以用如下方式调用筛法函数:int ns1000;int main() . sieve(1000, ns); . return 0;这时存储的问题在一个层次中管理(定义变量或动态分这时存储的问题在一个层次中管理(定义变量或动态分配)。责任清晰,易于把握,是最好的处理方案。配)。责任清晰,易于把握,是最好的处理方案。 有时无法采用上述方法。有时无法采用上述方法。例如前面直方图程序,只能由例如前面直方图程序,只能由readscores根据情况分根据情况分配存储,送出存储块地址。配存储,送出存储块地址。main用指针接收。用指针接收。这种做法完全正确这种做法完全正确动态分配的块将

79、一直存在到明确释放为止(直到对它调动态分配的块将一直存在到明确释放为止(直到对它调用用free),),与分配所在的函数无关。与分配所在的函数无关。readscores返回存储块地址不仅传回数据,也将管理返回存储块地址不仅传回数据,也将管理这个块的责任转交给这个块的责任转交给main。可见前面的可见前面的 main 结束前缺了结束前缺了free(scores)调用。调用。应该加上。应该加上。前面的前面的readscores返回块地址,由参数传回块的大小返回块地址,由参数传回块的大小(将(将int变量地址给变量地址给int*)。另一种可能方式是让另一种可能方式是让readscores返回块大小,无

80、法分返回块大小,无法分配存储时返回配存储时返回0。现在考虑这种设计的问题:。现在考虑这种设计的问题:调用方式:调用方式:if (readscores(.)=0) /*处理错误处理错误*/ 函数原型应该是:函数原型应该是:int readscores(?); /* 参数类型?参数类型?*/现在需要的是通过实参得到动态块的地址,这是一个现在需要的是通过实参得到动态块的地址,这是一个(double*)值值已知,要想通过函数的参数取得送出来的已知,要想通过函数的参数取得送出来的int值,参数值,参数就应该为就应该为(int*)类型,实参用类型,实参用int变量地址变量地址要通过参数送出要通过参数送出(

81、double*)类型的值,实参就应该传递类型的值,实参就应该传递(double*)变量的地址。因此,形参应该是指向变量的地址。因此,形参应该是指向(double*)类型的指针,也就是类型的指针,也就是(double*)类型类型正确的原型:正确的原型:int readscores(double* dpp);调用:调用:if(readscores(&scores)=0)/*错误处理错误处理*/这里讨论了不同函数层次之间存储分配、传递和使用的这里讨论了不同函数层次之间存储分配、传递和使用的几种技术。各有适用之处几种技术。各有适用之处,可根据情况选择。可根据情况选择。关于动态存储分配关于动态存储分配计

82、算机系统的内存由计算机系统的内存由OS(操作系统)管理。启动一个操作系统)管理。启动一个程序时,程序时,OS为它分配内存,存放它的代码和数据。程为它分配内存,存放它的代码和数据。程序结束时序结束时OS收回该程序所占用的所有内存。收回该程序所占用的所有内存。C程序启动时从程序启动时从OS得到的内存,其中一大块由自己的动得到的内存,其中一大块由自己的动态存储管理系统管理。程序里调用态存储管理系统管理。程序里调用malloc将在这块里分将在这块里分配存储,配存储,free将内存块退回动态存储管理系统。将内存块退回动态存储管理系统。这是这是C程序内部的事情。至于程序内部的事情。至于C动态存储管理系统如

83、何动态存储管理系统如何与与OS打交道的问题是看不见的(透明的)。打交道的问题是看不见的(透明的)。一个一个C程序结束时,程序结束时,OS收回原先分配给它的全部内存。收回原先分配给它的全部内存。这是这是OS的存储管理问题,与我们的程序无关的存储管理问题,与我们的程序无关。7.7 定义类型定义类型基本类型有基本类型有类型名类型名,可用于定义,可用于定义/说明变量,描述函数说明变量,描述函数参数与返回值,做类型强制等。参数与返回值,做类型强制等。数组、指针等等可能使说明变得很复杂,使用不便。也数组、指针等等可能使说明变得很复杂,使用不便。也不容易保证多个类型描述的一致性。不容易保证多个类型描述的一致

84、性。如果能把复杂如果能把复杂类型描述类型描述看作类型(用户定义类型)看作类型(用户定义类型) 加加以命名,可带来很大方便,特别是在实现复杂程序以命名,可带来很大方便,特别是在实现复杂程序/软软件系统时。件系统时。定义类型是重要语言机制,定义好的类型最好能像内部定义类型是重要语言机制,定义好的类型最好能像内部类型一样使用。类型一样使用。C语言类型定义机制较弱,其主要作用是语言类型定义机制较弱,其主要作用是简化描述简化描述。类型定义用关键字类型定义用关键字typedef,其后的描述形式与变量定其后的描述形式与变量定义相似,使原变量位置的标识符成为新类型名。义相似,使原变量位置的标识符成为新类型名。

85、typedef unsigned long int ULI;定义后的定义后的ULI可以像基本类型名一样用:可以像基本类型名一样用:ULI x, y, *p;ULI fun1(double x, ULI n);p = (ULI*)malloc(n*sizeof(ULI);这种类型定义可简化程序书写这种类型定义可简化程序书写,有一定实际价值。,有一定实际价值。有时定义新类型可提高可读性和清晰性。如:有时定义新类型可提高可读性和清晰性。如:typedef double LENGTH;typedef double AREA;定义不同类型名可能帮人看到不一致情况定义不同类型名可能帮人看到不一致情况。注注

86、:C语语言言认认为为定定义义的的只只是是原原类类型型的的别别名名。LENGTH和和AREA是是double的别名的别名。提高程序的可读性提高程序的可读性。用预处理命令可产生类似效果。如:用预处理命令可产生类似效果。如:#define LENGTH double#define AREA double但两种写法处理过程不同,类型定义由编译程序处理。但两种写法处理过程不同,类型定义由编译程序处理。有些类型有些类型不能不能通过宏的方式(例如数组类型)。通过宏的方式(例如数组类型)。定义数组类型定义数组类型数组类型定义形式符合前面解释。例:数组类型定义形式符合前面解释。例:typedef double

87、VECT44;此后可以写:此后可以写:VECT4 v1, v2;int intprod(VECT4 v, VECT4 u);55的双精度数组类型:的双精度数组类型:typedef double MAT55;MAT a1, a2, a3; /*定义定义55数组变量数组变量*/double det(MAT m); /*说明函数参数说明函数参数*/MAT *p = (MAT *)malloc(sizeof(MAT);定义指针类型定义指针类型 例如:例如:typedef int * IP;typedef MAT * MATP;用宏定义代替它们,可能造成意想不到的结果。例如写用宏定义代替它们,可能造成意

88、想不到的结果。例如写#define MIP int *而后希望定义两个指针:而后希望定义两个指针:MIP mp1, mp2;可发现可发现mp2不是指针,而是一个不是指针,而是一个int变量。只要把这个描述按规则变量。只要把这个描述按规则展开,就不难发现其中的问题。展开,就不难发现其中的问题。宏是低级正文代换机制,应尽量少用。这又是一例。宏是低级正文代换机制,应尽量少用。这又是一例。需要定义类型名时,应该用需要定义类型名时,应该用typedef。复杂类型描述与解读复杂类型描述与解读类型描述可能变得很复杂(类型描述可能变得很复杂(C语言的缺点)。语言的缺点)。应应尽尽量量不不写写复复杂杂描描述述。

89、用用typedef分分解解,易易理理解解/少少出出错。定义后可多次使用错。定义后可多次使用/节省时间节省时间/方便维护。方便维护。读程序时可能遇到复杂类型描述。应了解类型描述的一读程序时可能遇到复杂类型描述。应了解类型描述的一般构造及解读方式。描述类型时也能更清楚,知道怎样般构造及解读方式。描述类型时也能更清楚,知道怎样写,什么地方需要加括号等。写,什么地方需要加括号等。类型描述中可出现:已定义类型名,被定义标识符,类型描述中可出现:已定义类型名,被定义标识符,构造符号(三个(组)运算符):构造符号(三个(组)运算符):*()指针指针数组数组函数函数可加圆括号改变结合关系。又增加了理解难度。可

90、加圆括号改变结合关系。又增加了理解难度。解读方式解读方式:符号意义不变,优先级和结合关系按运算符:符号意义不变,优先级和结合关系按运算符规定规定。和和()结合性强,结合性强,*结合力弱;结合力弱;和和()从左到右从左到右结合,结合,*从右向左结合。从右向左结合。首先辨认被说明(定义)的标识符,从它向外分析。这首先辨认被说明(定义)的标识符,从它向外分析。这个标识符常出现在描述中间,被其他符号包围。个标识符常出现在描述中间,被其他符号包围。一些例子:一些例子:1)int *f(int);被说明的是被说明的是f,f是一个函数。是一个函数。2)int (*fp)(int);说明的是说明的是fp。fp

91、是一个指针,指向是一个指针,指向.3)char *argv;argv是个指针,指向是个指针,指向(char*)4)int (*dp)16;dp是指针,它指向有是指针,它指向有16个元素的整型数组。个元素的整型数组。5)int *dp116;dp1是个是个16个元素的数组,其元素是整型指针。个元素的数组,其元素是整型指针。6)int (*(*g(int)4)(double);被说明的是函数被说明的是函数g,有一整型参数,返回指针,指针指有一整型参数,返回指针,指针指向向4个元素的数组,数组元素是指向函数的指针,被指个元素的数组,数组元素是指向函数的指针,被指函数有一个双精度参数并返回整数值。函数

92、有一个双精度参数并返回整数值。类类型型描描述述就就是是用用三三种种构构造造符符号号及及表表示示结结合合性性的的括括号号,逐逐层构造起来。很复杂的类型描述非常少见。层构造起来。很复杂的类型描述非常少见。有有些些构构造造不不合合法法:函函数数不不能能返返回回数数组组,函函数数不不能能返返回回函函数,函数不能作数组元素等。数,函数不能作数组元素等。利利用用typedef能能更更清清晰晰地地描描述述复复杂杂类类型型。下下面面以以例例6)为为例例。实实际际分分解解应应该该根根据据需需要要,看看哪哪个个(哪哪些些)层层次次的的类型有逻辑意义,有用处。类型有逻辑意义,有用处。原描述:原描述:int (*(*

93、g(int)4)(double);先定义函数指针类型和函数指针数组类型:先定义函数指针类型和函数指针数组类型:typedef int (*funp)(double);typedef funp fparray4;有这两个类型,有这两个类型,g的类型可以简单地说明为:的类型可以简单地说明为:fparray *g(int);g是函数,有一个是函数,有一个int参数,返回指向参数,返回指向fparray的指针的指针上面定义的两个类型也可以用于定义变量等。上面定义的两个类型也可以用于定义变量等。7.8 指向函数的指针指向函数的指针例:设需要定义求数学函数的根。例:设需要定义求数学函数的根。前面定义过一个

94、函数,但那只是玩具,只能当作练习答前面定义过一个函数,但那只是玩具,只能当作练习答案,实际中没什么用。案,实际中没什么用。原因:求根函数规定了被求根数学函数的名字。规定函原因:求根函数规定了被求根数学函数的名字。规定函数名则使求根函数不能用于多个函数,只能对一个特定数名则使求根函数不能用于多个函数,只能对一个特定的函数求根。的函数求根。程序可能需要求多个函数的根,应只写一个求根函数。程序可能需要求多个函数的根,应只写一个求根函数。提高函数通用性方法:提高函数通用性方法:引进新参数引进新参数。要使求根函数能处理不同的数学函数,必须为它引进要使求根函数能处理不同的数学函数,必须为它引进与与函数有关

95、函数有关的参数。的参数。其其他他许许多多语语言言也也支支持持函函数数的的“函函数数参参数数”,但但采采用用的的方式可能与方式可能与C语言不同。语言不同。C采采用用函函数数指指针针是是为为简简化化语语言言,同同时时又又能能提提供供最最大大灵灵活活性,可以支持其他程序设计技术。性,可以支持其他程序设计技术。C语语言言里里的的函函数数指指针针也也有有类类型型,一一个个函函数数指指针针只只能能指指向向某种特定类型(具有特定原型)的函数某种特定类型(具有特定原型)的函数C允允许许指指向向函函数数的的指指针针(函函数数指指针针),可可通通过过这这种种参参数数传递被处理函数,对不同调用可传递不同函数。传递被

96、处理函数,对不同调用可传递不同函数。函数指针是函数指针是C语言里指针的另一重要用途语言里指针的另一重要用途要要定定义义带带有有函函数数指指针针参参数数的的函函数数,最最好好是是首首先先定定义义函函数数指针类型。指针类型。指向数学函数的指针类型:指向数学函数的指针类型:typedef double (*MFP)(double);这这里里假假定定数数学学函函数数都都是是有有一一个个双双精精度度参参数数、返返回回双双精精度度值的。注意类型值的。注意类型MFP的定义形式。的定义形式。(* MFP)的括号不能少,去掉后意义就变了的括号不能少,去掉后意义就变了。下下面面定定义义的的FUNP是是另另一一函函

97、数数指指针针类类型型,指指向向有有两两个个int参数,返回参数,返回double指针值的函数:指针值的函数:typedef double*(*FUNP)(int, int);函数指针变量的定义和使用函数指针变量的定义和使用用函数指针类型定义指针变量:用函数指针类型定义指针变量:MFP p1, p2;没定义指针类型时定义必须写(不提倡):没定义指针类型时定义必须写(不提倡):double (*p1)(double),(*p2)(double);给函数指针变量赋值给函数指针变量赋值:对对函函数数名名求求值值,得得到到的的是是指指向向该该函函数数的的指指针针值值,可可以以赋赋给类型合适的函数指针变量

98、。给类型合适的函数指针变量。无无论论是是库库函函数数,还还是是自自己己定定义义的的函函数数,都都可可以以赋赋给给类类型型合适的函数指针变量合适的函数指针变量例,(设已经包含例,(设已经包含)函数指针赋值:)函数指针赋值: p1 = sin;赋赋过过值值的的函函数数指指针针可可直直接接当当作作函函数数使使用用,调调用用被被指指函函数。在上面赋值之后,写数。在上面赋值之后,写x = p1(3.24);相当于写相当于写x = sin(3.24);写间接也可以(注意括号):写间接也可以(注意括号):x = (*p1)(3.24);通通过过函函数数指指针针调调用用函函数数,被被调调函函数数由由函函数数指

99、指针针当当时时的的值值决定。同样语句在不同时刻可能调用不同函数。决定。同样语句在不同时刻可能调用不同函数。为程序提供了新的灵活性。为程序提供了新的灵活性。绝不能调用悬空的函数指针!绝不能调用悬空的函数指针!函数指针作为函数的参数函数指针作为函数的参数求根问题:以函数指针作为求根函数的参数。重新定义求根问题:以函数指针作为求根函数的参数。重新定义弦线法求函数根的函数:弦线法求函数根的函数:double cross(MFP fp, double x1, double x2) double y1 = fp(x1), y2 = fp(x2); return (x1 * y2 - x2 * y1)/(y

100、2 - y1);double root(MFP fp, double x1, double x2) double x, y, y1 = fp(x1); do x = cross(fp, x1, x2); y = fp(x); if (y * y1 0.0) y1 = y; x1 = x; else x2 = x; while (y = 1E-6 | y = -1E-6); return x;函数使用实例(设函数使用实例(设fun是类型合适的函数):是类型合适的函数):x = root(sin, 0.4, 4.5);y = root(fun, 1.26, 7.03);可以在函数头部直接描述函数指

101、针参数,如:可以在函数头部直接描述函数指针参数,如:double root(double(*fp)(double),.).采采用用先先定定义义类类型型的的写写法法,程程序序更更清清晰晰易易读读,也也有有利利于于一一致性的维护和程序的修改。致性的维护和程序的修改。在在任任何何时时候候,只只要要程程序序里里需需要要用用函函数数指指针针,都都应应该该定定义义相应的函数指针类型相应的函数指针类型有些书上可看到下面形式的函数指针有些书上可看到下面形式的函数指针/函数指针参数:函数指针参数:double (*p1)(), (*p2)();int f(double (*fp)(), .) .这是过时形式,属

102、不良方式。绝对不要用这是过时形式,属不良方式。绝对不要用!没有函数指针的完整类型描述,编译程序无法做类型检没有函数指针的完整类型描述,编译程序无法做类型检查,实际程序容易隐藏错误,极难发现。例如:查,实际程序容易隐藏错误,极难发现。例如:int f(double (*fp)() . ) . x = fp(3); . x = f(sin, .); . . y = f(pow, .);. .数值积分和数值积分函数数值积分和数值积分函数这个函数应有这个函数应有3个参数:对应被积函数的一个函数指针个参数:对应被积函数的一个函数指针参数,表示积分限的两个参数,表示积分限的两个double,返回双精度值:

103、返回双精度值:double numInt(MFP fp, double a, double b);用区域分割法逼近积分值(矩形法用区域分割法逼近积分值(矩形法/梯形法等)。分割梯形法等)。分割长度趋于长度趋于0时有共同极限。用等长分割和矩形法:时有共同极限。用等长分割和矩形法:enum DIVN = 30 ;double numInt(MFP fp, double a, double b) double res = 0.0, step = (b - a) / DIVN; int i; for (i = 0; i 1E-6; divn*=2) res0 = res; step = (b - a)

104、 / divn; for (res=0.0, i=0; idivn; +i) res += fp(a + i*step) * step; dif = res - res0; return res;请考虑:这个函数能应付各种数学函数的积分吗?什么请考虑:这个函数能应付各种数学函数的积分吗?什么情况可能出问题?出什么问题?这些问题有解决方案吗情况可能出问题?出什么问题?这些问题有解决方案吗?其中有没有很困难,以至根本无法解决的问题?其中有没有很困难,以至根本无法解决的问题?以函数指针为参数的数组操作实用函数以函数指针为参数的数组操作实用函数在介绍指针与数组的关系时,给出过一些利用指针操作在介绍指针

105、与数组的关系时,给出过一些利用指针操作数组的函数。现在有了函数指针,可以给这类函数加一数组的函数。现在有了函数指针,可以给这类函数加一个操作函数,写出一批操作数组的实用函数。个操作函数,写出一批操作数组的实用函数。 数组元素变换函数数组元素变换函数对整个数组或其中一段元素对整个数组或其中一段元素做做“变换变换”,将,将各元素修改各元素修改为从它们算出的某个值。用函数指针可以写出通用变换为从它们算出的某个值。用函数指针可以写出通用变换函数,将函数,将元素元素操作操作提升提升为序列操作:为序列操作: void tran_int(int *b, int *e, int (*fp)(int) for

106、( ; b != e; +b) *b = fp(*b);也可以为也可以为double类型写出类似操作类型写出类似操作void tran_double(double *b,double *e,MFP fp) for ( ; b != e; +b) *b = fp(*b);例:将例:将double数组数组a前前20个元素改为对应的个元素改为对应的平方根平方根:tran_double(a, a+20, sqrt); 找找“不匹配不匹配”想逐个比较两个想逐个比较两个int数组的元素,找出第一对数组的元素,找出第一对“不匹配不匹配”元素。为了通用,可把元素。为了通用,可把元素匹配元素匹配标准参数化。标准

107、参数化。定义定义“不匹配不匹配”查找函数,它返回前一数组里不匹配元查找函数,它返回前一数组里不匹配元素的位置(指针),找不到就返回素的位置(指针),找不到就返回end的的值。值。假定参数假定参数fp(匹配判断函数)匹配判断函数)在匹配时返回非在匹配时返回非0 0值。值。 int* mismatch(int *b1, int *e1, int *b2, int (*fp)(int, int) for ( ; b1 != e1; +b1, +b2) if (fp(*b1, *b2) = 0) break; return b1;这里假定这里假定b2所指数组至少与所指数组至少与b1,e1)一样大,函数

108、不会一样大,函数不会越界(要求使用者保证)。越界(要求使用者保证)。使用实例:使用实例:假定要找第一对不等元素,只要定义一个相等函数:假定要找第一对不等元素,只要定义一个相等函数:int equal(int m, int n) return m = n; 假定假定a和和b是数组,要考虑前是数组,要考虑前20个元素:个元素:int *p;p = mismatch(a, a+20, b, equal); 假假设设需需要要找找两两个个数数组组前前30个个元元素素中中第第一一对对奇奇偶偶性性不不同同的的元素,只需定义下面函数:元素,只需定义下面函数:int parity(int m, int n) return m%2=n%2; 调用:调用:p = mismatch(a, a+30, b, parity); 本章要点:本章要点:指针的概念、定义和基本操作指针的概念、定义和基本操作以指针为参数的函数。特别是通过指针参数从函数里以指针为参数的函数。特别是通过指针参数从函数里向外传递信息(三个要素)向外传递信息(三个要素)指针与数组的关系,指针运算(指针算术)指针与数组的关系,指针运算(指针算术)动态存储分配的基本技术动态存储分配的基本技术指向函数的指针指向函数的指针此外:此外:类型的描述和解读,类型的描述和解读,typedef指针数组和命令行参数指针数组和命令行参数

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

最新文档


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

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