C++学习记录——이십칠 C++11(3)

news2024/11/16 1:38:47

文章目录

  • 1、lambda
    • 1、捕捉列表
    • 2、简述C++线程
    • 3、lambda对象大小
  • 2、C++线程
    • 1、整体了解
    • 2、锁
      • 1、互斥锁
      • 2、递归互斥锁
      • 3、时间控制锁
      • 4、lock_guard
    • 3、atomic(原子)
    • 4、条件变量


1、lambda

在之前写排序时,用到过排升序,排降序,写了仿函数,有less和greater,这样就写好了排序。但是这样还是感觉有点复杂,因为我们还需要写两个函数。lambda可以解决这个问题,我们先看看lambda的语法。

在这里插入图片描述

int main()
{
	auto add1 = [](int x, int y)->int {return x + y; };
	cout << add1(1, 2) << endl;
	return 0;
}

add1是一个lambda对象。上面也可以写成[](int x, int y)…(1, 2)。还可以写成这样

	auto add2 = [](int x, int y)
	{
		return x + y;
	};

lambda表达式之间不能互相赋值

1、捕捉列表

假如要交换两个变量的值

	int x = 0, y = 1;
	auto swap1 = [](int& x, int& y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap1(x, y);
	cout << x << " " << y << endl;

	//利用捕捉列表
	auto swap2 = []()
	{
		int tmp = x;
		x = y;
		y = tmp;
	};

swap2是无法用x和y,这相当于一个独立的域,只是定义在main里面,但不能访问main的变量。

	auto swap2 = [x, y]() mutable
	{
		int tmp = x;
		x = y;
		y = tmp;
	};

如果不写mutable,那么像这样写的就是传值捕捉,lambda内部是默认const修饰的,不能修改,加上异变mutable就是去掉这一属性,加上了mutable必须带上参数列表,即使为空。但这样写完后,值并没有交换,因为这是传值。把传值捕捉改成引用捕捉[&x, &y],这样不加mutable也可以交换值,不过这里就是一个坑点,&被识别为引用而不是取地址。

还可以混合捕捉,[&x, y]。全部引用捕捉[&],全部传值捕捉[=],[&, x]意思就是只有x是传值,其它都是引用,但不能这样写[&, =],因为意图不明确。

2、简述C++线程

我们不能像Linux那样写歌pthread_create来创建线程,因为这是POSIX
的线程库,是类Unix的系统用的,它是一个可移植的操作系统接口,Linux选择了这个标准,而Windows没有选择,Windows有自己的标准,所以Windows想要和Linux支持完全一样的线程代码,可以用条件编译

#ifdef _WIN32
    CreateThread
#else
    pthread_create

像这样来写,就麻烦。后来Linux,Windows都支持一个thread库,转换的操作由库来完成,这个库是一个面向对象的,库的用法和POSIX有很多相似之处,也有不一样的,比如函数参数args,C++11就用可变模板参数替代POSIX的args。

C++中线程头文件是<thread.h>。我们写一点lambda的代码来初步了解一下C++线程。

//传统写法
void Func(int n, int num)
{
	for (int i = 0; i < n; ++i)
	{
		cout << num << ":" << i << " ";
	}
	cout << endl;
}

int main()
{
	thread t1(Func, 7, 1);//一般不传仿函数,而是传函数指针
	thread t2(Func, 4, 2);
	//但是这里主线程提前结束了,就会崩,所以要join
	t1.join();
	t2.join();
	return 0;
}
//lambda写法
int main()
{
	int n1, n2;
	cin >> n1 >> n2;
	//thread t1(Func, 7, 1);//一般不传仿函数,而是传函数指针
	//thread t2(Func, 4, 2);
	//但是这里主线程提前结束了,就会崩,所以要join
	thread t1([n1](int num)
	{
		for (int i = 0; i < n1; ++i)
		{
			cout << num << ":" << i << " ";
		}
		cout << endl;
	}, 1);
	thread t2([n2](int num)
	{
		for (int i = 0; i < n2; ++i)
		{
			cout << num << ":" << i << " ";
		}
		cout << endl;
	}, 2);
	t1.join();
	t2.join();
	return 0;
}

现在这两个线程代码有些冗余,因为它们做的操作都一样。线程不允许拷贝构造,但是可以移动构造。

int main()
{
	size_t m;
	cin >> m;
	//要求m个线程分别打印n
	vector<thread> vthds(m);
	for (size_t i = 0; i < m; i++)
	{
		size_t n;
		cin >> n;
		vthds[i] = thread([i, n]() {
			for (int j = 0; j < n; j++)
			{
				cout << i << ":" << j << endl;
			}
			cout << endl;
			});
	}
	return 0;
}

给vthds[i]的是一个将亡值。如果要去掉这些线程,因为库中没有拷贝构造的原因,我们得用引用来删除。

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

3、lambda对象大小

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;
	};
	cout << sizeof(swap1) << endl;
	return 0;
}

会打印出1。编译器会把lambda变成一个仿函数,并且又会把仿函数变成一个仿函数的类,lambda会被编译成一个仿函数类型的对象,仿函数的类是一个空类,没有给成员,所以它的大小是1。

在反汇编代码中,lambda会先构造一个仿函数对象,不过这个对象独特,每个lambda对象都不一样的仿函数类型,然后调用operator()函数。为什么两个lambda对象不能互相赋值?是因为它们的类型就不一样。

2、C++线程

1、整体了解

C++中线程的id是一个类,Linux是一个整数,获取id用get_id(),如果还在创建线程时就要看id,那么就用std里的子空间this_thread里的get_id函数来查看,哪个线程用就打印谁的id;Linux的sleep在C++中着两个函数,sleep_until和sleep_for,绝对和相对时间,时间上比如秒,second,休息多少秒是封装在一个chrono空间中的,这个需要引入头文件chrono

this_thread::sleep_for(chrono::seconds(1));

chrono里也有hours,minutes函数,milliseconds毫秒函数等。

无锁编程作为了解。无锁编程中会用yield接口,它可以主动让出时间片,也就是很少用锁的线程,实现无锁编程需要用到CAS,CAS能提供原子性的一系列操作,比如把旧值存起来,进行比较,如果在对这个值进行写操作的时候,它已经被改变了,那就放弃,进行swap或者set操作。在面对线程安全问题时,常用操作就是加锁,C++中也有锁的类,mutex。如果不加锁,那就是利用原子操作,它的本质就是CAS。

可以看陈皓大佬的这篇文章

无锁队列的实现

int i = 0;
i += 1;

CAS会如何处理这个+=?要对i加1,加1之前会先去i这个位置进行比较,旧值是0,新值是1,如果在写之前i的值和旧值一样,就set或者swap,不一样就false,不做操作,比较和set或swap是硬件上保证原子的。如果一个线程先改了值,另一个线程再过来时值已经被改动了,它会false,然后再继续判断它为什么false。

那么无锁编程如何利用CAS?在入队列时,先找到队列尾部,两个线程都想插入节点,如果tail的next是空,那就可以添加,那么另一个线程再去时就会发现tail的next不是空了,那它就退出,更新tail,再去看看能不能添加。

2、锁

1、互斥锁

经典场景

int x = 0;

void Func(int n)
{
	for (int i = 0; i < n; i++)
	{
		++x;
	}
}

int main()
{
	thread t1(Func, 100);
	thread t2(Func, 200);

	t1.join();
	t2.join();
	return 0;
}

程序能够正常运行,是因为线程的栈是独立的,两个线程执行两个函数会在各自的栈中开辟栈帧,不过访问的全局变量是一样的。这里肯定有线程安全问题,毕竟访问的是同一个全局变量。为了解决安全问题就加锁。

#include <mutex>

int x = 0;
mutex mtx;

void Func(int n)
{
	for (int i = 0; i < n; i++)
	{
		mtx.lock();
		++x;
		mtx.unlock();
	}
}

int main()
{
	thread t1(Func, 10000);
	thread t2(Func, 20000);

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

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

这样写有问题,for循环应当在临界区内,进入临界区之前加锁。

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

int main()
{
	int n = 100000;
	size_t begin = clock();
	thread t1(Func, n);
	thread t2(Func, n);

	t1.join();
	t2.join();
	size_t end = clock();
	cout << x << " | " << (end - begin) << endl;
	return 0;
}

实际上它也会快很多。并行不一定就会串行快,还要看具体的场景。上面的并行场景,t1加锁了,然后在执行,t2看到了就去阻塞,带着上下文切出去,但是代码只有++x,t1走得很快,可能t2上下文还没切出去t1就结束了,t2又得回来。当执行的任务多了,并行就会比串行效率高了。

lambda写法

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 << " | " << (end - begin) << endl;
	return 0;
}

2、递归互斥锁

int x = 0;

void Func(int n)
{
	if (n == 0)
		return;
	++x; 
	Func(n - 1);
}

int main()
{
	thread t1(Func, 10000);
	thread t2(Func, 10000);

	t1.join();
	t2.join();
	
	cout << x << endl;
	return 0;
}

Debug模式下会爆栈。加锁一下

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

这样加锁可以运行,但是解锁放在Func(n - 1)后就不行了,造成了死锁问题,这里就需要用递归互斥锁,即使是放在Func(n - 1)后面也能运行,不过如果n过大还是会爆栈。递归互斥锁在进入递归时,如果发现有自己之前加的锁,那就不加锁了,即使有lock。

3、时间控制锁

timed_mutex类型,有try_lock_for try_lock_until try_lock等函数,具体的查看cplusplus.com,for接口会接收一个时间,如果过了这段时间还没解锁那就会直接解锁。还有递归的时间控制锁。

4、lock_guard

假设加锁后,临界区内抛出了异常,抛后会直接跳到捕获异常的地方,那就会造成死锁,因为没有解锁。这个的解决办法是写一个类,构造时加锁,析构时自动解锁

template <class T>
class LockGuard
{
public:
	LockGuard(T& lk)
		:_lk(lk)
	{
		_lk.lock();
	}
	~LockGuard()
	{
		_lk.unlock();
	}
private:
	T _lk;
};

int x = 0;
mutex mtx;

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

异常之后会写。在抛异常时,锁出了作用域,就会自己解锁,也就是解决了死锁问题。但是这个写法是不对的

在这里插入图片描述

即使改成右值,初始化列表里写成_lk(move(lk))也不行,因为锁只有拷贝构造,没有移动构造。这样写就行

private:
	T& _lk;

加引用的成员变量必须在初始化列表里初始化。

库中有这个类,不需要我们自己写,有两个lock_guard和unique_lock,头文件mutex。如果有可能抛异常的话,那就用这两个接口,unique_lock支持手动解锁。

3、atomic(原子)

C++11把它封装成库,支持CAS。它支持±*/与或异或的原子性。常用的是atomic的类,默认构造可以不给值,并且不允许拷贝,看一下之前的这个代码

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++)
		{
			++i;
		}
		mtx.unlock();
	});
	thread t2([&, n]() {
		mtx.lock();
		for (int i = 0; i < n; i++)
		{
			++i;
		}
		mtx.unlock();
	});

	t1.join();
	t2.join();
	size_t end = clock();
	cout << x << " | " << (end - begin) << endl;
	return 0;
}

改成原子的

int main()
{
	int n = 100000;
	atomic<int> x = 0;// = {0} or 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 << " | " << (end - begin) << endl;
	return 0;
}

底层是用CAS支持的。内置类型的变量要做一些简单的操作用原子类就好,不需要加锁。但是用原子类来定义的变量,传给参数不是原子类定义的函数,不能传,就需要用store或者load函数,x.load()和x.store()。

4、条件变量

头文件condition_variable,提供了两种接口,wait和notify

写两个线程交替打印数字,一个打印奇数,一个打印偶数。

int main()
{
	int n = 100;
	int x = 1;
	size_t begin = clock();
	thread t1([&, n](){
		for (int i = 0; i < n; i++)
		{
			cout << this_thread::get_id() << ":" << x << endl;
		}
	});
	thread t2([&, n]() {
		for (int i = 0; i < n; i++)
		{
			cout << this_thread::get_id() << ":" << x << endl;
		}
	});

	t1.join();
	t2.join();
	size_t end = clock();
	cout << x << " | " << (end - begin) << endl;
	return 0;
}

打印出来的结果并不是我们想象的那样的交替,有可能同时打印一个数,即使加锁也不能解决问题,这就得用到条件变量。条件变量要配合锁,锁会用unique_lock。

wait接口,当前线程会阻塞,直到被唤醒。wait时会修改条件变量的值,其它线程也有可能修改,所以在wait之前需要加锁;在wait发生的一瞬间,就会解锁,其它线程就可以使用锁了,后面notify的时候,唤醒线程,一瞬间就会加上锁,所以要用unique_lock,需要不靠类来解锁。wait有wait_for和wait_until。

notify有notify_one和notify_all,随机唤醒一个和唤醒所有,但是唤醒所有的话谁先占据资源就不确定,所以通常不使用all。 notify_one会唤醒正在等待条件的线程,没有等待的就什么都不做。

	mutex mtx;
	condition_variable cv;
	int n = 100;
	int x = 1;
	size_t begin = clock();
	thread t1([&, n](){
		while (x < n)
		{
			unique_lock<mutex> lock(mutex);
			cv.wait(lock);
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
		}
	});
	thread t2([&, n]() {
		while(x < n)
		{
			unique_lock<mutex> lock(mutex);
			cv.wait(lock);
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
		}
	});

如何保证t1先运行,t2阻塞?wait会让线程阻塞在条件变量上。

	thread t1([&, n](){
		while (x < n)
		{
			unique_lock<mutex> lock(mutex);
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
		}
	});
	thread t2([&, n]() {
		while(x < n)
		{
			unique_lock<mutex> lock(mutex);
			cv.wait(lock);
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
		}
	});

如果t1申请了锁,那就执行;如果t2申请了锁,然后去wait,释放锁,t1再申请到了锁,然后执行。等到t1执行完,t2才能申请到锁来执行代码。

那么接下来如何防止一个线程不断运行?如果t1这样写

	thread t1([&, n](){
		while (x < n)
		{
			unique_lock<mutex> lock(mtx);
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
	});

线程都是要看自己的时间片来运行的,唤醒t2后,t2会去加锁,t1时间又回到了加锁那一行,因为t1时间片还没到,t1和t2抢锁,t1申请到了,t1就会继续打印。实际也确实会出现这种情况。

	thread t1([&, n](){
		while (x < n)
		{
			unique_lock<mutex> lock(mtx);
			if (x % 2 == 0)//t1打印奇数,偶数就去阻塞
			{
				cv.wait(lock);
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
	});
	thread t2([&, n]() {
		while(x < n)
		{
			unique_lock<mutex> lock(mtx);
			cv.wait(lock);
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
		}
	});

按照这样写,t1连续得到锁后,它wait,释放锁,阻塞在条件变量上,t2就能拿到锁,然后执行,回到加锁代码行上,此时t1还在wait,没唤醒它,然后t2也来到wait那一行,两者就都wait,所以t2也要有唤醒的动作。但是即使有唤醒,也有可能两者运行速度不一样而导致问题,所以t2也得有条件判断。

	thread t1([&, n](){
		while (x < n)
		{
			unique_lock<mutex> lock(mtx);
			if (x % 2 == 0)//t1打印奇数,偶数就去阻塞
			{
				cv.wait(lock);
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
	});
	thread t2([&, n]() {
		while(x < n)
		{
			unique_lock<mutex> lock(mtx);
			if (x % 2 != 0)
			{
				cv.wait(lock);
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
	});

这样它们就都能保证交替打印了。

为什么会出现这些问题?t1拿到了锁,t2阻塞在锁上,t1打印,t1notify,但是没有线程wait,那么t1的锁出了作用域,解锁,这时候t1和t2竞争锁,一般情况下t2会获取锁,即使t1获取锁,它还是会wait。即使某个线程时间片到了,切出去了,另一个线程也不会继续打印。

也可以这样改

	thread t1([&, n](){
		while (1)
		{
			unique_lock<mutex> lock(mtx);
			if (x >= n) break;
			if (x % 2 == 0)//t1打印奇数,偶数就去阻塞
			{
				cv.wait(lock);
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
	});
	thread t2([&, n]() {
		while(1)
		{
			unique_lock<mutex> lock(mtx);
			if (x > n) break;
			if (x % 2 != 0)
			{
				cv.wait(lock);
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;
			cv.notify_one();
		}
	});

另外还有一个版本的wait,wait返回false就阻塞,true就继续执行,适合多生产多消费。

cv.wait(lock, [&x]() {return x % 2 != 0; });//t1
cv.wait(lock, [&x]() {return x % 2 == 0; });//t2

本篇gitee

结束。

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

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

相关文章

leetcode438. 找到字符串中所有字母异位词(java)

滑动窗口 找到字符串中所有字母异位词滑动窗口数组优化 上期经典 找到字符串中所有字母异位词 难度 - 中等 Leetcode 438 - 找到字符串中所有字母异位词 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出…

msvcp140.dll丢失的解决方法,win10系统dll报错的解决方法

今天&#xff0c;我将为大家分享一个关于msvcp140.dll丢失的解决方法&#xff0c;特别是针对在Windows 10系统上遇到这个问题的朋友们。在开始之前&#xff0c;我想先简要介绍一下msvcp140.dll文件的作用。msvcp140.dll是Microsoft Visual C运行时库的一部分&#xff0c;它包含…

基于Java+SpringBoot+Vue前后端分离智慧图书管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

【【萌新的STM32学习-18 中断的基本概念3】】

萌新的STM32学习-18 中断的基本概念3 EXTI和IO映射的关系 AFIO简介&#xff08;F1&#xff09; Alternate Function IO 复用功能IO 主要用于重映射和外部中断映射配置 1.调试IO配置 来自AFIO_MAPR[26:24] , 配置JTAG/SWD的开关状态 &#xff08;这个我们并不用太过深刻的关注&…

使用实体解析和图形神经网络进行欺诈检测

图形神经网络的表示形式&#xff08;作者使用必应图像创建器生成的图像&#xff09; 一、说明 对于金融、电子商务和其他相关行业来说&#xff0c;在线欺诈是一个日益严重的问题。为了应对这种威胁&#xff0c;组织使用基于机器学习和行为分析的欺诈检测机制。这些技术能够实时…

【C++11新特性】可变参数模板

文章目录 1. 认识可变参数模板2. 可变参数模板的定义方式3. 参数包的展开方式3.1 递归展开参数包3.2 逗号表达式展开参数包 1. 认识可变参数模板 可变参数模板是C11新增的最强大的特性之一&#xff0c;它对参数高度泛化&#xff0c;能够让我们创建可以接收可变参数的函数模板和…

【SpringBoot学习笔记02】静态资源

Spring Boot 通过 MVC 的自动配置类 WebMvcAutoConfiguration 为这些 WebJars 前端资源提供了默认映射规则&#xff0c;部分源码如下。 jar包&#xff1a; JAR 文件就是 Java Archive File&#xff0c;顾名思意&#xff0c;它的应用是与 Java 息息相关的&#xff0c;是 Java 的…

springboot整合rabbitmq死信队列

springboot整合rabbitmq死信队列 什么是死信 说道死信&#xff0c;可能大部分观众大姥爷会有懵逼的想法&#xff0c;什么是死信&#xff1f;死信队列&#xff0c;俗称DLX&#xff0c;翻译过来的名称为Dead Letter Exchange 死信交换机。当消息限定时间内未被消费&#xff0c;…

编码过程中需要注意哪些安全问题?

SQL 安全 注入式&#xff08;Inject&#xff09;攻击是一类非常常见的攻击方式&#xff0c;其基本特征是程序允许攻击者将不可信的动态内容注入到程序中&#xff0c;并将其执行&#xff0c;这就可能完全改变最初预计的执行过程&#xff0c;产生恶意效果。下面是几种主要的注入…

为 Python 创建别名

有时您有自己喜欢的 Python 版本&#xff0c;并且不想在新版本到来时放弃它。 您的旧脚本可能无法在新版本的 Python 上运行&#xff0c;或者旧版本上的项目太多&#xff0c;将它们迁移到新版本是一场马拉松。 在这种情况下&#xff0c;您决定保留两个版本的 Python。 在本文中…

什么是算法?

目录 算法是指解决方案的准确而完整的描述。 1.算法的基本特征 所谓算法&#xff0c;是一组严谨地定义运算顺序的规则 并且每一个规则都是有效的&#xff0c;且是明确的 此顺序将在有限的次数下终止 什么是算法&#xff1f; 算法的4个基本特征 算法的6个基本方法 选择算…

浏览器输入一个URL之后发生了什么?

URL解析DNS解析TCP连接TSL连接HTTP请求TCP挥手接收并解析响应 URL 解析 主要分为&#xff1a; 协议&#xff0c;eg http,https域名或者ip地址&#xff0c;eg www.baidu.com 域名相对于ip地址来说&#xff0c;更方便人们记忆&#xff0c;但是实际的网络传输中使用的是ip地址 端…

Java“牵手”天猫商品快递费用API接口数据,天猫API接口申请指南

天猫平台商品快递费用接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取天猫商品的标题、价格、库存、商品快递费用&#xff0c;宝贝ID&#xff0c;发货地&#xff0c;区域ID&#xff0c;快递费用&#xff0c;月销量、总销量、库存、详情…

十几款拿来就能用的炫酷表白代码

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 表白代码 1、坐我女朋友好吗&#xff0c;不同意就关机.vbs2、坐我女朋友好吗&…

订单状态定时处理、来单提醒和客户催单

一、Spring Task 1.1 基本介绍 1.2 cron表达式 cron表达式其实就是一个字符串&#xff0c;通过cron表达式可以定义任务触发的时间 构成规则&#xff1a;分为6或7个域&#xff0c;由空格分隔开&#xff0c;每个域代表一个含义 每个域的含义分别为&#xff1a;秒、分钟、小时、日…

C++|观察者模式

观察者模式&#xff1a; 定义对象间的一种一对多&#xff08;变化&#xff09;的依赖关系&#xff0c;以便当一个 对象(Subject)的状态发生改变时&#xff0c;所有依赖于它的对象都 得到通知并自动更新 动机&#xff1a; 在软件构建过程中&#xff0c;我们需要为某些对象建立…

【深度学习 | 核心概念】那些深度学习路上必经的核心概念,确定不来看看?(一)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

基于吉萨金字塔建造算法优化的BP神经网络(预测应用) - 附代码

基于吉萨金字塔建造算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于吉萨金字塔建造算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.吉萨金字塔建造优化BP神经网络2.1 BP神经网络参数设置2.2 吉萨金字塔建造算法应用…

SpringBoot与前端交互遇到的一些问题

一、XXX.jar中没有主清单属性 场景&#xff1a; SpringBoot打的jar包在Linux运行报错 解决方案&#xff1a; 百度找了很多都是一样的答案&#xff0c;但是解决不了我的问题&#xff0c;于是我新建了一个springboot项目发现打的jar包可以在Linux上运行。检查了下只要把下面这2个…

R语言之缺失值处理

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/dive-into-AI 】或者公众号【AIShareLab】回复 R语言 也可获取。 文章目录 缺失值处理1. 识别缺失值2. 探索数据框里的缺失值3. 填充缺失值3.1 删除缺失值&#xff1a;na.omit( )、complete.cases( …