【C++】—— C++11新特性之 “右值引用和移动语义”

news2025/1/16 13:53:20

前言:

  • 本期,我们将要的介绍有关 C++右值引用 的相关知识。对于本期知识内容,大家是必须要能够掌握的,在面试中是属于重点考察对象。

目录

(一)左值引用和右值引用

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

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

(二)左值引用与右值引用比较

(三)右值引用使用场景和意义

(四)完美转发 

1、概念

2、模板中的&& 万能引用

3、std::forward

总结


(一)左值引用和右值引用

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

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

C++98/03 标准中就有引用,使用 "&" 表示。但此种引用方式有一个缺陷,即正常情况下只能操作 C++中的左值,无法对右值添加引用。举个例子:
 

int main()
{
	int num = 10;
	int& b = num; //正确
	int& c = 10; //错误

	return 0;
}

输出展示:

 【解释说明】

  • 如上所示,编译器允许我们为 num 左值建立一个引用,但不可以为 10 这个右值建立引用。因此,C++98/03 标准中的引用又称为左值引用。
     

那么到底什么是左值?什么是左值引用呢?

  1. 左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边;
  2. 定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
     

注意:虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。也就是说,常量左值引用既可以操作左值,也可以操作右值,例如:
 

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

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

我们知道,右值往往是没有名称的,因此要使用它只能借助引用的方式。这就产生一个问题,实际开发中我们可能需要对右值进行修改(实现移动语义时就需要),显然左值引用的方式是行不通的。


为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 "&&" 表示:

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

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

	return 0;
}

输出展示:

 但是如果是下面这几个表达式,就会发生报错现象:

10 = 1;
x + y = 1;
fmin(x, y) = 1;

输出显示:

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址。例如下面代码所示:

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

	int&& rr1 = 10;
	double&& rr2 = x + y;
	cout << rr1 << " " << rr2 << " " << endl;

	rr1 = 20;
	rr2 = 5.5;
	cout << rr1 << " " << rr2 << " " << endl;

	return 0;
}

输出展示:

 当我们不想被修改时,我们可以加上 【const】关键字:

【解释说明】

  1. 不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1;
  2. 如果不想rr1被修改,可以用const int&& rr1 去引用;
  3. 是不是感觉很神奇这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要
     

(二)左值引用与右值引用比较

左值引用总结:

  • 1. 左值引用只能引用左值,不能引用右值。
  • 2. 但是const左值引用既可引用左值,也可引用右值

int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a; // ra为a的别名
    
    return 0;
}

输出展示:

 又例如以下示例:

int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra2 = 10; // 编译失败,因为10是右值

    return 0;
}

输出展示:

 左值引用只能引用左值,不能引用右值。但是当我们加上 const 时,此时左值引用可以给右值取别名:

int main()
{
    int a = 10;
    // const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;
    
    return 0;
}

输出展示:

【解释说明】

值得一提的是,虽然C++ 语法上是支持定义常量右值引用的,但这种定义出来的右值引用并无实际用处:

const int& ra3 = 10;
  1. 一方面,右值引用主要用于移动语义和完美转发,其中前者需要有修改右值的权限;
  2. 其次,常量右值引用的作用就是引用一个不可修改的右值,这项工作完全可以交给常量左值引用完成。
     


右值引用总结:

  • 1. 右值引用只能右值,不能引用左值。
  • 2. 但是右值引用可以move以后的左值。
     

代码展示:

 引用左值会发生报错行为:


通过 move 可以支持将左值转换为右值引用 

 在C++中,move是一个函数模板,可以将给定的对象转换为对应的右值引用。它并不执行实际的内存移动操作,而是将对象标记为可以进行移动操作的右值。这样,用户可以利用该标记来实现更高效的移动语义。


(三)右值引用使用场景和意义

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

现有以下代码: 

 【解释说明】

首先,对于上述代码中的 res1 和 res2 ,它们分别为左值和右值;

紧接着大家想想,我们对左值和对右值拷贝有没有什么区别呢?

  • 如果是内置类型,他们其实区别不是很大,但是对于自定类型他们的区别可就很大了,
  • 因为自定义类型的右值,一般很多地方又把它叫做将亡值。通常都是一些表达式的返回值、一个函数调用等;
  • 而对于右值又分为 纯右值(一般来说是内置类型)和将亡值(一般来说是自定义类型)

对于上述的 res1,它作为一个左值,我们不能对其进行操作,只能去做深拷贝。因为虽然看起来这里是一个赋值,其实应该是拷贝构造;

而对于 res2 来说,它本身是右值,假如是自定义类型作为一个将亡值,我们就没有必要去对其进行拷贝操作。此时就引出了关于右值引用实现引动构造的概念。


例如现在有这样一个我们手写模拟的 string :

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

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;

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

当我们再这样的场景在来观察上述的 res1 和res2:

 我们可以发现此处的右值发生的是相应的深拷贝,这样显然是会造成不必要的浪费的。为了解决上述这样的问题,我们就可以引入 “移动构造” 这样的概念:

// 移动构造
string(string&& s)
	:_str(nullptr)
{
	cout << "string(string&& s) -- 移动拷贝" << endl;
	swap(s);
}

紧接着,再次运行上述代码,我们可以发现编译器会去自动识别:

此时,当我们就是想把 s1 转为右值可以怎么做呢?其实很简单(这里就体现了move):

 输出展示:

我们通过调试也可以发现此时确实达到了预期的效果:

 【小结】

通过上述我们可以发现左值引用的好处就是直接减少拷贝

左值引用的使用场景可以分为以下两个部分:

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

左值引用的短板:

  • 但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。

例如现在有以下这样的代码: 

    zp::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

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

【说明】

  • zp::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

 紧接着,我们去打印看结果是什么:

 此时,传值返回带来的代价得到了极大的解决:

 右值引用和移动语义解决上述问题:

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

再运行上面zp::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。


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

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

输出展示:

 【解释说明】

  • 这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。zp::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为 zp::to_string 函数调用的返回值赋值给ret1,这里调用的移动赋值。

(四)完美转发 

1、概念

  • 完美转发(perfect forwarding)是C++11引入的一项特性,旨在实现在函数模板中对参数类型进行精确传递的能力;
  • 它主要用于保留传递到函数模板的实参的值类别,并将其转发到内部调用的函数,从而实现类型和值类别的完全保持;

举个例子:

template<typename T>
void PerfectForward(T t)
{
    Fun(t);
}

【解释说明】

  1. 如上所示,PerfectForward() 函数模板中调用了 Func() 函数;
  2. 在此基础上,完美转发指的是:如果 PerfectForward() 函数接收到的参数 t 为左值,那么该函数传递给 Func() 的参数 t 也是左值;
  3. 反之如果 function() 函数接收到的参数 t 为右值,那么传递给 Func() 函数的参数 t 也必须为右值。

使用任何一种引用形式,可以实现转发,但无法保证完美。因此如果使用 C++ 98/03 标准下的 C++ 语言,我们可以采用函数模板重载的方式实现完美转发,例如:

template<typename T>
void Func(T& arg)
{
    cout << "左值引用:" << arg << endl;
}

template<typename T>
void Func(T&& arg)
{
    cout << "右值引用:" << arg << endl;
}

template<typename T>
void PerfectForward(T&& arg)
{
    Func(arg);  // 利用重载的process函数进行处理
}

int main()
{
    int value = 42;
    PerfectForward(value);       // 传递左值
    PerfectForward(123);         // 传递右值

    return 0;
}

输出展示:

 【解释说明】

  1. 在上述示例中,我们定义了两个重载的函数模板 Func,一个接收左值引用参数T& arg,另一个接收转发引用参数T&& arg;
  2. 然后,我们再定义一个模板函数PerfectForward,其参数也是转发引用T&& arg。在PerfectForward函数内部,我们通过调用Func函数来处理传递的参数;
  3. 通过函数重载的机制,传递的左值参数将匹配到接收左值引用的Func函数,传递的右值参数则匹配到接收转发引用的Func函数,从而正确地进行区分和处理;
  4. 通过函数模板的重载,我们可以根据参数类型将左值和右值区分开来,并分别处理,实现了针对不同值类别的精确匹配和操作。

2、模板中的&& 万能引用

显然,上述使用重载的模板函数实现完美转发也是有弊端的,此实现方式仅适用于模板函数仅有少量参数的情况,否则就需要编写大量的重载函数模板,造成代码的冗余。为了方便用户更快速地实现完美转发,C++ 11 标准中允许在函数模板中使用右值引用来实现完美转发。

还是以 PerfectForward() 函数为例,在 C++11 标准中实现完美转发,只需要编写如下一个模板函数即可:

//模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
template<typename T>
void PerfectForward(T&& t)
{
    Fun(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;
}

输出展示:

 【解释说明】

  1. 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  2. 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
  3. 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
  4. 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
     

3、std::forward

C++11 标准的开发者已经帮我们想好的解决方案,该新标准还引入了一个模板函数 forword<T>(),我们只需要调用该函数,就可以很方便地解决此问题。

  1. 完美转发通常与转发引用(forwarding reference)和 std::forward 函数一起使用;
  2. 转发引用是一种特殊的引用类型,使用&&语法进行声明,用于在函数模板中捕获传递的实参;
  3. std::forward 是一个模板函数,用于在函数模板内部将转发引用作为右值或左值引用进行转发。

如下演示了该函数模板的用法:

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)
{
	// forward<T>(t)在传参的过程中保持了t的原生类型属性。
	Fun(std::forward<T>(t));
}
int main()
{
	PerfectForward(10); // 右值

	int a;
	PerfectForward(a); // 左值
	PerfectForward(move(a)); // 右值

	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(move(b)); // const 右值
	return 0;
}

程序执行结果为:

通过完美转发,我们可以在函数模板中正确处理传递实参的值类别,并将其转发到内部函数,以达到类型和值类别的完全保持,提高代码的灵活性和效率。


总结

学到这里,一些读者可能无法记清楚左值引用和右值引用各自可以引用左值还是右值,这里给大家一张表格,方便大家记忆:
 

  •  表中,Y 表示支持,N 表示不支持。

以上便是关于左值引用和右值引用的全部知识讲解!感谢大家的观看与支持!!!

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

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

相关文章

如何在不使用任何软件的情况下将 PDF 转换为 Excel

通常&#xff0c;您可能会遇到这样的情况&#xff1a;您需要的数据不在 Excel 工作表中&#xff0c;而是以数据表形式出现在 PDF 文件中。为了将此数据放入 Excel 工作表中&#xff0c;如果您尝试将数字复制并粘贴到电子表格中&#xff0c;则列/行将无法正确复制和对齐。因此&a…

C++信息学奥赛1145:字符串p型编码

#include <iostream> #include <string> using namespace std; int main() {string str;// 输入一个字符串cin>>str;int nstr.length();for(int i0;i<n;i){int a1;while(str[i]str[i1]){i;a;}cout<<a<<str[i]; // 输出连续相同字符的个数和该…

开源硬件:下一个技术革命?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

常见前端面试之VUE面试题汇总七

20. 对 vue 设计原则的理解 1.渐进式 JavaScript 框架&#xff1a;与其它大型框架不同的是&#xff0c;Vue 被设计 为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上 手&#xff0c;还便于与第三方库或既有项目整合。另一方面&#xff0c;当与现代化的…

COMPUTERS GEOSCIENCES投稿流程

COMPUTERS & GEOSCIENCES投稿流程 期刊介绍页面投稿网页投稿流程1.注册投稿账号2.注册orcid3.需要上传的文件4. 利益声明&#xff08;Declaration of Interest Statement&#xff09;5.Highlights6.爱思唯尔文章类别怎么选 COMPUTERS & GEOSCIENCES投稿流程 期刊介绍页…

前端速查速记系列----评论列表

小程序评论列表 效果图 wxml代码 <view id"econtent"><block wx:for"{{commentlist}}" wx:for-item"item" wx:for-index"index" wx:key"{{item.id}}"><view class"box1"><view class"…

【Java转Go】快速上手学习笔记(六)之网络编程篇一

目录 TCP一个简单案例server.go 服务端client.go 客户端 HTTPserver.go 服务端client.go 客户端 RPC一个很简单的示例server.go 服务端client.go 客户端 WebSocketserver.go 服务端client.go 客户端 完整代码server.go 服务端client.go 客户端 go往期文章笔记&#xff1a; 【J…

FPGA应用于图像处理

FPGA应用于图像处理 FPGA&#xff08;Field-Programmable Gate Array&#xff09;直译过来就是现场可编程门阵列。是一种可以编程的逻辑器件&#xff0c;具有高度的灵活性&#xff0c;可以根据具体需求就像编程来实现不同的功能。 FPGA器件属于专用的集成电流中的一种半定制电…

Kotlin的Lambda闭包语法

Lambda 表达式是一种在现代编程语言中常见的特性&#xff0c;它可以用来创建匿名函数或代码块&#xff0c;使得将函数作为参数传递、简化代码以及实现函数式编程范式变得更加便捷。Lambda 表达式在函数式编程语言中得到广泛应用&#xff0c;也在诸如 Java 8 和 Kotlin 等主流编…

centos安装MySQL 解压版完整教程(按步骤傻瓜式安装

一、卸载系统自带的 Mariadb 查看&#xff1a; rpm -qa|grep mariadb 卸载&#xff1a; rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64 二、卸载 etc 目录下的 my.cnf 文件 rm -rf /etc/my.cnf 三、检查MySQL是否存在 有则先删除 #卸载mysql服务以及删除所有mysql目录 #没…

Java集合案例:斗地主游戏开发

斗地主游戏的开发业务需求分析业务&#xff1a;共有54张牌点数&#xff1a;“3”,“4”,“5”,“6”,“7”,“8”,“9”,“10”,“J”,“Q”,“K”,“A”,“2”花色&#xff1a;“♠”,“❤”,“♣”,“♦”大小王&#xff1a;“&#x1f472;”,“&#x1f0cf;” *点数分别要…

【⑭MySQL | 数据类型(二)】字符串 | 二进制类型

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL字符串 | 二进制类型类型的分享✨ 目录 前言5 字符串类型6 二进制类型总结 5 字符串类型 字符串类型用来存储字符串数据&#xff0c;还可以存储图片和声音的二进制数据。字符串可以区分或者不区分大小写的串比…

java八股文面试[java基础]——如何实现不可变的类

知识来源&#xff1a; 【23版面试突击】如何实现不可变的类&#xff1f;_哔哩哔哩_bilibili 【2023年面试】怎样声明一个类不会被继承&#xff0c;什么场景下会用_哔哩哔哩_bilibili

cnpm安装时总是报错

使用npm下载cnpm安装时总是报错 解决办法&#xff1a; 1、执行&#xff1a;npm config get proxy npm config get https-proxy 如果返回值不为null&#xff0c;继续执行&#xff1a; &#xff08;这一步很重要&#xff0c;一定要保证两个命令的返回值都为null,话说回来&#xf…

TypeScript三种特殊类型

1.any类型 说明&#xff1a;any类型代表着可以赋值任意类型 let nickname:any"王二"nickname15nicknametruenicknameundefinednicknamenullnickname{}2.unknown类型 说明&#xff1a;类似any类型&#xff1b;只是不能赋值到其它类型上&#xff1b;除了any和known。…

java八股文面试[java基础]——字节码

字节码技术应用 字节码技术的应用场景包括但不限于AOP&#xff0c;动态生成代码&#xff0c;接下来讲一下字节码技术相关的第三方类库&#xff0c;第三方框架的讲解是为了帮助大家了解字节码技术的应用方向&#xff0c;文档并没有对框架机制进行详细分析&#xff0c;有兴趣的可…

使用Aircrack-ng进行无线网络破解

Aircrack-ng是一款流行的无线网络渗透测试工具&#xff0c;主要用于密码破解和网络分析。但是&#xff0c;请注意&#xff0c;仅在有合法授权的情况下使用这些工具。 以下是一个使用Aircrack-ng进行无线网络破解的示例&#xff0c;以及一些步骤和注意事项&#xff1a; 步骤&a…

2023年京东睡眠经济市场数据分析(京东商品数据)

如今&#xff0c;伴随快节奏的生活&#xff0c;越来越多的人饱受睡眠问题的影响。同时&#xff0c;伴随现代化的发展&#xff0c;睡眠障碍群体或许会达到新的体量&#xff0c;而日趋增加的失眠人群自然而然低催生了助眠产品的增长。随着人们对健康睡眠重视的程度不断提高&#…

Dubbo 开发入门

一、通过模板生成项目脚手架 Dubbo Initializer 可用来快速生成 Java 项目脚手架&#xff0c;帮助简化微服务项目搭建、基本配置、组件依赖管理等。 Initializer 仍在持续更新中&#xff0c;更多 Dubbo Feature 的支持将会陆续发布。 选择 Dubbo 版本 Initializer 将使用 dub…

05:TIM定时器功能------输入捕获功能

目录 1:简历 2:频率测量 3:主从触发模式 4:结构 A:输入捕获功能测频率 1:连接图 2:函数介绍 3:测周法 4 输入捕获模式基本结构 5:步骤 6:代码 B :PWMI模式测频率占空比 1: 连接图 2:PWMI模式的结构 3:函数介绍 4:步骤 5:代码 1:简历 IC&#xff08;Input Capture…