免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5572 | 回复: 12
打印 上一主题 下一主题

[C] c语言可变参数的函数编写 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-09-13 12:11 |只看该作者 |倒序浏览
link:http://blog.chinaunix.net/space. ... blog&id=2808215
TABLE
1.介绍
2.使用
  2.1 代码实例,简单体会。
  2.2三个主要的宏
  2.3可变参数的个数确定
  2.4重扫描
  2.5 更多的代码实例
3.注意事项/限制
4.参考资源


1.介绍
        c语言有一个强大的功能,就是它允许定义可接受可变参数列表的函数。如:
#include <stdio.h>
int printf(const char * fmt, ...);
        "..."代表多个参数,可变。

        当定义带有可变参数的函数时,需要的头文件是:<stdarg.h>. 其内定义了三个宏va_start,va_arg,va_end,和一个数据类型va_list来实现我们的需求。

2.使用

2.1 代码实例,简单体会。

  1. filename: sum_n_nums.c
  2. #include <stdio.h>  
  3. /*
  4. *功能:求n个整数的加和,并返回
  5. *输入:
  6. *       int n,欲相加的数的个数
  7. *       ... 可变参数
  8. * 返回:
  9. *       double, 加和的结果
  10. */  
  11.   
  12. double sum_n_nums(int n, ...)  
  13. {  
  14.     double  isum;  
  15.     int     i;  
  16.     va_list ap;//va_list 类型  
  17.   
  18.     va_start(ap, n); //初始化ap,定位  
  19.     for (i = 0, isum = 0; i < n; i++) {  
  20.         isum += va_arg(ap, int); //一次取得可变参数  
  21.     }  
  22.     va_end(ap);//清理  
  23.   
  24.     return isum;  
  25. }  
  26.   
  27. int  
  28. main()//测试用例  
  29. {  
  30.     printf("%.2f\n" ,sum_n_nums(10 ,123 ,123 ,5434 ,123 ,  
  31.        123 ,5434 ,123 ,123 ,5434 ,23));  
  32.     return 0;  
  33. }#include <stdio.h>  
  34. /*
  35. *功能:求n个整数的加和,并返回
  36. *输入:
  37. *       int n,欲相加的数的个数
  38. *       ... 可变参数
  39. * 返回:
  40. *       double, 加和的结果
  41. */  
  42.   
  43. double sum_n_nums(int n, ...)  
  44. {  
  45.     double  isum;  
  46.     int     i;  
  47.     va_list ap;//va_list 类型  
  48.   
  49.     va_start(ap, n); //初始化ap,定位  
  50.     for (i = 0, isum = 0; i < n; i++) {  
  51.         isum += va_arg(ap, int); //一次取得可变参数  
  52.     }  
  53.     va_end(ap);//清理  
  54.   
  55.     return isum;  
  56. }  
  57.   
  58. int  
  59. main()//测试用例  
  60. {  
  61.     printf("%.2f\n" ,sum_n_nums(10 ,123 ,123 ,5434 ,123 ,  
  62.        123 ,5434 ,123 ,123 ,5434 ,23));  
  63.     return 0;  
  64. }
复制代码
2.2三个主要的宏

va_list
        它是一个适合保存va_start, va_arg, va_end所需信息的类型。如要访问不同的参数,那么调用函数要声明一个va_list类型的数据对象。(常用ap作为其名字)。
va_start宏
        概述
        #include <stdarg.h>
         void va_start(va_list ap, parmN);

        无返回值
        说明
        在访问所有为命名的参数之前要调用宏va_start.它对ap进行初始化,也就是对可变参数进行定位。参数parmN是函数定义(在...之前的那个)可变参数表中最右边的固定参数的标识符。例如:
        int printf(const char *fmt, ...);
        fmt 就是parmN.所以一个函数必须至少声明一个固定的参数。      
        注意:
        如果parmN用register存储类别, 一个函数或者数组类型,或者和应用默认的参数提升的类型不兼容的参数累声明,那么这种行为未定以。
va_arg宏
        概述
        #include <stdarg.h>
        type va_arg(va_list ap, type)
        返回值
            第一次调用va_start后,对va_arg的第一次调用返回的是parmN制定的参数后面的参数值,后面的调用一次返回剩下的参数值。
        说明
        宏va_list展开为一个表达式,这个表达式的类型和值跟调用的下一个参数相同。va_arg 的每一次调用都会修改ap, 这样后面的参数值就会依次返回。
        注意
        如果实际不存在下一个参数,或者类型和实际存在的下一个参数类型不兼容,那么这种行为未定以。
va_end宏
        概述
        #include <stdarg.h>
        void va_end(va_list ap);
       无返回值
        说明
        促进函数的正常返回,进行整理工作。可能会对ap进行修改, 这样ap就不在有用了。
        注意
        如果没用执行相应的va_start,或者没又在返回之前调用va_end, 那么这种行为未定以。

2.3可变参数的个数确定
        因为参数个数是可变的,所以函数执行的时候确定传进来的参数个数是很重要的。va_arg宏处理不存在的参数行为为定义,我们不能用while( va_arg(ap, char *) != NULL) 类似的形式来确定结束条件。一下提出两类方法:

        1.用最后一个固定参数对可变参数的个数进行确定。

        例1

        int printf(const char *fmt, ...);

        给定了fmt,那么后面可变参数的个数就确定了。如fmt = “a is %d, b is %f, and c is %s \n", 通过控制格式,我们就可以确定需要传递,三个可变参数。


        例2
        int sum_n_nums(int n, ...);//传n个数,然后返回n个数的和。

        可变参数的个数通过n来确定。


        2.将可变参数的最后一个参数作为结束条件
        例
#include <unistd.h>
        int execl(const char *pathname, const char *arg0, ...);
        在使用这个函数的时候要求传递可变参数的最后一个参数需要是 NULL,也就是(char *) 0.这样我们就可以用 while (va_arg(ap, char *) != NULL)的形式来确定结束条件。
        推荐的方式是,在每一次循环扫描内都执行一次va_start,va_end.推荐的方式是,在每一次循环扫描内都执行一次va_start,va_end.

2.4重扫描

        推荐的方式是,在每一次循环扫描内都执行一次va_start,va_end.
2.5 更多的代码实例

  1. filename: minprintf.c
  2.     #include <stdio.h>  
  3.     #include <stdarg.h>  
  4.     /*
  5.      * 功能: printf函数的简体版
  6.      */  
  7.       
  8.       
  9.      void minprintf(char *fmt, ...)  
  10.     {  
  11.         va_list ap;  
  12.         char *p, *sval;  
  13.         int ival;  
  14.         double dval;  
  15.       
  16.         va_start(ap, fmt);  
  17.         for (p = fmt; *p; p++) { //通过fmt来控制可变参数(第一种方法)。  
  18.             if (*p != '%') {  
  19.                 putchar(*p);  
  20.                 continue;  
  21.             }  
  22.             switch (*++p) {  
  23.                 case 'd':  
  24.                     ival = va_arg(ap, int);  
  25.                     printf("%d", ival);  
  26.                     break;  
  27.                 case 'f':  
  28.                     dval = va_arg(ap, double);  
  29.                     printf("%f", ival);  
  30.                     break;  
  31.                 case 's':  
  32.                     for (sval = va_arg(ap, char *); *sval; sval++)  
  33.                         putchar(*sval);  
  34.                     break;  
  35.                 default:  
  36.                     putchar(*p);  
  37.                     break;  
  38.             }  
  39.         }  
  40.         va_end(ap);  
  41.     }  
  42.     //无测试用例  
复制代码

  1. filename: cat_strs.c
  2.     #include <stdio.h>  
  3.     #include <stdlib.h>  
  4.     #include <string.h>  
  5.     #include <stdarg.h>  
  6.       
  7.     /*
  8.      *功能:和并多个字符串,然后返回。
  9.      *输入:
  10.      *    char *first: 合并的第一个字符串
  11.      *    ...  后继的多个字符串,要以(char *) 0结束
  12.      *返回:
  13.      *     char *  合并后字符串指针
  14.      *注意:
  15.      *    合并后的字符串保存在静态空间;
  16.      *    如果要合并的字符串总长度 大于 静态太分配的 CAT_BUF_LEN 那么函数出错,返回。
  17.      *    传递的可变参数一定以NULL结束
  18.      */  
  19.       
  20.      char *cat_strs(char *first, ...)  
  21.     {  
  22.     #ifndef CAT_BUF_LEN  
  23.     #define CAT_BUF_LEN 1024  
  24.     #endif  
  25.         static char result[CAT_BUF_LEN];  
  26.         va_list ap; //va_list 类型  
  27.         char *pr;  
  28.         int len_sum;  
  29.         char *ptmp;  
  30.       
  31.         memset(result, '\0', sizeof (result));//初始化静态空间  
  32.          
  33.         va_start(ap, first);//初始化ap  
  34.         // 判断合并的字符串总长度是否大于CAT_BUF_LEN  
  35.         while ((ptmp = va_arg(ap, char *)) != NULL)  //第二种方法控制结束条件  
  36.             len_sum += strlen(ptmp);  
  37.         len_sum += strlen(first);  
  38.         if (len_sum > CAT_BUF_LEN -1) {  
  39.             fprintf(stderr, "cat_strs: the CAT_BUF_LEN is not enought!\n");  
  40.             va_end(ap);  
  41.             exit(EXIT_FAILURE);  
  42.         }  
  43.         va_end(ap);  
  44.       
  45.         va_start(ap, first); //重扫描!  
  46.         pr = result;  
  47.         //合并字符串  
  48.         strcat(pr, first);  
  49.         while (ptmp = va_arg(ap, char *)) {  
  50.             strcat(pr, ptmp);  
  51.         }  
  52.         va_end(ap);  
  53.          
  54.         return result;  
  55.     }  
  56.       
  57.     int  
  58.     main(void) //测试用例  
  59.     {  
  60.         char *result;  
  61.       
  62.         result = cat_strs("start ", "sajfklefjwle" ,"wejflsjfdghwejl",   
  63.                "wejflsjfdghwejl", " end\n", NULL);//一定要以空指针结束  
  64.         fputs(result, stdout);  
  65.         exit(0);  
  66.     }  
复制代码
3.注意事项/限制

        1. 声明函数时必须至少有一个固定的参数,最后一个固定参数引用时候习惯上称为parmN。

        2. 必须在函数内执行va_start(ap, parmN),之后才能执行va_arg,或va_end。

        3 .对于va_arg,不能编写类型T,使得当它作为一个参数传递时范围会变大。例如,不能用double代替float,int代替char等。

        可以编写的类型T,仅仅通过在其后面加一个*就可以转换为相应的指针类型。

        4 .前面执行了va_start,那么必须后面执行va_end.一旦执行了va_end,那么不能再执行va_arg。除非再次执行va_start, 进行重扫描。

4.参考资源

        《c标准库》,P.J.Plauger 著,卢红星 徐明亮 霍建同 译, 2009年第1版

        《c程序设计语言 第2版》,Brian W.Kernighan/ Dennis M.Ritchie 著, 徐宝文 李志 译, 2009年第2版

《unix 环境高级编程 第2版》,W。Richard Stevens Stephen A。Rago 著, 尤晋元 张亚英 戚正伟 译, 2006年第1版

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
2 [报告]
发表于 2011-09-13 12:36 |只看该作者
《unix 环境高级编程 第2版》,W。Richard Stevens Stephen A。Rago 著, 尤晋元 张亚英 戚正伟 译, 2006年第1版

给可变参数参考什么了????

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-06-17 22:20:00每日论坛发贴之星
日期:2015-06-17 22:20:00
3 [报告]
发表于 2011-09-13 13:38 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
95
程序设计版块每日发帖之星
日期:2015-09-05 06:20:00程序设计版块每日发帖之星
日期:2015-09-17 06:20:00程序设计版块每日发帖之星
日期:2015-09-18 06:20:002015亚冠之阿尔艾因
日期:2015-09-18 10:35:08月度论坛发贴之星
日期:2015-09-30 22:25:002015亚冠之阿尔沙巴布
日期:2015-10-03 08:57:39程序设计版块每日发帖之星
日期:2015-10-05 06:20:00每日论坛发贴之星
日期:2015-10-05 06:20:002015年亚冠纪念徽章
日期:2015-10-06 10:06:482015亚冠之塔什干棉农
日期:2015-10-19 19:43:35程序设计版块每日发帖之星
日期:2015-10-21 06:20:00每日论坛发贴之星
日期:2015-09-14 06:20:00
4 [报告]
发表于 2011-09-13 14:09 |只看该作者
《unix 环境高级编程 第2版》,W。Richard Stevens Stephen A。Rago 著, 尤晋元 张亚英 戚正伟 译, 2006年 ...
zylthinking 发表于 2011-09-13 12:36



    APUE 里面有一些例子函数用到了这个。

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
5 [报告]
发表于 2011-09-13 14:14 |只看该作者
APUE 里面有一些例子函数用到了这个。
MMMIX 发表于 2011-09-13 14:09



喔, 没注意到。。。。。。。

论坛徽章:
0
6 [报告]
发表于 2011-09-13 17:33 |只看该作者
求n个整数的加和
double sum_n_nums(int n, ...)
返回值却是double
pmerofc 发表于 2011-09-13 13:38


返回double有什么不当?

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
7 [报告]
发表于 2011-09-13 17:35 |只看该作者
返回double有什么不当?
j3kljs02398j 发表于 2011-09-13 17:33


据说c标准没有规定

论坛徽章:
2
程序设计版块每日发帖之星
日期:2015-06-17 22:20:00每日论坛发贴之星
日期:2015-06-17 22:20:00
8 [报告]
发表于 2011-09-13 17:36 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
0
9 [报告]
发表于 2011-09-13 17:39 |只看该作者
2.将可变参数的最后一个参数作为结束条件
        例
#include <unistd.h>
        int execl(const char *pathname, const char *arg0, ...);
回复 2# zylthinking


execl函数取自该书定义,所以参考文档就加上了。

论坛徽章:
0
10 [报告]
发表于 2011-09-13 17:44 |只看该作者
回复 8# pmerofc


    返回double是怕溢出。
该函数是求几个整数的加和,如果其中的某些数比较大,或者相加的数特别多,都容易溢出。
对于一些数学运算返回值一般都是double。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP