<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->
Printf 函数是一组函数的总称,包含:
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
注:这组函数使用可变函数列表作为参数。
还包括间接使用 printf 的函数,如 wxWidget 中的 wxLogXXX 等。
Printf 函数的 format 参数 使用 % 作为特殊字符用以定义所要打印的串的特殊格式; printf 函数可以定义以下打印格式:
1) 标识字符 (flag characters): #, 0, -, ‘ ‘, +
0 : 表示在有位数要求时,使用 0 作为填充字符,而不是空白字符 ( 默认 ) ;
- : 表示在有对齐要求时,使用左对齐,而非右对齐 ( 默认 )
‘ ‘ : 强制在正数后空串前留一位空字符。
+ : 显示正负数的数学符号 (+/-)
2) 位数和精度 (field width and precision)
Printf(“%12.4f”, num);
Printf(“%12”, num);
3) 长度修饰语 (length modifier) : h, hh, l, ll, z, t ( 仅用于整型数的转换 )
其中, t 用来将整型数打印成内存地址。
4) 转换分类符 (conversion specifier) : d, i; o, u, x, X; e, E, f, F, g, G; c, s; p;
其中, %p 用于以 16 进制打印 void* 的指针地址;
问题:如何使用 printf 打印 %?
% 是 printf 函数的 format 参数特殊字符,如果想在 format 中识别 % ,需使用 %% 才行;或者使用 %s 格式打印带有 % 的字符串,因为使用 %s 将打印相应参数中的所有字符。
下面是一个综合例子:
#include <cstdio>
#include <stdarg.h>
const int MAX_LEN = 1024;
int vspf(char *fmt , ...)
{
char msg[MAX_LEN] = {0};
va_list argptr;
va_start(argptr, fmt ); //get the address of va, fmt must be the last parameter before va list.
int cnt = vsnprintf(msg, MAX_LEN, fmt, argptr);
va_end(argptr);
printf("%s", msg);
return(cnt);
}
int main()
{
// printf("test % string: %s , %s /n", "test", "abc"); // dangrous
printf("test %% string: %s , %s /n", "test", "a%%b%%c");
printf("test string: %s , %s /n", "t%est", "a%b%c");
int width = -10;
int num = 111;
double high = 12.34;
printf("test: %*d %1$d/n", width, num); // one may write ‘*’ or ‘*m$’ (for some decimal integer m) to print the next argument, or in the m-th argument.
printf("====================/n");
printf("test: %2$ *1$d/n", width, num);
printf("test: %012d %+12d/n", width, num);
printf("test: %12d %12.4f/n", width, high );
printf("test: %-d %-d/n", width, num);
printf("test: %+d %+d/n", width, num);
vspf("test: %s, %s /n", "abc","xyz");
vspf("test: %s, %s , %d/n", "a%b%c","xyz", num);
}
实际输出 (Linux) :
test % string: test , a%%b%%c
test string: t%est , a%b%c
test: 111 -10
====================
test: 111
test: -00000000010 +111
test: -10 12.3400
test: -10 111
test: -10 +111
test: abc, xyz
test: a%b%c, xyz , 111
例子中用到了很多 printf 的例子,并使用了 vsnprintf ,由于该函数和 va_list 直接相关,所以下说说 va_list 。
函数参数的传递原理:函数参数是以数据结构 : 栈的形式存取 (C 语言在函数调用时,先将最后一个参数压入栈 ) 。在 stdcall 下,堆栈中 , 各个参数的分布情况是倒序的 . 即最后一个参数在列表中地址最高部分 , 第一个参数在列表地址的最低部分 . 参数在堆栈中的分布情况如下 :
最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数代码段
获取省略号指定的参数:在函数体中声明一个 va_list ,然后用 va_start 函数来获取参数列表中的参数,使用完毕后调用 va_end() 结束。举个例子 (VS) :
void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)
{
va_list args;
va_start(args, pszFormat); // pszFormat 参数下一个参数的地址 , 即第一个可变参数地址 .
_vsnprintf(pszDest, DestLen, pszFormat, args);
va_end(args);
}
原理: va_start 使 argp 指向第一个可选参数。 va_arg 返回参数列表中的当前参数并使 argp 指向参数列表中的下一个参数。 va_end 把 argp 指针清为 NULL 。函数体内可以多次遍历这些参数,但是都必须以 va_start 开始,并以 va_end 结尾。
va_start, va_arg 和 va_end 仅仅是一些宏 , 用于获取参数堆栈地。
摘自: wchar.h
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedef char * va_list;
#endif
注 :char 型指针的特点是 ++ 、 -- 操作对其作用的结果是增 1 和减 1 (因为 sizeof(char) 为 1 )
摘自 stdarg.h
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
摘自 *vadefs.h - defines helper macros for stdarg.h
/* A guess at the proper definitions for other platforms */( 不同平台定义不同,指针是依赖平台的 )
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) //4 的倍数
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap) ( ap = (va_list)0 )
注: _INTSIZEOF(n) 宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟 sizeof(int) 对齐。一般的 sizeof(int)= 4 ,也就是参数在内存中的地址都为 4 的倍数。比如,如果 sizeof(n) 在 1 - 4 之间,那么 _INTSIZEOF(n) = 4 ;如果 sizeof(n) 在 5 - 8 之间,那么 _INTSIZEOF(n)=8 。具体细节可以参见:数据在机器内的存储与运算章节。
va_arg(ap,t) 得到第一个参数的值 ( 即调用前 ap 的值 ), 并且将 ap 指针上移一个 _INTSIZEOF(t), 即指向下一个可变参数的地址。这个宏分两步: 1) (ap += _INTSIZEOF(t)) 移动指针地址 ; 2) 使用移动后的 ap 值计算原先的参数值 ( 减掉移动值 _INTSIZEOF(t)) 。
如何直接只用可变参数列表:
虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数 , 但是想直接使用可变参数列表,还需要知道可变参数的个数 , 以便结束遍历。解决办法 : a. 参数指定 : 在第一个起始参数中指定参数个数 , 那么就可以在循环还中读取所有的可变参数 ; b. 位哨 : 定义一个结束标记 , 在调用函数的时候 , 在最后一个参数中传递这个标记 , 这样在遍历可变参数的时候 , 可以根据这个标记结束可变参数的遍历。举例:
void arg_cnt(int cnt, ...)
{
int value=0;
int i=0;
int arg_cnt=cnt;
va_list arg_ptr;
va_start(arg_ptr, cnt);
for(i = 0; i < cnt; i++)
{
value = va_arg(arg_ptr,int);
printf("value%d=%d/n", i+1, value);
}
}
注:可变参数列表比较危险,不建议使用 ( 《 C++ 编码规范》 ) 。