zoukankan      html  css  js  c++  java
  • 简答的理解C语言中的各种类型函数

    1.变参函数

    变长参数的函数即参数个数可变、参数类型不定 的函数。最常见的例子是printf函数、scanf函数和高级语言的Format函数。在C/C++中,为了通知编译器函数的参数个数和类型可变(即是不定的、未知的),就必须以三个点结束该函数的声明。

    1 // printf函数的声明  
    2 int printf(const char * _Format, ...);      //const char * _Format是格式控制,控制有多少个%d...,确定输出的个数与类型
    3 4 //scanf函数声明  5 int scanf(const char * _Format, ...); 6 7 //自定义变长参数函数func的声明  8 int func(int a,int b,...);

    注意:上面func 函数的声明指出该函数至少有两个整型参数和紧随其后的0个或多个类型未知的参数。在C/C++中,任何使用变长参数声明的函数都必须至少有一个指定的参数(又称强制参数),即至少有一个参数的类型是已知的,而不能用三个点省略所有参数的指定,且已知的指定参数必须声明在函数最左端。在c++类似的应用中称之为重载函数。

    1 //下面这种声明是非法的  
    2 int func(...);    //错误 3 int func(...,int a);    //错误

    ----------------------------------------------------------------------

    变参函数的实现

    我们知道函数调用过程中参数传递是通过栈来实现的,一般调用都是从右至左的顺序压参数入栈。例如int func(int a,int b,float c),则float类型的c在最下面,然后到int类型的 b ,int类型的a在最上面!

      因此参数与参数之间是相邻的,知道前一个参数的类型及地址,根据后一个参数的类型就可以获取后一个参数的内容。对于变长参数函数,结合一定的条件,我们可以根据最后一个指定参数获取之后的省略参数内容。如,对于函数func,我们知道了参数b的地址及类型,就可知道第一个可变参数的栈地址(如果有的话),如果知道第一个可变参数的类型,就可知道第一个可变参数的内容和第二个可变参数的地址(如果有的话)。以此类推,可以实现对可变参数函数的所有参数的访问。

           那么,要怎么指定上诉的“一定的条件”呢?最简单的方法就像printf等函数一样,使用格式化占位符。分析格式化字符串参数,通过事先定义好的格式化占位符可知可变参数的类型及个数,从而获取各个参数内容。一般对于可变参数类型相同的函数也可直接在强制参数中指定可变参数的个数和类型,这样也能获取各个参数的内容。

           无论哪种,都涉及对栈地址偏移的操作。结合栈存储模式和系统数据类型的字长,我们可根据可变参数的类型很容易得到栈地址的偏移量。这里简单介绍使用va_start、va_arg、va_end三个标准宏来实现栈地址的偏移及获取可变参数内容。这三个宏定义在stdarg.h头文件中,他们可根据预先定义的系统平台自动获取相应平台上各个数据类型的偏移量。

    //访问可变参数流程  
      
    va_list args; //定义一个可变参数列表  
    
    va_start(args,arg);//初始化args指向强制参数arg的下一个参数;  
    
    va_arg(args,type);//获取当前参数内容并将args指向下一个参数  
      
    ...//循环获取所有可变参数内容  
      
    va_end(args);//释放args  

    下面实现一个简单的例子:

     1 //sum为求和函数,其参数类型都为int,但参数个数不定  
     2 //第一个参数(强制参数)n指定后面有多少可变参数  
     3 int sum(unsigned int n,...)  
     4 {  
     5    int sum=0;  
     6    va_list args;  
     7    va_start(args,n);  
     8    while(n>0)  
     9    {  
    10     //通过va_arg(args,int)依次获取参数的值  
    11      sum+=va_arg(args,int);  
    12      n--;  
    13    }  
    14    va_end(args);  
    15    return sum;  
    16 }  

    注意:对于可变参数函数的调用有一点需要注意,实际的可变参数的个数必须不小于前面强制参数中指定的个数要多,即后续参数多一点不要紧,但不能少,如果少了则会访问到函数参数以外的堆栈区域,这可能会把程序搞崩掉。前面强制参数中指定的类型和后面实际参数的类型不匹配也有可能造成程序崩溃。 

    参考的是 http://blog.csdn.net/tht2009/article/details/7019635#

    2.内部函数和外部函数

    当一个源程序由多个源文件组成时,根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。

    2.1内部函数

      如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。定义这种函数,在函数类型之前添加“static”关键字即可。

      使用内部函数的好处:不用的人编写不用的函数时,不用担心自己定义的函数是否与其他文件中的函数同名,因为同名也没关系。

    2.2外部函数

      在定义函数时没有加关键字,如“static”或者“extern”等等,表明此函数就是外部函数,也就是我们平时用的最多的函数类型。

    参考 http://www.cnblogs.com/JessonChan/archive/2010/12/12/1903983.html

    3.inline(内联)函数

    在函数声明之前添加关键字"inline"的函数,就是内联函数。

      函数调用需要时间和空间开销,调用函数实际上将程序执行流程转移到被调函数中,被调函数的代码执行完后,再返回到调用的地方。这种调用操作要求调用前保护好现场并记忆执行的地址,返回后恢复现场,并按原来保存的地址继续执行。对于较长的函数这种开销可以忽略不计,但对于一些函数体代码很短,又被频繁调用的函数,就不能忽视这种开销。引入内联函数正是为了解决这个问题,提高程序的运行效率。

      在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。由于在编译时将内联函数体中的代码替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间开销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。

    inline函数是提高运行时间效率,但却增加了空间开销。

    即inline函数目是:为了提高函数的执行效率(速度)。
    非内联函数调用有栈内存创建和释放的开销
    在C中可以用宏代码提高执行效率,宏代码不是函数但使用起来像函数,编译器用复制宏代码的方式取代函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程,从而提高速度。在多语句宏定义时候,运用inline函数代替宏定义。

      使用内联函数时应注意以下几个问题:
    (1) 在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件中共享。
    (2) 内联函数应该简洁,只有几个语句,如果语句较多,不适合于定义为内联函数。 
    (3) 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。
    (4) 内联函数要在函数被调用之前声明。

    4.回调(递归)函数

    什么是递归函数?递归函数是在函数体内部直接或间接地自己调用自己的函数。

    1 void int f(int n)
    2 {
    3   if (条件)    //满足条件则退出循环,返回所需的值
    4   {
    5      return ;    
    6    } 
    7    f(n-1);      //否则继续调用本函数。。。
    8 }    

    例子1:

     1 //逆转数组a的元素顺序
     2 void revert(int a[], int len)
     3 {
     4    if(len = 1)
     5    {
     6         return;  
     7     }    
     8 
     9     revert(a+1, len-2);
    10 
    11     int tmp;
    12     tmp = a[0];
    13     a[0] = a[len-1];
    14     a[len-1] = tmp; 
    15 }   

    例子2:汉诺塔程序

    问题描述: A、B、C 三个桌子,其中A桌子上放了几个大小不同的盘子,盘子的排列顺序为: 从上到下,依次从小到大递增;现要求把这些盘子从 A 桌子上移动到 C 桌子上,盘子移动时有一点要求:每次移动必须保证三张桌子上大盘子在下、小盘子在上;打印移动次序。

     1 void recursion_hano(int n,char A, char B, char B,)
     2 {
     3     //停止条件
     4     if(n == 1) 
     5     {    
     6         move(A,C);
     7         return; 
     8     } 
     9      
    10     recursion_hano(n-1,A,C,B);//将 上一个盘子 从A移动到B 
    11     move(A,C);    //将最大的盘子移动到 C  
    12     recursion_hano(n-1,B,A,C);//将B中盘子移到C 
    13 }

    还有一种圆形排列法....

    参考http://blog.csdn.net/leo115/article/details/7991734

  • 相关阅读:
    [LeetCode] 493. Reverse Pairs 翻转对
    [LeetCode] Super Washing Machines 超级洗衣机
    [LeetCode] Perfect Number 完美数字
    [LeetCode] 483. Smallest Good Base 最小的好基数
    [LeetCode] Sliding Window Median 滑动窗口中位数
    [LeetCode] Diameter of Binary Tree 二叉树的直径
    [LeetCode] 01 Matrix 零一矩阵
    [LeetCode] Convert BST to Greater Tree 将二叉搜索树BST转为较大树
    [LeetCode] Reverse String II 翻转字符串之二
    [LeetCode] Minimum Time Difference 最短时间差
  • 原文地址:https://www.cnblogs.com/simonLiang/p/5730059.html
Copyright © 2011-2022 走看看