【C++进阶】之C++11的简单介绍(一)

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

【C++进阶】之C++11的简单介绍

  • C++11简介
  • 列表初始化
    • {}初始化
    • std::initializer_list容器
      • 文档介绍
        • initializer_list容器的底层
      • std::initializer_list容器的使用
  • 与类型相关的两个新特性
    • 变量名推导
    • decltype关键字
      • typeid介绍
        • type_info类
    • nullptr
    • 重载函数
  • STL中的一些变化
    • 增加了一些新容器
    • 老容器增加了一些新的方法
  • pair容器的模拟实现
  • 右值引用和移动构造
    • 左值引用和右值引用
    • 左值引用和右值引用的比较
    • 引用的底层原理
    • 右值引用存在的价值
      • 移动构造
      • 左值引用为什么无法完全解决拷贝的问题
        • 右值引用引用右值的几种情况
          • 引用的不是指针
          • 引用的是指针变量
    • 右值引用引用左值
    • 完美转发
      • 完美转发的应用场景
      • 万能引用

C++11简介

在这里插入图片描述

列表初始化

{}初始化

C++98中,标准允许使用花括号对数组和结构体对象进行初始化。

在这里插入图片描述

C++11之后,扩大了{}的使用范围,可以对所有的内置类型和自定义类型使用{},还可以省略=

在这里插入图片描述

new对象的时候初始化也可以使用{}

看下面这段代码,思考使用{}直接初始化自定义类的原理:

#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<stack>

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

	Date& operator=(const Date& x)
	{
		cout << "operator=(const Date & x)" << endl;
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date y = { 2024,9,17 };
}

运行结果:

在这里插入图片描述
只调用了构造函数,这是优化后的结果。

在这里插入图片描述
那直接构造自定义类型中,{1,2,3,4,5,6,7是什么类型,调用的是什么构造函数呢?

在这里插入图片描述

std::initializer_list容器

文档介绍

点击跳转

在这里插入图片描述
从上面的文档我们能得到关于initializer_list以下信息:

  • 这个类型的对象是被编译器自动的创建的,是一个用逗号分割被花括号包裹的元素列表。
  • 使用这个对象的时候,应该包含头文件。
  • 初始化列表的对象会被自动的构建,底层是一个const T类型的数组,但是这个对象只有使用权,并不拥有底层的数组,也就是它们的空间不归这个对象管理和释放。
  • 只使用这个初始化列表对象为参数的构造函数是一个特殊的构造函数,当使用初始化器列表构造函数语法时,初始化器列表构造器优先于其他构造函数,不会走隐式类型转换。

看下面这段代码验证上述信息:

#include<iostream>

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

	template<class T>
	Date(const initializer_list<T>& x)
	{
		auto it = x.begin();
		_year = *it;
		it++;
		_month = *it;
		it++;
		_day = *it;

		cout << "Date(const initializer_list<T>& x)" << endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date y = { 2024,9,17 };
}

当以初始化列表初始化Date类时,当存在带参构造函数和初始化列表类型的构造函数时,编译器会优先调用初始化列表构造函数

在这里插入图片描述

只存在带参构造函数,编译器会去调用带参构造函数:

在这里插入图片描述
这是多参数的隐式类型转化,编译器会去找有没有对应数量的参数以及相同的类型的构造函数。

如果数量不符或者类型不一致就不能隐式类型转化。

在这里插入图片描述

在这里插入图片描述

底层直接调用带参构造,并没有先构造为initializer_list类型,所以这里不包含头文件也可以。
这里实际上也是编译器优化的结果,应该先构造为initializer_list类型再去构造出一个Date类的对象,最后调用赋值构造函数,这里直接优化为调用带参的Date构造函数。

在这里插入图片描述

initializer_list容器的底层

看下面这段代码:

#include<iostream>

using namespace std;

int main()
{
	std::initializer_list<int> x = { 1,2,3,4,5 };
	cout << sizeof(x) << endl;

	return 0;
}

运行结果:

在这里插入图片描述

这个类的大小为8,实际上,这个类的成员变量是两个指针,一个指向const T*类型的数组首元素,一个指向最后一个元素的下一个元素处。就像beginend一样。

在这里插入图片描述

如果用一个initializer_list容器去初始化另外一个,它们的_First和_Last应该是一样的:

在这里插入图片描述

官方文档里面已经谈到过:

在这里插入图片描述

initializer_list这个类是没有析构函数的,也就是它并不负责数组的空间管理,它只是用来初始化别的容器的。所以不用担心重复析构的问题。

在这里插入图片描述

我们在反汇编里面并没有看见调用malloc函数和new [],应该是直接在栈上开的局部数组。如果是字符串字面量,则是在只读数据段。

在这里插入图片描述

std::initializer_list容器的使用

std::initializer_list容器主要用来初始化我们自定义的类,像vectorList等容器了以initializer_list为参数的构造函数,我们下面来支持一下我们自己实现的vector类的{}构造,给它增加以initializer_list为参数的构造函数,让它支持这个功能。

这是没有增加初始化列表构造函数,我们自己实现的vector函数不支持{}直接初始化:

在这里插入图片描述

增加初始化列表构造函数:

	template<class T>
	vector(std::initializer_list<T> x)
    {
	  resize(x.size());
      iterator it = begin();
	  for (auto data : x)
      {
		 *it = data;
		  it++;
	   }
    }

运行结果:

在这里插入图片描述

与类型相关的两个新特性

变量名推导

auto关键字的出现给我们提供了很多遍历。像某些时候如果变量的类型名很长,我们不想写,就可以用auto关键字,让编译器去帮助我们推导。

auto 关键字用于自动类型推导,编译器会根据初始化表达式自动推断变量的类型。这意味着,在编写代码时,你不需要显式地指定变量的类型,编译器会在编译时为你做这件事。

在这里插入图片描述
auto关键字工作在编译时。使用 auto 可以使代码更加简洁,尤其是在处理复杂的类型或者模板编程时,例如:

#include<unordered_map>
#include<queue>
#include<vector>
int main()
{
	std::unordered_map<int, int> mp;
	std::unordered_map<int, int>::iterator it = mp.begin();
	auto it = mp.begin();
	return 0;
}

auto关键字不建议做函数的返回值和函数的参数,可读性不好。

decltype关键字

decltype表达式可以将变量声明为指定类型。

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	decltype(&a) ret1 = nullptr;
	const int b = 2;
	double c = 2.1;

	decltype(b * c) ret2 = 0;

	const int& d = a;

	decltype(d) ret3 = 0;
	cout << typeid(ret1).name() << endl;
	cout << typeid(ret2).name() << endl;
	cout << typeid(ret3).name() << endl;
}

运行结果:

在这里插入图片描述

只会声明为表达式里面值的基本类型,不会加修饰像&const等。

typeid介绍

在这里插入图片描述

#include <iostream>
#include <typeinfo>  

class Base {
public:
   virtual void func() {} // 引入虚函数以支持动态类型识别  
};

class Derived : public Base {

};

int main() {
    Derived d;
    Base* bPtr = &d;
    Base& bRef = d;

    std::cout << typeid(bPtr).name() << std::endl; // 可能输出 "PBase"(指针类型),具体取决于编译器  
    std::cout << typeid(*bPtr).name() << std::endl; // 输出 "Derived",因为bPtr指向Derived类型的对象  
    std::cout << typeid(bRef).name() << std::endl; // 输出 "Derived",因为bRef是Derived类型的引用  

    return 0;
}

运行结果:

在这里插入图片描述

在这里插入图片描述

通过调试我们可以看见,确实存在这个虚函数表,并且Base对象和Derived对象的虚函数表的里面会有一些关于类型的信息。具体可以看博主这篇博客

在这里插入图片描述

type_info类

type_info类是一个特殊的类,它不是给用户使用的,所以我们无法调用它的构造函数,它是用于存储类型相关的信息的。std::type_info对象总是在运行时被访问,但其所表示的类型信息可能在编译时就已经确定了(对于非多态类型),也可能在运行时才能确定(对于多态类型)。

在这里插入图片描述

我们只能通过typeid关键字来获得 type_info类型,它是const type_info &类型。

#include <iostream>
#include <typeinfo>  

using namespace std;

int main()
{
	int a = 0;
	const type_info& ti = typeid(a);

	cout << ti.name() << endl;
	return 0;
}

运行结果:

在这里插入图片描述

nullptr

这个关键字我们很早就在使用了,它是一个新的类型用来表示空指针,了解决C语言NULL的一些问题。

这是C语言中的NULL

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

NULL是一个宏,是一个整数常量,由于它既可以赋值给整数类型变量,也可以赋值给指针变量,指针和整数混合使用的时候,容易产生类型安全的问题,也会造成代码可读性不好。

但是nullptr则不同,它有独立的类型:std::nullptr_t,不会造成NULL这样的问题。

在这里插入图片描述

#include<iostream>
using namespace std;


int main()
{
	int* aPtr = NULL;
	int a = NULL;
	cout << a << endl;
	int* bPtr = nullptr;
	cout << typeid(nullptr).name() << endl;
}

运行结果:

在这里插入图片描述
nullptr转成指针类型时就会采用正确的空指针值。而不能赋值给其它类型。

在这里插入图片描述

在这里插入图片描述

重载函数

使用NULL给重载函数传值,就会出现问题。

#include<iostream>
using namespace std;

void f(int)
{
	cout << "void f(int)" << endl;
}

void f(int*)
{
	cout << "void f(int*)" << endl;
}

int main()
{
	f(NULL);
	return 0;
}

运行结果:

在这里插入图片描述
这里我们的本意可能是传给int*为参数的f函数,编译器却调用了f(int),可能是指针类型需要强转。

使用nullptr就不会出现这种问题:

在这里插入图片描述

但是此时如果传nullptr,出现一个更符合它的重载函数,不用转成指针类型,编译器就会就先调用它:

在这里插入图片描述

STL中的一些变化

增加了一些新容器

在这里插入图片描述
arrayforward_listunordered_mapunordered_set就是C++11中新增的容器。最有用的两个就是最后面两个,它们的底层是哈希表,前面我们已经具体的介绍过了。

在这里插入图片描述

老容器增加了一些新的方法

  1. rbeginrend方法,这些是反向迭代器,用到的不多。
  2. 新增列表初始化构造函数。
  3. 新增push_backinsertemplace_back等方法的右值引用版本,极大的提高了效率。
  4. 增加了移动构造,移动赋值,也可以减少拷贝,提高效率。

pair容器的模拟实现

前面我们一直使用过pair容器,没有模拟实现,今天我们来模拟实现以下pair容器。

namespace my_std
{
	template<class T1,class T2>
	class pair
	{
	public:
		pair()
		{

		}

		pair(const T1& a, const T2& b)
			:_first(a),
			_second(b)
		{

		}
		template<class U, class V>
		pair(const pair<U, V>& pr)
			:_first(pr._first),
			_second(pr._second)
		{

		}

		~pair(){}
	public:
		T1 _first;
		T2 _second;
	};
}

测试函数:

#include<iostream>
#include<vector>
#include<map>
using namespace std;


namespace my_std
{
	template<class T1,class T2>
	class pair
	{
	public:
		pair()
		{

		}

		pair(const T1& a, const T2& b)
			:_first(a),
			_second(b)
		{
			cout << typeid(T1).name() << endl;
		}
		template<class U, class V>
		pair(const pair<U, V>& pr)
			:_first(pr._first),
			_second(pr._second)
		{

		}

		~pair(){}
	public:
		T1 _first;
		T2 _second;
	};
}
int main()
{
	my_std::pair<string, string> kv1("sort", "排序");
	my_std::pair<string, string> kv2("string", "字符串");

		
	my_std::pair<const char*, const char*> kv3("sort", "排序");
	my_std::pair<const string, string> kv4(kv3);

	cout << kv1._first << ": " << kv1._second << endl;
	cout << kv2._first << ": " << kv2._second << endl;
	cout << kv3._first << ": " << kv3._second << endl;
	cout << kv4._first << ": " << kv4._second << endl;

}

运行结果:

在这里插入图片描述

右值引用和移动构造

左值引用和右值引用

  • 左值和左值引用

我们在之前的博客中也谈到过引用,那就是左值引用,是在C++11之前出现的,左值就是一个表示数据的表达式(像变量名、解引用的指针),它可以出现在等号的左边,我们可以对左值取地址,像const修饰的变量虽然我们不能修改它,但是可以取地址,所以它也是左值。

int main()
{

	//左值
	int a = 3;
	int* ptr = &a;
	double b = 2.3;


	//左值引用
	double& b_ = b;
	int*& ptr_ = ptr;
	int& a_ = a;
}
  • 右值和右值引用

右值也是一个表达数据的表达式(函数表达式的返回值(不能是左值引用),字面常量,表达式返回值),但是它只能出现在表达式的右边,而不能出现表达式的左边,也不能被修改。对右值进行取别名就叫做右值引用。

上面是比较官方的定义,实际上,右值和左值最大的不同除了不能对右值进行修改,还有一点就是不能对右值取地址。

右值(C++11对右值进行了细分):

  • 纯右值(内置类型的右值):10/a+b。
  • 将亡值(自定义类型的右值):函数传值返回的返回值/obj++/to_string(1234).
#include<iostream>
using namespace std;

int* a = (int*)malloc(sizeof(int));
int*& f()
{
	return a;
}

int* ff()
{
	return a;
}
int main()
{

	int x = 0;
	int y = 3;
	//左值
	9;
	x + y;
	*f() = 3;//f()是左值
	int*& left_ = ff();//ff()是右值,对右值不能普通的左值引用

	cout << *f() << endl;
	//左值引用
	
}

在这里插入图片描述

#include<iostream>
using namespace std;

int* a = (int*)malloc(sizeof(int));
int*& f()
{
	return a;
}

int* ff()
{
	return a;
}
int main()
{

	int x = 0;
	int y = 3;
	//右值
	9;
	x + y;
	*f() = 3;//f()是左值
	//int*& left_ = ff();//ff()是右值,对右值不能普通的左值引用

	cout << *f() << endl;
	//右值引用
	int&& r1 = 9;
	int*&& r2 = ff();
	int&& r3 = x + y;
}

运行结果:

在这里插入图片描述

但是const修饰的左值引用可以引用右值,对于指针来说,const必须修饰ptr也就是引用变量本身,而不是它指向的内容*ptr

在这里插入图片描述

左值引用和右值引用的比较

上面代码示例验证了const左值引用可以引用右值,其实const左值引用也可以引用左值。

#include<iostream>
using namespace std;

int main()
{

	int x = 0;
	int y = 3;
	//右值
	9;
	
	//常量的左值引用可以对右值引用
	int const& l1 = 9;
	//const修饰的左值引用也可以引用左值
	const int& l2 = x;
}
  • 右值引用只能引用右值,但是它可以引用经过move的左值。
#include<iostream>
using namespace std;



int main()
{
	int x = 0;
	int y = 3;
	//右值
	9;
	x + y;
   
	int&& r1 = 9;
	int&& r2 = move(x);

	return 0;
}

move函数可以将左值转成右值,关于这个函数的其它细节,我们到后面内容详谈。

引用的底层原理

在语法层面上,我们认为,左值引用是给左值引用取别名,右值引用是给右值取别名,但底层引用的原理实际上和指针相似。

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	int& l = a;
	l = 3;

	int* ptr = &a;
	*ptr = 3;
	cout << "--------------------------------" << endl;
	int&& r = 100;

	r = 10;
	return 0;
}

在这里插入图片描述

右值引用存在的价值

右值引用主要解决左值引用没有解决的问题。

很多情况下,使用左值引用可以减少不必要的拷贝,这极大的提高了资源的利用率,但是还有时候,由于返回的变量是临时变量所以我们无法左值引用返回,就需要拷贝,C++11中,右值引用的出现就是为了解决这一问题:

	string string::substr(size_t pos, size_t len)
	{
		string s;
		if (len == string::npos || pos + len > size_)
		{
			char* tmp = new char[size_ - pos + 1];//给\0也要开空间
			memcpy(tmp, str_ + pos, size_ - pos + 1);
			s = tmp;
		}
		else
		{
			char* tmp = new char[len + 1];
			memcpy(tmp, str_ + pos, len);
			tmp[len] = '\0';
			s = tmp;
		}
		return s;
	}

这里我们使用的是我们自己实现的string容器:

#include"string.h"


int main()
{
	my_string::string s1 = "xxxxxxxxxx";
	cout << "------------------------------------------------------------" << endl;
	my_string::string s2 = s1.substr(0);


	return 0;
}

在这里插入图片描述

移动构造

由于有了右值引用,所以我们的构造函数也多了移动构造,移动构造的作用就是转移右值的资源,减少了不必要的拷贝:

// 移动构造
	string::string(string&& s)
		:str_(nullptr)
		, size_(0)
		, capacity_(0)
	{
		cout << "string(string&& s) -- 移动语义" << endl;
		swap(s);
	}

在这里插入图片描述

我们可以验证一下,使用移动构造是否成功将substr方法中的局部变量s的资源窃取,从而避免了拷贝:

在这里插入图片描述

左值引用为什么无法完全解决拷贝的问题

上述问题中,解决拷贝问题的关键在于移动构造,移动构造的参数是右值引用,而移动构造解决拷贝问题的关键又在于它可以转移右值的资源。

const修饰的左值引用也可以引用右值,为什么不把右值传给它,然后转移其资源呢?

  • 无法转移,因为它被const修饰,无法修改它里面的值。
右值引用引用右值的几种情况
引用的不是指针

但是右值引用不同,右值引用给右值起别名之后,变量本身不再具有常性,反而变得可以修改,所以我们可以转移其资源,移动构造我们可以变相的认为延长了它右值的生命周期(因为转移后它的资源还存在),右值引用功不可没。

右值引用变相的让右值变得可以修改。

#include<iostream>

using namespace std;

int* f()
{
	int* ptr = (int*)malloc(sizeof(int));
	*ptr = 3;
	return ptr;
}
int main()
{
	f() = (int*)2;
    return 0;
}

ptr会拷贝一个临时对象存储ptr的值,这个值作为返回值返回,具有常性,不能修改:

在这里插入图片描述
我们将其作为参数或者右值传给右值引用变量就可以修改了,因为这个变量具有左值属性。

#include<iostream>

using namespace std;

int* f()
{
	int* ptr = (int*)malloc(sizeof(int));
	return ptr;
}
int main()
{
	int*&& r1 = f();
	r1 = (int*)0;
	int* ptr_ = f();
	ptr_ = (int*)0;
	return 0;
}

这里ptr_r1都可以修改,貌似它们是一样的其实不一样,前者是对右值的引用,在语法层面是对其起别名,也就是对那个函数返回的临时变量起别名,少了一次拷贝。但是r1是把临时变量拷贝一份。
对于右值引用可以修改,我们无需重点关注这个,因为它最重要的应用不在这里,而是在移动构造里面的转移资源,借助右值引用减少了拷贝,这个我们上面已经重点介绍过,不再重复赘述。

  • 有小伙伴可能会这样写代码:
#include<iostream>

using namespace std;

int*&& f()
{
	int* ptr = (int*)malloc(sizeof(int));
	return move(ptr);
}
int main()
{
	f() = (int*)0;

	return 0;
}

这里虽然函数的返回值是右值引用类型,但是它仍然还是一个右值,因为这个右值返回后还没有来得及保存,没有赋值给一个普通变量或者右值引用的变量,它仍然具有右值属性,不能修改。所以我们要少使用右值引用类型作为返回值,因为这容易让人被误导,认为它没有被接收前可以修改,它的作用也不大,右值引用最常见的应用还是移动构造。

在这里插入图片描述

引用的是指针变量

此时如果引用的是指针变量,并且这个指针变量指向的内容具有常性,我们使用右值引用,只能让这个指针变量本身变得可修改,并不能让其指向的内容可修改。
下面是一段代码:


#include <iostream>  
using namespace std;

int main()
{
	int num = 3;
	int const* ptr = &num;

	int const * &&r1 = move(ptr);//如果不加*修饰r1指向的内容,程序就会报错
	//*r1 = 4;不可修改r1指向的内容

	r1 = nullptr;//r1可修改
	return 0;
}

运行结果:

在这里插入图片描述

不加const修饰r1变量指向的内容,程序就会报错:

在这里插入图片描述

右值引用引用左值

右值引用也可以引用左值,就是我们前面提到的将左值转化为右值的函数move,这个函数并不移动任何资源,只是简单的将左值转化为右值:

#include"string.h"


int main()
{
	my_string::string s1 = "xxxxxxxxxx";

	my_string::string s2(s1);

	my_string::string s3(move(s1));
	return 0;
}

在这里插入图片描述
这里在移动构造s3后,s1内部的资源会被搬空。

在这里插入图片描述

完美转发

右值引用变量在接收一个右值之后,它的属性没有保持和右值一样,而是一个左值的属性,如果我们想在参数的传递过程中一直保持右值属性,就需要学习完美转发。

完美转发所依赖的语法之一万能引用:

template<class T>
  void forward(T&& x)
 {
 }

模板类型T+&&,所构成的类型T&&叫做万能引用,它既能接收左值又能接收右值,但不能维持它们原先的属性(const、左值\右值)。

下面一段代码来验证我们的结论:

#include<iostream>

using namespace std;

void Func(int& x) 
{
	cout << "左值引用" << endl; 
}
void Func(const int& x)
{
	cout << "const 左值引用" << endl;
}

void Func(int&& x) 
{
	cout << "右值引用" << endl;
}
void Func(const int&& x) 
{
	cout << "const 右值引用" << endl;
}
template<class T>
void forward_(T&& x)
{
	Func(x);//转发
 }

int main()
{
	int num = 3;
	const int& l1  = num;
	int& l2 = num;
	forward_(l1);
	forward_(l2);
	forward_(move(l2));
	forward_(move(l1));
	return 0;
}

运行结果:

在这里插入图片描述

当我们使用完美转发之后,就可以保持变量对应的属性了:

#include<iostream>

using namespace std;

void Func(int& x) 
{
	cout << "左值引用" << endl; 
}
void Func(const int& x)
{
	cout << "const 左值引用" << endl;
}

void Func(int&& x) 
{
	cout << "右值引用" << endl;
}
void Func(const int&& x) 
{
	cout << "const 右值引用" << endl;
}
template<class T>
void forward_(T&& x)
{
	Func(forward<T>(x));//转发
 }

int main()
{
	int num = 3;
	const int& l1  = num;
	int& l2 = num;
	forward_(l1);
	forward_(l2);
	forward_(move(l2));
	forward_(move(l1));
	return 0;
}

运行结果:

在这里插入图片描述

完美转发的应用场景

C++给一些模板类的push_backinsert等函数增加了右值引用版本,这样也可以减少容器中T类型的深拷贝,因为T可能是string这种需要深拷贝的类型。右值传给万能引用参数之后,又变成左值了,这个时候,如果我们还要赋值或者调用其它的函数,需要保持其原有属性才能减少拷贝,这个时候可以使用完美转发或者move,但是使用move要注意,使用move后你的最开始的那个拥有资源的变量,就不安全了,因为它的资源有被窃取的风险,所以要少使用move
在这里插入图片描述
在这里插入图片描述

万能引用

万能引用作为函数形式参数,既可以接收左值,又可以接收右值。为了把它和单纯的模板参数的右值引用区分,通常有如下规定:

  1. 万能引用必须要在函数方法前加模板参数,无论这个模板参数有没有出现,否则就是右值引用,不是万能引用:
#include"string.h"
#include<iostream>
using namespace std;

template<class T>
class A
{
public:
	void Func(T&& x)//普通右值引用
	{
		cout << "void Func(T&& x)" << endl;
	}
};
int main()
{
	my_string::string s1 = "xxxxxx";
	A<my_string::string>().Func(s1);
	A<my_string::string>().Func(my_string::string("ssssss"));
	return 0;
}

Func函数此时是右值引用:

在这里插入图片描述

  1. 在函数名前面加上模板参数才是万能引用:
#include"string.h"
#include<iostream>
using namespace std;

template<class T>
class A
{
public:
	template<class T>
	void Func(T&& x)//万能引用
	{
		cout << "void Func(T&& x)" << endl;
	}
};
int main()
{
	my_string::string s1 = "xxxxxx";
	A<my_string::string>().Func(s1);
	A<my_string::string>().Func(my_string::string("ssssss"));
	return 0;
}

在这里插入图片描述

  1. 当万能引用和const 左值引用作为函数重载的不同参数出现,编译器会去调用更符合要求的函数:
#include"string.h"
#include<iostream>
using namespace std;

template<class T>
class A
{
public:

	void Func(const T& x)
	{
		cout << "void Func(const T & x)" << endl;
	}
	template<class T>
	void Func(T&& x)
	{
		cout << "void Func(T&& x)" << endl;
	}
};
int main()
{
	my_string::string s1 = "xxxxxx";
	A<my_string::string>().Func(s1);
	return 0;
}

运行结果:

在这里插入图片描述
如果加上const就会去调用前者,因为它更加符合要求:

在这里插入图片描述

  • 本人知识、能力有限,若有错漏,烦请指正,非常非常感谢!!!
  • 转发或者引用需标明来源。

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

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

相关文章

【CSS、JS】监听transitionend多次触发的原因

现有代码如下&#xff0c;移入红色内容区域触发动画&#xff0c;监听动画触发&#xff0c;但是每次触发控制台会打印4次 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content…

低功耗4G模组采集温湿度传感器数据~ 超全教程!不会的小伙伴看这篇!

在物联网&#xff08;IoT&#xff09;快速发展的今天&#xff0c;温湿度传感器作为环境监测的重要设备&#xff0c;被广泛应用于农业、工业、智慧城市等领域。本文将详细介绍如何使用低功耗4G模组Air780E采集温湿度传感器数据并实现网页查看&#xff0c;帮助初学者快速上手。 一…

springboot汉妆养生会馆网站-计算机毕业设计源码96229

目录 摘要 Abstract 1 绪论 1.1选题背景 1.2研究意义 1.3系统开发目标 2相关技术介绍 2.1 Java编程语言 2.2 B/S模式 2.3 MySQL简介 2.4 SpringBoot框架 3.汉妆养生会馆网站的设计与实现系统分析 3.1 可行性分析 3.1.1技术可行性分析 3.1.2经济可行性分析 3.1.3…

canvas小蜘蛛

一. 效果 二. 代码 <!--* Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git* Date: 2024-10-2…

【视频混剪Demo】FFmpeg的使用【Windows】

#1024程序员节 | 征文# 目录 一、简介 二、音频素材页 2.1 功能描述 &#x1f449; 搜索 &#x1f449; 添加 &#x1f449; 删除 2.2 效果展示 2.3 代码实现 &#x1f449; 前端 &#x1f449; 后端 三、视频素材页 3.1 功能描述 &#x1f449; 搜索 &#x1…

【2024CANN训练营第二季】使用华为云体验AscendC_Sample仓算子运行

环境介绍 NPU&#xff1a;Ascend910B2 环境准备 创建Notebook 华为云选择&#xff1a;【控制台】-【ModelArts】 ModelArts主页选择【开发生产】-【开发空间】-【Notebook】 页面右上角选择【创建Notebook】 选择资源 主要参数 规格&#xff1a;Ascend: 1*ascend-snt…

微搭低代码学习1:不同页面传递值

这个系列逐渐学习低代码平台&#xff0c;补足因为技术栈不足带来的问题&#xff0c;同时借助低代码平台快速搭建成型的系统。 这个博客用来记录一个非常常见的操作&#xff0c;在两个页面/多个页面之间传递值 文章目录 1. 创建页面2. 添加逻辑主动跳转页逻辑设置数据接收页逻辑…

【数据结构与算法】之栈详解

栈&#xff08;Stack&#xff09;是一种基本的线性数据结构&#xff0c;遵循后进先出、先进后出的原则。本文将更详细地介绍栈的概念、特点、Java 实现以及应用场景。 1. 栈概念概述 想象一摞叠放的盘子&#xff0c;你只能从最上面取盘子&#xff0c;放盘子也只能放在最上面。…

html和css实现页面

任务4 html文件 任务5 htm文件 css文件 任务6 html文件 css文件 任务7 html文件 css文件

工业交换机的电源类型

工业交换机的电源通常有以下几种类型和注意事项&#xff1a; 1. 电源类型&#xff1a; 交流电源&#xff08;AC&#xff09;&#xff1a;一些工业交换机使用标准的AC电源&#xff0c;通常是110V或220V。适用于有稳定电源环境的场合。 直流电源&#xff08;DC&#xff09;&#…

javaWeb项目-ssm+jsp大学生校园兼职系统功能介绍

本项目源码&#xff08;点击下方链接下载&#xff09;&#xff1a;java-ssmjsp大学生校园兼职系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#x…

使用Selenium时,如何模拟正常用户行为?

Selenium作为自动化测试和网页数据抓取的利器&#xff0c;被广泛应用于自动化网页交互、爬虫开发等领域。然而&#xff0c;随着网站反爬虫技术的不断升级&#xff0c;简单的自动化脚本很容易被识别和阻止。因此&#xff0c;模拟正常用户行为&#xff0c;降低被检测的风险&#…

springmvc+jdk1.8升级到springboot3+jdk17(实战)

1.查找springboot3官方要求 这里查的是springboot 3.2.6版本的 2.升级jdk到17 Java EE 8之后&#xff0c;Oracle在19年把javax捐给了eclipse基会&#xff0c;但不允许使用javax的命名空间&#xff0c;所以eclipse才发展成为现在的Jakarta ee标准。Springboot3后使用Jakarta a…

HTML简单版的体育新闻案例

代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document</title> &l…

使用QT绘图控件QCustomPlot绘制波形图

使用QT绘图控件QCustomPlot绘制波形图 下载QCustomPlot 下载QCustomPlot,链接路径 解压之后就能看到源代码了 在Qt中添加QCustomPlot的帮助文档 在Qt Creator的菜单:工具–>选项–>帮助–>文档–>添加qcustomplot\documentation\qcustomplot.qch文件。

windbg调试exedump步骤,技巧总结

所有信息参考官方文档&#xff1a;开始使用 WinDbg&#xff08;用户模式&#xff09; - Windows drivers | Microsoft Learn 需要着重关注的标签页如下&#xff1a; 用户模式&#xff08;入门&#xff09; 命令摘要 Help 菜单上的命令 Contents.sympath&#xff08;设置符号…

解锁PDF权限密码

目录 背景: 定义与功能&#xff1a; 过程&#xff1a; 主要功能&#xff1a; 使用方式&#xff1a; 使用限制&#xff1a; 注意事项&#xff1a; 总结&#xff1a; 背景: 前段时间自己设置了PDF文件的许可口令&#xff0c;忘了口令导致自己无法编辑内容等&#xff0c;这…

OpenCV和HALCON

OpenCV和HALCON是两种广泛用于图像处理和计算机视觉的开发库&#xff0c;它们各有优缺点&#xff0c;适合不同的应用场景。以下是两者的比较&#xff1a; 1. 开发背景与定位 OpenCV (Open Source Computer Vision Library)&#xff1a; 开源库&#xff0c;最初由Intel开发&…

Matlab中计算道路曲率的几种方法

我使用Prescan采集到的道路中心线数据&#xff0c;都是离散点&#xff08;x&#xff0c;y&#xff0c;z&#xff09;&#xff0c;但在作研究时&#xff0c;通常都是道路曲率&#xff0c;这时需要将离散点坐标转换为曲率&#xff0c;但通过计算得到的曲率与实际曲率有一些误差&a…

sentinel原理源码分析系列(八)-熔断

限流为了防止过度使用资源造成系统不稳&#xff0c;熔断是为了识别出”坏”资源&#xff0c;避免好的资源受牵连(雪崩效应)&#xff0c;是保证系统稳定性的关键&#xff0c;也是资源有效使用的关键&#xff0c;sentinel熔断插槽名称Degrade(降级)&#xff0c;本人觉得应该改为熔…