文档章节

Effective C++: 省略符形参

SHIHUAMarryMe
 SHIHUAMarryMe
发布于 2016/11/23 20:48
字数 1216
阅读 61
收藏 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
博文 162
码字总数 137201
作品 0
武汉
程序员
私信 提问
【C++笔记】函数(笔记)

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

u013165921
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
06/22
0
0
swift4.1 系统学习十 函数

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

小曼Study
10/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Windows 10 设置 Java 环境变量

首先你需要在我的电脑中打开,找到环境变量属性。 找到环境变量属性 找到环境变量属性后单击将会看到下面的设置界面。 在这个界面中设置高级系统设置。 环境变量 在弹出的界面中选择设置环境...

honeymose
今天
1
0
用any-loader封装jQuery的XHR —— 随便写着玩系列

哎,都说没人用JQuery啦,叫你别写这个。 其实我也是好高骛远使用过npm上某个和某个很出名的XHR库,嗯,认识我的人都知道我喜欢喷JQ,以前天天喷,见面第一句,你还用JQ,赶紧丢了吧。但我也...

曾建凯
今天
7
0
聊聊storm的AggregateProcessor的execute及finishBatch方法

序 本文主要研究一下storm的AggregateProcessor的execute及finishBatch方法 实例 TridentTopology topology = new TridentTopology(); topology.newStream("spout1", spout......

go4it
今天
4
0
大数据教程(7.5)hadoop中内置rpc框架的使用教程

博主上一篇博客分享了hadoop客户端java API的使用,本章节带领小伙伴们一起来体验下hadoop的内置rpc框架。首先,由于hadoop的内置rpc框架的设计目的是为了内部的组件提供rpc访问的功能,并不...

em_aaron
今天
5
0
CentOS7+git+github创建Python开发环境

1.准备CentOS7 (1)下载VMware Workstation https://pan.baidu.com/s/1miFU8mk (2)下载CentOS7镜像 https://mirrors.aliyun.com/centos/ (3)安装CentOS7系统 http://blog.51cto.com/fengyuns......

枫叶云
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部