初识C++之左值引用与右值引用

news2024/9/23 17:18:09

目录

一、左值引用与右值引用

1. 左值和右值的概念

1.1 左值

1.2 右值

 1.3 左值与右值的区分

2. 左值引用与右值引用

2.1 左值引用与右值引用的使用方法

2.2 左值引用的可引用范围

2.3 右值引用的可引用范围

3. 右值引用的作用

3.1 减少传值返回的拷贝

3.2 插入时的右值引用

4. 右值引用可以被修改

5. 右值引用后的值被视为左值

6. 完美引用

7. 完美转发

二、新的类功能

1. C++11中新增的默认成员函数

2. default关键字

3. delete关键字


一、左值引用与右值引用

1. 左值和右值的概念

1.1 左值

在以前,大家都应该学过引用,在C++中,引用还是比较常用的,因为它是给变量取别名,可以减少一层拷贝。但是,在C++11增加了右值引用后,我们以前所使用的引用都应该叫做“左值引用”

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址,也可以对它赋值。左值一般出现在符号的左边,但也可能出现做符号的右边。const修饰的左值,不能给他赋值,但是可以取它的地址。而左值引用,其实就是给左值取别名。

在以前,大家可能听说过“在赋值符号左边的就是左值”,或者“不能修改的就是左值”。这两种说法其实都是错误的。例如将一个变量的赋值给另一个变量时,该表达式的左右两个值都是左值;const修饰的左值,虽然也是左值,但是它却不允许修改

如下图中的所有变量,其实都是左值。其中的“int* p1 = &d;”中的p1和d其实都是左值。

1.2 右值

右值也是一个数据的表达式。如字面常量表达式返回值函数返回值(不能是左值引用返回)等待。右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边右值不能取地址。右值引用是对右值的引用,给右值取别名。

当然,不能简单的将右值理解为“在赋值符号右边的值”或“不能修改的值就是右值”。这两种理解都是错误的。例如在上面的左值的图中,就有在赋值符号右边,但是是左值的值;也有被const修饰无法被修改,但是是左值的值。

如下图中的都是一些常见的右值:

 1.3 左值与右值的区分

要区分左值与右值,只需要记住一个标准:“左值可以取地址,右值不能被取地址”。这就是左值和右值的本质区别。

因为左值一般是被放在地址空间中的,有明确的存储地址;而右值一般可能是计算中产生的中间值,也可能是被保存在寄存器上的一些值,总的来讲就是,右值并没有被保存在地址空间中,也就无法取地址。

2. 左值引用与右值引用

2.1 左值引用与右值引用的使用方法

左值引用的方法很简单,就是大家以前所学习的引用,即在变量类型的右边加上一个“&”即可:

右值引用的使用也很简单,在变量类型的右边加上两个“&&”即可:

2.2 左值引用的可引用范围

一般来讲,左值引用只能引用左值。但是,经过const修饰的左值引用,既可以引用左值,也可以引用右值

const左值引用可以引用右值的原因很简单。右值都是不能被修改的值,普通的左值引用是可以修改的,这就会导致权限的放大,因此会报错;而const左值引用是不能修改的,此时是权限平移,也就可以引用右值了。

2.3 右值引用的可引用范围

一般来讲,右值引用只能引用右值,不能引用左值。但是存在一种特殊情况,那就是右值引用可以引用move以后的左值

3. 右值引用的作用

左值引用的作用我们都很清楚了,一般都是用于函数的返回值和参数等。在这些场景中,可以提高效率和减少拷贝。大家都很清楚,这里就不再赘述了。

既然有了左值引用,那右值引用的作用是什么呢?其实,右值引用的作用之一,就是补齐左值引用的缺陷。

3.1 减少传值返回的拷贝

要了解右值引用的这个作用,我们就要先看看左值引用原来存在的问题。

一般来讲,左值引用就是用于当返回值或做函数参数,例如在下面的函数中:

在上图中,有一个函数模板,它的参数和返回值都是const T&,从这里看,并没有什么问题,使用const T&做参数,也解决了右值不能被左值引用的问题。

但是,在上图中返回的x在func()函数结束后并不会被销毁。如果,我们想返回的值是func()函数的作用域内,出了作用域就会被销毁的局部变量,此时该变量就不能用左值引用的方式返回。如下图:

如果运行上面的程序,就会出现报错。因为func()函数返回的变量出了作用域就会被销毁,如果用左值引用,就会出现非法寻址。在这种情况下,如果只有左值引用,就不能使用传引用返回,而必须使用传值返回。在上面的图中返回的值仅仅是一个变量,如果在未来,这个返回值是一个需要深拷贝二维数组或者其他包含了大量数据的数据结构,如set或map。此时使用传值返回就需要进行大量的拷贝,代价很高。

第一个解决方案就是,我们可以通过new一个对象来解决这个问题。但是new出来的对象是需要自己释放的,如果不释放就会导致内存泄漏。因此,这样写虽然可以解决问题, 但是却有内存泄漏的隐患,毕竟new的对象太多时,我们不一定能记得所有需要释放的对象。

而第二个解决方案就是, 我们可以给该函数多加一个输出型参数来接收:

虽然加输出型参数不失为一个解决方案,但是使用起来却并不方便。

那如果我们既不想new对象,也不想传输出型参数狙解决拷贝的问题呢?这里其实就可以用“右值引用”。当然,要解决这个问题,并不是单单将返回值修改为右值引用即可:

原因有两个。一个是此处返回的ret是一个左值,它是无法被右值引用的;而最重要的原因在于,此处的ret是一个局部变量,出了作用域就会销毁。左值引用不起效是因为它无法解决局部变量出了作用域会被销毁的问题。右值引用是引用右值的别名,同样无法解决这一问题。

为了更好的解释右值引用的作用,先准备以下一个类:

#pragma once
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
#include <assert.h>

using namespace std;

namespace mystring
{
	class string
	{
	public:
		typedef char* iterator;
		//构造与析构相关函数

//构造与析构相关函数
		string(const char* str = "")//字符串及无参构造
		{
			_size = strlen(str);
			_capacity = _size;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}


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

		string& operator=(const string& s)//“=”重载
		{
			cout << "拷贝赋值" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		~string()//析构函数
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		iterator begin()//iterator的begin()迭代
		{
			return _str;
		}

		iterator end()//iterator的end()迭代
		{
			return _str + _size;
		}


		string& operator+=(char ch)//运算符+=重载
		{
			push_back(ch);

			return *this;
		}

		void swap(string& s)//类的字符串交换
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		void reserve(size_t capacity)//空间扩容
		{
			char* str = new char[capacity + 1];
			strcpy(str, _str);                 

			delete[] _str;
			_str = str;
			_capacity = capacity;
		}

		char& operator[](size_t pos)//[]运算符重载
		{
			assert(pos < _size);

			return _str[pos];
		}

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

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

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

这个string类里面仅保留的构造,拷贝构造和赋值,并且在调用拷贝构造和赋值时会打印一条语句,便于接下来的试验。

在这个类的基础上,再搭配一个to_string()函数:

 有了上面的代码后,我们再写出以下代码:

用我们自己写的一个类进行构造。to_string()函数在返回时,一般来讲会需要进行两次拷贝。因为值返回的时候并不是直接返回,而是会生成一个临时变量,将要返回的值拷贝给临时变量,然后再将临时变量的值拷贝给接收对象。

但是,这种方式返回时,就会平白无故多一次拷贝,因此编译器会对这一行为进行优化,不再生成临时拷贝,而是直接将返回值拷贝给接收对象。

当然,编译器并不会对所以返回值都进行优化。因为这种优化一般是将一个栈帧中的值拷贝给另一个栈帧中的值。如果这个值不在栈帧中,例如这个值是一个全局变量,那么编译器就无法优化。 当然,还有一些情况不能优化,例如下图:

总的来讲,如果编译器无法优化,就会生成一份临时拷贝。

但是,这里依然没有完全解决问题,因为哪怕进行了优化,依然需要进行一次拷贝构造,如果返回的对象有大量数据,依然会造成效率损失。

因此,就有了另一种处理方案,就是“右值引用”。例如,在类中提供一个右值引用为参数的拷贝构造。由于编译器的匹配机制,就会将一些传右值进行构造的对象,调用右值引用的拷贝构造。右值拷贝又分为两种:

(1)普通的右值,被称为纯右值

(2)即将被销毁的右值,被称为将亡值

在右值引用中,如果某些右值是将亡值,即在完成交换后就会被销毁的值,此时就可以直接将双方的值进行交换。

在这里,因为是直接交换两个对象中的数据,所以不会进行拷贝,只会交换指针。通过这种方式,就可以在数据量非常庞大的情况下提高效率。

但是,这种拷贝不要随意使用。因为它是将两个对象的值进行交换,如果使用不当,就可能导致变量的值被修改。如下图:

在这个程序里面,构造了s1,和s2。s2在构造的时候,将s1转化为了右值,此时它就会去调移动拷贝构造。但是,移动拷贝构造中是交换双方的值。这就会导致s1的数据被修改:

因此,这种使用右值引用移动构造的方法,只能适用于将亡值。因为将亡值马上就要被销毁,哪怕交换数据后也不会造成什么影响。

既然拷贝构造可以采用移动构造,那么拷贝赋值也是可以采用移动赋值的:

在编译器中,在对返回值进行优化时,虽然返回值是左值,但是编译器会将返回值识别为右值,然后采用交换数据的方式来避免拷贝。

由此,左值引用和右值引用虽然都可以减少拷贝,但是它们的原理并不一样。

左值引用是别名,直接起作用。而右值引用是间接起作用,实现移动拷贝和移动赋值,在拷贝的场景中,如果是右值(将亡值),就转移资源。

3.2 插入时的右值引用

在插入时,也是可能需要进行拷贝构造的。例如如下程序:

在这里,用的是我们自己写的string,所以在插入中进行拷贝构造时,会调用我们自己写的拷贝构造。在这里,提前将移动拷贝屏蔽,运行该程序:

在只有拷贝构造的情况下,此时无论传的是左值还是右值,都是调用拷贝构造进行深拷贝。

但是,如果将移动拷贝放出来,再次运行该程序:

同样的程序,此时其中传入右值插入的就会调用移动拷贝,在处理大量数据的情况下,就会有很高的提升。

如果没有移动拷贝,在插入时就会为传入的字符串单独开辟一块空间,然后再将这片空间中的内容拷贝到指定的位置。但是有了移动构造后,传入的右值就是直接将其与指定位置的数据进行交换。因为传入的右值在被使用时都被视作临时变量,使用完就销毁。在这种情况下,就可以使用移动拷贝提高效率。

因此,在很多容器的插入接口中,也都提供了右值引用的插入版本:

4. 右值引用可以被修改

大家知道,右值是不能被修改的。但是,右值经过右值引用后,其实就可以被修改

原因是右值不能被修改,是因为右值原本在地址空间上是没有地址的。但是在被右值引用后,右值会被存储在栈上,这也就导致右值在地址空间上有了地址,因此,此时右值就可以被修改。

基于右值被右值引用后可以被修改的特性,便有了const的右值引用:

当对右值引用加上const修饰后,该右值就无法再被修改。

其实这一特性的存在原因很简单。在上面我们讲了可以通过右值引用,将将亡值的数据与接收对象的数据交换。而将亡值是右值,如果将亡值没有明确的地址,那它就无法和接收对象的数据完成交换。

要注意,虽然右值引用后右值可以被修改,但它修改的并不是对应的常量,而是在对应地址上存储的值。因为常量是不允许修改的。

5. 右值引用后的值被视为左值

在上文中,我们已经介绍过,在stl容器中的插入函数都实现了右值引用的版本。在这里我们也可以先尝试一下让自己写的list提供右值引用的插入。

先写以下测试程序:

 在这个程序里面,用的是stl的list。运行程序:

进行了三次移动拷贝。再将这个list换成我们自己写的list并运行:

可以看到,在换成我们自己写的list后就是进行了4次深拷贝。在数据量够大的情况下,效率差距就会非常大。

因此,在这里,我们就需要对我们自己写的list进行改造,使其提供右值引用的插入版本:

 将程序修改完后,我们再次运行程序:

可以看到,虽然我们已经在list中提供了右值引用的版本,但是在插入时,依然是“深拷贝”。上文中讲了,右值经过右值引用后就可以被修改。既然右值已经可以被修改了,就说明它不再是一个右值,而是一个左值。因此,一个右值经过右值引用传参后,其实它再次被使用时,是被当做一个左值来使用的。这就会导致在list中除了最开始的push_back(),其他函数都是调用的左值引用的函数。

如果想保持它的右值属性,可以给每个右值引用的函数中将左值用move()强制转为右值

此时再运行程序:

此时就成功实现了移动拷贝。

6. 完美引用

在上文中说了,当一个右值被右值引用后,当它被再次使用时,它就不再是右值,而是左值。因此,在二次使用时如果想将其当做右值使用,就要用move()将其转换为右值。

但是,如果我们传入的右值被二次使用时是右值,就可以使用“完美引用”

“完美引用”只能用于有模板的情况。如果没有模板,则无法使用:

在上面的程序中,分别提供了左值引用和右值引用的func()函数。该程序也根据传入的左值或右值调用了不同的函数。但如果将左值引用的函数去掉:

可以看到,此时就会报错。因为右值引用是无法引用左值的。

但是,如果这个函数有右值,则会不同:

当有模板时,这个函数就既可以接收左值,也可以接受右值了

这种有模板的右值引用,就被叫做“完美引用”。当传入的值是左值时,编译器就会折叠一个“&”,将其看做“左值引用”;当传入的值是右值时,就正常使用,将其看做“右值引用”。

7. 完美转发

“完美引用”虽然在引用的时候,编译器可以根据它是左值还是右值取进行左值引用或右值引用,但也仅仅是在引用的时候可以识别。在引用过来进行二次使用后,它依然会将其统一看做“左值”

在上面的程序中,虽然我们传过去的值既有左值也有右值,但在其被二次传递时,就被视作了左值。于是全部都是调用左值引用。

当然,我们也可以在二次传递时加上move()将其转为右值:

通过move()的方式,就会全部都进行右值引用。

但是,如果我们想让传入的左值和右值保持原来的属性呢?就是说传入的是左值,在二次传递时就传递左值;如果传入的是右值,在二次传递时,就传递右值。此时,就可以使用“完美转发”。

“完美转发”可以在传参的过程中保留对象原生类型属性。要使用时,直接使用“std::forward<>”即可:

在forward<>后面的变量要用“()”包裹起来,否则可能出现编译错误。

二、新的类功能

1. C++11中新增的默认成员函数

大家知道,C++中有六大默认成员函数,分别是构造函数、拷贝构造函数、拷贝赋值重载、析构函数、取地址重载和const取地址重载。其中最重要的就是前四个。这些默认成员函数如果我们不自己写,编译器就会帮我们自动生成。

在C++11后,类中又新增了两个默认成员函数:移动构造函数移动赋值重载

移动构造函数和移动赋值重载,虽然它们也可以被编译器自动生成, 但是与原来的六大默认成员函数的只要不写就自动生成不同,这两个默认成员函数要自动生成是有一定条件的

如果我们没有实现移动构造函数移动赋值重载函数,没有实现拷贝构造拷贝赋值重载析构函数中的任意一个,编译器就会自动生成一个移动构造函数或移动赋值重载函数。 

默认生成的移动构造函数和移动拷贝函数,对于内置类型会执行逐成员按字节拷贝;对于自定义成员,如果它实现了移动构造和移动赋值重载,就调用它的移动构造和移动赋值重载没有实现,则掉用拷贝构造和拷贝赋值。 

 注意:如果你提供了移动构造或移动赋值,编译器就不会自动提供拷贝构造和拷贝赋值

2. default关键字

根据上文中的内容可以知道,移动构造和移动赋值的默认生成条件非常苛刻,必须要保证拷贝构造函数、赋值重载函数和析构函数都没有实现才会自动生成。但如果在某些情况下,必须要自己实现在这三个默认成员中的一员,这就意味着如果想用移动构造和移动赋值,我们就必须要自己写。

但是,这样就会很烦。因此,当遇到必须要实现拷贝构造、赋值重载和析构函数中的任意一员,编译器自动提供的移动构造和移动赋值又满足需求的情况下,就可以使用default关键字

该关键字可以强制生成某个默认成员函数

可以看到,在上面的程序中,我们并没有自己写移动构造,但是在用了default关键字强制生成移动拷贝后,依然可以正常使用

3. delete关键字

在C++11之前,如果不想让类中的某个默认成员函数被调用,就会将它放在私有成员中

但是这种方法也是有缺陷的。将默认成员函数放在私有中,虽然可以避免外部调用,但是却无法避免内部调用

可以看到,虽然拷贝构造是私有,但是却无法阻止类内部的拷贝构造。存在缺陷。当然,我们也可以将对应的默认成员函数在类中“只声明不实现”,这样在调用时就会无法找到定义报错。

在C++11之后,就可以使用delete关键字实现这一操作了。

与default关键字强制生成某个默认成员函数不同,delete关键字可以限制某些默认函数的生成。要禁止某个默认成员函数,在该默认成员函数的声明后面加上“=delete”即可。被=delete修饰的函数称为删除函数

被delete所修饰的默认成员函数,无论是类外还是类内部,都无法使用

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

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

相关文章

2023北京新一代信息技术应用融合创新人才发展峰会暨鲲鹏开发者创享日·北京站成功举办

以技术创新促产业发展&#xff0c;以开放使能筑人才根基 4月25日&#xff0c;由北京市经济和信息化局、北京市朝阳区人民政府、国家工业信息安全发展研究中心与华为技术有限公司联合主办&#xff0c;北京鲲鹏联合创新中心、北京市中小企业公共服务平台、中国软件行业协会承办的…

字节超全学习流程图流出,100天涨薪10k,从功能测试到自动化测试

今年年初&#xff0c;由于经济压力让我下定决心进阶自动化测试&#xff0c;已经24的我做了3年功能测试&#xff0c;坐标广州薪资定格在8k&#xff0c;可能是生活过的太安逸&#xff0c;觉得8000的工资也够了。 但是生活总是多变的&#xff0c;女朋友的突然怀孕&#xff0c;让我…

软件测试面试一定要看的面试题和笔试题全套教程

1、什么是软件测试&#xff1f;2’ 【要点】 在规定条件下对程序进行操作&#xff0c;以发现错误&#xff0c;对软件质量进行评估&#xff0c;包括对软件形成过程的文档、数据以及程序进行测试。 【详解】 软件测试就是在软件投入运行前对软件需求分析、软件设计规格说明书…

ApplicationContextAware接口

一、ApplicationContextAware接口的基本介绍 public interface ApplicationContextAware extends Aware {void setApplicationContext(ApplicationContext applicationContext) throws BeansException;}在Spring/SpringMVC中&#xff0c;我们拿到IOC容器无非有三种方式&#x…

通达信结构紧凑形态选股公式编写思路

在威廉欧奈尔的《笑傲股市》、马克米勒维尼的《股票魔法师》等书籍中都有结构紧凑形态的相关描述&#xff0c;股票在形成基底时&#xff0c;价格波动幅度逐渐减小&#xff0c;量能逐步萎缩&#xff0c;同时价格相对强度较高。 结构紧凑的形态通过眼睛观察&#xff0c;一般可以…

JS类的学习

文章目录 一、JavaScript 类(class)二、JavaScript 类继承三、 JavaScript 静态方法总结 一、JavaScript 类(class) 类是用于创建对象的模板。 我们使用 class 关键字来创建一个类&#xff0c;类体在一对大括号 {} 中&#xff0c;我们可以在大括号 {} 中定义类成员的位置&…

【Shell编程之条件语句】

目录 一、条件测试操作1、test命令2、文件测试2.1、常用的测试操作符 3、整数值比较3.1、常用的测试操作符(重点&#xff09; 4、逻辑测试4.1、常用的测试操作符号 二、if语句的结构1、单分支结构2、双分支结构3.多分支结构 一、条件测试操作 1、test命令 测试表达式是否成立…

同城跑腿APP开发需具备哪些功能?

移动互联网的飞速发展加上人们生活水平的提高&#xff0c;生活工作闲暇之余&#xff0c;人们不愿意为买药、送文件、取东西、送花、排队等小事浪费时间或者是根本没有时间去处理类似的事情。这个时候就想如果能够花钱请人来替我做这些事就好了&#xff0c;于是同城跑腿就在这样…

C/C++中的数据结构对齐,#pragma pack() 和 __attribute__

C/C中的数据结构对齐 总览 数据结构对齐是指在计算机内存中排列和访问数据的方式。它包含三个独立但相关的问题&#xff1a;数据对齐&#xff08;data alignment&#xff09;&#xff0c;数据结构填充&#xff08; data structure padding&#xff09;和打包&#xff08;pack…

根据 vue-grid-layout 动态设置Echarts尺寸大小

文章目录 前言一、vue-grid-layout 是什么&#xff1f;二、正文1.引入vue-grid-layout2.myEcharts组件3. Utils中的debounce防抖函数 总结 前言 此文背景是根据 vue-grid-layout 动态拖拽组件大小里面包含 Echarts 组件情景&#xff0c;也可以单独把监听动态设置Echarts 尺寸抽…

Java文件IO操作基础

目录 前言 java.io.File 1. 构造方法 2. 方法 get类方法 文件的创建和删除 目录的创建与删除 输入输出流 InputStream FileInputStream 概述 代码实例1 代码实例2 字符集问题? Scanner 读取 OutputStream Java输入输出流的使用案例 创作不易, 多多支持&#x1f636;‍&…

C++的异常

文章目录 1. C语言传统的处理错误的方式2. C异常概念3. 异常的使用3.1 异常的抛出和匹配原则 4. C标准库的异常体系5. 自定义异常体系6. 异常的重新抛出7. 函数调用链中异常栈展开匹配原则8. 异常安全9. 异常规范10. 异常的优缺点 1. C语言传统的处理错误的方式 传统的错误处理…

Windows下版本控制器(SVN)-TortoiseSVN使用+权限配置+合并深度介绍+分支介绍

文章目录 基础知识-Windows下版本控制器(SVN)3.4 TortoiseSVN使用3.4.1 SVN检出(SVN Checkout)3.4.2 SVN更新(SVN Update)3.4.3 **SVN**提交(SVN Commit)3.4.4 **SVN**还原(SVN Revert)3.4.5 解决文件冲突3.4.6 授权访问3.4.7 显示日志(Show log)3.4.8 版本库浏览(Repo-browser…

中国社会科学院大学与美国杜兰大学金融管理硕士项目——找寻属于你的那道光

人生的路很长&#xff0c;再不舍过去&#xff0c;也要朝前走。总有一束光在闪耀&#xff0c;总有路在前方。让我们去找寻属于自己的光。这道光或明或暗&#xff0c;或远或近。给自己一个希望&#xff0c;它就是那一束光会撒在你身上&#xff0c;未来会一片灿烂。社科院与杜兰大…

什么是3D渲染,3D渲染在CG项目中为何如此重要?

随着科技的发展&#xff0c;现如今任何人都可以使用免费软件在个人计算机上创作 3D 图像&#xff0c;当然也有人对于专业 3D 艺术的创作方式及其相关工作流程存在一些误解&#xff0c;认为创建一个模型后&#xff0c;在上面放上材料和纹理&#xff0c;就可以立马得到一个漂亮的…

软件测试职业发展方向有哪些

随着人工智能时代的到来&#xff0c;IT行业受到了越来越多人的重视。软件测试作为把控软件质量必不可少的环节&#xff0c;其重要性可见一斑。 据第三方平台统计&#xff0c;北京软件测试工程师的平均薪资为16.2K&#xff0c;除了一线城市外&#xff0c;随着互联网行业逐渐下沉…

虹科新品 | 用于医疗应用的压力和气体流量传感器

ES Systems在创新MEMS方面拥有丰富的经验&#xff0c;设计了高质量和高性能的气体流量和压力传感器&#xff0c;由于其技术规格&#xff0c;出色的可靠性和有竞争力的价格&#xff0c;这些传感器在竞争产品中具有独特的品质。 Part.01 应用背景 众所周知&#xff0c;在医疗领域…

软件测试技术(五)软件测试流程

软件测试流程 软件测试流程如下&#xff1a; 测试计划测试设计测试执行 单元测试集成测试确认测试系统测试验收测试回归测试验证活动 测试计划 测试计划由测试负责人来编写&#xff0c;用于确定各个测试阶段的目标和策略。这个过程将输出测试计划&#xff0c;明确要完成的测…

freeswitch的任务引擎问题与解决方案

概述 freeswitch核心框架中有一个定时任务系统task&#xff0c;在开发过程中用来做一些延时操作和异步操作很方便。 我们在VOIP的呼叫流程中&#xff0c;经常会有一些对实时性要求没那么高的操作&#xff0c;或者会有阻塞流程的操作&#xff0c;我们都可以开启一个定时任务子…

【 Spring 事务 】

文章目录 一、为什么需要事务(简单回顾)二、MySQL 中的事务使⽤三、Spring 中事务的实现3.1 Spring 编程式事务(手动事务)3.2 Spring 声明式事务(自动事务)3.2.1 Transactional 作⽤范围3.2.2 Transactional 参数说明3.2.3 Transactional 不进行事务回滚的情况3.2.4 Transactio…