引言

在C和C++开发中,我们经常会用到printf来进行字符串的格式化,例如printf("format string %d, %d", 1, 2);,这样的格式化只是用于打印调试信息。printf函数实现的是接收可变参数,然后解析格式化的字符串,最后输出到控制台。那么问题来了,当我们需要实现一个函数,根据传入的可变参数来生成格式化的字符串,应该怎么办呢?

你可以在这里看到更好的排版

正文

可变参数

首先来一个可变参数使用示例,testVariadic方法接收int行的可变参数,并以可变参数为-1表示结束。va_list用于遍历可变参数,va_start方法接收两个参数,第一个为va_list,第二个为可变参数前一个参数,下面的例子里该参数为a。

/**
下面是 <stdarg.h> 里面重要的几个宏定义如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); // ANSI version
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
*/
//-1表示可变参数结束
void receiveVariadic(int a, ...) {
va_list list;
va_start(list, a);
int arg = a;
while (arg != -1) {
arg = va_arg(list, int);
printf("%d ", arg);
}
printf("\n");
va_end(list);
} //test
void testVari()
{
printf("------%s------\n", __FUNCTION__);
//-1表示可变参数结束
receiveVariadic(1, 2, 3, 4, 5, 6, -1);
}

运行结果

------testVari------
2 3 4 5 6 -1

格式化字符串

好了,我们已经介绍了怎样实现一个接收可变参数的C函数,接下来介绍根据接收的可变参数来格式化字符串。这里介绍两种方式,第一种是利用宏定义,第二种通过函数的方式来实现。

通过宏定义的方式

en…让咱们先来看看第一个版本的宏,这个宏定义对于不熟悉宏的人来说可能看着有点费劲,不过不要怕,稍后会做解释,代码如下:

#define myFormatStringByMacro_WithoutReturn(format, ...) \
do { \
int size = snprintf(NULL, 0, format, ##__VA_ARGS__);\
size++; \
char *buf = (char *)malloc(size); \
snprintf(buf, size, format, ##__VA_ARGS__); \
printf("%s", buf); \
free(buf); \
} while(0)

宏基础知识

首先需要介绍宏用到的知识:\, 这个\的作用是可换行定义宏,毕竟如果一行很长的宏可读性很差,使用方式在换行时加上\即可。第二个是介绍(format, ...),这里的...是预定义的宏,用于接收可变参数,就像是printf函数一样。接着介绍##__VA_ARGS__,同样的__VA_ARGS__也是预定义的宏,表示接收到的...传入的可变参数。##的作用是用来处理未传入可变参数的情况,当没有传入可变参数的时候,编译器或通过优化将snprintf(NULL, 0, format, ##__VA_ARGS__);优化为snprintf(NULL, 0, format);。你可以理解为没有可变参数时,##前的逗号,__VA_ARGS__都被“干掉了”。

你一定会觉得困惑,为什么要写do-while语句呢?这是为了宏的健壮性,如果使用宏的人像下面这样使用的话,就会出问题

#define testMarco(a, b) \
int _a = a + 1; \
int _b = b + 1; \
printf("\n%d", _a + _b); \ void test()
{
if (1 > 0)
testMarco(1, 2);
}

上面的代码连编译都不会通过, 会报错如下:

如果手动展开这个宏的话,会变成这个样子,问题就显而易见了。但是如果if语句加上了{}的话,就不会有问题,可以看出规范写法是多么的重要

最新文章

  1. NetBeans invalid jdkhome specified 问题解决方法
  2. android初练二
  3. 【ORACLE】MD5加密
  4. Oracle笔记1-数据库概念
  5. Windows自带.NET Framework版本大全
  6. 【XLL API 函数】xlGetName
  7. 前端技巧:禁止浏览器static files缓存篇(转)
  8. Failed to load PDF in chrome/Firefox/IE
  9. always语言指导原则
  10. 转】Nginx+tomcat配置集群负载均衡
  11. C# WinForm 和 javascript进行交互 使用HTML做界面
  12. Swift - String与NSString的区别,以及各自的使用场景
  13. MSXML2;System.ServiceModel.Configuration;对应dll的添加方法
  14. MySQL递归的替代方案
  15. [LeetCode] Find Largest Value in Each Tree Row 找树每行最大的结点值
  16. 【一天一道LeetCode】#292. Nim Game
  17. 【RecyclerView优化】
  18. PHP通过经纬坐标计算两个地址的距离
  19. 剖析一个用C++写的行情交易系统
  20. Ceph分布式存储集群-硬件选择

热门文章

  1. python_0基础学习_day02
  2. Java性能调优的11个实用技巧
  3. 关于http 500错误的小结分享
  4. 图像反转(一些基本的灰度变换函数)基本原理及Python实现
  5. [ubuntu][deepin]系统增加自定义开机启动项
  6. pytest
  7. MySQL InnoDB Cluster介绍
  8. 轻量级移动端类库,大小20多k,支持多指触摸。
  9. (十七)c#Winform自定义控件-基类窗体
  10. 爱奇艺JAVA后台面经