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的更多相关文章

  1. ES6相关实用特性

    本文总结ECMAScript6相关实用特性 目录 let和const 箭头函数 class 对象字段 模板字符串 解构赋值 函数参数扩展 迭代器for...of 模块加载 map和weakmap se ...

  2. C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)

    因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...

  3. C++11新特性总结 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; ...

  4. C++11新特性总结 (一)

    1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己写了,看别人写的代码估计都会有些吃力.C++ Primer5是学习C++1 ...

  5. 【转】C++11常用特性的使用经验总结

    出处 http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方 ...

  6. C++ 11 新特性

    C++11新特性:          1.auto          2.nullptr          3.for          4.lambda表达式          5.override ...

  7. [转载] C++11新特性

    C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,非常想了解一下C++11. 英文版的维基百科看起来非常费劲,而中文版维基百 ...

  8. 在C++98基础上学习C++11新特性

    自己一直用的是C++98规范来编程,对于C++11只闻其名却没用过其特性.近期因为工作的需要,需要掌握C++11的一些特性,所以查阅了一些C++11资料.因为自己有C++98的基础,所以从C++98过 ...

  9. C++11常用特性的使用经验总结

    转自:http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方 ...

  10. [转]C++11常用特性的使用经验总结

    转载出处 http://www.cnblogs.com/feng-sc C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++1 ...

随机推荐

  1. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-31-JavaScript的调用执行-上篇

    1.简介 在做web自动化时,有些情况playwright的api无法完成以及无法应对,需要通过或者借助第三方手段比如js来完成实现,比如:去改变某些元素对象的属性或者进行一些特殊的操作,本文讲解pl ...

  2. 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 ...

  3. timeSetEvent()函数定时器的使用

    1.定时器函数的使用 微软公司在其多媒体Windows中提供了精确定时器的底层API支持,利用多媒体定时器可以很精确地读出系统的当前时间,并且能在非常精确的时间间隔内完成一个事件.函数或过程的调用. ...

  4. 大数据 - MapReduce:从原理到实战的全面指南

    本文深入探讨了MapReduce的各个方面,从基础概念和工作原理到编程模型和实际应用场景,最后专注于性能优化的最佳实践. 关注[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作 ...

  5. Http的演进

    Http的演进 Http在1.1版本之前具有无状态的特点,每次请求都需要通过TCP三次握手四次挥手与服务器重新建立连接.比如某个客户端在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户请求 ...

  6. JTAG串链

  7. 有序对的LCP

    求\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n} LCP(s_i, s_j)\) 方法一 \(1.\)Trie . \(2.\)求\(cnt\),\(cnt[ ...

  8. Hexo 主题开发之自定义模板

    关于 Hexo 如何开发主题包的教程在已经是大把的存在了,这里就不在赘述了.这边文章主要讲的是作为一个主题的开发者,如何让你的主题具有更好的扩展性,在用户自定义修改主题后,能够更加平易升级主题. 问题 ...

  9. 后端程序员必会的前端知识-01:html、css

    第一章. HTML 与 CSS HTML 是什么:即 HyperText Markup language 超文本标记语言,咱们熟知的网页就是用它编写的,HTML 的作用是定义网页的内容和结构. Hyp ...

  10. Scipy快速入门

    Scipy快速入门 注意事项 图床在国外,配合美区.日区网络使用更佳,如遇图片加载不出来,考虑换个VPN吧. 监修中敬告 本文处于Preview阶段,不对文章内容负任何责任,如有意见探讨欢迎留言. 联 ...