文章目录
一、可调用对象
1. 定义
- 函数
- 函数指针
- lambda表达式
- bind创建的对象
- 重载了函数调用运算符的类
2. 使用
C++11中提供了std::function
和std::bind
统一了可调用对象的各种操作。
不同类型可能具有相同的调用形式,如:
// 普通函数
int add(int a, int b){return a+b;}
// lambda表达式
auto mod = [](int a, int b){ return a % b;}
// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};
上述三种可调用对象虽然类型不同,但是共享了一种调用形式:
int(int ,int)
std::function
就可以将上述类型保存起来,如下:
std::function<int(int ,int)> a = add;
std::function<int(int ,int)> b = mod ;
std::function<int(int ,int)> c = divide();
二、std::function对象
1. 定义
-
std::function 是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行
-
定义格式:
std::function<函数类型(参数)>
-
所需头文件:
#include<functional>
-
std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。
2. 使用
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
// 存储自由函数
std::function<void(int)> f_display = print_num; //将print_num函数指针存储在f_display中
f_display(-9); // -9
// 存储 lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42(); // 42
// 存储到 std::bind 调用的结果
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337(); // 31337
// 存储到类成员函数的调用
//类成员函数需要加&,因为传入的是指针
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1); // 314160
f_add_display(314159, 1); // 314160
// 存储到数据成员访问器的调用
std::function<int(Foo const&)> f_num = &Foo::num_; //存储数据成员
std::cout << "num_: " << f_num(foo) << '\n'; // num_: 314159
// 存储到成员函数及对象的调用
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2); // 314161
// 存储到成员函数和对象指针的调用
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3); // 314161
// 存储到函数对象的调用
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18); // 18
}
三、 std::bind
1. 定义
- 它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体.可将std::bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表.
- std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存
std::bind主要有以下几个作用:
- 将可调用对象和其参数绑定成一个仿函数
- 只绑定部分参数,减少可调用对象传入的参数
- 改变参数绑定顺序
2. 头文件
#include<functional>
3. std::bind绑定普通函数
double myDivide(double x, double y)
{
return x/y;
}
auto fn_half = std::bind(myDivide,1,2); //绑定成仿函数
std::cout << fn_half() << std::endl;
// bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。
// 因此std::bind (myDivide,1,2)等价于std::bind (&myDivide,1,2)
4. std::bind绑定一个成员函数
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, 5);
f(); // 100
}
// 第一个参数表示对象的成员函数的指针,
// 第二个参数表示对象的地址。
//必须显示的指定&Foo::print_sum,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在Foo::print_sum前添加&。
//使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 &foo;
5. std::bind绑定一个引用参数
默认情况下,bind的那些不是占位符(占位符的概念后面介绍)的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数希望以引用的方式传递,或是要绑定参数的类型无法拷贝。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std::placeholders;
using namespace std;
ostream & print(ostream &os, const string& s, char c)
{
os << s << c;
return os;
}
int main()
{
vector<string> words{"hello", "world", "this", "is", "C++11"};
ostringstream os;
char c = ' ';
for_each(words.begin(), words.end(),
[&os, c](const string & s){os << s << c;} );
cout << os.str() << endl;
ostringstream os1;
// ostream不能拷贝,若希望传递给bind一个对象,
// 而不拷贝它,就必须使用标准库提供的ref函数
for_each(words.begin(), words.end(),
bind(print, ref(os1), _1, c));
//ref函数返回一个对象,包含给定的引用,此对象是可以拷贝的。
cout << os1.str() << endl;
}
四、占位符
1. 定义
(std::placeholders std::is_placeholder std::is_bind_expression)
一个变量的占位符, 用于函数绑定时使用
定义如下:
namespace placeholders {
extern /* unspecified */ _1;
extern /* unspecified */ _2;
extern /* unspecified */ _3;
// ...
}
他的应用就是bind绑定参数时的占位符,当调用bind返回的函数对象时,带有占位符_1的参数被调用中的第一个参数替换,_2被调用中的第二个参数替换,依此类推。
2. 使用
using namespace std::placeholders;
五、nullptr与NULL
1. 定义
NULL在C的头文件中,通常定义如下:
#define NULL ((void*)0)
但是在C++中,NULL是这样定义的:
#define NULL 0
NULL在C++与C中的区别:在C++中不能将void *类型的指针隐式转换成其他指针类型
然而nullptr
并非整型类别,甚至也不是指针类型,但是能转换成任意指针类型。nullptr的实际类型是std:nullptr_t
总结:如果你想表示空指针,那么使用nullptr,而不是NULL。
注:nullptr在C++ 11中才出现。
六、shared_ptr(智能指针)
1. 定义
要确保用 new 动态分配的内存空间在程序的各条执行路径都能被释放是一件麻烦的事情。C++11 模板库的 <memory>
头文件中定义的智能指针,即 shared _ptr 模板,就是用来部分解决这个问题的。
只要将 new 运算符返回的指针 p 交给一个 shared_ptr 对象“托管”,就不必担心在哪里写delete p
语句——实际上根本不需要编写这条语句,托管 p 的 shared_ptr 对象在消亡时会自动执行delete p。而且,该 shared_ptr 对象能像指针 p —样使用,即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。
只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。
2. 使用
通过 shared_ptr 的构造函数,可以让 shared_ptr 对象托管一个 new 运算符返回的指针,写法如下:
shared_ptr<T> ptr(new T); // T 可以是 int、char、类等各种类型,用ptr托管指向T的指针
此后,ptr 就可以像 T* 类型的指针一样使用,即 *ptr 就是用 new 动态分配的那个对象。
多个 shared_ptr 对象可以共同托管一个指针 p,当所有曾经托管 p 的 shared_ptr 对象都解除了对其的托管时,就会执行delete p
。
例如下面的程序:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
int i;
A(int n):i(n) { };
~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
shared_ptr<A> sp2(sp1); //A(2)同时交由sp2托管
shared_ptr<A> sp3;
sp3 = sp2; //A(2)同时交由sp3托管 多个shared_ptr对象托管同一个指针.
cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
A * p = sp3.get(); // get返回托管的指针,p 指向 A(2)
cout << p->i << endl; //输出 2
sp1.reset(new A(3)); // reset导致托管新的指针, 此时sp1托管A(3)
sp2.reset(new A(4)); // sp2托管A(4)
cout << sp1->i << endl; //输出 3
sp3.reset(new A(5)); // sp3托管A(5),A(2)无人托管,被delete
cout << "end" << endl;
return 0;
}
输出结果:
2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed
原理:多个 sharecLptr 对象托管同一个指针。这多个 shared_ptr 对象会共享一个对共同托管的指针的“托管计数”。有 n 个 shared_ptr 对象托管同一个指针 p,则 p 的托管计数就是 n。当一个指针的托管计数减为 0 时,该指针会被释放。shared_ptr 对象消亡或托管了新的指针,都会导致其原托管指针的托管计数减 1。
shared_ptr 的 reset 成员函数可以使得对象解除对原托管指针的托管(如果有的话),并托管新的指针。原指针的托管计数会减 1。
注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针:
A* p = new A(10);
shared_ptr <A> sp1(p), sp2(p);
sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。
七、Lambda表达式
1. 用途
实际上就是提供一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的,这样的场景其实很多,所以匿名函数几乎是现代编程语言的标配
2. 基本语法
[捕获列表](参数列表) mutable(可选)异常属性 -> 返回类型{
// 函数体
}
[caputrue](params) opt -> ret { body; };
- Lambda表达式以一对中括号开始
- 跟函数定义一样,我们有参数列表
- 有函数体,里面会有return 语句
- Lambda表达式一般不需要说明返回值( 相当于auto ); 有特殊情况需要说明时,则应使用箭头语法的方式
- 每个lambda表达式都有一个全局唯一的类型,要精确捕捉lambda表达式到一个变量中,只能通过auto声明的方式
3. 基本使用
- 捕获列表
- 参数列表
- 返回类型
- 函数体
#include<iostream>
#include<algorithm> // 算法库
#include<vector>
using namespace std;
int main()
{
int c = [](int a, int b)->int{
return a + b;
}(1,2);
std::cout << c << std::endl; // 3
int d = [](int n){
return [n](int x){ // 捕获外面的参数n
return n+x;
}(2);
}(1);
std::cout << d << std::endl; // 3
// 函数式编程
auto adder = [](int n){
return [n](int x){
return n+x;
};
};
std::cout << adder(1)(2) << std::endl; // 3
}
运行:g++ -std=c++11 test.cpp
for(auto i : v)遍历容器元素
v是一个可遍历的容器或流,比如vector类型