映月读书网 > C语言解惑 > 21.4 结构函数的返回值 >

21.4 结构函数的返回值

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