个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)
做日志打印或其它可变参数处理时,通常我们会想到使用va_list/va_start/va_end做可变参数的收集和处理。使用这种方式处理可变参数比较通用,同时适用于c与c++中。
1. 关于va_list的理解
va_list是一个变参的结构,通常它的结构中,会有一个起始参数的地址。
就像我们经常见到的main函数中的那样:argc-argcount参数个数,argv-argvalues参数指针数组
int (int argc, char** argv)
我们通过argc知道了参数的个数,通过argv获取到每个参数的指针。
va_list初始化后,提供一个类似argv的变量,用于做参数的遍历处理,但实际情况比这个复杂许多,内部提供的可能是参数位于寄存器上地址与偏移、参数位于栈地址与偏移。
va_list初始化后,通常并不能用于获取参数个数,不能提供类似argc的获取;参数个数需要额外的判断,例如vprintf基于formart的%d,%s等来获取需要输入的参数个数。
对于va_list结构,标准库没有提供直接读取va_list内部信息的函数,对于不同操作系统结构可能会不同,另外可以使用 va_arg 逐个提取参数,或者使用 va_copy 复制 va_list。
相关对va_list的处理函数
va_start:初始化一个可变参数列表var_list内部的指针;
va_arg:获取下一个参数的值
va_end:清理一个可变参数列表var_list;
va_copy(C99+):复制参数列表状态
2. 关于va_start/va_end
通常var_start与var_end成对出现,va_start负责初始化一个var_list可变参数列表指针,va_end负责使用完清理。
void va_start(va_list ap, prev_param);
void va_end(va_list ap);
参数ap:一个 va_list 类型的变量,用于存储可变参数列表的内部状态。
参数prev_param:可变参数列表之前的最后一个固定
var_start与var_end使用时,要特别注意的一个点是,成对出现与使用,并且如果使用过一次var_list后,需要重新初始化才能再次使用;原因是因为使用过的va_list内部的指针偏移被修改过了,再次使用的话,偏移未重置,读取的参数可能就有问题了或越界了。
一个使用var_start与var_end两次的样例,形如如下的效果:
先使用一次vsnprintf获取输出字符串长度,然后再申请字符串,然后再调用一遍vsnprintf把字符串输出到buffer中。
std::string LogToStr(const char* format, ...) {
// init va_list
va_list valist;
va_start(valist, format);
// fill buffer
int length = vsnprintf(nullptr, 0, format, valist);
std::string buffer;
if (length > 0) {
// destory and reinit valist
va_end(valist);
va_start(valist, format);
// alloc size and fill buffer
buffer.resize(length + 1);
length = vsnprintf(&buffer[0], length + 1, format, valist);
}
// destory valist and return buffer
va_end(valist);
return buffer;
}
3. 接受va_list的系统函数
直接接受va_list的系统函数也比较多,常见的有vprintf, vfprintf, vsnprintf等:
int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vsnprintf(char* buffer, size_t size, const char* format, va_list ap);
使用vprintf,打印到标准输出的样例:
void print_formatted(const char* format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
使用vfprintf,输出到文件的样例:
void log_to_file(FILE* file, const char* format, ...) {
va_list args;
va_start(args, format);
vfprintf(file, format, args);
va_end(args);
}
使用vsnprintf,输出到定长字符串的样例:
void safe_format_string(char* buffer, size_t size, const char* format, ...) {
va_list args;
va_start(args, format);
vsnprintf(buffer, size, format, args);
va_end(args);
}
函数/宏名称 | 作用 |
---|---|
vprintf | 将格式化字符串输出到标准输出。 |
vfprintf | 将格式化字符串输出到指定文件流。 |
vsprintf | 将格式化字符串存储到指定缓冲区。 |
vsnprintf | 将格式化字符串存储到指定缓冲区,并确保不超出缓冲区大小。 |
vscanf | 从标准输入读取格式化数据。 |
vfscanf | 从指定文件流读取格式化数据。 |
vsscanf | 从指定字符串读取格式化数据。 |
vfwprintf | 将格式化后的宽字符串输出到指定文件流。 |
vswprintf | 将格式化后的宽字符串存储到指定缓冲区。 |
vsyslog | 将格式化日志输出到系统日志。 |
vsnprintf_s | 安全版本的 vsnprintf ,确保不超出缓冲区大小。 |
除了这一些能接受va_list的函数来直接调用外,还可以直接遍历va_list使用。
可以使用var_arg来一个一个取参数使用:
type va_arg(va_list ap, type);
int SumValues(const int count, ...) {
va_list args;
va_start(args, count);
int sum = 0;
for (int i=0; i<count; i++){
sum += va_arg(args, int);
}
va_end(args);
return sum;
}
其它,stl库模版实现可变参数
另外还一种stl库中带的可变参数模板形式:
template<typename... Args>
void myFunction(Args... args) {
int count = sizeof...(args);
// 处理args
}
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}
int main() {
std::cout << sum(1, 2, 3, 4) << std::endl; // 输出: 10
std::cout << sum(1.5, 2.5, 3.5) << std::endl; // 输出: 7.5
return 0;
}
个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)