C++11 - 1
1.统一的初始化列表
2.变量类型推导
3.范围for
4.final和override
5.STL中的新增容器
6.可变参数模板
1.统一的初始化列表
1.1{}初始化
在C++98中, 标准允许使用{}对数组或结构体元素进行统一的列表初始值设定.如下:
struct Point { int _x; int _y; }; void test() { int arr[] = { 1, 2, 3 }; Point p = { 1, 2 }; }
C++11扩大初始化列表的使用范围,使其可用于所有的内置类型和用户自定义的类型,
使用初始化列表时,可添加等号(=),也可不添加
// C++11中列表初始化也可以适用于new表达式中 int* p = new int[4] { 0 }; // 创建对象时也可以使用列表初始化方式调用构造函数初始化 class Date { private: int _year; int _month; int _day; public: Date(int year, int month, int day) : _year(year), _month(month), _day(day) {} } void test() { // old style Date d1(2002, 8, 1); // new Date d2{2002, 8, 1}; Date d3 = {2002, 8, 1}; }
1.2 std::initializer_list
使用场景1: 用于对象的初始化
std::vector<int> v = { 1, 2, 3, 4 }; std::list<int> lt = { 2, 1, 3 }; v = { 4, 5, 6}; // 需要实现initializer_list版本的operator=
使用场景2: 用于函数参数的初始化
void _test(std::initializer_list<int> il) {} void test { _test({ 1, 2, 3 }); }
使用场景3: 重载operator[]
创建了一个类
A
,其中重载了下标运算符,赋值运算符。实现了一个可以通过下标运算符[]
来对数组内的值进行批量初始化。
- 重载下标运算符的目的是将需要初始化的下表进行一个记录,以便后续的初始化工作
- 重载赋值运算符的目的是将之前记录的所有需要初始化的索引进行一个循环初始化工作。
class A { private: std::vector<int> v; std::vector<int> index; //辅助数组, 用于记录index public: // 用于记录需要初始化的下标 A& operator[](std::initializer_list<int> il) { for(auto iter = il.begin(); iter != il.end(); ++iter) { index.push_back(*iter); } return *this; } //将index保存中的下标进行统一初始化 A& operator=(int x) { if(false == index.empty) { for(auto it = index.begin(); it != index.end(); ++it) { if(v.size() < *it) v.resize(*it); v[*it - 1] = x; } index.clear(); return *this; } } void Print() { for(auto e : v) { std::cout << e << " "; } std::cout << std::endl; } }; void test() { A a; a[{ 1, 3, 5 }] = 1; a.Print(); a[{ 2, 4, 6}] = 2; a.print(); }
可以看到我们自定义批量初始化仅需要一行即可完成
a[{1, 3, 5}] = 1;
。可见初始化的灵活性、强大性。
2.变量类型推导
2.1 auto
C++11中废弃auto原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
auto 仅仅是一个占位符,在编译器期间它会被真正的类型所替代
int a = 0; auto i = a; std::cout << typeid(i).name() << std::endl; // int
auto 除了可以独立使用,还可以和某些具体类型混合使用
int x = 0; auto p1 = &x; // auto 推导为 int* auto* p2 = &x; // auto* = int* auto推到为int auto& r1 = x; // auto推导为int auto r2 = r1; // r1为int&类型,但auto推导出int. 这表明当=右边的表达式是一个引用类型时, //auto 会把引用抛弃,直接推导出它的原始类型 // auto与const结合 int x = 0; const auto n = x; // int auto f = n; // int 说明当=右边的表达式带有 const 属性时, // auto 不会使用 const 属性,而是直接推导出 non-const 类型 const auto& r1 = x; // int auto& r2 = r1; // r1是const int& r2是const int // 这说明当 const 和引用结合时,auto 的推导将保留表达式的 const 类型 // 总结: // 1.当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性; // 2.当类型为引用时,auto 的推导结果将保留表达式的 const 属性。
auto 的限制
必须显示初始化
不能在函数的参数中使用
在定义函数时只是对参数进行了声明, 指明参数类型,但没有给它赋值,与第一条的显示初始化相违背
不能作用于类的非静态成员变量
不能定义数组
// 错误举例
char url[] = “https://legacy.cplusplus.com/”;
auto str[] = url; // 编译时报错6. **不能作用于模板参数** ```c++ template <class T> class A {}; void test() { A<int> a1; A<auto> a2 = a1; // 错误 }
auto的应用场景
最常见的是定义迭代器
vector<int> v; auto it = v.begin()
用于泛型编程
auto 的另一个应用就是当我们不知道变量是什么类型,或者不希望指明具体类型的时候,比如泛型编程中。如下:
class A { public: static int get() { return 100; } }; class B { public: static const char* get() { return "https://legacy.cplusplus.com/"; } }; template <class T> void func() { auto val = T::get(); std::cout << val << std::endl; } void test() { func<A>(); // 100 func<B>(); // https://legacy.cplusplus.com/ } // 不使用auto的方法 template <class T1, class T2> void func() { T2 val = T1::get(); std::cout << val << std::endl; } void test() { func<A, int>(); func<B, const char*>(); }
2.2 decltype
decltype是C++11新增的一个关键字,和auto的功能一样,用来在编译时期进行自动类型推导。引入decltype是因为auto并不适用于所有的自动类型推导场景,在某些特殊情况下auto用起来很不方便,甚至压根无法使用
auto varName = value; decltype(exp) varName = value;
auto根据 = 右边的value推导出变量的类型, decltype根据exp表达式推导出变量的类型,与value没有关系
auto要求变量必须初始化, decltype不要求,如下
decltype(exp) varName;
注意:exp为一个返回值为void的函数时,其推导结果为void,此时编译器报错
1. decltype的几种形式
int x = 0; decltype(x) y = 1; // y -> int decltype(x + y) z = 0; // z -> int const int& i = x; decltype(i) j = y; // j -> const int & const decltype(z)* p = &z; // p -> const int * decltype(z)* pi = &z; // pi -> int* decltype(pi)* ppi = π // ppi -> int* *
2.推导规则
如果exp是一个不被括号()包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,decltype(exp)的类型和exp一致
如果exp是函数调用,则decltype(exp)的类型就和函数返回值的类型一致
如果exp是一个左值,或被括号()包围,decltype(exp)的类型就是exp的引用,假设exp的类型为T,则decltype(exp)的类型为T&
// 规则1 class A { public: int age; string name; static int count; } int A::count = 0; void test1() { int x = 0; const int& y = x; A a; decltype(x) m = 0; // int decltype(y) n = x; // const int & decltype(A::count) l = 0; // int decltype(a.name) str; // string } // 规则2 int& func1(int ,char);//返回值为int& int&& func2(void);//返回值为int&& int func3(double);//返回值为int const int& func4(int,int,int);//返回值为const int& const int&& func5(void);//返回值为const int&& int n=50; decltype(func1(100,'A')) a=n;//a的类型为int& decltype(func2()) b=0;//b的类型为int&& decltype(func3(10.5)) c=0;//c的类型为int decltype(func4(1,2,3)) x=n;//x的类型为const int& decltype(func5()) y=0;//y的类型为const int&& //注意:exp中调用函数时需要带上括号和参数,但这仅仅是形式,并不会真的去执行函数代码 // 规则3 class A { public: int x; } void test3() { A a; decltype(a.x) m = 0; // int decltype((a.x)) n = m; // int& int x = 0, y = 0; decltype(m + n) e = 0; // m+n返回右值, 推导为int decltype(m = m + n) f = e; // m=m+n返回左值, 推导为int& }
decltype的应用
template <class T> class A { private: decltype(T.begin()) _it; // typename T::iterator _it; public: void func(T& container) { _it = container.begin(); } }; void test() { const vector<int> v; A<const vector<int>> obj; obj.func(v); } // typename T::iterator _it; 为什么编译器报错 // T::iterator并不能包括所有的迭代器类型,当T是一个const容器时,应用使用const_iterator
2.3 nullptr
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
由上述代码可知: NULL是一个宏
C语言中对NULL定义为 #define NULL ((void)0)*
C++中对NULL定义为 #define NULL 0
不论采用何种定义,在使用空值的指针时, 都不可避免会遇到一些问题, 如下
void func(int)
{
std::cout << "func(int)" << std::endl;
}
void func(int*)
{
std::cout << "func(int*)" << std::Endl;
}
void test()
{
func(0); // func(int)
func(NULL); // func(int)
func((int*)NULL); // func(int*)
func(nullptr); // func(int*)
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的 初衷相悖.
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0
nullptr无法隐式转换为整型,而可以隐式匹配指针类型
在C++11中,sizeof(nullptr) 与 sizeof((void)0)所占的字节数相同。*
3. 范围for
vector<int> v; // 迭代器遍历 auto iter = v.begin(); while(iter != v.end()) { std::cout << *iter << std::endl; ++iter; } // 范围for for(auto e : v) { std::cout << e << std::endl; }
只要可以使用第一种的迭代器遍历就能使用范围for, 因为范围for的底层实现就是使用迭代器遍历
使用条件:
- for循环迭代的范围必须是确定的
- 迭代的对象要实现++和==的操作
4. final和override关键字
4.1 final
作用:
- 修饰虚函数,表示该虚函数不能再被重写
- 修饰类, 表示该类不能作为父类, 也就是不能被继承
// 修饰虚函数 class Father { public: virtual void func() { std::cout << "I am a father" << std::endl; } }; class Son : public Father { public: virtual void func() final // 子类重写虚函数, 报错 { std::cout << "I am a son" << std::endl; } }; // 修饰类 class Fatner final {}; class Son : public Father // 父类不能被继承, 报错 {};
4.2 override
作用: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class Father { public: virtual void func() { std::cout << "I am a father" << std::endl; } }; class Son : public father { publlic: virtual void func() override // 不重写, 编译报错 { std::cout << "I am a son" << std::endl; } };
5.STL中的新增容器
-
std::unordered_set
-
std::unordered_map
-
std::array
-
std::forward_list
6.可变参数模板
在c++11之前,类模板和函数模板只能含有固定数量的模板参数,c++11增加了可变模板参数特性:允许模板定义中包含0到任意个模板参数. 声明可变参数模板时,需要在typename或class后面加上省略号"…"
省略号作用:
- 声明一个参数包, 这个参数中可以包含0到任意个模板参数
- 在模板定义的右边, 可以将参数包展开成一个一个独立的参数
1. 可变参数模板函数
template<class ...Args> void Show(Args ...args) { cout << sizeof ...(args) << endl; }
参数包可以包含0个或者多个参数,如果需要用参数包中的参数,则一定要将参数包展开
展开参数包
- 递归函数方式展开参数包
// 递归终止函数 void Show() { cout << endl; } // 展开函数 template<class T, class ...Args> void Show(const T& val, Args ...args) { cout << val << " "; Show(args...); } void test() { Show(1, 2, 3, 4); }
- 初始化列表方式展开参数包
// 逗号表达式 template<class T> void print_args(T t) { cout << t << " "; } template<class ...Args> void Show(Args ...args) { int a[] = { (print_args(args), 0)... }; cout << endl; } // 升级版 template<class T> int print_args(T t) { cout << t << " "; return 0; } template<class ...Args> void Show(Args ..args) { int a[] = { print_args(args)... }; cout << endl; }
2. 可变参数模板类
待学习