目录
前言:这里都是C++11的一些相对比较零碎或者简单的知识点,而一些重要的知识点(比如右值引用、lambda表达式、智能指针等之后再介绍)
C++11第2篇:C++11重要知识点介绍_糖果雨滴a的博客-CSDN博客
在C++98中,可以用{}对数组或者结构体元素进行统一的列表初始值设定比如:
- struct Point
- {
- int _x;
- int _y;
- };
-
- int main()
- {
- Point p = { 1, 2 };
- int array1[]{ 1, 2, 3, 4, 5 };
- int array2[5]{ 0 }
-
- return 0;
- }
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
- struct Point
- {
- int _x;
- int _y;
- };
-
- int main()
- {
- int x1 = 1;
- int x2{ 2 };
- int array1[]{ 1, 2, 3, 4, 5 };
- int array2[5]{ 0 };
- Point p{ 1, 2 };
- // C++11中列表初始化也可以适用于new表达式中
- int* pa = new int[4]{ 0 };
-
- return 0;
- }
而C++11支持这一特性,最主要的是可以应用在各种容器中,例如:
- int main()
- {
- vector<int> v = { 1,2,3,4 };
- list<int> lt = { 1,2 };
-
- // 这里{}会先初始化构造一个pair对象
- map
dict = { {"erase", "删除"}, {"insert", "插入"} }; -
- // 使用大括号对容器赋值
- v = {10, 20, 30};
-
- return 0;
- }
这个就是容器可以使用{}进行赋值的C++11中新增加的{}的类型。
这个一般是作为构造函数的参数,C++11对STL中的不少容器都增加了intializer_list作为参数的构造函数,这样就初始化容器对象更方便了,也可以作为operator=(赋值运算符重载)的参数。
让自己模拟实现的vector也支持{}初始化和赋值:
- namespace hb
- {
- template<class T>
- class vector {
- public:
- typedef T* iterator;
-
- vector(initializer_list
l) - {
- _start = new T[l.size()];
- _finish = _start + l.size();
- _endofstorage = _start + l.size();
-
- iterator vit = _start;
-
- for (auto e : l)
- *vit++ = e;
- }
-
- vector
& operator=(initializer_list l) { - vector
tmp(l); - std::swap(_start, tmp._start);
- std::swap(_finish, tmp._finish);
- std::swap(_endofstorage, tmp._endofstorage);
-
- return *this;
- }
- private:
- iterator _start;
- iterator _finish;
- iterator _endofstorage;
- };
- }
用于实现自动类型推断。要求必须进行显示初始化,让编译器将定义对象的类型设置位初始化值的类型。
- int a = 10;
- auto p = &a;
可以将变量的类型声明为表达式指定的类型。
- int x = 1;
- double y = 2.2;
-
- decltype(x * y) ret; // ret的类型就是double
NULL被定义成的是字面量0,这样可能会带来一些问题,因为0既能表示指针常量,又能表示整型常量。因此为了更清晰和安全的角度考虑,C++11新增了nullptr,用来表示空指针。
范围for是一种遍历的方式,并且代码很简洁,底层是基于STL的迭代器。只要那个容器支持迭代器,就一定支持范围for。
- int main()
- {
- vector<int> v{ 1, 2, 3, 4, 5 };
- for(auto e : v)
- {
- cout << e << ' ';
- }
-
- return 0;
- }
这两个基于哈希表实现的容器是C++11中新出的两个很有用处的容器,具体使用已经在前两个博客中介绍了。
C++ 哈希表及unordered_set + unordered_map容器_糖果雨滴a的博客-CSDN博客
这个新增容器用处不是很大,就是一个静态数组。不过相对于正常创建的静态数组,使用这个容器访问会更加安全,一旦越界就会报错。不过vector同样越界会报错,因此我们一般使用的都是vector,很少会用array。
这个新增容器也是如此,用处并不大。forward_list就是单链表,而list是双链表,forward_list只不过比list要少存储一个指针,能减少一点存储占用。
- // Args是一个模板参数包,args是一个函数形参参数包
- // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
- template <class ...Args>
- void ShowList(Args... args)
- {}
上面的参数args前面有省略号,所以它就是一个可变模板参数,我们把带省略号的参数称为”参数包“,它里面包含了0到N个模板参数。
我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模板参数的一个主要特点。因此如何展开可变模板参数是最大的难点。
语法是不支持使用args[i]这样方式获取可变参数的。
因此我们有这样几种方法:
- // 递归终止函数
- template <class T>
- void ShowList(const T& t)
- {
- cout << t << endl;
- }
-
- // 展开函数
- template <class T, class ...Args>
- void ShowList(T value, Args... args)
- {
- cout << value << " ";
- ShowList(args...);
- }
-
- int main()
- {
- ShowList(1);
- ShowList(1, 'A');
- ShowList(1, 'A', std::string("sort"));
-
- return 0;
- }
这个就是通过递归的方式,每次让参数包中的第一个类型进入模板T中,然后剩下的参数进入参数包中,以进行下一次递归,而进入模板T中的就可以得到了。而如果只剩下一个参数时,这时这个参数会进入T中,而参数包就变成了0,如果不去实现上面的那个只有T的函数,就会导致报错,因为参数包中已经没有参数了,无法进行递归了。
- void ShowList()
- {}
上面那个函数也可以写成这样,这时当参数包为0时再次递归就会进入到这个函数,也可以结束。
- template <class T>
- void PrintArg(T t)
- {
- cout << t << " ";
- }
-
- //展开函数
- template <class ...Args>
- void ShowList(Args... args)
- {
- int arr[] = { (PrintArg(args), 0)... };
- cout << endl;
- }
-
- int main()
- {
- ShowList(1);
- ShowList(1, 'A');
- ShowList(1, 'A', std::string("sort"));
-
- return 0;
- }
这种方式是通过PrintArg函数,先进入到函数中,得到每一个参数包中的值,然后因为我们不知道参数包中的类型是什么,所以我们通过逗号表达式的方法,将每一个数都变成0,然后存入arr中,这样就不会因为不知道类型而直接将值存入数组导致报错。
也可以写成这样:
- template <class T>
- void PrintArg(T t)
- {
- cout << t << " ";
-
- return 0;
- }
-
- //展开函数
- template <class ...Args>
- void ShowList(Args... args)
- {
- int arr[] = { PrintArg(args)... };
- cout << endl;
- }
-
- int main()
- {
- ShowList(1);
- ShowList(1, 'A');
- ShowList(1, 'A', std::string("sort"));
-
- return 0;
- }
这里可以不用逗号表达式,而是让上面的函数直接返回0。
- template <class... Args>
- void emplace_back (Args&&... args);
因为有了参数包和万能引用(万能引用在接下来的右值引用中介绍)等,就有了emplace的出现。而相对于原本的push_back,insert等,emplace的接口又有什么优势呢?
emplace系列的接口对于右值,是直接构造,而push_back则需要先构造,再进行移动构造。
emplace的效率要稍微高那么一点,不过并不是很大。而用法上几乎一样。
- int main()
- {
- std::list< std::pair<int, bit::string> > mylist;
- mylist.emplace_back(10, "sort");
- mylist.emplace_back(make_pair(20, "sort"));
- mylist.push_back(make_pair(30, "sort"));
- mylist.push_back({ 40, "sort"});
-
- return 0;
- }