还是老问题。没有返回值照样可以改变实参的值,有返回值一样可以不改变实参的值。设计函数关键是看如何简单、实用。结构函数可以无返回值,也可以返回结构或结构指针,推荐尽可能优先考虑void类型。
【例21.17】一个源程序如下:
#include <stdio.h> struct List { int a ; double z ; }arg[3] ,*p ; void fg ( struct List * ); void disp ( struct List * ); void main ( ) { p=arg ; p->a=1000 ; p->z=98.9 ; p++ ; disp (arg ); fg (p ); printf (\"%fn\" , p->z ); p->z=123.456 ; ++p ; disp (arg ); } void fg (struct List *p ) { printf (\"%d ,%fn\" , p->a , p->z ); p++ ; p->z=88.5 ; printf (\"%fn\" , p->z ); } void disp ( struct List *s ) { int i ; for (i=0 ;i<3 ;i++ ) printf (\"%d ,%fn\" , s[i].a , s[i].z ); }
有一位程序员分析得出如下运行结果。
1000 ,98.900000 //disp 使用结构数组名调用,故只有arg[0] 有数据 0 ,0.000000 0 ,0.000000 0 ,0.000000 //p 指向的是arg[1] ,这一组为0 88.500000 // 赋值arg[2].z 的输出 88.500000 // 返回后保留arg[2] 的值 1000 ,98.900000 // disp 使用结构数组名调用 0 ,0.000000 0 ,123.456000 // 因为用它覆盖了88.5
请问这个分析对吗?
【解答】不对。对第1次和第2次的输出的分析是对的。对返回主程序的输出的分析是错的。这时要注意函数fg返回的指针到底指向哪里。在fg中,p是指向arg[2]。但这个函数没有返回值,既然现在的指针不参与返回,这就要取决于在程序中的具体使用方法。
在这个函数fg中,p参与左值运算,但在fg程序运行结束时,它是返回进入时的指向,即arg[1],所以这时的z为0,输出应为0值而不是88.5,并且保留对arg[2]的修改。输入123.456是修改当前指向的arg[1]的z值。虽然执行指针运算使p指向arg[2],但调用时却是使用数组名,所以从arg[0]开始输出。由此可见,输出结果应该为
1000 ,98.900000 0 ,0.000000 0 ,0.000000 0 ,0.000000 88.500000 0.000000 1000 ,98.900000 0 ,123.456000 0 ,88.500000
【例21.18】如果将例21.17中的fg函数设计为返回指针的函数,是否会输出像那个程序员分析的结果呢?
【解答】如果只是将函数声明和定义分别修改,主程序不变,则仍然不会符合他的分析。为了更好地说明问题,特意在程序中输出指针指向的地址,一看输出结果,就非常清楚了。
// 增加输出地址的源程序 #include <stdio.h> struct List { int a ; double z ; }arg[3] ,*p ; struct List *fg ( struct List * ); // 原型声明 void disp ( struct List * ); int main ( ) { p=arg ; p->a=1000 ; p->z=98.9 ; p++ ; disp (arg ); printf (\" 调用fg 之前=0x%xn\" , p ); // 输出调用时的指向地址 fg (p ); printf (\" 调用fg 之后=0x%xn\" , p ); // 输出调用返回后的指向地址 printf (\"%fn\" , p->z ); p->z=123.456 ; ++p ; disp (arg ); return 0 ; } struct List *fg (struct List *p ) { printf (\"%d ,%fn\" , p->a , p->z ); p++ ; p->z=88.5 ; printf (\"%fn\" , p->z ); printf (\" 在fg 中=0x%xn\" , p ); // 输出程序最后使用时的指向地址 return p ; } void disp ( struct List *s ) { int i ; for (i=0 ;i<3 ;i++ ) printf (\"%d ,%fn\" , s[i].a , s[i].z ); }
程序运行结果如下:
1000 ,98.900000 0 ,0.000000 0 ,0.000000 调用fg 之前=0x427bb0 0 ,0.000000 88.500000 在fg 中=0x427bc0 调用fg 之后=0x427bb0 0.000000 1000 ,98.900000 0 ,123.456000 0 ,88.500000
尽管将fg函数设计为返回指针的函数,但在主程序中并没有使用这个返回值,所以它返回主程序之后,指针指向的地址值与调用时的一样,所以效果也与例21.17相同。
显然,如果在主程序中使用语句
p= fg (p ); // 程序中用p 接收返回的地址值
接收返回的地址值,则输出结果为:
1000 ,98.900000 0 ,0.000000 0 ,0.000000 调用fg 之前=0x427bb0 0 ,0.000000 88.500000 在fg 中=0x427bc0 调用fg 之后=0x427bc0 88.500000 1000 ,98.900000 0 ,0.000000 0 ,123.456000
这个结果就与那个程序员分析的一样了。
【例21.19】如果将例21.18中的fg函数设计为如下的返回结构的函数。
struct List fg (struct List *p ) { printf (\"%d ,%fn\" , p->a , p->z ); p++ ; p->z=88.5 ; printf (\"%fn\" , p->z ); printf (\"%un\" , p ); return *p ; }
假设主函数不变,试分析输出结果。
【解答】因为没有使用返回值,所以结果与例21.18的一样。
函数有返回值,主函数不使用,这个函数对主函数的影响就与它的返回值无关。如果使用如下调用方式。
*p=fg (p );
在fg函数里,最后使用的是arg[2],返回的指针指向进入时的结构数组元素arg[1],所以除了保留修改的arg[2]值之外,还将arg[2]的值整体赋给arg[1],所以输出是“88.5”。这个值接着又被新输入的“123.456”代替。
由此可见,如何设计函数的返回值以及如何使用返回值,均是要仔细斟酌的。
为了加强理解,可以使用跟踪调试方法观察程序的运行过程。下面给出配合单步跟踪的源程序,程序里将地址用十六进制输出,为arg[3]的a赋值以便对比,并在输出结果中给出执行的过程。
// 演示程序 #include <stdio.h> struct List { int a ; double z ; }arg[3] ,*p ; struct List fg ( struct List * ); void disp ( struct List * ); int main ( ) { p=arg ; p->a=1000 ; p->z=98.9 ; p++ ; disp (arg ); *p=fg (p ); printf (\"%0x ,%d ,%fn\" , p ,p->a ,p->z ); printf (\"%0x ,%d ,%fn\" , (p+1 ),(p+1 )->a ,(p+1 )->z ); p->z=123.456 ; printf (\"%0x ,%d ,%fn\" , p ,p->a ,p->z ); ++p ; disp (arg ); return 0 ; } struct List fg (struct List *p ) { printf (\"%d ,%f ,%0xn\" , p->a , p->z ,p ); p++ ; p->a=58 ; p->z=88.5 ; printf (\"%d ,%f ,%0xn\" , p->a ,p->z ,p ); return *p ; } void disp ( struct List *s ) { int i ; for (i=0 ;i<3 ;i++ ) printf (\"%d ,%f ,%0xn\" , s[i].a , s[i].z ,s+i ); }
程序运行结果如下。
1000 ,98.900000 ,427ba0 // 主程序设置arg[0] 0 ,0.000000 ,427bb0 //arg[1] 的初值 0 ,0.000000 ,427bc0 //arg[2] 的初值 0 ,0.000000 ,427bb0 //427bc0 进入函数fg ,输出arg[1] 的值 58 ,88.500000 ,427bc0 // 设置arg[2] 427bb0 ,58 ,88.500000 // 输出arg[2] 427bc0 ,58 ,88.500000 // 返回并输出arg[1] ,等效arg[1]=arg[2] 427bb0 ,58 ,123.456000 // 123.456 覆盖88.5 ,输出修改的arg[1] , 1000 ,98.900000 ,427ba0 // 输出全部内容 58 ,123.456000 ,427bb0 // 主程序操作修改返回的arg[1] 58 ,88.500000 ,427bc0 //fg 函数修改的内容
【例21.20】假设已定义如下复数结构。
typedef struct { double re , im ; }complex ;
编写复数的加、减、乘、除的计算函数并验证之。
【解答】主要是除法运算需要考虑除数为0的情况。其实,作为除法函数,应该处理这种情况,但作为调用者,也应该避免这种情况。不要把希望寄托在别人身上,要考虑主动预防错误。
对于除法函数,可能选择的路也很多,要根据要求考虑合适的处理方法。有时不希望直接使用exit函数退出,而希望在得到结果后自己处理。例如:
complex p (complex x , complex y ) { double d ; complex z ; z.re=0 ;z.im=0 ; d = y.re*y.re + y.im*y.im ; if (d==0 ) return z ; z.re = (x.re * y.re + x.im*y.im )/d ; z.im = (x.im * y.re - x.re*y.im )/d ; return ( z ); }
这里返回的z的实部和虚部都是0,可以在主程序中判别这个值进行处理。p函数是在计算出d之后,再判别d是否为0。其实,可以先判别除数的实部和虚部是否为0,如果为0,则不要去计算d值。例如:
complex p (complex x , complex y ) { double d ; complex z ; z.re=0 ;z.im=0 ; if ((y.re==0 )&& (y.im==0 )) return z ; d = y.re*y.re + y.im*y.im ; z.re = (x.re * y.re + x.im*y.im )/d ; z.im = (x.im * y.re - x.re*y.im )/d ; return ( z ); }
主程序根据z值自己决定如何处理。其实,在调用p函数之前,应该养成先判别除数是否为0的习惯。如果为0,则根本不需要调用p函数。
注意:函数可以有多个返回路径,但每次运行只能有一个条件满足,也即一个路径。
这里给出一个示范的处理方法。
#include <stdio.h> typedef struct { double re , im ; }complex ; complex add (complex , complex ); complex minus ( complex , complex ); complex mul (complex , complex ); complex p (complex , complex ); int main ( ) { complex a ,b ,c ,d ; a.re=4.0 ; a.im=3.0 ; b.re=2.0 ; b.im=1.0 ; d.re=0.0 ; d.im=0.0 ; c=add (a ,b ); printf (\"%lf + %lfin\" ,c.re ,c.im ); c=minus (a ,b ); printf (\"%lf + %lfin\" ,c.re ,c.im ); c=mul (a ,b ); printf (\"%lf + %lfin\" ,c.re ,c.im ); if ((b.re==0 )&& (b.im==0 )){ printf (\" 除数为0 ,不能调用,退出!n\" ); return 0 ; } c=p (a ,b ); if ((c.re==0 )&& (c.im==0 )){ printf (\" 除数为0 ,返回为0 ,输出为0 。n\" ); } printf (\"%lf + %lfin\" ,c.re ,c.im ); c=p (a ,d ); if ((c.re==0 )&& (c.im==0 )) { printf (\" 除数为0 ,返回为0 ,输出为0 。n\" ); } printf (\"%lf + %lfin\" ,c.re ,c.im ); if ((d.re==0 )&& (d.im==0 )){ printf (\" 除数为0 ,不能调用,退出!n\" ); return 0 ; } c=p (a ,d ); if ((c.re==0 )&& (c.im==0 )) { printf (\" 除数为0 ,返回为0 ,输出为0 。n\" ); } return 0 ; } complex add (complex x , complex y ) { complex z ; z.re = x.re + y.re ; z.im = x.im + y.im ; return ( z ); } complex minus ( complex x , complex y ) { complex z ; z.re = x.re - y.re ; z.im = x.im - y.im ; return ( z ); } complex mul (complex x , complex y ) { complex z ; z.re = x.re * y.re - x.im*y.im ; z.im = x.im * y.re + x.re*y.im ; return ( z ); } complex p (complex x , complex y ) { double d ; complex z ; z.re=0 ;z.im=0 ; if ((y.re==0 )&& (y.im==0 )) return z ; d = y.re*y.re + y.im*y.im ; z.re = (x.re * y.re + x.im*y.im )/d ; z.im = (x.im * y.re - x.re*y.im )/d ; return ( z ); }
程序运行结果如下。
6.000000 + 4.000000i 2.000000 + 2.000000i 5.000000 + 10.000000i 2.200000 + 0.400000i 除数为0 ,返回为0 ,输出为0 。 0.000000 + 0.000000i 除数为0 ,不能调用,退出!
注意:主程序有意制造除数为0的情况以检验处理分支。
【例21.21】本程序是编写一个用Eratosthenes筛选算法找出比N小的系数的程序。算法思想如下。
产生一个包含从2到N的排序连接链。
for ( num = 2 ; num = n ; num = 可能存在的下一个链元素)
删除链中所有为num的整数倍的数,链中剩下的数为素数。
编写的程序通不过编译,请找出错误。
// 源程序 const int N=100 ; struct number{ // 结构具有指向自己的成员next int num ; struct number *next ; }a[N] ; #include <stdio.h> void Star (struct number * ); void Find (struct number * ); int main () { struct number *p ; // 结构指针 p=&a[2] ; // 指向结构变量 Star (p ); // 初始化 Find (p ); // 求解 return 0 ; } void Star (struct number *p ) { int i ; for (i=2 ;i<N-1 ;i++ ) // 初始化,实际形成一排序连接链 { a[i].num =i ; a[i].next = &a[i+1] ; } a[N-1].num =N-1 ; // 处理第N 个数组元素 a[N-1].next =0 ; // 结束标志 } void Find (struct number *p ) { int n=0 ; for (p=&a[2] ;p ;p=p->next ) // 使结构指针自动指向下一个可能的链元素 for (n=2 ; n<p->num ; n++ ) { if (p->next==0 ) break ; // 没有下一可能链元素则退出循环 else { for (n=2 ;n < p->num ;n++ ) // 以从2 开始的整数n 作为除数,以指针 // 指向的下一个链元素为被除数 { if ((p->next->num )%n==0 ) // 如果余数为0 {// 则指针改为指向下一个链元素的next ,即将下一链 p->next = p->next->next ; // 元素删除,并且退出循环 break ; } } } } if (N<=2 ) // 输出 printf (\" 不存在比%d 小的素数n\" ,N ); else{ int i=1 ; for (p=&a[2] ;p ;p=p->next ,i++ ){ printf (\"%5d\" ,p->num ); if (i%5==0 ) printf (\"n\" ); } } printf (\"n\" ); }
【解答】分析第1次扫描的信息。
error C2057 : expected constant expression error C2466 : cannot allocate an array of constant size 0
错误很简单,是结构数组“a[N]”的N不符合要求。即使使用
const unsigned mt N = 100
声明,N也不能作为数组的维数,这里需要使用宏定义来定义常数,即
#define N 100
程序输出结果如下:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97