[C++]:C++11(三)

1. 可变参数模版

1.1 概念

可变参数模板允许我们定义能接受可变数目模板参数的模板。简单来说,就好比一个函数可以接受任意个数的实际参数一样,可变参数模板能应对不同数量的模板参数情况。比如,我们可以有一个模板类或者模板函数,有时候传入 3 个类型参数,有时候传入 5 个,而可变参数模板都能妥善处理。

1.2 语法

下面是一个简单的可变参数模板函数的基本语法形式:

template<typename... Args>
void myFunction(Args... args) {// 函数体逻辑
}

在这里,typename... Args 声明了一个可变参数模板参数包(parameter pack),它代表了零个或多个类型参数。而 Args... args 则是函数参数包,对应着实际传入的参数,这些参数的类型由前面的类型参数包决定。

例如,我们可以这样调用这个函数:

myFunction(1, 2.5, "hello");

在这个调用中,编译器会自动推导出 Args 分别为 intdoubleconst char*args 则依次为对应类型的实际参数值。

可变参数模板类的语法也类似,以下是一个示例:

template<typename... T>
class MyTemplateClass {// 类的成员定义等相关逻辑
};

我们可以根据不同的类型参数实例化这个类,像 MyTemplateClass<int, double> 或者 MyTemplateClass<std::string, char, int> 等等。

1.3 展开

可变参数模板定义好了,关键还得知道怎么使用里面的参数,也就是要展开参数包。常用的展开方式有几种:

例如,我们想要实现一个函数来打印所有传入的参数,可以这样做:

// 递归终止函数
void printArgs() {std::cout << std::endl;
}
template<typename T>
void printArg(T arg) {std::cout << arg << " ";
}// 可变参数模板函数,用于展开参数包
template<typename T, typename... Args>
void printArgs(T first, Args... rest) {printArg(first);printArgs(rest...);
}
int main()
{printArgs("111");printArgs("111",1);printArgs("111",1,1.2);printArgs("111", 1, 1.2,'a');return 0;
}

在这个例子中,printArgs 函数不断地取出参数包中的第一个参数,调用 printArg 进行处理(这里只是简单打印),然后再递归调用自身处理剩下的参数,直到参数包为空,最终会调用 printArgs这个无参函数,结束递归。

template<typename... Args>
void anotherPrintArgs(Args... args) {int dummy[] = { (std::cout << args << " ", 0)... };(void)dummy;  // 避免编译器警告,因为我们只是利用数组初始化的副作用来展开参数包
}

这里利用了逗号表达式的特性,在初始化列表中,每个元素都是一个逗号表达式 (std::cout << args << " ", 0),它先执行 std::cout << args << " " 输出参数内容,然后返回 0 用于初始化数组元素,通过这种方式依次展开参数包中的所有参数进行处理。

其中使用逗号表达式还要还有一个原因就是 C++的容器存储的类型必须相同,而可变参数模版支持多种不同的类型,所以我们这里返回 0 统一处理。

最后需要强调的是,虽然我们可以通过 sizeof...获取可变模版参数的个数,但不能通过其看看可变参数:

template<typename T, typename... Args>
void printArgs(T first,Args... rest) {cout << sizeof...(rest) << endl;//Args中参数个数错误示例://for (int i = 0; i < sizeof...(args); i++)//{//    cout << args[i] << " "; //打印参数包中的每个参数//}//cout << endl;
}

当然你可能还会有这个疑惑,那就是那既然可变参数模版是通过递归实现的,那么我们的递归结束条件是不是可以以下方式表示:

template<typename T>
void printArg(T arg) {std::cout << arg << " ";
}
// 可变参数模板函数,用于展开参数包
template<typename T, typename... Args>
void printArgs(T first, Args... rest) {if (sizeof..(args) == 0)//errorreturn;printArg(first);printArgs(rest...);
}

因为函数模板的推演是编译时逻辑,需根据传入实参类型生成对应函数才能被调用,而该判断属于运行时逻辑,当编译时推演到参数包参数个数为 0 时,仍要按编译逻辑继续推演传入 0 个参数时的函数,可此时函数要求至少传入一个参数,就会产生报错,二者逻辑阶段不同所以不能这样处理。

2. emplace_back

在 C++ 中,像 std::vector 这样的标准容器,我们之前常用的向容器中添加元素的方式有 push_back。例如对于一个存储 int 类型元素的 std::vector

std::vector<int> myVector;
myVector.push_back(10);

push_back 是将一个已经构造好的对象复制(如果对象所属类没有合适的移动构造函数等情况时)或者移动到容器的末尾位置来完成元素添加。但对于一些复杂类型,尤其是包含资源管理的自定义类型,这种先构造再赋值/移动的过程可能会带来不必要的开销。

而 C++11 引入了可变参数模板这一强大特性,而 emplace_back 函数充分利用了它来实现更高效、更灵活的元素添加机制。

比如说std::vector(以及很多其他标准容器)的 emplace_back 函数被定义为可变参数模板函数,其基本语法形式大致如下(以 std::vector 为例):

template <typename... Args>
void emplace_back(Args&&... args);

这里的 Args&&... args 就是可变参数模板的参数包,其中 Args 代表了零个或多个不同的类型,args 则是对应这些类型的右值引用形式的实际参数。

例如,假设有一个自定义的类 Person,构造函数接受 string 类型的姓名和 int 类型的年龄作为参数:

class Person {
public:Person(std::string name, int age) : m_name(std::move(name)), m_age(age) {}
private:std::string m_name;int m_age;
};

使用 emplace_back 向存储 Person 对象的 vector 中添加元素时,可以这样做:

std::vector<Person> people;
people.emplace_back("Alice", 25);

在这个调用过程中:

  • 基于可变参数模板的特性,编译器会根据传入的 "Alice"(类型为 const char*,会隐式转换为 std::string)和 25 这两个参数,自动推导出 Args 包含 std::stringint 这两个类型。
  • 然后 emplace_back 不是像 push_back 那样先构造一个 Person 对象然后再复制或者移动到容器中,而是直接在容器内部(也就是 vector 管理的内存空间里)利用传入的参数就地构造 Person 对象。这避免了额外的构造和可能的复制/移动开销,尤其是对于那些构造过程相对复杂、开销较大的对象来说,能显著提高性能。

以下通过一个更具体的示例来对比二者的区别:

#include <iostream>
#include <vector>
#include <string>class ResourceHolder {
public:ResourceHolder() {//std::cout << "Default constructor called" << std::endl;}ResourceHolder(const ResourceHolder& other) {std::cout << "Copy constructor called" << std::endl;}ResourceHolder(ResourceHolder&& other) {std::cout << "Move constructor called" << std::endl;}~ResourceHolder() {// std::cout << "Destructor called" << std::endl;}
};int main() {// 使用push_backstd::vector<ResourceHolder> vecPush;ResourceHolder rh1;vecPush.push_back(rh1);  // 这里会调用拷贝构造函数,因为 rh 是左值// 使用emplace_backstd::vector<ResourceHolder> vecEmplace;ResourceHolder rh2;vecEmplace.emplace_back(std::move(rh2));  // 直接调用默认构造函数在容器内就地构造对象return 0;
}

在上述示例中:

  • 当使用 push_back 时,由于传入的是一个已经存在的左值对象 rh,所以会调用拷贝构造函数将该对象复制到 vecPush 容器中,有额外的构造副本的开销。
  • 而使用 emplace_back 时,直接在 vecEmplace 容器内部按照需要的构造方式(这里是调用默认构造函数)就地构造对象,避免了不必要的拷贝构造过程,更加高效。

其实emplace_back本质就是利用可变模板参数(类型为万能引用),可接收左值对象、右值对象或参数包。传入左值对象时,需先实例化左值对象,用定位新表达式初始化空间时匹配拷贝构造函数;传入右值对象时,先实例化右值对象,初始化空间时匹配移动构造函数;传入参数包可直接调用函数插入,初始化空间时匹配构造函数。

3. lambda 表达式

3.1 用法

Lambda 表达式是 C++11 引入的一项重要特性,它提供了一种简洁、灵活的方式来定义匿名函数,使得在需要使用函数对象的场景中,代码更加紧凑和易于理解。Lambda 表达式可以在需要函数的地方就地定义,而无需像传统方式那样先定义一个具名函数或函数对象。

书写格式为 [capture-list](parameters)mutable->return-type{statement}

  • [capture-list](捕捉列表):总是位于 lambda 函数开头,用于捕捉上下文中变量供函数使用。
    • [var] 表示值传递捕捉变量 var
    • [=] 表示值传递捕获所有父作用域中的变量(成员函数包括 this 指针)。
    • [&var] 表示引用传递捕捉变量 var
    • [&] 表示引用传递捕捉所有父作用域中的变量(成员函数包括 this 指针)。
    • [this] 表示值传递方式捕捉当前的 this 指针。
    • 捕捉列表可由多个捕捉项组成并用逗号分割,但不允许变量重复传递,且全局 lambda 函数的捕捉列表必须为空,块作用域中的 lambda 函数仅能捕捉父作用域中的局部变量。
  • (parameters)(参数列表):与普通函数参数列表一致,若无需参数传递可连同括号一起省略。
  • mutable:默认 lambda 函数为 const 函数,该修饰符可取消常量性,使用时参数列表不可省略(即使参数为空)。
  • ->return-type(返回值类型):用追踪返回类型形式声明函数返回值类型,无返回值时可省略,返回值类型明确时也可省略由编译器推导。
  • {statement}(函数体):在其中可使用参数及所有捕获到的变量,函数体格式上可换行但末尾要有分号,且若 lambda 表达式要直接调用,需借助 auto 赋值给一个变量使其像普通函数一样使用。

我们可以一个简单的交换两数的方式,具体来介绍一下不同捕捉列表的使用方法:

首先我们以引用的方式捕捉所有父作用域中的变量,省略参数列表和返回值类型。比如:

int main()
{int a = 10, b = 20;auto Swap = [&]{int tmp = a;a = b;b = tmp;};Swap(); //交换a和breturn 0;
}

lambda表达式会自动对变量 ab进行捕捉,然后进行我们的交换逻辑。并且我们可以发现在交换的过程中,我们只使用了 ab两个变量,所以我们也可以只捕捉 ab

int main()
{int a = 10, b = 20;auto Swap = [&a, &b]{int tmp = a;a = b;b = tmp;};Swap(); //交换a和breturn 0;
}

实际当我们以[&][=]的方式捕获变量时,编译器也不一定会把父作用域中所有的变量捕获进来,编译器可能只会对lambda表达式中用到的变量进行捕获,因为没有必要把用不到的变量也捕获进来,当然这个主要取决于编译器的具体实现。

还有一点值得注意的是:如果以传值方式进行捕捉,那么首先编译肯定不会通过,因为传值捕获到的变量属性是 const,即使使用关键字mutable其实也意义不大,并且此时参数列表也不可省略。比如:

int main()
{int a = 10, b = 20;auto Swap = [a, b]()mutable{int tmp = a;a = b;b = tmp;};Swap(); return 0;
}

因为是传值捕捉,所以即使 lambda表达式内部交换了两数,也对父作用域的变量 ab没有任何影响。

3.2 原理

接下来我们来探究一下 lambda 表达式究竟是怎样实现的呢?其实并不是很复杂:

  • 编译器会自动为每个 Lambda 表达式生成一个独一无二的类。这个类包含了Lambda 表达式所需的各种信息和功能实现。
  • 类中的成员变量用于存储 Lambda 表达式捕获的变量(如果有捕获的话)。例如,如果Lambda 表达式通过值捕获了某个变量,那么在生成的类中会有一个成员变量来保存该变量的值;如果是通过引用捕获,类中会有相应的引用成员(通常是对外部变量的引用)。
  • 最重要的是,编译器会在生成的类中重载函数调用运算符 ()。Lambda 表达式的函数体就被转化为这个重载的 operator() 函数的实现。
  • 当调用 Lambda 表达式时,实际上就是在调用这个生成类的重载函数 operator()
  • Lambda 表达式的参数列表中的参数会被传递给生成类的 operator() 函数作为其参数。这样在函数体中就可以像使用普通函数参数一样使用这些参数进行计算等操作。

比如下面我们实现一个简单的交换类,并在其中重载 operator(),然后再实现一个lambda表达式,对比其汇编:

#include <iostream>// 交换类(仿函数)
class SwapClass {
public:void operator()(int& a, int& b) {int temp = a;a = b;b = temp;}
};int main() {int x = 5, y = 10;// 使用交换类进行交换SwapClass swapObj;swapObj(x, y);std::cout << "After using SwapClass: x = " << x << ", y = " << y << std::endl;// 使用 lambda 表达式进行交换auto lambdaSwap = [](int& a, int& b) {int temp = a;a = b;b = temp;};lambdaSwap(x, y);std::cout << "After using lambda expression: x = " << x << ", y = " << y << std::endl;return 0;
}

通过汇编我们就可以看出 lambda其实本质就是一个仿函数。并且在 VS 下,lambda 表达式底层被处理为类名包含保证唯一性的 uuid 的函数对象(可以通过 typeid().name()观察类型),因此每个 lambda 表达式类型不同,所以每一个 lambda 表达式之间也不能相互赋值。

4. function包装器

4.1 基本概念

function是一种函数包装器,本质是一个类模板。它可以对多种可调用对象进行包装,包括函数指针、仿函数、lambda表达式、类的成员函数等。其类模板原型为:

template <class T> function;      // undefined 
template <class Ret, class... Args> 
class function<Ret(Args...)>;

其中,Ret表示被包装的可调用对象的返回值类型,Args...表示被包装的可调用对象的形参类型。其语法形式如下:

std::function<返回值类型(参数类型列表)> func_name;

例如,我们要定义一个可以包装接受两个 int 参数并返回 int 值的可调用对象的 function,可以这样写:

std::function<int(int, int)> my_func;

4.2 使用示例

比如说,我们不同方式实现一个加法方法,然后用包装器 function 包装。

int f(int a, int b) { return a + b; }
struct Functor {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() {// 1、包装函数指针(函数名)function<int(int, int)> func1 = f;cout << func1(1, 2) << endl;// 2、包装仿函数(函数对象)function<int(int, int)> func2 = Functor();cout << func2(1, 2) << endl;// 3、包装lambda表达式function<int(int, int)> func3 = [](int a, int b) { return a + b; };cout << func3(1, 2) << endl;// 4、类的静态成员函数// function<int(int, int)> func4 = Plus::plusi;function<int(int, int)> func4 = &Plus::plusi;  // &可省略cout << func4(1, 2) << endl;// 5、类的非静态成员函数function<double(Plus, double, double)> func5 = &Plus::plusd;  // &不可省略cout << func5(Plus(), 1.1, 2.2) << endl;return 0;
}

其中需要注意的是:取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”。包装非静态的成员函数时也需要注意,非静态成员函数的第一个参数是隐藏this指针,因此在包装时需要指明第一个形参的类型。

并且我们也可以通过 function 来避免因可调用对象类型不同而产生多次不必要的函数模板实例化,比如以下代码:

// 函数模板,用于对可调用对象进行一些操作,并返回操作后的结果
// 这里假设可调用对象接受一个int参数,返回一个int结果
template<class F, class T>
T operateOnValue(F f, T x)
{static int count = 0;std::cout << "count: " << ++count << std::endl;std::cout << "count: " << &count << std::endl;return f(x);
}// 普通函数,将传入的整数乘以2
int multiplyByTwo(int num)
{return num * 2;
}// 仿函数类,将传入的整数加上5
class AddFive {
public:int operator()(int n) const {return n + 5;}
};

如果我们不使用 functional 对不同类型进行统一管理,那么我们在分别调用multiplyByTwoAddFive(),以及 lambda 表达式时,最终 operateOnValue 函数模板会被实例化 三次,从打印 count 的地址不同以及 count 的值都没有递增可以看出。

int main()
{std::cout << operateOnValue(multiplyByTwo, 10) << std::endl;std::cout << operateOnValue(AddFive(), 10) << std::endl;std::cout << operateOnValue([](int n) { return n * n; }, 10) << std::endl;return 0;
}

如果使用了 function 对不同类型但有着相同参数和返回值类型的可调用对象进行了统一包装,最终 operateOnValue 函数模板只会被实例化一次,从打印 count 的地址相同以及 count 的值会逐次累加(从 1 开始每次加 1,表示调用次数)就可以看出来,避免了因可调用对象类型差异而产生的多次不必要的实例化。

// 主函数,展示如何使用function来统一类型,避免函数模板的多次实例化
int main()
{// 使用std::function包装普通函数std::function<int(int)> func1 = multiplyByTwo;std::cout << operateOnValue(func1, 10) << std::endl;// 使用std::function包装仿函数std::function<int(int)> func2 = AddFive();std::cout << operateOnValue(func2, 10) << std::endl;// 使用std::function包装lambda表达式,这里的lambda表达式实现将传入整数平方的功能std::function<int(int)> func3 = [](int n) { return n * n; };std::cout << operateOnValue(func3, 10) << std::endl;return 0;
}

5. bind包装器

5.1 基本概念

bind也是一种函数包装器,本质是一个函数模板。它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。其函数模板原型为:

template <class Fn, class... Args> 
/* unspecified */ bind(Fn&& fn, Args&&... args); 
template <class Ret, class Fn, class... Args> 
/* unspecified */ bind(Fn&& fn, Args&&... args);

其中,fn为可调用对象,args...为要绑定的参数列表(值或占位符)。其基本语法如下:

auto newCallable = bind(callable, arg_list);

其中callable是需要包装的可调用对象,newCallable是生成的新的可调用对象,arg_list是逗号分隔的参数列表。arg_list中的参数可能包含形如_n的占位符(n是整数),表示新生成可调用对象的参数位置。我们一般需要使用 placeholders进行绑定,比如说使用 bind 绑定一个加法函数:

int Plus(int a, int b)
{return a + b;
}
int main()
{//无意义的绑定function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);cout << func(1, 2) << endl; //3return 0;
}

5.2 绑定固定值

我们也可以将函数的某些参数绑定为固定值。例如,将Plus函数的第二个参数固定绑定为10:

int Plus(int a, int b) { return a + b; }
int main() {// 绑定固定参数function<int(int)> func = bind(Plus, placeholders::_1, 10);cout << func(2) << endl;  // 12return 0;
}

以后每次使用时,我们就只需要传第一个参数,第二个参数就固定为 10。

5.3 调整顺序

对于类成员函数,可以通过控制占位符的位置来调整参数传递顺序。例如,交换Sub类中sub成员函数两个参数的顺序:

class Sub {
public:int sub(int a, int b) { return a - b; }
};
int main() {// 调整传参顺序function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);Sub bj;//也可以传对应实例化的地址function<int(int, int)> func = bind(&Sub::sub, &bj, placeholders::_2, placeholders::_1);cout << func(1, 2) << endl;  // 1return 0;
}

其中需要注意的是:取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”。包装非静态的成员函数时,非静态成员函数的第一个参数是隐藏 this 指针,因此在包装时需要指明第一个形参的类型。

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

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

相关文章

【Nginx从入门到精通】05-安装部署-虚拟机不能上网简单排错

文章目录 总结1、排查步骤 一、排查&#xff1a;Vmware网关二、排查&#xff1a;ipStage 1 &#xff1a;ping 127.0.0.1Stage 2 &#xff1a;ping 宿主机ipStage 3 &#xff1a;ping 网关 失败原因解决方案Stage 4 &#xff1a;ping qq.com 总结 1、排查步骤 Vmware中网关是否…

InstantStyle容器构建指南

一、介绍 InstantStyle 是一个由小红书的 InstantX 团队开发并推出的图像风格迁移框架&#xff0c;它专注于解决图像生成中的风格化问题&#xff0c;旨在生成与参考图像风格一致的图像。以下是关于 InstantStyle 的详细介绍&#xff1a; 1.技术特点 风格与内容的有效分离 &a…

卷积神经网络各层介绍

目录 1 卷积层 2 BN层 3 激活层 3.1 ReLU&#xff08;Rectified Linear Unit&#xff09; 3.2 sigmoid 3.3 tanh&#xff08;双曲正切&#xff09; 3.4 Softmax 4 池化层 5 全连接层 6 模型例子 1 卷积层 卷积是使用一个卷积核&#xff08;滤波器&#xff09;对矩阵进…

Elastic 和 Red Hat:加速公共部门 AI 和机器学习计划

作者&#xff1a;来自 Elastic Michael Smith 随着公共部门组织适应数据的指数级增长&#xff0c;迫切需要强大、适应性强的解决方案来管理和处理大型复杂数据集。人工智能 (Artificial intelligence - AI) 和机器学习 (machine learning - ML) 已成为政府机构将数据转化为可操…

SAP B1 登陆报错解决方案 - 系统架构目录服务器选择

背景 登录时出现如下报错&#xff0c;报错显示为【系统架构目录服务器选择】 强行登录会发现过往账套都不见了 出现原因 出于各种原因在开机时没有把 SAP 所有的服务成功启动&#xff08;上一次启动科学上网后全局代理没关干净之类的&#xff09;。 解决方案 关机几分钟重启…

基于深度卷积神经网络(CNN)模型的图像着色研究与应用系统实现

1.摘要 许多历史照片都是黑白的&#xff0c;通过颜色化可以恢复这些照片的历史感和真实感&#xff0c;使人们更好地理解和感受历史事件。随着深度学习技术的发展&#xff0c;特别是卷积神经网络和自监督学习的兴起&#xff0c;研究人员提出了新的方法来解决这些问题。通过将颜色…

【CVE-2024-9413】SCP-Firmware漏洞:安全通告

安全之安全(security)博客目录导读 目录 一、概述 二、修订历史 三、CVE根因分析 四、问题修复解决 一、概述 在SCP固件中发现了一个漏洞&#xff0c;如果利用该漏洞&#xff0c;可能会允许应用处理器&#xff08;AP&#xff09;在系统控制处理器&#xff08;SCP&#xf…

Oracle 19C 安装RAC磁盘投票失败

ORACLE 19C 安装RAC第二个节点报错&#xff0c;没有找到足够的 voting 文件&#xff08;投票磁盘&#xff09; 1、磁盘投票失败分析 1.1、02节点报错日志 CRS-4123: Starting Oracle High Availability Services-managed resources CRS-2672: Attempting to start ora.mdnsd…

【Maven】IDEA创建Maven项目 Maven配置

文章目录 简介配置环境变量配置仓库测试安装 IDEA创建项目pom.xml 简介 Maven 是一个非常流行的项目管理和构建自动化工具&#xff0c;主要应用于 Java 项目的构建、依赖管理和项目信息管理。它是由 Apache 软件基金会维护的开源项目。Maven 的设计理念是通过一个项目对象模型…

vue3:使用插件递归组件

vue3:使用插件递归组件 首先安装插件 npm i unplugin-vue-define-optionsvite.config.ts 配置插件 // vite.config.ts// 引入 unplugin-vue-define-options import DefineOptions from "unplugin-vue-define-options"; export default defineConfig({// 注册插件 De…

开源TTS语音克隆神器GPT-SoVITS_V2版本地整合包部署与远程使用生成音频

文章目录 前言1.GPT-SoVITS V2下载2.本地运行GPT-SoVITS V23.简单使用演示4.安装内网穿透工具4.1 创建远程连接公网地址 5. 固定远程访问公网地址 前言 本文主要介绍如何在Windows系统电脑使用整合包一键部署开源TTS语音克隆神器GPT-SoVITS&#xff0c;并结合cpolar内网穿透工…

Keil+VSCode优化开发体验

目录 一、引言 二、详细步骤 1、编译器准备 2、安装相应插件 2.1 安装C/C插件 2.2 安装Keil相关插件 3、添加keil环境变量 4、加载keil工程文件 5、VSCode中成功添加工程文件后可能出现的问题 5.1 编码不一致问题 6、在VSCode中进行编译工程以及烧录程序 7、效果展示…

Llama模型文件介绍

文章目录 概要文件组成 概要 在使用 LLaMA&#xff08;Large Language Model Meta AI&#xff09;权重时&#xff0c;通常会涉及到与模型权重存储和加载相关的文件。这些文件通常是以二进制格式存储的&#xff0c;具有特定的结构来支持高效的模型操作。以下以Llama-7B为例&…

Spring Web入门练习

加法计算器 约定前后端交互接⼝ 约定 "前后端交互接⼝" 是进⾏ Web 开发中的关键环节. 接⼝⼜叫 API&#xff08;Application Programming Interface), 我们⼀般讲到接⼝或者 API&#xff0c;指的都是同⼀个东西. 是指应⽤程序对外提供的服务的描述, ⽤于交换信息…

Easyexcel(5-自定义列宽)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09;Easyexcel&#xff08;5-自定义列宽&#xff09; 注解 ColumnWidth Data…

FIFO和LRU算法实现操作系统中主存管理

FIFO&#xff0c;用数组实现 1和2都是使用nextReplace实现新页面位置的更新 1、不精确时间&#xff1a;用ctime输出运行时间都是0.00秒 #include <iostream> #include <iomanip> #include<ctime>//用于计算时间 using namespace std;// 页访问顺序 int pa…

Unity3d场景童话梦幻卡通Q版城镇建筑植物山石3D模型游戏美术素材

注明&#xff1a;网络素材&#xff0c;仅供学习使用&#xff01; https://download.csdn.net/download/elineSea/90017291

batchnorm与layernorn的区别

1 原理 简单总结&#xff1a; batchnorn 和layernorm是在不同维度上对特征进行归一化处理。 batchnorm在batch这一维度上&#xff0c; 对一个batch内部所有样本&#xff0c; 在同一个特征通道上进行归一化。 举个例子&#xff0c; 假设输入的特征图尺寸为16x224x224x256&…

SpringAOP模拟实现

文章目录 1_底层切点、通知、切面2_切点匹配3_从 Aspect 到 Advisor1_代理创建器2_代理创建时机3_Before 对应的低级通知 4_静态通知调用1_通知调用过程2_模拟 MethodInvocation 5_动态通知调用 1_底层切点、通知、切面 注意点&#xff1a; 底层的切点实现底层的通知实现底层的…

标准驱动开发(Linux2.6(cdev) 的开发)

Linux2.6&#xff08;cdev&#xff09; 的开发 目录 Linux2.6&#xff08;cdev&#xff09; 的开发 回顾 Linux2.6&#xff08;cdev&#xff09; 的开发 了解一下 Linux2.6 开发框架 学习 Linux2.6 的相关接口 1、申请设备号&#xff08;alloc_chrdev_region&#xff09…