<C和指针---读书笔记7>

 函数定义:

      类型  函数名 (参数列表) 

        {

                 代码块

           }

 

类型: 是指返回值类型

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),其实啥也不干。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/mokang0421/p/7476046.html