C++11语言(三)
一、引言
上期我们介绍了C++11的大部分特性。C++11的初始化列表、auto关键字、右值引用、万能引用、STL容器的的emplace函数。
要补充的是右值引用是不能取地址的,我们程序员一定要遵守相关的语法。操作是未定义的很危险。
二、 仿函数和函数指针
我们先从仿函数的形成和函数指针的形成开始介绍起来,有了这个大家会更好的清楚lamda表达式、装配器function和绑定器bind。
仿函数顾名思义就是类似于函数,肯定不是函数,但是肯定具有和函数一样的功能。那问题来了为什么不用函数指针呢?
函数指针使用起来还是有点问题的。首先需要typedef一个固定的函数类型。没错这个函数是固定的不可变的,参数类型是固定的。那为什么我们不用上一章讲过的C语言不定参数。C语言的不定参数也需要传入类型,这个点是比较麻烦的。而且明确的就是类型不是字符串,因为不定参数是宏定义而不是真正的函数。
为了方便比较我们也来使用一下函数指针吧。
#include <stdio.h>// 函数指针的模拟,随便写的类型名。
// 打印数组。// 定义函数指针
typedef void (*ptest)(int* a, int n);void test(int* a, int n)
{for (int i = 0;i < n;++i){printf("%d ", a[i]);}printf("\n");
}void function(ptest func,int* a,int n)
{func(a,n);
}int main()
{int a[] = { 1,2.3,4,5,6,7,8 };int n = sizeof(a) / sizeof(a[0]);function(test,a,n);return 0;
}
那我们是如何实现仿函数的呢?C++用的最多的就是类和对象,利用模板的特性我们可以传入各种不同的类,那仿函数的原型毫无疑问就是类,类中包含的函数传入到另一个函数或是其他类中也可以调用内部中的函数。这样我们实现一个基本的多态。多种形态。和特殊化处理。那我们该如何命名类中的函数。既然叫函数就应该和函数差不多,不然可读性不是很好用的也麻烦,而且本来就是当函数来使用没必要取什么特殊的名字。这样我们就实现出了一个类函数。
class 类名
{public:// 运算符重载 : operator + 符号 // 就可以通过符号与类结合调用。// 这完全就是C++基于类和变量之间不同的考量。返回类型 operator () (参数){; // …………所要调用的方法和所要实现的内容。}
}; //这个分号千万不要忘记了,就把它当做是C语言定义变量。
那下面我们来做一个简单的练习。首先我们要知道C++中包含排序方法。虽然排序的方法多种多样,但是排序标准都各有不同。并不一定是基于简单的比较大小,有可能是运用了特殊的比较方法。还可能会有顺序的差异例如升序、降序。所以我们使用C++库<algorithm>中std::sort()函数进行排序。另外排序标准由我们来进行确定。
#include <iostream>
#include <algorithm>// 仿函数。
class Func
{
public:// 这里依据函数实现的不同而返回类型不同。// 还可以加入模板。template<typename T>bool operator () (T i, T j){// 升序的话是后面的数据大于前面的数据。// 我们为了演示反其道而行之。return i > j;}
};int main()
{int a[] = { 1 , 2 , 4 , 5 , 6 , 7 , 8 };int n = sizeof(a) / sizeof(a[0]);// 起始位置和末尾位置的下一个(有效位置的下一个,也就是无效位置。更确切的讲是规定范围。)// 最后一个参数是用来传递排序标准的,什么都不传的话默认是升序(缺省参数)。// 如果是STL容器的话直接用里面的begin函数和end函数。std::sort(a, a + n);// 范围forfor (int x : a){std::cout << x << " ";}std::cout << std::endl;std::sort(a, a + n, Func());for (int x : a){std::cout << x << " ";}std::cout << std::endl;return 0;
}
三、 Lamda表达式
上述的仿函数虽然与函数指针简便不少,例如不用考虑参数的类型,不用定义函数指针。但C++标准委员会还是觉得不好用,能不能出现一个匿名的函数呢?直接不用写名字。这个其实最开始是python最先搞出来的。C++看到后觉得好用直接也实现一个出来了。底层就是我们上面实现的仿函数,只是用完就丢这个特性和仿函数不一样。
[捕捉列表:值捕捉&+变量、对象=引用。值引用:变量、对象=const引用]
// 如果只有一个&,说明是引用函数中的所有变量、对象。
->返回类型(没有就省略这个->)(参数类型)
{; //…………实现的方法。
}
接下来我们还是使用std::sort()函数进行练习。
只需要更改其中的一行代码就行了。lamda表达式其实就是匿名函数(没有名字,说明接下来无法通过名字来调用,同时也意味着无法递归。(函数自己本身调用一个新的原本函数自己本身,可以理解创建新的自己分身。)
std::sort(a, a + n,[](int i,int j)
{return i > j;
});
四、 包装器function、绑定器bind介绍
这两个函数都是库<functional>文件中的类。
std::function和函数指针有着异曲同工之妙,在项目中对于函数的发封装起到非常大的作用。std::function可以与lamda表达式、仿函数和bind装配器有着不错的兼容。函数指针并不能做到这点。此外函数指针为了重复使用一般都是泛型编程(返回值或是参数都是用void*,这样就可以不用管参数的类型,参数的数量,返回值的数量和类型,但是这样出错了非常不好检查出来。)
std::bind包装器,常常用于为了一个类能够调用其他类中的类函数,或者是固定好函数的参数,实现编程的隐蔽性和泛型编程。
下面我们以打印数组来做实例对象。
#include <iostream>
#include <algorithm>
#include <functional>class Func
{
public:void print(int* a,int n){for (int i = 0; i < n; ++i){std::cout << a[i] << " ";}std::cout << std::endl;}
};int main()
{int a[] = { 1 , 2 , 4 , 5 , 6 , 7 , 8 };int n = sizeof(a) / sizeof(a[0]);// 我们尝试使用std::function包装仿函数。// 还可以和模板进行搭配。// 调用未加static的类函数需要用到类中的指针或是引用。std::function<void(Func, int*, int)> func1 = &Func::print;func1(Func(), a, n);// 绑定器可以固定参数,调用更加方便,也防止别人乱改,也通过std::placeholders库中的_n来确定该输入几个参数。// 一般可以将std::bind看作function的初始化类型。// 使用std::bind赋值给std::function的时候,记得已经在bind固定了的参数的类型从function中去掉。// 因为参数已经固定无需再传参了。std::function<void(int*,int)> func2 = std::bind(&Func::print,Func(), std::placeholders::_1,std::placeholders::_2);func2(a, n);std::function<void()> func3 = std::bind(&Func::print, Func(), a, n);// 当然可以直接将所有参数都固定,直接变为无参函数。func3();return 0;
}
五、 智能指针
我们在系统申请空间,总是要还的,所以我们常常需要手动释放它。但是这非常不方便。所以C++搞出了智能指针,自动释放申请空间或是自动关闭某个开关。但是最初的智能指针被骂的非常惨。因为C++根本没有考虑多引用、多指向的情况。unique_ptr是明确了只有一个指针指向这块空间。
shared_ptr是允许多个指针指向申请的空间,运用了引用计数的概念,初始化时申请了一块空间进行统计使用同一空间的指针数量。当引用为零时自动释放申请空间。都使用shared_ptr了,肯定赋值的对象、接受的参数肯定也是shared_ptr。
weak_ptr虚指针,防止shared_ptr指向过多,不能释放,造成内存泄漏,从而导致服务器崩溃。在shared_ptr互相指向时使用,shared_ptr不增加引用计数。相互指向造成你不释放,我也不释放的难题。不能单独使用,只能和shared_ptr一起使用。
template<class T>class unique_ptr{public:explicit unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使⽤ T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;unique_ptr(unique_ptr<T>&& sp):_ptr(sp._ptr){sp._ptr = nullptr;}unique_ptr<T>& operator=(unique_ptr<T>&& sp){delete _ptr;_ptr = sp._ptr;sp._ptr = nullptr;}private:T* _ptr;};template<class T>class shared_ptr{public:explicit shared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(new int(1)){}template<class D>
{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;//atomic<int>* _pcount; function<void(T*)> _del = [](T* ptr) {delete ptr; };};// 需要注意的是我们这⾥实现的shared_ptr和weak_ptr都是以最简洁的⽅式实现的, // 只能满⾜基本的功能,这⾥的weak_ptr lock等功能是⽆法实现的,想要实现就要 // 把shared_ptr和weak_ptr⼀起改了,把引⽤计数拿出来放到⼀个单独类型,shared_ptr // 和weak_ptr都要存储指向这个类的对象才能实现,有兴趣可以去翻翻源代码 template<class T>class weak_ptr{public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}
private:T* _ptr = nullptr;};
}