还是老问题。没有返回值照样可以改变实参的值,有返回值一样可以不改变实参的值。设计函数关键是看如何简单、实用。结构函数可以无返回值,也可以返回结构或结构指针,推荐尽可能优先考虑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