列表初始化 分析initializer_list<T>的实现

列表初始化(1)_统一初始化

1. 统一初始化(Uniform Initialization)

(1)在C++11之前,很多程序员特别是初学者对如何初始化一个变量或对象的问题很容易出现困惑。因为可以用小括号、大括号或赋值操作符等多种方式进行初始化

(2)基于这个原因,C++11引入了“统一初始化”的概念。这意味着我们可以使用{}这种通用的语法在任何需要初始化的地方。

【实例分析】初始化列表

#include <iostream>
#include <vector>
#include <map>
#include <complex>
using namespace std;

//编译选项:g++ -std=c++11 test1.cpp -fno-elide-constructors

struct Test
{
    int x;
    int y;
} test  {123, 321}; //等价于test = {123,321}

int main()
{
    int i;   //未初始化
    int j{}; //j被初始化为0
    int* p;  //未初始化
    int* q{}; //j被初始化为nullptr

    int* a = new int{123}; //等价于int* x=new int(123);
    double b = double{12.12}; //等价于double(12.12)产生一个临时对象,再拷贝初始化
    int* arr = new int[3]{1, 2, 3}; //C++11中新增的初始化堆上数组的方式
    std::map<std::string, int> mm {{"2", 1},{"2", 2}, {"3", 3}}; //相当于map<string,int> mm = {...};

    int values[]{1, 2, 3}; //等价于int values[]={1, 2, 3};
    vector<int> v{2, 3, 5, 7, 11, 13, 17};

    complex<double> c{4.0, 3.0}; //等价于c(4.0, 3.0);
    cout << test.x << endl;
    cout << test.y << endl;

    return 0;
}

2. 列表初始化的使用细节

(1)引入初始化列表(initializer-list)出现的一些模糊概念

//x,y究竟为0,0还是123,321?
struct A
{
  int x;
  int y;

  A(int,int):x(0), y(0){} //非聚合类型,使用{}初始化时会调用相应的构造函数
} a = {123, 321};  //a.x=0, a.y=0

(2)聚合类型的定义

  ①类型是一个普通类型的数组(如int[10]、char[]、long[2][3])

  ②类型是一个类(class、struct或union),且:

    A.无基类、无虚函数以及无用户自定义的构造函数。

    B.无private或protected的普通数据成员(即非静态数据成员)。

    C.不能有{}和=直接初始化的非静态数据成员(“就地初始化”)

【实例分析】聚合类型与非聚合类型的初始化

#include <iostream>
using namespace std;

//x,y究竟为0,0还是123,321?
struct A
{
  int x;
  int y;

  A(int,int):x(0), y(0){} //非聚合类型,使用{}初始化时会调用相应的构造函数
} a = {123, 321};  //a.x=0, a.y=0

struct Base{};

//聚合类型的定义
struct Foo : public Base //不能有基类
{
private:
    double z;      //不能有private的普通成员
    static int k;  //ok,但必须在类外用int Foo::k = 0的方式初始化
public:
    Foo(int x, int y, double z):x(x),y(y),z(z) //不能有构造函数!
    {
        cout<< "Foo(int x, int y, double z)" << endl;
    } 

    Foo(const Foo& foo)  //不能有构造函数!
    {
        this->x = foo.x;
        this->y = foo.y;
        this->z = foo.z;

        cout<< "Foo(const Foo& foo)" << endl;
    }

    int x;
    int y; //不能通过int y=0;或int y{0}来"就地初始化"
    virtual void F(){}; //不能有虚函数!

};

int main()
{
    Foo f1(1, 2, 3.0);     //直接调用构造函数初始化
    Foo f2{4, 5, 6.0};     //由于Foo是个非聚合类型,使用{}时会调用相应的构造函数来初始化。
    Foo f3 = {7, 8, 9.0};  //非聚合类型会调用构造函数来初始化

    cout <<"a.x = " << a.x << ", a.y = " << a.y << endl;

    return 0;
}
/*输出结果
Foo(int x, int y, double z)
Foo(int x, int y, double z)
Foo(int x, int y, double z)
a.x = 0, a.y = 0
*/

(3)注意事项

  ①聚合类型的定义是非递归的。简单来说,当一个类的普通成员是非聚合类型时,这个类也有可能是聚合类型,也就是说可以直接用列表初始化。

  ②对于一个聚合类型,可以直接使用{}进行初始化,这时相当于对其中每个元素分别赋值;而对于非聚合类型,则需要先自定义一个合适的构造函数才能使用{}进行初始化,此时使用初始化列表将调用它对应的构造函数。

列表初始化(2)_分析initializer_list<T>的实现

1. 初始化列表的实现

(1)当编译器看到{t1,t2…tn}时便会生成一个initializer_list<T>对象(其中的T为元素的类型),它关联到一个array<T,n>

(2)对于聚合类型,编译器会将array<T,n>内的元素逐一分解并赋值给被初始化的对象。这相当于为该对象每个字段分别赋值

(3)对于非聚合类型。如果该类存在一个接受initializer_list<T>类型的构造函数,则初始化时会将initializer_list<T>对象作为一个整体传给构造函数。如果不存在这样的构造函数,则array内的元素会被编译器分解并传给相应的能接受这些参数的构造函数(比如列表中有2个元素的,就传给带2个参数的构造函数。有3个元素的,就传给带3个参数的构造函数,依此类推……)。

【实例分析】initializer_list<T>初体验

#include <iostream>
#include <vector>
#include <map>
#include <complex>
using namespace std;

//编译选项:g++ -std=c++11 test1.cpp -fno-elide-constructors

class Foo
{
public:
    Foo(int)
    {
        cout << "Foo(int)"<< endl;
    }

    Foo(int, int)
    {
        cout << "Foo(int, int)"<< endl;
    }

    Foo(const Foo& f)
    {
        cout << "Foo(const Foo& f)"<< endl;
    }
};

int main()
{
    Foo f1(123);
    Foo f2 = 123;   //先将调用Foo(int)将123转为Foo对象,再调用拷贝构造函数(后面这步可能被优化)
    Foo f3 = {123}; //生成initializer_list<int>,然后分解元素后,由于列表中只有1个元素,所以将其传给Foo(int)
    Foo f4 = {123, 321}; //生成initializer_list<int>,然后分解元素后,由于列表中有两个元素,所以将其传给Foo(int, int)

    //编译器会为以下花括号形成一个initializer_list<string>,背后有个array<string,6>
    //调用vector<string>的构造函数时,编译器会找到一个接受initializer_list<string>
    //的重载的构造函数。所有的容器均有这样的构造函数。在这个构造函数里会利用
    //initializer_list<string>来初始化。
    vector<string> city{"Berlin", "New York", "London", "Cairo","Tokyo", "Cologne"};

    //编译器会为以下花括号形成一个initializer_list<double>,背后有个array<double,2>。
    //调用complex<double>的构造函数时,array内的2个元素被分解并传给
    //Comlex<double>(double,double)这个带有两个参数的构造函数。因为comlex<double>并无
    //任何接受initializer_list的构造函数。
    complex<double> c{4.0, 3.0}; //等价于c(4.0, 3.0)

    return 0;
}

2. initializer_list<T>模板

//initializer_list<T>源码分析

#include <iostream>

template <class T>
class initializer_list
{
public:
    typedef T         value_type;
    typedef const T&  reference; //注意说明该对象永远为const,不能被外部修改!
    typedef const T&  const_reference;
    typedef size_t    size_type;
    typedef const T*  iterator;  //永远为const类型
    typedef const T*  const_iterator;
private:
    iterator    _M_array; //用于存放用{}初始化列表中的元素
    size_type   _M_len;   //元素的个数

    //编译器可以调用private的构造函数!!!
    //构造函数,在调用之前,编译会先在外部准备好一个array,同时把array的地址传入模板
    //并保存在_M_array中
    constexpr initializer_list(const_iterator __a, size_type __l)
    :_M_array(__a),_M_len(__l){};  //注意构造函数被放到private中!

    constexpr initializer_list() : _M_array(0), _M_len(0){} // empty list,无参构造函数

    //size()函数,用于获取元素的个数
    constexpr size_type size() const noexcept {return _M_len;}

    //获取第一个元素
    constexpr const_iterator begin() const noexcept {return _M_array;}

    //最后一个元素的下一个位置
    constexpr const_iterator end() const noexcept
    {
        return begin() + _M_len;
    }
};

(1)initializer_list是一个轻量级的容器类型,内部定义了iterator等容器必需的概念,本质上是一个迭代器

(2)对于std:: initializer_list<T>而言,它可以接收任意长度的初始化列表,但要求元素必须是同种类型(T或可转换为T)。

(3)它有3个成员函数:size()、begin()和end()

(4)拥有一个无参构造函数,可以被直接实例化,此时将得到一个空的列表。之后可以进行赋值操作,如initializer_list<int> list; list={1,2,3,4,5};

(5)initializer_list<T>在进行复制或赋值时,它内部将保存着列表的地址保存在_M_array中,它进行的是浅拷贝,并不真正复制每个元素,因此效率很高。

【编程实验】打印初始化列表的每个元素

#include <iostream>

//打印初始化列表的每个元素
void print(std::initializer_list<int> vals)
{
    //遍历列表中的每个元素
    for(auto p = vals.begin(); p!=vals.end(); ++p){
        std::cout << *p << " ";
    }

    std::cout << std::endl;
}

//std::initializer_list<T>的浅拷贝。以下的返回值应改为std
//以下的返回值应改为std::vector<int>类型,而不是std::initializer_list<int>类型。
std::initializer_list<int> func(void)
{
    int a = 1;
    int b = 2;

    return {a, b}; //编译器看到{a, b}时,会做好一个array<int,2>对象(其生命
                   //期直至func结束),然后再产生一个initializer_list<int>
                   //临时对象,由于initializer_list<int>采用的是浅拷贝,当
                   //函数返回后array<int,2>会被释放,所以无法获取到列表中的元素!
}

int main()
{
    print({1,2,3,4,5,6,7,8,9,10});

    print(func());

    return 0;
}
/*测试结果:
e:\Study\C++11\7>g++ -std=c++11 test1.cpp
e:\Study\C++11\7>a.exe
1 2 3 4 5 6 7 8 9 10
*/

3. 让自定义的类可以接受任意长度初始化列表

(1)自定义类中重载一个可接受initializer_list<T>类型的构造函数

(2)在该构造函数中,遍历列表元素并赋值给相应的字段。

【编程实验】自定义类的初始化列表

#include <iostream>
#include <map>

using namespace std;

class Foo
{
public:
    Foo(int a, int b)
    {
        cout << "Foo(int a, int b)" << endl;
    }

    Foo(initializer_list<int> list)
    {
        cout << "Foo(initializer_list<int> list) : ";

        for(auto i : list){
            cout <<i<< " ";
        }

        cout << endl;
    }

};

class FooMap
{
    std::map<int, int> content;
    using pair_t = std::map<int, int>::value_type;
public:
    FooMap(std::initializer_list<pair_t> list)
    {
        for(auto it = list.begin(); it!=list.end(); ++it){
            content.insert(*it);

            std::cout << "{" << (*it).first <<"," <<(*it).second <<"}" << " ";
        }

        std::cout << std::endl;
    }
};

int main()
{
    Foo f1(77, 5);     //Foo(int a, int b), a = 77, b = 5;

    //注意:由于定义了Foo(initializer_list<int> list)函数,以下3种方
    //式的初始化都会将{...}作为一个整体传递给该函数。如果没有定义该函
    //数,则由于该类是个非聚合类用{}初始化时,会调用构造函数来初始化。
    //但由于Foo类不存在3个参数的构造函数,所以f3那行会编译失败!
    Foo f2{77, 5};     //Foo(initializer_list<int> list)
    Foo f3{77, 5, 42}; //Foo(initializer_list<int> list)
    Foo f4 = {77, 5};  //Foo(initializer_list<int> list)

    FooMap fm = {{1,2}, {3,4},{5,6}};

    return 0;
}
/*测试结果:
e:\Study\C++11\7>g++ -std=c++11 test2.cpp
e:\Study\C++11\7>a.exe
Foo(int a, int b)
Foo(initializer_list<int> list) : 77 5
Foo(initializer_list<int> list) : 77 5 42
Foo(initializer_list<int> list) : 77 5
{1,2} {3,4} {5,6}
*/

参考文章:

C++11之initialization_list

原文地址:https://www.cnblogs.com/leijiangtao/p/12064550.html

时间: 2024-11-08 02:50:23

列表初始化 分析initializer_list<T>的实现的相关文章

c++11——列表初始化

1. 使用列表初始化 在c++98/03中,对象的初始化方法有很多种,例如 int ar[3] = {1,2,3}; int arr[] = {1,2,3}; //普通数组 struct A{ int x; struct B{ int y; int z; } b; }a = {1, {3,4}}; //POD类型,可以直接使用memcpy复制的对象 int i = 0; Foo foo = f;//拷贝初始化 int i(0); Foo f(123); //直接初始化 在c++98/03中,普通

C++11列表初始化

列表初始化:1.旧语法中定义了初始化的几种不同形式,如下: int data = 0; //赋值初始化 int data = {0}; //花括号初始化 int data(0); //构造初始化 int data{0}; //花括号初始化 2.C++11以旧语法中花括号初始化形式为基础,设计了列表初始化语法,统一了不同的初始化形式. 数据类型 变量{初始化列表} 1 #include <iostream> 2 #include <iterator> 3 using namespac

C++11之列表初始化

1. 在C++98中,标准允许使用花括号{}来对数组元素进行统一的集合(列表)初始化操作,如:int buf[] = {0};int arr[] = {1,2,3,4,5,6,7,8}; 可是对于自定义的类型,却是无法这样去初始化的,比如STL标准模板库中容器,使用的频率非常之高,如vector,若要初始化其内容,则需要每次进行push_back 或使用迭代器去初始化,这是极其不便的.C++11 中,可以”列表初始化“方式来快速的初始化内置类型或STL中容器. 2.集合(列表)的初始化方式已经成

列表初始化

1 int var = 0; 2 int var = {0}; 3 int var {0}; 4 int var(0); 无论是初始化对象还是某些时候为对象赋新值,都可以用这样一组右花括号括起来的初始值. 当用于内置类型的变量时,这种初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错,也就是说安全性比那些作转换的更高: 1 long double ld = 3.14 2 int a{ld},b={ld};// 错误:没发生转换,因为存在丢失信息的风险 3

C++列表初始化

在C++11中,使用花括号来初始化变量得到全面的应用,也可以用花括号来对变量赋值.当用于内置类型变量时,这种初始化的一个重要特点是如果使用列表初始化且初始值存在丢失信息的风险,编译器将报错. int _tmain(int argc, _TCHAR* argv[]) { long double ld = 3.1415926536; int a{ ld }; int b = { ld }; int c(ld); int d = ld; return 0; } 编译将得到以下错误: (11): err

【共读Primer】51.[6.3]返回类型和return语句--列表初始化返回值 Page203

列表初始化返回值 C++11规定可以以列表初始化的方式来进行返回值的表达. vector<string> process() { if(expected.empty()) return {}; else if(expected == actual) return {"funcationX", "okay"}; else return {"funcationX", expected, actual}; } 主函数main的返回值 非vo

类的初始化分析要点代码

分析要点: 1.初始化:类型(枚举,结构,类)的实例在构建过程中得一系列过程 2.初始化的主要目的:对内部的存储属性赋值 3.结构:如果不自觉添加初始化,会自动添加默认init和memberwise init 类的初始化:代码如下 class YY { var i:int// 会报错,因为没有初始值(存储属性) } //NN没有构造器 class NN{ var i:int init(a:int){i=1} } //黄金规则1:实例化之后所有的存储属性必须有值(两种赋值方法) //专门(特定)的

JFinal初始化分析-常量初始化-1

spring自己基本不熟悉,因为超多的xml配置太繁琐,就放弃了.自己也不是专做开发的,主要工作是运维.jfianl的出现,无xml配置,令人惊喜. 当时就想学一下这个框架的设计,就找博文来读,但是发现很多博主一遍赞叹jfinal的强大,一边说jfinal的设计很简单,好像每个博主自己都可以设计一套jfinal一样,但基本每个博主也都是蜻蜓点水的分析一下启动或某一块,然后及基本不再写了. 我自己觉得jfinal很强大,体量虽然很小,但是设计却不简单,相反却很精巧.可以看出波哥不是因为设计而设计.

cc2530操作任务系统初始化分析

操作系统任务初始化void osalInitTasks( void ){ uint8 taskID = 0; // 分配内存,返回指向缓冲区的指针 tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); // 设置所分配的内存空间单元值为0 osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); // 任务优先级由高向低依次排列,高优先级对应tas