首页 » C语言解惑 » C语言解惑全文在线阅读

《C语言解惑》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.0000000,0.0000000,0.000000     //p指向的是arg[1],这一组为088.500000     //赋值arg[2].z的输出88.500000     //返回后保留arg[2]的值1000,98.900000     // disp使用结构数组名调用0,0.0000000,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.9000000,0.0000000,0.0000000,0.00000088.5000000.0000001000,98.9000000,123.4560000,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.9000000,0.0000000,0.000000调用fg之前=0x427bb00,0.00000088.500000在fg中=0x427bc0调用fg之后=0x427bb00.0000001000,98.9000000,123.4560000,88.500000  

尽管将fg函数设计为返回指针的函数,但在主程序中并没有使用这个返回值,所以它返回主程序之后,指针指向的地址值与调用时的一样,所以效果也与例21.17相同。

显然,如果在主程序中使用语句


p= fg(p);     //程序中用p接收返回的地址值  

接收返回的地址值,则输出结果为:


1000,98.9000000,0.0000000,0.000000调用fg之前=0x427bb00,0.00000088.500000在fg中=0x427bc0调用fg之后=0x427bc088.5000001000,98.9000000,0.0000000,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.000000i2.000000 + 2.000000i5.000000 + 10.000000i2.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 expressionerror 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