文档章节

Effective C++: 省略符形参

SHIHUAMarryMe
 SHIHUAMarryMe
发布于 2016/11/23 20:48
字数 1216
阅读 87
收藏 0

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,因此我们就可以通过第一个参数的地址来获取出来其他参数的值.

 

© 著作权归作者所有

共有 人打赏支持
SHIHUAMarryMe
粉丝 13
博文 164
码字总数 138212
作品 0
武汉
程序员
私信 提问
【C++笔记】函数(笔记)

在头文件中进行函数声明 建议变量和函数应该在头文件中声明,在源文件中定义。 使用引用避免拷贝 如果函数无须改变引用形参的值,最好将其声明为常量引用。 尽量使用常量引用 表示数组大小的...

u013165921
2018/01/15
0
0
C与C++的细微区别——省略形式参数名

一、C与C++的细微区别 在函数声明中: 无论是C还是在C++,都可以省略形式参数名。 但是,通常都不建议省略形式参数名。 在函数定义中: 1. 当需要使用形式参数的时候,显然,必须给形式参数命...

Start-up
2012/05/08
0
0
C++复制构造函数以及赋值操作符

当定义一个新类型的时候,需要显式或隐式地指定复制、赋值和撤销该类型的对象时会发生什么——这是通过定义特殊成员:复制构造函数、赋值操作符和析构函数来达到的。如果没有显式定义复制构造...

风筝Fergus
2013/04/21
0
0
C++难点解析之const修饰符

C++难点解析之const修饰符 c++ 相比于其他编程语言,可能是最为难掌握,概念最为复杂的。结合自己平时的C++使用经验,这里将会列举出一些常见的难点并给出相应的解释。 const修饰符 const在c...

jackie8tao
2018/06/22
0
0
swift4.1 系统学习十 函数

swift的函数与其他语言中的函数还是有很多的不同点的。 辅助文件, 在“统一的函数引用体系”中会用到。 本节主要内容: // 函数 / 学过任何一门语言的小伙伴们对函数都会感到不陌生。 Apple官...

小曼Study
2018/10/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

从 JVM 分析 hibernate-validator NoClassDefFoundError

最近排查一个spring boot应用抛出hibernate.validator NoClassDefFoundError的问题,异常信息如下: Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.hibernat......

微笑向暖wx
15分钟前
0
0
c++指针和字符串

天王盖地虎626
20分钟前
0
0
从 JVM 分析 hibernate-validator NoClassDefFoundError

最近排查一个spring boot应用抛出hibernate.validator NoClassDefFoundError的问题,异常信息如下: Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.hibernat......

java菜分享
25分钟前
1
0
超500款社交APP对标微信,细分未来社交领域

“微信之父”张小龙在2019微信公开课PRO活动上透露,截止2018年8月,微信的日登录量已超过10亿。腾讯2018年三季度财报显示,QQ智能终端月活跃账户达6.98亿人。 当前微信和QQ无疑是中国最大且...

ThinkSNS账号
32分钟前
1
0
Fiddler 抓包工具总结

序章 Fiddler是一个蛮好用的抓包工具,可以将网络传输发送与接受的数据包进行截获、重发、编辑、转存等操作。也可以用来检测网络安全。反正好处多多,举之不尽呀!当年学习的时候也蛮费劲,一...

javaer
34分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部