【C++】进一步认识模板

news2024/7/6 20:20:10

🏖️作者:@malloc不出对象
⛺专栏:C++的学习之路
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
      • 一、非类型模板参数
      • 二、模板的特化
        • 2.1 函数模板特化
        • 2.2 类模板的特化
          • 2.2.1 类模板的全特化
          • 2.2.2 类模板的偏特化
      • 三、模板分离编译
        • 3.1、为什么模板不支持分离编译
      • 四、模板总结


前言

本篇文章我们需要进一步了解模板的使用以及讲解为什么使用模板不能声明与定义分离的原因。

一、非类型模板参数

模板参数分为类型形参非类型形参

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

下面我们先来看看下面这个例子:

#include <iostream>
using namespace std;

#define N 10
template<class T>
class Array
{
public:

private:
	T _a[N];
};

int main()
{
	Array<int> a1;
	Array<double> a2;

	return 0;
}

使用#define宏替换我们可以创建一个长度为10任意类型的静态数组,但是如果我想让a1数组创建一个长度为10的静态数组,而a2为一个长度为100的静态数组,使用#define是完不成任务的。这时候就需要使用我们的非类型模板参数了,我们可以显式的去实例化任意长度的静态数组,下面我们一起来看看非类型模板参数的使用:

#include <iostream>
using namespace std;

template<class T, size_t N> // 非类型模板参数
class Array
{
public:

private:
	T _a[N];
};

int main()
{
	Array<int, 10> a1;
	Array<double, 20> a2;

	return 0;
}

在这里插入图片描述

下面我们继续来探究一下非类型模板参数的性质:

#include<iostream>
using namespace std;

template<class T, size_t N = 10>
void func(const T& a)
{
	int arr[N] = { 0 };
	for (size_t i = 0; i < N; i++)
	{
		arr[i] = a;
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main()
{
	func(1);

	return 0;
}

非类型模板参数除了可以用于类模板,当然也是可以用于函数模板的。

第一个性质:非类型参数的值是一个整型常量,它是一个固定大小的,不能被修改的。

在这里插入图片描述

第二个性质:非类型模板参数的类型一定是整型的,这是语法规定。

在这里插入图片描述

注:整型可以是整型家族的任意一种(long long、char、long、short、bool…)。


讲到非类型模板参数,这里我们顺便提及C++11中提出的一个容器array,它采用的就是非类型模板参数。

在这里插入图片描述

在这里插入图片描述

我们可以看到它的功能与vector很多都是类似的,也就是array能做到的vector也是一定可以做到的,那么C++11为何要提出这个容器呢?它有什么优势?

它主要其实不是与vector做对比,它对比的是静态数组,它在数组越界上是全面进行检查的,而静态数组是抽查的,在有些情况下是检查不到越界问题的,下面我们就来一起看看这个问题:

在这里插入图片描述

我们可以看到静态数组越界访问进行修改竟然也不会报错!!这就很离谱了,下面我们来看看使用模板类array的好处:

在这里插入图片描述

一旦数组越界访问了,我们立即能够检查出来,assert断言使程序崩溃,这是由于重载了运算符 [],在函数内部使用assert检查数组是否越界!!这就是相较于静态数组的优点所在,但是我们既然有了vector,我们其实没有理由去使用arrayvector能实现一切array能实现的功能,所以array其实是没有很大意义的,我们也很少去使用它!!

二、模板的特化

概念:通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,此时就需要特殊处理。

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

2.1 函数模板特化

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

下面我们来看一个简单的例子:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, 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);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

template<class T>  // 基础函数模板
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误

	return 0;
}

别看着有这么多代码,其实我们这里主要的目的就是使用函数模板来比较日期的大小罢了。我们来看看结果:

在这里插入图片描述

可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例三中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。

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

在这里插入图片描述

当然了这里的处理方式肯定是不局限于模板特化这一种方式的,这里我们只是特定的针对模板这一块的问题提出解决方法。实际上函数模板特化一般情况下我们其实是不推荐使用的,遇到不能处理或者处理有误的类型我们都是将该函数直接给出。

如上述问题我们直接在类外定义一个函数就完事了:

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

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

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

2.2 类模板的特化

我们先来看一个例子:

namespace curry
{
	class Date
	{
	public:
		Date(int year = 1900, 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);
		}
		friend ostream& operator<<(ostream& _cout, const Date& d)
		{
			_cout << d._year << "-" << d._month << "-" << d._day;
			return _cout;
		}
	private:
		int _year;
		int _month;
		int _day;
	};

	// 利用仿函数变小堆
	template<class T>
	struct less
	{
		// 仿函数
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};

	// 特化类模板
	template<>
	struct less<Date*>  
	{
		bool operator()(const Date* x, const Date* y)
		{
			return *x < *y;
		}
	};

	template<>
	struct greater<Date*>
	{
		bool operator()(const Date* x, const Date* y)
		{
			return *x > *y;
		}
	};

	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue   // 优先级队列
	{
	public:
		// 建大堆
		// 向上调整建堆
		void ajust_up(int child)
		{
			Compare com;  // com对象去调用仿函数

			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])  // 大堆,升序
				if (com(_con[parent], _con[child])) // 比小的
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		// 向下调整建堆
		void ajust_down(int parent)
		{
			size_t child = 2 * parent + 1;

			while (child < _con.size())
			{
				Compare com;
				//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child++;
				}
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);  // 插入元素向上调整建堆
			ajust_up(_con.size() - 1);
		}

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);  // 先交换栈顶元素,删除最后一个元素,再向下调整建堆
			_con.pop_back();
			ajust_down(0); // 向下调整堆,从根节点开始
		}

		const T& top()
		{
			return _con[0];
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}

	private:
		Container _con;
	};

	void test()
	{
		priority_queue<Date, vector<Date>> q1; // 大堆
		q1.push(Date(2018, 10, 29));
		q1.push(Date(2018, 10, 28));
		q1.push(Date(2018, 10, 30));
		cout << q1.top() << endl;

		priority_queue<Date, vector<Date>, greater<Date>> q2;
		q2.push(Date(2018, 10, 29));
		q2.push(Date(2018, 10, 28));
		q2.push(Date(2018, 10, 30));
		cout << q2.top() << endl;

		cout << "------------------------" << endl;

		priority_queue<Date*, vector<Date*>> q3; // 大堆
		q3.push(new Date(2018, 10, 29));
		q3.push(new Date(2018, 10, 28));
		q3.push(new Date(2018, 10, 30));
		cout << *(q3.top()) << endl;

		priority_queue<Date*, vector<Date*>, greater<Date*>> q4; // 小堆
		q4.push(new Date(2018, 10, 29));
		q4.push(new Date(2018, 10, 28));
		q4.push(new Date(2018, 10, 30));
		cout << *(q4.top()) << endl;
	}
}

在这里插入图片描述

2.2.1 类模板的全特化

全特化即是将模板参数列表中所有的参数都确定化,上述例子我们讲的就是全特化,下面我们继续来看看例子:

#include <iostream>
using namespace std;

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

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

int main()
{
	Data<int, char> d2;
	Data<int, int> d1;
	Data<double, int> d4;
	Data<short, int> d5;
	Data<double, double> d6;

	return 0;
}

在这里插入图片描述

从上图我们发现类模板全特化只能显式实例化出一种参数类型,而原类模板可以实例化所有参数类型。

2.2.2 类模板的偏特化

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

偏特化有以下两种表现方式:

部分特化

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

#include <iostream>
using namespace std;

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

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

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>" << endl; }
private:
	T1 _d1;
	int _d2;
};

int main()
{
	Data<int, char> d2;
	Data<int, int> d1;
	Data<int*, int> d3;
	Data<double, int> d4;
	Data<short, int> d5;
	Data<double, double> d6;

	return 0;
}

在这里插入图片描述

参数更进一步的限制

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

我们来看看下面这个例子:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, 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);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

// 原类模板
template<class T>
struct Less
{
	bool operator()(T& x, T& y)
	{
		return x < y;
	}
};

// 类模板全特化,只能特化出一份具体指针类型
template<>
struct Less<Date*>
{
	bool operator()(Date* x, Date* y)
	{
		return *x < *y;
	}
};

// 偏特化 -- 进一步的限制,针对指针这个泛类
template<class T>
struct Less<T*>
{
	bool operator()(T* x, T* y)
	{
		return *x < *y; 
	}
};

int main()
{
	Date d1(2023, 3, 26);
	Date d2(2023, 3, 27);
	cout << Less<Date>()(d1, d2) << endl;

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less<Date*>()(p1, p2) << endl;

	int* p3 = new int(1);
	int* p4 = new int(2);
	cout << Less<int*>()(p3, p4) << endl;

	return 0;
}

对于上述全特化类模板来说,它只能实例化出Date*这一种类模板,如果我们需要其他指针类型就得全特化多份类模板了,因此它是极其不方便的。偏特化类模板就解决了这一问题,它针对的是所有指针类型的日期类对象的比较!!所以说偏特化还是非常具有实际意义的。

下面我们再继续简单的看几个偏特化类模板的例子:

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

//两个参数全特化为int*, char*类型
template<>
class Data<int*, char*>
{
public:
	Data() { cout << "Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};

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

//两个参数偏特化为引用类型
template <class T1, class 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*, char*> d1;		// 调用全特化的int*, char*版本
	Data<int, double> d2;		// 调用基础的模板
	Data<int*, int*> d3;		// 调用偏特化的指针版本
	Data<int&, int&> d4(1, 2);	// 调用偏特化的引用版本

	return 0;
}

在这里插入图片描述


关于基础、偏特化以及全特化类(函数)模板的调用顺序其实很简单,有全特化
类(函数)模板就用全特化类(函数)模板,它就相当于一份现成的代码,针对的是个体;没有对应的全特化类(函数)模板就使用偏特化类(函数)模板,它就相当于一份半产品,针对的是一类;没有对应的全特化和偏特化类(函数)模板就使用基础类(函数)模板,它针对的是所有类型。

我们可以用下图来表示它们之间的关系:

在这里插入图片描述

三、模板分离编译

Q:什么是分离编译?

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

3.1、为什么模板不支持分离编译

在探究这个问题之前首先我们来看看模板分离编译产生的现象:

// test.h
template<class T>
T Add(const T& left, const T& right);

void func();


// test.cpp
#include "test.h"

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

void func()
{
	cout << "void func()" << endl;
}


// test_03_25.cpp
#include <iostream>
using namespace std;
#include "test.h"

int main()
{
	Add(1, 2);
	Add(1.1, 2.2);

	func();

	return 0;
}

在这里插入图片描述

下面我们来分析一下在test_03_25.cpp这个源文件中为什么找不到模板函数Add的地址,而找得到func函数的地址。

首先我们在C语言阶段就知道要生成可执行程序(.exe)需要经过预处理、编译、汇编、链接四个阶段,那么对于多个源文件来说我们生成了几个可执行程序(.exe)和目标文件(.obj)??

我首先给出结论:多个源文件会生成多个目标文件(.obj),而经过链接过程之后只会生成一个可执行程序(.exe)!!详细过程可以看看我的这篇博客。
那么在链接之前每个源文件都要经过预处理、编译、汇编这三个阶段最后生成各自的目标文件(.obj),也就是说在链接之前每个源文件是独立进行的,并未产生交互的行为。那么在test.cpp中对于Add函数模板来说它知道要实例化出什么类型的函数吗?因此Add函数根本没有进行实例化,没有实例化那么函数的地址自然是不知道的,而func函数是定义了的,所以test.obj中是没有Add函数地址,但是有func函数的地址;对于test_03_25.cpp来说它引用了test.h这个头文件,它会将test.h中的内容展开到本源文件中,在本源文件中我们声明了Add模板函数以及func函数,那么它们其实会生成一个无效的地址加入到符号表中的,这个仅仅是为了通过预处理、编译、汇编阶段而不会产生报错的行为,实际上在最后链接的过程我们还会对符号进行重定位操作,这个过程是选出有效的函数地址,最后才能进行符号表合并。在本源文件中确实找不到Add函数的实际地址,因此就发生了链接错误!!


那么我们找到了问题所在,本质原因就是因为Add函数没有在test.cpp这个源文件中进行实例化,那么我们就应该想办法让它实例化!!!

解决方案一:显式实例化

在这里插入图片描述

我们在test.cpp中显式声明了Add函数模板的类型,那么Add函数模板自然就能实例化出对应的函数,那么就有了相应的函数地址,由此就解决了问题。但是这个解决方法是不常用、不好用的,我们只能显式实例化一种类型,如果我还想使用多份类型不一致的参数类型,那我还是要显式声明多份函数,这样也就失去了模板泛型编程的意义了,那我们还不如直接定义多份函数呢!!所以这种解决方案我们是极其不推荐的。

第二种解决方案:将函数模板/类模板声明与定义全部放在一个头文件中,源文件直接引用这个头文件。

// test.h
#include <iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right);

void func();

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

void func()
{
	cout << "void func()" << endl;
}

// test.03_25.cpp
#include "test.h"

int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;
	func();

	return 0;
}

在这里插入图片描述

对于上述声明与定义全部放在头文件中,有些读者可能会问有时候我们直接在类中定义函数不就好了吗?为什么还要声明呢?

在类和对象中我提到过这个问题,成员函数如果在类中定义,编译器可能会将其当成内联函数处理,那么对于代码量多的成员函数当做内联函数展开的话那么是不是会造成代码膨胀啊,所以我们一般都是代码量小的成员函数在类中直接定义,而代码量大的成员函数先在类中声明,在类外实现!!

四、模板总结

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

缺点:
1.模板会导致代码膨胀问题,也会导致编译时间变长
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误(个人认为这个是现阶段对于我来说造成的最大问题;模板导致代码膨胀是由于内联函数的展开,而内联函数其实是一种理想的状态)。


关于模板其实还有挺多内容没讲到的,现阶段我们只需要大概了解它的一些基本使用场景就可以了。最后本篇文章的讲解就到这里了,如果有任何错处或者疑问欢迎大家评论区交流哦~~ 🙈 🙈

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

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

相关文章

Java学数据结构(3)——树Tree B树 红黑树 Java标准库中的集合Set与映射Map 使用多个映射Map的案例

目录 引出B树插入insert删除remove 红黑树(red black tree)自底向上的插入自顶向下红黑树自顶向下的删除 标准库中的集合Set与映射Map关于Set接口关于Map接口TreeSet类和TreeMap类的实现使用多个映射Map&#xff1a;一个词典的案例方案一&#xff1a;使用一个Map对象方案二&…

Leetcode.75 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sort 函数的情况下解决这…

[管理与领导-50]:IT基层管理者 - 8项核心技能 - 5 - 沟通是润滑剂

目录 前言&#xff1a; 一、什么是沟通 1.1 定义 1.2 沟通模型 1.3 沟通的六层次模型 1.4 为什么需要沟通 二、沟通的五维度 三、沟通的原则 3.1 以终为始 3.2 双赢思维&#xff1a;人们只会做对自己有利的事 3.3 牵善的思维 四、沟通的过程 五、沟通技巧 六、深…

统计Mysql库中每个表的总行数,解决table_rows不准确问题

1、拼接SQL selectsubstring( GROUP_CONCAT(a.sf SEPARATOR ),1,length(GROUP_CONCAT(a.sf SEPARATOR ))-10) as sql_str from( select concat(select ", TABLE_name , ", count(*) as row_num from , TABLE_SCHEMA, .,TABLE_name, union all ) as sf frominformat…

matlab使用教程(25)—常微分方程(ODE)选项

1.ODE 选项摘要 解算 ODE 经常要求微调参数、调整误差容限或向求解器传递附加信息。本主题说明如何指定选项以及每个选项与哪些微分方程求解器兼容。 1.1 选项语法 使用 odeset 函数创建 options 结构体&#xff0c;然后将其作为第四个输入参数传递给求解器。例如&#xff0…

支付宝的支付

对于前端的入门学习的人员来说&#xff0c;支付宝提供的沙箱环境&#xff0c;可以让你体验支付的整个流程。 一、沙箱环境 沙箱&#xff08;又叫沙盘&#xff09;环境是用于开发者测试的模拟环境&#xff0c;中间发生任何行为都是虚拟的&#xff0c;如支付。 二、技术选型 支…

一文800字从0到1运用工具Postman快速导出python接口测试脚本

Postman的脚本可以导出多种语言的脚本&#xff0c;方便二次维护开发。 Python的requests库&#xff0c;支持python2和python3&#xff0c;用于发送http/https请求 使用unittest进行接口自动化测试 一、环境准备 1、安装python&#xff08;使用python2或3都可以&#xff09; …

前端组件库造轮子——Input组件开发教程

前端组件库造轮子——Input组件开发教程 前言 本系列旨在记录前端组件库开发经验&#xff0c;我们的组件库项目目前已在Github开源&#xff0c;下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。 文章旨在总结经验&#xff0c;开源…

基于Java+SpringBoot+Vue前后端分离科研工作量管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

c语言练习题33: 判断回⽂字符串

判断回⽂字符串&#xff1a; 题目&#xff1a; 输⼊⼀个字符串&#xff0c;判断这个字符串是否是回⽂字符串&#xff08;字符串的⻓度⼩于等于30&#xff0c;字符串不包含空 格&#xff09;&#xff0c;如果是回⽂字符串输出Yes&#xff0c;如果不是回⽂字符串输出No。 //回…

Hugging Face Transformer 的APIs应用实例

拥抱面变压器 API 简要摘要 一、说明 Hugging Face 的变压器库提供了广泛的 API&#xff0c;可用于处理各种 NLP 任务的预训练变压器模型。在本教程中&#xff0c;我们将探讨主要 API 并提供示例来帮助你了解它们的用法。 二、导入模型 1. 分词器接口&#xff1a; 分词器 AP…

浅谈分布式共识算法概念与演进

分布式共识是指在分布式系统中&#xff0c;多个节点之间达成共识的过程。 分布式共识的意义在于确保分布式系统中各个节点之间的数据一致性。通过分布式共识算法&#xff0c;可以使得多个节点针对某个状态达成一致&#xff0c;从而保证系统中各个节点之间的数据一致性。这对于…

应知道的16个Python基础知识

列表推导式 # 列表推导式,用一行代码生成一个有规律的列表 # 列表推导式,用一行代码生成一个有规律的列表 import randomlist_comprehension =[i for i in range(10)] print(list_comprehension)list_comprehension2 =[(x,y)for x in range(4) for y in range(5,10)] print(…

手写Spring源码——实现一个简单的spring framework

这篇文章主要带大家实现一个简单的Spring框架&#xff0c;包含单例、多例bean的获取&#xff0c;依赖注入、懒加载等功能。 一、创建Java项目 首先&#xff0c;需要创建一个Java工程&#xff0c;名字就叫spring。 创建完成后&#xff0c;如下图&#xff0c;再依次创建三级包 二…

Linux系统编程系列之进程基础

一、什么是进程 关于进程的定义很多&#xff0c;这里讲一种比较直接的&#xff0c;进程就是程序中的代码和数据被加载到内存中运行的过程&#xff0c;就是程序的执行过程。进程是动态的&#xff0c;而程序是静态的。程序存储在硬盘里&#xff0c;进程只有在程序被执行后&#x…

生信分析Python实战练习 1 | 视频18

开源生信 Python教程 生信专用简明 Python 文字和视频教程 源码在&#xff1a;https://github.com/Tong-Chen/Bioinfo_course_python 目录 背景介绍 编程开篇为什么学习Python如何安装Python如何运行Python命令和脚本使用什么编辑器写Python脚本Python程序事例Python基本语法 数…

STM32 Cubemx配置串口收发

文章目录 前言注意事项Cubemx配置printf重定向修改工程属性修改源码 测试函数 前言 最近学到了串口收发&#xff0c;简单记录一下注意事项。 注意事项 Cubemx配置 以使用USART1为例。 USART1需配置成异步工作模式Asynchronous。 并且需要使能NVIC。 printf重定向 我偏向…

使用cgroup工具对服务器某些/全部用户进行计算资源限制

使用cgroup工具对服务器某些/全部用户进行计算资源限制 主要介绍&#xff0c;如何对指定/所有用户进行资源限定&#xff08;这里主要介绍cpu和内存占用限制&#xff09;&#xff0c;防止某些用户大量占用服务器计算资源&#xff0c;影响和挤占他人正常使用服务器。 安装cgrou…

Transformer代码计算过程全解

条件设置 batch_size1 src_len 8 # 源句子的最大长度 根据这个进行padding的填充 tgt_len 7 # 目标输入句子的最大长度 根据这个进行padding的填充 d_model512 # embedding的维度 d_ff2048 # 全连接层的维度 h_head8 # Multi-Head Attention 的…

【C++】—— C++11之可变参数模板

前言&#xff1a; 在C语言中&#xff0c;我们谈论了有关可变参数的相关知识。在C11中引入了一个新特性---即可变参数模板。本期&#xff0c;我们将要介绍的就是有关可变参数模板的相关知识&#xff01;&#xff01;&#xff01; 目录 序言 &#xff08;一&#xff09;可变参…