菜鸟问与答(函数)

1、什么是函数
在C语言中,完成一个特定任务的程序段、子程序称作函数,在一个完整的C程序中,通常是由很多个函数组成的,每一个函数完成其指定的任务。

2、什么是函数原型
一个函数有函数名、函数的返回值及返回值的类型、函数的参数及参数的类型。对函数的所有这些特征予以描述称作函数的原型。函数原型一般放在整个程序文件的开始部分(内部函数)。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件必须包含这个头文件。

**3、什么是函数的返回值
** 当一个函数执行完之后传递给调用函数一个值,称为返回值。是否需要返回一个值给调用函数,具体情况视程序中的需要而定,有时根本就不需要返回一个值给调用函数。

4、为什么要说明函数的原型
函数原型能告诉编译程序一个函数接受什么样的参数,将返回什么样的返回值,这样编译程序就能检查对函数的调用是否正确,是否存在错误的类型转换。例:

int some_func(int,char *,long);

编译程序应会检查所有对该函数的调用(包括该函数的定义)是否使用了三个参数并且返回一个int类型的值。如果编译程序发现函数的调用或定义与函数原型不匹配,编译程序应会报告出错或警告信息。例如对上述函数原型来说,当编译程序检查以下语句时,就会报告出错或警告消息:

x=some_func(1); /参数个数少/
x=some_func("HELLO!",1,"DUDE!"); /参数类型错误/
x=some_func(1,str,2879,"T"); /参数太多/

下面的函数调用同样是不正确的,因为函数some_func()的返回值不是一个long *类型的值。

lValue=some_func(1,str,2879); /函数返回值应该是int而不是long型。/

同样编译程序还能检查函数的定义(或函数体)是否与函数原型匹配。例如当编译程序检查以下函数定义时,应会报告出错或警告信息:

int some_func(char * string,,long lValue,int iValue) /参数的位置不对/

总之,在源文件中说明函数原型提供了一种检查函数是否被正确引用的机制。目前许多流行的编译程序都会检查被引用的函数的原型是否已在源文件中说明过,如果没有,就会发出警告信息。

5、一个函数可以有多少个参数
一个函数的参数的数目没有明确的限制,但是参数过多(例如超过8个)显然是一种不可取的编程风格。参数的数目直接影响到调用函数的速度,参数越多,函数调用越慢。另一方面,函数的参数少,程序就显得精练、简捷,这有助于检查和发现程序中的错误,因此通常应该尽量减少参数的数目,如果一个函数的参数超过4 个,就应该考虑一下函数是否编写得当。如果一个函数不得不使用很多参数,可以定义一个结构来容纳这些参数,这是一种非常好的解决办法。
在下例中,函数print_report()需要使用10个参数,然而在它的参数说明中并没有列出这些参数,而是通过一个RPT_PARMS的结构得到这些参数。

#include

  1<stdio.h>   
  2typedef struct   
  3{   
  4int orientation;   
  5char rpt_name[25];   
  6char rpt_path[40];   
  7int destination;   
  8char output_file[25];   
  9int starting_page;   
 10char ending_page;   
 11char db_name[25];   
 12char db_path[40];   
 13int draft_quality;   
 14}RPT_PARMS;   
 15  
 16int print_report(RPT_PARMS *);   
 17void main(void)   
 18{   
 19RPT_PARMS rpt_parm;   
 20...   
 21...   
 22rpt_parm.orientation=ORIENT_LANDSCAPE;   
 23rpt_parm.rpt_name="QSALES.RPT";   
 24rpt_parm.rpt_path="C:\REPORTS";   
 25rpt_parm.destingation=DEST_FILE;   
 26rpt_parm.output_file="QSALES.TXT";   
 27rpt_parm.starting_page=1;   
 28rpt_parm.ending_page=RPT_END;   
 29rpt_parm.db_name="SALES.DB";   
 30rpt_parm.db_path="C:\DATA";   
 31rpt_parm.draft_quality=TRUE;   
 32  
 33ret_code=print_report(&amp;rpt_parm);   
 34....   
 35....   
 36}   
 37  
 38int print_report(RPT_PARMS *p)   
 39{   
 40int rc;   
 41....   
 42....   
 43orient_printer(p-&gt;orientation);   
 44set_printer_quality(p-&gt;draft_quality==TRUE)?DRAFT:NORMAL);   
 45....   
 46....   
 47return rc;   
 48} 
 49
 50上例唯一不足的是编译程序无法检查引用print_report()函数时RPT_PARMS结构的10个成员是否符合要求。   
 51**  
 526、什么是内部函数 **   
 53内部函数用static来说明,是作用域只限于说明它的源文件的函数。作用域指的是函数或变量的可见性。如果一个函数或变量在说明它的源文件以外也是可见的,那么就称它有全局或外部作用域果一个函数或变量在说明它的源文件中是可见的,那么就称它具有局部或内部作用域。   
 54内部函数只能在说明它的源文件中使用。如果你呈希望一个函数不会在说明它的源文件以外被使用,就应该将它说明为内部函数,这样做是一个好的编程习惯,因为这样可以避免与其它源文件中的同名函数发生冲突。   
 55例: 
 56
 57#include<stdio.h>   
 58int open_customer_table(void);   
 59static int open_customer_indexes(void);   
 60  
 61int open_customer_table(void)   
 62{   
 63int ret_code;   
 64....   
 65....   
 66if(ret_code==OK)   
 67{   
 68ret_code=open_customer_indexes();   
 69}   
 70return ret_code;   
 71}   
 72  
 73static int open_customer_indexes(void)   
 74{   
 75int ret_code;   
 76....   
 77....   
 78return ret_code;   
 79}   
 80
 81
 82在上例中,函数open_customer_table()是一个外部函数,它可以被任何模块调用,而函数open_customer_indexes()是一个内部函数,它永远不会被其它模块调用。之所以这样说明这二个函数,是因为函数open_customer_indexes()只需被函数open_customer_table()调用,即只需在上例的源文件中使用。 
 83
 84**7、如果一个函数没有返回值,是否需要加入return语句**   
 85在C语言中,用void关键字说明的函数是没有返回值的,并且也没有必要加入return语句。在有些情况下,一个函数可能会引起严重的错误,并且要立即退出该函数,这时就应该加入return语句,以跳过函数体内还未执行到的代码。但是在void函数中随意加入return语句是一个不好的编程习惯,困此,在void函数中退出函数的操作应该尽量集中和简洁。   
 86  
 878、怎样把数组作为参数传给函数   
 88在把数组作为参数传给函数时,有值传递(传值 by value)和地址传递(传址 by reference)二种方式。在值传递方式中,在说明和定义函数时,要在数组参数尾部加上一对方括号,调用函数时只需要将数组的地址(即数组名)传递给数组。   
 89例:在下例中数组x[]是通过值传递方式传递给byval_func()函数的。 
 90
 91#include<stdio,h>   
 92  
 93void byval_func(int[]); /*这个函数的参数是一个int型数组*/   
 94  
 95void main(void)   
 96{   
 97int x[10];   
 98int y;   
 99for(y=0;y&lt;10;y++) /*初始化整型数组*/   
100x[y]=y;   
101byval_func(x); /*把数组名放在参数里,注意是值传递*/   
102}   
103  
104void byval_func(int i[])   
105{   
106int y;   
107for(y=1;y&lt;11;y++)   
108{   
109i[y]=y;   
110printf("%d"\n",i[y]);   
111}   
112}   
113  
114
115
116在上例中,定义了一个名为x的数组,并对它的10个元素赋了初值,函数byval_func()的说明如下所示:   
117  
118
119
120int byval_func(int[]); 
121
122参数int[]告诉编译程序byval_func()只有一个参数,即一个int类型值组成的数组,在调用byval_func()函数时,只需将数组的地址传递给该函数,即:   
123
124
125byval_func(x); 
126
127  
128在值传递方式中,数组x将被复制一份,复制所得的数组将被存放在栈中,然后由byval_func()函数接收并打印出来。由于传递给byval_func()函数的是初始数组的一份考贝,因此在byval_func()函数内部修改传递过来的数组对初始数组没有任何影响。   
129值传递方式的开销是非常大的,其原因有这样几点:第一,需要完整地复制初始数组并将这份考贝存放到栈中,这将耗费相当长的时间,因而值传递方式的效率比较低二,初始数组的考贝需要占用额外的内存空间(栈中的内存)三,编译程序需要专门产生一部分用来复制初始数组的代码,这将使程序变大。   
130地址传递方式克服了值传递方式的缺点,是一种更好的方式。在地址传递方式中,传递给函数的是指向初始数组的指针,不用复制初始数组,因此程序变得精练和高效,并且也节省了内存和栈空间。在地址传递方式 中,只需在函数原型中将函数的参数说明为指向数组元素数据类型 的一个指针。   
131例: 
132
133#include<stdio.h>   
134void const_func(const int *);   
135void main(void)   
136{   
137itn x[10];   
138int y;   
139for(y=0;y&lt;10;y++)   
140x[y]=y;   
141const_func(x);   
142}   
143  
144void const_func(const int *i)   
145{   
146int y;   
147for(y=1;y&lt;11;y++)   
148{   
149i[y]=y;   
150printf("%d\n",*(i+y));   
151}   
152}   
153
154
155在上例中,同样定义了一个名为x的数组,并对它的10个元素赋了初值。函数const_func()的说明如下所示:   
156  
157
158
159int const_func(const int *); 
160
161参数const int *告诉编译程序const_func()函数只有一个参数,即指向一个int类型常量的指针。在调用const_func()函数时,同样只需将数组的地址传递给该函数,即:   
162  
163
164
165const_func(x); 
166
167在地址传递方式中,没有复制初始数组并将其考贝存放在栈中,const_func()函数只接收到指向一个int 类型常量的指针,因此在编写程序时要保证传递给const_func()函数的是一个由int类型值组成的数组的指针。const修饰符的作用是防止const_func()函数意外地修改初始数组中的某一个元素。   
168地址传递方式唯一不足的是必须由程序本身来保证将一个数组传递给函数作为参数,例如在函数const_func()的原型和定义中,都没有明确指示该函数的参数是指向一个由int类型值组成的数组的指针。但是,地址传递方式速度快,效率高,因此在对运行速度要求比效高时,应访采用这种方式。</stdio.h></stdio,h></stdio.h></stdio.h>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus