Notice: 省略符形参并不是可变模板参数! 同时这是针对非模板的可变参数解决方案!!!!!!!
再C中有一个函数:
int printf(const char* format, ...); 这里的 "..."一直不明白是什么意思,也一直没有当回事今天就来深入了一下.
但是实际对这个函数调用的时候我们可以写成:
printf("%s", "shihuamarryme");
print("%s%d", "shihuamylife", 20);等等形式.
于是翻看了一下C++ primer发现在C++中还可以这样写(其实利用了C++的SFINAE):
function(...);
因此也就是说为了兼容C在C++中普遍有2种写法:
void function(...); //代号: 1
void function(int number, ...); //代号: 2
以上面两种写法为例:
#include <iostream>
void function(...) //代号: 1
{
std::cout << 1 << std::endl;
}
void function(int number, ...) //代号: 2
{
std::cout << 2 << std::endl;
}
//function的匹配规则是: 如果第一个参数是int,或者转为int的类型就优先调用: 1, 其他情况调用: 2.
int main()
{
int n = 10;
function(n);
std::cout << "----------------" << std::endl;
std::string str("shihua");
function(str);
return 0;
}
其中这个省略符还伴随着标准库提供的其他几个组件,这些组件都是以宏(#defined)的形式实现的,如果是在C++中使用需要包含 #include<cstdarg>:
va_list: 这个在vs15中的实现为 typedef char* var_list;
va_start( std::va_list ap, parm_n );
具体实现为:#define va_start(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
demo for va_list
#include <iostream>
#include <cstdarg>
int add_nums(int count, ...)
{
int result = 0;
va_list args; //注意这里: args 其实相当于 char* args;
va_start(args, count); //这里是我们要重点关注的.
//在main()中我们调用了 add_nums(); 注意我们一共传了 5 个参数进来;
//因此 count 的值为 4;
//在调用了var_start(args, count)之后, args此时指向: 参数列表中的第一个25.
for (int i = 0; i < count; ++i) {
result += std::va_arg(args, int);
}
va_end(args);
return result;
}
int main()
{
std::cout << add_nums(4, 25, 25, 50, 50) << '\n';
}
va_copy(va_list dest, va_list src ); 使dest 指向 src指向的内容.
具体实现为: #define va_copy(destination, source) ((destination) = (source))
demo:
#include <iostream>
#include <cstdarg>
#include <cmath>
void sample_stddev(int count, ...)
{
double sum = 0;
va_list args1;
va_start(args1, count);
va_list args2;
va_copy(args2, args1); //注意这里!.
for (int i = 0; i < count; ++i) {
double num = va_arg(args2, double);
sum += num;
}
std::cout << sum << std::endl;
}
int main()
{
sample_stddev(3, 20.1, 20.2, 20.3);
return 0;
}
输出为: 60.6
va_arg( va_list ap, t); 提取 ap 指向的内存位置的下一个位置中的数据作为T类型.
具体实现为: #define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
关于 _INTSIZEOF(t)详见下文.
#include <iostream>
#include <cstdarg>
#include <cmath>
double stddev(int count, ...)
{
double sum = 0;
double sum_sq = 0;
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
double num = va_arg(args, double);
sum += num;
sum_sq += num*num;
}
va_end(args);
return std::sqrt(sum_sq/count - (sum/count)*(sum/count));
}
int main()
{
std::cout << stddev(4, 25.0, 27.3, 26.9, 25.7) << '\n';
}
va_end( va_list ap ); 不 ap 清理内存中的数据,需要注意的是运行了该函数 ap 也会被修改.
具体实现为: #define va_end(ap) ((void)(ap = (va_list)0))
在了解为什么我们能够从省略的形参中读取参数内容前我们需要了解的是(以下均摘选自vs15 c++库):
1,通过宏获取变量的地址:
#define _ADDRESSOF(v) (&const_cast<char&>(reinterpret_cast<const volatile char&>(v)))
//如果是宏定义的话: 那么其实V只是相当于一个占位符,被传递进来的任何数值都被通过V替换.
//这样一来传递进来的值有可能是: const type&, volatile&, 也可能是 const volatile& 为了保证各种情况下都能成功.
//因此在reinterpret_cast中通过明确指定来完成.
2,通过宏来进行内存对齐:
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
1, 我们知道对于IX86,sizeof(int)一定是4的整数倍,所以~(sizeof(int) - 1) )的值一定是
右面[sizeof(n)-1]/2位为0,整个这个宏也就是保证了右面[sizeof(n)-1]/2位为0,其余位置
为1,所以_INTSIZEOF(n)的值只有可能是4,8,16,......等等,实际上是实现了字节对齐。
2, #define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
的目的在于把sizeof(n)的结果变成至少是sizeof(int)的整倍数,这个一般用来在结构中实现按int的倍数对齐。
如果sizeof(int)是4,那么,当sizeof(n)的结果在1~4之间是,_INTSIZEOF(n)的结果会是4;当sizeof(n)的结果在5~8时,
3,_INTSIZEOF(n)的结果会是8;当sizeof(n)的结果在9~12时,_INTSIZEOF(n)的结果会是12;……总之,会是sizeof(int)的倍数。
以为这就完了? 没呢!!!!!
我们为什么能够通过第一个参数的地址来进行位对齐然后获取其他参数呢内容呢?
1,详细参阅 cpu 的big-endian 和 little-endian.
2,其中参数是存放在连续内存中的(具体如何连续参阅结合big/little-endian).
3,因此我们就可以通过第一个参数的地址来获取出来其他参数的值.