函数定义
dataType functionName(){ //body }
- dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int、float、char 等。
- functionName 是函数名,它是标识符的一种,命名规则和标识符相同。函数名后面的括号
( )
不能少。 - body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由
{ }
包围。 - 如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 dataType 一样。
内联函数
要将一个函数定义为内联函数,需要在函数定义时加上 inline 函数修饰符。关键字 inline 告诉编译器,任何地方只要调用内联函数,就直接把该函数的机器码插入到调用它的地方。这样程序执行更有效率,就好像将内联函数中的语句直接插入到了源代码文件中需要调用该函数的地方一样。
inline 修饰符并非强制性的:编译器有可能会置之不理。例如,递归函数通常不会被编译成内联函数。编译器有权自行决定是否要将有 inline 修饰符的函数编译成内联函数。
和其他函数不同的是,在每个用到内联函数的翻译单元中,都必须重复定义这个内联函数。编译器必须时刻准备好该函数定义,以便在调用它时及时插入内联代码。因此,经常在头文件中定义内联函数。
如果某个翻译单元内的某个函数的所有声明都具有 inline 修饰符,而没有 extern 修饰符,那么该函数具有内联定义(inline definition)。
内联定义只针对翻译单元,它不构成外部定义,因此别的翻译单元可以包含该函数的外部定义。如果有外部定义附加到内联定义中,那么编译器可以*选择要使用哪一种定义。
如果使用存储类修饰符 extern 来声明一个采用 inline 定义的函数,那么该函数的定义就会是外部的(external)。例如,下面的声明与 swapf()的定义如果放在例 1 的同一个翻译单元中,那么 swapf()就具有 extern 定义:
extern void swapf( float *p1, float *p2 );
一旦函数 swapf()具有外部的定义,其他翻译单元只需要采用普通的函数声明,就可以调用它。然而,从别的翻译单元调用函数,将不会被编译成内联函数。
内联函数其实就是普通函数,只不过它们在调用时采用机器码形式。和普通函数一样,内联函数具有自己的地址。如果内联函数使用到宏,预处理器就会展开宏,展开时所用的宏值,取该内联函数在源代码中定义所在位置的宏值。然而,在没被声明为 static 的内联函数中,不应该以静态存储周期的方式来定义可修改的对象。
数组作为函数参数
当把数组名作为函数实参时,它会自动被转换为指针。当使用数组来声明函数参数时,方括号 [] 内的任何常量表达式都会被忽略。在函数块内,数组参数名是一个指针变量,并且数组的值可以在该函数内被修改。
值传递
这种方式使用变量、常量、数组元素作为函数参数,实际是将实参的值复制到形参相应的存储单元中,即形参和实参分别占用不同的存储单元,这种传递方式称为“参数的值传递”或者“函数的传值调用”。
值传递的特点是单向传递,即主调函数调用时给形参分配存储单元,把实参的值传递给形参,在调用结束后,形参的存储单元被释放,而形参值的任何变化都不会影响到实参的值,实参的存储单元仍保留并维持数值不变。
地址传递
这种方式使用数组名或者指针作为函数参数,传递的是该数组的首地址或指针的值,而形参接收到的是地址,即指向实参的存储单元,形参和实参占用相同的存储单元,这种传递方式称为“参数的地址传递”。
地址传递的特点是形参并不存在存储空间,编译系统不为形参数组分配内存。数组名或指针就是一组连续空间的首地址。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该首地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。
可变参数函数
#include<stdio.h> #include<stdarg.h> int simple(int num,...) { int i, result=0; va_list vl; //va_list指针,用于va_start取可变参数,为char* va_start(vl,num); //取得可变参数列表中的第一个值 printf("num:%d, vl:%d\n",num,*vl); for (i = 0; i < (num-1); i++) //这里num表示可变参数列表中有多少个参数 { //这里把vl往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下 一个参数) result = va_arg(vl, int); //这里打印下,可以看出,vl总是指向result后面的那个参数 printf("in for result:%d, *vl:%d\n", result, *vl); } va_end(vl); //结束标志 return result; } int main(int argc, char **argv) { int num = argc; int i = 0; simple(5,1,2,3,4,5); return 1; }
[root@localhost tmp]# ./a.out num:5, vl:1 in for result:1, *vl:2 in for result:2, *vl:3 in for result:3, *vl:4 in for result:4, *vl:5
当编写支持参数数量可变的函数时,必须用 va_list 类型定义参数指针,以获取可选参数。在下面的讨论中,va_list 对象被命名为 argptr。可以用 4 个宏来处理该参数指针,这些宏都定义在头文件 stdarg.h 中:
1. void va_start(va_list argptr, lastparam);
宏 va_start 使用第一个可选参数的位置来初始化 argptr 参数指针。该宏的第二个参数必须是该函数最后一个有名称参数的名称。必须先调用该宏,才可以开始使用可选参数。
2. type va_arg(va_list argptr, type);
展开宏 va_arg 会得到当前 argptr 所引用的可选参数,也会将 argptr 移动到列表中的下一个参数。宏 va_arg 的第二个参数是刚刚被读入的参数的类型。
3. void va_end(va_list argptr);
当不再需要使用参数指针时,必须调用宏 va_end。如果想使用宏 va_start 或者宏 va_copy 来重新初始化一个之前用过的参数指针,也必须先调用宏 va_end。
4. void va_copy(va_list dest, va_list src);
宏 va_copy 使用当前的 src 值来初始化参数指针 dest。然后就可以使用 dest 中的备份获取可选参数列表,从 src 所引用的位置开始。
函数调用
//一个文件中包含两个函数 #include<stdio.h> void butler(void); //第一次出现 int main(void) { printf("I will summon the butler function.\n"); butler(); //第二次出现 printf("Yes.Bring me some tea and writeable DVDs.\n"); return 0; } void butler(void) //第三次出现 { printf("You rang,sir?\n"); }
butler()函数在程序中出现了3次
1. void butler(void);
第一次是函数原型,告知编译器在程序中要使用该函数,也叫作函数声明
2. butler();
第二次以函数调用的形式出现在main()中
3. void butler(void)
第三次是函数定义,函数定义即是函数本身的源代码
return是如何将值返回给主调函数的
被调函数运行结束后才会返回主调函数,但是被调函数运行结束后系统为被调函数中的局部变量分配的内存空间就会被释放。也就是说,return 返回的那个值在被调函数运行一结束就被释放掉了,那么它是怎么返回给主调函数的呢?
事实上在执行 return 语句时系统是在内部自动创建了一个临时变量,然后将 return 要返回的那个值赋给这个临时变量。所以当被调函数运行结束后 return 后面的返回值真的就被释放掉了,最后是通过这个临时变量将值返回给主调函数的。而且定义函数时指定的返回值类型实际上指定的就是这个临时变量的类型。这些都是系统自动完成的,了解即可。
这也是为什么当 return 语句中表达式的类型和函数返回值类型不一致时,将 return 的类型转换成函数返回值类型的原因。return 语句实际上就是将其后的值赋给临时变量,所以它要以临时变量的类型为准,即函数返回值的类型。