【C++】C++11(lambda、可变参数模板、包装器、线程库)

  🌈个人主页:秦jh_-CSDN博客
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482

 9efbcbc3d25747719da38c01b3fa9b4f.gif​ 

目录

前言

lambda表达式

C++98中的一个例子

lambda表达式语法

 函数对象与lambda表达式

新的类功能 

默认成员函数 

强制生成默认函数的关键字default

禁止生成默认函数的关键字delete 

可变参数模板 

递归函数方式展开参数包 

逗号表达式展开参数包 

STL容器中的empalce相关接口函数: 

 包装器

 function包装器

使用方法

 包装成员函数的函数指针:

bind

线程库

thread类的简单介绍 

线程函数参数

lock_guard

 unique_lock

 原子性操作库(atomic)

条件变量


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了C++11的相关内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

lambda表达式

C++98中的一个例子

待排序元素为自定义类型,需要用户定义排序时的比较规则 。每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类。因此,在C++11语法中出现了Lambda表达式。

lambda表达式语法

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

1. lambda表达式各部分说明:

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。 

lambda表达式的定义是一个局部的匿名对象。lambda编译时,编译器会生成对应仿函数。

lambda的本质是仿函数。  

 

默认捕捉过来的对象是const修饰的,并且是传值捕捉。所以如果要修改,就要加上mutable。传值捕捉的对象如果要修改,就要加上mutable。 加上mutable后,就可以修改了。但a,b是值拷贝,所以修改局部域里的a,b,不影响外面的a,b。

因为这里是引用方式捕捉,所以不用mutable也可以修改捕捉对象。 调用swap3后,修改里面的a,b,会影响外面的a,b。

捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式传值还是传引用。 

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针 

注意:

  1. 父作用域指包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]

上图是值捕捉和引用捕捉的例子。

上图都是混合捕捉。图二是a,b传引用捕捉,d,e传值捕捉。 

 函数对象与lambda表达式

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的 类对象。

函数对象在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如 果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

新的类功能 

默认成员函数 

原来C++类中,有6个默认成员函数:

  1.  构造函数
  2. 析构函数
  3.  拷贝构造函数
  4. 拷贝赋值重载
  5.  取地址重载
  6.  const 取地址重载 

C++11 新增了两个:移动构造函数和移动赋值运算符重载。 

注意:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝(值拷贝),自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。 

强制生成默认函数的关键字default

假设你要使用某个默认的函数,但是因为一些原 因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以 使用default关键字显示指定移动构造生成。 

禁止生成默认函数的关键字delete 

 

 如果想要限制某些默认函数的生成,在C++98中,是将该函数设置成private,并且只进行声明。在C++11中,只需在该函数声明加上=delete即可,编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

可变参数模板 

 下面是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
 void ShowList(Args... args)
 {}

 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

 如果想知道参数包的参数个数,可以用sizeof。不过需要注意写法sizeof...(args)。

语法不支持使用args[i]这样的方式获取可变参数。

递归函数方式展开参数包 

我们无法直接获取参数包args中的每个参数, 只能通过展开参数包的方式来获取参数包中的每个参数。

当传三个参数时,参数包有三个参数,其中第一个参数会匹配val,剩下两个参数到了另一个参数包中,然后进行递归,最后再写上一个递归终止函数。

逗号表达式展开参数包 

template <class T>
 void PrintArg(T t)
 {
     cout << t << " ";
 }

 //展开函数
template <class ...Args>
 void ShowList(Args... args)
 {
 int arr[] = { (PrintArg(args), 0)... };
 cout << endl;
 }

这里不使用逗号表达式,做出如下图的修改 

 

PrintArg只接受一个参数,当参数包有三个参数时, 构造arr数组时,会调用三次PrintArg,返回三个0。arr数组就会开3个int大小的空间        。

STL容器中的empalce相关接口函数: 

 

emplace系列的接口,支持模板的可变参数,并且万能引用。 

上图中push_back和emplace_back二者没啥区别。实际中,emplace_back不会这样传,如果直接传pair,效率跟push_back是一样的。实际是传pair的参数包,emplace不会将这些参数包构造pair对象,而是会一直往下传,直到new出节点的时候,直接拿出参数包进行初始化。

 

上面是emplace的简单实现。传入参数包时,会将参数包一直往下传,直到new节点时,根据_data的类型进行初始化,直接构造。 

总结:push_back只能先构造再拷贝构造,emplace可以兼容push_back的功能,还能接收参数包进行直接构造。

效率比较结论:

 包装器

 function包装器

 function包装器也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。 

 包装器不是定义可调用对象,而是包装可调用对象。

现今所学的可调用对象有函数指针、仿函数对象、lambda。但他们有各有缺点:

因此,使用function包装起来,这样方便统一类型。

模板参数说明:

Ret: 被调用函数的返回类型

Args…:被调用函数的形参

 function的构造

function还重载了operator() 

使用方法

#include<functional>

int f(int a, int b)
{
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

//不是定义可调用对象,而是包装可调用对象
int main()
{
	function<int(int, int)> fc1;
	//function<int(int, int)> fc2(f);
	function<int(int, int)> fc2 = f; //单参数构造隐式类型转换  //函数指针
	function<int(int, int)> fc3 = Functor();  //仿函数对象
	function<int(int, int)> fc4 = [](int x, int y) {return x + y; }; //lambda
 
	cout << fc2(1, 2) << endl;
	cout << fc2.operator()(1, 2) << endl;
	cout << fc3(1, 2) << endl;
	cout << fc4(1, 2) << endl;
	return 0;
}

 function其实就是个仿函数,它是将现有的东西传给它,然后存起来,再通过operator()把参数传给可调用对象去调用。

 下面通过一道题了解它的用法:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode-cn.com/problems/evaluate-reverse-polish-notation/submissions/

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;

        map<string,function<int(int,int)>> opFuncMap={
            {"+",[](int a,int b){return a+b;}},
            {"-",[](int a,int b){return a-b;}},
            {"*",[](int a,int b){return a*b;}},
            {"/",[](int a,int b){return a/b;}}
        };

        for(auto& str:tokens)
        {
            if(opFuncMap.count(str)) //操作符
            {
                function<int(int,int)> func=opFuncMap[str];
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();

                st.push(func(left,right));
            }
            else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

 上方是参考代码。

 包装成员函数的函数指针:

上面这种方法需要定义有名对象,很麻烦。下面是另一种方式:

这种方法可以用匿名对象。实际上,调用非静态的成员函数不是直接把参数传给过去。成员函数调用的时候,不能直接传this指针。

operator()底层:如果是指针,就用对象的指针去调用plusd,如果是对象,就用对象去调用。所以里面的参数不是直接传过去的,如果是成员函数,就用该对象去调用。

bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。 

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对 象来“适应”原对象的参数列表。 调用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中 的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示 newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对 象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。


  

上图是用bind调整参数顺序。 _n表示对应的第n个参数,如果没展开命名空间,就要显示写出来。下面是调整的原理。

调整参数个数:

 

注意,sub有三个参数,其中一个是隐藏的this指针。 这里和前面一样,可以传成员函数指针,或者对象。实际不会这样用。

实际使用如上,像固定的参数,可以直接绑死。这样就能调整参数个数了。

下面是另一个使用例子:

如果不想每次都传固定的参数,如名称,就可以绑定该参数。如下图:

绑定还可以绑中间的参数,如下图:


bind的返回值可以用function来接收。 

如果有一个方法对应的函数参数个数不是两个,这时就可以使用bind。


 下面介绍下模板中Ret的作用:

 Ret指的是可调用对象返回值的类型。这个地方用显式实例化的方式,控制可调用对象的返回值的类型。

线程库

thread类的简单介绍 

在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在 并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。 

注意:

  1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
  2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。 

get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含了一个结构体。

当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。 线程函数一般情况下可按照以下三种方式提供:

  • 函数指针
  • lambda表达式
  • 函数对象

thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个 线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。

 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:

  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束

主线程有线程对象,很容易获取线程id。从线程没有线程对象,那该怎么获取从线程的线程id呢?

这里还有一个配套的类,封装在this_thread的命名空间里面。 里面的get_id接口就可以获取。

运行上面代码,结果不一定等于3000。这时候就得加锁了。

如上图,这个锁可以放在for循环里面,也可以放在外面,但是放在里面效率会更低一些。

每个线程都有独立的栈,for循环里的i都是各自的。

放到外面时,就是一个线程先加完了,另一个再加。因为循环++的过程是很快的,放到外面就相当于变成串行的了。放到里面的话线程就会一直来回切换,效率就是变低。

线程函数参数

 

如果不想用全局变量,可以传递给线程函数。

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。 

如果想要通过形参改变外部实参时,必须借助std::ref()函数,强行把中间的拷贝变成引用拷贝。

注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。

下面是用lambda的写法,

lock_guard

我们平时锁的时候是有风险的,如下图,如果中间部分抛异常了,就不会解锁了。解锁没有执行,就会发生死锁。

下面是一种解决方法,模拟lockguard的实现

 

这样就不怕抛异常了。程序正常结束,就调用析构函数。如果抛异常,生命周期也结束。  

 

如果有两个循环,第二个循环我们并不想锁。这时就可以用花括号来增加一个局部域,控制lock对象的生命周期。 

库里面的lock_guard是模板的,不仅可以管理互斥锁,别的锁也可以管理。它是自动的加锁和解锁的。

 unique_lock

unique_lock和lock_guard的功能类似。 但是unique_lock不仅可以支持自动的锁和解锁,还可以支持手动的。 如果在锁的中途想解锁就可以用unique_lock。

 原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问 题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数 据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。 

虽然加锁可以解决,但是加锁有一个缺陷就是:其他线程会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。 

因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入 的原子操作类型,使得线程间数据的同步变得非常高效。 

 

 如上图,如果不用atomic,就可能会发生线程安全的问题。

用了atomic就可以解决。

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。 

条件变量

下面通过一个程序来介绍,支持两个线程交替打印,一个打印奇数,一个打印偶数。记得包头文件<condition_variable>

int main()
{
	std::mutex mtx;
	condition_variable c;
	int n = 100;
	bool flag = true;

	thread t2([&]() {
		int j = 1;
		while (j < n)
		{
			unique_lock<mutex> lock(mtx);

			// 只要flag == true t2一直阻塞
			// 只要flag == false t2不会阻塞
			while (flag)
				c.wait(lock);

			cout << j << endl;
			j += 2; // 奇数
			flag = true;

			c.notify_one();
		}
	});

	// 第一个打印的是t1打印0
	thread t1([&]() {
		int i = 0;
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			// flag == false t1一直阻塞
			// flag == true t1不会阻塞
			while (!flag)
				c.wait(lock);

			cout << i << endl;

			flag = false;
			i += 2; // 偶数

			c.notify_one();
		}
	});

	t1.join();
	t2.join();

	return 0;
}

wait的功能:让当前线程阻塞,直到被唤醒。在进入阻塞的瞬间,会把锁进行解锁,允许在这个锁上阻塞的其他线程继续走。如果被唤醒,就会解阻塞,并且获得这个锁。 

notify就是唤醒线程,one是唤醒一个,all是唤醒全部。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

12.11数据结构-图

无向完全图&#xff1a;在无向图中&#xff0c;如果任意两个顶点之间都存在边&#xff0c;则称该图为无向完全图。 有向完全图&#xff1a;在有向图中&#xff0c;如果任意两个顶点之间都存在方向相反的两条弧&#xff0c;则称该图为有向完全图。 含有n个顶点的无向完全图有…

深度学习作业 - 作业十一 - LSTM

问题一 推导LSTM网络中参数的梯度&#xff0c;并的分析其避免梯度消失的效果 LSTM网络是为了解简单RNN中存在的长程依赖问题而提出的一种新型网络结构&#xff0c;其主要思想是通过引入门控机制来控制数据的流通&#xff0c;门控机制包括输入门、遗忘门与输出门&#xff0c;同…

Sigrity System Explorer DC IR Drop Analysis模式进行直流压降仿真分析操作指导

Sigrity System Explorer DC IR Drop Analysis模式进行直流压降仿真分析操作指导 Sigrity System Explorer DC IR Drop Analysis模式可以用于直流压降仿真分析,通过搭建简易拓扑用于前仿真分析,下面搭建一个简易的直流系统进行说明,以下图为例,准备好PCB的SPICE模型SpiceNe…

华为HarmonyOS实现跨多个子系统融合的场景化服务 -- 4 设置打开App Button

场景介绍 本章节将向您介绍如何使用Button组件打开APP功能&#xff0c;可调用对应Button组件打开另一个应用。 效果图展示 单击“打开APP”按钮&#xff0c;出现提示弹窗&#xff0c;单击“允许”&#xff0c;跳转至新的应用界面。 说明 弹窗是否弹出以及弹窗效果与跳转目标…

Spring Security 6 系列之二 - 基于数据库的用户认证和认证原理

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级为6.3.0&#xff0c;关键是其风…

[笔记] Ubuntu Server 24.04安装MySql8,并配置远程连接

1、MySql安装 #更新列表 sudo apt update ​ #安装mysql sudo apt install mysql-server ​ #运行状态 mysql sudo service mysql status ​ # 安装完成&#xff0c;已自动启动&#xff0c;该步可以不用 启动 mysql sudo /etc/init.d/mysql start ​ # 该步骤可以不配置&…

软件开发中 Bug 为什么不能彻底消除

在软件开发中&#xff0c;Bug无法彻底消除的原因主要包括&#xff1a;软件复杂度高、人员认知与沟通受限、需求和环境不断变化、工具与测试覆盖不足、经济与时间成本制约。其中“需求和环境不断变化”尤为关键&#xff0c;因为在实际开发中&#xff0c;业务逻辑随着市场与用户反…

使用ElasticSearch实现全文检索

文章目录 全文检索任务描述技术难点任务目标实现过程1. java读取Json文件&#xff0c;并导入MySQL数据库中2. 利用Logstah完成MySQL到ES的数据同步3. 开始编写功能接口3.1 全文检索接口3.2 查询详情 4. 前端调用 全文检索 任务描述 在获取到数据之后如何在ES中进行数据建模&a…

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(三)

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(三) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《拉…

调用完BAPI_PO_CREATE1创建采购订单之后,如果不调用BAPI_TRANSACTION_COMMIT,数据库里面没有数

在调用完BAPI_PO_CREATE1创建采购订单之后&#xff0c;如果不调用BAPI_TRANSACTION_COMMIT&#xff0c;那么就无法生成真正的采购订单号&#xff0c;在数据库里面没有数 运行结果 特别注意

linux(CentOS8)安装PostgreSQL16详解

文章目录 1 下载安装包2 安装3 修改远程连接4 开放端口 1 下载安装包 官网下载地址&#xff1a;https://www.postgresql.org/download/ 选择对应版本 2 安装 #yum源 yum -y install wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redha…

如何通过递延型指标预测项目的长期成果?

递延型指标&#xff08;Deferred Metrics&#xff09;是指那些并不立即反映或直接影响当前操作、决策或行为的指标&#xff0c;而是随着时间的推移&#xff0c;才逐渐显现出影响效果的指标。这类指标通常会在一段时间后反映出来&#xff0c;或者需要一定的周期才能展现其成果或…

Reactor 响应式编程(第四篇:Spring Security Reactive)

系列文章目录 Reactor 响应式编程&#xff08;第一篇&#xff1a;Reactor核心&#xff09; Reactor 响应式编程&#xff08;第二篇&#xff1a;Spring Webflux&#xff09; Reactor 响应式编程&#xff08;第三篇&#xff1a;R2DBC&#xff09; Reactor 响应式编程&#xff08…

力扣-图论-14【算法学习day.64】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

Leetcode经典题11--加油站

题目描述 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。 给定两个整数数组 gas 和…

前端退出对话框也就是点击右上角的叉,显示灰色界面,已经解决

文章目录 遇到一个前端bug&#xff0c;点击生成邀请码 打开对话框 然后我再点击叉号&#xff0c;退出对话框&#xff0c;虽然退出了对话框&#xff0c;但是显示灰色界面。如下图&#xff1a; 导致界面就会失效&#xff0c;点击任何地方都没有反应。 发现是如下代码的问题&am…

软件需求概述(尊享版)

软件需求与软件分析 软件需求&#xff1a;用户角度&#xff0c;注重软件外在表现 软件分析&#xff1a;开发者角度&#xff0c;注重软件内部逻辑结构 面向对象分析模型 类/对象模型&#xff08;全部的类和对象&#xff09; 对象-关系模型&#xff08;对象之间的静态关系&…

配置Hugging_face国内镜像

目录 随着人工智能技术的蓬勃发展&#xff0c;Huggingface这一开源平台已成为研究者和开发者的宝贵资源&#xff0c;提供了丰富的预训练模型&#xff0c;助力自然语言处理任务的快速推进。然而&#xff0c;对于身处国内的我们而言&#xff0c;访问Huggingface主仓库时&#xff…

Rust之抽空学习系列(四)—— 编程通用概念(下)

Rust之抽空学习系列&#xff08;四&#xff09;—— 编程通用概念&#xff08;下&#xff09; 1、函数 函数用来对功能逻辑进行封装&#xff0c;能够增强复用、提高代码的可读 以下是函数的主要组成部分&#xff1a; 名称参数返回类型函数体 1.1、函数名称 在Rust中&…

电脑游戏运行时常见问题解析:穿越火线提示“unityplayer.dll丢失”的修复指南

电脑游戏运行时常见问题解析&#xff1a;穿越火线提示“unityplayer.dll丢失”的修复指南 在探索电脑游戏的无限乐趣时&#xff0c;我们时常会遇到一些不期而遇的挑战。今天&#xff0c;我们将聚焦于一个常见的游戏运行错误——穿越火线&#xff08;或其他使用Unity引擎的游戏…