C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅
功能更强大,而且能提升程序员的开发效率。
1、列表初始化
在C++98中可以对数组元素进行列表初始化,但是对一些自定义的类型无法用 这样的方法初始化,例如
int arr[]={1,2,3,4,5};
vector<int> v{1,2,3,4,5};//C++98不支持这样初始化,C++11支持
所以C++11提供了统一的语法可以初始化任意对象。
C++11支持单个对象列表初始化,但是多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list 类型参数的构造函数即可。注意:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()。
#include <initializer_list>
template<class T>
class Vector
{
public:
// ...
Vector(initializer_list<T> l): _capacity(l.size()), _size(0)
{
_array = new T[_capacity];
for(auto e : l)
_array[_size++] = e;
}
Vector<T>& operator=(initializer_list<T> l)
{
delete[] _array;
size_t i = 0;
for (auto e : l)
_array[i++] = e;
return *this;
}
// ...
private:
T* _array;
size_t _capacity;
size_t _size;
};
2、变量类型推导
2.1 auto类型推导
在定义变量时需要先给出实际的变量类型,但是有时候不确定变量或变量类型比较复杂,就可以使用auto根据变量初始化表达式类型推导变量的实际类型,这样既简洁又方便。
#include <map>
#include <string>
int main()
{
//1、不确定变量的类型
short a = 32670;
short b = 32670;
short c = a + b;//数据溢出
auto d = a + b;//结果正确
//2、变量类型复杂
std::map<std::string, std::string> m{{"apple", "苹果"}, {"banana","香蕉"}};
// 使用迭代器遍历容器, 迭代器类型太繁琐
//std::map<std::string, std::string>::iterator it = m.begin();
auto it = m.begin();
while(it != m.end())
{
cout<<it->first<<" "<<it->second<<endl;
it++;
}
return 0;
}
auto使用细则
- auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加& - 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
auto使用的前提是:必须对auto声明的类型进行初始化,否则不能推导出auto的实际类型。
所以需要decltype类型推导,decltype是根据表达式的实际类型推演出定义变量时所用的类型。
2.2 decltype类型推导
应用场景:需要根据表达式运行完成之后的结果进行类型推导,这时候不能使用auto,而是需要decltype类型推导。
decltype是根据表达式的实际类型推演出定义变量时所用的类型。
- 推演表达式类型作为变量的定义类型
- 推演函数返回值的类型
3、范围for
先看一个例子来认识一下范围for的语法
int arr[] = {1,2,3,4};
//for循环遍历
int len = sizeof(arr)/sizeof(arr[0]);
for(int i = 0; i < len; i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
//范围for遍历
for(auto &e:arr)
{
cout<<e<<" ";
}
cout<<endl;
有上面的例子可知,范围for的组成:for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。其他用法与正常的for用法相同。
范围for的使用条件
- for循环的迭代范围必须确定
对于数组来说,必须知道第一个元素与最后一个元素的范围;对于类来说,必须知道begin和end. - 迭代的对象要实现++和==的操作
4、final和override
- final
不希望虚函数被继承或重写,在类名和虚函数后面加上final关键字,如果进行重写或继承则会报错。
class TestBase
{
virtual void test() final;//虚函数test不可以被重写
}
class Test final: public TestBase//Test类不能被继承
{
virtual void test();//编译出错,有final关键字表示test函数不能被重写
}
class Test2 : public Test//编译出错,Test不能被继承
{
}
- override
需要被重写的虚函数可以加上override,如果出现错误,在编译的时候会报错;如果没有加上override,在重写虚函数的时候出错不会报错,为了方便建议在需要重写虚函数的时候加上override.
class TestBase
{
virtual void foo();
}
class Test : public TestBase
{
virtual void fo0();//存在函数名出错的情况,此时编译通过
virtual void fo0() override;//此时编译不通过
}
5、智能指针
智能指针的详细内容,请查看我的另一篇博客。
C++智能指针总结
6、新增容器
6.1 array
array与数组类似,保存在栈内存中,相较于保存在堆内存的vector,访问元素更加灵活,所以在访问元素时性能更高。
array 会在编译时会创建一个固定大小的数组,并且array 不能够被隐式的转换成指针,使用array只需指定其类型和大小。
6.2 forward_list
forward_list与list相似,不同的是list是双向链表,而forward_list是单向链表,只能朝前迭代,所以可以以O(1)的时间复杂度进行插入,但是不支持随机存取。
6.3 无序容器
无序容器有unordered_map/unordered_multimap 、unordered_set/unordered_multiset
容器内部不进行排序,底层是通过Hash实现的。
7、 委派构造函数
委派构造函数的目的也是为了减少程序员书写构造函数的时间。通过委派其他构造函数,多构造函数的类编写更加容易。
class Info{
public:
// 目标构造函数
Info():
_type(0),
_a('a')
{
InitRSet();
}
// 委派构造函数
Info(int type): Info()
{
_type = type;
}
// 委派构造函数
Info(char a): Info()
{
_a = a;
}
private:
void InitRSet()
{ //初始化其他变量 }
private:
int _type = 0;
char _a = 'a';
//...
};
在初始化列表中调用”基准版本”的构造函数称为委派构造函数,而被调用的”基准版本”则称为目标构造函数。
注意:构造函数不能同时”委派”和使用初始化列表。
8、默认函数控制
在C++中对于空类编译器会生成默认成员函数,如果在类中显示定义了,编译器就不会产生,这样就会容易混淆,到底有没有默认的成员函数,所以在C++11中让程序员自己控制是否需要编译器生成默认的成员函数。
8.1 显示缺省函数
在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。
class A
{
public:
A(int a): _a(a)
{}
// 显式缺省构造函数,由编译器生成
A() = default;
// 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
A& operator=(const A& a);
private:
int _a;
};
8.2 删除默认函数
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。