C++特性

回调

回调函数就是一个被作为参数传递的函数。在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内对应的值

外部变量:指的是和当前 lambda 表达式位于同一作用域内的所有局部变量
全局部变量:无论为什么访问方式都可以对其进行修改
引用:如果外部变量的访问方式为引用则可以在函数内部使用修改外部变量
传值:如若外部变量的访问方式为传值则只能在函数内使用变量不能修改外部变量,要想修改外部变量则可借助mutable关键字,但是修改的仅是拷贝真正的外部变量不会变化
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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/158082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python】【Torch】神经网络中各层输出的特征图可视化详解和示例

本文对神经网络各层特征图可视化的过程进行运行示例&#xff0c;方便大家使用&#xff0c;有助于更好的理解深度学习的过程&#xff0c;尤其是每层的结果。 神经网络各层特征图可视化的好处和特点如下&#xff1a; 可视化过程可以了解网络对图像像素的权重分布&#xff0c;可…

PyCharm玩转ESP32

想必玩ESP32的童鞋都知道Thonny&#xff0c;当然学Python的童鞋用的更多的可能是PyCharm和VsCode Thonny和PyCharm的对比 对于PyCharm和VsCode今天不做比较&#xff0c;今天重点说一下用PyCharm玩转ESP32&#xff0c;在这之前我们先对比下Thonny和PyCharm的优缺点 1、使用Tho…

【HarmonyOS】低代码平台组件拖拽使用技巧之常用基础组件(下)

【关键字】 HarmonyOS、低代码平台、组件拖拽、代码编辑器 1、写在前面 本篇接着上一篇的内容来介绍&#xff0c;继续来看我们的登录页面的下半部分的使用&#xff0c;本篇会介绍按钮组件、单选框组件的拖拽使用&#xff0c;还会介绍代码编辑器的使用&#xff0c;最后会完成登…

【云栖 2023】姜伟华:Hologres Serverless 之路——揭秘弹性计算组

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;姜伟华 | 阿里云计算平台事业部资深技术专家、阿里云实时数仓 Hologres 研发负责人 演讲主题&#xff1a;Hologres Serverless 之路——揭秘弹性计算组 实时化成为…

PDF文件无密码,如何解密?

PDF文件有两种密码&#xff0c;一个打开密码、一个限制编辑密码&#xff0c;因为PDF文件设置了密码&#xff0c;那么打开、编辑PDF文件就会受到限制。想要解密&#xff0c;我们需要输入正确的密码&#xff0c;但是有时候我们可能会出现忘记密码的情况&#xff0c;或者网上下载P…

怎么做好品牌营销,小红书爆款笔记怎么做?

只要在小红书平台进行传播&#xff0c;能够尽可能多的创造爆款笔记&#xff0c;就是所有品牌方和达人的目标。今天来马文化传媒为大家分享下怎么做好品牌营销&#xff0c;小红书爆款笔记怎么做&#xff1f; 一、判断爆款笔记的三大指标 判断一篇笔记是否是爆款笔记&#xff0c;…

【网络通信】探索TCP通信与UDP通信的奥妙

&#x1f33a;个人主页&#xff1a;Dawn黎明开始 &#x1f380;系列专栏&#xff1a;Java ⭐每日一句&#xff1a;知不足而奋进&#xff0c;望远山而前行 &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️ 文章目录 一.&#x…

QQ微云在哪里打开?答案在这,记得收藏!

QQ微云是腾讯推出的一项智能云服务&#xff0c;它可以帮助大家储存照片、视频、文档等重要文件&#xff0c;以防数据丢失。用户们可以免费使用QQ微云&#xff0c;并且可以随时随地上传或下载自己的文件。那么&#xff0c;qq微云在哪里打开&#xff1f;今天&#xff0c;就让我们…

Python 装饰器用法详解

目录 一、基本概念 二、语法形式 三、用法示例 1、用于日志记录 2、用于性能测试 3、用于事务处理 4、用于缓存结果 5、用于权限验证 总结 Python装饰器是Python中一种非常有用且强大的工具&#xff0c;它允许我们在不修改原有函数或类的基础上&#xff0c;对它们进行…

SOLIDWORKS实用技巧——工程图模板替换

概述 工程师常在出图时选择最佳模板&#xff0c;在编辑一段时间后&#xff0c;发现需要更改图纸大小&#xff0c;怎样更改图纸大小还不影响现有工作。你是否也有此类问题&#xff1f; 那么&#xff0c;新建工程图时的模板从哪里来&#xff1f;如何轻松替换已有工程图的图纸格…

IDEA-SVN合并分支到主干

IDEA-SVN合并branch分支到主干master 1.选择VCS的 Integrate Project 2.选择分支合并 Source1 是合并后的分支 , 主分支 master Source2 是被合并的分支 , 分支 branch Try merge 可以尝试是否可以能够被合并,并且无冲突 3.合并完成后当前项目会出现需要提交的内容,检查一…

CSS特效014: hover后左右开门的效果

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

汽车级芯片NCV7518MWATXG 可编程六沟道低压侧 MOSFET预驱动器 特点、参数及应用

NCV7518MWATXG 可编程六沟道低压侧 MOSFET 预驱动器属于 FLEXMOS™ 汽车级产品&#xff0c;用于驱动逻辑电平 MOSFET。该产品可通过串行 SPI 和并行输入组合控制。该器件提供 3.3 V/5 V 兼容输入&#xff0c;并且串行输出驱动器可以采用 3.3 V 或 5 V 供电。内部通电重置提供受…

【Linux 进入救援模式】

要在CentOS 7中进入单用户模式并修改IP地址&#xff0c;可以按照以下步骤进行操作&#xff1a; 在启动过程中按下键盘上的“e”键进入GRUB编辑器。 找到启动项中以“linux16”或“linuxefi”开头的行&#xff0c;并在该行末尾添加“单用户模式&#xff08;single&#xff09;…

InfoNCE Loss公式及源码理解

InfoNCE Loss公式及源码理解–从交叉熵损失谈起 当谈论到信息论中的损失函数时&#xff0c;InfoNCE&#xff08;Noise Contrastive Estimation&#xff09;和交叉熵损失都是两个关键的概念。它们不仅在衡量概率分布之间的差异方面发挥着重要作用&#xff0c;而且在深度学习的自…

做进销存什么软件好用

进销存软件是企业管理库存、采购和销售等环节的信息化管理系统&#xff0c;对于企业的运营和管理具有重要的意义。在选择进销存软件时&#xff0c;需要考虑以下因素&#xff1a; 功能需求&#xff1a;选择能够满足企业实际需求的进销存软件。例如&#xff0c;系统是否支持商品…

边缘计算系统设计与实践

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 随着物联网、大数据和人工智能等技术的快速发展…

使用低代码可视化开发平台快速搭建应用

目录 一、JNPF可视化平台介绍 二、搭建JNPF可视化平台 【表单设计】 【报表设计】 【流程设计】 【代码生成器】 三、使用JNPF可视化平台 1.前后端分离&#xff1a; 2.多数据源&#xff1a; 3.预置功能&#xff1a; 4.私有化部署&#xff1a; 四、总结 可视化低代码…

【云原生】Spring Cloud Alibaba 之 Gateway 服务网关实战开发

目录 一、什么是网关 ⛅网关的实现原理 二、Gateway 与 Zuul 的区别&#xff1f; 三、Gateway 服务网关 快速入门 ⛄需求 ⏳项目搭建 ✅启动测试 四、Gateway 断言工厂 五、Gateway 过滤器 ⛽过滤器工厂 ♨️全局过滤器 六、源码地址 ⛵小结 一、什么是网关 Spri…

打包项目报错:程序包javax.servlet不存在

背景&#xff1a; WebService项目在没有配置Tomcat的情况下重新打包&#xff0c;由于是直接导入别人写好的项目&#xff0c;没有配置其他环境&#xff0c;所以报错程序包javax.servlet不存在 解决方法&#xff1a; 找到servlet-api.jar包&#xff0c;导入到现有项目的SDK 重…