直接初始化和拷贝初始化
如果使用=来初始化一个变量,实际上执行的是拷贝初始化
。编译器把等号右边的初始值拷贝到新创建的对象中去。 如果不使用等号,则执行的是直接初始化
。
含有可变形参的函数
C++11提供了两种主要的方法:
- initializer_list方法,要求实参类型必须相同1
- 可变参数模板
另外传统C++还给与了一个与C语法兼容的方法
- 省略符形参
initializer_list的用法
void error_msg(ErrCode e, initializer_list<string> il) {
cout << e.msg() << ": ";
for (const auto &elem : il) {
cout << elem << " ";
cout << endl;
}
error_msg(ErrCode(32), {"functionX", " is not ", "right"});
省略符形参
省略符形参是为了方便C++程序访问某些特殊的C代码而设置的。这些代码使用了名为varargs的C标准库的功能。通常,省略符形参不应该用于其他目的。
省略符形参应该仅仅用于C/C++通用的类型。特别需要注意的是,大多数类类型的对象传递给省略符形参时,都无法正确拷贝。
省略符形参必定在函数定义的最后面。
我们采用 va_start,va_end和va_arg来使用省略符形参
用法如下所示
int sumi(int c...) {
va_list argptr;
va_start(argptr, c);
int sum = c;
c = va_arg(argptr, int);
while( 0 != c) {
sum += c;
c = va_arg(argptr, int);
}
va_end(argptr);
return sum;
下面分析一下这个过程中的原理
其实va_list只是一个指针。可以认为uint8_t *指针。
当然我们也可以认为是void指针,不过void指针的加减运算是未定义的,即便在大多数编译器中void*指针加减就是以一个字节为单位的.
va_start(argptr, c)
这句宏语句,使得我们把argptr的指针指向,c的下一个位置。
这句话等价于 argptr = (uint8_t *)&c + sizeof(c);
c = va_arg(argptr, int);
这句宏语句,作用是返回参数值,并且将argptr指针指向下一个地址。
这句话等价于 c = * (int *)( (argptr += sizeof(int)) -sizeof(int) )
这句话比较复杂,我们可以拆解为
argptr += sizeof(int);
c = * (int *) (argptr - sizeof(int));
va_end(argptr) 将argptr置为空指针。 当然这句话其实没有必要,只要保证以后不会使用argptr变量就行了。
在VC中,这几个宏的定义是
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1)) //类型n的大小
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //ap指向第一个不定参数地址
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址 返回当前ap指向的值,并且让ap指向下一个参数的地址
#define va_end(ap) ( ap = (va_list)0 ) // 将指针置为无效
这里还有些题外话,C语言的这种方式,使得我们的函数的参数长度不定。在汇编上,体现为函数调用时,加入参数栈的过程,参数栈的长度,函数本身不知道。只有调用这个函数的函数才知道。 此时,函数参数栈的压栈和出栈都由调用这个函数的函数来做。这种调用方法被称为 __cdecl
. 另外还有一种方式,__stdcall
,是由函数自身来清理。这要求函数自身知道自己的参数栈需要多少空间。
可变长模板
可变长模板,将会在模板一章细述
constexpr函数
constexpr是指能用于常量表达式的函数。定义constexpr函数必须遵守以下规定
- 函数的返回值类型,以及所有形参的类型必须都是字面值
- 函数体中有且只有一条return语句2
constexpr被隐式的指定为内联函数。