Featured image of post C++面向对象高级开发 兼谈对象模型 C++11新特性数量不定的模板参数、auto、range Based For及引用

C++面向对象高级开发 兼谈对象模型 C++11新特性数量不定的模板参数、auto、range Based For及引用

C++11-数量不定的模板参数, C++11-auto, C++11-range-based for, 引用

C++面向对象高级开发-兼谈对象模型-C++11新特性数量不定的模板参数、auto、range-based for及引用

Notes

  • C++11-数量不定的模板参数
  • C++11-auto
  • C++11-range-based for
  • 引用

数量不定的模板参数

数量不定的模板参数

在传统的模板类或模板函数中,模板参数的个数是确定的,数量不定的模板参数允许任意数量的模板参数。print函数接收两组模板参数,第一组中有一个模板参数typename T,第二组中的模板参数为:typename... Types,数量不定。在调用句中,可推断bitset容器必有对<<操作符重载才可重定向至std::cout。print函数内部使用std::cout输出第一参数后,对于剩余参数使用递归调用实现输出。直到递归至最后一个参数,第二参数已无值递归,故必须有无参数函数print()可调用。对于第二组数量不定的模板参数,并非必须递归调用,可以继承、复合等。若需要统计第二组数量不定的模板参数的个数,可调用sizeof...(args),此处的...与模板参数、函数参数中的…均为必须。

auto

// Non-auto
list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);

// with-auto
list<string> c;
auto ite = find(c.begin(), c.end(), target);

// Compile Fatal Error
list<string> c;
...
auto ite;
ite = find(c.begin(), c.end(), target);

示例中使用字符串作为数据类型的列表容器,当需要使用迭代器iterator时,iterator的数据类型应为list::iterator,数据类型名过长不便写出或需要运行时类型推断或lambda函数时,可使用auto关键字。编译器可从=右侧的函数中推出返回值类型。如auto ite;无赋值动作的语句编译器无法推断其数据类型。

range-based for

range-based for的语法规则为:

for ( decl : coll ) {
    statement
}

在for括号内以冒号为分隔,冒号左侧为定义声明变量,冒号右侧为集合,容器;编译器会将容器中的每个元素分别分配给左侧的变量值,设定后每个变量执行statement语句。示例如下:

for (int i : {2,3,5,7,9,13,17,19}) {
    std::cout << i << std::endl;
}

ranged-base for语法大幅简化了从容器的迭代器循环或使用for-each规则获取元素的方法。使用传统容器存放数据的示例如下:

vector<double> vec;
...
for ( auto elem : vec ) {
    std::cout << elem << std::endl;
}

for ( auto& elem : vec ) {
    elem *= 3;
}

示例中第一个for循环将vec容器中的值通过值传递拷贝到elem元素中,elem元素的类型应为容器中元素的数据类型。若需要对容器中的元素的值改变,则必须使用引用传递,引用传递需在数据类型后增加&amp;符号。

引用

引用

讨论引用,参考变量的三种形式分别为值,指针和引用。

图中memory部分为内存示例,并非真实内存地址映射。

示例代码中,定义指针p指向变量x(Pointer to Integer),定义r为x的引用(Reference to Integer)。变量x为整型数据,占用4个字节,指针p占用4个字节(32位),r引用占用4个字节。编译器在底层中引用通过指针实现,逻辑上引用r与变量x本身等同。故object和其reference的大小相同,地址也相同。引用必须有初值,且一旦设定初值后不可再变。语句r=x2;的含义为变量x2的值赋值至x。编译器底层封装将引用变量与原始变量的sizeof与地址判断均为相同(假象)。

三种形式变量值输出代码示例:

#include <iostream>
#include <cstdlib>

typedef struct Stag { int a, b, c, d; } S;

int main(int argc, const char * argv[]) {
    double x = 0;
    double* p = &x; //p指向x,p的值是x的地址
    double& r = x;  //r代表x,现在r,x都是0

    std::cout << sizeof(x) << std::endl;         //8
    std::cout << sizeof(p) << std::endl;         //4
    std::cout << sizeof(r) << std::endl;         //8
    std::cout << p << std::endl;    //0x7ffeefbff4a8
    std::cout << *p << std::endl;   //0
    std::cout << x << std::endl;    //0
    std::cout << r << std::endl;    //0
    std::cout << &x << std::endl;   //0x7ffeefbff4a8
    std::cout << &r << std::endl;   //0x7ffeefbff4a8

    S s;
    S& rs = s;
    std::cout << sizeof(s) << std::endl;    //16
    std::cout << sizeof(rs) << std::endl;   //16
    std::cout << &s << std::endl;   //0x7ffeefbff488
    std::cout << &rs << std::endl;  //0x7ffeefbff488
    return EXIT_SUCCESS;
}

通常情况下,可以认为引用是一种实现极优的指针,引用大多用在参数传递中。在下面的示例代码中,分别展现了三种不同的参数传递方式,

void func1(Cls* pobj) { pobj->xxx(); }
void func2(Cls obj) { obj.xxx(); }
void func3(Cls& obj) { obj.xxx(); }
...
Cls obj;
func1(&obj);
func2(obj);
func3(obj);

func2()函数的声明为按值传递,func1()为按指针传递,func3()为按引用传递。按指针传递的函数实现中,与按引用传递和按值传递的实现不同,即无论按值传递或按引用传递,函数内部实现的设计不变。同理,在函数调用过程中,按指针传递的调用方式与按值传递和按引用传递不同,按值传递在参数所占空间较大时速度较慢,按引用传递底层为指针传输,所以按引用传递是更优的设计方法。所以,引用通常不用于声明变量,而是用于参数类型和返回值类型的描述。

double imag(const double& im) { ... }
double imag(const double  im) { ... }
//Ambiguity

代码中声明了imag(const double&amp;)imag(const double)(signature)函数,参数个数相同,参数类型也相同,函数对应的signature相同。同时定义带引用参数传递函数与不带引用参数传递函数出现二义性。其原因为,不管是否为带引用参数传递函数,调用方法是一致的,若认为带引用参数传递函数与不带引用参数传递函数可以进行函数重载,在调用imag函数时,两种函数均可调用出现复义性。

函数的signature签名即不含返回值与函数体的部分

若存在函数double imag(const double im) {}与函数double imag(const double im) const {}中,const为函数signature中的一部分,即存在const与不带const的函数signature不同,可并存。