函数定义:
类型 函数名 (参数列表)
{
代码块
}
类型: 是指返回值类型
Return语句: 注意return语句将不再往下执行,将退出整个函数。
如果里面没有返回语句也是可以的。
函数声明:
为什么要声明? 我们直到变量声明,是为了分配好地址然后本质上都是对地址的操作。
函数声明其实也是,当程序执行某个分支函数时,需要跳转到函数入口地址处。
函数声明还有一个作用: 函数一般带有参数列表,列表里面规定了函数的使用条件,为了避免你胡乱写一通造成跑飞,编译器要求你提前声明,
声明返回值类型、声明参数列表,这样编译器就能彻底的检查你调用这个函数是否规范。避免后续debug问题。
函数声明方法:
(1) 函数定义出现在 调用前。 这时直接使用即可。适用于简单的代码。
(2) 使用函数原型。 ------ 类型 函数名 (参数列表);
在大型工程中,子函数十分繁杂,一般都将子函数写入”xxxx.h”文件中,然后调用即可。避免纠缠与函数声明与否的问题上。
参数列表:
对参数的调用,是按照”传值调用”,意思是将参数列表的内容copy一个副本,函数调用所操作的对象均是这个副本。
但是按照这个方法:如果参数列表是一个指针变量,副本依旧是copy值,但间接访问副本,最终访问的却是真正的内容。仅仅是指针值不变。
故参数列表里面有 指针、数组名 这类代表指针类型的时候,需要特别注意。
当函数声明、函数定义均好了。下面就是函数调用了。
函数调用支持普通的调用这没得说,支持递归调用吗?
递归调用: 支持,有时候直接看注释,看递归完成什么功能,不必纠缠于实现。因为它是螺旋上升式的,比较饶人。 所以不纠缠于实现、只关心功能。
可变参数列表
比如想求 average(length,va1,va2…..) 想做成任意整数的求平均,函数定义该怎么写?
考虑到这种需求,C标准提供了一个stdarg.h的库。用于专门处理这种 可变参数列表。
这个库函数提供了一下: va_list 、va_start() 、va_arg、va_end.
typedef char *va_list;
#define va_start(ap,v) ap = (va_list)&v + sizeof(v)
#define va_arg(ap,t) (((t *)ap)++[0])
#define va_end(ap)
详细描述: va_list: 声明一个char指针
va_start(ap,v) 其中v是参数列表 “…”前的最后一个参数名 。 等价于: ap = (va_list)&v + sizeof(v)
先求出sizeof(v), 同时取v的地址,加起来就代表指针指向v下一个。做为ap的值。
va_arg(ap,t) : 其中t是 数据类型; 即把ap当成一个 t类型的指针, 取出该值,并 t类型指针+1 = ap+ 1* sizeof(t) 、
能够正好的卡好位置。
va_end(ap) :释放。
//////////////////////////////////////////////////////////////////////////////////
有一下事例可供参考:
Int average (int length ,…) {
Int sum =0;
Int I;
Va_list va;
Va_start(ap, length);
For( I =0;i<length;i++) {
Sum = sum + va_arg(ap,int);
Va_end(ap);
Return (sum/length);
}
}
调用的时候:
A = average(3 ,10,11,12);
B= average(4 ,10,11,12,15);
------------------------------------------------------------------------------------------------------------------------------------
这个 标准库函数,用于可变参数列表。
比如printf(“the print value is %c, %d, %d”,a,b,c); printf(“the print value is %d, %c,”,e,f);
显然这些参数列表里面的内容 是变动的,参数个数是变动的。 在函数原型中该怎么写?
C语言通过指针的形式,可以完成 可变参数列表的读取。 只需要读取可变参数列表首地址,每读一个,指针相应变动,这样就能保证读出正确。
当可变参数列表是固定的一种数据类型时是容易的,比如int型。 但可变参数列表的 数据类型也是变动的话,我们就不好办了。
因此程序员必须知晓,可变参数列表的 数据类型顺序。
Printf( const char * fmt , … ) 这是可变参数函数声明的方法。 其中 const char *fmt是一个指向常数字符串的指针。 … 就是可变参数列表的缩写。
//////////////////////////////////////////////////////////////////////
#include "stdarg.h"
char DbgBuff[128];
int Printf (const char *fmt,...)
{
va_list ap;
int n;
va_start(ap, fmt);
n=vsprintf(DbgBuff, fmt, ap);
va_end(ap);
DbgPutChar(DbgBuff);
return n;
}
//////////////////////////////////////////////////////////////////////
Stdarg.h里面内容是这样写的:
typedef char *va_list;
#define va_start(ap,v) ap = (va_list)&v + sizeof(v)
#define va_arg(ap,t) (((t *)ap)++[0])
#define va_end(ap)
Va_list ap ; 就是声明ap是一个 char * 变量,即指向字符串的指针。
Va_start(ap, fmt) 等价于 ap = (char * ) &fmt +sizeof(fmt)
&fmt获得字符串指针的地址,因为在堆栈中 : …5 …4 …3 …2 …1 …0 + 指针地址。
获得指针地址,并转为char *型是为了 赋值给ap. 加上fmt所占大小,就是…0的地址。 即获得了 可变参数0的地址。
Va_arg(ap,t) 比如va_arg(ap ,int) 等价于 ( ((int *) ap) ++[0] ) = *ap++[0] = *ap +[0] 并ap+1 (此处因为ap被强制变为int,故指针ap+1,实质上 +4处理 )。 即ap指向了 可变变量1的地址。 即获得可变变量0的值并 将指针转移到 …1
完成了整个变量列表的可持续性的访问。
Va_end(ap),其实啥也不干。