【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)
更加具体 模板初阶知识 大家这一去看看之前的文章 ----- 模板初阶
本篇文章主要讲解 模板的高阶操作:非类型模板参数、全特化、偏特化等,以及关于模板声明与定义不能分离(在两个不同的文件中)的问题。
三、非类型模板参数
之前所使用的模板参数都是用来匹配不同的类型,如
int
、double
、Date
等,模板参数除了可以匹配类型外,还可以匹配常量(非类型),完成如数组、位图等结构的大小确定
⚡问题引入⚡
问题:
假设我现在自定义了一个静态栈,栈的大小设置为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; // 大小为 10arr<20> a2; // 大小为 20arr<100> a3; // 大小为 100cout << "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; // 大小为 10arr<double , 20> a2; // 大小为 20arr<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 { /*……*/ };
因此可以总结出,非类型模板参数 的使用要求为
- 只能将 整型家族 类型作为非类型模板参数,其他类型不在标准之内
- 非类型模板参数必须为常量(不可被修改),且需要在编译阶段确定结果
整型家族:
char
、short
、bool
、int
、long
、long long
等
⚡问题的解决⚡
此时我们已经知道的非类型模板参数的用法,只需要给上面的栈添加非类型模板参数,这样就实现了 st1 和 st2 构造不同的大小。
// 静态栈
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); // 标准流输出 --> printffriend std::istream& operator>>(std::istream& in, Date& d); // 标准流插入 --> scanfpublic:// 构造函数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 对象里面还是 T1 和 T2 并不是我们想要的 int 和 double。
那么这个时候 ,我们就可以对 T1 和 T2 进行模板的特化
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++ 的理解,请持续关注我哦!!!
相关文章:

【C++】模板进阶--保姆级解析(什么是非类型模板参数?什么是模板的特化?模板的特化如何应用?)
目录 一、前言 二、什么是C模板? 💦泛型编程的思想 💦C模板的分类 三、非类型模板参数 ⚡问题引入⚡ ⚡非类型模板参数的使用⚡ 🔥非类型模板参数的定义 🔥非类型模板参数的两种类型 ὒ…...

Cookie与Session
Cookie Set-Cookie: sessionIdabc123; ExpiresWed, 09 Jun 2024 10:18:14 GMT; Path/; Secure; HttpOnlySession session作用域 首先需要了解servlet容器可能包含多个web应用。 在servlet容器中同一应用的servlet 对 session数据是可见的,不同应用之间session是相互…...

Nuxt3 的生命周期和钩子函数(十一)
title: Nuxt3 的生命周期和钩子函数(十一) date: 2024/7/5 updated: 2024/7/5 author: cmdragon excerpt: 摘要:本文详细介绍了Nuxt3中几个关键的生命周期钩子和它们的使用方法,包括webpack:done用于Webpack编译完成后执行操作…...

Windows ipconfig命令详解,Windows查看IP地址信息
「作者简介」:冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础著作 《网络安全自学教程》,适合基础薄弱的同学系统化的学习网络安全,用最短的时间掌握最核心的技术。 ipconfig 1、基…...

在C#/Net中使用Mqtt
net中MQTT的应用场景 c#常用来开发上位机程序,或者其他一些跟设备打交道比较多的系统,所以会经常作为拥有数据的终端,可以用来采集上传数据,而MQTT也是物联网常用的协议,所以下面介绍在C#开发中使用MQTT。 安装MQTTn…...

VBA提取word表格内容到excel
这是一段提取word表格中部分内容的vb代码。 Sub 提取word表格() mypath ThisWorkbook.Path & "\"myname Dir(mypath & "*.doc*")n 4 index of rowsRange("A1:F1") Array("课程代码", "课程名称", "专业&…...

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

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

数据库作业
命令 登陆数据库 mysql -uroot -p123456 --prompt"\u\h:\d--> " 创建数据库zcr create database zcr; 修改数据库zcr字符集为gbk alter database zcr default character set gbk collate gbk_chinese_ci; 选择数据库zcr use zcr 查看数据库zc…...
12、matlab中for循环,if else判断语句,break和continue用法以及switch case语句使用
1、前言 在MATLAB中,for循环用于迭代一个固定次数的循环。可以使用if else语句在循环中进行条件判断,根据条件的不同执行相应的代码块。break和continue可以用于控制循环的执行流程,break用于提前结束循环,而continue用于跳过当前…...
AcWing 3207:门禁系统 ← 桶排序中“桶”的思想
【题目来源】https://www.acwing.com/problem/content/3210/【题目描述】 涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况。 每位读者有一个唯一编号,每条记录用读者的编号来表示。 给出读者的来访记录,请问每一条记录中的读者…...
开发个人Go-ChatGPT--3 服务拆分
开发个人Go-ChatGPT–3 服务拆分 个人Go-ChatGPT项目可拆分用户服务(user),AI模型服务(AiModel),… 每个服务都可以再分为 api 服务和 rpc 服务。api 服务对外,可提供给 app 调用。rpc 服务是…...

Android --- 新电脑安装Android Studio 使用 Android 内置模拟器电脑直接卡死,鼠标和键盘都操作不了
新电脑安装Android Studio 使用 Android 内置模拟器电脑直接卡死,鼠标和键盘都操作不了 大概原因就是,初始化默认Google的安卓模拟器占用的RAM内存是2048,如果电脑的性能和内存一般的话就可能卡死,解决方案是手动修改安卓模拟器的config文件&…...

从入门到深入,Docker新手学习教程
编译整理|TesterHome社区 作者|Ishaan Gupta 以下为作者观点: Docker 彻底改变了我们开发、交付和运行应用程序的方式。它使开发人员能够将应用程序打包到容器中 - 标准化的可执行组件,将应用程序源代码与在任何环境中运行该代码…...
Postman编写测试脚本
在 Postman 中,编写测试脚本通常使用 JavaScript,这些脚本可以在请求发送前后执行。以下是一些示例代码,展示了如何在 Postman 中使用测试脚本。 1. 测试脚本示例:检查响应状态码 // 测试脚本在请求发送后执行 pm.test("Re…...

代码随想录算法训练Day57|LeetCode200-岛屿数量、LeetCode695-岛屿的最大面积
岛屿数量 题目描述 力扣200-岛屿数量 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此…...
StopWatch的使用
org.springframework.util.StopWatch 是 Spring 框架提供的一个轻量级的计时工具,用于测量代码执行时间。它比 Apache Commons Lang 的 StopWatch 提供了更多的功能,例如累计多个时间段、打印详细报告等。 以下是如何使用 Spring 的 StopWatchÿ…...

MySQL基础篇(三)数据库的修改 删除 备份恢复 查看连接情况
对数据库的修改主要指的是修改数据库的字符集,校验规则。 将test1数据库字符集改为gbk。 数据库的删除: 执行完该数据库就不存在了,对应数据库文件夹被删除,级联删除,里面的数据表全部被删除。 注意:不要随…...

android手机电视相框项目-学员做出个bug版本邀请大家review提意见
背景 前几天给我的vip学员布置了一个android手机/电视相框的项目,具体详情看如下链接: https://mp.weixin.qq.com/s/l2roDoco-o59SLlORENZlA 这个项目我说过不给提供答案哈,让各位学员朋友自己独立思考完成哈,因为尽量想让大家慢…...

web零碎知识2
不知道我的这个axios的包导进去没。 找一下关键词: http请求协议:就是进行交互式的格式 需要定义好 这个式一发一收短连接 而且没有记忆 这个分为三个部分 第一个式请求行,第二个就是请求头 第三个就是请求体 以get方式进行请求的失手请求…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...