【C++】模板进阶--保姆级解析(什么是非类型模板参数?什么是模板的特化?模板的特化如何应用?)

目录

一、前言

二、什么是C++模板? 

💦泛型编程的思想  

💦C++模板的分类 

三、非类型模板参数

⚡问题引入⚡   

⚡非类型模板参数的使用⚡    

🔥非类型模板参数的定义

🔥非类型模板参数的两种类型

🔥非类型模板参数的使用规则

⚡问题的解决⚡   

⚡非类型模板参数的实例应用⚡    

四、模板的特化 

💧 概念

💧 函数模板特化 

💧 类模板特化 

🔥全特化🔥

🔥偏特化🔥 

💧模板特化的应用示例 

五、总结 

六、共勉 


一、前言

       在我们学习C++时,常会用到函数重载。而函数重载,通常会需要我们编写较为重复的代码,这就显得臃肿,且效率低下重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数。此外,代码的可维护性比较低,一个出错可能会导致所有的重载均出错。

       那么,模板的出现,就让这些问题有了解决方案,在之前的文章中已经详细的讲解了C++的 ----- 模板初阶,所以本次博客将为大家详细的讲解C++的模板进阶!!

二、什么是C++模板? 

        程序设计中经常会用到一些程序实体它们的实现和所完成的功能基本相同不同的仅 仅是所涉及的数据类型不同而模板正是一种专门处理不同数据类型的机制


       模板------是泛型程序设计的基础(泛型generic type——通用类型之意)
 

        函数、类以及类继承为程序的代码复用提供了基本手段,还有一种代码复用途径——类属类型(泛型)利用它可以给一段代码设置一些取值为类型的参数(注意:这些参数 的值是类型,而不是某类型的数据),通过给这些参数提供一些类型来得到针对不同类 型的代码。

💦泛型编程的思想  

  首先我们来看一下下面这三个函数,如果学习过了C++函数重载 和 C++引用 的话,就可以知道下面这三个函数是可以共存的,而且传值会很方便 

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

但是真的很方便吗?这里只有三种类型的数据需要交换,若是我们需要增加交换的数据呢?再CV然后写一个函数吗?
这肯定是不现实的,所以很明显函数重载虽然可以实现,但是有一下几个不好的地方: 

  • 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  • 代码的可维护性比较低,一个出错可能所有的重载均出错

那是否能做到这么一点,告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码 


 ⭐总结: 

        所以,总结上面的这么一个技术,C++的祖师爷呢就想到了【模版】这个东西,告诉编译器一个模子,然后其余的工作交给它来完成,根据不同的需求生成不同的代码 

 这就是👉泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础


💦C++模板的分类 

1️⃣: 函数模板(function tempalte):使用泛型参数的函数(function with generic parameters)
2️⃣:类模板(class template):使用泛型参数的类(class with generic parameters) 

更加具体 模板初阶知识 大家这一去看看之前的文章 ----- 模板初阶

本篇文章主要讲解 模板的高阶操作:非类型模板参数、全特化、偏特化等,以及关于模板声明与定义不能分离(在两个不同的文件中)的问题。


三、非类型模板参数

     之前所使用的模板参数都是用来匹配不同的类型,如 intdoubleDate 等,模板参数除了可以匹配类型外,还可以匹配常量(非类型),完成如数组、位图等结构的大小确定 

⚡问题引入⚡   


 问题: 

        假设我现在自定义了一个静态栈,栈的大小设置为100。然后我构建了一个int 的类型的栈st1,和一个double 类型的栈st2。那么我希望stl 的大小为100,st2 的大小为500,能不能实现呢?

 --------------  肯定是不能的! ! !


 如下面这个例子所示: 

#define N 100

// 静态栈
template<class T>
class Stack
{
private:
	int _a[N];
	int _top;
};

int main()
{
	Stack<int> st1;
	Stack<double> st1;

	return 0;
}

  • 从上图 可以发现,栈 的两个对象 的大小都为 100。 

 那有什么办法 可以解决这个问题呢?

这个时候,就要引出 ---- 非类型模板参数 


⚡非类型模板参数的使用⚡    

 我们知道模板参数分为 : 类型形参非类型形参 

  • 类型模板形参 : 出现在模板参数列表中,跟在 class 或者 typename 类之后的参数类型名称。
template <class T>  // T 为模板参数中的 ---------- 类型模板形参
  • 非类型模板形参 : 就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template <size_t N>  // N 为模板参数中的 ------- 非类型模板形参

🔥非类型模板参数的定义

在定义模板参数时,可以不再使用 class 或 typename,而是直接使用具体的类型,如 size_t,此时称为 非类型模板参数 

注:非类型模板参数必须为常量,即在编译阶段确定值 


🔥非类型模板参数的两种类型

1️⃣: 利用 非类型模板参数 定义一个大小可以自由调整的 整型数组 类 

template<size_t N>
class arr
{
public:
    T& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}
	size_t size() const
	{
		return N;
	}

private:
	int _arr[N];	//创建大小为 N 的整型数组
};

int main()
{
	arr<10> a1;   // 大小为 10
	arr<20> a2;   // 大小为 20
	arr<100> a3;  // 大小为 100

	cout << "a1.size(): " << a1.size() << endl;
	cout << "a2.size(): " << a2.size() << endl;
	cout << "a3.size(): " << a3.size() << endl;
}


2️⃣:可以再加入一个模板参数:类型,此时就可以得到一个 泛型、大小可自定义 的数组 

template<class T, size_t N>
class arr
{
public:
    T& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}
	size_t size() const
	{
		return N;
	}

private:
	int _arr[N];	//创建大小为 N 的整型数组
};

int main()
{
	arr<int , 10> a1;   // 大小为 10
	arr<double , 20> a2;   // 大小为 20
	arr<char , 100> a3;  // 大小为 100

	// 输出它们的 类型
	cout << typeid(a1).name() << endl;
	cout << typeid(a2).name() << endl;
	cout << typeid(a3).name() << endl;
}


非类型模板参数支持缺省,因此写成这样也是合法的 

template<class T, size_t N = 10>	//缺省大小为10

🔥非类型模板参数的使用规则

 非类型模板参数要求类型为 整型家族,其他类型是不行的

  • 比如下面这些 非类型模板参数 都是标准之内的 
//整型家族(部分)
template<class T, int N>
class arr1 { /*……*/ };

template<class T, long N>
class arr2 { /*……*/ };

template<class T, char N>
class arr3 { /*……*/ };
  • 而一旦使用其他家族类型作为 非类型模板参数,就会引发报错 
//浮点型,非标准
template<class T, double N>
class arr4 { /*……*/ };


 因此可以总结出,非类型模板参数 的使用要求为

  • 只能将 整型家族 类型作为非类型模板参数,其他类型不在标准之内
  • 非类型模板参数必须为常量(不可被修改),且需要在编译阶段确定结果

整型家族:charshortboolintlonglong long 等


⚡问题的解决⚡   

      此时我们已经知道的非类型模板参数的用法,只需要给上面的栈添加非类型模板参数,这样就实现了 st1st2 构造不同的大小。

// 静态栈
template<class T, size_t N>
class Stack
{
private:
	int _a[N];
	int _top;
};

int main()
{
	Stack<int, 100> st1;
	Stack<double, 500> st2;

	return 0;
}


⚡非类型模板参数的实例应用⚡    

在 C++11 标准中,引入了一个新容器 array,它就使用了 非类型模板参数,为一个真正意义上的 泛型数组,这个数组是用来对标传统数组的 

array 的第二个模板参数就是 非类型模板参数 

#include <iostream>
#include <cassert>
#include <array>

using namespace std;

int main()
{
	int arrOld[10] = { 0 };	//传统数组
	array<int, 10> arrNew;	//新标准中的数组

	//与传统数组一样,新数组并没有进行初始化
	//新数组对于越界读、写检查更为严格

	arrOld[15];	//老数组越界读,未报错
	arrNew[15];	//新数组则会报错

	arrOld[12] = 0;	//老数组越界写,不报错,出现严重的内存问题
	arrNew[12] = 10;	//新数组严格检查
	return 0;
}

array 是泛型编程思想中的产物,支持了许多 STL 容器的功能,比如 迭代器 和 运算符重载 等实用功能,最主要的改进是 严格检查越界行为 

实际开发中,很少使用 array,因为它对标传统数组,连初始化都没有,vector 在功能和实用性上可以全面碾压,并且 array 使用的是 栈区 上的空间,存在栈溢出问题,可以说 array 是一个鸡肋的容器 


四、模板的特化 

💧 概念

通常情况下,模板可以帮我们实现一些与类型无关的代码,但在某些场景中,【泛型】无法满足调用方的精准需求,此时会引发错误。

  • 比如使用 日期类 ,实现一个专门用来进行小于比较的函数模板
// 日期类
class Date
{
	//友元函数
	friend std::ostream& operator<<(std::ostream& out, const Date& d); // 标准流输出 --> printf
	friend std::istream& operator>>(std::istream& in, Date& d); // 标准流插入 --> scanf

public:
	// 构造函数
	Date(int year = 1970,int month = 1,int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	// 运算符重载
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	// 打印日期
	void Printf()
	{
		cout << _year << " / " << _month << " / " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 流插入
std::ostream& operator<<(std::ostream& out, const Date& d) {
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

// 流提取
std::istream& operator>>(std::istream& in, Date& d) {
	in >> d._year >> d._month >> d._day;
	return in;
}

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	cout << Less(1, 2) << endl;

	Date d1(2024, 7, 6);
	Date d2(2024, 7, 8);

	cout << Less(d1, d2) << endl;

	return 0;
}
  • 可以看到,不管是内置类型,还是自己实现的日期类,都可以通过Less函数模板来比较大小,而且结果都是正确的 

  • 那如果我们要比较指针类型呢? 
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{

	Date* p1 = new Date(2024, 7, 6);
	Date* p2 = new Date(2024, 7, 8);

	cout << Less(p1, p2) << endl; 

	return 0;
}
  • 我们运行发现,结果是正确滴呀,6 确实小于 8 哦! 

  • 如果我们再运行一次,可以看到,竟然变成了 0 了,也就是说 6 小于 8 为 false !!

  •  也就是说,Less 绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果

上述示例中,p1 指向的对象显然小于p2指向的对象但是Less 内部并没有比较p1和p2指向的对象内容,而比较的是pl和p2指针地址,这就无法达到预期,而错误。 

此时,就需要对 -------------- 模板进行特化处理

即 : 在原模板类的基础上 , 针对特殊类型所进行特殊化的实现方式。

模板特化中分为 函数模板特化 类模板特化。 


💧 函数模板特化 

 函数模板的特化步骤:

  • 必须要先有一个基础的函数模板
  • 关键字 template 后面接 一对空的尖括号<>
  • 『函数名后跟一对尖括号』,尖括号中指定需要特化的类型
  • 函数形参表:必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一 些奇怪的错误。

 代码示例

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

int main()
{
	Date* p1 = new Date(2024, 7, 6);
	Date* p2 = new Date(2024, 7, 8);
	cout << Less(p1, p2) << endl;

	Date* p3 = new Date(2024, 7, 8);
	Date* p4 = new Date(2022, 7, 6);
	cout << Less(p3, p4) << endl;

	return 0;
}
  •  此时,就会调用特化之后的版本,而不走模板生成得啦!

注意:般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给 

bool Less(Date* left, Date* right)
{
	return *left < *right;
}

该种实现简单明了,代码的可读性高,容易泻。因为对于一些参数类型复 杂的函数模板,特化时才会特别给出,因此函数模板不建议特 


💧 类模板特化 

       模板特化主要用在 --- 类模板,它可以在泛型思想之上解决大部分特殊问题,并且类模板特化还可以分为:全特化偏特化适用于不同场景

🔥全特化🔥

全特化指 将所有的模板参数特化为具体类型,将模板全特化后,调用时,会优先选择更为匹配的模板类。
简单一点来说:全特化 就是将模板参数列表中 所有的参数都确定话

全特化的特化步骤: 

  • 首先必须要有一个基础的类模板 
  • 关键字template后接一对空的尖括号<>
  • 『类名后跟一对尖括号』,尖括号中指定需要特化的类型

 假设有下面这样一个 Data 类,我希望 构造函数 打印出来的 d2 对象里面 Tl int T2 是 double,有什么办法吗?

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

int main()
{
	Data<int, int> d1;
	Data<int, double> d2;

	return 0;
}

我们实例化 dl d2 对象时,编译器会自动调用其默认构造函数,当我们打印的时候,可以看到实际上d2 对象里面还是 T1T2  并不是我们想要的 int double。  

那么这个时候 ,我们就可以对 T1T2 进行模板的特化

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};


// 全特化
template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
private:
	int _d1;
	double _d2;
};

int main()
{
	Data<int, int> d1;
	Data<int, double> d2;

	return 0;
}

当我们运行以后,可以看到 d2 对象就去调用刚刚写好的特化类模板 


 总结: 

对模板进行全特化处理后,实际调用时,会优先选择已经特化并且类型符合的模板这就好比虽然你家冰箱里有菜,但你还是想点外卖,因为外卖对于你来说更加合适 


🔥偏特化🔥 

偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本。

偏特化又可分为以下两种表现形式:『 部分特化』、『 参数更进一步的限制』

偏特化的特化步骤: 

  • 首先必须要有一个基础的类模板 
  • 关键字template后接一对尖括号,尖括号中指定特定类型
  • 『类名后跟一对尖括号』,尖括号中指定需要特化的类型

『 部分特化』: 

将模板参数类表中的一部分参数特化。 

比如我们对 Tl 类型进行特化处理,固定其类型为 double 

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

// 部分特化 -- 将第一个参数特化为double
template<class T2>
class Data<double, T2>
{
public:
	Data()
	{
		cout << "Data<double, T2>" << endl;
	}
private:
	double _d1;
	T2 _d2;
};

int main()
{
	Data<int, int> _d1;
	Data<double, double> _d2;
	Data<double, char> _d3;

	return 0;
}

 可以看到,当我们指定T1 为 double 的时候,才会调用这个部分特化的类模板。


 『 参数更进一步的限制』

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限 制所设计出来的一个特化版本。 

// 基础模板
template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

// 部分特化 -- 将第一个参数特化为double
template<class T2>
class Data<double, T2>
{
public:
	Data()
	{
		cout << "Data<double, T2>" << endl;
	}
private:
	double _d1;
	T2 _d2;
};

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() 
	{ 
		cout << "Data<T1*, T2*>" << endl; 
	}

private:
	T1 _d1;
	T2 _d2;
};

//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}

private:
	const T1& _d1;
	const T2& _d2;
};

// 主函数
int main()
{
	Data<int, int> d1; // 调用基础的版本

	Data<double, double> d2; // 调用部分特化的double版本

	Data<int*, int*> d3; // 调用特化的指针版本

	Data<int&, int&> d4(2, 4); // 调用特化的引用版本

	return 0;
}

运行以后可以看到,当我们实例化的对象为指针类型或者引用类型的时候,就会去调用这两个特化模板。 


💧模板特化的应用示例 

 我们还是拿日期类来举例,假设我现在要对3个实例化对象进行排序


// Less模板 --- 比较小于
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

int main()
{
	Date d1(2024, 7, 7);
	Date d2(2024, 7, 6);
	Date d3(2024, 7, 8);

	vector<Date> v1;
	v1.push_back(d1);
	v1.push_back(d2);
	v1.push_back(d3);

	// 排序
	sort(v1.begin(), v1.end(), Less<Date>());

	// 打印
	for (auto e : v1)
	{
		cout << e;
	}

	return 0;
}
  •  可以看到,此时是能直接排序的,结果是日期升序。

 那 如果我将 vector 里面存放的是 Date* 类型的数据,还能排序吗?

// Less模板 --- 比较小于
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

int main()
{
	Date d1(2024, 7, 7);
	Date d2(2024, 7, 6);
	Date d3(2024, 7, 8);

	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);

	// 排序
	sort(v2.begin(), v2.end(), Less<Date*>());

	// 打印
	for (auto e : v2) {
		cout << *e << endl;
	}

	return 0;
}

 因为,v2 当中存放的地址,所以我们打印的时候要解引用,打印以后看到,日期还不是升序呀,那么我们排序的到底是什么呢?

如果我们不解引用,直接打印v2的每个元素可以看到,v2 中放的地址是升序的。因为此处需要在排序过程中,让sort 比较v2中存放地址指向的日期对象,但是走了Less模板,sort 在排序时实际比较的是v2中指针的地址,因此无法达到预期。

通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。 

因为: sort 最终按照Less模板中的方式比较,所以只会比较指针,而不是比较指针指向空间中内容。

那么此时可以使用类版本特化来处理上述问题: 

全特化处理: 

// Less模板 --- 比较小于
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
	bool operator()(Date* x, Date* y) const
	{
		return *x < *y;
	}
};

int main()
{
	Date d1(2024, 7, 7);
	Date d2(2024, 7, 6);
	Date d3(2024, 7, 8);

	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);

	// 排序
	sort(v2.begin(), v2.end(), Less<Date*>());

	// 打印
	for (auto e : v2) {
		cout << *e;
	}

	return 0;
}

 特化之后,再运行就可以得到正确的排序结果了

偏特化处理: 

// Less模板 --- 比较小于
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};


//偏特化后的比较模板
template<class T>
class Less<T*>
{
public:
	bool operator()(T* x, T* y) const
	{
		return *x < *y;
	}
};

int main()
{
	Date d1(2024, 7, 7);
	Date d2(2024, 7, 6);
	Date d3(2024, 7, 8);

	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);

	// 排序
	sort(v2.begin(), v2.end(), Less<Date*>());

	// 打印
	for (auto e : v2) {
		cout << *e;
	}

	return 0;
}

  特化之后,再运行就可以得到正确的排序结果了


五、总结 

模板是 STL 的基础支撑,假若没有模板、没有泛型编程思想,那么恐怕 "STL" 会变得非常大 

模板的优点 

  • 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  • 增强了代码的灵活性

模板的缺点 

  • 模板会导致代码膨胀问题,也会导致编译时间变长
  • 出现模板编译错误时,错误信息非常凌乱,不易定位错误

总之,模板 是一把双刃剑,既有优点,也有缺点,只有把它用好了,才能使代码 更灵活、更优雅 


六、共勉 

以下就是我对 【模板进阶】 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 C++ 的理解,请持续关注我哦!!!    

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

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

相关文章

使用 ESP32-WROOM + DHT11 做个无屏温湿度计

最近梅雨天&#xff0c;有个房间湿度很大&#xff0c;而我需要远程查看温湿度&#xff0c;所以无所谓有没有显示屏&#xff0c;某宝上的温湿度计都是带屏的&#xff0c;如果连WIFI查看温湿度操作也比较麻烦&#xff0c;还需要换电池&#xff0c;实在不能满足我的需求&#xff0…

剖析DeFi交易产品之UniswapV3:交易路由合约

本文首发于公众号&#xff1a;Keegan小钢 SwapRouter 合约封装了面向用户的交易接口&#xff0c;但不再像 UniswapV2Router 一样根据不同交易场景拆分为了那么多函数&#xff0c;UniswapV3 的 SwapRouter 核心就只有 4 个交易函数&#xff1a; exactInputSingle&#xff1a;指…

Vue进阶(四十五)Jest集成指南

文章目录 一、前言二、环境检测三、集成问题汇总四、拓展阅读 一、前言 在前期博文《Vue进阶&#xff08;八十八&#xff09;Jest》中&#xff0c;讲解了Jest基本用法及应用示例。一切顺利的话&#xff0c;按照文档集成应用即可&#xff0c;但是集成过程中遇到的问题可能五花八…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第55课-芝麻开门(语音 识别 控制3D纪念馆开门 和 关门)

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第55课-芝麻开门&#xff08;语音识别控制3D纪念馆开门和关门&#xff09; 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtn…

KVM使用命令行添加新磁盘(注:支持热插拔)

1、使用qemu-img创建格式为qcow2的磁盘 [rootkvm ~]# qemu-img create -f qcow2 /var/lib/libvirt/images/test-disk.qcow2 15G 2、显示虚拟机硬盘列表&#xff0c;查看未使用的target [rootkvm ~]# virsh domblklist kvm-client 3、添加硬盘到kvm-client虚拟机中 [rootkvm…

SpringBoot | 大新闻项目后端(redis优化登录)

该项目的前篇内容的使用jwt令牌实现登录认证&#xff0c;使用Md5加密实现注册&#xff0c;在上一篇&#xff1a;http://t.csdnimg.cn/vn3rB 该篇主要内容&#xff1a;redis优化登录和ThreadLocal提供线程局部变量&#xff0c;以及该大新闻项目的主要代码。 redis优化登录 其实…

html+css+js图片手动轮播

源代码在界面图片后面 轮播演示用的几张图片是Bing上的&#xff0c;直接用的几张图片的URL&#xff0c;谁加载可能需要等一下&#xff0c;现实中替换成自己的图片即可 关注一下点个赞吧&#x1f604; 谢谢大佬 界面图片 源代码 <!DOCTYPE html> <html lang&quo…

C++继承初识

一。继承 1.继承本质是复用相同的代码&#xff08;属性&#xff09; 2.格式&#xff1a;class 类名&#xff1a;继承方式 父类 3.继承方式的规律&#xff1a; 父类的&#xff1a; 对于私有成员&#xff0c;不管哪种继承方式都不可见--->不想被子类继承的成员 对于保护…

代码随想录——划分字母区间(Leetcode763)

题目链接 贪心 class Solution {public List<Integer> partitionLabels(String s) {int[] count new int[27];Arrays.fill(count,0);// 统计元素最后一次出现的位置for(int i 0; i < s.length(); i){count[s.charAt(i) - a] i;}List<Integer> res new Ar…

非对称加密算法原理与应用2——RSA私钥加密文件

作者:私语茶馆 1.相关章节 (1)非对称加密算法原理与应用1——秘钥的生成-CSDN博客 第一章节讲述的是创建秘钥对,并将公钥和私钥导出为文件格式存储。 本章节继续讲如何利用私钥加密内容,包括从密钥库或文件中读取私钥,并用RSA算法加密文件和String。 2.私钥加密的概述…

JDK都出到20多了,你还不会使用JDK8的Stream流写代码吗?

目录 前言 Stream流 是什么&#xff1f; 为什么要用Steam流 常见stream流使用案例 映射 map() & 集合 collect() 单字段映射 多字段映射 映射为其他的对象 映射为 Map 去重 distinct() 过滤 filter() Stream流的其他方法 使用Stream流的弊端 前言 当你某天看…

深度学习模型加密python版本

支持加密的模型: # torch、torch script、onnx、tensorrt 、torch2trt、tensorflow、tensorflow2tensorrt、paddlepaddle、paddle2tensorrt 深度学习推理模型通常以文件的形式进行保存&#xff0c;相应的推理引擎通过读取模型文件并反序列化即可进行推理过程. 这样一来&#…

跨平台Ribbon UI组件QtitanRibbon全新发布v6.7.0——支持Qt 6.6.3

没有Microsoft在其办公解决方案中提供的界面&#xff0c;就无法想象现代应用程序&#xff0c;这个概念称为Ribbon UI&#xff0c;目前它是使应用程序与时俱进的主要属性。QtitanRibbon是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件&#xff0c;QtitanRibb…

vue3【实战】来回拖拽放置图片

效果预览 技术要点 img 标签默认就是可拖拽的&#xff08;a 标签也是&#xff09;事件 e 内的 dataTransfer 对象可用于临时存储事件过程中的数据拖拽事件的默认行为是用浏览器新开页签打开被拖拽对象&#xff0c;所以通常需要禁用默认的浏览器行为被拖拽元素必须设置 id&#…

拉曼光谱入门:2.拉曼光谱发展史、拉曼效应与试样温度的确定方法

1.拉曼光谱技术发展史 这里用简单的箭头与关键字来概括一下拉曼光谱技术的发展史 1928年&#xff1a;拉曼效应的发现 → 拉曼光谱术的初步应用20世纪40年代&#xff1a;红外光谱术的发展 → 拉曼光谱术的限制20世纪60年代&#xff1a;激光作为光源的引入 → 拉曼光谱术的性能提…

阿里云人工智能平台PAI部署开源大模型chatglm3之失败记录

想学习怎么部署大模型&#xff0c;跟着网上的帖子部署了一个星期&#xff0c;然而没有成功。失败的经历也是经历&#xff0c;记在这里。 我一共创建了3个实例来部署chatglm3&#xff0c;每个实例都是基于V100创建的&#xff08;当时没有A10可选了&#xff09;&#xff0c;其显…

数据库缓存管理

1. 简介 缓存管理器是数据库管理系统&#xff08;DBMS&#xff09;中负责管理内存中page并处理文件和索引管理器的page请求的组件。由于内存空间有限&#xff0c;我们不能将所有page存储在缓存池中。因此&#xff0c;缓存管理器需要制定替换策略&#xff0c;当空间填满时选择哪…

rider使用libman

问题 rider没有libman的相关功能&#xff0c;需要使用cli 安装Libman dotnet tool install -g Microsoft.Web.LibraryManager.Cli # 如果存在可以尝试更新 dotnet tool update -g Microsoft.Web.LibraryManager.Cli查看命令 libman --help初始化 cdnjs官网 libman init安…

【十三】图解 Spring 核心数据结构:BeanDefinition 其二

图解 Spring 核心数据结构&#xff1a;BeanDefinition 其二 概述 前面写过一篇相关文章作为开篇介绍了一下BeanDefinition&#xff0c;本篇将深入细节来向读者展示BeanDefinition的设计&#xff0c;让我们一起来揭开日常开发中使用的bean的神秘面纱&#xff0c;深入细节透彻理解…

CTFShow的RE题(三)

数学不及格 strtol 函数 long strtol(char str, char **endptr, int base); 将字符串转换为长整型 就是解这个方程组了 主要就是 v4, v9的关系&#xff0c; 3v9-(v10v11v12)62d10d4673 v4 v12 v11 v10 0x13A31412F8C 得到 3*v9v419D024E75FF(1773860189695) 重点&…