友情链接:C/C++系列系统学习目录
文章目录
🚀一、函数模板
// demo 15-2.c
#include <iostream>
using namespace std;
int Max(int a, int b)
{
return a>b ? a:b;
}
char Max(char a, char b)
{
return a>b ? a:b;
}
float Max(float a, float b)
{
return a>b ? a:b;
}
void main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout<<"max(1, 2) = "<<Max(x, y)<<endl;
float a = 2.0;
float b = 3.0;
cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;
system("pause");
return ;
}
//实际上,以上程序,只需要一个“函数”就可以搞定!
#include <iostream>
using namespace std;
//template 关键字告诉 C++编译器 我要开始泛型编程了,请你不要随意报错
//T - 参数化数据类型
template <typename T>
T Max(T a, T b){
return a>b ? a:b;
}
/*如果 T 使用 int 类型调用,相当于调用下面这个函数
int Max(int a, int b)
{
return a>b ? a:b;
}
*/
void main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout<<"max(1, 2) = "<< Max(x, y) <<endl; //实现参数类型的自动推导
cout<<"max(1, 2) = "<< Max<int>(x, y) <<endl; //显示类型调用
float a = 2.0;
float b = 3.0;
cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;
system("pause");
return ;
}
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
⛳(一)基本用法
1.函数模板定义形式
由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用
template < 类型形式参数表 >
类型 函数名 (形式参数表)
{
//语句序列
}
//例如:(class可以换成typename)
template <class AnyType> --》模板说明
void swap(AnyType &a,AnyType &b){
AnyType temp ;
temp = a; --》函数定义
a = b;
b = temp ;
}
max<int>(a, b); //显式类型调用 --》函数模板调用
max(a, b); //自动数据类型推导
-
在标准C++98添加关键字typename之前,C++使用关键字class来创建模板。也就是说,类型形式参数的形式:typename T1 , typename T2 , …… , typename Tn 或 class T1 , class T2 , …… , class Tn(注:typename 和 class 的效果完全等同),用typename使得参数AnyType表示类型这一点更为明显
-
模板说明的类型参数必须在函数定义中出现一次,函数参数表中可以使用类类型的参数,也可以使用一般类型参数
-
模板参数列表的参数是否一样、以及个数、返回值是哪个,都由程序员决定:
template <typename TR,typename T> TR ave(T a,T b) { return (a+b)/2; }
2.模板函数:
⛳(二)函数模板与函数重载
需要多个对不同类型使用同一种算法的函数时,可使用模板,然而,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,被重载的模板的函数特征标必须不同。
#include <iostream>
using namespace std;
template <typename T>
void Swap(T &a, T &b){
T t;
t = a;
a = b;
b = t;
cout<<"Swap 模板函数被调用了"<<endl;
}
void Swap(char &a, int &b){
int t;
t = a;
a = b;
b = t;
cout<<"Swap 普通函数被调用了"<<endl;
}
//第一版
int Max(int a, int b)
{
cout<<"调用 int Max(int a, int b)"<<endl;
return a>b ? a:b;
}
template<typename T>
T Max(T a, T b)
{
cout<<"调用 T Max(T a, T b)"<<endl;
return a>b ? a:b;
}
template <typename T>
T Max(T a, T b, T c){
cout<<"调用 T Max(T a, T b, T c)"<<endl;
return Max(Max(a, b), c);
}
//第二版
int Max1(int a, int b)
{
cout<<"调用 int Max(int a, int b)"<<endl;
return a>b ? a:b;
}
template<typename T1, typename T2>
T1 Max1(T1 a, T2 b)
{
cout<<"调用 T Max1(T1 a, T2 b)"<<endl;
return a>b ? a:b;
}
void main(void){
char cNum = 'c';
int iNum = 65;
Swap(cNum, iNum); //模板函数和普通函数并存,参数类型和普通重载函数更匹配,调用普通函数
//Swap(cNum, iNum); //如果不存在普通函数,函数模板不提供隐式的数据类型转换,必须是严格的匹配
int a = 1;
int b = 2;
cout<< "Max(a, b):" << Max(a, b) << endl; //当函数模板和普通函数都符合调用时,优先选择普通函数
//Max<>(a, b); 如果显式的使用函数模板,则使用<> 类型列表
char c = 'a'; //如果函数模板会产生更好的匹配,使用函数模板
Max1(c, a);
Max(1.0, 2.0);
Max(3.0, 4.0, 5.0);
system("pause");
return ;
}
-
函数模板和普通函数区别:
- 两者允许并存,模板函数和普通函数并存时,参数类型和哪个更匹配就调用哪个;
- 函数模板不允许自动类型转化,必须是严格的匹配,普通函数能够进行自动类型转换:
-
函数模板和普通函数在一起,调用规则:
-
函数模板可以像普通函数一样被重载
-
C++编译器优先考虑普通函数
-
如果函数模板可以产生一个更好的匹配,那么选择模板
-
可以通过空模板实参列表的语法限定编译器只通过模板匹配:
Max<>(a, b);
-
⛳(三)模板的局限性与显示具体化
假设有如下函数模板:
template <class T>
void f(T a, T b)
{
a = b;
}
- 局限性:
如果T为数组,这种假设就不成立了,如果里面的语句为判断语句 if(a>b),但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。
总之,编写的模板函数很可能无法处理某些类型,为了解决这种问题,对于结构体等,一种解决方案是,C++允许您重载运算符+,以便能够将其用于特定的结构或类,另一种解决方案是,可以提供模板的重载,为这些特定的类型提供具体化的模板。
- 显示具体化:
class Person
{
public:
Person(string name, int age)
{
this->mName = name;
this->mAge = age;
}
string mName;
int mAge;
};
//普通交换函数
template <class T>
void mySwap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
//第三代具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
//具体化优先于常规模板
template<> void mySwap<Person>(Person &p1, Person &p2)
{
string nameTemp;
int ageTemp;
nameTemp = p1.mName;
p1.mName = p2.mName;
p2.mName = nameTemp;
ageTemp = p1.mAge;
p1.mAge = p2.mAge;
p2.mAge = ageTemp;
}
void test()
{
Person P1("Tom", 10);
Person P2("Jerry", 20);
cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl;
cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl;
mySwap(P1, P2);
cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl;
cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl;
}
-
试验其他具体化方法后,C++98标准(第三代具体化)选择了下面的方法。对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本。显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
-
如果有多个原型,则编译器在选择原型时,非模板函数优先于显式具体化和模板函数,而显式具体化优先于使用函数模板生成的版本:
... template <class T> // template void Swap(T &,T &); //explicit specialization for the job type template <> void Swap<job>(job &, job &) ; int main() { double u,v; ... Swap(u,v); // use template job a, b; Swap(a, b) // use void Swap<job>(job &, job&) }
第一次将调用Swap( )时使用通用版本,而第二次调用使用基于job类型的显式具体化版本。
-
Swap<job>中的<job>是可选的,因为函数的参数类型表明,这是job的一个具体化。因此,该原型也可以这样编写:
template <> void Swap(job &, job &) ;
⛳(四)函数模板机制剖析
-
实例化和具体化:
- 实例化即使用某种类型,调用模板生成一个此函数的实例,例如上面的
Swap(u,v);
- 具体化则是为了某些特殊类型,提供模板的重载,为这些特定的类型提供具体化的模板。
- 实例化即使用某种类型,调用模板生成一个此函数的实例,例如上面的
-
编译器并不是把函数模板处理成能够处理任意类型的函数而是编译器从函数模板通过具体类型产生不同的函数
- 注意,函数模板不能缩短可执行程序。如果有两个实例化函数,最终仍将由两个独立的函数定义,就像以手工方式定义了这些函数一样。最终的代码不包含任何模板,而只包含了为程序生成的实际函数。使用模板的好处是,它使生成多个函数定义更简单、更可靠。
- 在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation)。
-
可用通过<>显示进行模板的调用,称为显示具体化,注意:以此方式,参数可以进行类型转换,相当于强制类型转换:
template <class T> T Add(T a,T b) // pass by value{ return a + b; } ... int m = 6 ; double x = 10. 2 ; cout << Add<double> (x,m) << endl; // explicit instantiation
这里的模板与函数调用Add(x, m)不匹配,因为该模板要求两个函数参数的类型相同。但通过使用
Add<double>(x, m)
,可强制为double类型实例化,并将参数m强制转换为double类型,以便与函数Add<double>(double, double)
的第二个参数匹配。
🚀二、类模板
template <typename T>
class A
{
public:
A(T t)
{
this->t = t;
}
T &getT()
{
return t;
}
public:
T t;
};
类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同
⛳(一)基本用法
1.类模板定义形式:
类模板由模板说明和类说明构成
//模板说明同函数模板,如下:
template <类型形式参数表>
类声明
//例如:
template <typename Type>
class ClassName
{
//ClassName 的成员函数
private :
Type DataMember;
}
-
类模板用于实现类所需数据的类型参数化,类模板在表示支持多种数据结构显得特别重要,这些数据结构的表示和算法不受所
包含的元素类型的影响
2.单个类模板的使用:
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t=0)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
void printA(A<int> &a){
cout<<a.getT()<<endl;
}
int main(void){
//1.模板类定义类对象,必须显示指定类型
//2.模板类如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(666);
cout<<a.getT()<<endl;
//模板类做为函数参数
printA(a);
system("pause");
return 0;
}
3.继承中类模板的使用:
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
template <typename Tb>
class B: public A<int> //要实例化父类的类型参数
{
public:
B(Tb b):A<Tb>(b)
{
this->b = b;
}
private:
Tb b;
};
void printA(A<int> &a){
cout<<a.getT()<<endl;
}
int main(void){
//1.模板类定义类对象,必须显示指定类型
//2.模板种如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(666);
cout<<a.getT()<<endl;
B<int> b(888);
cout<<"b(888): "<<b.getT()<<endl;
//模板类做为函数参数
printA(a);
system("pause");
return 0;
}
父类是一般类,子类是模板类, 和普通继承的玩法类似;子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数;父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中
⛳(二)类模板函数的三种表达描述方式
-
所有的类模板函数写在类的内部,如以上代码所示
-
所有的类模板函数写在类的外部,在一个 cpp 中:
#include <iostream> using namespace std; template <typename T> class A { public: A(T t=0); T& getT(); A operator +(const A &other); void print(); private: T t; }; template <typename T> A<T>::A(T t) { this->t = t; } template <typename T> T& A<T>::getT() { return t; } template <typename T> A<T> A<T>::operator+(const A<T> &other){ //第一个A<T>是因为返回类型是模板类对象,第二个A<T>是因为是类限定域说明,第三个是因为参数是模板类对象 A<T> tmp; //类的内部类型可以显示声明也可以不显示 tmp.t =this->t + other.t; return tmp; } template <typename T> void A<T>::print(){ cout<< this->t <<endl; } int main(void){ A<int> a(666), b(888); //cout<<a.getT()<<endl; A<int> tmp = a + b; tmp.print(); system("pause"); return 0; }
在同一个 cpp 文件中把模板类的成员函数放到类的外部,需要注意以下几点:
- 函数前声明 template <类型形式参数表>
- 类的成员函数前的类限定域说明必须要带上虚拟参数列表
- 返回的变量是模板类的对象时必须带上虚拟参数列表
- 成员函数参数中出现模板类的对象时必须带上虚拟参数列表
-
所有的类模板函数写在类的外部,在不同的.h和.cpp中
注意:当类模板的声明(.h 文件)和实现(.cpp 或.hpp 文件)完全分离,因为类模板的特殊实现,我们应在使用类模板时使用#include 包含 实现部分的.cpp 或.hpp 文件。即在main.cpp中使用#include "demo.cpp"而不是.h文件
⛳(三)类模板与友元函数
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
...
//声明一个友元函数,实现对两个 A 类对象进行加法操作
template <typename T>
friend A<T> addA(const A<T> &a, const A<T> &b);
private:
T t;
};
//A 类的友元函数,就是它的好朋友
template <typename T>
A<T> addA(const A<T> &a, const A<T> &b){
A<T> tmp;
cout<<"call addA()..."<<endl;
tmp.t = a.t + b.t;
return tmp;
}
int main(void){
A<int> a(666), b(888);
A<int> tmp = a + b;
A<int> tmp1 = addA<int>(a, b);
tmp.print();
tmp1.print();
system("pause");
return 0;
}
-
类内部声明友元函数,必须写成以下形式:
template<typename T> friend A<T> addA (A<T> &a, A<T> &b);
-
友元函数实现 必须写成:
template<typename T> A<T> add(A<T> &a, A<T> &b) { //...... }
-
友元函数调用 必须写成
A<int> c4 = addA<int>(c1, c2);
⛳(四)类模板与静态成员
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t=0);
T& getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
T t;
};
template <typename T> int A<T>::count = 666;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T& A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){ //第一个A<T>是因为返回类型是模板类对象,第二个A<T>是因为是类限定域说明,第三个是因为参数是模板类对象
A<T> tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<< this->t <<endl;
}
//当我们的虚拟的类型 T 被 int 实例化以后,模板类如下:
class A
{
public:
A(int t=0);
int &getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
int t;
};
int A::count = 666;
A::A(int t)
{
this->t = t;
}
int &A::getT()
{
return t;
}
A A::operator+(const A &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
void A::print(){
cout<<this->t<<endl;
}
//当我们的虚拟的类型 T 被 float 实例化以后,模板类如下:
class A
{
public:
A(float t=0);
float &getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
float t;
};
int A::count = 666;
A::A(float t)
{
this->t = t;
}
float &A::getT()
{
return t;
}
A A::operator+(const A &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
void A::print(){
cout<<this->t<<endl;
}
int main(void){
A<int> a(666), b(888);
A<int> tmp = a + b;
A<float> c(777), d(999);
a.count = 888;
cout<<"b.count:"<<b.count<<endl; //888
cout<<"c.count:"<<c.count<<endl; //666
cout<<"d.count:"<<d.count<<endl; //666
c.count = 1000;
cout<<"修改后, d.count:"<<d.count<<endl; //1000
system("pause");
return 0;
}
-
从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个 static 数据成员
-
和非模板类的 static 数据成员一样,模板类的 static 数据成员也应该在文件范围定义和初始化
-
static 数据成员也可以使用虚拟类型参数T:
static T count
-
和函数模板一样,类模板的类型参数可以有一个或多个,每个类型前面都必须加 typename 或 class,如:
template <typename T1,typename T2> class someclass {...}; //在定义对象时分别代入实际的类型名,如: someclass<int, char> object;
-
和使用类一样,使用类模板时要注意其作用域,只有在它的有效作用域内用使用它定义对象。
⛳(五)模板类valarray简介
-
valarray类是由头文件varlarray支持的。用于处理数值,支持诸如将数组中所有元素的值相加以及在数组中找出最大和最小的值操作。
-
valarray被定义为一个模板类,以便能够处理不同的数据类型。
-
模板特性意味着声明对象,必须指定具体的数据类型。因此,使用valarray类来声明一个对象时,需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型:
valarray<int> q_values;// int数组 valarray<double> weights;//double数组
类特性意味着要使用valarray对象,需要了解这个类的构造函数和其它用法。下面是几个使用其构造函数的例子:
double gpa[5]={3.1,3.5,3.8,2.9,3.3};
valarray<double> v1; //一个double类型的数组,长度为0
valarray<int> v2(8); //一个int类型的数组,长度为8
valarray<int> v3(10,8); //含8个int元素的数组;每个设置为10
valarray<double>v4(gpa,4); //由4个元素组成的数组,初始化为gpa的前4个元素
//即第二个参数指定数组长度,第一个参数设置元素值
//在c++11中,也可使用初始化列表:
valarray<int> v5={20,32,17,9};//c++11
下面是这个类的一些方法:
operator[]()
:让您能够访问各个元素。size()
:返回包含的元素数。sum()
:返回所有元素的总和max()
:返回最大的元素min()
:返回最小的元素