C++11特性
- 相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。
- 相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率。
- 用一下常见的特性即可
大括号的花样使用
- C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
- 调用的自定义类型的构造函数。
int main()
{
int a[]{ 21, 22, 23, 24 };
map<string, int> v1{ {"endeavour",24} };//主要支持了initializer_list 作为参数的构造函数
//vector<int> v2(1, 2, 3); 必须要有对应参数类型和个数的构造函数
initializer_list<int> lt1{ 21,22,23 };
auto it = lt1.begin();
while (it != lt1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
vector<int>* v2 = new vector<int>[4]{ {24,25,26} };
int* p1 = new int[3] { 24, 25, 26 };
int* p2 = new int(27);
return 0;
}
变量类型的推导decltype、typeid
- 推演表达式类型作为变量的定义类型
- 推演函数返回值的类型
int main()
{
int a[]{ 21, 22, 23, 24 };
map<string, int> v1{ {"endeavour",24} };
initializer_list<int> lt1{ 21,22,23 };
auto pa = &a;
auto pm = &v1;
auto plt = <1;
vector<decltype(pa)> vv;//返回的是一个类型
cout << typeid(pa).name() << endl<<endl;//拿到的是类型的字符串
//cout << typeid(pm).name() << endl<<endl;
cout << typeid(plt).name() << endl<<endl;
cout << typeid(vv).name() << endl << endl;
return 0;
}
增加的新容器
int main()
{
int a[]{ 21, 22, 23, 24 };
array<int, 5> ai{ 25,26,27 };
a[11] = 28;//对越界是一种抽查
ai[6] = 29;//检查更严格。
//另外有用的就是 哈希表的 map set
return 0;
}
右值引用
- 普通左值引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值。
- 右值引用:只能引用右值,一般情况不能直接引用左值。
- std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
- 被转化的左值,其生命周期并没有随着左值的转化而改变,即std::move转化的左值变量lvalue不会被销毁。
- STL中也有另一个move函数,就是将一个范围中的元素搬移到另一个位置。
int main()
{
int a = 24;//左值可以取地址,可以赋值;const左值可以取地址,不能赋值,在内存中有地址。
int& b = a;
int c = b;
const int& d = 24; //const左值可以引用右值
//25 = a; //右值具有常性,不能取地址,不能出现再取值符号的左边,不能修改,因为在内存中没有地址
//25 = 1;
//int* pc = &25;
int&& f = 25; //右值引用后的c 是左值。
f = 27;
//int&& e = a; //右值引用不能引用 左值
int&& e = move(a); //右值引用可以引用 move以后的左值
return 0;
}
左值引用的盲区:
mystring::String f2()
{
mystring::String s("hhee");
return s;
}
int main()
{
mystring::String s1 = f2();
return 0;
}
- s返回前 会产生一个临时对象,深拷贝拷贝s;tmp(s);然后s出作用域自动析构;
- 然后s1(tmp),在进行一次深拷贝构造 ,这个过程连续,被编译器优化了
- 这里的 = 号 不是赋值重载,这里是刚创建的对象用一个对象初始化。
- 如果s的数据量很大,那么开销很大,两次申请,两次strcpy,两次析构…
- 如果加static,引用返回,会有多线程安全问题:多线程使用的都是这一份。
移动语义
- 将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。
- 有了移动语义后,函数外若有对象接受返回的参数时,会把返回值处理成 将亡值(右值)。直接进行一次移动构造即可,不用在进行原来的两次拷贝构造。
namespace mystring
{
class stirng
{
....
private:
char* _str;
size_t _size;
size_t _capacity; // 不包含最后做标识的\0
};
}
swap
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
,_capacity(0)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
//this->swap(s);
swap(s);
}
移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
右值引用、移动语义总结:
-
所有的STL容器都增加了移动构造,移动赋值重载;面对常量时,效率更高。
-
C++11中,算法里面的swap和容器里面的swap效率没有区别。
右值引用
- 实现移动语义(移动构造与移动赋值)
- 给中间临时变量取别名:
- 实现完美转发…
int main()
{
string s1("hello");
string s2(" world");
string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
stirng&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名 表达式结果具有常性时右值
return 0;
}
完美转发
- 完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
- 如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。
- 模板里面的T&& 做参数,不再局限是右值引用,叫做万能引用;即可引用左值,也可以引用右值;但接受一个参数后民退化成左值,用
std::forward<T>
可以保持右值属性。
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<class T>
void PerfectForward(T&& x)
{
//Fun(x);
Fun(std::forward<T>(x));
}
新增的成员函数
移动构造/移动赋值
- 如果 没有定义 拷贝构造/拷贝赋值/移动构造/移动赋值/析构函数的 任何一个,编译器会 自动生成 移动构造/移动赋值 函数
- 如果 需要定义 拷贝构造/拷贝赋值/移动构造/移动赋值/析构 函数的任何一个,不要忘了 移动构造/移动赋值 函数,否则右值会走普通的拷贝构造。
- 条件很苛刻,对于需要深拷贝的类,都需要自己写。
G.
- 大脑一片混乱…