【c++11】包装器
🔥个人主页:Quitecoder
🔥专栏:c++笔记仓
包装器(Wrapper) 是一个常见的编程设计模式,通常用于封装或“包装”某个现有的对象、函数、数据结构或者操作,以提供额外的功能或简化接口。在不同的上下文中,包装器可能有不同的实现方式和目的,但核心思想都是“将现有功能封装起来,以实现更强的扩展性、易用性或者功能分离”。
目录
- 1.function包装器
- 类成员函数与普通函数的区别
- **为什么非静态成员函数不能直接作为函数指针传递?**
- 2.bind
- **基本语法**
- **返回值**
- **常见用法示例**
- 1. **绑定普通函数的参数**
- 2. **绑定成员函数**
- 3. **绑定成员函数与对象实例**
- 4. **使用多个占位符**
- 5. **绑定 Lambda 表达式**
1.function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。那么我们来看看,我们为什么需要function呢?
ret = func(x);
上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!为什么呢?
template<class F, class T>
T useF(F f, T x)
{static int count=0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}
useF
是一个模板函数,它接受:
- 一个函数对象
f
(可以是普通函数、函数对象或 Lambda 表达式)。 - 一个参数
x
,这个参数的类型是T
,将被传递给函数对象f
。
从结果可以看出,count
的值在每次调用不同 useF
时都被重置为 1,且 count
的地址在每次调用中都不同。这说明 静态变量 count
并没有在多个调用之间共享状态,而是每次调用 useF
都生成了一个独立的 count
变量。
为什么 count
的值和地址不共享?
在模板函数中,静态变量的生命周期是与模板实例相关联的。这意味着每次为不同的模板参数组合生成一个模板实例时,静态变量 count
都是独立的。
模板实例化的过程
-
在第一次调用
useF(f, 11.11)
时,模板参数F
被推导为double (*)(double)
(函数指针),T
被推导为double
。这会实例化一个useF<double (*)(double), double>
模板函数。- 对应的
count
是useF<double (*)(double), double>
的静态变量,值为 1,地址为0056B428
。
- 对应的
-
在调用
useF(Functor(), 11.11)
时,模板参数F
被推导为Functor
,T
仍然是double
。这会实例化另一个独立的模板函数useF<Functor, double>
。- 对应的
count
是useF<Functor, double>
的静态变量,值为 1,地址为0056B42C
。
- 对应的
-
在调用
useF([](double d) -> double { return d / 4; }, 11.11)
时,模板参数F
被推导为一个特定的 Lambda 类型(Lambda 表达式的类型是匿名的),T
为double
。这又会实例化一个新的模板函数useF<LambdaType, double>
。- 对应的
count
是useF<LambdaType, double>
的静态变量,值为 1,地址为0056B430
。
- 对应的
静态变量在模板中的作用域
- 每个模板实例的静态变量是独立的,不会共享状态。
- 对于每一种
F
和T
的组合,都会实例化一个独立版本的useF
函数,其静态变量count
也是独立的。
std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
他不是定义可调用对象,而是包装可定义对象
int f(int a, int b)
{return a + b;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{// 函数名(函数指针)function<int(int, int)> func1 = f;cout << func1(1, 2) << endl;// 函数对象function<int(int, int)> func2 = Functor();cout << func2(1, 2) << endl;// lambda表达式function<int(int, int)> func3 = [](const int a, const int b){return a + b; };cout << func3(1, 2) << endl;// 类的成员函数function<int(int, int)> func4 = &Plus::plusi;cout << func4(1, 2) << endl;function<double(Plus, double, double)> func5 = &Plus::plusd;cout << func5(Plus(), 1.1, 2.2) << endl;return 0;
}
类成员函数与普通函数的区别
- 普通函数
int f(int a, int b)
{return a + b;
}
- 这是一个普通的全局函数。
- 普通函数的调用是直接的:
f(1, 2)
,它不依赖于对象或者类的上下文。 std::function
可以直接接受普通函数指针,因此不需要加取地址符(&
)来指明是函数指针。
- 类的静态成员函数
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
plusi
是 静态成员函数,属于类Plus
,但它不依赖于对象实例。- 静态成员函数的调用方式和普通函数类似,可以通过类名直接访问
Plus::plusi
,但也可以通过对象实例调用。静态成员函数的行为类似于普通的全局函数,因此它可以作为一个普通函数来传递。
为什么静态成员函数要加取地址符 &
?
在调用静态成员函数时,我们通常需要通过类名来指明该函数是属于类的静态函数,而不是实例成员函数。因此,即使是静态成员函数,它也需要通过取地址符 &
来指定其指针类型。
function<int(int, int)> func4 = &Plus::plusi;
- 这里,
&Plus::plusi
是静态成员函数的指针,告诉std::function
func4
要存储的是plusi
函数的地址。 - 虽然
plusi
是静态成员函数,但它依然是一个函数,并且它的签名是int(int, int)
,和普通函数一样,因此我们使用取地址符&
来获取函数指针。
- 类的非静态成员函数
function<double(Plus, double, double)> func5 = &Plus::plusd;
plusd
是一个 非静态成员函数,它依赖于类的实例来调用,因为它需要访问类实例的成员数据(如果有的话)。- 非静态成员函数的调用需要通过对象实例来绑定:
obj.plusd(a, b)
。为了将非静态成员函数作为函数指针传递,必须先提供一个对象实例来进行绑定。
为什么非静态成员函数不能直接作为函数指针传递?
非静态成员函数不是普通的全局函数,它是绑定到类的实例上的。也就是说,调用一个非静态成员函数需要一个类的实例,因此它的地址实际上是包含了实例的上下文的。这种成员函数的指针通常被称为成员函数指针,它和普通函数指针有很大的区别。
在 C++ 中,非静态成员函数必须通过对象实例来调用。例如:
Plus p;
p.plusd(1.1, 2.2); // 通过对象 p 来调用
而在 std::function
中,传递成员函数指针时,需要额外提供一个对象实例或引用来绑定成员函数。可以通过如下方式来实现:
function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
- 这里的
Plus()
是一个临时对象,它被传递给func5
作为对象实例,从而调用plusd
成员函数。
成员函数不能像普通函数一样直接作为指针传递,它们是绑定到对象的,因此不能直接传递函数指针。非静态成员函数需要通过对象实例来绑定,因此我们在 std::function
中也需要传递一个对象实例来确保函数能够正确调用。
#include <functional>
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名std::function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;// 函数对象std::function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;// lambda表达式std::function<double(double)> func3 = [](double d)->double { return d /4; };cout << useF(func3, 11.11) << endl;return 0;
}
包装器解决了模版实例化多份的问题
题目链接:逆波兰表达式
题目描述:
第一种是没有包装器的做法,遇见数字加进去,遇见符号计算
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(auto& str:tokens){if(str == "+" || str == "-" || str == "*" || str == "/"){int right = st.top();st.pop();int left = st.top();st.pop();switch(str[0]){case '+':st.push(left+right);break;case '-':st.push(left-right);break;case '*':st.push(left*right);break;case '/':st.push(left/right);break;}}else{// 1、atoi itoa// 2、sprintf scanf// 3、stoi to_string C++11st.push(stoi(str));} }return st.top();}
};
下面是用包装器
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;map<string, function<int(int, int)>> opFuncMap ={{ "+", [](int i, int j) {return i + j; } },{ "-", [](int i, int j) {return i - j; } },{ "*", [](int i, int j) {return i * j; } },{ "/", [](int i, int j) {return i / j; } }};for (auto& str : tokens){if (opFuncMap.find(str) != opFuncMap.end()){int right = st.top();st.pop();int left = st.top();st.pop();st.push(opFuncMap[str](left, right));}else{// 1、atoi itoa// 2、sprintf scanf// 3、stoi to_string C++11st.push(stoi(str));}}return st.top();}
};
2.bind
std::bind
是 C++11 引入的一个函数模板,用于创建一个新的可调用对象(通常是函数对象)。这个函数对象“绑定”了原始函数的一些参数,并返回一个新的函数,可以通过新的参数进行调用。可以把它看作是部分应用(partial application)的一种实现。 std::bind
允许我们预先绑定一些参数,使得我们可以方便地创建定制化的、部分应用的函数。
基本语法
std::bind(callable, arg1, arg2, ..., argN)
- callable:可以是普通函数、函数对象、成员函数、或者 Lambda 表达式等。
- arg1, arg2, …, argN:是你想要绑定的参数,
std::bind
会在新的函数调用时预先固定这些参数的值。
返回值
std::bind
返回一个可调用对象(通常是函数对象),该对象能够在稍后的时间接受剩余的参数并执行绑定函数。
常见用法示例
1. 绑定普通函数的参数
假设我们有一个普通函数 add
,它接受两个整数并返回它们的和:
#include <iostream>
#include <functional> // 引入 std::bindint add(int a, int b) {return a + b;
}int main() {// 绑定 add 函数,第一个参数绑定为 10auto add10 = std::bind(add, 10, std::placeholders::_1); // _1 表示占位符,表示等待一个新的参数std::cout << add10(5) << std::endl; // 10 + 5 = 15return 0;
}
输出:
15
在这个例子中,std::bind
将 add
函数的第一个参数绑定为 10,返回一个新的函数 add10
,它只需要一个参数来完成调用。std::placeholders::_1
是一个占位符,表示 add10
需要一个新的参数来替代这个占位符。
2. 绑定成员函数
如果你想绑定一个类的成员函数,需要传递一个对象实例来调用成员函数:
#include <iostream>
#include <functional> // 引入 std::bindclass Calculator {
public:int add(int a, int b) {return a + b;}
};int main() {Calculator calc;// 绑定成员函数 add,必须提供对象实例auto bound_add = std::bind(&Calculator::add, &calc, std::placeholders::_1, std::placeholders::_2);std::cout << bound_add(3, 4) << std::endl; // 3 + 4 = 7return 0;
}
输出:
7
在这个例子中,我们使用 std::bind
绑定了类的成员函数 add
,并指定了一个对象实例 &calc
。std::placeholders::_1
和 std::placeholders::_2
表示调用时提供的两个参数。
3. 绑定成员函数与对象实例
有时你需要绑定成员函数,并且在绑定时固定对象实例,而后续调用时只需要提供其他参数。你可以通过以下方法来完成:
#include <iostream>
#include <functional> // 引入 std::bindclass Printer {
public:void print(const std::string& str) {std::cout << "Printed: " << str << std::endl;}
};int main() {Printer printer;// 绑定成员函数 print,并固定对象实例auto bound_print = std::bind(&Printer::print, &printer, std::placeholders::_1);bound_print("Hello, World!"); // 输出: Printed: Hello, World!return 0;
}
输出:
Printed: Hello, World!
在这个例子中,std::bind
将 Printer::print
成员函数与对象 printer
绑定,并返回一个新的函数 bound_print
,该函数只需传递一个字符串参数即可。
4. 使用多个占位符
std::bind
支持多个占位符。占位符的编号从 _1
开始,用于指定参数的顺序。
#include <iostream>
#include <functional> // 引入 std::bindint multiply(int a, int b, int c) {return a * b * c;
}int main() {// 绑定前两个参数,并保留最后一个参数auto bound_multiply = std::bind(multiply, 2, 3, std::placeholders::_1);std::cout << bound_multiply(4) << std::endl; // 2 * 3 * 4 = 24return 0;
}
输出:
24
在这个例子中,我们将 multiply
的前两个参数绑定为 2 和 3,使用占位符 _1
表示剩余的参数,最后通过 bound_multiply(4)
提供第三个参数。
5. 绑定 Lambda 表达式
std::bind
不仅支持函数指针,还可以绑定 Lambda 表达式:
#include <iostream>
#include <functional> // 引入 std::bindint main() {// 绑定 Lambda 表达式,固定第一个参数auto bound_lambda = std::bind([](int a, int b) { return a + b; }, 10, std::placeholders::_1);std::cout << bound_lambda(5) << std::endl; // 10 + 5 = 15return 0;
}
输出:
15
std::bind
的核心功能:将函数、成员函数或 Lambda 表达式与一些固定的参数绑定,生成一个新的可调用对象,后续调用时可以提供剩余的参数。std::placeholders::_N
:占位符用于指定绑定参数的位置,_1
表示第一个占位符,_2
表示第二个,占位符的顺序决定了参数传递的顺序。- 用途:
- 部分应用:可以在调用函数时预先固定一些参数。
- 适应某些 API 设计:例如,事件回调、适配器设计等,需要将一部分参数绑定到函数中。
- 结合算法使用:在 STL 算法(如
std::for_each
)中,结合std::bind
可以生成带有部分固定参数的自定义操作。
相关文章:

【c++11】包装器
🔥个人主页:Quitecoder 🔥专栏:c笔记仓 包装器(Wrapper) 是一个常见的编程设计模式,通常用于封装或“包装”某个现有的对象、函数、数据结构或者操作,以提供额外的功能或简化接口。…...

信息学奥赛一本通 1422:【例题1】活动安排
【题目链接】 ybt 1422:【例题1】活动安排 【题目考点】 1. 贪心 【解题思路】 该题属于区间选点问题,ybt 1324:【例6.6】整数区间 是给定一些区间,选择一些点使得每个区间范围内至少有1个点。 本题为:给定一些区…...

数据库、数据仓库、数据湖有什么不同
数据库、数据仓库和数据湖是三种不同的数据存储和管理技术,它们在用途、设计目标、数据处理方式以及适用场景上存在显著差异。以下将从多个角度详细说明它们之间的区别: 1. 数据结构与存储方式 数据库: 数据库主要用于存储结构化的数据&…...

llama.cpp LLM_CHAT_TEMPLATE_DEEPSEEK_3
llama.cpp LLM_CHAT_TEMPLATE_DEEPSEEK_3 1. LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM2. static const std::map<std::string, llm_chat_template> LLM_CHAT_TEMPLATES3. LLM_CHAT_TEMPLATE_DEEPSEEK_3References 不宜吹捧中国大语言模型的同时,又去贬低美国大语言…...

深度学习的应用场景及常用技术
深度学习作为机器学习的一个重要分支,在众多领域都有广泛的应用,以下是一些主要的应用场景及常用技术。 1.应用场景 1. 计算机视觉 图像分类 描述:对图像中的内容进行分类,识别出图像中物体所属的类别。例如,在安防领…...

小程序项目-购物-首页与准备
前言 这一节讲一个购物项目 1. 项目介绍与项目文档 我们这里可以打开一个网址 https://applet-base-api-t.itheima.net/docs-uni-shop/index.htm 就可以查看对应的文档 2. 配置uni-app的开发环境 可以先打开这个的官网 https://uniapp.dcloud.net.cn/ 使用这个就可以发布到…...

网件r7000刷回原厂固件合集测评
《网件R7000路由器刷回原厂固件详解》 网件R7000是一款备受赞誉的高性能无线路由器,其强大的性能和可定制性吸引了许多高级用户。然而,有时候用户可能会尝试第三方固件以提升功能或优化网络性能,但这也可能导致一些问题,如系统不…...

微信登录模块封装
文章目录 1.资质申请2.combinations-wx-login-starter1.目录结构2.pom.xml 引入okhttp依赖3.WxLoginProperties.java 属性配置4.WxLoginUtil.java 后端通过 code 获取 access_token的工具类5.WxLoginAutoConfiguration.java 自动配置类6.spring.factories 激活自动配置类 3.com…...

[STM32 - 野火] - - - 固件库学习笔记 - - -十三.高级定时器
一、高级定时器简介 高级定时器的简介在前面一章已经介绍过,可以点击下面链接了解,在这里进行一些补充。 [STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器 1.1 功能简介 1、高级定时器可以向上/向下/两边计数,还独有一个重复计…...

后台管理系统通用页面抽离=>高阶组件+配置文件+hooks
目录结构 配置文件和通用页面组件 content.config.ts const contentConfig {pageName: "role",header: {title: "角色列表",btnText: "新建角色"},propsList: [{ type: "selection", label: "选择", width: "80px&q…...

8.原型模式(Prototype)
动机 在软件系统中,经常面临着某些结构复杂的对象的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。 之前的工厂方法和抽象工厂将抽象基类和具体的实现分开。原型模式也差不多&#…...

Python-基于PyQt5,pdf2docx,pathlib的PDF转Word工具(专业版)
前言:日常生活中,我们常常会跟WPS Office打交道。作表格,写报告,写PPT......可以说,我们的生活已经离不开WPS Office了。与此同时,我们在这个过程中也会遇到各种各样的技术阻碍,例如部分软件的PDF转Word需要收取额外费用等。那么,可不可以自己开发一个小工具来实现PDF转…...

13 尺寸结构模块(size.rs)
一、size.rs源码 // Copyright 2013 The Servo Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE…...

STM32单片机学习记录(2.2)
一、STM32 13.1 - PWR简介 1. PWR(Power Control)电源控制 (1)PWR负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能; (2)可编程电压监测器(…...

CSS 样式化表格:从基础到高级技巧
CSS 样式化表格:从基础到高级技巧 1. 典型的 HTML 表格结构2. 为表格添加样式2.1 间距和布局2.2 简单的排版2.3 图形和颜色2.4 斑马条纹2.5 样式化标题 3. 完整的示例代码4. 总结 在网页设计中,表格是展示数据的常见方式。然而,默认的表格样式…...

【python】tkinter实现音乐播放器(源码+音频文件)【独一无二】
👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 【python】tkinter实现音乐播放器(源码…...

javascript常用函数大全
javascript函数一共可分为五类: •常规函数 •数组函数 •日期函数 •数学函数 •字符串函数 1.常规函数 javascript常规函数包括以下9个函数: (1)alert函数:显示一个警告对话框,包括一个OK按钮。 (2)confirm函数:显…...

C#属性和字段(访问修饰符)
不同点逻辑性/灵活性存储性访问性使用范围安全性属性(Property)源于字段,对字段的扩展,逻辑字段并不占用实际的内存可以被其他类访问对接收的数据范围做限定,外部使用增加了数据的安全性字段(Field)不经过逻辑处理占用内存的空间及位置大部分字段不能直接被访问内存使用不安全 …...

DeepSeek为什么超越了OpenAI?从“存在主义之问”看AI的觉醒
悉尼大学学者Teodor Mitew向DeepSeek提出的问题,在推特上掀起了一场关于AI与人类意识的大讨论。当被问及"你最想问人类什么问题"时,DeepSeek的回答直指人类存在的本质:"如果意识是进化的偶然,宇宙没有内在的意义&a…...

langchain基础(二)
一、输出解析器(Output Parser) 作用:(1)让模型按照指定的格式输出; (2)解析模型输出,提取所需的信息 1、逗号分隔列表 CommaSeparatedListOutputParser:…...

数据库安全管理中的权限控制:保护数据资产的关键措施
title: 数据库安全管理中的权限控制:保护数据资产的关键措施 date: 2025/2/2 updated: 2025/2/2 author: cmdragon excerpt: 在信息化迅速发展的今天,数据库作为关键的数据存储和管理中心,已经成为了企业营运和决策的核心所在。然而,伴随着数据规模的不断扩大和数据价值…...

Leetcode598:区间加法 II
题目描述: 给你一个 m x n 的矩阵 M 和一个操作数组 op 。矩阵初始化时所有的单元格都为 0 。ops[i] [ai, bi] 意味着当所有的 0 < x < ai 和 0 < y < bi 时, M[x][y] 应该加 1。 在 执行完所有操作后 ,计算并返回 矩阵中最大…...

【Proteus】NE555纯硬件实现LED呼吸灯效果,附源文件,效果展示
本文通过NE555定时器芯片和简单的电容充放电电路,设计了一种纯硬件实现的呼吸灯方案,并借助Proteus仿真软件验证其功能。方案无需编程,成本低且易于实现,适合电子爱好者学习PWM(脉宽调制)和定时器电路原理。 一、呼吸灯原理与NE555功能分析 1. 呼吸灯核心原理 呼吸灯的…...

SAP HCM insufficient authorization, no.skipped personnel 总结归纳
导读 权限:HCM模块中有普通权限和结构化权限。普通权限就是PFCG的权限,结构化权限就是按照部门ID授权,颗粒度更细,对分工明细化的单位尤其重要,今天遇到的问题就是结构化权限的问题。 作者:vivi,来源&…...

五. Redis 配置内容(详细配置说明)
五. Redis 配置内容(详细配置说明) 文章目录 五. Redis 配置内容(详细配置说明)1. Units 单位配置2. INCLUDES (包含)配置3. NETWORK (网络)配置3.1 bind(配置访问内容)3.2 protected-mode (保护模式)3.3 port(端口)配置3.4 timeout(客户端超时时间)配置3.5 tcp-keepalive()配置…...

4 [危机13小时追踪一场GitHub投毒事件]
事件概要 自北京时间 2024.12.4 晚间6点起, GitHub 上不断出现“幽灵仓库”,仓库中没有任何代码,只有诱导性的病毒文件。当天,他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒,等待不…...

Shadow DOM举例
这东西具有隔离效果,对于一些插件需要append一些div倒是不错的选择 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"utf-8"> <title>演示例子</title> </head> <body> <style&g…...

力扣动态规划-18【算法学习day.112】
前言 ###我做这类文章一个重要的目的还是记录自己的学习过程,我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非常非常高滴!!! 习题 1.下降路径最小和 题目链接:931. …...

网络基础
协议 协议就是约定 网络协议是协议中的一种 协议分层 协议本身也是软件,在设计上为了更好的模块化,解耦合,也是设计成为层状结构的 两个视角: 小白:同层协议,直接通信 工程师:同层协议&…...

使用 EXISTS 解决 SQL 中 IN 查询数量过多的问题
在 SQL 查询中,当我们面对需要在 IN 子句中列举大量数据的场景时,查询的性能往往会受到显著影响。这时候,使用 EXISTS 可以成为一种优化的良方。 问题的来源 假设我们有两个表,orders 和 customers,我们需要查询所有…...