C++11重要特性总结

C++11特性

  • 1.统一列表初始化{}
    • 使用
    • 原理
  • 2. 声明
    • 关键字 auto
    • 关键字 decltype
    • nullptr
  • 3. 范围for
  • 5. stl增加的容器
  • 6. 右值引用
    • 左值和右值的区分
    • 左值和右值引用场景
      • 移动构造
      • 移动赋值
    • 右值引用move()
    • 完美转发forward()
      • 模板中的万能引用
      • forward作用
  • 7. 新的类成员函数
    • 移动构造
    • 移动赋值
    • 强制生成默认函数的关键字default
    • 禁止生成默认函数的关键字delete
  • 8. lambda表达式
    • lambda语法
    • 捕捉列表具体介绍[]
    • 函数对象(仿函数)
  • 9.可变参数模板Args
    • 使用逗号表达式展开可变参数包
  • 11.包装器
    • 引入背景
    • 包装器使用方法
    • bind

1.统一列表初始化{}

使用

在C++98时期,使用{}只能初始化数组,结构体。如下:

struct XX
{int x;int y;
};
int main()
{int a[] = { 0,1,2,3,4 };int b[5] = { 0 };XX x = { 1,2 };return 0;
}

C++11为了方便我们统一列表初始化数据:

  1. 使用{}可以初始化所有的内置类型和用户自定义类型。并且=可以省略
struct XX
{int x;int y;
};
int main()
{//C++98int a[] = { 0,1,2,3,4 };int b[5] = { 0 };XX x = { 1,2 };//C++11int aa[]{ 0, 1, 2, 3, 4 };int bb[5]{ 0 };XX xx{ 1,2 };//C++11中初始化列表也可以适用于new表达式中// pa指向int数组的第一个元素,是整形的。//将三个数都初始化为1int* pa = new int[3]{ 1, 1, 1};return 0;
}
  1. 创建对象时也可以使用{}来调用构造函数
string str("hello world");  //c++98string str1 = { "hello world" };string str2{ "hello world" };map<int, string> m1 = { {1, "小明"}, {2, "小红"} };map<int, string> m2{ {1, "小明"}, {2, "小红"} };

原理

使用{}构造函数,其实是事先生成了一个initializer_list对象。
在这里插入图片描述
然后我们stl容器,都支持使用initializer_list对象调用构造:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因此我们实际上使用{}时,是先生成initializer_list对象,然后再构造。

2. 声明

关键字 auto

自动推断类型:非常方便
在这里插入图片描述

关键字 decltype

将变量的类型声明为指定类型:

  1. 可以使用该关键字声明变量
  2. 也可以使用该关键字创建指定类型的对象
	const int a = 10;double b = 1.1;decltype(a * b) ret;   //ret类型为 doubledecltype(&a) p;       // p的类型为 const int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;vector<decltype(a* b)> v;   //v的类型为doublecout << typeid(v).name() << endl;

在这里插入图片描述

nullptr

在这里插入图片描述

3. 范围for

可以使用该语法遍历容器或者数组

	vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };for (auto e : v){cout << e << " ";}cout << endl;

在这里插入图片描述

5. stl增加的容器

在这里插入图片描述

6. 右值引用

左值和右值的区分

左值:可以取地址,可以对它赋值,可以出现在赋值符号的左边。
右值:不可取地址,不能出现在赋值符号的左边。

注意:定义时const修饰的后的左值,不能给它赋值,但是可以取地址,因此它是左值。

如下左值引用和右值引用例子:

	//下面都是左值int a = 10;int* p = &a;const int b = 10;//左值的引用int& ra = a;int*& rp = p;const int& rb = b;///int x = 10;int y = 20;int add(int, int);//下面都是右值20;     //字面常量x + y;    //表达式返回值add(x, y);  //函数返回值//下面是右值引用int&& name1 = 20;int&& name2 = x + y;int&& name3 = add(x, y);

加上const可以实现,左值引用给右值取别名:

	const int& cc = 10;cout << cc;    //  10

使用move函数,可以实现右值引用给左值取别名:

	int a = 10;int&& b = move(a);  //使a暂时变为右值cout << b;   // 10

左值和右值引用场景

看下面这段代码:分别是普通的左值引用构造,右值引用构造(注意看形参就可以区分)
然后主要介绍左值引用解决的什么情况,右值引用解决了什么情况。

//拷贝构造函数string(const string& str):_size(str._size), _capacity(str._capacity){_str = new char[_capacity + 1];strcpy(_str, str._str);}//移动构造string(string&& str):_str(nullptr),_size(0),_capacity(0){cout << "string(string&& str)--移动构造" << endl;swap(str);}//原赋值函数string& operator=(const string& s){if (this != &s){char* tem = new char[s._capacity + 1]; //先申请新空间strcpy(tem, s._str);   //拷贝//没有抛异常,往下执行delete[] _str;//释放空间_str = tem;   //赋回来_size = s._size;_capacity = s._capacity;}return *this;}//移动赋值string& operator=(string&& s){if (this != &s){cout << "string& operator=(string&& s)---移动赋值" << endl;swap(s);}return *this;}

移动构造

  • 左值引用解决的问题:直接减少拷贝。1. 左值引用传参 2.传引用返回(函数内的局部对象不能用传引用返回,因为出作用域会销毁)
  • 左值引用短板:如果是局部对象,就不能传引用返回,只能传值返回,传值返回(这样至少会产生一次拷贝构造)
    在这里插入图片描述

在这里插入图片描述

  • 右值引用本质上将参数右值的资源窃取过来,占为己有,(那么就不需要申请资源了),swap实现了这个功能。因为将亡值或者临时变量,本来就是要删除的数据,将这个数据直接返回,更高效。
  • 右值引用可以解决上面的资源浪费的问题:增加了移动构造后,因为返回值是将亡值,所以会被识别成右值,调用移动构造。
    在这里插入图片描述

移动赋值

当编译器识别出要赋值的参数为右值时,自动调用移动赋值,减少了拷贝,提高效率。
在这里插入图片描述

右值引用move()

将一个左值强制转化为右值
std::move()

下面例子将s1强制识别成右值,运行完之后,s1的数据被s2掠夺。
在这里插入图片描述

完美转发forward()

模板中的万能引用

模板中的&&不表示右值引用。它可以作推理使用:
当传进来左值时,折叠一个&,自动识别成左值引用。
当传进来右值时,识别成右值引用。
如下所示:

template<class T>
void Test(T&& t)
{Fun(t);
}
void Fun(int& x)
{cout << "左值引用" << endl;
}void Fun(int&& x)
{cout << "右值引用" << endl;
}
void Fun(const int& x)
{cout << "const 左值引用" << endl;
}void Fun(const int&& x)
{cout << "const 右值引用" << endl;
}
int main()
{int a = 10;Fun(a);   //左值引用Fun(move(a));  //右值引用return 0;
}

forward作用

实际上,当模板识别成右值时,为了swap会自动把右值转换成左值(为了掠夺资源,变成左值),但是有时候我们可能需要保持它的右值属性(我们暂时不掠夺呢,把它传到应该掠夺的部分)。所以我们应该维持它的右值属性,继续往下传递。

使用forward可以保持右值属性不变,继续传下去。
在这里插入图片描述
按道理来说,上面小节的测试,应该都是左值引用,因为在Test里被识别成左值了。但是编译器应该做了一些处理,导致右值属性被保留。


工程中涉及到完美转发的情形就是利用forward一直保留变量的属性,直至进行资源掠夺阶段。

7. 新的类成员函数

移动构造和移动赋值的知识背景是右值引用部分。

移动构造

  • 若自己没实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造,对内置类型成员会执行逐成员按字节拷贝。自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没实现就调用拷贝构造。
  • 如果你提供了移动构造,编译器不会提供拷贝构造。

移动赋值

  • 如果自己没实现移动赋值重载函数,并且没有实现析构函数、拷贝构造、宝贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数,对于内置类型成员会逐字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没实现就调用拷贝赋值。
  • 如果你提供了移动赋值,编译器不会提供拷贝赋值。

强制生成默认函数的关键字default

假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如: 我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
语法如下:

class string
{
//必须生成一个默认的移动构造string(string&& s) = default;
}

禁止生成默认函数的关键字delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class string
{
//不再生成该移动构造函数string(string&& s) = delete;
}

8. lambda表达式

lambda表达式是C++借鉴其他语言所得到的产物。
其底层实现逻辑类似于仿函数的逻辑。
产生原因:
每次写仿函数都要实现一个类,很麻烦。

lambda语法

[capture-list] (parameters) mutable -> return-type {statement}

  • [capture-list]:捕捉列表,编译器根据[]来判断下边的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表,与普通函数参数列表一致,如果不需要传递参数,可与()一同省略
  • mutable:默认情况下,lambda函数总是const函数,mutable可以取消其常量性,使用该修饰符时,参数列表不可省略(即使参数为空)
  • -> return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
  • {statement}:函数体。可以使用捕获到的变量

注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

使用示例:

struct Stu
{string _name;int _age;
};
int main()
{vector<Stu> v{ {"zhangsan", 10}, {"lisi", 20}, {"wangwu", 3} };//lanmda表达式,按年龄排序sort(v.begin(), v.end(), [](Stu s1, Stu s2)->bool {return s1._age < s2._age; });//定义一个lambda表达式,按字符串排序auto com = [](Stu s1, Stu s2)->bool {return s1._name < s2._name; };sort(v.begin(), v.end(), com);return 0;
}

捕捉列表具体介绍[]

捕捉列表描述了上下文哪些东西可以被lambda使用,以及实用的方式是传值还是传引用。

  • [var] :表示传递的方式是捕捉变量
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用的捕捉变量(不是取地址!!)
  • [&=]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递的方式,捕获当前this指针

注意:

  • 父作用域是包含lambda函数的语句块
  • 捕捉列表可以灵活使用,以逗号分隔
    比如:[=,&a,&b]:以引用的方式捕捉变量ab,剩下的按值传递。
    [&, a, this]:a和this按值传递捕捉,其他变量按引用捕捉
  • 捕捉列表不允许重复捕捉,否则会编译错误
    比如:[=, a]就会重复捕捉a
  • 块作用域以外的lambda函数捕捉列表必须为空
  • lambda表达式不能互相赋值

在这里插入图片描述

函数对象(仿函数)

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator0运算符的类对象。

class Mutil
{
public:int operator()(const int& x, const int& y){return x * y;}
};int main()
{Mutil t1;cout << t1(5, 6) << endl;auto t2 = [](int x , int y) {return x * y; };cout << t2(5, 6) << endl;return 0;
}
  1. 首先仿函数和lambda使用方法完全类似,并且底层汇编i代码,调用也类似
    在这里插入图片描述

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

9.可变参数模板Args

首先看一下可变参数模板的示例:

//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中可以包含0~n任意个模板参数
template<class ...Args>
void ShowList(Args... args)
{//语法就是这样的cout << sizeof...(args) << endl;
}int main()
{ShowList();ShowList('a');ShowList('a','b');ShowList('a','b',1,2);return 0;
}

运行结果:

在这里插入图片描述
可知可变参数包可以接收任意个数的参数。


上面的args的前面有省略号,表示args一个可变的模板参数,我们把带有省略号的参数称为称为“参数包”,它里面包含了0~N个模板参数。

我们无法直接获取args的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数。

下面举例,如何展开参数包里的参数:

int N = 0;
template<class T>
void ShowList(const T& val)
{//语法就是这样的,必须有一个单参数的,结束递归cout << "单参函数打印:" << val << endl;
}//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中可以包含0~n任意个模板参数
template<class T, class ...Args>
void ShowList(const T& val, Args... args)
{//语法就是这样的N++;cout << val << endl;ShowList(args...);
}int main()
{ShowList('a','b',1,2);cout << N;return 0;
}

在这里插入图片描述
代码解释:
首先main函数传进去四个参数,一个给T&模板,剩下三个给args函数包。以此类推,直到函数包只有一个参数2,最后一个参数传递,必须再写一个递归终止函数,否则会报错。

使用逗号表达式展开可变参数包

//template<class T>
//int PrintArg(T t)
//{
//	cout << t << " ";
//	return 0;
//}template<class T>
void PrintArg(T t)
{cout << t << " ";
}template<class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args),0)... };//int arr1[] = { PrintArg(args)... };cout << endl;
}//上面的showlist会推演成下面的表达式:template<class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(1),0),(PrintArg('a'),0),(PrintArg(2),0),(PrintArg(string("123214")),0)};//int arr1[] = { PrintArg(args)... };cout << endl;
}int main()
{ShowList(1,'a', 3, string("123214"));return 0;
}

在这里插入图片描述

在这里插入图片描述

11.包装器

function包装器也叫适配器。C++中的function本质是一个类模板,也是一个包装器。

引入背景

ret = func(x)

上面这段代码func的具体类型是什么样的?func可能是函数?函数指针?函数对象(仿函数)?也有可能是lambda表达式对象?这些都是可调用的类型!

这些在一起会导致模板效率低下,如下:

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}double f(double i)
{return i / 2;
}struct Functor
{double operator()(double y){return y / 3;}
};int main()
{//函数名cout << useF(f, 11.11)<<endl;//函数对象cout << useF(Functor(), 11.11) << endl;//lambda公式cout << useF([](double d)->double {return d / 4; }, 11.11);return 0;}

在这里插入图片描述
由上面的代码可知,count有三个,即useF实例化出了三份代码,分别是函数指针类型的、仿函数类型的、lambda表达式类型的。包装器可以很好解决上面的问题。


#include<functional>template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}double f(double i)
{return i / 2;
}struct Functor
{double operator()(double y){return y / 3;}
};int main()
{函数名//cout << useF(f, 11.11)<<endl;函数对象//cout << useF(Functor(), 11.11) << endl;lambda公式//cout << useF([](double d)->double {return d / 4; }, 11.11);//函数名function<double(double)> f1 = f;//仿函数function<double(double)> f2 = Functor();//lambda公式function<double(double)> f3 = [](double d)->double {return d / 4; };cout << useF(f1, 11.11) << endl;cout << useF(f2, 11.11) << endl;cout << useF(f3, 11.11) << endl;return 0;}

在这里插入图片描述

使用functional包装后,模板只实例化出一份。

包装器使用方法

在这里插入图片描述
记得要实例化成:返回值(参数)的格式。

下面是:普通函数,仿函数,lambda表达式,类成员函数,类静态成员函数的包装方法。

#include<functional>int f(int a, int b)
{return a + b;
}struct Functor
{int operator()(int a, int b){return a + b;}
};class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}};int main()
{//函数名std::function<int(int, int)> f1 = f;cout << f1(1, 2) << endl;//函数对象std::function<int(int, int)> f2 = Functor();cout << f2(1, 2) << endl;//lambda表达式std::function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };cout << f3(1, 2) << endl;//类的静态成员函数(无this指针)std::function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 2) << endl;//类的动态成员函数(得传入this指针)std::function<double(Plus, double, double)> f5 = &Plus::plusd;cout << f5(Plus(), 1.1, 2.0) << endl;return 0;
}

bind

bind是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
功能1:原本n个参数的函数->需要m个参数的函数
功能2:调整参数顺序

类的声明如下:
在这里插入图片描述

auto newFunc = bind(func, arg_list);

newFunc是一个新的可调用对象,arg_list是func的参数列表。当调用newFunc的时候,newFunc会调用func,并把arg_list中的参数传给func。

如下例子:

#include<functional>int Plus(int a, int b)
{return a - b;
}class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{//表示绑定函数Plus, 其中原函数的第二个参数被放到了第一个位置//原函数的第一个参数被放到了第二个位置function<int(int, int)> func1 = bind(Plus, placeholders::_2, placeholders::_1);//auto func1 = bind(Plus, placeholders::_2, placeholders::_1);  cout << func1(1, 2) << endl;   //因为参数颠倒了,变为了 2-1//此函数是,绑定了第二个参数,第一个参数需要自己指定auto func2 = bind(Plus, placeholders::_1, 1);cout << func2(2) << endl;//此函数是,绑定了第1个参数,第二个参数需要自己指定auto func3 = bind(Plus, 1, placeholders::_1);cout << func3(2) << endl;//此函数绑定了成员函数,需要制定类域的函数地址,需要传入this指针auto func4 = bind(&Sub::sub, Sub(), 1, 2);cout << func4() << endl;//此函数传入了对象的地址Sub s;auto func5 = bind(&Sub::sub, &s, 1, 2);cout << func5() << endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/480135.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构中线性表简述

线性表是数据结构中最简单、最常用的一种结构&#xff0c;它是由一组具有相同数据类型的元素组成的数据集合。线性表中的元素之间存在顺序关系&#xff0c;每个元素都有一个前驱元素和一个后继元素&#xff0c;除了第一个元素没有前驱元素&#xff0c;最后一个元素没有后继元素…

ThreadLocal(2):运用场景

通过上一章介绍&#xff0c;我们已经基本了解ThreadLocal的特点。但是它具体是运用在什么场景中呢&#xff1f; 接下来让我们看一个案例&#xff1a; 事务操作。 1 转账案例 1.1 场景构建 ​ 这里我们先构建一个简单的转账场景&#xff1a; 有一个数据表account&#xff0c;…

Vue | (二)Vue组件化编程 | 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;模块与组件、模块化与组件化&#x1f4da;非单文件组件&#x1f407;基本使用&#x1f407;关于组件的几个注意点&#x1f407;组件的嵌套 &#x1f4da;单文件组件&#x1f407;一个.vue 文件的组成&#x1f407;实例 学习链接&#xff1a;尚硅谷Vue2.0…

【洛谷题解】B2034 计算 2 的幂

题目链接&#xff1a;计算 2 的幂 - 洛谷 题目难度&#xff1a;入门 涉及知识点&#xff1a;pow函数返回值 题意&#xff1a; 分析&#xff1a;用pow计算再强制转换即可 AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int main(){int a;ios::syn…

初始回溯算法

回溯算法一般用于对数据枚举后选取符合条件的结果并最终返回结果集的问题&#xff0c;之所以叫回溯法&#xff0c;是因为它可进可退 要想理解回溯的本质&#xff0c;还是要通过具体的题目去学习。 路径问题 https://www.nowcoder.com/practice/b736e784e3e34731af99065031301b…

GZ036 区块链技术应用赛项赛题第9套

2023年全国职业院校技能大赛 高职组 “区块链技术应用” 赛项赛卷&#xff08;9卷&#xff09; 任 务 书 参赛队编号&#xff1a; 背景描述 随着异地务工人员的增多&#xff0c;房屋租赁成为一个广阔是市场&#xff1b;目前&#xff0c;现有技术中的房屋租赁是由…

Git 客户端可视化工具tortoisegit

Git 使用教程 git一点通 (kdocs.cn) 二、Git 客户端可视化工具-推荐 1.常用工具 tortoisegit 官网 https://tortoisegit.org/ 推荐 sourcetree 官网 https://www.sourcetreeapp.com/ 2.tortoisegit安装 2.1 下载安装包 2.2 下载语言包 2.3 安装 2.4 安装语言包 5.使用 5.1 新建…

strongswan教程

在 CentOS 7 上使用 StrongSwan 5.7.2 建立 IPSec VPN 连接&#xff0c;可以按照以下步骤进行配置&#xff1a; 准备3台服务器&#xff1a; A:192.168.3.209&#xff0c;私网172.18.1.0/24 B:192.168.3.29&#xff0c;私网172.18.2.0/24 C:192.168.3.154&#xff0c;私网10…

LeetCode--代码详解 104. 二叉树的最大深度

104. 二叉树的最大深度 题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&…

体验一下UE5.3的Skeletal Editor

UE5.3中增加了蒙皮网格骨架编辑工具&#xff0c;用户无需导出Fbx就可以直接编辑蒙皮网格&#xff0c;支持修改绑定姿势的骨骼位置、修改蒙皮权重、对已蒙皮多边形进行编辑以及对蒙皮网格减免等操作&#xff0c;就来体验一下。 1.加载插件 要使用Skeletal Editor功能&#xff…

量化巨头“卖空”被刷屏!网友:又一类量化策略要“收摊”了

量化圈遇到了龙年首宗“大事件”&#xff01; 2月20日晚间&#xff0c;沪深交易所同时出手对量化巨头灵均投资的异常交易行为进行“处理”。 沪深交易所均称发现灵均在2月19日开盘1分钟内&#xff0c;名下多个账户通过计算机程序自动生产交易指令&#xff0c;短时间大量下单卖…

解锁文档处理新境界:ONLYOFFICE编辑功能为开发者带来新机遇

引言 ONLYOFFICE最新发布的文档8.0版本带来了一系列引人注目的功能和优化&#xff0c;为用户提供了更强大、更高效的在线编辑体验。这次更新涵盖了多个方面&#xff0c;包括PDF表单、RTL支持、单变量求解、图表向导以及插件界面设计更新等。这些新功能不仅提升了文档处理的便利…