1.关于C++11的介绍
- C++11标准为C++编程语言的第三个官方标准,正式名叫ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++ 。 在正式标准发布前,原名C++0x。它将取代C++标准第二版ISO/IEC 14882:2003 - Programming languages -- C++ 成为C++语言新标准。
- C++11包含了核心语言的新机能,并且拓展C++标准程序库,并且加入了大部分的C++ Technical Report 1程序库(数学上的特殊函数除外)。C++ 标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月3召开的标准会议完成国际标准的最终草案。最终于2011年8月12日公布,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案(编辑上的修正)。此次标准为13年第一次重大修正。
- ISO将在2014年和2017年发布C++的后续版本 。
具体修改:
- 1.对C++核心语言的扩充
- 2.核心语言运行期的强化(右值引用和 move 语义;泛化的常数表达式;对POD定义的修正)
- 3.核心语言建构期表现的加强(外部模板)
- 4.核心语言使用性的加强(初始化列表;统一的初始化;类型推导[auto关键字];以范围为基础的 for 循环;Lambda函数与表示法;另一种的函数语法;对象构建的改良;显式虚函数重载;空指针;强类型枚举;角括号;显式类型转换;模板的别名;无限制的unions)
- 5.核心语言能力的提升(变长参数模板;新的字符串字面值;用户自定义的字面值;多任务存储器模型;thread-local的存储期限;使用或禁用对象的默认函数;long long int 类型;静态assertion;允许sizeof运算符作用在类型的数据成员上,无需明确的对象;)
- 6.C++标准程序库的变更(标准库组件的升级;线程支持;多元组类型;散列表;正则表达式;通用智能指针;可扩展的随机数功能;包装引用;多态函数对象包装器;用于元编程的类型属性;用于计算函数对象返回类型的统一方法)。
2.统一的列表初始化
{}初始化
- 在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。
struct Func { int _x; int _y; }; int main() { int arr1[] = { 1,2,3,4,5,6 }; int arr2[4] = { 0 }; //0 0 0 0 Func f = { 1,2 }; }
- C++11扩大了用大括号括起来的列表
{初始化列表}
的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号,也可不添加。//C++11 struct Func2 { int _x; int _y; }; int main() { //1.使用{}对数组元素进行初始化 int arr1[]{ 1,2,3,4,5,6 }; //int arr1[]={1,2,3,4,5,6}; int arr2[4]{ 0 }; //0 0 0 0 //2.使用{}对结构体元素初始化 Func2 f{ 1,2 }; //不加等号 //3.使用{}对内置类型初始化 int x1 = 10; //一般就用这个 int x2 = { 10 }; int x3{ 10 }; //4.C++11中列表初始化也可以用于new表达式中(C++98无法初始化) int* p1 = new int[4]{ 0 }; //0 0 0 0,不加等号 int* p2 = new int[4]{ 1,2,3,4 }; //不加等号 // int* p3 =new int(0); return 0; }
小细节:在对new表达式初始化时不需要加=,我们以前讲new和delete那节时就没有加=。
- 创建对象时也可以使用列表初始化方式调用构造函数初始化。
class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int year, int month, int day)" << endl; } private: int _year; int _month; int _day; }; int main() { //一般调用构造函数创建对象的方式 Date d1(2022, 12, 12); //C++11支持的列表初始化,这里也会调用构造函数初始化 Date d2 = { 2022, 11, 11 }; //可添加等号 Date d3{ 2022, 10, 10 }; //可不添加等号 return 0; }
3.initializer_list容器
提起容器相比各位都不太陌生了。
在C++11中又提出了一个新容器initializer_list
初始值设定项列表
template<class T> class initializer_list; //初始值设定项列表
此类型用于访问 C++ 初始化列表中的值,该列表是 类型的元素列表。
这种类型的对象由编译器根据初始化列表声明自动构造,初始化列表声明是括在大括号中的逗号分隔元素的列表:
const T
auto il = { 10, 20, 30 };
// initializer_list
3.1 三大成员函数
nitializer_list本质就是一个大括号括起来的列表,如果用auto关键字定义一个变量来接收一个大括号括起来的列表,然后以
typeid(变量名).name()
的方式查看该变量的类型,此时会发现该变量的类型就是initializer_list。接口函数的应用:
double test_initializer_list2() { initializer_list<double> ilt = { 1.2, 4.3, 6.4 }; initializer_list<double>::iterator it = ilt.begin(); while (it != ilt.end()) { cout << *it << " ";//1.2, 4.3, 6.4 it++; } cout << endl; cout << ilt.size()<< endl; for (auto e : ilt) { cout << e << " ";//1.2, 4.3, 6.4 } return 0; }
3.2initializer_list的应用场景
initializer_list容器没有提供对应的增删查改等接口,因为initializer_list并不是专门用于存储数据的,而是为了让其他容器支持列表初始化的。
class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int year, int month, int day)" << endl; } private: int _year; int _month; int _day; }; int main() { //vector // 使用大括号对容器赋值,{}调用构造函数构造一个vector对象,再赋值 vector<int> v1 = { 1, 2 ,3 ,4, 5 }; vector<int> v2{ 1, 2, 3, 4, 5 }; vector<Date> v3 = { { 2022, 1, 1 }, { 2022, 1, 2 }, { 2022, 1, 3 } }; //list list<int> lt1{ 1, 2, 3 }; //set set<int> s1{ 3, 4, 5, 6, 3 }; //map // 这里{"sort", "排序"}会先初始化构造一个pair对象 map<string, string> dict = { {"string", "字符串" }, {"sort", "排序" } }; return 0; }
- C++98并不支持直接用列表对容器进行初始化,这种初始化方式是在C++11引入initializer_list后才支持的。
- 而这些容器之所以支持使用列表进行初始化,根本原因是因为C++11给这些容器都增加了一个构造函数,这个构造函数就是以initializer_list作为参数的。
cplusplus官网搜vector::vector
这个新增的构造函数要做的就是遍历initializer_list中的元素,然后将这些元素依次插入到要初始化的容器当中。
我们前面模拟实现过vector迭代器,如果也想让他能够使用列表初始化,我们可以用initializer_list模拟一下:
namespace bit { template<class T> class vector { public: typedef T* iterator; vector(initializer_list<T> il) { _start = new T[il.size()]; _finish = _start; _endofstorage = _start + il.size(); //迭代器遍历 //typename initializer_list<T>::iterator it = il.begin(); //while (it != il.end()) //{ // push_back(*it); // it++; //} //范围for遍历 for (auto e : il) { push_back(e); } } vector<T>& operator=(initializer_list<T> il) { vector<T> tmp(il); std::swap(_start, tmp._start); std::swap(_finish, tmp._finish); std::swap(_endofstorage, tmp._endofstorage); return *this; } private: iterator _start; iterator _finish; iterator _endofstorage; }; }
这个模拟实现权当了解一下就可以了。
4.声明
4.1auto
auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。(原来我们在写范围for时就经常用到它)。
int main() { int i = 10; auto p = &i; auto pf = strcpy; //输出p、pf的类型 cout << typeid(p).name() << endl; cout << typeid(pf).name() << endl; map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} }; //map<string, string>::iterator it = dict.begin(); auto it = dict.begin(); //迭代器 return 0; }
但是虽然简短代码但是他关键字decltype可以将变量的类型声明为表达式指定的类型。也有一个致命缺陷就是不能一眼看出类型了,如果原先不知道it的类型是迭代器,那基本不能一眼识别出it的类型。
4.2decltype
关键字decltype可以将变量的类型声明为表达式指定的类型。
- typeid().name;decltype;auto三者的区别:
- typeid拿到的只是类型的字符串,不能再用它去定义一个变量
- decltype:将变量的类型声明为表达式指定的类型,可以用其去定义变量。
- auto必须要求显式初始化,而decltype没要求。
int main() { int x = 10; double y = 1.2; cout<<"typeid(x).name():"<<typeid(x).name() << endl; //typeid(x).name() n= 20; 不能这样用 decltype(x) z = 2.4; // int 2 auto m=5.5; // double 5.5 cout << "decltype:" << z << endl; cout << "auto:"<<m << endl; }
decltype:的应用场景:
// decltype的一些使用使用场景 template<class T1, class T2> void F(T1 t1, T2 t2) { decltype(t1 * t2) ret = t1 * t2; //这个的意思是ret的类型是t1*t2 //decltype(t1 * t2) ret ; vector<decltype(t1* t2)> v; v.push_back(ret); cout <<"typeid(ret).name()" <<typeid(ret).name() << endl; } int main() { const int x = 1; double y = 2.2; decltype(x * y) ret; decltype(&x) p; cout << typeid(ret).name() << endl; //double cout << typeid(p).name() << endl; //int const * F(1, 'a'); //int F(1, 2.2); //double return 0; }
4.3nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endi
其实NULL应该定义成第三个NULL ((void*)0)但是以前的版本都是定义成NULL 0存在安全隐患所以专门补了一个nullptr。
就比如下面这个例子:
void f(int arg) { cout << "void f(int arg)" << endl; } void f(int* arg) { cout << "void f(int* arg)" << endl; } int main() { f(0); //void f(int arg) f(NULL); //void f(int arg) f(nullptr); //void f(int* arg) return 0; }
- NULL和nullptr的含义都是空指针,所以这里调用函数时肯定希望匹配到的都是参数类型为
int*
的重载函数,但最终却因为NULL本质是字面量0,而导致NULL匹配到了参数为int
类型的重载函数。- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
4.4范围for
范围for我们以前就应用过很多了,接下来我们就再简单介绍一下:
C++11提供了一个特殊版本的 for 循环,在很多情况下,它都可以简化数组的处理,这就是基于范围的 for 循环。在使用基于范围的 for 循环处理数组时,该循环可以自动为数组中的每个元素迭代一次,这就是范围for。
int main()
{ //遍历数组的for循环
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, };
for (int i = 0; i < 10; i++)
cout << arr[i] << " "; //1 2 3 4 5 6 7 8 0 0
cout << endl;
//而遍历容器类的For
std::vector<int> vec{ 1,2,3,4,5,6,7,8, };
for (std::vector<int>::iterator itr = vec.begin(); itr != vec.end(); itr++)
std::cout << *itr<<" "; //1 2 3 4 5 6 7 8
std::cout << endl;
}
不管上面哪一种方法,都必须明确的确定for循环开头以及结尾条件,而熟悉C#或者python的人都知道在C#和python中存在一种for的使用方法不需要明确给出容器的开始和结束条件,就可以遍历整个容器,幸运的是C++11中引入了这种方法也就是基于范围的For(Range-Based-For),用基于范围的For 改写上面两个例子:
std::vector<int> vec{ 1,2,3,4,5,6,7,8,9}; for (auto n : vec) std::cout << n<<" "; //1 2 3 4 5 6 7 8 9 std::cout << endl; int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8}; 1 2 3 4 5 6 7 8 0 0 for (auto n : arr) std::cout << n << " "; std::cout << endl;
可以看到改写后的使用方法简单了很多,代码的可读性提升了一个档次,但是需要注意的在上述对容器的遍历是只读的,也就是说遍历的值是不可修改的。
std::vector<int> vec{ 1,2,3,4,5,6,7,8,9,10 };
cout << "修改前" << endl;
for (auto n : vec)
std::cout << n++<<" "; //1 2 3 4 5 6 7 8 9 10
cout << endl;
cout << "修改后" << endl;
for (auto j : vec)
std::cout << j<<" "; //1 2 3 4 5 6 7 8 9 10
std::cout << endl;
我们遍历后对容器内元素的加1操作并没有生效,修改后的输出仍然和原来的元素值一样,因为这种遍历方法做的是值拷贝。那么如果要遍历容器内的元素的同时又要修改元素的值该怎么做呢,方法就是将遍历的变量声明为引用类型,看下面例子:
std::vector<int> vec{ 1,2,3,4,5,6,7,8,9,10 };
cout << "修改前" << endl;
for (auto& n : vec) //引用类型
std::cout << n++<<" "; //1,2,3,4,5,6,7,8,9,10
cout << endl;
cout << "修改后" << endl;
for (auto j : vec)
std::cout << j<<" "; //2,3,4,5,6,7,8,9,10,11
cout << endl;
可以看到,容器内的元素每个都加了1,两者的区别仅为在修改的时候,将声明的遍历遍历n从auto 声明为auto &,使用基于范围的For循环一定要注意这一点, 基于范围for循环的遍历是只读的遍历,除非将变量变量的类型声明为引用类型。
4.5 STL中的一些改变
C++11中新增了四个容器,分别是array、forward_list、unordered_map和unordered_set。
array
- array容器本质就是一个静态数组,即固定大小的数组。
- array容器有两个模板参数,第一个模板参数代表的是存储的类型,第二个模板参数是一个非类型模板参数,代表的是数组中可存储元素的个数。
int main() { std::array<int, 10> a1; //定义一个可存储10个int类型元素的array容器 std::array<double, 5> a2; //定义一个可存储5个double类型元素的array容器 return 0; }
关于awary的补充:
- C语言数组越界检查中,越界读基本检查不出来,越界写是抽查,不一定能检查出来。
- awary容器越界读写都能被检查出来。
- 但是在实际情况中awary用的很少,一方面是数组用惯了,很难改变,另一方面是用awary不如用vector+resize来替代C语言中的数组。
- array容器的对象是建立在栈区的,不适合定义大数组。
forward_list容器
这个容器本质就是一个单链表,很鸡肋,一般情况下我们都用list,这里就不在细讲了。
后面的关于map和set的在前面已经说过了就不讲了。