【C++】进一步认识模板
🏖️作者:@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
,我们其实没有理由去使用array
,vector
能实现一切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.出现模板编译错误时,错误信息非常凌乱,不易定位错误(个人认为这个是现阶段对于我来说造成的最大问题;模板导致代码膨胀是由于内联函数的展开,而内联函数其实是一种理想的状态)。
关于模板其实还有挺多内容没讲到的,现阶段我们只需要大概了解它的一些基本使用场景就可以了。最后本篇文章的讲解就到这里了,如果有任何错处或者疑问欢迎大家评论区交流哦~~ 🙈 🙈
相关文章:
【C++】进一步认识模板
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录 前言一、非类型模板参数二、模板的特…...
Mysql Oracle 区别
1. oracle select *, id需要在星号前加别名,mysql则不需要 mysql语法: select *, id from xin_student_t;oracle语法: select st.*, st.id from xin_student_t st;2. oracle表定义了别名,在查询时可以不用别名指定字段…...
华为OD-第K长的连续字母字符串长度
题目描述 给定一个字符串,只包含大写字母,求在包含同一字母的子串中,长度第 k 长的子串的长度,相同字母只取最长的那个子串。 代码实现 # coding:utf-8 # 第K长的连续字母字符串长度 # https://www.nowcoder.com/discuss/353150…...
【编程题】有效三角形的个数
文章目录 一、题目二、算法讲解三、题目链接四、补充 一、题目 给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。 示例1: 输入: nums [2,2,3,4] 输出: 3 **解释:**有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 …...
【mysql是怎样运行的】-EXPLAIN详解
文章目录 1.基本语法2. EXPLAIN各列作用1. table2. id3. select_type4. partitions5. type 1.基本语法 EXPLAIN SELECT select_options #或者 DESCRIBE SELECT select_optionsEXPLAIN 语句输出的各个列的作用如下: 列名描述id在一个大的查询语句中每个SELECT关键…...
数据结构例题代码及其讲解-链表
链表 单链表的结构体定义及其初始化。 typedef struct LNode {int data;struct LNode* next; }LNode, *LinkList;①强调结点 LNode *p; ②强调链表 LinkList p; //初始化 LNode* initList() {//定义头结点LNode* L (LNode*)malloc(sizeof(LNode));L->next NULL;return …...
[Open-source tool] 可搭配PHP和SQL的表單開源工具_Form tools(1):簡介和建置
Form tools是一套可搭配PHP和SQL的表單開源工具,可讓開發者靈活運用,同時其有數個表單模板和應用模組供挑選,方便且彈性。Form tools已開發超過20年,為不同領域的需求者或開發者提供一個自由和開放的平台,使他們可建構…...
移动数据业务价值链的整合
3G 时代移动数据业务开发体系的建立和发展,要求运营商从封闭、统一的业 务形态、单一提供业务,向开放的、个性化多元化的业务体系以及多方合作参与提 供业务的方向发展,不可避免的使通信价值链不断延长和升级,内容提供商、服务 …...
合并两个链表
题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 比如以下例子: 题目接口: /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListN…...
测试框架pytest教程(9)跳过测试skip和xfail
skip无条件跳过 使用装饰器 pytest.mark.skip(reason"no way of currently testing this") def test_example(faker):print("nihao")print(faker.words()) 方法内部调用 满足条件时跳过 def test_example():a1if a>0:pytest.skip("unsupported …...
HTML <textarea> 标签
实例 <textarea rows="3" cols="20"> 收拾收拾 </textarea>定义和用法 <textarea> 标签定义多行的文本输入控件。 文本区中可容纳无限数量的文本,其中的文本的默认字体是等宽字体(通常是 Courier)。 可以通过 cols 和 rows 属性来…...
探索图结构:从基础到算法应用
文章目录 理解图的基本概念学习图的遍历算法学习最短路径算法案例分析:使用 Dijkstra 算法找出最短路径结论 🎉欢迎来到数据结构学习专栏~探索图结构:从基础到算法应用 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒🍹✨博客主页:I…...
Redis之GEO类型解读
目录 基本介绍 基本命令 geoadd 命令 geopos 命令 geodist 命令 georadius 命令 georadiusbymember 命令 geohash 命令 基本介绍 GEO 主要用于存储地理位置信息(纬度、经度、名称)添加到指定的key中。该功能在 Redis 3.2 版本新增。 GEO&…...
uniapp 微信小程序 路由跳转
保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面 //在起始页面跳转到test.vue页面并传递参数 uni.navigateTo({url: test?id1&name"lisa" }); uni.redirectTo(OBJECT) 关闭当前页面,跳转到应用…...
【android12-linux-5.1】【ST芯片】HAL移植后没调起来
ST传感器芯片HAL按官方文档移植后,测试一直掉不起来,加的日志没出来。经过分析,是系统自带了一个HAL,影响的。 按照官方文档,移植HAL后,在/device/<vendor\>/<board\>/device.mk*路径增加PROD…...
Redis Lua脚本执行原理和语法示例
Redis Lua脚本语法示例 文章目录 Redis Lua脚本语法示例0. 前言参考资料 1. Redis 执行Lua脚本原理1.1. 对Redis源码中嵌入Lua解释器的简要解析:1.2. Redis Lua 脚本缓存机制 2. Redis Lua脚本示例1.1. 场景示例1. 请求限流2. 原子性地从一个list移动元素到另一个li…...
百望云华为云共建零售数字化新生态 聚焦数智新消费升级
零售业是一个充满活力和创新的行业,但也是当前面临很大新挑战和新机遇的行业。数智新消费时代,数字化转型已经成为零售企业必须面对的重要课题。 8 月 20 日-21日,以“云上创新 韧性增长”为主题的华为云数智新消费创新峰会2023在成都隆重召…...
JMETER基本原理
Jmeter基本原理是建立一个线程池,多线程运行取样器产生大量负载,在运行过程中通过断言来验证结果的正确性,可以通过监听来记录测试结果; JMETER是运行在JVM虚拟机上的,每个进程的开销比loadrunner的进程开销大&#x…...
elementUI自定义上传文件 前端后端超详细过程
下面是使用Element UI自定义上传文件的前后端详细过程: 前端过程: 引入Element UI组件库:在前端项目中引入Element UI库,可以通过CDN引入或者通过npm安装并导入。 创建上传组件:在前端代码中创建一个上传组件&#x…...
快速排序笔记
一、quick_sort方法中如果 il,jr 会死循环的分析 1、示例代码 void quick_sort(int a[],int l,int r){if(l>r) return;int il,jr; //此处设置会导致死循环int x num[(lr)>>1];while(i<j){while(a[i] <x); //死循环的地方while(a[--j] >x);if(i<j) swap(a…...
JAVA:(JSON反序列化Long变成了Integer)java.lang.Integer cannot be cast to java.lang.Long
困扰了好几个小时。。。 场景:mybatisplus从数据库取数据,只是用了最基础的 LambdaQueryWrapper 来查询,实体类如下。 TableField(typeHandler JacksonTypeHandler.class) private Set<Long> ids; 得到的Set数据却是Set<Integer…...
ui设计师简历自我评价(合集)
UI设计最新面试题及答案 1、说说你是怎么理解UI的? UI是最直观的把产品展示展现在用户面前的东西,是一个产品的脸面。人开始往往是先会先喜欢上美好的事物后,在去深究内在的东西的。 那么也就意味着一个产品的UI首先要做的好看,无论风格是…...
Nginx 反向代理
一. Nginx 反向代理 1.1 反向代理介绍 在计算机网络中,反向代理一般指代理服务器,其首先代替内网的服务器接收客户端请求 并从一个或多个服务器检索资源,然后将这些资源返回给客户端。在客户端看来,这些资 源就好像来自代理服务…...
[论文阅读笔记25]A Comprehensive Survey on Graph Neural Networks
这是一篇GNN的综述, 发表于2021年的TNNLS. 这篇博客旨在对GNN的基本概念做一些记录. 论文地址: 论文 1. 引言, 背景与定义 对于图像数据来说, CNN具有平移不变性和局部连接性, 因此可以在欧氏空间上良好地学习. 然而, 对于具有图结构的数据(例如社交网络 化学分子等)就需要用…...
iview时间控件 动态不可选日期 可选择24小时范围内 时间往后退24小时
演示 html 设定 起始时间 触发on-change 方法结束时间 options 动态设置不可选择的日期。 <!-- 起始时间 --> <FormItem :label"$t(startTime)" prop"startTime"><DatePickertransfertype"datetime":placeholder"$t(pleas…...
Rest学习环境搭建:服务消费者
建一个子模块 导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache…...
JVM内存模型介绍
内存模型 内存模型如下图所示 堆 堆是Java虚拟机所管理的内存最大一块。堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域唯一的目的就是存放对象实例。所有的对象实例都在这里分配内存 Java堆是垃圾收集器管理的主要区域。从内存回收的角度来看&am…...
2000-2021年地级市产业升级、产业结构高级化面板数据
2000-2021年地级市产业升级、产业结构高级化面板数据 1、时间:2000-2021年 2、范围:地级市 3、指标:年份、地区、行政区划代码、地区、所属省份、地区生产总值、第一产业增加值、第二产业增加值、第三产业增加值、第一产业占GDP比重、第二…...
Java实现密码加密实现步骤【bcrypt算法】
一、SpringBoot和SSM框架均可实现密码加密的方法 在Spring Boot和SSM中实现密码加密可以使用bcrypt算法。bcrypt是一种密码哈希函数,通过将密码与随机生成的盐值进行混合,然后再进行多次迭代的计算,最终生成一个安全的哈希密码。 下面是使用…...
商城-学习整理-集群-K8S(二十三)
目录 一、k8s 集群部署1、k8s 快速入门1)、简介2)、架构1、整体主从方式2、Master 节点架构3、Node 节点架构 3)、概念4)、快速体验1、安装 minikube2、体验 nginx 部署升级 5)、流程叙述 2、k8s 集群安装1、kubeadm2、…...
网站权重怎么查询/百度app大全
MySQL的图形化界面的基本使用方法 一、首先将java.war放进tomact安装包下面的webapps下,然后运行bin/startup.bat,然后查看webapps下面时候新生成了一个和你的war包名相同的文件夹。二、编辑新生成的文件下的WEB-INF/classes/db-config.properties 原图如图…...
哪里可以做外贸网站/百度有刷排名软件
关于js中正则匹配特殊字符 function CheckStr(){var res /[~!#$%^&*()_\-<>?:"{}|,.\/;\\[\]~!#¥%……&*()——\-{}|《》?:“” 【】、;‘’,。、]/im;…...
新闻网站开发方案/搜什么关键词能找到网站
项目介绍 股票交易模拟系统是针对目前股票交易模拟管理的实际需求,从实际工作出发,对过去的股票交易模拟管理系统存在的问题进行分析,完善客户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利&#…...
吴忠网站建设/品牌推广的作用
1. 向量的基本概念1——向量、向量的表示、向量的模、单位向量、零向量 2. 向量的基本概念2——负向量、相等向量、自由向量、径向量 3. 向量的坐标表示 4. 向量的线性运算(加法和数乘,减法可以看做数乘与加法的复合) 5. 向量线性运算规则 6.…...
蚌埠武汉网站制作总部/百度极速版app下载安装
“It is a rough road that leads to the heights of greatness. ”(成就伟业之路,充满艰难坎坷。) 如果你选择程序员,你可能会对上面的话深有感触。程序员和医生是极少数需要干到老学到老的职业,可我们的社会地位明显…...
公章在线制作网站/小说百度风云榜
需求场景 在很多的数据开发场景下,MaxCompute项目管理员需要能够提供给某些角色或团队(如开发人员、运维人员)对项目内所有表具备特定权限。例如,某些客户可能需要在生产项目中,给ETL开发团队赋予所有表(或者所有ods开…...