【C++】右值引用 移动语义

目录

    • 前言
    • 一、右值引用与移动语义
      • 1.1 左值引用和右值引用
      • 1.2 右值引用使用场景和意义
      • 1.3 右值引用引用左值及其一些更深入的使用场景分析
        • 1.3.1 完美转发
    • 二、新的类功能
    • 三、可变参数模板


前言

本篇文章我们继续来聊聊C++11新增的一些语法——右值引用,我们在之前就已经讲过了左值引用,并且左值引用给我们带来了很多的好处直接减少了拷贝操作提高了效率,那么右值引用到底起什么作用呢?下面我们一起来学习吧!!

一、右值引用与移动语义

1.1 左值引用和右值引用

有关引用我们在之前的文章就讲过,它其实就是给别人取别名,所以无论是左值引用还是右值引用都是给别人取别名,只不过取别名的对象不一样罢了,左值引用就是给左值取别名,右值引用就是给右值取别名!!

什么是左值?什么是左值引用?

左值就是一个表示数据的表达式(如变量名或者解引用的指针),说的再通俗一点它是一个变量,标识一块空间,空间中存储着数据。我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	// 以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;
	int& pvalue = *p;
	return 0;
}

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(临时对象不能左值返回)等等,说的再通俗一点右值就是一个常量,它没有变量去标识,因此也就不能取到它的地址,它只是一个数据!!右值引用就是对右值的引用,给右值取别名。

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	// 下面的语句都会编译报错,左操作数必须为左值
	//10 = 1;
	//x + y = 1;
	//fmin(x, y) = 1;
	return 0;
}

需要注意的是:右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。也就是说,不能取字面量 10 的地址,但是 rr1 引用后,可以对 rr1 取地址,也可以修改 rr1。如果不想 rr1 被修改,可以用 const int&& rr1 去引用。注:rr1 和 rr2 都是左值。

在这里插入图片描述

左值可以引用右值吗?右值可以引用左值吗?

int main()
{
	// 左值引用可以引用右值吗? const的左值引用可以
	double x = 1.1, y = 2.2;
	//double& rr1 = x + y;	// 编译报错, x + y是右值, 也是临时对象, 临时对象具有常属性, 需要const保证权限平行
	const double& rr2 = x + y; // 可以

	// 右值引用可以引用左值吗?不可以, 可以引用move以后的左值
	int a = 10;
	//int&& rr3 = a; // 编译报错, 右值引用不能引用左值
	int&& rr4 = 10;  // 可以, 10是右值, 对右值取别名
	int&& rr5 = move(a);  // move(a)的本质是得到一个右值表达式

	return 0;
}

左值引用与右值引用总结:左值引用只能引用左值,不能引用右值。但是 const 左值引用既可引用左值,也可引用右值。右值引用只能引用右值,不能引用左值,但是右值引用可以 move 以后的左值。

其实上面并不是右值引用的使用场景,因为const左值引用既能做到对左值引用又能做到对右值引用,那么增加右值引用不就是多余的吗!!下面我们继续来看看右值引用的使用场景。

1.2 右值引用使用场景和意义

左值引用解决的问题:

做参数:a. 减少拷贝,提高效率。b. 做输出型参数
做返回值:a. 减少拷贝,提高效率(不能返回临时对象的引用)。

从上述中我们可以知道左值引用的短板就是不能返回临时对象的引用,那么对于自定义类型对象返回时,是不是一定会进行一次深拷贝的操作,所以右值引用设计出来就是为了解决此类问题的。

在讲右值引用前,我们先来了解一组概念:右值有两类,第一类是纯右值,即内置类型右值;第二类是将亡值,即自定义类型右值。右值将亡值的资源可以转移到指定的对象。

在这里插入图片描述

就好比日常生活中,有些重症患者已经到了病入膏肓的情况,它可以将自身的一些器官捐赠给有需要的人身上。右值引用+移动语义最重要的是理解资源转移的过程!

下面我们通过一段代码来进行分析:

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}

		const char* c_str() const
		{
			return _str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};

	bit::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		bit::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str; 
	}
}

int main()
{
	curry::string ret1 = curry::to_string(1234);

	return 0;
}

我们先来看看没有C++11之前未新增移动拷贝的情况:

在这里插入图片描述

从上图我们也可以看到编译器优化后只调用了一次深拷贝操作,下面当我们在自定义类型中添加了移动构造后,看看编译器会做什么处理:

在这里插入图片描述

从上图我们可以看到当新增移动拷贝函数之后,编译器识别为右值类型就会优先去调用移动拷贝函数,相较于深拷贝移动拷贝只是将资源进行了转移——得到那块空间的起始地址就能访问那块空间的所有资源!!避免了拷贝的操作,移动拷贝实际并不是真正的拷贝,其实就可以理解为改变了接收资源的对象!!所以右值引用在这种情况下提供了非常大的价值!!如果返回的对象是一个数组、一棵树呢?是不是极大程度上减少了拷贝,提高了效率!!移动语义包括移动构造移动赋值两个成员函数!!

注:右值引用并不是直接作为返回值起作用的,右值引用返回临时对象跟左值引用返回对象的情况是一样的,临时对象都是会被销毁的!!不能直接返回临时对象的左右值引用!!


我们通过监视窗口来继续看看移动构造的具象过程:

int main()
{
	curry::string s1("hello world");
	curry::string ret1 = s1;
	curry::string ret2 = (s1 + '1');
	curry::string ret3 = move(s1); // move操作可以理解为将左值转变为右值返回 -- 是一个表达式

	return 0;
}

在这里插入图片描述

通过上图我们可以发现move操作是有风险的,它能将一个对象的资源转交给另一个对象,转移完之后这个对象就悬空了,我们不能对这个悬空的对象做操作!!当你不想用个资源了之后可以这样做,如果后续你还要用到它就不能这么干了!!

我们来看看下面这种情况:

在这里插入图片描述


C++11后,STL 中的容器都是增加了移动构造和移动赋值的。

在这里插入图片描述

STL 容器的插入接口函数也增加了右值引用版本

在这里插入图片描述

在这里插入图片描述


总结:

  • 右值引用使用的场景:自定义类型临时对象返回或自定义类型对象作为右值进行传参时,此时编译器会优先进行移动拷贝间接减少拷贝,将右值资源直接进行转移!!
  • 左右值引用区别:左值引用是直接减少拷贝,而右值引用是间接减少拷贝,编译器识别出是左值还是右值,如果是右值则不再进行深拷贝,直接进行移动拷贝转移资源提高效率!!

1.3 右值引用引用左值及其一些更深入的使用场景分析

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
// forward _Arg as movable
return ((typename remove_reference<_Ty>::type&&)_Arg);
}
1.3.1 完美转发

模板中的&& 万能引用

// 下面的 Fun都是重载函数, 因为引用也属于类型
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 万能引用:既能引用左值,也能引用右值
// 引用折叠
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值,这是大佬规定的语法。

我们输出一下结果看看是否已经根据类型实现了万能引用:

在这里插入图片描述

我们发现结果并不是我们想象的那样:左值引用就调用左值引用对应的函数,右值引用就调用右值引用的函数??

我们继续进行分析,在之前的例子中我们说过右值是不能取地址的例如10,但是右值引用过后int&& rr1 = 10;这时候你会发现rr1此时成为了一个变量,既然是变量就有空间(地址),所以此时的rr1就充当了左值!!好,我们继续来看一个例子:

在这里插入图片描述

结论:模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发!!


完美转发:保持变量的属性,防止提早转变成左值属性,导致调用的接口函数不一致。

在这里插入图片描述

注:完美转发操作在容器中的各种函数中十分常见,因为C++11提供了右值引用+移动语义,并且在某些场景下我们需要一直转发才能防止变量属性提早转变(退化为左值)!!

我们来看看完美转发的场景:

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};

template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}

	void push_back(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));
	}

	void push_back(const T& x)
	{
		Insert(_head, x);
	}

	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = std::forward<T>(x); // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}

	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = x; // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};

int main()
{
	List<curry::string> lt;
	lt.push_back("1111");
	lt.push_back(curry::string("hello"));

	return 0;
}

在这里插入图片描述

我们来分析一下为什么会是这样的结果:

在这里插入图片描述

我们来看看每个接口函数间不一直进行转发会发生什么情况:

在这里插入图片描述

我们发现当insert函数中的x未进行转发,导致提前退化为左值故调用赋值重载!!所以我们在某些场景下一定要小心将数据一直转发下去!!

二、新的类功能

原来 C++ 类中,有 6 个默认成员函数:构造函数、析构函数、拷贝构造函数,赋值运算符重载、取地址重载和 const 取地址重载。重要的是前 4 个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。拷贝构造函数和赋值运算符重载是针对左值的拷贝,而移动构造和移动赋值时针是右值的拷贝。不需要深拷贝的类,也就不需要自己写移动构造和移动赋值。拷贝对象需要深拷贝时,自己写移动构造和移动赋值。比如:string、vector 和 list 等。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行浅拷贝,对于自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行浅拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用赋值运算符重载。
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。如果没有移动构造和移动赋值,才会去调用拷贝构造和赋值运算符重载。

类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。这个在类和对象就讲了,这里就不再细讲了。

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

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

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

如果能想要限制某些默认函数的生成,在 C++98 中,可以将该函数设置成 private,这样只要其他人想要调用就会报错。在 C++11 中更简单,只需在该函数声明加上 = delete 即可,该语法指示编译器不生成对应函数的默认版本,称 = delete 修饰的函数为删除函数。

继承和多态中的 final 与 override 关键字

final 可以修饰一个类,表示这个类不能被继承;也能修饰一个虚函数,表示这个虚函数不能被重写。override 修饰子类的虚函数,如果子类的虚函数没有完成重写,就会编译报错。

三、可变参数模板

在这里插入图片描述
其实在 C 语言中我们就已经接触过可变参数了,只是我们并没有深入的去研究过,上述printf的参数列表中... 就代表任意多个参数,这里我们就不去研究C语言中的可变参数模板到底是如何提取出参数的了,我们来看下C++是如何来操作的!!

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现
阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了。

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

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

上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为参数包,它里面包含了 0 到 N(N>=0)个模版参数。我们无法直接获取参数包 args 中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用 args[i] 这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

计算可变参数的个数

// 可变参数的模板
template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	string str("hello");
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', str);

	return 0;
}

在这里插入图片描述

通过上图我们能准确的知道每次调用showList有多少参数,但是我们该如何展开其中的参数包获取参数包的值呢?

递归函数方式展开参数包

template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}

template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}

int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));

	return 0;
}

此时传递了两个模板参数,第一个模板参数接收的是传过来的值,第二个接收的是剩余的参数包,打印完value之后再继续递归展开剩余的参数包,直至参数包的个数为0递归结束!!

在这里插入图片描述

逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的,printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。

这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列
表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…
(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在
数组构造的过程展开参数包。

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

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

int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));

	return 0;
}

在这里插入图片描述


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

在这里插入图片描述

我们可以看到C++11在很多容器中都新增了emplace相关的接口函数,并且emplace接口函数中使用了可变模板参数的万能引用,那么相对于push_back()、insert等接口emplace有什么优势的地方吗?我们来看一组例子:

int main()
{
	list<curry::string> list;
	curry::string s1("1111");

	// 插入左值  -- 没区别
	list.push_back(s1);
	list.emplace_back(s1);
	cout << endl;

	// 插入move()
	curry::string s2("22222");
	list.push_back(move(s1));
	list.emplace_back(move(s2));
	cout << endl;

	// 直接插入右值 -- 开始有区别
	//list.push_back("3333");
	//list.emplace_back("3333");

	return 0;
}

在这里插入图片描述

下面我们来看看区别在哪?

在这里插入图片描述

我们可以发现push_back比emplace_back多调用了一次移动构造将右值资源进行了转移,那么为何emplace只会调用一次构造呢?原因是"3333"直接被当做const char*类型的参数包传下去,最终直接去调用const char*的构造函数,而在push_back中"3333"要隐式类型转换为string类型的对象,先去会去调用构造函数,然后它为右值类型会与移动构造进行匹配,所以它比emplace会多调用一次移动构造,但其实理论上俩者的效率都差不大多,因为需要深拷贝的类移动构造并不会消耗太多的时间!!


下面我们来看看浅拷贝类emplace_back与push_back的效率问题:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}

	Date& operator=(const Date& d)
	{
		cout << "Date& operator=(const Date& d))" << endl;
		return *this;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// 没有区别
	list<Date> list;
	Date d1(2024, 5, 18);
	list.push_back(d1);
	list.emplace_back(d1);
	cout << "-----------------" << endl;

	Date d2(2024, 5, 18);
	list.push_back(move(d1));
	list.emplace_back(move(d2));
	cout << "-----------------" << endl;

	// 有区别
	list.push_back(Date(2024, 5, 18));
	list.push_back({ 2024, 5, 18 });
	cout << "-----------------" << endl;

	list.emplace_back(Date(2024, 5, 18));
	list.emplace_back(2024, 5, 18);
	
	return 0;
}

注:由于我们实现的是浅拷贝类,所以移动构造与拷贝构造都是浅拷贝并无其他区别,这里我们就不去实现移动拷贝函数了。
在这里插入图片描述

通过上图我们知道emplace少调用了一次拷贝构造,当这个浅拷贝的类非常大时是不是就减少了大量的拷贝提高了效率,所以从这个角度新增emplace接口还是有一定价值的!!

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

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

相关文章

马蹄集 oj赛(双周赛第二十七次)

目录 栈的min 外卖递送 奇偶序列 sort 五彩斑斓的世界 括号家族 名次并列 栈间 双端队列 合并货物 逆序对 活动分组 栈的min 难度:黄金巴 占用内存:128 M时间限制:1秒 小码哥又被安排任务了&#xff0c;这次他需要要设计一个堆栈&#xff0c;他除了可以满足正常的栈…

Spring Security整合Gitee第三方登录

文章目录 学习链接环境准备1. 搭建基本web应用引入依赖ThirdApp启动类创建index页面application.yml配置访问测试 2. 引入security引入依赖ProjectConfig访问测试 第三方认证简介注册gitee客户端实现1引入依赖application.yml配置文件创建index.html页面启动类InfoControllerPr…

【openlayers系统学习】3.4波段数学计算(计算NDVI)

四、波段数学计算&#xff08;计算NDVI&#xff09; 我们已经看到了如何使用 ol/source/GeoTIFF​ 源代码来渲染真彩色和假彩色合成。我们通过将缩放的反射率值直接渲染到红色、绿色或蓝色显示通道中的一个来实现这一点。还可以对来自GeoTIFF&#xff08;或其他数据瓦片源&…

javaSwing购物系统项目(文档+视频+源码)

摘要 由Java swing实现的一款简单的购物程序&#xff0c;数据库采用的是mysql&#xff0c;该项目非常简单&#xff0c;实现了管理员对商品类型和商品的管理及用户注册登录后浏览商品、加入购物车、购买商品等功能&#xff0c;旨在学习Java 图形界面开发 系统实现 我们先来管理…

20240516-Flyme AIOS 特种兵发布会

目录 1 Flyme AIOS 2 路演功能 2.1 拖拽流转 2.2 任务剧本自定义 2.3 智能体商店 2.4 实况通知 2.5 AI壁纸 3 MYVU 3.1 翻译功能 3.2 AR导航-骑行 3.3 AI语音转文字-科技向善 3.4 Flyme AR-提词器增强 1 Flyme AIOS 1&#xff09;目标&#xff1a;All in AI&#…

Android:OkHttp网络请求框架的使用

目录 一&#xff0c;OkHttp简介 二&#xff0c;OkHttp请求处理流程 三&#xff0c;OkHttp环境配置 四&#xff0c;OkHttp的使用 1.get网络请求 2.post上传表单数据 3.post上传json格式数据 4.文件上传 5.文件下载 一&#xff0c;OkHttp简介 OkHttp是square公司推出的一…

淘宝api接口是什么意思?api接口申请资格是什么?

淘宝其开放性和灵活性为开发者提供了广阔的创新空间。而淘宝API接口&#xff0c;作为连接淘宝平台与外部应用的桥梁&#xff0c;发挥着至关重要的作用。那么&#xff0c;淘宝api接口是什么意思&#xff1f; 一、淘宝API接口是什么意思&#xff1f; 淘宝API接口&#xff0c;全称…

UNI-APP设置屏幕保持常亮-不熄灭屏幕

前言 最近在实际开发过程中&#xff0c;我们会发现在自己使用的app当中会根据系统无操作熄灭屏幕对于一下需要长时间保持屏幕的业务就很不友好&#xff0c;uni-app也是提供了相应方法加上代码之后-注意app端没报错-不生效就是权限问题-需要设置相对应权限-打自定义包 代码实现…

vue+springboot实现echarts数据图统计

①vue项目修改配置 安装依赖&#xff1a; npm i echarts -S 修改路由index.js&#xff1a; import Vue from vue import VueRouter from vue-router import Manager from ../views/Manager.vue // 解决导航栏或者底部导航tabBar中的vue-router在3.0版本以上频繁点击菜单报错…

【编译原理复习笔记】语法分析-补充(二义性与LR错误处理)

二义性文法的 LR 分析 每个二义性文法都不是 LR 的 但是某些二义性文法更加简短&#xff0c;描述更方便 如 I7 和 I8 具有移进归约冲突 使用优先级和结合性解决冲突 对于 I7&#xff0c;由于乘号优先级高于加号&#xff0c;所以当下一个输入符号为乘号时&#xff0c;我们优…

03-02-Vue组件之间的传值

前言 我们接着上一篇文章 03-01-Vue组件的定义和注册 来讲。 下一篇文章 04-Vue&#xff1a;ref获取页面节点–很简单 父组件向子组件传值 我们可以这样理解&#xff1a;Vue实例就是一个父组件&#xff0c;而我们自定义的组件&#xff08;包括全局组件、私有组件&#xff09;…

Java基础入门day49

day49 tomcat 启动 进入tomcat的bin目录&#xff0c;双击或者运行startup.bat文件启动tomcat 控制台最后出现服务器启动在多少毫米之内&#xff0c;代表服务器成功启动 org.apache.catalina.startup.Catalina.start Server startup in 405 ms 验证tomcat 在浏览器中输入 loca…

linux---进程通信

提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、匿名管道 进程之间的通信目的一般是来控制另一个进程。也可以用来实现数据的交流&#xff0c;还有资源共享等。 匿名管道原理&#xff1a; &#xff08;铺垫&#xff09;进程之间是具有独立性&…

超市进销存|基于SprinBoot+vue的超市进销存系统(源码+数据库+文档)

超市进销存系统 目录 基于SprinBootvue的超市进销存系统 一、前言 二、系统设计 三、系统功能设计 1 登录注册 2 管理员功能模块 3员工功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#x…

【时间复杂度和空间复杂度之间的故事】

【时间复杂度和空间复杂度之间的故事】 一.前言 二.时间复杂度定义时间复杂度的计算规则习题 三.空间复杂度定义计算方法习题空间复杂度 O(1)空间复杂度 O(n) 本文主要讲解关于时间复杂度与空间复杂度 &#x1f600;&#x1f603;&#x1f601;&#x1f601;&#x1f607;&…

桂林电子科技大学计算机工程学院、广西北部湾大学计信学院莅临泰迪智能科技参观交流

5月18日&#xff0c;桂林电子科技大学计算机工程学院副院长刘利民、副书记杨美娜、毕业班辅导员黄秀娟、广西北部湾大学计信学院院长助理刘秀平莅临广东泰迪智能科技股份有限公司产教融合实训基地参观交流。泰迪智能科技副总经理施兴、广西分公司郑廷和、梁霜、培训业务部孙学镂…

Outlook 开启smtp配置

微软 Outlook 邮箱各种服务详细信息 服务类型服务器地址端口加密方法POPoutlook.office365.com995TLSIMAPoutlook.office365.com993TLSSMTPsmtp.office365.com587STARTTLS 然而仅仅有以上信息还不够&#xff0c;需要获取服务密码 (授权码) 才能够使用 POP, IMAP, SMTP 这三种…

常见应用流量特征分析

目录 1.sqlmap 1.常规GET请求 2.通过--os-shell写入shell 3.post请求 2.蚁剑 编码加密后 3.冰蝎 冰蝎_v4.1 冰蝎3.2.1 4.菜刀 5.哥斯拉 1.sqlmap 1.常规GET请求 使用的是sqli-labs的less7 &#xff08;1&#xff09;User-Agent由很明显的sqlmap的标志&#xff0c;展…

基础常用动词,柯桥西班牙语培训

1. Ser&#xff1a;是 表示身份: Soy Ana. Soy estudiante. 我是安娜。我是一名学生。 表示属性: Es duro. 这是硬的。 表示国籍: Soy espaol, de Madrid. 我是西班牙人&#xff0c;来自马德里。 2. Estar: 是..., 在... 表示身体状况: Estoy muy cansada, necesito dormir.我很…