一、C++11简介
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了
C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞
进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。
从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于
C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中
约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言
相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不
仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。
二、列表初始化
{}的初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
int a[] = { 1,2,3,4,5,6 };//定义一个含有1 2 3 4 5 6 的数组vector<int> a1(10, 1);//构造一个含有10个1的数组vector<int> a2 = { 1,2,3,4,5,6 };//构造一个 1 2 3 4 5 6 的数组
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
//结构体Point
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;
}
当我们创建对象时也可以使用上述的方法:
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, 1, 1); // old style// C++11支持的列表初始化,这里会调用构造函数初始化Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };return 0;
}
std::initializer_list
initializer_list是为了让上面的列表初始化能落地而实现的,例如下述代码:
vector<int> a1 = { 10, 20, 30 };//构造一个 1 2 3 4 5 6 的数组
上述代码中的{ 1,2,3,4,5,6 }就是一个initializer_list类型,我们可以在编译器中打印出它们的类型:
int main()
{// the type of il is an initializer_list auto a1 = { 10, 20, 30 };cout << typeid(a1).name() << endl;//class std::initializer_list<int>return 0;
}
可以看到auto类型自动将a1识别成了initializer_list。
std:initializer_list一般是作为构造函数的参数,C++11对STL中的许多容器都增加了std:initializer_list作为参数的构造函数,这样对初始化容器对象就更方便了,也可以作为operator=的参数,这样就可以用大括号赋值。
总结一下就是:initializer_list是为了让容器能够支持列表初始化而产生的。
我们之前模拟过vector的模拟实现,如果要让它也支持列表初始化,那么就需要加一个以std:initializer_list为参数的构造函数:
namespace bear
{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;typename initializer_list<T>::iterator lit = l.begin();while (lit != l.end()){*vit++ = *lit++;}//范围for遍历//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;};
}
说明一下:
在构造函数中遍历initializer_list时可以使用迭代器遍历,也可以使用范围for遍历,因为范围for底层实际采用的就是迭代器方式遍历。
使用迭代器方式遍历时,需要在迭代器类型前面加上typename关键字,指明这是一个类型名字。因为这个迭代器类型定义在一个类模板中,在该类模板未被实例化之前编译器是无法识别这个类型的。
最好也增加一个以initializer_list作为参数的赋值运算符重载函数,以支持直接用列表对容器对象进行赋值,但实际也可以不增加。
三、变量类型推导及声明
在C++11中,提供了多种简化声明的方式,特别是在使用模板时:
auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以auto就没什么价值了。
C++11中废弃auto原来的用法,将
其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
例如下列情景:
int main()
{int i = 10;auto p = &i;auto pf = strcpy;cout << typeid(p).name() << endl; //int *cout << typeid(pf).name() << endl; //char * (__cdecl*)(char *,char const *)map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };//map<string, string>::iterator it = dict.begin();auto it = dict.begin(); //简化代码return 0;
}
如果我们不用auto来代替it的类型,那么我们就会写很长的一段类型,非常的复杂。
decltype
关键字decltype可以将变量的类型声明为表达式指定的类型。例如:
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1*t2) ret;cout << 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; //doublecout << typeid(p).name() << endl; //int const *F(1, 'a'); //intF(1, 2.2); //doublereturn 0;
}
该方法也可以用来推演函数返回值的类型。
nullptr
由于C++中NULL被定义成字面量0,这样就可能会带来一些问题,因为0既能表示指针常量,又能表示整型常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
在大部分情况下使用NULL不会存在什么问题,但是在某些极端场景下就可能会导致匹配错误。例如:
void f(int arg)
{cout << "void f(int arg)" << endl;
}
void f(int* arg)
{cout << "void f(int* arg)" << endl;
}
int main()
{f(NULL); //void f(int arg)f(nullptr); //void f(int* arg)return 0;
}
NULL和nullptr的含义都是空指针,所以这里调用函数时肯定希望匹配到的都是参数类型为int*
的重载函数,但最终却因为NULL本质是字面量0,而导致NULL匹配到了参数为int
类型的重载函数,因此在C++中一般推荐使用nullptr。
四、范围for循环
这个大家应该都比较了解了:
int main()
{int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//将数组元素值全部乘以2for (auto& e : arr){e *= 2;}//打印数组中的所有元素for (auto e : arr){cout << e << " ";}cout << endl;return 0;
}
使用条件
一、for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
二、迭代的对象要支持++和==操作
范围for本质上是由迭代器支持的,在代码编译的时候,编译器会自动将范围for替换为迭代器的形式。而由于在使用迭代器遍历时需要对对象进行++和==操作,因此使用范围for的对象也需要支持++和==操作。