【C++中的模板】

和你有关,观后无感.................................................................................................................

目录

前言

一、【模板的引入和介绍】

1、泛型编程

2、【模板的介绍】

二、【 函数模板】

2.1【模函数板的介绍】

1.2【函数模板的原理】

1.3【函数模板的实例化】

1、隐式实例化

2、显示实例化

1.4【模板参数的匹配原则】

三、【类模板】

3.1【类模板的介绍】

3.2【类模板的实例化】

四、【非类型模板参数】

五、【函数模板特化】

1、概念

2、函数模板的特化步骤

六、【类模板特化】

1、全特化

2、偏特化

七、【模板的分离编译】

1、什么是分离编译

2、模板的分离编译

3、模板分离编译失败的原因

4、解决方法

八、【模板总结】

总结


前言

本片内容主要是C++中模板的有关知识,主要讲述了模板及其实用和注意事项,请耐心观看。


一、【模板的引入和介绍】

1、泛型编程

相必我们大家都听说过泛型编程这个概念:

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

简单的说就是编写一种可以在多种数据类型上都能用的代码。比如说,你写一次代码,就可以在很多不同的数据类型上使用,而不需要为每种数据类型都重新写一遍相同逻辑的代码。它能让编程变得更简单、更灵活。

比如我们在C语言中如果想实现一个用来交换整形类型的函数可能会像下面那样写:

void swap_int(int* px, int* py)
{int tmp = *px;*px = *py;*py = tmp;
}

而当我们需要交换浮点类型就需要在写一个函数:

void swap_double(double* px, double* py)
{double tmp = *px;*px = *py;*py = tmp;
}

我们会发现C语言中并不允许我们定义重名函数,所以哪怕是相同逻辑但是数据类型不同,我们也要编写两个完全不同的函数来实现各自的功能,就像上面的交换函数。

而在C++中,我们引入了函数重载的概念其中就让我们可以定义同名函数,也就是说我们可以对同名函数进行重载使同名函数也就是相同逻辑的函数能够针对不同数据类型进行匹配调用,从而使不同类型的参数能够调用对应参数类型的函数从而实现相同的功能。

下面还是交换的例子:

void Swap(int& p1, int& p2)//交换整形
{int tmp = p1;p1 = p2;p2 = tmp;
}
void Swap(double& p1, double& p2)//交换浮点型
{double tmp = p1;p1 = p2;p2 = tmp;
}
void Swap(char& p1, char& p2)//交换字符型
{char tmp = p1;p1 = p2;p2 = tmp;
}

但是我们也发现函数重载虽然能实现不同类型参数调用不同参数类型函数,实现相同功能,但是仍然具备很多问题:

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

那么有没有一种办法能够让我们只写一份函数代码,实现出对应逻辑,而对于不同参数,编译器能够自动根据参数类型进行函数调用,这样就可以减少代码量,提高代码的简洁度。

2、【模板的介绍】

实际上是有解决方法的,我们可以联想到古代的活字印刷术,古人为提高抄写的效率发明的一种可以代替繁琐的“刀刻竹简”的方法。古人为了提高抄写的效率,往往将一些需要反复抄写的东西制作成活字印刷板,这样到了下次需要抄写时,可以直接将活字印刷板拓印到纸上,从而完成抄写的工作,还有一个例子就是,生活中我们时常能看到某个品牌生产的产品往往具有相同的外观,比如我们中秋节吃的月饼,往往都长得一样,这是因为生产商使用了某个固定的模具俩生产自己的月饼,这会大大提高他们的生产效率。

实际上在C++中,也存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的产品(即生成具体类型的代码),那将会节省许多精力。而这个模具我们称之为模板。

那么模板到底是什么呢?

模板就是我们自己写出一个不指定参数类型的函数,然后在调用函数时,编译器自己根据实参的类型把我们写好的不指定类型的函数,隐式编写一份用来匹配实参,我们所编写的函数就称为模板。

针对于上面的交换函数我们可以使用模板进行演示,先简单看一个例子(后面会提到具体的用法):


template<typename T>//声明模板参数这里也可以使用template<class T>
void Swap(T& x, T& y)//编写交换函数逻辑
{T tmp = x;x = y;y = tmp;
}
int main()
{int x = 1;int y = 2;cout << "交换前:" << "x=" << x << " " << "y=" << y << endl;Swap(x, y);cout <<"交换后:" << "x=" << x << " " << "y=" << y << endl;double dx = 1.1;double dy = 2.2;cout << "交换前:" << "dx=" << dx << " " << "dy=" << dy << endl;Swap(dx, dy);cout << "交换后:" << "dx=" << dx << " " << "dy=" << dy << endl;return 0;
}

我们可以看到上面的Swap()函数确实能实现我们的需求,但是我们一定有很多疑问像

template<class T> 这句代码是做什么的,这里的Swap()函数,实际上是一种函数模板,除了函数模板还存在类模板,我会一一介绍。

二、【 函数模板】

2.1【模函数板的介绍】

【概念】

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数实例化,根据实参类型产生函数的特定类型版本。

【使用格式】

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){//函数内容;}

这里举个例子就针对于上面的“交换函数”,这里举个例子:

template<typename T>
void Swap(T& p1, T& p2)
{T tmp = p1;p1 = p2;p2 = tmp;
}

注意:

typename是用来定义模板参数的关键字,也可以使用class(切记:不能使用struct代替class)

1.2【函数模板的原理】

 
弄明白了函数模板能为我们做什么,那么函数模板的底层原理到底是什么?

大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生产淘汰掉了很多手工产品。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。

实际上函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器,在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。

那么函数模板真的那么神奇吗,就像下面的Swap()函数他到底是如何根据参数类型实现对应的逻辑的呢?

template<typename T>
void Swap(T& p1, T& p2)
{T tmp = p1;p1 = p2;p2 = tmp;
}

结论:

实际上编译器是在这里先根据T所接收到的参数类型去实例化对应参数类型的Swap()函数,比如T接收到了int,那么编译器就会在底层实例化出一个参数类型为int的Swap()函数。当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

我们知道,模板实际上是编译器帮我们把活给做了,编译器通过参数的类型,自动生成对应参数的函数代码,并自动匹配调用,到这里模板好像也没有那么神奇,只是编译器把本来属于我们的任务给完成了。

1.3【函数模板的实例化】

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。

1、隐式实例化

让编译器根据实参推演模板参数的实际类型

那么隐式实例化该怎样使用呢?,下面看这样一个例子:

template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = 10, b = 20;int c = Add(a, b); //编译器根据实参a和b推演出模板参数为int类型return 0;
}

特别注意:使用模板时,编译器一般不会进行类型转换操作。所以,以下代码将不能通过编译,原因是a,b的类型不统一,编译器不会主动进行类型转换:

int main()
{int a = 10;double b = 1.1;int c = Add(a, b);return 0;
}

因为在编译期间,编译器根据实参推演模板参数的实际类型时,根据实参a将T推演为int,根据实参b将T推演为double,但是模板参数列表中只有一个T,编译器无法确定此处应该将T确定为int还是double。
 此时,我们有两种处理方式,第一种就是我们在传参时将b强制转换为int类型,第二种就是使用下面即将说到的显示实例化。

我们看一下强制类型转换:

int main()
{int a = 10;double b = 1.1;int c = Add(a, (int)b);return 0;
}

我们会发现,这里强制类型转换实际上并不能解决问题,1.1+10=11.1,结果却为11,下面再让我们看一下显示实例化。

2、显示实例化

在函数名后的加上“ <> ”并在其中指定模板参数的实际类型

template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = 10;double b = 1.1;int c = Add<int>(a, b); //指定模板参数的实际类型为intreturn 0;
}

这里我们看到,将Add()写成了,Add<int>(),就是告诉编译器对模板参数全都按照int类型处理,即使b为double,也会被当作int类型。

注意:

在使用显式实例化时,如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

但是这里好像也没有解决我们上面的问题,下面让我们看一下下面的例子:

template<typename T1,typename T2>
T2 Add(const T1& x, const T2& y)
{return x + y;
}
int main()
{int a = 10;double b = 1.1;//指定模板参数的实际类型为int和doublecout << a << " " << b << " " << Add<int, double>(a, b) << endl;return 0;
}

我们可以看到这里显示地把a实例化为int(T1为int),把b实例化为double(T2为double),才能得到正确的结果,所以模板虽然一定程度上解决了我们很多问题,但是它并不是万能的,对于我们多样化的要求,还是需要我们自己加以控制才能得到想要的结果。

1.4【模板参数的匹配原则】

1、 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{return left + right;
}
void Test()
{Add(10, 20); // 与非模板函数匹配,编译器不需要特化Add<int>(10, 20); // 调用编译器特化的Add版本
}

2、对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板函数

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
void Test()
{Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

3、 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译return 0;
}

因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。

三、【类模板】

3.1【类模板的介绍】

同函数模板一样类模板的定义也要用到关键字template< >:

template<class T1, class T2, ..., class Tn>
class //类模板名
{// 类内成员定义
};

举个例子:

template<class T>//这里T可为int,也可为double,分数有90整和90.5
class Score
{
public:void Print(){cout << "数学:" << _Math << endl;cout << "语文:" << _Chinese << endl;cout << "英语:" << _English << endl;}
private:T _Math;T _Chinese;T _English;
};

注意:类模板中函数放在类外进行定义时,需要加模板参数列表。

比如下面的T可以写成int 和double

template<class T>
class Score
{
public:void Print();
private:T _Math;T _Chinese;T _English;
};
//类模板中的成员函数在类外定义,需要加模板参数列表
template<class T>//
void Score<T>::Print()
{cout << "数学:" << _Math << endl;cout << "语文:" << _Chinese << endl;cout << "英语:" << _English << endl;
}

除此之外,类模板一般不支持分离编译(即声明在xxx.h文件中,而定义却在xxx.cpp文件中)。具体原因在后面会讲解。

3.2【类模板的实例化】


类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟“ <> ”。

然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

比如下面的例子:

Stack是类名,Stack<int>和Stack<double>才是类型.

template<class T>
Stack<int> s1;
Stack<double> s2;

注意

类模板名字不是真正的类,而实例化的结果才是真正的类。

四、【非类型模板参数】

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

例如,当我们要实现一个静态数组的类,就需要用到非类型模板参数。

template<class T, size_t N> //N:非类型模板参数
class StaticArray
{
public:size_t arraysize(){return N;}
private:T _array[N]; //利用非类型模板参数指定静态数组的大小
};

使用非类型模板参数后,我们就可以在实例化对象的时候指定所要创建的静态数组的大小了。

int main()
{StaticArray<int, 10> a1; //定义一个大小为10的静态数组cout << a1.arraysize() << endl; //10StaticArray<int, 100> a2; //定义一个大小为100的静态数组cout << a2.arraysize() << endl; //100return 0;
}

注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果,因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数。

五、【函数模板特化】

1、概念

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

比如:这里我们实现了一个专门用来进行判断两个对象是否相等的函数模板

template<class T>
bool IsEqual(const T& x, const T& y)
{return x == y;
}

我们大概会这样使用该函数模板:

cout << IsEqual(1, 1) << endl; //1
cout << IsEqual(1.1, 2.2) << endl; //0

这样使用是没有问题的,它的判断结果也是我们所预期的,但是我们也可能会这样去使用该函数模板:

char a1[] = "fantasyuanqian";
char a2[] = "fantasyuanqian";
cout << IsEqual(a1, a2) << endl; //0

判断结果是这两个字符串不相等,这很好理解,因为我们希望的是该函数能够判断两个字符串的内容是否相等,而该函数实际上判断是确实这两个字符串所存储的地址(数组名是数组首元素的地址,这里实际上将T转化为了int)是否相同,这是两个存在于栈区的字符串,其地址显然是不同的。
类似于上述实例,使用模板可以实现一些与类型无关的代码,但对于一些特殊的类型可能会得到一些错误的结果,此时就需要对模板进行特化,即在原模板的基础上,针对特殊类型进行特殊化的实现方式。

就像这里对于字符串的判断,我们应该拿到通过地址拿到存储内容再进行比较,所以要进行特殊处理:

我们知道当传入的类型是char*时,应该依次比较各个字符的ASCII码值进而判断两个字符串是否相等,或是直接调用strcmp函数进行字符串比较,那么此时我们就可以对char*类型进行特殊化的实现。

下面是特化后的例子:

//基础的函数模板
template<class T>
bool IsEqual(const T& x, const T& y)
{return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(const char* x, const char* y)
{return strcmp(x, y) == 0;
}

我们发现,特化后的template没有模板参数了,并且在函数名后显示实例化了参数类型,最后重写了模板函数的实现内容。

2、函数模板的特化步骤

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

注意: 

一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。例如,上述实例char*类型的特化还可以这样给出:

//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{return x == y;
}
//对于char*类型的特化
bool IsEqual(char* x, char* y)
{return strcmp(x, y) == 0;
}

六、【类模板特化】

不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。

1、全特化

全特化就是将模板参数列表中所有的参数都确定化。

比如对于下面的模板类:

template<class T1, class T2>
class Fantasy
{
public://构造函数Fantasy(){cout << "Fantasy<T1, T2>" << endl;}
private:T1 _F1;T2 _F2;
};

当T1和T2分别是double和int时,我们若是想对实例化的类进行特殊化处理,那么我们就可以对T1和T2分别是double和int时的模板进行全特化:

template<class T1, class T2>
class Fantasy
{
public://构造函数Fantasy(){cout << "Fantasy<T1, T2>" << endl;}
private:T1 _F1;T2 _F2;
};
//对于T1是double,T2是int时进行特化
template<>
class Fantasy<double, int>
{
public://构造函数Fantasy(){cout << "Fantasy<double, int>" << endl;}
private:double _F1;int _F2;
};

类模板的特化步骤:

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

那么如何证明当T1是double,T2是int时,使用的就是我们自己特化的类模板呢?
当我们实例化一个对象时,编译器会自动调用其默认构造函数,我们若是在构造函数当中打印适当的提示信息,那么当我们实例化对象后,通过观察控制台上打印的结果,即可确定实例化该对象时调用的是不是我们自己特化的类模板了。

2、偏特化

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

比如对于以下模板类:

template<class T1, class T2>
class Fantasy
{
public://构造函数Fantasy(){cout << "Fantasy<T1,T2>" << endl;}
private:T1 _F1;T2 _F2;
};

偏特化又可分为以下两种表现形式:
1、部分特化
我们可以仅对模板参数列表中的部分参数进行确定化。
例如,我们可以对T1为int类型的类进行特殊化处理。

//对T1为int的类进行特化
template<class T2>
class Fantasy<int, T2>
{
public://构造函数Fantasy(){cout << "Fantasy<int,T2>" << endl;}
private:int  _F1;T2 _F2;
};
int main()
{Fantasy<int, int> f1;Fantasy<double,int> f2;Fantasy<double,double> f3;return 0;
}

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

template<class T1,class T2>
class Fantasy<T1*, T2*>
{
public://构造函数Fantasy(){cout << "Fantasy<T1*,T2*>" << endl;}
private:T1  _F1;T2 _F2;
};
template<class T1, class T2>
class Fantasy<T1&, T2&>
{
public://构造函数Fantasy(){cout << "Fantasy<T1&,T2&>" << endl;}
private:T1  _F1;T2 _F2;
};
int main()
{Fantasy<int, int> f1;Fantasy<int*,int*> f2;Fantasy<int& ,int&> f3;return 0;
}

此时,当实例化对象的T1和T2同时为指针类型或同时为引用类型时,就会分别调用我们特化的两个类模板。

七、【模板的分离编译】

这里附上一篇资料:

为什么C++编译器不能支持对模板的分离式编译-CSDN博客

1、什么是分离编译

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

2、模板的分离编译

在分离编译模式下,我们一般创建三个文件,一个头文件用于进行函数声明,一个源文件用于对头文件中声明的函数进行定义,最后一个源文件用于调用头文件当中的函数。
按照此方法,我们若是对一个加法函数模板进行分离编译,其三个文件当中的内容大致如下:

但是使用这三个文件生成可执行文件时,却会在链接阶段产生报错。

下面我们对其进行分析:
我们都知道,程序要运行起来一般要经历以下四个步骤:

  1. 预处理: 头文件展开、去注释、宏替换、条件编译等。
  2. 编译: 检查代码的规范性、是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
  3. 汇编: 把编译阶段生成的文件转成目标文件。
  4. 链接: 将生成的各个目标文件进行链接,生成可执行文件。

以上代码在预处理阶段需要进行头文件的包含以及去注释操作。

这三个文件经过预处理后实际上就只有两个文件了,若是对应到Linux操作系统当中,此时就生成了 Add.i 和 main.i 文件了。

预处理后就需要进行编译,在 Add.i 当中有Add函数的定义,并且在 main.i 里面也有Add函数模板的声明和调用Add函数的代码,因此在编译阶段并不会发现任何语法错误,之后便顺利将 Add.i 和 main.i 翻译成了汇编语言,对应到Linux操作系统当中就生成了 Add.s 和 main.s 文件。

之后就到达了汇编阶段,此阶段利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,对应到Linux操作系统当中就是生成了 Add.o 和 main.o 两个目标文件。

前面的预处理、编译和汇编都没有问题,现在就需要将生成的两个目标文件进行链接操作了,但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义,主要原因是函数模板并没有生成对应的函数,因为在全过程中都没有实例化过函数模板的模板参数T,所以函数模板根本就不知道该实例化T为何类型的函数。

3、模板分离编译失败的原因


在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

4、解决方法

解决类似于上述模板分离编译失败的方法有两个,第一个就是在模板定义的位置进行显示实例化。
例如,对于上述代码解决方案如下:

1、模板定义的位置显式实例化。

在函数模板定义的地方,对T为int和double类型的函数进行了显示实例化,这样在链接时就不会找不到对应函数的定义了,也就能正确执行代码了。

2、将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。

虽然第一种方法能够解决模板分离编译失败的问题,但是我们这里并不推荐这种方法,因为我们需要用到一个函数模板实例化的函数,就需要自己手动显示实例化一个函数,非常麻烦。

现在就来说说解决该问题的第二个方法,也是我们所推荐的,那就是对于模板来说最好不要进行分离编译,不论是函数模板还是类模板,将模板的声明和定义都放到一个文件当中就行了。一般都放在头文件中,该头文件常命名为 “.hpp ” 

八、【模板总结】

优点:

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

缺陷:

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


总结

本篇内容到这里就结束了,如有错误还请指正,最后模板确实是C++中不可或缺的一部分,有了模板能减少很多重复冗余的工作,希望本篇博客能帮到你。


.............................................................................我好像在哪儿见过你,我在劝我,该忘了你

                                                                                              ————《我好像在哪见过你》

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

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

相关文章

修改word文件的创作者方法有哪些?如何修改文档的作者 这两个方法你一定要知道

在数字化时代&#xff0c;文件创作者的信息往往嵌入在文件的元数据中&#xff0c;这些元数据包括创作者的姓名、创建日期以及其他相关信息。然而&#xff0c;有时候我们可能需要修改这些创作者信息&#xff0c;出于隐私保护、版权调整或者其他实际需求。那么&#xff0c;有没有…

【开源设计】京东慢SQL组件:sql-analysis

京东慢SQL组件&#xff1a;sql-analysis 一、背景二、源码简析三、总结 地址&#xff1a;https://github.com/jd-opensource/sql-analysis 一、背景 开发中&#xff0c;无疑会遇到慢SQL问题&#xff0c;而常见的处理思路都是等上线&#xff0c;然后由监控报警之后再去定位对应…

vue 前端读取Excel文件并解析

前端读取Excel文件并解析 前端如何解释Excel呢 平时项目中对于Excel的导入解析是很常见的功能&#xff0c;一般都是放在后端执行&#xff1b;但是也有特殊的情况&#xff0c;偶尔也有要求说前端执行解析&#xff0c;判空&#xff0c;校验等&#xff0c;最后组装成后端接口想要的…

【大数据】利用 Apache Ranger 管理 Amazon EMR 中的数据权限

利用 Apache Ranger 管理 Amazon EMR 中的数据权限 1.需求背景简介2.系统方案架构图3.主要服务和组件简介3.1 Amazon EMR3.2 Simple Active Directory3.3 Apache Ranger 4.部署步骤4.1 部署 Simple AD 服务4.2 部署 Apache Ranger4.3 部署 Amazon EMR4.4 在 Amazon EMR 的主节点…

【数据结构】二叉树(带图详解)

文章目录 1.树的概念1.2 树的结构孩子表示法孩子兄弟表示法 1.3 相关概念 2.二叉树的概念及结构2.1 二叉树的概念2.2 数据结构中的二叉树-五种形态2.3 特殊的二叉树2.4 二叉树的存储结构顺序存储链式存储 2.5 二叉树的性质 3. 堆3.1 堆的定义3.2 堆的实现堆的结构堆的插入向上调…

java技术栈快速复习02_前端基础知识总结

前端基础 经典三件套&#xff1a; html&#xff08;盒子&#xff09;css&#xff08;样式&#xff09;JavaScript&#xff08;js&#xff1a;让盒子动起来&#xff09; html & css HTML全称&#xff1a;Hyper Text Markup Language(超文本标记语言)&#xff0c;不是编程语…

不科学上网使用Hugging Face的Transformers库

参考 Program Synthesis with CodeGen — ROCm Blogs (amd.com) HF-Mirror - Huggingface 镜像站 https://huggingface.co/docs/transformers/v4.40.1/zh/installation#%E7%A6%BB%E7%BA%BF%E6%A8%A1%E5%BC%8F 准备 apt show rocm-libs -a pip install transformers python …

计算机网络—数据链路层

一、数据链路层的基本概念 结点&#xff1a;主机、路由器 链路&#xff1a;网络中两个结点之间的物理通道&#xff0c;链路的传输介质主要有双绞线、光纤和微波。分为有线链路、无线链路 数据链路&#xff1a;网络中两个结点之间的逻辑通道&#xff0c;把实现控制数据协议的…

ABAP 查找第二代增强

文章目录 ABAP 查找第二代增强第一种方法-根据包去查找第二种方法-通过MODX_FUNCTION_ACTIVE_CHECK重要的表MODSAP表TFDIR表TFTIT表 ABAP 查找第二代增强 第一种方法-根据包去查找 第二种方法-通过MODX_FUNCTION_ACTIVE_CHECK 第二代增强&#xff08;基于函数模块的增强&…

git如何将多个commit合并成一个?

我们使用git进行版本控制&#xff0c;在本地开发完某个功能时&#xff0c;需要提交commit&#xff0c;然后push至开发分支。简单的功能还好&#xff0c;几个commit可能就好了。但是如果功能比较复杂&#xff0c;commit多达十几甚至几十个时&#xff0c;commit管理就会很冗长。比…

【IC设计】CRC(循环冗余校验)

目录 理论解读CRC应用CRC算法参数解读常见CRC参数模型 设计实战校招编程题分类串行输入、并行计算、串行输出**串行计算、串行输出&#xff08;线性移位寄存器&#xff09;LSFR线性移位寄存器&#xff08;并转串&#xff09;(并行计算)模二除 总结——串行、并行计算的本质参考…

成功解决STM32-No ST-LINK detected问题!

本文目录 一、原因二、解决方法一&#xff1a;有复位按键方法二&#xff1a;没有复位按键 一、原因 在之前一直都用的好好的&#xff0c;突然出现这个问题&#xff0c;原因只有两个&#xff1a; 接线松了&#xff0c;或者杜邦线损坏&#xff0c;换新的线试一下。上一次下载到…

【AI赋能演示力】:纯新人食用指南!ChatPPT万字实测报告

引言 随着科技的日新月异&#xff0c;人工智能已经深入到我们工作生活的方方面面&#xff0c;尤其是在提高效率与创新设计方面发挥着越来越重要的作用。 追溯至2023年3月&#xff0c;一款名为ChatPPT的人工智能驱动的PPT设计工具震撼登场并开启公测&#xff0c;标志着办公智能…

ORACLE 性能优化 高水位调节

当我需要去做优化时,有一个固定的优化思路:SQL优化->索引优化->分区优化->优化器hints优化 SQL 语句优化 1. 选用适合的 ORACLE 优化器 ORACLE 的优化器共有 3 种 : a. RULE ( 基于规则 ) b. COST ( 基于成本 ) c. CHOOSE ( 选 择性) 设置缺省的优化器, 可以通…

C语言贪吃蛇项目

今天给大家带来一款简单的贪吃蛇游戏&#xff0c;一起随我来看看吧 游戏效果&#xff1a; 实现基本的功能&#xff1a; • 贪吃蛇地图绘制 • 蛇吃⻝物的功能&#xff1a;&#xff08;上、下、左、右⽅向键控制蛇的动作&#xff09; • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分…

paddleocr C++生成dll

目录 编译完成后修改内容: 新建ppocr.h头文件 注释掉main.cpp内全部内容&#xff0c;将下面内容替换进去。ppocr.h需要再环境配置中包含进去头文件 然后更改配置信息&#xff0c;将exe换成dll 随后右击重新编译会在根目录生成dll,lib文件。 注意这些dll一个也不能少。生成…

第七篇:专家级指南:Python异常处理的艺术与策略

专家级指南&#xff1a;Python异常处理的艺术与策略 1 引言 在编程的世界中&#xff0c;异常处理是一门必修的艺术。它不仅涉及到程序的错误处理&#xff0c;更广泛地影响着软件的稳定性、健壮性和用户体验。本篇文章将深入探讨Python中的异常处理&#xff0c;展示如何通过精心…

深度学习之基于YOLOv5智慧交通拥挤预警检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着城市化进程的加速和人口规模的不断增长&#xff0c;交通拥挤问题日益严重。传统的交通拥挤预警方…

C++Day 7 作业

1、lambda #include <iostream>using namespace std;int main() {int a 100;int b 90;int temp;auto fun [&]()mutable->int {temp a;ab;btemp;};fun();cout<<a<<endl;return 0; } 2、vector #include <iostream> #include <vector>…

控制台主机不能运行,切换终端实现RPG运行

鄙人转载&#xff0c;主要是移植过程中使用小熊猫C2.25.1 过程中&#xff0c;字符集不同&#xff0c;导致某些空格 从bilibili专栏粘贴导致出现符号不匹配&#xff0c;但是编辑器不能替换 用原来的devc 5.11 发现问题&#xff0c;读出额外的英文&#xff1f; 使用文件替换&…