一、概念
initializer_list是C++11中提供的一种标准库类型(ps:其实也是一个模板类),用于表示某种使用特定类型的值的数组。
initializer_list中的值都是常量值,无法修改。
二、提供的操作(以int型举例)
申明: initializer_list<int> lst;
也可以这样 initializer_list<int> lst{1,2,3,4};
还可以这样 initializer_list<int> lst = {1,2,3,4};
常见操作:
lst.size() lst.begin() lst.end()
三、用途之一
可以更方便的给vector、 string类型赋初始化值。
//c++98 vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); //c++11,感谢c++11 vector<int> v2 = { 1, 2, 3, 4 };
四、用途之二
当一个函数的入参可能有多个参数时,可将入参定义为initializer_list,这样就灵活多了。
#include <iostream> #include <vector> using namespace std; class MyNumber { public: MyNumber(const initializer_list<int> &v) { for (auto itm : v) { mVec.push_back(itm); } } void print() { for (auto itm : mVec) { cout << itm << " "; } cout<<endl; } private: vector<int> mVec; }; int main() { MyNumber m({1, 2, 3, 4}); m.print(); return 0; }
root@ubuntu:~/c++# g++ -std=c++11 init.cpp -o init root@ubuntu:~/c++# ./init 1 2 3 4
#include <iostream> #include <vector> class MyNumber { public: MyNumber (){ std::vector<int> mvec = {1, 2}; mVec = mvec; std::cout<<"进入构造函数1"<<std::endl; } MyNumber(const std::initializer_list <int> &v) { for (auto itm : v) { mVec.push_back(itm); } } void print() { for (auto itm : mVec) { std::cout << itm << " "; } std::cout<<std::endl; } private: std::vector<int> mVec; }; int main() { MyNumber n; //n = { 1, 2, 3, 4 };//这句会调用构造函数2 n.print(); return 0; }
root@ubuntu:~/c++# g++ -std=c++11 init2.cpp -o init root@ubuntu:~/c++# ./init 进入构造函数1 1 2
#include <iostream> #include <vector> class MyNumber { public: MyNumber (){ std::vector<int> mvec = {1, 2}; mVec = mvec; std::cout<<"construct MyNumber ()"<<std::endl; } MyNumber(const std::initializer_list <int> &v) { std::cout<<"construct MyNumber (const std::initializer_list)"<<std::endl; for (auto itm : v) { mVec.push_back(itm); } } void print() { for (auto itm : mVec) { std::cout << itm << " "; } std::cout<<std::endl; } private: std::vector<int> mVec; }; int main() { MyNumber n; n = { 1, 2, 3, 4 };//这句会调用构造函数2 n.print(); return 0; }
root@ubuntu:~/c++# g++ -std=c++11 init2.cpp -o init root@ubuntu:~/c++# ./init construct MyNumber () construct MyNumber (const std::initializer_list) 1 2 3 4
#include <iostream> #include <vector> #include <map> // 使用 std::initializer_list<int> 来初始化任意长度的初始化列表 //stl中的容器是通过使用 std::initializer_list 完成的 class Foo { public: Foo(std::initializer_list <int>){} }; class FooVector { std::vector<int> content_; public: FooVector(std::initializer_list<int> list)//initializer_list 负责接收初始化列表 { for (auto it = list.begin(); it != list.end(); ++it) { content_.push_back(*it); } } }; //map 是以 pair形式插入的。map中的元素的类型value_type //typedef pair<const Key, Type> value_type; class FooMap { std::map<int, int> content_; using pair_t = std::map<int, int>::value_type;//重新命名类型 typedef public: FooMap(std::initializer_list<pair_t> list) { for (auto it = list.begin(); it != list.end(); ++it) { content_.insert(*it); } } }; //使用 std::initializer_list 给自定义类型做初始化 void test01() { Foo foo = { 1,2,3,4,5 }; FooVector foo1 = { 1, 2, 3, 4, 5 }; FooMap foo2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; } //使用 std::initializer_list 传递同类型的数据 void func(std::initializer_list<int> list) { std::cout << "size = "<<list.size() << std::endl; //对 std::initializer_list 访问只能通过begin() end() 循环遍历 //迭代器是只读的,无法修改某一个元素,但可以整体赋值 for (auto it = list.begin(); it != list.end(); it++) { std::cout << *it << std::endl; } } void test02() { std::cout << "test02" << std::endl; func({});//1个空集合 func({ 1,2,3 });//传递 { 1,2,3 } } /* std::initializer_list 的内部并不负责保存初始化列表中元素的拷贝,仅仅 存储了列表中元素的引用而已,因此需要再持有对象的生存周期之前传递完毕 */ //错误的使用方法 std::initializer_list<int> func2(void) { int a = 1, b = 2; return { a,b };//ab 返回时并没有拷贝 } //正确的使用 std::vector<int> func3(void) { int a = 1, b = 2; return { a,b };//ab 返回时并没有拷贝 } void test03() { std::initializer_list<int> myList; size_t n = myList.size(); std::cout <<" myList.size " << n << " " << std::endl; myList = { 1,2,3,4,5,6 }; n = myList.size(); std::cout <<" myList.size " << n << " " << std::endl; myList = { 11,22}; n = myList.size(); std::cout <<" myList.size " << n << " " << std::endl; std::vector<int> a; a = func2();//值时乱码值 std::cout <<" func2 return " << std::endl; for (auto n : a) { std::cout << n << std::endl; } a = func3(); std::cout <<" func3 return " << std::endl; for (auto n : a) { std::cout << n << std::endl; } } int main(void) { test01(); test02(); test03(); return 0; }
root@ubuntu:~/c++# g++ -std=c++11 init2.cpp -o init root@ubuntu:~/c++# ./init test02 size = 0 size = 3 1 2 3 myList.size 0 myList.size 6 myList.size 2 func2 return 2 0
----------------不是1,2
func3 return 1 2
How to Initialize a map in one line using initialzer_list ?
同样的,我们也可以用std::initialzer_list<T>初始化一个map:
std::map<std::string, int> mapOfMarks = { {"Riti",2}, {"Jack",4} };
相对应的,编译器会在内部创建这样的一个对象:
std::initializer_list<std::pair<const std::string, int> > = { {"Riti",2}, {"Jack",4} };
#include <iostream> #include <vector> #include <map> class FooMap { std::map<int, int> content_; using pair_t = std::map<int, int>::value_type;//重新命名类型 typedef public: FooMap(std::initializer_list<pair_t> list) { for (auto it = list.begin(); it != list.end(); ++it) { std::cout << it->first << " " << it->second << std::endl; content_.insert(*it); } } }; int main() { FooMap foo2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; return 0; }
root@ubuntu:~/c++# g++ -std=c++11 init2.cpp -o init root@ubuntu:~/c++# ./init 1 2 3 4 5 6
最后,对 std::initializer_list 的访问只能通过 begin() 和 end() 进行循环遍历,遍历时取得的迭代器是只读的。因此,无法修改 std::initializer_list 中某一个元素的值,但是可以通过初始化列表的赋值对 std::initializer_list 做整体修改,代码如下:
std::initializer_list<int> list; size_t n = list.size(); // n == 0 list = { 1, 2, 3, 4, 5 }; n = list.size(); // n == 5 list = { 3, 1, 2, 4 }; n = list.size(); // n == 4
std::initializer_list 拥有一个无参数的构造函数,因此,它可以直接定义实例,此时将得到一个空的 std::initializer_list。
之后,我们对 std::initializer_list 进行赋值操作(注意,它只能通过初始化列表赋值),可以发现 std::initializer_list 被改写成了 {1, 2, 3, 4, 5}。
然后,还可以对它再次赋值, std::initializer_list 被修改成了 {3, 1, 2, 4}。
看到这里,可能有读者会关心 std::initializer_list 的传递或赋值效率。
假如 std::initializer_list 在传递或赋值的时候如同 vector 之类的容器一样,把每个元素都复制了一遍,那么使用它传递类对象的时候就要斟酌一下了
实际上, std::initializer_list 是非常高效的。它的内部并不负责保存初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已。
因此,我们不应该像这样使用:
#include <iostream> #include <vector> std::initializer_list<int> func(void) { int a = 1, b = 2; return { a, b }; // a、 b 在返回时并没有被拷贝 } int main() { std::vector<int> a = func(); for (auto n : a) { std::cout << n << std::endl; } return 0; }
root@ubuntu:~/c++# ./init 1 2
虽然这能够正常通过编译,但却无法传递出我们希望的结果( a、 b 在函数结束时,生存期也结束了,因此,返回的将是不确定的内容)。
这种情况下最好的做法应该是这样:
std::vector<int> func(void) { int a = 1, b = 2; return { a, b }; }
使用真正的容器,或具有转移 / 拷贝语义的物件来替代 std::initializer_list 返回需要的结果。
我们应当总是把 std::initializer_list 看做保存对象的引用,并在它持有对象的生存期结束之前完成传递。
initializer_list的一些操作
(1)initializer_list< T > lst : 默认初始化,T类型元素的空列表;
(2)initializer_list< T > lst{a,b,c} : lst的元素数量和初始值一样多,lst的元素是对应初始值的副本,列表中的元素为const
(3)lst2(lst)或者lst2 = lst:拷贝或者赋值一个initializer_list对象, 拷贝后,原始列表和副本共享元素;
(4)lst.size():列表中的元素数量;
(5)lst.begin():返回指向lst中首元素的指针;
(6)lst.end():返回指向lst中尾元素下一位置的指针。