七章节指针.ppt

上传人:本田雅阁 文档编号:3184086 上传时间:2019-07-22 格式:PPT 页数:97 大小:373.03KB
返回 下载 相关 举报
七章节指针.ppt_第1页
第1页 / 共97页
七章节指针.ppt_第2页
第2页 / 共97页
七章节指针.ppt_第3页
第3页 / 共97页
七章节指针.ppt_第4页
第4页 / 共97页
七章节指针.ppt_第5页
第5页 / 共97页
点击查看更多>>
资源描述

《七章节指针.ppt》由会员分享,可在线阅读,更多相关《七章节指针.ppt(97页珍藏版)》请在三一文库上搜索。

1、第七章 指针,程序执行中数据存于内存。在可用期间数据有确定存储位置,占据一些存储单元。 内存单元的编号:地址。机器语言通过地址访问数据。高级语言用变量等作为存储单元/地址的抽象。,建立变量就是安排存储。赋值时存入,用值时从中提取。 外部变量/静态变量有全局存在期,程序执行前安排存储位置,保持到程序结束。自动变量在函数调用时安排存储,至函数结束。再调用时重新安排存储。,变量存在期就是它占据所安排存储的期间。任何变量在存在期间总有确定存储位置,有固定的地址。 寄存器变量可能放在寄存器,无地址。本章不考虑寄存器变量。,变量存在时有地址,地址用二进制编码,因此可能成为程序处理的数据。问题:地址作为数据

2、有什么用?,若程序可以处理对象地址,就可通过地址处理相关对象。 对象(如变量)地址也被作为数据,地址值/指针值。以地址为值的变量称为指针变量/指针(pointer)。,指针是一种访问其他对象的手段,利用这种机制能更灵活方便地实施对各种对象的操作。,主要操作 指针赋值:将程序对象的地址存入指针变量。 间接访问:通过指针访问被指对象。 指针还能保存其他对象的地址。下面讨论以变量为例。,指针保存着变量x地址,也说指针指向x。示意图:,指针可赋值,其指向在执行中可变。p某时指x,后可能指向y。这样,通过p访问被指对象的语句,前次访问x,后来就访问y。这种新灵活性很有用。,C中用指针常能写出更简洁有效的

3、程序。有些问题必须用指针处理。指针在大型复杂软件中使用广泛。指针使用的水平是评价人的C程序设计能力的重要方面。 C指针灵活/功能强。掌握有难度,易用错,应特别注意。应特别注意使用指针的常见错误,注意!,7.2 指针变量的定义和使用,指针有类型,只能保存特定类型的变量的地址。 指向int的指针p只能指向int变量。p所指也看作int,从p间接访问当作int。常说int指针p1等。,定义指针需指明指向类型。定义指向int的指针变量: int *p, *q; 指针变量可以与其他变量一起定义: int *p, n, a10, *q, *p1, m;,指针是变量,可赋值取值,定义域与存在期。应赋给类型正

4、确的指针值,取出的值也是特定类型的指针值。 用(int *)表示整型指针的类型,其他类似。,指针操作 取地址运算符& 和间接访问操作用*。一元运算符。,取地址运算 ,多个指针可能同时指向同一变量。变量相等就是值相等,指针变量相等说明两个指针指向程序里同一东西。,间接运算 间接运算得到被指针所指的变量,这种表达式可以像普通变量一样使用。设p指向n。间接赋值: *p = 17; 这里写*p相当于直接写n。另一个赋值: m = *p + *q * n; /* 访问n三次 */,+ *p; /* 使变量n的值加1,变成18 */ (*p)+; /* 使变量n的值再加1,变成19。*/ *p += *q

5、 + n; /* 变量n被赋以新值57 */ q = /* 指针q指向了数组a的元素 */,指针作为函数参数 指针作为函数参数有特殊意义,利用这种参数可写出能修改调用时环境的函数。函数调用处的环境指在调用函数的位置能访问的变量全体。 前面函数的特点:可使用调用处环境中变量的值(通过参数),但不能修改这些变量(数组参数除外)。 在函数f里调用g,可将f局部变量作为实参。用g改变f局部变量的唯一方法是将g的返回值赋给f的局部变量。 这种方法局限性太强,例如无法在一个调用中修改两个局部变量的值。利用指针可以改变这种情况。,例:定义函数swap,希望用它交换两个变量的值。因为要改变两个变量,无法通过返

6、回值解决。,下面定义不行: void swap0 (int x, int y) int t = x; x = y; y = t; int f() int a = 5, b = 10; swap0(a, b); /*不行*/ ,函数内修改形参,不会改变调用时的实参。,分析:要(在另一函数里)调用函数g修改调用处的变量(如局部变量),必须在g里面掌握这个变量。,用指针可以解决问题。 把m的地址(也是值)通过指针参数p传给g,函数内对p间接访问就能操作m,包括对m赋值(改变m)。,例: void set3(int * np) *np = 3; 使用实例: int main() int n, m; s

7、et3( ,请回忆scanf的情况。,通过参数改变调用环境的方案包括三方面:1)函数定义中用指针参数;2)函数内用间接操作实际变量;3)调用时以被操作变量的地址作为实参。 函数swap可定义为: void swap(int *p, int *q) int t = *p; *p = *q; *q = t; 交换变量m和n的值,调用形式是: swap(,swap参数类型是(int *),实参必须是合法整型变量的地址。,设有变量定义: int a10, k; 调用实例: swap(,介绍标准库函数scanf时,强调在接受输入的变量前必须写&,就是将变量的地址传给scanf。 scanf采用与swap

8、一样的技术,通过间接访问为指定变量赋值,把输入的值赋给指定变量。,例:改造上章输入整数值并检查值范围的函数,引进指针参数,使之能更好处理输入错误。 前面实现里用特殊整数值指明输入出错,有缺点(可能不存在合适的值)。指针参数提供了另一种方法:,函数增加一个指针参数,通过它送回读入值。用函数返回值传递函数执行的状态信息。 返回1表示输入成功,0表示输入未能正常完成。,新函数定义: int getnumber(char prompt, int imin, int imax, int repeat, int *np) int i; *np = 0; for (i = 0; repeatimax) pr

9、intf(“Correct range %d, %d.n“, imin, imax); while (getchar() != n) ; else return 1; return 0; ,前面的调用现在可以重写为: getnumber(“Choose a range 0, n. Input n: “, 2, 32767, 5, ,更合适的调用形式: if (getnumber(“Your guess: “, 0, m-1, 5, &guess) = 0) /* 处理输入出错的程序片段 */ 这类函数的形参为指针,实参必须是合法变量地址。,这里可以看到标准库函数的样子。通过返回值表示函数的工作

10、情况,是一种常用技术。,与指针有关的一些问题 空指针值:特殊指针值,表示指针变量闲置(未指向任何变量)。是唯一对任何指针类型都合法的值。 空指针值用0表示,标准库定义符号常量 NULL: p = NULL; 和 p = 0; 相同 前一写法易看到是指针,用时必须包含标准头文件。,指针初始化 指针变量定义时可用合法指针值初始化: int n, *p = 若没有初始化,外部指针和局部静态指针自动初始化为用空;普通局部指针不初始化。,指针使用中的常见错误 使用指针的最常见错误是非法间接访问:在指针未指向合法变量的情况下做间接。如: int f (.) int *p, n = 3; *p = 2; .

11、 p没有初始化,没有指向合法变量。,“悬空指针”指值不是(当时)合法的变量地址的指针变量,也常被称为“野指针”。 间接访问悬空指针是严重错误,后果可能很严重。,常见错误写法(设p是悬空int指针,n是int变量): swap(p, 编译程序不能发现scanf的错误。有些系统可能对第一个例子(假设p未初始化)给出警告。 间接访问空指针也同样无理和非法。,通用指针 类型(void *),可以指向任何变量。声明: int n, *p; double x, *q; void *gp1,*gp2;,任何指针值可以赋给通用指针(不必转换)。例: gp1 = / gp2指向x,若通用指针gpt指向g,g类型

12、是指针pt的指向类型,将gpt赋给pt(要写强制转换)通过pt保证正确访问g。gp1 = /* 合法,p是(int*) */,q = (double*)gp1;/*不合法,q是(double*) */,不合法?,上面的转换既不改变指针的值,也不改变其所指对象的类型,但却让程序今后把n当作double型数据看待,这就非法了。,指针类型代表一种观点。被整型指针所指的变量总看成是整型;被双精度指针指向。 指针转换是观点转换。从整型指针转换到通用指针就是丢掉类型信息。保证恢复到原有类型(转回整型指针),被指对象还可用。,通用指针不能做间接运算。被普通指针所指变量的类型明确,间接后可以作为该类型的变量使

13、用。 通用指针可指向任何变量,间接访问的意义无法确定。通用指针没提供被指对象的类型信息,所以不能通过它们直接使用被指对象。 通用指针最无用,唯一用途就是提供指针值。标准库的某些函数使用了通用指针。,7.3 指针与数组,C指针与数组关系密切,以指针为媒介可以完成各种数组操作。常能使程序更加简洁有效。 用指针做数组操作同样要特别注意越界错误。 指针和数组的关系是C语言特有的,除了由C派生出的语言(如C+),一般语言中没有这种关系。,指向数组元素的指针 类型合适的指针可以指向数组元素。假定有定义: int *p1, *p2, *p3, *p4; int a10 = 1,2,3,4,5,6,7,8,9

14、,10;,可以写: p1 = ,p4没指向a的元素,是指向a最后元素向后一个位置。 C语言保证这个地址存在,但写 *p4 是错误的。,写数组名得到数组首元素地址,元素类型的指针值。 “p1 = ,指针运算 当指针p指向数组元素时说p指到了数组里。这时由p可以访问被p指的元素,还可访问数组的其他元素。 例:p1指向a首元素,值合法(a0的地址),p1+1也合法(a1的地址)。p1+2、p1+3、也合法,分别为a其他元素的地址。由它们可间接访问a各元素。,例: *(p1 + 2) = 3;/* 给a2赋值 */ p2 = p1 + 5; /* 使p2指向a5 */,也可由指向非首元素的指针出发访问

15、数组其他元素: *(p2 + 2) = 5; /* 给a7赋值 */ 可用减法访问所指位置之前的元素: *(p2 - 2) = 4; /* 给a3赋值 */,通过指针访问数组元素时必须保证不越界。 运算取得的指针值(即使不间接访问)必须在数组范围内(可过末元素一位置),否则无定义。 这类运算称为“指针运算”。其他常用指针运算: 用指针运算得到的值做指针更新: p2 = p2 - 2; /* 这使p2改指向a3 */,用增/减量操作做指针更新(指针应指在数组里): p3 = p2; +p3; -p2; p3 += 2; 如果两指针指在同一个数组里,可以求差,得到它们间的数组元素个数(带符号整数)

16、。 n = p3 p2; /* 也可以求 p2 p3 */,指在同一个数组里的指针可以比较大小: if (p3 p2) 当p3所指的元素在p2所指的元素之后时条件成立(值为1),否则不成立(值为0)。两个指针不指在同一数组里时,比较大小没有意义。,两个同类型指针可用 = 和 != 比较相等或不等;任何指针都能与通用指针比较相等或不等,任何指针可与空指针值(0或NULL)比较相等或不等。 两指针指向同一数据元素,或同为空值时它们相等。,数组写法与指针写法 如果指针指在数组里,通过指针访问数组元素也可用下标形式写。 设p1指向数组a0,p3指向a5。可写: p13 = 5; p32 = 8; p1

17、3一类写法称为数组写法,*(p+3)一类写法称为指针写法。两类写法有等价效力,可自由选用。,数组名求值得到指针值 可参与一些指针运算,可采用指针写法。a3可写为*(a+3)。(数组名可以“看作”常量指针) 数组名可以与其他指针比大小,相等与不相等,等等。 注意:数组名不是指针变量,特别是不能赋值,不能更改。若a为数组,下面操作是错误的: a+; a += 3; a = p; 有些运算虽不赋值但也可能没意义。 如 a3 不可能得到合法指针值,结果超出数组界限。,指针运算原理 当一个指针指向数组里的元素时,为什么能算出下一元素位置?(这是指针运算的基础) 指针有指向类型,p指向数组a时,p的类型与

18、a元素类型一致,数据对象的大小可以确定。 p+1的值可根据p的值和数组元素大小算出。由一个数组元素位置可以算出下一元素位置,或几个元素之后的元素位置。指针运算的基础。,通用指针即使指到数组里,因没有确定指向类型,因此不能做一般指针计算,只能做指针比较。,基于指针的数组程序设计 指针运算是处理数组元素的另一方式,有时很方便。设有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 (

19、p1 = p2 = a; p1 - p2 10; +p1) printf(“%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指向该数组的“首元素”。,前面函数体里参数用数组写法(对指针可这样写)。通过指针形参d访问的相应实参数组里的各元素。(数组参数就是利用指针

20、实现的!) 这也使采用数组参数的函数能修改实参数组。,函数里也可用指针方式做元素访问。 int intsum (int n, int a) int i, m = 0; for (i = 0; i n; +i) m += *(a+i); return m; ,函数里不能用sizeof确定数组实参大小:函数的数组形参实际是指针,求sizeof算出的是指针的大小。 所有指针大小都一样,它们保存的都是地址值,各种类型的地址值采用同样表示方式。 另一方面,sizeof的计算是在编译中完成的。实参是动态运行中的东西。,使用数组的一段元素 以数组为参数的函数可处理一段元素。求元素和: double sum(

21、int n, double a); 设有双精度数组b,40个元素已有值:,用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 (const char *s) int n = 0; while (*s != 0) s+; n+; return n; 通过局部指针(参

22、数是局部变量)扫描串中字符。,另一实现: int strLength (const char *s) char *p = s; while (*p != 0) p+; return p - s; ,参数是指针类型(char *),实参应是字符串或存字符串的数组。可用指向字符串的指针作为参数。,例2,用指针实现字符串复制函数。直接定义: void strCopy (char *s, const char *t) while (*s = *t) != 0) s+; t+; 赋值表达式有值,0就是0,函数可简化: void strCopy (char *s, const char *t) while

23、 (*s = *t) s+; t+; ,把指针更新操作也写在循环测试条件里,程序是: void strCopy (char *s, const char *t) while (*s+ = *t+) ; / 空语句 注意优先级与结合性,增量运算的作用与值等。,例3,利用指针,输出int数组里一段元素: 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_se

24、q(a+2, a+6); prt_seq(a+4, a+4); prt_seq(a+10, a+10); 最后两个调用对应空序列。序列为“半闭半开”。,还可写出许多类似函数。“设置”函数: void set_seq(int *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 =

25、 0.0; if (b = e) return 0.0; for (p = b; p != e; +p) x += *p; return x / (e - b); ,字符指针与字符数组,定义字符指针时可用字符串常量初始化,如: char *p = “Programming“; 1)定义p;2)建字符串常量“Programming”;3)令p指向字符串常量。见下图。,常用字符指针指向字符数组元素。如指向常量字符串或存着字符串的字符数组,通常指向字符串开始。 也可以指到字符串中间,把它指的东西当字符串用。,比较: char a = “Programming“; 1)定义12个字符元素的数组;2)用

26、“Programming”中各字符和空字符初始化a的各元素。如下图。,1)指针p可重新赋值(a不能赋值),如: p = “Programming Language C“; 2)p和a类型不同,大小不同。a占12个字符的空间。 3)a的元素可以重新赋值。如: a8=e; a9=r; a10=0; a变成“Programmer”。 按规定不得修改字符串常量。,可定义字符指针变量并让它指向已有字符数组。C程序常用这种方式使用和操作字符数组内容。 例如,输入一行到数组里:,enum NLINE = 256 ; char lineNLINE; int count; char *p; /*-*/ p =

27、line; while(plineNLINE-1 /* 统计e的个数 */,7.4 指针数组,复杂C程序里常用指针的数组。 例:需要一组字符串,常用字符指针数组索引它们。如软件中错误信息常用一组字符串表示。分散管理不便。可定义指针数组,指针分别指向输出信息串常量。 也可定义其他类型的指针数组。,定义字符指针数组: char *pa10; 优先级也适用于定义。优先级高,pa是数组,其元素是字符指针。,定义字符指针数组时可用字符串常量提供初始值。例: char *days = “Sunday“, “Monday“, “Tuesday“, “Wednesday“, “Thursday“, “Frid

28、ay“, “Saturday“ ;,简单实例: printf(“Work days: “); for(i=1; i6; +i) printf(“%s “, daysi); printf(“nWeekend: “); printf(“%s %sn“, days6,days0);,字符指针数组实例: 改写第6章的C语言关键字统计程序,把原来的两维字符数组keywords改为字符指针数组。 只需定义下面数组,并用(关键字)字符串对各指针做初始化: char *keywords = “auto“, “break“, “volatile“, “while“ ; 其他部分不需要改,程序可正常工作。,指针数

29、组与两维数组 两维字符数组与字符指针数组不同 。定义: char color16=“RED“,“GREEN“,“BLUE“; char *color=“RED“,“GREEN“,“BLUE“;,命令行参数的处理 启动程序的基本方式是输入命令,要求OS装入代码文件并执行。命令行:描述命令的字符行。图形用户界面系统里的命令行存在于图标/菜单的定义中。,源文件abcd.c加工出可执行文件abcd.exe。命令: abcd 该程序就会装入执行。,除命令名外,命令行常包括其他信息。DOS命令: copy a:file1.txt dir windowssystem /p 附加信息也是字符序列,称为命令行参

30、数。,写处理命令行参数的程序,要用C的命令行参数机制。处理命令行参数很像处理函数参数,写程序时要考虑和处理程序启动时实际命令行提供的信息。,命令行被看作空格分隔的字段,各个命令行参数。 命令名编号为0,其余依次编号。程序启动时把各命令行参数做成字符串,程序里可按规定方式使用。 设有程序 prog1;设启动程序的命令行是: prog1 there are five arguments 这时prog1是编为0的命令行参数,there是编号1的命令行参数,;共5个命令行参数。,可通过main的参数获取命令行参数。main(void)表示不处理命令行参数,main的另一形式带两个参数: int mai

31、n (int argc, char *argv);,常以argc、argv作为main的参数名,参数类型确定。 main开始执行时,argc是命令行参数个数;argv指向一个含argc+1个指针字符指针数组,前argc个指针指向各命令行参数串,最后有一个空指针。,右图是一个现场情况。,可由argc得到参数个数,通过argv访问它们。 可以访问启动程序的命令名本身。在一些系统里,0号参数还包括完整的目录路径。,例:写程序echo打印各命令行参数。写程序时不知道调用时的命令行参数是什么,但可以打印它们:,#include int main (int argc, char *argv) int i;

32、 for (i = 0; i argc; +i) printf(“Args%d: %sn“, i, argvi); return 0; ,while (*argv != NULL) printf(“%sn”, *argv+);,用IDE开发程序时,编辑/调试/执行等工作都在环境里完成,执行程序时如何提供命令行参数? 集成开发环境都有专门机制为启动命令行提供参数。 可转到IDE之外在命令行状态下启动程序。在图形用户界面系统里,有关命令行参数的讨论同样有效。 建立程序项、命令菜单项等也要写出实际命令行,包括提供必需的命令行参数。 一些图形界面系统里可把数据文件拖到程序文件上作为处理对象。此时将自动

33、产生一个命令行。,7.5 多维数组作为参数的通用函数,两维或多维数组参数必须说明除第一维外各维的大小,失去一般通用性。C99提供了处理多维数组的通用函数的标准机制。存在一些技术。下面以两维数组为例,多维数组类似处理。考虑: void prtmat (int m, int p10) int i, j; for (i=0; i m; +i) for (j=0; j10; +j) printf(“%d ”, pij); putchar(n); 只能对第二维长10的数组使用。,(*p),void prtmat (int m, int n, int p) 定义错误,无法通过编译。为什么?,p的指向类型是

34、int数组(两维数组的元素是一维数组)。定义没给出一维数组大小,指针定义不完全。这样,函数知道p0的位置,但无法算出p1等子数组位置以及pij位置。编译无法完成。 实际中确实需定义处理多维数组的通用函数。,解决方案: 考虑数组 int a108; 首元位置&a00。每行8元素,第1行开始: &a00 + 8 访问第1行首元素用表达式:*(&a00 + 8) 访问第i行首元素用表达式:*(&a00 + i*8) 访问aij:*(&a00 + i*8 + j),处理两维数组所需信息:1)基本元素类型;2)数组两个维的长度;3)数组开始位置。,输出两维整型数组的函数,每行输出在一行。 void pr

35、tMatrix (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, ,如果写: prtMatrix(10, 8, a);会怎样?,prtMatrix(10, 8, a0);?,7.6 动态存储管理,变量(简单变量/数组等)用于保存数据,需安排存储(存储分配)。高级语言编程不需要考虑存储细节,有关工作由编译程序完成。编程效

36、率高。 C外部变量/局部静态变量在编译时确定存储,开始执行前分配。自动变量在执行进入定义函数时分配存储。 共同性质:变量大小都是静态确定的。,例:自动数组大小需用静态表达式描述。函数中变量和参数决定了执行时所需存储空间,都可在编译时确定。 静态处理存储的优点是方便/效率高,执行中工作简单/速度快。但对编程方式加了限制,有些问题不好解决。,例:要处理学生成绩,需要用数组存放。成绩项数可能不同,能否先通知数据项数,再建数据表示?,理想方式: int n; . scanf(“%d“, /* 不行!*/ . /* 读入数据,然后处理 */ 不能用变量说明scores大小(必须静态确定)。,至今讨论的机

37、制无法很好解决这类问题。,这里的问题:程序运行中需要使用存储,有时程序对存储的需求量在写程序时不能确定。,可能解决方案: 1)分析问题,定义适当大小的数组。若分析正确,一般都能处理。但数据很多时程序就不能用。 2)定义尽可能大的数组以满足任何需要。浪费大量存储资源。如有多个这种数组就更难办。系统可能无法容纳几个大数组,但实际上它们并不同时需要很大空间。,解决的办法是“动态存储分配”。在程序运行中做存储分配工作。,动态存储分配与释放 根据运行中的需要分配存储,取得存储块使用,称为动态存储分配。在运行中根据需要动态进行。,程序里怎样使用分配的存储块?程序使用变量是通过名字。动态分配的存储块没有名字

38、,需要其他访问途径。 借助于指针。用指针指向存储块,间接使用被指存储。访问动态分配存储是指针的最重要用途。,与此对应:动态释放,不用的动态存储块应交还。,动态分配/释放由动态存储管理系统完成,这是程序运行系统的子系统,管理着称作堆(英文heap)的存储区。 大部分常规语言都有这种机制。,C语言的动态存储管理机制 用标准库函数实现,,1)存储分配函数malloc()。原型: void *malloc(size_t ns); /*size_t是某整型*/ 分配一块不小于ns的存储,返回其地址。无法满足时返回空指针值。,int n; double *data; . scanf(“%d“, if (d

39、ata = NULL) /* 分配未完成时的处理 */ datai*(data+j)/*正常处理*/,malloc的返回值(void*)应通过类型强制转为特定指针类型后赋给指针变量。,分配存储块大小应该用sizeof计算。 动态分配必须检查成功与否。 动态分配的块大小也是确定的。越界使用(尤其是越界赋值)可能导致程序或系统垮台。,2)带计数和清0的存储分配函数calloc。原型: void *calloc(size_t n, size_t size); size是元素大小,n是个数。 分配一块存储,足够存n个大小为size的元素,并把元素全部清0;无法分配时返回空指针值。,前面的存储分配问题也

40、可用下面语句实现: data = (double*)calloc(n, sizeof(double); 主要差别:malloc对所分配的区域不做任何事情,calloc对整个区域自动清0。,3)动态存储释放函数free。原型: void free(void *p); free释放p指的存储块。该块必须是通过动态存储分配得到的。p值为空时什么也不做。执行free(p)后p值未变,被指块可能已变。不能间接访问已释放存储块。不要对并非指向动态分配块的指针用本操作。,为保证动态存储的有效使用,动态分配块不再用时应释放。动态存储块的释放只能通过调用free完成。,程序例子: int fun (.) int

41、 *p; . p = (int *)malloc(.); . free(p); return .; /*退出函数前应释放函数内分配且已无用的动态存储*/ fun退出时p存在期结束,若没有访问分配块的其他途径,将不可能再用到函数里分配的存储块。,动态存储的流失。 如程序长期执行,存储流失就可能成为严重问题。对实际系统可能是很严重的问题。,4)分配调整函数realloc。函数原型是: void *realloc(void *p, size_t n); 更改已有分配。p指原分配块,n是新大小要求。 返回大小至少为n的存储块指针。新块与原块一致:新块小时保存原块n范围内数据;新块大时原数据存在,新增部

42、分不初始化。分配成功后原块可能改变。 无法满足时返回空指针,原块不变。,常用写法(防止分配失败导致原存储块丢失) : q = (double*)realloc(p, m * sizeof(double); if (q = NULL) /*未成功,p仍指原块,特殊处理*/ else p = q; /* 令p指向新块,正常处理 */ . ,例1:修改筛法程序,由命令行参数得到所需范围。如无命令行参数则要求用户输入确定范围的整数值。,先考虑整体设计。为了清晰,将筛法用函数实现。 命令行参数(字符串)需要转换到整数,定义函数: int s2int(const char *s);,函数实现(标准库有类似

43、函数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 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

44、+= i) anj = 0; / i的倍数不是素数 ,main的工作:1)获取范围(由命令行或用户),2)分配空间及初始化,3)执行筛法,4)打印输出。,enum LARG = 65535 ; int main(int argc, char *argv) int i, j, n, *ns; if (argc = 2) n = s2int(argv1); else getnumber(“Largest: “,2,LARG,5, ,例2,改造成绩直方图程序,使之能处理任意个数据。,如何处理事先无法确定数目的数据集合。用数组限制了能处理的项数,现在改用动态分配。让getscores根据需要申请存储块

45、,返回动态分配的块和实际项数。 double* readscores(int* np);,通过np送回项数,无法得到存储时返回NULL。若已读部分数据,扩大存储失败,给出信息并返回部分数据。 int main() int n; double *scores = readscores( . /* 其他不必修改 */ ,readscores的定义: 事先不知道数据项数,可以先分配一块,读入中发现不够用时扩大。开始分配多大?采用什么扩大策略?,下面采用开始分配一块,随后不够时加倍的策略。有关存储分配和扩大策略的讨论见书。,这个定义主要显示分配调整技术,没有追求完善。读入中遇到错误数据就立即结束。 数

46、据检查和处理问题前面已讨论过,修改这个函数,使之能合理处理输入数据错误,给出有用信息,或增加其他有用功能等都留作 练习。,enum 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“, ,函数、指针和动态分配,用函数处理一组数据,得到处理结果,最好的

47、方式是为函数提供数组位置和元素数(或结束位置)。前面多采用这种方式。 这时函数不必知道处理的是数组变量还是动态存储。,例如,完全可以用如下方式调用筛法函数: int ns1000; int main() . sieve(1000, ns); . return 0; ,这时存储的问题在一个函数里处理。责任清晰,易于把握,是最好的处理方案。,有时无法用上述做法,直方图程序只能由readscores根据情况分配,送出存储块地址。main用指针接收。,这种做法完全正确,因为动态分配的块将一直存在到明确释放(调用free),与分配所在的函数结束无关。 readscores返回存储块地址不仅传回数据,也将

48、管理这个块的责任转交给main。可见前面的main结束前缺了free(scores)调用。应加上。,前面的readscores返回块地址,由参数传块大小(将int变量地址给int*)。另一可能是让readscores返回块大小,无法完成时返回0。考虑这种设计的问题:,调用方式: if (readscores(.)=0) /*处理错误*/ ,int readscores(?); /* 参数类型?*/ 需要通过实参得到动态块的地址,(double*)值。,已知,想通过函数参数取得int值,参数应为(int*)类型,实参用int变量地址。通过参数送出(double*)值,实参应传(double*)变量的地址,形参应该是指向(double*)类型的指针,即(double*)类型。,正确原型: int readscores(double* dpp);,调用: if(readscores(&scores)=0)/*错误处理*/

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1