C++11【二】

news2024/11/19 16:48:56

文章目录

  • 一、lambda表达式
  • 二、线程库
  • 三、包装器
  • 四、绑定(不常用)

一、lambda表达式

如果待排序元素为数据集合中的元素和自定义类型,需要用户定义排序时的比较规则不同如下代码,但都可以使用sort方法,

#include <algorithm>
#include <functional>
int main()
{
int a[] = {4,1,8,5,3,7,0,9,2,6};
sort(a, a+sizeof(a)/sizeof(a[0]));
sort(a, a + sizeof(a) / sizeof(a[0]), greater<int>());
return 0;
}
struct T
{
 
      T(const char* str, double price)
     :_name(str)
     , _price(price)
      {}
string _name;  
double _p;
};

struct CPLess
{
      bool operator()(const T& p, const T& p)
      {
        return p._p < p._p;
      }
};
struct CPGreater
{
      bool operator()(const T& p, const T& p)
      {
        return p._> p._p;
       }
};
int main()
{
       vector<T> t= { { "A", 6.6 }, { "B", 6}, { "C", 1.1}, { "D",5.7  } };
      sort(t.begin(), t.end(), CPLess());
sort(t.begin(), t.end(), CPGreater());
}

随着C++语法的发展,发现上面的写法太复杂,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式,上面的代码使用C++11中的lambda表达式来解决,可以看出它实际是一个匿名函数。

int main()
{
   vector<T> t= { { "A", 6.6 }, { "B", 6}, { "C", 1.1}, { "D",5.7  } };
sort(t.begin(), v.end(), [](const T& g1, const T& g2){
return g1._p < g2._p; });
sort(t.begin(), t.end(), [](const T& g1, const T& g2){
return g1._p > g2._p; });
}

lambda表达式格式:[c] § mutable -> return-type {s}。
1、[c] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
2、§:参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
3、mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
4、->return type:返回值类型。用->形式声明函数的返回值类型,没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
5、{s}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意:在lambda函数定义中,参数列表和返回值类型可以不写,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

捕获列表详解:捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。捕捉外面作用域的变量。
[v]:表示值传递方式捕捉变量v,我们传值的直接用x和y的话,它是一个常量不能修改,所以在后面加上mutable,可变的。但是没有完成交换,传值是一种拷贝,里面的改变不影响外面。正确写法要接下来要说的引用捕捉。

int main()
{
	int x = 0, y = 1;
	int m = 0, n = 1;

	auto swap1 = [](int& rx, int& ry)
	{
		int tmp = rx;
		rx = ry;
		ry = tmp;
	};

	swap1(x, y);
	cout << x << " "<< y << endl;

	// 传值捕捉
	auto swap2 = [x, y]() mutable 
	{
		int tmp = x;
		x = y;
		y = tmp;
	};

	swap2();
	cout << x << " " << y << endl;
}

[&v]:表示引用传递捕捉变量v

	// 引用捕捉
	auto swap2 = [&x, &y]()
	{
		int tmp = x;
		x = y;
		y = tmp;
	};

当变量太多可以用一种方式来解决,[=]:表示值传递方式捕获所有父作用域中的变量(包括this)和[&]:表示引用传递捕捉所有父作用域中的变量(包括this),也可以混合捕捉。捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复,但是可以[&,a]&全部引用捕捉出了a传值捕捉。
应用场景:比如线程用到lambda表达式,要求m个线程分别打印n。

int main()
{

	size_t m;
	cin >> m;
	vector<thread> vthds(m);
	for (size_t i = 0; i < m; i++)
	{
		size_t n;
		cin >> n;

		vthds[i] = thread([i, n, m]() {
			for (int j = 0; j < n; j++)			{
				cout << i << ":" << j << endl;
			}
		cout << endl;
			});
	}

	for (auto& t : vthds)
	{
		t.join();
	}

	return 0;
}

lambda大小
首先 lambda是一个可调用对象,模拟的是函数对象即仿函数对象。函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。
lambda其实只有一个字节大,它在底层会被编译器处理成仿函数,参数都会作为仿函数类的参数,lambda对象就是生成仿函数类的类型,它会被编译器转换为一个没有名字的仿函数类对象并由编译器生成类名。而仿函数的类是一个空类,空类对象没有给成员变量,就是一个字节。

总之,底层编译器对于lambda表达式的处理方式,就是按照函数对象的方式处理的,如果定义了一个lambda表达式,编译器会自动生成一个类,名称叫lambda+uuid(唯一字符串保证生成不同的类),在该类中重载了operator()。

所以lambda表达式之间不能相互赋值,即使看起来类型相同,因为底层生成的是不同的类型,每次的生成类名不一样属于不同的类,不同类不能互相赋值。

class Rate
{
public:
R(double r): _r(r)
{}
double operator()(double m, int y)
{ return m * _r * y;}
 private:
   double _r;
};
int main()
{
// 函数对象
   double r = 0.49;
   R a(r);
   a(10000, 2);
   // lambda
  auto b = [=](double m, int y)->double{return m*r*y;
};
   b(10000, 2);
   return 0;
}

二、线程库

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

thread() :构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
thread(fn,args1, args2,…):构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
get_id() :获取线程id
joinable() :线程是否还在执行,joinable代表的是一个正在执行中的线程。
join() :该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach():在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

1.线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
3、当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下有三种方式提供:函数指针、lambda表达式、函数对象。
4、thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象。
5、 可以通过joinable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:
采用无参构造函数构造的线程对象
线程对象的状态已经转移给其他线程对象
线程已经调用join或者detach结束
线程函数参数
线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,所以即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。如果想要通过形参改变外部实参时,必须借助std::ref()函数。

void ThreadFunc1(int& x)
{
x += 10;
}
void ThreadFunc2(int* x)
{
*x += 10;
}
int main()
{
int a = 10;
thread t1(ThreadFunc1, a);
t1.join();
cout << a << endl;

thread t2(ThreadFunc1, std::ref(a);
t2.join();
cout << a << endl;

// 地址的拷贝
thread t3(ThreadFunc2, &a);
t3.join();
cout << a << endl;
return 0;
}

atomic
多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的是没有事的,因为只读操作不会影响到数据,也不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦,传统方式可以对共享修改的数据可以加锁保护,是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁,所以C++11就引入了原子操作。原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。

它支持原子性,让我们不用加锁即无锁编程,比如线程对一个变量同时进行加减。无锁编程就是尽可能少使用锁,如果这有一个队列,多线程往里面插入数值,要加锁,要加锁效率有点低,这时候就可以实现无锁队列。
首先要保证原子性,保证线程安全。比如两个线程尾插结点,有两个new1和new2,如果同时来的话,没加锁就会导致会导致一个添加上造成内存泄漏,所要要加锁,但是锁太重了。

在C++11中,不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问,使用atomic类模板,定义出需要的任意原子类型。

原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等
如下代码:

int main()
{
	int n = 100000;
	atomic<int> x = 0;;


	thread t1([&, n](){
			for (int i = 0; i < n; i++)
			{
				++x;
			}
		});

	thread t2([&, n]() {
			for (int i = 0; i < n; i++)
			{
				++x;
			}
		});

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

	cout << x << endl;
	return 0;
}

mutex mtx;
int x = 0;
void Func(int num)
{

	 //并行
	for (int i = 0; i < num; i++)
	{
		mtx.lock();
		++x;
		mtx.unlock();
	}
	
	// 串行
/*	mtx.lock();
	for (int i = 0; i < num; i++)
	{
		++x;
	}
	mtx.unlock()*/;
}

int main()
{
	int k= 10000;
	thread t1(Func,1000);
	thread t2(Func,2000);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

第一个相当于并发,意思就是交错着加加,第二个是串行,意思就是两个中的一个先执行for循环另一个在执行for循环即一个线程跑完另一个再跑。我们经过测试后发现串行比并行快。因为以并行来看,有两个线程来到for循环里面,t1执行,t2阻塞会进入休眠状态切出去,切换上下文会消耗很多时间,可能还没切出去就已经解锁了,就把t2唤醒回来。
在这里插入图片描述
如果我们使用list,加完之后再插入或者再增加一些操作,它们的时间就差不多了就往中间靠了,并行可以接近于串行了。并行的时候一个线程执行操作,另一个在阻塞着,但是它可以做下面的事情比如打印,所以串行不一定比并行快。

我们再用前面的lambda表达式改造一下,不期望n改变,因为锁要变,所以捕捉列表可写写[&,n]:

int main()
{
	int n = 100000;
	int x = 0;
	mutex mtx;
	size_t begin = clock();

	thread t1([&, n](){
			mtx.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mtx.unlock();
		});

	thread t2([&, n]() {
	      	mtx.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			
			}
			mtx.unlock();
		});
	t1.join();
	t2.join();
	size_t end = clock();
	cout << x << endl;
	cout << end - begin << endl;
	return 0;
}

有的时候我们需要递归互斥锁:recursive_mutex,如果不用,栈可能会死锁,比如解锁在func(n-1)后面,t1进来还没解锁又调用自己自己把自己又锁住了。它可以加锁之后,如果递归进来发现是自己就会解开。

int x = 0;
recursive_mutex mtx;
void Func(int n)
{
	if (n == 0)
		return;
	mtx.lock();
	++x;
	Func(n - 1);
	mtx.unlock();
}

int main()
{
	thread t1(Func, 2000);
	thread t2(Func, 2000);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

锁这里还面临另一个问题:加锁解锁之后,中间会抛异常,会产生死锁,它调到catch里面,没有解锁,另一个线程再来就会被卡住。
有些地方想支持就算抛异常也不会影响,实现了一个lockguard的类,它借助构造函数把锁给它,然后进行加锁,然后用析构函数解锁,这里这样写不对编译不过,就是在初始化列表那里出了问题,也不能加move因为它没有移动构造,只有在私有域加个引用,是锁对象的别名。但是实际不需要我们写,库里面有lock_guard,还有unique_lock,它可以手动加锁和解锁,比如可能中途需要解锁做一些动作,解开过一会再加过来。

template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lk)
		:_lk(lk)
	{
		_lk.lock();
	}

	~LockGuard()
	{
		_lk.unlock();
	}

private:
	Lock& _lk;
};
void Func(int n)
{
	for (int i = 0; i < n; i++)
	{
		try
		{
			
			//LockGuard<mutex> lock(mtx);
			//lock_guard<mutex> lock(mtx);
			unique_lock<mutex> lock(mtx);
			++x;
			// 抛异常
			if (rand() % 3 == 0)
			{
				throw exception("抛异常");
			}
			//mtx.unlock();
		}
		catch (const exception& e)
		{
			cout << e.what() << endl;
		}
	}
	
}

int main()
{
	
	thread t1(Func, 10);
	thread t2(Func, 10);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

下面就是条件变量
有一个经典操作,两个线程交替打印偶数和奇数。
这时候就需要条件变量,非常适合我走一下通知你一下的同步场景,交替走。
用一个条件变量就行
1、首先要保证谁先运行,如果让t1打印奇数,那它就需要先运行,放在前面不到一定它运行,一个线程被创建起来,它要放到CPU排队,它有可能排上也有可能排不上。那么如何保证t1先运行。
先熟悉一下条件变量的接口:还要注意条件变量不是线程安全的,要结合锁使用。
wait接口:当前执行的线程会进行阻塞,直到被唤醒,没有通知它之前都会进行阻塞,在马上阻塞的那一瞬间,这个函数会被unlock,允许其他线程继续。
在wait的时候必须得配合互斥锁:一个线程准备在条件变量上等待,但是进入等待之前另一个线程唤醒了条件变量,也就是wait操作被挂起,但并没有完成wait,这时一个线程触发信号操作唤醒,然后第一个线程才完成wait操作,这个线程收不到另一个线程的信号操作即导致唤醒丢失,而且还要防止多个线程同时请求wait,在wait之前要加锁保证wait是原子操作,怎么把锁加上,得传一个unique对象,构造对象的时候就是把锁加上了,也让在wait的时候把锁解开,让其他线程继续走。
如果其他线程notified,唤醒那一瞬间先获取锁,再修改条件变量的值,用锁进行保护。
notified接口:notified_one:通知一个等待的线程;notified_all:通知所有等待线程。如果没有等待什么都不做。

在这里插入图片描述
2、解决保证t1先运行,t2打印偶数,加一个判断条件,如果等于奇数就阻塞,进行wait。直到被t1notified唤醒
分析:有两种情况,t1先抢到锁,t2后抢到锁,t1先运行,t2阻塞在锁上面

t2先抢到锁,t1后抢到锁,t2先运行,t1阻塞在锁上面,但是t2会被一下步wait阻塞,并且wait会解锁,保证了t1先运行。
所以无论哪种可能,谁先抢到锁,都能保证t1先运行。
3、接下来要防止一个线程不断运行,以上t1会一直运行,要阻止它打印第二次,所以要让t1阻塞,因为t1要打印奇数就往后走,加一个判断条件,如果等于偶数就阻塞,进行wait。直到被t2唤醒。

两种写法,wait还提供一个前置条件的仿函数,如果是false就阻塞。如下:

int main()
{
	mutex mtx;
	condition_variable cv;

	int n = 100;
	int x = 1;
	thread t1([&, n]() {
		while (1)
		{
			unique_lock<mutex> lock(mtx);
			if (x >= 100)
				break;

			//if (x % 2 == 0) // 偶数就阻塞
			//{
			//	cv.wait(lock);
			//}
			cv.wait(lock, [&x]() {return x % 2 != 0; });

			cout << this_thread::get_id() << ":" << x << endl;
			++x;

			cv.notify_one();
		}
		});

	thread t2([&, n]() {
		while (1)
		{
			unique_lock<mutex> lock(mtx);
			if (x > 100)
				break;

			//if (x % 2 != 0) // 奇数就阻塞
			//{
			//	cv.wait(lock);
			//}
			cv.wait(lock, [&x](){return x % 2 == 0; });


			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
		});
	t1.join();
	t2.join();
	return 0;
}

三、包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。也是可调用对象,它是对以前的对象进行包装比如函数指针、仿函数,包装成新的可调用对象,统一类型。
先看下面这代码,可以看出usf函数模板会被实例化为三份,因为f是变化的,可能是函数之后或仿函数或lambda,x同一个类型。

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(function<double(double)>([](double d)->double{ return d / 4; }), 11.11) << endl;

	return 0;
}

我们要想出一种能让只实例化一份,让各种可调用对象进行类型统一。包装器就可以解决,
类模板原型如下:

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;
	}

	int plusd(int a, int b)
	{
		return a + b*10;
	}

private:
	int x = 10;
};

int main()
{
	function<int(int, int)> f1(f);
	function<int(int, int)> f2(f);  // 函数指针

	cout << f1(1, 2) << endl;
	cout << f2(1, 2) << endl;

	//function<int(int, int)> f3(Functor()); // 识别报错
	function<int(int, int)> f3 = Functor();// 函数对象
	cout << f1(1, 2) << endl;
	cout << f3(1, 3) << endl;

	function<int(int, int)> f4 = [](const int a, const int b) {return a + b; }; // lambda
	cout << f4(1, 3) << endl;

	function<int(int, int)> f5 = &Plus::plusi;  // 类静态成员函数指针
	cout << f5(1, 2) << endl;

	Plus plus;
	function<int(Plus, int, int)> f6 = &Plus::plusd;  // 类成员函数指针
	cout << f6(plus, 1, 2) << endl;

	function<int(int, int)> f7 = [&plus](int x, int y)->int {return plus.plusd(x, y); };
	cout << f7(1, 2) << endl;
// 函数名
	cout << useF(function<double(double)>(f), 11.11) << endl;

	// 函数对象
	Functor ft;
	cout << useF(function<double(double)>(ft), 11.11) << endl;

	// lamber表达式
	cout << useF(function<double(double)>([](double d)->double{ return d / 4; }), 11.11) << endl;

	return 0;

	return 0;
}

还有一种场景,可以建立命令和动作的映射关系,传任何可调对象都可以:

int(vector<string>&tokens)
{
     stack<int>st;
     map<string,function<int(int,int)>>opmap={}
     {{"+",[](int x,int y)->int{return x+y;}
     {"-",[](int x,int y)->int{return x-y;}
     {"*",[](int x,int y)->int{return x*y;}
     {"/",[](int x,int y)->int{return x/y;}}
     for(auto& str:tokens)
     {
       if(opmap.count(str)==0)
       {
            st.push(stoi(str));
        }
        else
        {
            int r=st.top();
            st.pop();
            int l=st.top();
            st.pop();
            st.push(opmap[str](l,r));
        }
     }

}

四、绑定(不常用)

如果说包装器是类模板,那它就是是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

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

int SubFunc(int a, int b)
{
	return a - b;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b * x;
	}
private:
	int x = 20;
};

int main()
{
	//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
	function<int(int, int)> func1 = bind(Plus, placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;

	function<int(int, int)> func2 = bind(SubFunc, placeholders::_1, placeholders::_2);
	cout << func2(1, 2) << endl;

	// 调整参数的顺序
	function<int(int, int)> func3 = bind(SubFunc, placeholders::_2, placeholders::_1);
	cout << func3(1, 2) << endl;

	function<bool(int, int)> gt = bind(less<int>(), placeholders::_2, placeholders::_1);
	cout << gt(1, 2) << endl;

	// 绑定固定参数减少参数
	function<int(Sub, int, int)> func4 = &Sub::sub;
	cout << func4(Sub(), 10, 20) << endl;
	cout << func4(Sub(), 100, 200) << endl;

	function<int(int, int)> func5 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << func5(10, 20) << endl;
	cout << func5(100, 200) << endl;

	return 0;
}

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

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

相关文章

前端Vue3使用Moment Timezone处理不同时区时间

文章目录 前言一、基本时间概念UTC &#xff0c; GMT本地时间 为了方便比较&#xff0c;图中黑色背景的是 中国&#xff08;China&#xff09;区电脑&#xff0c;白色主题背景的是美国&#xff08;US&#xff09;区电脑&#xff09;2023-06-27T14:00:00.000Z 二、实际案例表单填…

正确认识:DOTA-E[c(RGDyK)2],DOTA标记序列多肽,双环肽螯合物

试剂基团反应特点&#xff08;Reagent group reaction characteristics&#xff09;&#xff1a; DOTA-E[c(RGDyK)2]为双环肽螯合物&#xff0c;DOTA标记序列多肽&#xff0c;螯合物前体多肽&#xff1a;放射性金属元素&#xff0c;例如&#xff1a; Cu、 Ga、 Lu、等可以通过与…

0基础入门---第四章---神经网络的学习

&#x1f31e;欢迎来到深度学习的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f64f;作者水平很有限&#xff0c;如果发现错误&#xff…

【突发小技巧】手动将jar包导入本地Maven仓库

1、下载jar包 以支付宝sdk为例&#xff1a;https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java/4.33.12.ALL 后面执行mvn install命令(也就是安装命令&#xff0c;生命周期&#xff1a;编译、测试、打包、安装)&#xff0c;也会参考这个依赖坐标&#xff1a; …

前端安全 - 保护你的应用免受攻击的关键

80. 前端安全 - 保护你的应用免受攻击的关键 作为前端工程师&#xff0c;我们不仅需要关注用户界面的设计和功能实现&#xff0c;还需要关注应用程序的安全性。前端安全是保护我们的应用程序免受恶意攻击和数据泄露的重要方面。本文将介绍前端安全的概念、常见的安全威胁以及一…

Linux集群服务器上运行命令的4个实用工具

目录 1. PSSH-Parallel SSH 安装parallel-ssh&#xff0c; 使用 2. Pdsh-并行远程Shell实用程序 3. ClusterSSH 安装 4. Ansible 我们假设你已经设置好了SSH以便访问所有服务器;其次假设&#xff0c;同时访问多台服务器时&#xff0c;在所有Linux服务器上设置基于密钥的无…

clone浅拷贝

通过一个实例对象 生成另一个对象&#xff0c;称为 拷贝。 需要 实现 java.lang.Cloneable 该接口是一个 标记接口 通过调用 clone() 方法&#xff0c;完成 对象拷贝。该方法是Object类中的方法&#xff0c;所有对象都继承该方法。 clone()方法是 浅拷贝&#xff0c;也就是 只…

Vue+vite创建项目关于vite.config.js文件的配置

Vuevite创建项目关于vite.config.js文件的配置 Vue项目创建时&#xff0c;我们见过vue-cli 创建项目和webpack 创建项目等方式。 现在Vue 3版本使用npm/pnpm create vuelatest 创建项目&#xff0c;是搭配使用vite工具构建的。 创建完成的项目&#xff0c;最明显的去别就是&am…

【硬件自动化软件设计及实现】如何设计并实现!

今天来聊聊关于硬件方向的自动化软件设计及实现,后面我会用实例来让我们更加深入的了解硬件自动化,首先开发工具选择的是python语言,为啥选择python语言呢,因为他的语法比较简洁,外置库非常多,反正就是对于做自动化方面很实用就对了。 1.硬件自动化测试大致分为三个阶段实…

Redis 来了,Navicat 用户炸开了锅 | 文末附免单王获奖名单

近期&#xff0c;Navicat 的后台热闹无比&#xff01;自 2023 年 5 月 Navicat Premium 16.2 Beta 中文版上线以来&#xff0c;童鞋们的留言如潮水般涌来。6 月中旬&#xff0c;我们正式发布了 Navicat Premium 16.2 与 Navicat for Redis&#xff0c;赋予了Navicat 更卓越的功…

JMeter工具接口性能压力测试分析与优化

目录 前言&#xff1a; 一、具体测试结果如下&#xff1a; 二、初始应用配置调整&#xff1a; 三、分析解决过程&#xff1a; 总结&#xff1a; 前言&#xff1a; 最近公司做的项目&#xff0c;要求对相关接口做性能压力测试&#xff0c;在这里记录一下分析解决过程。 压…

[Pytorch]导数与求导

文章目录 导数与求导一. 标量 向量 矩阵 的导数二.Pytorch中的反向求导.backward()三.非标量求导 导数与求导 一. 标量 向量 矩阵 的导数 标量&#xff0c;向量&#xff0c;矩阵间求导后的形状&#xff1a; y\x标量x(1)向量 x(n,1)矩阵 X(n,k)标量y(1)(1)(1,n)(k,n)向量 y(m…

Unity Image/GL实现一个框选功能吧

场景准备 准备一张框选背景图 导入到unity之后&#xff0c;修改 Texture Type 为 Sprite&#xff0c;&#xff08;根据图片需要&#xff09;在 Sprite Editor 中 编辑 九宫格格式。图片样式的不一致&#xff0c;设置的九宫格格式也不一致。本例中虚线部分需要等距离平铺&…

Visual C++类的继承及类中成员的访问特性——搞懂public、protected、private

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来说说Visual C中类的继承及类中成员的访问特性&#xff0c;也就是来搞懂public、protected、private这三个东西。 很多人搞不清楚这三个东西&#xff0c;并且很容易弄错&#xff0c;其实不是学习的人的…

vr全景在线虚拟展馆节约企业成本费用

博物馆作为人们了解历史、文化和艺术的重要场所&#xff0c;现在可以通过VR全景技术来进行展览&#xff0c;让参观者身临其境地感受历史文化的魅力。本文将介绍博物馆VR全景的特点、优势&#xff0c;以及如何使用VR全景技术来使得博物馆的展览和教育活动更丰富。 参观者可以对内…

【Linux】权限理解

Linux权限理解 shell外壳运行原理为什么我们不是直接访问操作系统&#xff1f;外壳程序的意义 权限的概念与操作用户的权限如何进行Linux下用户身份的切换? 角色和文件的权限权限是什么&#xff1f;Linux中文件的类型是如何被确定的&#xff1f;权限与角色的关系权限与文件属性…

玩机搞机---修改系统固件不开机 安卓13去除系统app签名验证的几种方法

谷歌在安卓13中对系统应用添加了一层校验验证&#xff0c;你如果修改了系统app.那么原有的签名加载后过不去验证&#xff0c;会导致进不去系统卡第一屏或者进入系统后修改的app错误等等故障。 Android 13增加了新的apk签名校验机制&#xff0c;现在开机中它会对所有系统分区&a…

Hive中怎样创建和查询视图信息?

视图是从数据库的数据表中选取出来的数据组成的逻辑窗口&#xff0c;它是一个虚拟机表。引入视图后&#xff0c;用户可以将注意力集中在关心的数据上&#xff0c;如果数据来源于多个基本表结构&#xff0c;并且搜索条件比较复杂时&#xff0c;需要编写的查询语句就会比较烦琐&a…

hivesql 将数据处理成复杂json

类型一 原数据&#xff1a;bankid是array类型 目标数据&#xff1a; {"bankname": ["SPDB", "WS_HBBANK", "mytest"],"grid": [{"name": "阶段1","values": ["38.0,1.0,1.0"]}…

安全生产月评选活动-优秀的“安全之星”,塑造榜样力量

安全生产月评选活动-优秀的“安全之星”&#xff0c;塑造榜样力量。 推荐功能&#xff1a;投票 企业可以举办安全生产月评选活动&#xff0c;选出优秀的“安全之星”&#xff0c;进行内部评选&#xff0c;塑造榜样力量。 首先&#xff0c;让我们先来了解一下“安全生产月评选活…