引言

在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语句加上了{}的话,就不会有问题,可以看出规范写法是多么的重要

C、C++格式化字符串的更多相关文章

  1. VBA 格式化字符串 - Format大全

    VBA 格式化字符串 VBA 的 Format 函数与工作表函数 TEXT 用法基本相同,但功能更加强大,许多格式只能用于VBA 的 Format 函数,而不能用于工作表函数 TEXT ,以下是本人归 ...

  2. Python中用format函数格式化字符串

    Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存. 1.百分号方式 语法:%[( ...

  3. C#定义类型转化 及 格式化字符串

    operator 关键字 operator 关键字用来重载内置运算符,或提供类/结构声明中的用户定义转换.它可以定义不同类型之间采用何种转化方式和转化的结果. operator用于定义类型转化时可采用 ...

  4. 关于printf错用格式化字符串导致double和long double输出错误的小随笔

    [题外话] 以前用HUSTOJ给学校搭建Online Judge,所有的评测都是在Linux下进行的.后来为了好往学校服务器上部署,所以大家重新做了一套Online Judge,Web和Judge都是 ...

  5. .NET中DateTime.Now.ToString的格式化字符串

    .NET中DateTime.Now.ToString显示毫秒:DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") DateTime.N ...

  6. Python格式化字符串~转

    Python格式化字符串 在编写程序的过程中,经常需要进行格式化输出,每次用每次查.干脆就在这里整理一下,以便索引. 格式化操作符(%) "%"是Python风格的字符串格式化操作 ...

  7. %----format 格式化字符串---- 生成器---- 迭代器

    %方式格式化字符串 顺序传参数 o转换8进制x转换十六进制 tp1 = "i am %s" % "alex"tp2 = "i am %s age %d ...

  8. 飘逸的python - 增强的格式化字符串format函数

    自python2.6开始,新增了一种格式化字符串的函数str.format(),可谓威力十足.那么,他跟之前的%型格式化字符串相比,有什么优越的存在呢?让我们来揭开它羞答答的面纱. 语法 它通过{}和 ...

  9. Python格式化字符串和转义字符

    地址:http://blog.chinaunix.net/uid-20794157-id-3038417.html Python格式化字符串的替代符以及含义     符   号     说     明 ...

  10. [转]:C#的ToString如何格式化字符串

    C 货币 2.5.ToString("C") ¥2.50 D 十进制数 25.ToString("D5") 00025 E 科学型 25000.ToString ...

随机推荐

  1. SpringBoot中读取配置文件的几种方式

    1.读取application文件 在application.yml或者properties文件中添加: info: name: xiaoming age: 13 sex: 1 读取方式如下: imp ...

  2. 【C++】string::substr函数

    形式:s.substr(p, n) 返回一个string,包含字符串s中从p开始的n个字符的拷贝(p的默认值是0,n的默认值是s.size() - p,即不加参数会默认拷贝整个s) int main( ...

  3. 机器学习中的误差 Where does error come from?

    误差来自于偏差和方差(bias and variance)   对于随机变量 X,假设其期望和方差分别为 μ 和 σ2.随机采样 N 个随机变量构成样本,计算算术平均值 m,并不会直接得到 μ (除非 ...

  4. Eclipse+CXF框架开发Web服务实战

    一. 说明 采用CXF框架开发webservice. 所用软件及版本如下.  操作系统:Window XP SP3.  JDK:JDK1.6.0_07,http://www.oracle.com/ ...

  5. dns自动配置shell脚本

    代码: #!/bin/bash #获取url echo "url:" read url #获取ip echo "ip:" read ip #向/etc/name ...

  6. Vector使用方法简单整理

    使用vector,需要引用vector库: #include<vector> 首先,创建一个可以容纳int的vector变量——arr: vector<int> arr; 接着 ...

  7. 100天搞定机器学习|day37 无公式理解反向传播算法之精髓

     100天搞定机器学习(Day1-34) 100天搞定机器学习|Day35 深度学习之神经网络的结构 100天搞定机器学习|Day36 深度学习之梯度下降算法 本篇为100天搞定机器学习之第37天,亦 ...

  8. OpenGL入门第一天:环境

    本文是个人学习记录,学习建议看教程 https://learnopengl-cn.github.io/ 非常感谢原作者JoeyDeVries和各位翻译提供的优质教程 近况(牢骚 这几天教母校初中的OI ...

  9. python+爬虫+微信机器人 打造属于你的网购价格监督利器

    写在最前 程序是为人类服务的,最近正好身边小伙伴们在做球衣生意,当然是去nikenba专区购买了,可是有些热门球衣发布几分钟就被抢完,有些折扣球衣也是很快就被抢售一空,那么我们只能靠自己的眼睛一直盯着 ...

  10. Log2Net的部署和运维

    前面几节中,笔者介绍了Log2Net组件的方方面面.但是,有人说,我用不起来,各种头疼的小问题.下面,我们就具体的看一看要怎么把这个组件应用到实际的项目中吧. 一.Log2Net组件的发布 前文中,我 ...