C++11实用特性2
1 可调用对象包装器、绑定器
1可调用对象
C++中的可调用对象分为四类:
- 函数指针:
任何一个函数都可以抽象成一个函数指针
int print(int a, double b)
{
cout << a << b << endl;
return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;
- 仿函数(函数对象)
如果一个对象具有operator()成员函数,那么就称其为仿函数
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Test
{
// ()操作符重载
void operator()(string msg)
{
cout << "msg: " << msg << endl;
}
};
int main(void)
{
Test t;
t("我是要成为海贼王的男人!!!"); // 仿函数
return 0;
}
- 类的静态函数
- 可转为函数指针的类对象(比较麻烦难懂,不推荐)
利用operator可将类对象转换为函数指针,需要在类内定义operator 函数指针()
,括号内只能为空,返回值要求是一个函数地址,且这个函数只能是属于类的静态成员函数;而此时就相当于调用这个对象并传入对应函数所需参数时,对应的函数就被调用 - 类成员函数指针、类成员变量指针(不好用)
定义非静态成员函数的函数指针时要加上类作用域才行,因为非静态成员函数在对象未分配前不存在地址
using fptr = void(Test::*) (int, string)//后面是参数列表,表明是属于Test类中的一个指针
2 可调用对象包装器function
function的出现使可调用对象变成一种统一的类型来使用,其使用语法如下:
#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;
要注意类成员函数指针是不能直接打包的,要借助bind才行
3 可调用对象绑定器
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。通俗来讲,它主要有两大作用:
- 将可调用对象与其参数一起绑定成一个
仿函数
。 - 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。
其使用语法如下:
// 绑定非类成员函数/变量,静态成员函数
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);
指定第一个参数的时候加上类作用域
2 lambda表达式
1 基本语法
lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。其最大的好处就是使用时定义,lambda表达式的语法形式简单归纳如下:
[capture](params) opt -> ret {body;};
- capture:捕获列表,对函数体外部变量的捕获方式
[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
拷贝的副本在匿名函数体内部是只读的
[=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量, 同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
[this] - 捕获当前类中的this指针
让lambda表达式拥有和当前类成员函数同样的访问权限
如果已经使用了 & 或者 =, 默认添加此选项
但是只能使用类中的成员变量
- params:参数列表,和普通函数参数列表一样,没有可以不写
- opt:函数选项,没有可以不写
- mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
- exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
- ret:返回值类型
- body:函数体
使用示例:
要注意匿名函数的大括号后面加上一个小括号才算调用
void func(int x, int y){
int a = 7;
int b = 9;
[=, &x](){
int c = a;
int d = x;
x++;
}();
cout<<x;
}
int main()
{
func(1,2);
return 0;
}
2 返回值
很多时候,lambda表达式的返回值是非常明显的,因此在C++11中允许省略lambda表达式的返回值。
// 完整的lambda表达式定义
auto f = [](int a) -> int
{
return a+10;
};
// 忽略返回值的lambda表达式定义
auto f = [](int a)
{
return a+10;
};
3 本质
lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。
mutable选项的作用就在于取消operator()的const属性。
由于lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:
#include <iostream>
#include <functional>
using namespace std;
int main(void)
{
// 包装可调用函数
std::function<int(int)> f1 = [](int a) {return a; };
// 绑定可调用函数
std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);
// 函数调用
cout << f1(100) << endl;
cout << f2(200) << endl;
return 0;
}
3 右值引用
右值引用也是C++提供的新特性:
- 左值(location value)
指存储在内存中、有明确存储地址的数据,即可以取地址 - 右值(readable value)
可以提供数据值的数据,不可取地址,比如数字
首先明确,无论左值引用(&)还是右值引用(&&),它们的前提都是引用,就是个别名,而引用的实质就是个指针常量void* const
1 右值引用的作用
有时我们会通过一个临时对象来构造我们的对象,如果这个临时对象本身就很大,它创建出来已经耗费了一部分系统资源,然后还要再拷贝给我们自己的对象,拷贝完再销毁这个临时对象,整个过程的开销是很大的,我们就利用右值引用来延长这种临时对象的声明周期,使得可以直接使用它而不发生拷贝
2 移动构造函数
上面的问题就引出了类中的又一个构造函数--移动构造函数
,其就是为了解决这种不必要的拷贝,实现复用其它对象中的资源(堆内存),这本质上是一种浅拷贝,但区别在于移动构造中要让原对象指向这片地址的指针指向空,使得该地址的管理权被新的对象独享,或者理解为移动构造函数把临时对象的指针指向移走了
class Test
{
public:
Test(){
}
Test(Test&& a):m_num(a.m_num)
{
a.m_num = nullptr;
}
private:
int *m_num;
};
Test getRet(){
Test t;
return t;
}
int main()
{
//getRet在返回的时候会创建一个临时对象接受t,
Test t1 = getRet();
Test &&t2 = getRet();
return 0;
}
在添加了移动构造函数之后,对象赋值的时候编译器会判断等号右边的是否为临时对象,便选择哪一种构造函数来实现,如果没有移动构造函数,那么调用的就是拷贝构造函数了。
如今C++中的函数在返回一个类对象时,理论上我们会认为通过复制构造函数复制一个临时对象,然后在赋值给外部变量。实际上编译器做了优化,省去了中间的临时对象环节。所以有时我们无法看到移动构造被调用
3 &&
在C++中,并不是所有情况下 && 都代表是一个右值引用,具体的场景体现在模板和自动类型推导中,如果是模板参数需要指定为T&&
,如果是自动类型推导需要指定为auto &&
,在这两种场景下 &&被称作未定的引用类型,或者称之为万能引用。另外还有一点需要额外注意const T&&特指一个右值引用,不是未定引用类型。
template<typename T>
void f(T&& param);
void f1(const T&& param);
f(10);
int x = 10;
f(x);
f1(x); // error, x是左值
f1(10); // ok, 10是右值
由于上述代码中存在T&&或者auto&&这种未定引用类型,当它作为参数时,有可能被一个右值引用初始化,也有可能被一个左值引用初始化,在进行类型推导时右值引用类型(&&)会发生变化,这种变化被称为引用折叠。在C++11中引用折叠的规则如下:
通过右值推导 T&& 或者 auto&& 得到的是一个右值引用类型
通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 或者 auto&& 得到的是一个左值引用类型
5 右值变左值
#include <iostream>
using namespace std;
void printValue(int &i)
{
cout << "l-value: " << i << endl;
}
void printValue(int &&i)
{
cout << "r-value: " << i << endl;
}
void forward(int &&k)
{
printValue(k);
}
int main()
{
int i = 520;
printValue(i);
printValue(1314);
forward(250);
return 0;
}
根据测试代码可以得知,编译器会根据传入的参数的类型(左值还是右值)调用对应的重置函数(printValue),函数forward()接收的是一个右值,但是在这个函数中调用函数printValue()时,参数k变成了一个命名对象,编译器会将其当做左值来处理。
4 转移和完美转发
1 转移move
在C++11添加了右值引用,并且不能使用左值初始化右值引用
,如果想要使用左值初始化一个右值引用需要借助std::move()函数,使用std::move方法可以将左值转换为右值
。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象
,只是转移,没有内存拷贝,而这个左值被转移了控制权以后就变成了将亡值
语法:
template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}
由于一个右值在绑定过后再次使用时会被当作左值,所以需要用move来保持它的右值属性
int &&a = 10;
int &&b = move(a);//去掉move会报错,左值不能给右值赋值
使用场景:
- 用左值对右值初始化
- 一个对象不再使用,但是要拷贝它的数据到另一个对象中,用move来避免拷贝
2 forward
一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值,并不是原来的类型了。forward用于保证引用的类型在传递过程中不会发生变化,其使用原型std::forward(t);
- 当T为左值引用类型时,t将被转换为T类型的左值
- 当T不是左值引用类型时,t将被转换为T类型的右值
#include <iostream>
using namespace std;
template<typename T>
void printValue(T& t)
{
cout << "l-value: " << t << endl;
}
template<typename T>
void printValue(T&& t)
{
cout << "r-value: " << t << endl;
}
template<typename T>
void testForward(T && v)
{
printValue(v);
printValue(move(v));
printValue(forward<T>(v));
cout << endl;
}
int main()
{
testForward(520);
int num = 1314;
testForward(num);
testForward(forward<int>(num));
testForward(forward<int&>(num));
testForward(forward<int&&>(num));
return 0;
}
C++11实用特性2的更多相关文章
- ES6相关实用特性
本文总结ECMAScript6相关实用特性 目录 let和const 箭头函数 class 对象字段 模板字符串 解构赋值 函数参数扩展 迭代器for...of 模块加载 map和weakmap se ...
- C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)
因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...
- C++11新特性总结 (二)
1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; ...
- C++11新特性总结 (一)
1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己写了,看别人写的代码估计都会有些吃力.C++ Primer5是学习C++1 ...
- 【转】C++11常用特性的使用经验总结
出处 http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方 ...
- C++ 11 新特性
C++11新特性: 1.auto 2.nullptr 3.for 4.lambda表达式 5.override ...
- [转载] C++11新特性
C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,非常想了解一下C++11. 英文版的维基百科看起来非常费劲,而中文版维基百 ...
- 在C++98基础上学习C++11新特性
自己一直用的是C++98规范来编程,对于C++11只闻其名却没用过其特性.近期因为工作的需要,需要掌握C++11的一些特性,所以查阅了一些C++11资料.因为自己有C++98的基础,所以从C++98过 ...
- C++11常用特性的使用经验总结
转自:http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方 ...
- [转]C++11常用特性的使用经验总结
转载出处 http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++1 ...
随机推荐
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-31-JavaScript的调用执行-上篇
1.简介 在做web自动化时,有些情况playwright的api无法完成以及无法应对,需要通过或者借助第三方手段比如js来完成实现,比如:去改变某些元素对象的属性或者进行一些特殊的操作,本文讲解pl ...
- JAVAweek5
学习内容 进制 1.(十进制):752=2*10(0)+5*10(1)0+7*10(2)=752 (二进制):1011(二进制的数)=1*2(0)+1*2(1)+0*2(2)+1*2(3) = 1 ...
- timeSetEvent()函数定时器的使用
1.定时器函数的使用 微软公司在其多媒体Windows中提供了精确定时器的底层API支持,利用多媒体定时器可以很精确地读出系统的当前时间,并且能在非常精确的时间间隔内完成一个事件.函数或过程的调用. ...
- 大数据 - MapReduce:从原理到实战的全面指南
本文深入探讨了MapReduce的各个方面,从基础概念和工作原理到编程模型和实际应用场景,最后专注于性能优化的最佳实践. 关注[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作 ...
- Http的演进
Http的演进 Http在1.1版本之前具有无状态的特点,每次请求都需要通过TCP三次握手四次挥手与服务器重新建立连接.比如某个客户端在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户请求 ...
- JTAG串链
- 有序对的LCP
求\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n} LCP(s_i, s_j)\) 方法一 \(1.\)Trie . \(2.\)求\(cnt\),\(cnt[ ...
- Hexo 主题开发之自定义模板
关于 Hexo 如何开发主题包的教程在已经是大把的存在了,这里就不在赘述了.这边文章主要讲的是作为一个主题的开发者,如何让你的主题具有更好的扩展性,在用户自定义修改主题后,能够更加平易升级主题. 问题 ...
- 后端程序员必会的前端知识-01:html、css
第一章. HTML 与 CSS HTML 是什么:即 HyperText Markup language 超文本标记语言,咱们熟知的网页就是用它编写的,HTML 的作用是定义网页的内容和结构. Hyp ...
- Scipy快速入门
Scipy快速入门 注意事项 图床在国外,配合美区.日区网络使用更佳,如遇图片加载不出来,考虑换个VPN吧. 监修中敬告 本文处于Preview阶段,不对文章内容负任何责任,如有意见探讨欢迎留言. 联 ...