📄 第8章 函 数.txt
字号:
......
return ret_code;
}
在上例中,函数open_customer_table()是一个外部函数,它可以被任何模块调用,而函数open_customer_indexes()是一个内部函数,它永远不会被其它模块调用。之所以这样说明这两个函数,是因为函数open_customer_indexes()只需被函数open_customer_table()调用,即只需在上例所示的源文件中使用。
请参见:
8.1 什么时候说明函数?
8.2 为什么要说明函数原型?
8.3 一个函数可以有多少个参数?
8.5 如果一个函数没有返回值,是否需要加入return语句?
在C语言中,用void关键字说明的函数是没有返回值的,并且也没有必要加入return语句。
在有些情况下,一个函数可能会引起严重的错误,并且要求立即退出该函数,这时就应该加入一个return语句,以跳过函数体内还未执行的代码。然而,在函数中随意使用return语句是一种不可取的编程习惯,因此,退出函数的操作通常应该尽量集中和简洁。
请参见:
8.8 用PASCAL修饰符说明的函数与普通C函数有什么不同?
8.9 exit()和return有什么不同?
8.6 怎样把数组作为参数传递给函数?
在把数组作为参数传递给函数时,有值传递(by value)和地址传递(by reference)两种方式。在值传递方式中,在说明和定义函数时,要在数组参数的尾部加上一对方括号([]),调用函数时只需将数组的地址(即数组名)传递给函数。例如,在下例中数组x[]是通过值传递方式传递给byval_func()函数的:
# include <atdio.h>
voidbyval_func(int[]); /*the byval_func() function is passed an
integer array by value * /
void main (void);
void main (void)
{
int x[10];
int y;
/* Set up the integer array. * /
for (y=0; y<10; y++)
x[y] = y;
/* Call byval_func() ,passing the x array by value. * /
byval_func(x);
}
/* The byval_function receives an integer array by value. * /
void byval_func(int i[])
{
int y;
/* print the content: of the integer array. * /
for (y=0; y<10; y++)
printf("%d\n", i[y]);
}
在上例中,定义了一个名为x的数组,并对它的10个元素赋了初值。函数byval_func()的说明如下所示:
intbyval_func(int []);
参数int[]告诉编译程序byval_func()函数只有一个参数,即一个由int类型值组成的数组。在调用byval_func()函数时,只需将数组的地址传递给该函数,即:
byval_func(x);
在值传递方式中,数组x将被复制一份,复制所得的数组将被存放在栈中,然后由byval_func()函数接收并打印出来。由于传递给byal_func()函数的是初始数组的一份拷贝,因此在byval_func()函数内部修改传递过来的数组对初始数组没有任何影响。
值传递方式的开销是非常大的,其原因有这样几点:第一,需要完整地复制初始数组并将这份拷贝存放到栈中,这将耗费相当可观的运行时间,因而值传递方式的效率比较低;第二,初始数组的拷贝需要占用额外的内存空间(栈中的内存);第三,编译程序需要专门产生一部分用来复制初始数组的代码,这将使程序变大。
地址传递方式克服了值传递方式的缺点,是一种更好的方式。在地址传递方式中,传递给函数的是指向初始数组的指针,不用复制初始数组,因此程序变得精练和高效,并且也节省了栈中的内存空间。在地址传递方式中,只需在函数原型中将函数的参数说明为指向数组元素数据类型的一个指针。请看下例:
# include <atdio. h>
void conat_func(const int* );
void main (void);
void main(void)
{
int x[10];
int y;
/* Set up the integer array. * /
for (y=0; y<10; y++)
x[y] = y;
/* Call conat_func(), passing the x array by reference. */
conat_func(x);
}
/*The const_function receives an integer array by reference.
Notice that the pointer i? declared aa const, which renders
it unmodif table by the conat_funcO function. * /
void conat_func(conat int* i)
{
int y;
/ * print the contents of the integer array. * /
for (y=0; y<10; y++)
printf(""%d\n", *(i+y));
}
在上例中,同样定义了一个名为x的数组,并对它的10个元素赋了初始值。函数const_func()的说明如下所示:
int const_func(const int·);
参数constint·告诉编译程序const_func()函数只有一个参数,即指向一个int类型常量的指针。在调用const_func()函数时,同样只需将数组的地址传递给该函数,即:
const_rune(x);
在地址传递方式中,没有复制初始数组并将其拷贝存放在栈中,const_rune()函数只接收到指向一个int类型常量的指针,因此在编写程序时要保证传递给const_func()函数的是指向一个由int类型值组成的数组的指针。const修饰符的作用是防止const_func()函数意外地修改初始数组中的某一个元素。
地址传递方式唯一的不足之处是必须由程序本身来保证将一个数组传递给函数作为参数,例如,在函数const—rune()的原型和定义中,都没有明确指示该函数的参数是指向一个由int类型值组成的数组的指针。然而,地址传递方式速度快,效率高,因此,在对运行速度要求比较高时,应该采用这种方式。
请参见:
8.8用PASCAL修饰符说明的函数与普通C函数有什么不同?
8.7 在程序退出main()函数之后,还有可能执行一部分代码吗?
可以,但这要借助C库函数atexit()。利用atexit()函数可以在程序终止前完成一些“清理”工作——如果将指向一组函数的指针传递给atexit()函数,那么在程序退出main()函数后(此时程序还未终止)就能自动调用这组函数。下例的程序中就使用了atexit()函数:
# include <stdio.h>
# include <atdlib. h>
void close_files(void);
void print_regiatration_message(void);
int main(int, char ** );
int main (int argc, char** argv)
{
atcxitCprint_regiatration_message);
atexit(cloae_files) ;
while (rec_count <max_recorda)
{
process_one_record ( );
}
exit (0);
}
在上例中,通过atexit()函数指示程序在退出main()函数后自动调用函数close_files()
和print_registration_message(),分别完成关闭文件和打印登记消息这两项工作。
在使用atexit()函数时你要注意这样两点:第一,由atexit()函数指定的要在程序终止前
执行的函数要用关键字void说明,并且不能带参数;第二,由atexit()函数指定的函数在入栈
时的顺序和调用atexit()函数的顺序相同,即它们在执行时遵循后进先出(LIFO)的原则。例
如,在上例中,由atexit()函数指定的函数在入栈时的顺序如下所示:
atexit(print_registration_message);
atexit(close_files);
根据LIFO原则,程序在退出main()函数后将先调用close_files()函数,然后调用print_
registration_message()函数。
利用atexit()函数,你可以很方便地在退出main()函数后调用一些特定的函数,以完成一
些善后工作(例如关闭程序中用到的数据文件)。
请参见:
8.9 exit()和return有什么不同?
8.8 用PASCAL修饰符说明的函数与普通C函数有什么不同?
用PASCAL修饰符说明的函数的调用约定与普通函数有所不同。对于普通的C函数,参数是自右至左传递的,而根据PASCAL调用约定,参数是自左至右传递的。下例是一个普通的C函数:
int regular_func(int,char*,long);
根据普通C函数的调用约定,函数参数入栈时的顺序为自右至左,因此,在调用regular()函数时,其参数的入栈顺序如下所示:
long
char·
int
当regular_func()函数返回时,调用regular_func()函数的函数负责恢复栈。
下例是一个用PASCAL修饰符说明的函数:
int PASCAL pascal_func(int,char *,long);
根据PASCAL调用约定,函数参数入栈时的顺序为自左至右,因此,在调用‘pascal—func()函数时,其参数的入栈顺序如下所示:
int
char *
long
当pascal_func()函数返回时,调用pascal_func()函数的函数负责恢复栈指针。
采用PASCAL调用约定的函数比普通C函数的效率要高一些——前者的函数调用要稍快一些。MicrosoftWindows就是一个采用PASCAL调用约定的操作环境的例子,WindowsSDK中有数百个用PASCAL修饰符说明的函数。
当Windows的第一个版本于80年代末期编写成功时,使用PASCAL修饰符能明显提高程序的执行速度。现在,计算机的运行速度已经相当快,PASCAL修饰符对程序运行速度的作用已经很小了。事实上,Microsoft在其WindowsNT操作系统中已经放弃了PASCAL调用约定。
在大多数情况下,采用PASCAL调用约定对程序的运行速度几乎没有明显的作用,因此,采用普通C函数的调用约定完全能满足编程要求。但是,当几个毫秒的运行时间对你的程序也很重要时,你就应该用PASCAL修饰符来说明你的函数。
请参见:
8.6怎样把数组作为参数传递给函数?
8.9 exit()和return有什么不同?
用exit()函数可以退出程序并将控制权返回给操作系统,而用return语句可以从一个函数中返回并将控制权返回给调用该函数的函数。如果在main()函数中加入return语句,那么在执行这条语句后将退出main()函数并将控制权返回给操作系统,这样的一条return语句和exit()函数的作用是相同的。下例是一个使用了exit()函数和return语句的程序:
#include <stdio.h>
#include <stdlib.h>
int main (int, char** );
int do_processing (void);
int do_something_daring();
int main (int argc, char** argv)
{
int ret_code;
if (argc <3)
{
printf ("Wrong number of arguments used ! \n");
/* return 1 to the operating system * /
exit(1);
}
ret_code = do_processing ();
......
/* return 0 to the operating system * /
exit(0);
}
int do_processing(void)
{
int rc;
rc = do_aomcthing_daring();
if (rc == ERROR)
{
printf ("Something fiahy ia going on around here... *\n);
/* return rc to the operating syatem * /
exit (re);
}
/* return 0 to the calling function * /
return 0;
}
在上例的main()函数中,如果argc小于3,程序就会退出。语句“exit(1)”指示程序在退出时将数字1返回给操作系统。操作系统有时会根据程序的返回值进行一些相关的操作,例如许多DOS批处理文件会通过一个名为ERRORLEVEL的全局变量来检查可执行程序的返回值。
请参见:
8.5如果一个函数没有返回值,是否需要加入return语句?
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -