深入篇【C++】总结<lambda表达式>与<包装器和bind>的使用与意义

深入篇【C++】总结<lambda表达式>与<包装器和bind>的使用与意义

  • 一.lambda表达式
    • 1.使用语法
    • 2.底层本质
    • 3.应用意义
  • 二.包装器(适配器)
    • 1.使用语法
    • 2.解决问题①
    • 3.解决问题②
    • 4.应用场景:指令+操作
  • 三.bind (适配器)
    • 1.调整参数位置
    • 2.绑定参数

一.lambda表达式

如果我们想对一个数组进行排序,怎么排序呢?
首先这里要分数组里的数据是什么类型,是内置类型呢?还是自定义类型呢?
如果是自定义类型,比如int类型,那么就可以直接利用大小进行比较。

int main()
{vector<int> v= { 4,1,8,5,3,7,0,9,2,6 };//给内置类型进行排序,即就是用大小进行比较sort(v.begin(), v.end());//默认是升序//如果向降序则需要传递仿函数对象sort(v.begin(), v.end(), greater<int>());
}

那如果数据是自定义类型呢?该如何进行比较呢?根据什么进行比较呢?
比如一个自定义类型商品,基本信息有名字价格和评价。

struct Goods
{string _name;   //名称double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str),_price(price),_evaluate(evaluate){}
};

那么当数组里都是商品数据时,我们想对这个数组进行排序呢?


int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };//怎么对这些商品进行排序呢?
}

首先直接将自定义类型放入sort里进行排序肯定是不行的,sort里的排序默认是根据int类型的大小进行比较的。所以这时如果想要对商品进行排序就需要使用仿函数。利用仿函数来进行比较。

//对应自定义类型的比较,就需要使用仿函数
struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());//通过价格比较--升序sort(v.begin(), v.end(), ComparePriceGreater());//通过价格比较--降序//按照价格进行比较,那我想要用评价进行比较呢?名称进行比较呢?
}

那如果我们想根据商品的评价进行比较呢?根据商品的名字进行比较呢?
这就需要再重写仿函数。


struct Compare_eval_Greater
{bool operator()(const Goods& gl, const Goods& gr){return gl._evaluate > gr._evaluate;}
};
struct Compare_eval_Less
{bool operator()(const Goods& gl, const Goods& gr){return gl._evaluate < gr._evaluate;}
};struct Compare_name_Greater
{bool operator()(const Goods& gl, const Goods& gr){return gl._name > gr._name;}
};
struct Compare_name_Less
{bool operator()(const Goods& gl, const Goods& gr){return gl._name < gr._name;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };//按照价格进行比较,那我想要用评价进行比较呢?名称进行比较呢?sort(v.begin(), v.end(), Compare_eval_Greater());//通过评价比较--降序sort(v.begin(), v.end(), Compare_eval_Less());//通过评价比较--升序sort(v.begin(), v.end(), Compare_name_Greater());//通过名字比较--降序sort(v.begin(), v.end(), Compare_name_Less());//通过名字比较--升序//每次如果要按照不同的方式进行比较时,就需要再写一个类仿函数,太麻烦了。//如果每次比较的逻辑不一样,还要去实现多个类,
}

这样写实在太复杂了,每次要按照不同的规则进行比较时就需要重写一个类。有没有更方便的方法呢?
C++11提供了lambda表达式:

int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._price > gr._price; });sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._price < gr._price; });sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._evaluate > gr._evaluate; });sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._evaluate < gr._evaluate; });//lambda表达式就是一个匿名对象
}

原本需要重写一个类,现在只要写一行类似表达式的东西就可以了。是不是很神奇。接下来我们就解析这行"表达式"。从上下面对比,这行"表达式"本质上就是一个匿名函数对象。

1.使用语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type {statement }

1.[ ]:称为捕捉列表,在lambda表达式的开头。可以捕捉上下文的变量给lambda函数使用。
2.( ):参数列表,跟函数参数列表是一样的,用来接收函数的参数的,如果函数没有要接收的参数,这个()就可以省略不用写。
3.mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性,使用该修饰符时,参数列表不可以省略。
4.->return type:函数返回值类型。跟函数返回值是类似的,不过当函数没有返回值时,可以省略,但是当有返回值时,也可以省略,因为编译器会自动推到返回值类型。
5.{ }:函数体,跟正常的函数体使用即可,这里面不仅可以使用参数外,还可以使用捕捉列表捕捉的变量。
6.最简单的lambda函数就是[] { };该函数什么都不会做。

int main()
{int a = 1, b = 2;auto fun1 = [](int a, int b) {return a + b; };//lambda函数cout << fun1(a, b) << endl;}

lambda函数是不能直接调用的,因为它是一个匿名函数,我们也不知道它的名字是什么,但如果想要调用lambda函数,我们可以利用auto将其赋值给一个变量。(auto可以自动推导lambda函数的类型)

【捕捉列表细节】:捕捉列表描述了上下文里哪些数据可以被lambda函数使用,已经使用的方式是传值还是传引用。
1.[变量]:表示以值传递方式捕捉变量
2.[&变量]:表示以传引用方式捕捉变量
3.[=]:表示以值传递方式捕获父作用域里的所有变量。
4.[&]:表示以引用方式捕捉父作用域里的所有变量。
5.捕捉列表允许多个捕捉项,之间用逗号分割。
6.不允许重复捕捉,不然会报错。

int main()
{int a = 1, b = 2;double rate1 = 10.0,rate2=10.2;auto fun1 = [rate1](int a, int b) {return (a + b)*rate1; };//捕捉列表,捕捉变量rate1后,函数体里就可以用该变量--->传值方式捕捉auto fun2 = [&rate1](int a, int b) {return (a + b) * rate1; };//捕捉列表,捕捉变量rate1后,函数体里就可以用该变量--->引用方式捕捉auto fun3 = [=](int a, int b) {return (a + b) * rate1/rate2; };//捕捉列表,捕捉全部变量,函数体里就可以用该作用域里的所有变量--->传值方式捕捉auto fun4 = [&](int a, int b) {return (a + b) * rate1 / rate2; };//捕捉列表,捕捉全部变量,函数体里就可以用该作用域里的所有变量--->引用方式捕捉cout << fun1(a, b) << endl;cout << fun2(a, b) << endl;cout << fun3(a, b) << endl;cout << fun4(a, b) << endl;}

2.底层本质

这么神奇的lambda表达式是如何搞出来的呢?其实lambda表达式并不神奇,它底层就是仿函数。实际在底层编译器对应lambda表达式的处理上,完全就和函数对象的方式处理进行的。函数对象是如何处理的呢?就是定义一个类,类里重载了()运算符重载。然后利用这个类定义个对象,该对象可以像函数一样被调用。所以如果定义了一个lambda表达式,编译器就会自动生成一个类,并在这个类里重载()运算符。
在这里插入图片描述

在这里插入图片描述
而且不同lambda函数的底层的创建的类名称是不一样的。
所以lambda函数之间是不可以互相赋值的(不同类型,无法赋值)。

3.应用意义

lambda表达式用起来还是很香的。对于那些比较规则可能有很多种的情况,非常适合使用,主要是看起来很简便,并且不需要再重写一个类,就在函数体里更改即可。

二.包装器(适配器)

可调用对象都有哪些呢?
1.函数指针
2.函数对象
3.lambda函数

1.使用语法

std::function在头文件< functional>
// 类模板原型如下
template function;
template <class Ret, class… Args>
class function<Ret(Args…)>
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

也就是当引用头文件functional后,我们就可以使用包装器function。
function与其他模板有些区别:function<返回值(参数)>。

2.解决问题①

可调用对象存在多种,当我们写一个需要传可调用对象参数的类时,使用模板,当传不同的可调用对象时就会实例化出不同的类模板,造成模板使用效率低效。

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);
}
//f函数指针
double f(double i)
{return i / 2;
}//functor()函数对象
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){ return d / 4; }, 11.11) << endl;return 0;
}

在这里插入图片描述
我们会发现useF函数模板实例化了三份。
当我们使用包装器时,将可调用对象:函数指针,函数对象,lambda函数,都可以封装在包装器里。
然后我们就可以统一调用不同的包装器(不同的包装器里包装着不同的可调用对象)。虽然是不同的包装器但是同一类型,所有最后只会实例化出一份。

int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d){ return d / 4; }, 11.11) << endl;cout << endl;function<double(double)> f1 = f;function<double(double)> f2 = Functor();function<double(double)> f3 = [](double d) { return d / 4; };//将函数指针 /函数对象 /lambda函数包装到包装器里//统一调用包装器f1,f2,f3.它们的类型都是一样的。cout << useF(f1, 11.11) << endl;cout << useF(f2, 11.11) << endl;cout << useF(f3, 11.11) << endl;return 0;
}

在这里插入图片描述

3.解决问题②

问题:如何将可调用对象存储到容器里即:可调用对象类型问题
想要将可调用对象存储到容器里,首先我们得需要知道它的类型,函数指针的类型实在是太麻烦了,而仿函数类型我们是可以知道,但lambda的类型我们是不知道的,所以难道容器里只能存储仿函数吗?不能存储lambda函数。

包装器就可以解决可调用对象的类型问题,它可以将函数指针,函数对象,lambda包装起来,并且这个包装的类型我们是知道的。那么我们就可以利用这个包装器将lambda包装起来,然后再存储这个包装器即可,这样lambda函数就被存储到容器里了。不仅是lambda函数被存储到容器里了,是所有的可调用对象都可以被存储到容器里了。

int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d){ return d / 4; }, 11.11) << endl;cout << endl;function<double(double)> f1 = f;//包装函数指针function<double(double)> f2 = Functor();//包装函数对象function<double(double)> f3 = [](double d) { return d / 4; };//包装lambda函数//将函数指针 /函数对象 /lambda函数包装到包装器里  这样可调用对象的类型就统一为包装器类型vector<function<double(double)>> v = { f1,f2,f3 };//可调用对象(包装器)就可以存储到容器里面了也可以直接这样写://vector<function<double(double)>> v ={f,Functor(),[](double d) { return d / 4; }};for (auto f : v){cout << useF(f,11.11) << endl;}return 0;
}

4.应用场景:指令+操作

现实中有很多这样的场景:指令+操作。就是输入对应的命令,就会给你回应对应的操作。
比如游戏中,各种按键,会引起人物不同的动作,这就是指令+操作。这该如何实现呢?
就是用容器map里面存储着指令,而指令对应着操作。操作也就是各种函数。也就是map里要存储"指令"和"函数操作".这个函数操作可以是函数指针,函数对象,lambda函数,反正是一个可调用的对象。这种情形应用很多。比如linux中的各种命令,也会对应着各种操作。比如cd,进入目录等待,也可以利用这里的原理设计出来。

就比如下面这个逆波兰值的求解:
在这里插入图片描述
里面我们就可以将对应的+ -* /都直接对应成相应的函数。然后将函数存储在map里。
普通版本:
在这里插入图片描述

包装器版本:
在这里插入图片描述

三.bind (适配器)

bind是一个函数模板,它就类似一个包装器,可以将一个可调用对象,包装生成一个新的可调用对象来适应原对象的参数列表。也就是我们可以将一个原本有n个参数的函数,通过bind绑定一些参数,最后生成只需要传m个参数的新函数(m比n小)。并且bind还可以用来调整函数的参数位置。

1.调整参数位置

使用方法:

auto newfunc=bind(func,arg_list)
1.newfunc是一个可调用对象
2.func是要被包装的可调用对象
3.arg_list是这个可调用对象的参数列表。

bind类似于一个包装器,是可以用来调整函数参数的位置。那么它是如何调整的呢?它是通过bind的占位符来实现的。当我们调用newfunc这个可调用对象时,newfunc会调用func,并且会将arg_list对应的参数传给func。

#include <iostream>using namespace std;#include <functional>int Sub(int a, int b)
{return a - b;
}
int main()
{//包装器只是将函数包装起来,它也就相当于是一个可调用对象,直接调用包装器即可function<int(int, int)> fsub = bind(Sub, placeholders::_1, placeholders::_2);//1.bind可以调整函数参数位置cout << fsub(4, 3) << endl;function<int(int, int)> fsubreserve = bind(Sub, placeholders::_2, placeholders::_1);//第一个实参还是传给_1,第二个实参还是传给_2;而bind还是按照位置将参数传给函数形参cout << fsubreserve(4, 3) << endl;
}

在这里插入图片描述
通过调整bind的占位符顺序,就可以调整函数的参数位置了。因为第一个实参设定传给的就是占位符1,第二个实参设定传给的就是占位符2.而占位符则是按照顺序传给函数的形参。

2.绑定参数

bind不仅可以调整可调用对象的参数位置。(通过包装可调用对象适配出想要的参数位置)。
还可以用来固定参数值。类似于缺省参数的功能。在包装这个可调用对象时就可以将对象的参数固定。而不需要去对象的内部。


#include <functional>double Plus(int a, int b, double rate)
{return (a + b) * rate;
}double PPlus(int a, double rate, int b)//函数右多个参数时
{return  rate * (a + b);
}int main()
{//2.bind可以绑定固定参数//可以像缺省参数那样给定一个参数一个默认值,当传参时,就可以不需要传这个参数,使用默认值function<double(int, int)> fplus1 = bind(Plus, placeholders::_1, placeholders::_2, 4.0);//将可调用对象Plus的第三个参数值固定为4.0function<double(int, int)> fplus2 = bind(Plus, placeholders::_1, placeholders::_2, 4.2);//将可调用对象Plus的第三个参数值固定为4.2function<double(int, int)> fplus3 = bind(Plus, placeholders::_1, placeholders::_2, 4.3);//将可调用对象Plus的第三个参数值固定为4.3cout << fplus1(4, 3) << endl;cout << fplus2(4, 3) << endl;cout << fplus3(4, 3) << endl;//可以固定不同位置上的参数function<double(int, int)> fpplus1 = bind(PPlus, placeholders::_1, 4.0, placeholders::_2);//绑定中间参数function<double(int, int)> fpplus2 = bind(PPlus, placeholders::_1, 4.2, placeholders::_2);//要注意,这里面还是_1和_2两个指示数。虽然是pplus函数里的参数是第一个和第三个,固定的是第二个参数//但还是用_1和_2来接收。这里决定是由函数要传几个参数决定是否要用_3的。如果要传3个参数,那么就会用到_3.

但它不像缺省参数,缺省参数是写死了,只能定义一种类型的函数。(因为缺省参数需要在函数内部写,一旦函数内部写完,外部就无法改动了)
但bind可以灵活的调整可调用对象参数的值,不需要到函数里面去改动,直接在函数外面调整就可以同时写出多个不同需求的函数。

那bind如何绑定类里面的成员函数呢(公有的成员函数)?

class AB
{
public:int abfunc(int a, int b){return a - b;}
};
//	//绑定类成员函数…………
//	//绑定类成员函数有些奇怪
//
int main()
{function<int(int, int)> fab = bind(&AB::abfunc,AB(), placeholders::_1, placeholders::_2);cout << fab(1, 1) << endl;
}

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

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

相关文章

C# Windows 窗体控件中的边距和填充

可以将 Margin 属性、Left、Top、Right、Bottom 的每个方面设置为不同的值&#xff0c;也可以使用 All 属性将它们全部设置为相同的值。 在代码中设置Margin&#xff0c;元素的左边设置为5个单位、上边设置为10个单位、右边设置为15个单位和下边设置为20个单位。 TextBox myT…

【gitlab】本地项目上传gitlab

需求描述 解决方法 下面的截图是gitlab空项目的描述 上传一个本地项目按其中“Push an existing folder”命令即可。 以renren-fast项目为例 # 用git bash 下载renren-fast项目 git clone https://gitee.com/renrenio/renren-fast.git# 在renren-fast的所属目录 打开git ba…

【数据结构与算法】如何对快速排序进行细节优化以及实现非递归版本的快速排序?

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;国庆长假结束了&#xff0c;无论是工作还是学习都该回到正轨上来了&#xff0c;从今天开始恢复正常的更新频率&#xff0c;今天为大家带来的内容…

【Python_PySide2学习笔记(十八)】勾选按钮QCheckBox类的基本用法

勾选按钮QCheckBox类的基本用法 前言正文1、创建勾选按钮2、勾选按钮获取选中状态3、创建按钮组4、按钮组添加勾选按钮5、按钮组设置单选6、按钮组信号&#xff1a;选中状态改变7、按钮组获取所有勾选按钮7.1、获取勾选按钮对象7.2、获取勾选按钮文本7.3、获取勾选按钮ID7.4、按…

win11安装IIS步骤-已验证23.10.10

IIS服务使用 步骤一&#xff1a;打开控制面板 通过 控制面板— 程序— 启用或关闭Windows功能 — 选择Internet Information Services默认安装IIS&#xff0c;如下图步骤所示 步骤二&#xff1a;打开IIS服务 建议根据下图勾选&#xff0c;建议全选安装&#xff0c;以便后续发…

聊聊2023年怎么入局小游戏赛道?

一、微信小游戏赛道发展史 第一阶段&#xff1a;轻度试水期&#xff0c;2017&#xff5e;2019年 微信小游戏于2017年底上线&#xff0c;初期以轻度休闲为主&#xff0c;例如棋牌、合成消除以及益智相关游戏类型。一是开发门槛不高&#xff0c;产品可以快速上线; 二是大部分厂…

虹科方案 | 汽车CAN/LIN总线数据采集解决方案

全文导读&#xff1a;现代汽车配备了复杂的电子系统&#xff0c;CAN和LIN总线已成为这些系统之间实现通信的标准协议&#xff0c;为了开发和优化汽车的电子功能&#xff0c;汽车制造商和工程师需要可靠的数据采集解决方案。基于PCAN和PLIN设备&#xff0c;虹科提供了一种高效、…

mac(M1)卸载miniconda3

参考https://stackoverflow.com/questions/29596350/how-to-uninstall-mini-conda-python step1 因为我目前只有一个base环境&#xff0c;所以直接在这个环境中安装 anaconda-clean即可 conda install anaconda-clean然后继续输入 anaconda-clean如果不加–yes&#xff0c;那…

[nltk_data] Error loading stopwords: <urlopen error [WinError 10054]

报错提示&#xff1a; >>> import nltk >>> nltk.download(stopwords) 按照提示执行后 [nltk_data] Error loading stopwords: <urlopen error [WinError 10054] 找到路径C:\\Users\\EDY\\nltk_data&#xff0c;如果没有nltk_data文件夹&#xff0c;在…

《安富莱嵌入式周报》第324期:单对以太网技术实战,IROS2023迪士尼逼真机器人展示,数百万模具CAD文件下载,闭环步进电机驱动器,CANopen全解析

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程&#xff1a; 第8期ThreadX视频教程&#xff1a;应用实战&#xff0c;将裸机工程移植到RTOS的任务划分…

黑马点评-05缓存穿透问题及其解决方案,缓存空字符串或使用布隆过滤器

缓存穿透问题(缓存空) 缓存穿透的解决方案 缓存穿透(数据穿透缓存直击数据库): 缓存穿透是指客户端请求访问缓存中和数据库中都不存在的数据,此时缓存永远不会生效并且用户的请求都会打到数据库 数据库能够承载的并发不如Redis这么高&#xff0c;如果大量的请求同时访问这种…

基于YOLOv5、YOLOv8的火灾检测(超实用项目)

目录 1.简介 2.YOLO算法 3.基于YOLOv5、YOLOv8的火灾检测 视频已上传b站 YOLOv5/YOLOv8的火灾检测&#xff08;超实用项目&#xff09;_哔哩哔哩_bilibili 本文为系列专栏&#xff0c;包括各种YOLO检测算法项目、追踪算法项目、双目视觉、深度结构光相机测距测速三维测量项…

[鹏城杯 2022]简单的php - 无数字字母RCE(取反)【*】

[鹏城杯 2022]简单的php 一、解题流程二、思考总结 题目代码&#xff1a; <?php show_source(__FILE__);$code $_GET[code];if(strlen($code) > 80 or preg_match(/[A-Za-z0-9]|\|"||\ |,|\.|-|\||\/|\\|<|>|\$|\?|\^|&|\|/is,$code)){die( Hello);}e…

epoll 定时器

参考&#xff1a; Linux下使用epoll监听定时器-CSDN博客 但是这个用的是gettimeofday。 本人使用的是 #include <stdlib.h> #include<stdio.h> #include <sys/timerfd.h> #include <sys/epoll.h> #include <unistd.h> #include <sys/time.…

hbba网站下载国家标准/行业标准的方法

hbba网站是不提供下载按钮并且不支持右键的&#xff0c;那么如何下载呢&#xff1f; 1、首先看一下pdf有多少页&#xff0c;一般标准介绍上有写。 2、使用edge或google浏览器打开pdf预览页面&#xff0c;打开开发者模式&#xff0c;用小箭头指向第一页&#xff0c;这样就获取到…

短视频矩阵源码开发部署---技术解析

一、短视频SEO源码搜索技术需要考虑以下几点&#xff1a; 1. 关键词优化&#xff1a;通过研究目标受众的搜索习惯&#xff0c;选择合适的关键词&#xff0c;并在标题、描述、标签等元素中进行优化&#xff0c;提高视频的搜索排名。 2. 内容质量&#xff1a;优质、有吸引力的内…

Qt QGridLayout和QFormLayout案例分析

QGridLayout和QFormLayout是Qt中常用的布局管理器&#xff0c;可以用于在应用程序中设置控件的位置和大小。 QGridLayout网格布局(栅格布局) QGridLayout是一个网格布局管理器&#xff0c;可以将控件放置在一个二维网格中。在QGridLayout中&#xff0c;控件可以跨越多个行和列…

在原生html中使用less

引入less <link rel"stylesheet/less" href"./lessDemo.less" /><script src"./js/less.min.js"></script> less.min.js文件下载地址:https://github.com/less/less.js 注意&#xff1a;less文件在前&#xff0c;js文件在后…

LabVIEW玩转魔方

LabVIEW玩转魔方 使用LabVIEW创建一个3D魔方&#xff0c;并找出解谜题的秘密&#xff0c;给朋友留下深刻深刻的印象。游戏中内置的机制使每张脸都能独立转动&#xff0c;从而混合颜色。要解决难题&#xff0c;每个面必须是相同的纯色 魔方的奥秘在于它的简单性和不可解性。这是…

阶段六-Day02-Maven

一、学习Maven 使用Maven创建Web项目&#xff0c;并部署到服务器。 二、Maven介绍及加载原理 1. 介绍 Maven是使用Java语言编写的基于项目对象模型&#xff08;POM&#xff09;的项目管理工具。开发者可以通过一小段描述信息来管理项目的构建、报告和文档。 使用Maven可以…