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

news2024/11/15 16:46:36

C++11 ——— 右值引用和移动语义

  • 右值引用和移动语义
    • 左值引用和右值引用
    • 左值引用与右值引用比较
    • 右值引用使用场景和意义
      • 左值引用的使用场景:
      • 左值引用的短板:
      • 左值引用中编译器的优化
      • 右值引用和移动语义
      • 右值引用引用左值
      • 右值引用的其他使用场景
  • 完美转发
    • 万能引用
  • 完美转发
    • 模板中的&& 万能引用
    • 完美转发的使用场景
    • 引用折叠

右值引用和移动语义

左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

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

左值(Lvalue)
 左值是指可以取地址的表达式,通常是具名变量或对象。左值在赋值表达式中出现在等号的左边,表示一个持久的对象。左值可以通过取地址符(&) 获取其内存地址。

特征

  • 可以取地址。
  • 具有持久的生命周期,直到其作用域结束。
  • 例如:变量名、数组元素、解引用的指针等。

左值引用就是给左值的引用,给左值取别名。

左值引用(Lvalue Reference)
 左值引用是对左值的引用,使用符号 & 声明。左值引用可以绑定到左值,允许通过引用来访问和修改原始对象。

特征

  • 只能绑定到左值。
  • 可以通过左值引用修改原始对象的值。
  • 例如:int a = 10; int& ref = a;,ref是a的左值引用。

示例:

int main()
{
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);   // p是指向动态分配的int对象的指针,是左值
    int b = 1;            // b是int变量,是左值
    const int c = 2;      // c是const int变量,是左值
    
    // 以下几个是对上面左值的左值引用
    int*& rp = p;         // rp是对p的左值引用,是左值
    int& rb = b;          // rb是对b的左值引用,是左值 
    const int& rc = c;    // rc是对c的const左值引用,是左值
    int& pvalue = *p;     // pvalue是对*p的左值引用,是左值

    // *p是对动态分配的int对象的解引用,是左值
    // new int(0)是动态分配int对象的右值表达式
    // 1和2是int字面值,是右值

    return 0;
}

右值(Rvalue)
 右值是指不能取地址的表达式,通常是临时对象,字面常量,表达式返回值,函数返回值。右值在赋值表达式中出现在等号的右边,表示不持久的值。C++11将右值细分为纯右值(prvalue)和将亡值(xvalue)。

右值的分类:

  • 纯右值(prvalue):
    表示临时对象不与任何对象关联的值
    例如:字面量(如1、true)、函数返回的非引用值(如int func() { return 42; })、表达式结果(如a + b)。
  • 将亡值(xvalue):
    表示即将被移动的对象,通常是一个临时对象的引用
    例如:通过std::move转换的对象、返回右值引用的函数。
#include <cmath> // 引入cmath库以使用fmin函数

int main()
{
    double x = 1.1, y = 2.2;

    // 以下几个都是常见的右值
    10;                  // 10是一个字面值,属于右值
    x + y;              // x + y是一个表达式,其结果是一个右值
    fmin(x, y);         // fmin(x, y)的返回值是一个右值,表示x和y的最小值

    // 以下几个都是对右值的右值引用
    int&& rr1 = 10;     // rr1是一个右值引用,绑定到右值10
    double&& rr2 = x + y; // rr2是一个右值引用,绑定到表达式x + y的结果
    double&& rr3 = fmin(x, y); // rr3是一个右值引用,绑定到fmin(x, y)的结果

    // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    10 = 1;             // 10是右值,不能作为赋值的左操作数
    x + y = 1;         // x + y是右值,不能作为赋值的左操作数
    fmin(x, y) = 1;    // fmin(x, y)是右值,不能作为赋值的左操作数

    return 0;
}

需要注意的是,右值不能直接取地址。然而,当右值被绑定到一个右值引用时,它会被存储在特定的内存位置,这样就可以通过该引用获取这个位置的地址。

例如,虽然无法直接获取字面量10的地址,但在将其绑定到右值引用rr1后,可以获取rr1的地址,并且可以修改rr1的值。
如果不希望rr1的值被修改,可以使用const int&& rr1来引用,这样rr1将成为一个常量右值引用。

int main()
{
    double x = 1.1, y = 2.2; // 定义两个double类型的变量x和y

    int&& rr1 = 10;          // rr1是一个右值引用,绑定到右值10
    const double&& rr2 = x + y; // rr2是一个常量右值引用,绑定到表达式x + y的结果

    rr1 = 20;                // 将rr1的值修改为20,合法,因为rr1是非常量的右值引用

    rr2 = 5.5;               // 这里会报错:不能修改常量右值引用
    // 解释:rr2是一个const double&&,这意味着它绑定的值不能被修改。尝试给rr2赋值会导致编译错误。

    return 0; // 程序正常结束
}

左值引用与右值引用比较

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。
#include <iostream>
using namespace std;

int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10; // 定义一个整型变量a,并初始化为10

    // 创建一个左值引用ra1,引用变量a
    int& ra1 = a; // ra1是a的别名,可以通过ra1访问和修改a的值
    cout << "ra1: " << ra1 << endl; // 输出ra1的值,即10

    // int& ra2 = 10; // 编译失败,因为10是右值,左值引用不能绑定到右值

    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10; // ra3是一个常量左值引用,引用右值10
    const int& ra4 = a;  // ra4是一个常量左值引用,引用左值a

    // 输出ra3和ra4的值
    cout << "ra3: " << ra3 << endl; // 输出ra3的值,即10
    cout << "ra4: " << ra4 << endl; // 输出ra4的值,即10

    return 0; // 程序正常结束
}

右值引用总结:

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以move以后的左值。
int main()
{
    // 右值引用只能绑定到右值,不能绑定到左值。
    int&& r1 = 10; // r1是一个右值引用,绑定到字面量10(右值)
    // 这行代码是合法的,因为10是右值。

    // 以下代码尝试将左值绑定到右值引用
    int a = 10;    // a是一个左值,初始化为10
    // int&& r2 = a; // 这行代码会导致编译错误
    // error C2440: “初始化”: 无法从“int”转换为“int &&”
    // message : 无法将左值绑定到右值引用

    // 右值引用可以引用move以后的左值
    int&& r3 = std::move(a); // std::move将左值a转换为右值
    // r3现在是一个右值引用,绑定到a的右值版本

    return 0; // 程序正常结束
}

右值引用使用场景和意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

右值引用肯定是要在某些场景比左值引用更加高效,所以才会引入右值引用的概念。
下面自己手动实现一个深拷贝的类,string类,观察其中的调用逻辑 :



namespace qq
{
	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[_size + 1];
			strcpy(_str, str);
			cout << "string(const char* str = "") -- 构造函数" << endl;
		}
		// 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;

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		// 赋值重载
		//左值
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);

			delete[] _str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;

			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;
		}
		const char* c_str()const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};


	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		qq::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;
	}
}

左值引用的使用场景:

做参数和做返回值都可以提高效率:

  • 左值引用做参数,防止传参的时候进行拷贝操作。
  • 左值引用做返回值时,防止返回临时对象的时候进行拷贝构造。

void func1(qq::string s)
{}
void func2(const qq::string& s)
{}
int main()
{
	qq::string s1("hello world");
	cout << "------" << endl;

	// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
	func1(s1);
	cout << "------" << endl;
	func2(s1);
	// string operator+=(char ch) 传值返回存在深拷贝
	// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
	s1 += '!';
	return 0;
}

在这里插入图片描述

  • 在上面的代码中,首先fun1fun2 分别接受string类型的参数,前者通过值传递,后者通过常量左值引用来传递。
  • 首先在构造对象s1的时候首先调用构造函数进行构造。
  • 其次在调用func(s1)的时候,会发生深拷贝,因为传入的是s1的一个副本,这就会发生深拷贝,因为传递的是一个副本。
  • 而在调用func(2)的时候,由于使用了常量左值引用,避免了拷贝,提高了效率。

左值引用的短板:

但是当函数的返回对象是一个局部变量的时候,当出了函数的作用域,该变量就被销毁了,因为其声明周期只限于函数作用域。所以此时就不能使用左值返回了,不得不只能继续使用传值返回,但是传值返回至少都有一次拷贝构造,这就造成了效率的低下。

在这里插入图片描述

左值引用中编译器的优化

在这里插入图片描述

  • 对于上面的str,其自身本是一个左值,传值返回,所以在销毁前会先创建一个临时对象,再用这个临时对象来赋值给s1
  • 所以理论上,在声明str的时候会调用一次构造函数
  • 其次在返回和赋值的时候会调用两次拷贝构造

在这里插入图片描述
但是实际上就只有一次拷贝构造,因为编译器会把连续的拷贝构造合为一个拷贝构造!
在这里插入图片描述

右值引用和移动语义

qq::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占为已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

		//移动构造
		//右值
		string(string&& s)
			:_str(nullptr),_size(0),_capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}

我们添加了移动构造之后,继续执行上面的代码可以发现,此时直接调用了移动构造。
在这里插入图片描述

  • 这里to_string的返回值是一个临时对象,然而此时这个临时对象不才会去调用const 修饰的左值引用,而是直接调用了右值引用,此时直接窃取这个将亡值。在这里插入图片描述
    在这里插入图片描述

并且这里编译器也对其做了相对于的优化,直接一步移动构造到位!
在这里插入图片描述

不仅仅有移动构造,还有移动赋值:

在qq::string类中增加移动赋值函数,再去调用qq::to_string(123),不过这次是将
qq::to_string(123)返回的右值对象赋值给ret1对象,这时调用的是移动构造。

//移动赋值
//右值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动赋值" << endl;
	swap(s);
	
	return *this;
}

在这里插入图片描述

  • 在这里,首先构造s1调用一次构造函数
  • 其次在to_string函数的内部再次调用构造函数来创建str
  • 在str返回的时候会调用移动构造创建出一个临时对象,最后再通过移动赋值向s1赋值。
  • 这里的移动构造和移动赋值只是完成了资源的交换,并没有拷贝,所以整体的效率得到了提高。

在这里插入图片描述

右值引用引用左值

根据 C++ 的语法规则,右值引用只能引用右值。那么,右值引用是否绝对不能引用左值呢?


实际上,在某些场景下,我们可能确实需要使用右值引用来引用左值,以实现移动语义。当我们需要将一个左值转化为右值引用时,可以使用 std::move 函数。 在 C++11 中,std::move() 函数位于 utility头文件中。
这个函数的名称可能会引起误解,因为它并不实际“搬移”任何东西。它的唯一功能是将一个左值强制转换为右值引用,从而实现移动语义。

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

下面的代码实现了move对左值的强转:

int main()
{
	qq::string s1("hello world");
	// 这里s1是左值,调用的是拷贝构造
	qq::string s2(s1);
	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
	// 资源被转移给了s3,s1被置空了。
	qq::string s3(std::move(s1));
	return 0;
}

在这里插入图片描述

右值引用的其他使用场景

C++11标准出来之后,STL中的容器除了增加移动构造和移动赋值之外,STL容器插入接口函数也增加了右值引用版本。

以list容器的push_back接口为例:
在这里插入图片描述
在这里插入图片描述
当使用std的list时,因为其已经实现了右值引用,所以当我们传入需要深拷贝的自定义string时,其会调用string的构造函数,左值的就调用左值引用版本的,右值就调用右值引用版本的。

  • 第一个lt.push_back(qq::string("11111"))中,传入的是一个右值,故其直接调用移动构造即可完成插入。
  • 第二个qq::string s1("12345") lt.push_back(s1); 其中s1是一个左值,所以调用左值引用版本的push_back,故这是一次深拷贝
  • 第三个lt.push_back(move(s1));对于s1进行了move操作,强制其调用右值引用版本,即移动构造

完美转发

万能引用

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。比如

template<class T>
void PerfectForward(T&& t)
{
	//...
}

但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,具体见下面的示例:

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

我们一共实现了四种Fun函数,分别是左值引用,右值引用,const 左值引用 和 const 右值引用。
然后通过传入不同的左值右值以分别调用其函数,比如,传入右值10,就应该调用右值引用函数,但是实际上的结果是:
在这里插入图片描述
这也就应证了之前说的:右值引用属性本身是个左值、,也就是说右值引用后续使用中都退化成了左值

所以这里就需要用到完美转发了!

完美转发

模板中的&& 万能引用

要想在参数传递过程中保持其原有的属性,需要在传参时调用forward函数。比如:

template<class T>
void PerfectForward(T&& t)
{
	Func(std::forward<T>(t));
}

在使用完完美转发后,当PerfectForward函数传入的是右值时,就不会退化为左值,而是匹配到右值引用的Func函数中,传入左值时,类似。
在这里插入图片描述

完美转发的使用场景

下面实现了一个简化版本的list:


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 PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));
	}
	void PushFront(T&& x)
	{
		//Insert(_head->_next, x);
		Insert(_head->_next, std::forward<T>(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<qq::string> lt;
	lt.PushBack("1111");
	lt.PushFront("2222");
	return 0;
}

在这里插入图片描述

代码执行过程

main 函数中,调用 lt.PushBack("1111");lt.PushFront("2222"); 时,字符串字面量 "1111""2222" 被传递给 PushBackPushFront

第一次调用 PushBack("1111")

  1. 构造 std::string 对象

    • 字符串字面量 "1111" 被隐式转换为 std::string 对象,调用构造函数 string(const char* str)
    • 输出string(const char* str = ) -- 构造函数
  2. 调用 Insert 方法

    • Insert 中,std::forward<T>(x)x 作为右值传递给 newnode->_data
    • 由于 newnode->_datastd::string 类型,右值绑定到 newnode->_data 时,会调用移动构造函数(如果存在)。
    • 输出string(const char* str = ) -- 构造函数(用于创建 newnode_data
  3. 移动赋值

    • Insert 方法中,newnode->_data 的赋值可能会触发移动赋值操作(如果 std::string 的实现中有这样的逻辑)。
    • 输出string& operator=(string&& s) -- 移动赋值

第二次调用 PushFront("2222")

  1. 构造 std::string 对象

    • 字符串字面量 "2222" 被隐式转换为 std::string 对象,调用构造函数 string(const char* str)
    • 输出string(const char* str = ) -- 构造函数
  2. 调用 Insert 方法

    • 同样,std::forward<T>(x)x 作为右值传递给 newnode->_data
    • 由于 newnode->_datastd::string 类型,右值绑定到 newnode->_data 时,会调用移动构造函数(如果存在)。
    • 输出string(const char* str = ) -- 构造函数(用于创建 newnode_data
  3. 移动赋值

    • 移动赋值操作可能再次被触发。
    • 输出string& operator=(string&& s) -- 移动赋值

引用折叠

这里需要介绍一下引用折叠:
引用折叠是 C++ 中的一种规则,涉及到如何处理引用的组合。具体来说,当我们在模板中使用引用时,可能会出现引用的引用。`C++ 规定了引用的折叠规则:

左值引用 + 左值引用: 结果为左值引用(T& & 折叠为 T&)。
左值引用 + 右值引用: 结果为左值引用(T& && 折叠为 T&)。
右值引用 + 左值引用: 结果为左值引用(T&& & 折叠为 T&)。
右值引用 + 右值引用: 结果为右值引用(T&& && 折叠为 T&&)。

完美转发的机制是通过结合万能引用、引用折叠和 std::forward 来实现的。std::forward 的工作原理依赖于引用折叠的规则,以确保在转发参数时能够正确处理左值和右值。

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

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

相关文章

苹果系统安装的QT程序无法显示窗口问题的解决办法

1、问题的提出 苹果系统是可以安装QT&#xff08;包含QT带来的集成开发环境QTCreator&#xff09;的。但是&#xff0c;QT安装完毕跑运行测试程序的时候&#xff0c;却会发现窗口无法显示。 右下角的应用窗口程序无法打开。 2、问题的解决 其实原因就在警告之中&#xff1a; …

为单身人士定制的幸福计划:相亲交友系统

在忙碌的都市生活中&#xff0c;单身人士往往因为工作繁忙、社交圈有限等原因&#xff0c;难以遇到合适的伴侣。相亲交友系统&#xff0c;作为现代科技与人际关系结合的产物&#xff0c;为单身人士提供了一个全新的解决方案。本文将从客户角度出发&#xff0c;探讨相亲交友系统…

【计算机网络】UDP TCP介绍

UDP & TCP介绍 UDP报文格式报文内容介绍端口号报文长度校验和载荷 TCP报文格式初步了解TCP机制确认应答超时重传连接管理滑动窗口流量控制拥塞控制紧急传输数据推送延时应答捎带应答面向字节流异常处理心跳机制 UDP 和 TCP 的区别 UDP 报文格式 对于网络协议, 本质上就是…

软件工程技术专业软件开发综合实训室解决方案

一、行业背景与前景分析 1.1 软件工程技术专业就业前景 近年来&#xff0c;中国的软件行业取得了显著的成就&#xff0c;即便在全球经济受到新冠疫情冲击的情况下&#xff0c;仍保持了强劲的增长势头。据工业和信息化部发布的数据&#xff0c;2021年我国软件和信息技术服务业…

mysqldump 迁移至MySQL到Oceanbase-CE V4 和旁路导入语法的学习

闲着没事干&#xff0c;测试着玩。 一.数据库环境 mysql&#xff1a; oceanbase&#xff1a; mysql下的表&#xff1a; 二、mysqldump迁移数据到OceanBase # 通过mysqldump导出数据 mysqldump -h 192.168.80.16 -uroot -P3306 -p --databases test > toob.sql #传输脚本到o…

DDR3 SDRAM操作流程

天空灰暗到一定程度&#xff0c;星晨就会熠熠生辉。 ----一起加油 DDR3 SDRAM的相关操作主要包括上电(Power on)、复位(Reset procedure)、初始化(Initialization)、ZQ对齐(ZQ calibration)、模式寄存器配置(MRS)、自刷新(Selfrefresh)、刷新(Refreshing)、激活(Activating)、读…

线上VR虚拟展厅里可以展示3D模型么?

虚拟展厅里可以展示3D模型。 虚拟展厅利用数字技术和三维建模技术创建一个虚拟的展览环境&#xff0c;使得参观者可以通过计算机、智能手机、平板电脑等设备远程参观展览。在这个过程中&#xff0c;3D模型作为虚拟展厅的重要组成部分&#xff0c;扮演着至关重要的角色。 在虚…

Google AI 概述——喜欢的三点和不喜欢的两点

如果你在谷歌上搜索某些内容&#xff0c;你可能注意到谷歌AI概览已经回归。不过&#xff0c;对此功能的实用性我仍持观望态度。 对于那些还没有使用过的人来说&#xff0c;谷歌AI概览基本上是从各个网站收集信息并将其整合在搜索结果页面的顶部。理论上&#xff0c;这应该使用…

opencv将灰度图转为彩色图片

文章目录 背景灰度图优势opencv读取灰度图彩色转灰度算法需求 方法测试代码 背景 在图像处理中通常需要将图片转为灰度图 灰度图&#xff0c;也称为灰度图像或黑白图像&#xff0c;是一种只包含亮度信息而不包含颜色信息的图像。在灰度图中&#xff0c;每个像素的亮度级别通常…

【STL中容器汇总】map、list、vector等详解

容器学习分享 1、STL简介1.1、STL六大组件 2、vector容器2.1、vector 基本操作2.2、vector容器示例2.3、vector容器存放自定义数据类型示例2.3、vector嵌套vector示例 3、list 容器3.1使用示例3.2、list容器基本函数 4、map容器4.1、map函数原型4.2、map函数示例 1、STL简介 ST…

pdf文件怎么编辑?7大常用的pdf在线编辑技巧,免费好用!

编辑pdf文件通常涉及对文本、图片及其他内容的添加、删除或修改。无论您希望清除过时的信息&#xff0c;还是想为pdf文件增添一些补充材料&#xff0c;掌握几种简单有效的编辑方法都是非常重要的。因此&#xff0c;本文将为大家介绍7大常用的pdf在线编辑技巧&#xff0c;详细解…

Java学习Day40:大战亢金龙!(spring框架之AOP)

AOP&#xff08;面向切面变成&#xff09;&#xff1a;不改变原有代码的情况下&#xff0c;对代码进行功能添加 1.一些概念 抽取出的方法&#xff1a;通知 原始方法&#xff1a;成为连接点&#xff08;可以是程序执行中的任意位置&#xff09;&#xff0c;对应原始的一个个方…

Qt篇——Qt在msvc编译下提示“C2001:常量中有换行符“的错误

在pro文件中添加以下配置即可&#xff1a; msvc{QMAKE_CFLAGS /utf-8QMAKE_CXXFLAGS /utf-8 }

哈希表、算法

哈希表 hash&#xff1a; 在编程和数据结构中&#xff0c;"hash" 通常指的是哈希函数&#xff0c;它是一种算法&#xff0c;用于将数据&#xff08;通常是字符 串&#xff09;映射到一个固定大小的数字&#xff08;哈希值&#xff09;。哈希函数在哈希表中尤为重要…

什么是浏览器指纹及其在线跟踪的用途?

互联网常常给人一种广阔的蓝色天空的感觉&#xff0c;在那里你可以自由航行&#xff0c;只有匿名才能提供这种自由。然而&#xff0c;事实并非如此。有一套工具被希望识别谁在访问其网站的企业广泛使用。这套工具被称为浏览器指纹识别&#xff0c;了解它的使用方式很重要。 一…

剑指offer JZ23 链表中环的入口结点

问题描述&#xff1a; 给定一个长度为n的链表&#xff0c;首先判断其是否有环&#xff0c;然后找到环的入口。 要求&#xff1a;空间复杂度 O(1)&#xff0c;时间复杂度 O(n)。 思路&#xff1a; 1. 投机一点的做法 从头遍历链表&#xff0c;如果有环&#xff0c;那么有些节…

Docker初识(Docker技术集群与应用)

一、基础设施即服务 IaaS&#xff08;Infrastructure as a Service&#xff09; eg&#xff1a;购买的云服务器&#xff0c;就是IaaS 提供给客户的服务是对所有设施的利用&#xff0c;包括处理、存储、网络和其他基本的计算资源。客户能够部署和运行任意软件&#xff0c;包括…

LLM大模型学习:探索LLM的精髓-理解Prompts概念与LangChain快速应用技巧”

LLM 中什么是Prompts&#xff1f;如何使用LangChain 快速实现Prompts 一 Prompt是一种基于自然语言处理的交互方式&#xff0c;它通过机器对自然语言的解析&#xff0c;实现用户与机器之间的沟通。 Prompt主要实现方式是通过建立相应的语料库和语义解析模型&#xff0c;来将自…

《A Few Useful Things to Know about Machine Learning》论文导读

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl机器学习作为人工智能领域的重要分支,近年来得到了广泛的关注和应用。Pedro Domingos的经典论文《A Few Useful Things to Know about Machine Learning》为我们提供了对机器学习深入且全面的理解…

Java方法的定义,即“函数“的定义!

方法的作用 提高代码的复用性&#xff0c;写一次&#xff0c;你需要的时候直接去调用即可。 定义一个函数方法 [修饰符1 修饰符2 ...] 返回值类型 方法名(形参){Java语句&#xff1b;... ... ... }初次接触方法的举例&#xff1a;两个整数的求和方法 根据上面的例子我们来分…