目录
前言:这里都是C++11的一些相对比较零碎或者简单的知识点,而一些重要的知识点(比如右值引用、lambda表达式、智能指针等之后再介绍)
C++11第2篇:C++11重要知识点介绍_糖果雨滴a的博客-CSDN博客
一.列表初始化
1.{}初始化
在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<string, string> dict = { {"erase", "删除"}, {"insert", "插入"} };
// 使用大括号对容器赋值
v = {10, 20, 30};
return 0;
}
2.initializer_list
这个就是容器可以使用{}进行赋值的C++11中新增加的{}的类型。
这个一般是作为构造函数的参数,C++11对STL中的不少容器都增加了intializer_list作为参数的构造函数,这样就初始化容器对象更方便了,也可以作为operator=(赋值运算符重载)的参数。
让自己模拟实现的vector也支持{}初始化和赋值:
namespace hb
{
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
for (auto e : l)
*vit++ = e;
}
vector<T>& operator=(initializer_list<T> l) {
vector<T> 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;
};
}
二.变量类似推导(声明)
1.auto
用于实现自动类型推断。要求必须进行显示初始化,让编译器将定义对象的类型设置位初始化值的类型。
int a = 10;
auto p = &a;
2.decltype
可以将变量的类型声明为表达式指定的类型。
int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型就是double
3.nullptr
NULL被定义成的是字面量0,这样可能会带来一些问题,因为0既能表示指针常量,又能表示整型常量。因此为了更清晰和安全的角度考虑,C++11新增了nullptr,用来表示空指针。
三.范围for
范围for是一种遍历的方式,并且代码很简洁,底层是基于STL的迭代器。只要那个容器支持迭代器,就一定支持范围for。
int main()
{
vector<int> v{ 1, 2, 3, 4, 5 };
for(auto e : v)
{
cout << e << ' ';
}
return 0;
}
四.STL容器新增
1.unordered_set和unordered_map
这两个基于哈希表实现的容器是C++11中新出的两个很有用处的容器,具体使用已经在前两个博客中介绍了。
C++ 哈希表及unordered_set + unordered_map容器_糖果雨滴a的博客-CSDN博客
2.array
这个新增容器用处不是很大,就是一个静态数组。不过相对于正常创建的静态数组,使用这个容器访问会更加安全,一旦越界就会报错。不过vector同样越界会报错,因此我们一般使用的都是vector,很少会用array。
3.forward_list
这个新增容器也是如此,用处并不大。forward_list就是单链表,而list是双链表,forward_list只不过比list要少存储一个指针,能减少一点存储占用。
五.可变参数模板
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数args前面有省略号,所以它就是一个可变模板参数,我们把带省略号的参数称为”参数包“,它里面包含了0到N个模板参数。
我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模板参数的一个主要特点。因此如何展开可变模板参数是最大的难点。
语法是不支持使用args[i]这样方式获取可变参数的。
因此我们有这样几种方法:
1.递归函数方式展开参数包
// 递归终止函数
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时再次递归就会进入到这个函数,也可以结束。
2.逗号表达式展开参数包
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。
3.STL中的emplace
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;
}