回调
回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。
回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。同时,有一些需求必须要使用回调函数来实现。
非常知名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。
反射机制
#include <map>
#include <iostream>
#include <string>
using namespace std;typedef void* (*PTRCreateObject)(void); class ClassFactory {
private: map<string, PTRCreateObject> m_classMap ; ClassFactory(){}; //构造函数私有化public: void* getClassByName(string className); void registClass(string name, PTRCreateObject method) ; static ClassFactory& getInstance() ;
};void* ClassFactory::getClassByName(string className){ map<string, PTRCreateObject>::const_iterator iter; iter = m_classMap.find(className) ; if ( iter == m_classMap.end() ) return NULL ; else return iter->second() ;
} void ClassFactory::registClass(string name, PTRCreateObject method){ m_classMap.insert(pair<string, PTRCreateObject>(name, method)) ;
} ClassFactory& ClassFactory::getInstance(){static ClassFactory sLo_factory; return sLo_factory ;
} class RegisterAction{
public:RegisterAction(string className,PTRCreateObject ptrCreateFn){ClassFactory::getInstance().registClass(className,ptrCreateFn);}
};#define REGISTER(className) \className* objectCreator##className(){ \return new className; \} \RegisterAction g_creatorRegister##className( \#className,(PTRCreateObject)objectCreator##className)// test class
class TestClass{
public:void m_print(){cout<<"hello TestClass"<<endl;};
};
REGISTER(TestClass);int main(int argc,char* argv[]) {TestClass* ptrObj=(TestClass*)ClassFactory::getInstance().getClassByName("TestClass");ptrObj->m_print();
}
仿函数/函数对象
想象一下这种场景,当我们有一个过滤函数
Process
是用来过滤数组中大于10的元素,我们可以为“比较元素是否大于10”这个操作定义成一个函数F
,作为函数指针传入前面的过滤函数中。如果此时要求把"10"也作为参数传入,可能就不那么美妙了。这个过滤函数
Process
可能来自第三方库,我们并不能直接修改它的参数定义,那我们只好把"10"这个值定义成一个全局变量,再在函数F
中调用。这种做法虽然可行,但是我们被迫要维护一个全局变量,容易出错。比如有一个同名的全局变量,那就会导致命名空间污染。总之,更优雅的方式就应运而生了,它就是仿函数。
仿函数,functor,又称函数对象,是指实现一个简单的类,并且重载了
operator()
操作符,该类的实例化对象就可以行使函数的功能了。以上例子,我们就可以定义个仿函数,在构造函数中将"10"作为参数传入函数对象内,由函数对象内部进行维护。同时重载一个
operator()
操作符,在函数体中使用这个"10"即可。仿函数的优点是能做到封装,功能扩展起来非常方便,而回调函数则没有办法。所以在STL的实现中,运用了大量仿函数来实现谓词。
//一元谓词——回调函数
void showFunc(int item) {std::cout << "调用回调函数:" << item <<< std::endl;
}//一元谓词——仿函数
class showFuncObj {
public:showFuncObj() : m_count(0) {}void operator()(int item) {std::cout << "调用仿函数:" << item << std::endl;m_count++;}void count() { return m_count; }
private:m_count;
};int main() {std::vector<int> vec {1,2,3,4,5,6,7};for_each(vec.begin(), vec.end(), showFunc);showFuncObj s;s = for_each(vec.begin(), vec.end(), s); //仿函数是按值传参s.count();return 0;
}
std::for_each()
C++ STL中 for_each 算法存在的意义是什么? - 知乎
函数接受三个参数:给定范围的起始迭代器、终止迭代器和一个可调用对象。它通过循环遍历范围内的每个元素,并将该元素传递给可调用对象进行处理。
std::for_each 相较于for而言,它可以帮助在STL容器中的每个元素都执行一次fn()函数
C++11
range-based for loops
C++11 新特性之Range-based for loops_51CTO博客_C++11新特性
vector<int> vec;
vec.push_back( 10 );
vec.push_back( 20 );for (int i : vec )
{cout << i;
}
vector vv{1,2,3,4,5,6,7,8,9,10};for_each(vv.begin(), vv.end(), [](auto item){ // 可以,但没必要fmt::print("{}", item);});for (auto it=vv.begin(); it!=vv.end(); it++) { // 不推荐fmt::print("{}", *it);}for (auto item : vv) { // 推荐fmt::print("{}", item);}
std::function
函数包装器,类模版std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。
std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。
std::function可以说是函数指针的超集,它除了可以指向全局和静态函数,还可以指向彷函数,lambda表达式,类成员函数,甚至函数签名不一致的函数,可以说几乎所有可以调用的对象都可以当做std::function,当然对于后两个需要使用std::bind进行配合
std::function可以指向类成员函数和函数签名不一样的函数,其实,这两种函数都是一样的,因为类成员函数都有一个默认的参数,this,作为第一个参数,这就导致了类成员函数不能直接赋值给std::function,这时候我们就需要std::bind了,简言之,std::bind的作用就是转换函数签名,将缺少的参数补上,将多了的参数去掉,甚至还可以交换原来函数参数的位置
- 一旦bind补充了缺失的参数,那么以后每次调用这个function时,那些原本缺失的参数都是一样的,举个栗子,上面代码中callback6,我们每次调用它的时候,第二个参数都只会是100。
- 正因为第一点,所以假如我们是在iOS程序中使用std::bind传入一个缺失参数,那么我们转化后的那个function会持有那些缺失参数,这里我们需要防止出现循环引用导致内存泄漏。
- std::function与std::bind使用总结-腾讯云开发者社区-腾讯云
任何东西都会有优缺点,std::function填补了函数指针的灵活性,但会对调用性能有一定损耗,经测试发现,在调用次数达10亿次时,函数指针比直接调用要慢2秒左右,而std::function要比函数指针慢2秒左右,这么少的损耗如果是对于调用次数并不高的函数,替换成std::function绝对是划得来的。
//包装仿函数
class Functor {
public:int operator()(int a) { return a; }
};int main() {auto f = std::function<int(int)>(Functor{});std::cout << "f(1) = " << f(1) << std::endl;return 0;
}
std::bind
是一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。
bind接收的第一个参数必须是一个可调用的对象f,包括函数、函数指针、函数对象、和成员函数指针,之后bind最多接受9个参数,参数数量必须与f的参数数量相等,这些参数被传递给f作为入参。 绑定完成后,bind会返回一个函数对象,它内部保存了f的拷贝,具有operator(),返回值类型被自动推导为f的返回类型。在发生调用时这个函数对象将把之前存储的参数转发给f完成调用。
std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:
- 将可调用对象和其参数绑定成一个彷函数;
- 只绑定部分参数,减少可调用对象传入的参数。
闭包
闭包(Closure)可以被理解为一个附带数据的操作
闭包,closure,一般是指带有状态的函数,这里的状态指的是调用环境的上下文。一个函数带上了状态,就是闭包。那么闭包就需要有捕获并持有外部作用域变量的能力,闭包状态的捆绑发生在运行时。在C++中,闭包的实现方式包括仿函数、std::bind()
绑定器以及lambda表达式。
仿函数的必包
C++ 98 中并没有严格意义上的闭包,但我们可以用仿函数(Functor)来模拟闭包的行为;仿函数即一个重载了小括号操作符的类,这个类拥有与函数相近的行为方式,它拥有自己的私有成员变量,
传统的实现闭包的方式,但是并不方便,并不能隐式捕获全体外部作用域变量,需要每个变量都对应构造函数的参数,而且每一段闭包代码都要单独定义一个仿函数的类。
std::bind()绑定器
ambda表达式
lambda并不是完美的闭包,但是它提供了多种捕获方式,完美结合了C++的语言特性。因为捕获变量并不能真正延长变量的生命周期,它只是提供了复制和引用的捕获语义。因为语言特性的关系(RAII),C++无法延长变量作用域,因为局部变量永远都需要在作用域结束后析构
匿名函数
-
lambda表达式是C++11引入的新特性,在编译器层面本质是一种仿函数。值传递捕获的参数转成对应类型的成员变量,引用传递捕获的参数转换成对应类型指针的成员变量。
-
捕获可以分为按值捕获和按引用捕获。非局部变量,如静态变量、全局变量等可以不经捕获,直接使用;
-
lamdba
表达式产生的是函数对象/仿函数,把匿名函数看作一个类对象就行上面就很好理解啦 -
类似参数传递,变量的捕获方式可以是值或引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷。由于被捕获变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值
int main()
{int var = 5;static int base = 3;function<int()> f = [=] () mutable -> int {var++;base++;return var + base;};auto ret1 = f();auto ret2 = f();cout << "ret1 = " << ret1 << " ret2 = " << ret2 << endl << " var = " << var << " base = " << base << endl;return 0;
}//结果
//ret1 = 10 ret2 = 12(*importent*)
//var = 5 base = 5
mutable关键字的作用:对 [=] 捕获的变量进行去const操作,但是不会影响外部变量
1. lambda匿名函数,捕获变量的时机在第一次定义的时候,而不是多次调用的时候。调用两次f(), 并不会捕获两次外部变量
2. lambda匿名函数其实是一个函数对象,所以lambda定义时按值捕获的时候,变量已经存在与这个lambda函数对象里面了,因此后续多次调用该lambda匿名函数的时候,都是修改的对象里面的值。
左值右值
万能引用与完美转发-CSDN博客
#include<iostream>using std::cout; using std::endl;void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<class T>
void Perfect(T&& t)
{Fun(t);//会一直调用左值Fun(std::forward<T>(t));
}int main()
{Perfect(1);int a = 5;Perfect(a);Perfect(std::move(a));const int b = 10;Perfect(b);Perfect(std::move(b));return 0;
}
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。
只要右值传参,就一定要使用完美转发
std::forward传入的左值,传递给调用函数仍然为左值,传入的右值,传递给调用函数的仍为右值,不会退化为左值。
模版
编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
可变参数模板
C++11:可变参数 - 简书
可变模版参数函数
可变模版参数类
在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。
变参数模板,顾名思义就是参数个数和类型都可能发生变化的模板,要实现这一点,那就必须要使用模板形参包。
template <class ...Args>
void Function(Args... args){}
Args:是模板参数包。
args:函数形参参数包
声明Args… args这个参数包中可能有0-任意个参数
方法一递归展开参数包的方法如下:
#include<iostream>using std::cout; using std::endl;void FunctionArg() { cout << endl; }template <class T,class ...Args>
void FunctionArg(T value,Args... args)
{cout << "参数个数为:" << sizeof...(args) << endl;cout << value << endl;FunctionArg(args...);
}template <class ...Args>
void Function(Args... args)
{FunctionArg(args...);
}int main()
{Function(std::string("sort"), 1, 'A');Function(1, 3, 2.34);
}
模板可变参数并不支持Args[i]来找参数
同时模板在展开参数时也不支持if语句。
因为if与else是逻辑代码,编译器在展开参数包的时是编译过程,模板在推演类型。同理也可以解释Args[i]型找参数
可变参数模板在实际的使用中,更多还是结合完美转发来使用,实现对象的统一构造或者接口调用封装等。可变参数的存在,使得模板接口的灵活度提升了一个档次
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }
RAII (Resource Acquisition Is Initialization)
RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
RAII是C++的发明者Bjarne Stroustrup提出的概念,“资源获取即初始化”也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
简单而言RAII就是指资源在我们拿到时就已经初始化,一旦不在需要该资源就可以自动释放该资源。
对于C++来说,资源在构造函数中初始化(可以再构造函数中调用单独的初始化函数),在析构函数中释放或清理。常见的情形就是在函数调用中创建C++对象时分配资源,在C++对象出了作用域时将其自动清理和释放(不管这个对象是如何出作用域的,不管是否因为某个中间步骤不满足条件而导致提前返回,也不管是否正常走完全部流程后返回)。
RAII 要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数,完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
decltype的作用
https://www.cnblogs.com/soloveu/p/14607370.html
泛型编程中结合auto,用于追踪函数的返回值类型,函数写为尾置返回类型.
这也是decltype最大的用途了。
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty)
{return x*y;
}
NULL与nullptr区别
用nullptr
通信协议
mcpack,pb
QT
QT信号与槽。
Qt原理-窥探信号槽的实现细节 - 知乎
QT事件
QT-事件循环机制 - 掘金
【精选】Qt QEvent 介绍-CSDN博客