C++11特性(详细版)
Posted 雨轩(爵丶迹)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11特性(详细版)相关的知识,希望对你有一定的参考价值。
C11
- 1、C11优势
- 2、列表初始化
- 3、变量类型推导
- 4、final 与 override
- 5、默认成员函数控制
- 6、右值引用与移动语义
- 7、完美转发
- 8、新的类功能
- 9、lambda表达式
- 10、可变参数列表(先学会基本特性)
- 11、包装器
1、C11优势
相比C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率。
2、列表初始化
- 在C++98中,标准允许使用花括号对数组元素进行统一的列表初始值设定
int array1[] = 1,2,3,4,5;
int array2[5] = 0;
C++98对于自定义类型,无法使用列表初始化,在C++11中改进了
- C++11中自定义类型也可以使用列表初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
1、内置类型的列表初始化
// 内置类型变量
int x1 = 10;
int x210;//建议使用原来的
int x3 = 1+2;
int x4 = 1+2;
int x51+2;
// 数组
int arr1[5] 1,2,3,4,5;
int arr2[]1,2,3,4,5;
// 动态数组,在C++98中不支持
int* arr3 = new int[5]1,2,3,4,5;
// 标准容器
vector<int> v1,2,3,4,5;//这种初始化就很友好,不用push_back一个一个插入
map<int, int> m1,1, 2,2,,3,3,4,4;
2、自定义类型的列表初始化
- 标准库支持单个对象的列表初始化
class Point
public:
Point(int x = 0, int y = 0): _x(x), _y(y)
private:
int _x;
int _y;
;
int main()
Pointer p = 1, 2 ;
Pointer p 1, 2 ;//不建议
return 0;
- 多个对象的列表初始化
多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即可。
注意:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()
class Date
public:
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
cout << "这是日期类" << endl;
private:
int _year;
int _month;
int _day;
;
int main()
//C++11容器都实现了带有initializer_list类型参数的构造函数
vector<Date> vd = 2022, 1, 17 , Date 2022, 1, 17 , 2022, 1, 17 ;
return 0;
3、变量类型推导
1、为什么需要类型推导
在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂
int main()
short a = 32670;
short b = 32670;
// c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题
short c = a + b;
std::map<std::string, std::string> m "apple", "苹果", "banana","香蕉" ;
// 使用迭代器遍历容器, 迭代器类型太繁琐
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
cout << it->first << " " << it->second << endl;
++it;
return 0;
C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁。
// 使用迭代器遍历容器, 迭代器类型太繁琐 可以使用auto
//std::map<std::string, std::string>::iterator it = m.begin();
auto it = m.begin();
2、decltype类型推导(了解)
为什么需要decltype
auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力。
decltype
decltype是根据表达式的实际类型推演出定义变量时所用的类型,比如
1、推演表达式类型作为变量的定义类型
int a = 10, b = 20;
decltype(a + b)c;
cout << typeid(c).name() << endl;
2. 推演函数返回值的类型
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
return left + right;
int main()
cout << typeid(Add(1, 2)).name() << endl;
return 0;
4、final 与 override
final
1、final修饰类的时候,表示该类不能被继承
class A final //表示该类是最后一个类
private:
int _year;
;
class B : public A //无法继承
;
2、final修饰虚函数时,这个虚函数不能被重写
class A
public:
virtual void fun() final//修饰虚函数
cout << "this is A" << endl;
private:
int _year;
;
class B : public A
public:
virtual void fun()//父类虚函数用final修饰,表示最后一个虚函数,无法重写
cout << "this is B" << endl;
;
override
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class A
public:
virtual void fun()
cout << "this is A" << endl;
private:
int _year;
;
class B : public A
public:
virtual void fun() override
cout << "this is B" << endl;
;
5、默认成员函数控制
在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成。
1、显示缺省函数
在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指定编译器生成该函数的默认版本(默认成员函数),用=default修饰的函数称为显式缺省函数。
class A
public:
A() = default;//让编译器默认生成无参构造函数
A(int year) //这样不写缺省值的时候,就不需要自己在去实现一个默认的无参构造函数
:_year(year)
void fun()
cout << "this is A" << endl;
private:
int _year;
;
2、删除默认函数(禁止调用)
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class A
public:
A() = default;
A(int a) : _a(a)
//C++11
// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
A(const A&) = delete;
A& operator=(const A&) = delete;
private:
int _a;
//C++98,设置成private就可以了
A(const A&) = delete;
A& operator=(const A&) = delete;
;
6、右值引用与移动语义
1、左值引用和右值引用
传统的C++就有引用,称为左值引用,C++11后,出了右值引用。无论是左值引用还是右值引用,都是给对象取别名(与对象共享一片空间)。
1、什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名和解引用的指针),我们可以获取它的地址,也可以对它赋值,左值可以出现在赋值符号的左边,右值不可以出现在左边。左引用加const修饰,不能对其赋值,但可取地址,是一种特殊情况。左值引用就是给左值取别名。
//以下都是左值
int* p = new int[10];
int a = 10;
const int b = 20;
//对左值的引用
int*& pp = p;
int& pa = a;
const int& rb = b;
左值:
1、可以取地址
2、一般情况下可以修改(const修饰时不能修改)
2、什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值、传值返回函数的返回值(不能是左值引用返回)等,右值可以出现在赋值符号的右边,但是不能出现在左边。右值引用就是给右值取别名。
double x = 1.1, y = 2.2;
//常见右值
10;
x + y;
add(1, 2);
//右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double && rr3 = add(1, 2);
//右值引用一般情况不能引用左值,可使用move将一个左值强制转化为右值引用
int &&rr4 = move(x);
//右值不能出现在左边,错误
10 = 1;
x + y = 1.0;
add(1, 2) = 1;
move:当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
2、左值引用与右值引用比较
左值引用总结:
- 左值引用只能引用左值,不能引用右值
- const左值引用既可引用左值,也可引用右值
// 左值引用只能引用左值,不能引用右值
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
//const左值引用既可以引用左值,也可以引用右值
const int& ra3 = 10;
const int& ra4 = a;
右值引用总结:
- 右值引用只能引用右值,一般情况下不能引用左值
- 右值引用可以引用move以后的左值
int a = 10;
int b = 20;
//不能引用左值
//int&& rr1 = a;
int&& rr2 = 10;
int&& rr3 = move(a);//强制转换为右值引用
3、右值引用使用场景和意义
左值引用既可以引用左值,可以引用右值,为什么C++11还要提出右值引用?因为左值引用存在短板,下面我们来看看这个短板以及右值引用是如何弥补这个短板的!
void fun1(bit::string s)
void fun2(bit::string& s)
int main()
bit::string s("1234");
//fun1(s);
//fun2(s);//左值引用提高了效率,不存在拷贝临时对象的问题
//可以使用左值引用返回,这个对象还在
s += 'a';
//不能使用左值引用返回,这个就是左值引用的一个短板
//函数返回对象出了作用域就不在了,就不能用左值引用返回(因为返回的是本身地址,栈帧已销毁)
//所以会存在拷贝问题
bit::string ret = bit::to_string(1234);
return 0;
右值引用弥补这个短板(右值引用场景1)
C++11移动语义的提出:将一个对象中资源移动到另一个对象中的方式。
str在按照值返回时,必须创建一个临时对象,临时对象创建好之后,str就被销毁了,str是一个将亡值,C++11认为其为右值,在用str构造临时对象时,就会采用移动构造,即将str中资源转移到临时对象中。而临时对象也是右值,因此在用临时对象构造s3时,也采用移动构造,将临时对象中资源转移到ret中,整个过程,只需要创建一块堆内存即可,既省了空间,又大大提高程序运行的效率。
这里我们就又可以对右值进行一个定义:
右值:1、纯右值 10 a+b 2、将亡值,函数返回的临时对象,匿名对象
此时我们将这一条语句分开写,看看又是什么情况
bit::string ret;
ret = bit::to_string(1234);//赋值重载多了一次拷贝构造
我们将移动赋值写上,就会进行优化,少一次拷贝构造
总结一下:右值引用出来以后,并不是直接使用右值引用去减少拷贝,提高效率。而是支持深拷贝的类,提供移动构造和移动赋值,这时这些类的对象进行传值返回或者是参数为右值时,则可以用移动构造和移动赋值,转移资源,避免深拷贝,提高效率。
以上是右值使用的场景1
//左值,拷贝构造,使用左值引用
list<bit::string> lt;
bit::string s("1234");
lt.push_back(s);
//以下传的都是右值,右值引用,所以是移动构造
lt.push_back("123");
lt.push_back(bit::string("2121"));
lt.push_back(std::move(s));
总结一下:右值引用使用场景二,还可以使用在容器插入接口函数中,如果实参是右值,则可以转移它的资源,减少拷贝
7、完美转发
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
void Func(int x)
cout << x << endl;
template<typename T>
void PerfectForward(T&& t)
Func(t);
PerfectForward为转发的模板函数,Func为实际目标函数,但是上述转发还不算完美,完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销,就好像转发者不存在一样。
所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。
我们先来了解万能引用
1、模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
2、模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力
3、但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
4、我们希望能够在传递过程中保持它的左值或者右值的属性,就需要用我们下面学习的完美转发
C++11通过forward函数来实现完美转发
void Func(int& x) cout << "左值引用" << endl;
void Func(const int& x) cout << "const 左值引用" << endl;
void Func(int&& x) cout << "右值引用" << endl;
void Func(const int&& x) cout << "const 右值引用" << endl;
template<typename T>
void PerfectForward(T&& t)
//Func(t);//没有使用forward保持其右值的属性,退化为左值
Func(forward<T>(t));
int main()
PerfectForward(1);//右值
int a = 10;
PerfectForward(a);
PerfectForward(move(a));
const int b = 20;
PerfectForward(b);
PerfectForward(move(b));
return 0;
右值引用的对象,再作为实参传递时,属性会退化为左值,只能匹配左值引用。使用完美转发,可以保持他的右值属性
8、新的类功能
默认成员函数
原来C++类中,有6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const取地址重载
重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11新增了两个:移动构造函数和移动赋值逸算符重载。
C++11新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
- 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 如果你没有自己实现移动赋值重载函数,目没有实现析构函数。接贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
没有实现移动构造的情况
C++11特性(详细版)