深入应用C++11 笔记 (二)
第一章 程序简洁之道
1.3 初始化列表
给类添加一个std::initializer_list构造函数,它将拥有任意长度初始化的能力。
class Foo { public: Foo(std::initializer_list<int>) {} }; Foo foo = { 1,2,3,4,5 };//OK
使用initializer_list来接收{…},如何通过它来给自定义容器赋值呢?代码如下:
class FooVector { std::vector<int> content_; public: FooVector(std::initializer_list<int> list) { for (auto it = list.begin(); it != list.end(); ++it) { content_.push_back(*it); } } }; class FooMap { std::map<int, int> content_; using pirt_t = std::map<int, int>::value_type; public: FooMap(std::initializer_list<pirt_t> list) { for (auto it = list.begin(); it != list.end(); ++it) { content_.insert(*it); } } }; FooVector foo_1 = { 1,2,3,4,5 }; FooMap foo_2 = { { 1,2 },{ 3,4 },{ 5,6 } };
std::initializer_list不仅可以用来对自定义类型做初始化,还可以用来传递同类型的数据集合:
void func(std::initializer_list<int> l) { for(auto it=l.begin();it!=l.end();++it) { std::cout<<*it<<std::endl; } } int main(void) { func({}); \\一个空集合 func({1,2,3}); \\传递{1,2,3} return 0; }
std::initializer_list的一些细节
- 它是一个轻量级的容器类型,内部定义了iterator等容器必须的概念
- 对于std::initializer_list <T>而言,它可以接收任意长度的初始化列表,但要求元素必须是同种类型T(或可转换为T)
- 它有三个成员接口:size(),begin(),end()
- 它只能被整体初始化或赋值
std::initializer_list<int> list={1,2,3}; size_t n=list.size();//n==3
对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的内部并不负责保存初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已
初始化列表会防止类型被收窄
类型收窄包括以下几种情况:
从一个浮点数隐式转换为一个整型数,如int i=2.2
从高精度浮点数隐式转换为低精度浮点数,如从long double隐式转换为double或float
从一个整型数隐式转换为一个浮点数,并且超出了浮点数的表示范围,如:float x=(unsigned long long)-1
从一个整型数隐式转换为一个长度较短的整型数,并且超出了长度较短的整型数的表示范围,如char x=65536
int a = 1.1;//OK int b = { 1.1 };//error float fa = 1.0e+40;//OK float fb = { 1.0e+40 };//error float fc = (unsigned long long) - 1;//OK float fd = { (unsigned long long) - 1 };//error float fe = (unsigned long long)1;//OK float ff = { (unsigned long long)1 };//OK const int x = 1024, y = 1;//OK char c = x;//OK char d = { x };//error char e = y;//OK char f = { y };//OK
只有遇到类型变窄的情况,初始化列表就不回允许这种转换发生
1.4 基于范围的for循环
for循环的用法
std::vector<int> arr; //<1> C++ 中遍历容器的一般方法 for (auto it = arr.begin(); it != arr.end(); ++it) { std::cout << *it << std::endl; }
void do_cout(int n) { std::cout << n << std::endl; } int main() { std::vector<int> arr = { 1,2,3,4,5,6,7,8 }; //<2> stl 的algorithm中有一个for_each算法 for_each(arr.begin(), arr.end(), do_cout); //或者使用lambda for_each(arr.begin(), arr.end(), [&](int n) {std::cout << n << std::endl; }); }
//C++11 中for用法 for(auto n:arr) { std::cout<<n<<std::endl; } //当然 如果我命明确数据类型 可以直接写出 for(int n:arr){}; //这个例子是使用只读的方式遍历容器的,如果需要在遍历的时候修改,则需要使用引用 for(auto &n:arr){};
基于范围的for循环使用细节
//对map进行遍历 std::map<std::string, int> mm = { {"1",1},{"2",2},{"3",3} }; for (auto &val : mm) { std::cout << val.first << "->" << val.second << std::endl; }
注意点:
- for循环中val的类型是std::piar。因此,对map这种关联性容器而言,需要使用val.first或val.second来提取键值。
- auto自动推导出的类型是容器中的value_type,而不是迭代器。
基于范围的for循环对容器的访问频率
std::vector<int> arr={1,2,3,4}; std::vector<int>& get_range(void) { std::cout<<"get_range->"<<std::endl; return arr; } int main(void) { for(auto val:get_range()) { std::cout<<val<<std::endl; } return 0; } //输出结果: //get_range-> //1 //2 //3 //4
从上面可以看出:
- 不论基于范围的for循环迭代多少次,get_range只在第一次迭代之前被调用
- 对于基于范围的for循环而言,冒号后面的表达式 只会被执行一次
- 通普通的for循环一样,如果 在迭代的过程中修改容器可能会引起迭代器失效