往期精彩:
- Modern C++ 学习笔记——易用性改进篇
- Modern C++ 学习笔记 —— 右值、移动篇
- Modern C++ 学习笔记 —— 智能指针篇
- Modern C++ 学习笔记 —— lambda表达式篇
- Modern C++ 学习笔记 —— C++面向对象编程
- Modern C++ 学习笔记 —— C++函数式编程
Modern C++ 学习笔记——易用性改进篇
关键字:自动类型推导、初始化、字面量
文章目录
自动类型推导
auto
自动类型推导,就是编译器能够更加表达式的类型,自动决定变量的类型,从C++14开始,还有函数的返回类型。但需要说明的是,auto并没有改变C++是静态语言这一事实。使用auto的变量类型仍然是编译时就确定了,只不过编译器能自动帮你填充而已。有了自动类型推导使得如下赘述称为历史
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
// 成为历史
// 循环体
}
for (auto it = v.begin(); it != end(); ++it) {
// 现在可以直接这么写,当然,是不使用基于范围的for循环的情况
// 循环体
}
不使用自动类型推导时,若容器类型未知,还需要加上typename.
template <typename T>
void foo(const T& container) // 此处const引用还要求const_iterator作为迭代器的类型
{
for (typename T::const_iterator it = container.begin(); it != container.end(); ++it) {
// 循环体
}
}
此外,如果begin返回的类型不是该类型的const_iterator嵌套类型的话,那实际上不用自动类型推断就没法表达了。举个例子,若我们的遍历函数还要求支持C数组的话,不使用自动类型推断就只能在上述代码增加一个对应的重载函数:
template <typename T, size_t N>
void foo(const T (&a)[N]) // 此处const引用还要求const_iterator作为迭代器的类型
{
typedef const T* ptr;
for (ptr it = container.begin(); it != a + N; ++it) {
// 循环体
}
}
如果使用自动类型推导,并且再加上C++11提供的begin和end函数,上面的代码就以统一了:
template <typename T>
void foo(const T& c)
{
using std::begin;
using std::end; // 使用依赖参数查找(ADL)见[1]
for (auto it = begin(c); it != end(c); ++it) {
// 循环体
}
}
从这个例子来看,自动类型推导不仅降低了代码的啰嗦程度,也提高了代码的抽象性。
你以为auto带来只是有这些可就错了,它带来的好处远远不止如此:
- 用auto声明的变量,其型别都推导自其初始化物,所以他们必须初始化:
int x1; // 存在潜在的未初始化风险
auto x2; // 编译错误!必须有初始化物
auto x3 = 0; // 没问题
- auto可以避免一类称为“型别捷径”的问题,其中潜在存在性能问题,没错就是性能问题。不太相信?那就继续往下看:
std::unordered_map<std::string, int> m;
...
for (const std::pair<std::string, int> &p : m) {
// 在p上实施某些操作
}
似乎,这块代码看起来合情合理。单其中暗藏隐患。原因是std::unordered_map
的键值部分是const,所以哈希表中的std::pair(也就是std::unordered_map本身)的型别并不是std::pair(std::string, int),而是std::pair(const std::string, int).
可是在上面的循环中用以声明变量p的类型并不是这个。结果编译器为了将std::pair对象转换为std::pair对象,将m中的每个对象都做了一次复制操作,形成一个p想要绑定的临时对象。循环的每次迭代结束时,该临时对象都会被析构一次。
使用auto就可以轻松化解:
unordered_map<std::string, int> m{
{
"hello", 12}};
cout << &(*(m.begin())) << endl;