文档章节

Effective C++: 省略符形参

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

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

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

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

Start-up
2012/05/08
0
0
C++难点解析之const修饰符

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

jackie8tao
06/22
0
0
期末复习之C语言编程基础知识梳理

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界
03/18
0
0
C++复制构造函数以及赋值操作符

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

风筝Fergus
2013/04/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

在Debian 9系统上安装Mysql数据库的方法教程

前言 看到题目大家应都会想,在 Debian 9 上安装 Mysql?那不是很简单的事儿吗?直接 sudo apt install mysql-server 不就行了吗? 没想到遇到了几个之前没遇到的问题,耽误了不少时间。 原来...

临江仙卜算子
41分钟前
4
1
从web实时通信讲H5 WebSocket

通常我们打开一个浏览器访问网页时,都会向页面所在的服务器发送一个HTTP请求,然后web服务器确认请求并向浏览器做出响应。简单的说,就是一个请求对应的一个响应。然而这种方法对许多的应用...

Code辉
55分钟前
3
0
Sharding-Sphere自动化执行引擎

Q: 什么叫"自动化执行引擎"? A: 一条SQL的生命周期是:从客户端发起、经过Sharding-Sphere处理、再到底层数据库执行消化。而在Sharding-Sphere里过程则是:SQL解析-->SQL优化-->SQL路由-->...

xiaomin0322
58分钟前
2
0
单模块中ReentrantLock的使用

背景 在单模块应用中,对同一个请求,需要进行同步。注意ReentrantLock的使用场景: 同一个线程中 同一个请求 RestController @RestControllerpublic class Controller {private final Re...

亚林瓜子
59分钟前
2
0
Linux 4.1内核热补丁成功实践

好久不见的干货重现江湖!今日的内容是基于UCloud运维同学反馈的个别宿主机上存在进程CPU峰值使用率异常现象问题进行的相关阐述。本文详细介绍了该问题的完整分析思路和用热补丁的方式成功解...

UCloudTech
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部