目录
模板的概念:
C++除了面向对象编程的编程思想,还有一种编程思想就是泛型编程,泛型编程的具体例子就是模板。所谓模板,就是一个通用的类型,我们可以向模板传递不同的数据类型,从而得到不同的具体类型。C++中提供的模板有函数模板和类模板;
函数模板:
函数模板定义:
函数模板的作用就是将函数的数据类型参数化,什么意思,就是我们平常写的函数都是具体有形参类型,返回类型,里面具体数据类型的。类型参数化可以在我们写函数时将这些数据类型变成未知的变量,从而得到一个函数模板,我们可以根据传入不同的数据类型得到不同的结果和实现。
函数模板的语法:
template<typename T>
上面的黑色加粗体template就是语法关键字,表示要声明一个模板;而黑色加粗体typename语法关键字表示后面紧跟一个虚拟数据类型;绿色的粗体就是一个通用类型。所以我们可以写一个模板函数 :
template<typename T>
void Swap(T &a, T &b)
{
T tmp;
tmp = a;
a = b;
b = tmp;
}
这个函数的两个形参是一个通用数据类型T,我们可以根据传进去的数据不同得到不同的实现。例如我们如果将T定义为char类型,那么这个函数就变成交换两个char类型;如果我们将T定义为int类型,那么这个函数的实现就变成两个int交换。
那么我们怎么利用这个模板得到一个具体的函数呢?下面有两种具体定义一个函数的方法:
1.让编译器自动类型推导:
Swap(a, b);
上面表示让编译器根据我们传进去的数据自己推导T应该成为什么数据类型。
2.显式指定数据类型:
Swap<int>(a, b);
上面表示显式指定数据类型,我们可以在实参列表前加上声明指定T的数据类型。
函数模板的注意事项:
1.不能不指定模板的通用数据类型
就是模板中的通用数据类型T,我们在调用模板时一定要显式说明T的数据类型,即使我们不要用到T,不然编译器会报错。
template<typename T>
void Swap()
{
;
}
int main()
{
//Swap();//错误
Swap<char>();
return 0;
}
2.函数模板中的通用数据类型要唯一
即我们不能模板传递不同的数据类型,这样编译器会不知道选择哪一种数据类型。
template<typename T>
void func(T& a, T& b)
{
;
}
int main()
{
char a = 'a';
int b = 20;
func(a,b);
return 0;
}
3.函数模板的隐式转换问题
我们知道,在普通函数中,如果我们传递实参与形参不同,编译器可能会自动帮我们发生隐式的类型转换。而在函数模板中,如果我们使用的不是显式数据指定的方式创建函数模板,那么编译器会不知道T的具体数据类型,所以不会发生隐式的数据转换。而如果我们使用的是显式地函数模板,那么编译器也可能会自动帮我们进行隐式数据转换。
4.函数模板和普通函数的调用规则
规则1:函数模板和普通函数都可以调用,优先调用普通函数;
规则2:我们可以加上空的模板参数列表强制调用函数模板,即函数调用时后面加上尖括号:
func < >(...);
规则3:函数模板可以发生重载,即不同模板函数名可以一样;
规则4:如果有更好的匹配,编译器也会根据更好匹配优先调用函数模板;
void func(int a)
{
cout << "普通函数" << endl;
}
template<typename T>
void func(T a)
{
cout << "函数模板" << endl;
}
template<typename T>
void func(T a, T b)
{
cout<<"函数模板2" << endl;
}
int main()
{
func(100);//调用普通函数,规则1
func<>(100);//调用函数模板,规则2
func(100, 200);//传进去两个数据,优先匹配调用函数模板2,模板函数发生重载,规则3,4
return 0;
}
5.函数模板的局限
函数模板也不是万能的,对于一些不可以比较的数据,函数模板也无法进行对比。例如如果传进去两个不同的类,那么函数模板就无法对比了。
当然,为了解决这个问题,我们也可以有两种方法:
方法一:具体实例化一个对比函数,明确告知里面的对比方式和数据类型,它的语法是:
template<> 返回值类型+函数名+(参数列表)
注意,参数列表里面的参数需要指定确定的数据类型!
class Info
{
public:
Info(int a, char b)
{
this->a = a;
this->b = b;
}
int a;
char b;
};
template<typename T>
bool Compare(T a, T b)
{
if (a == b)
return true;
else
return false;
}
template<> bool Compare(Info a, Info b)//具体化一个函数模板!
{
if (a.a == b.a && a.b == b.b)
return true;
else
return false;
}
int main()
{
Info A(2,'a');
Info B(2,'a');
if (!Compare(A, B))
{
cout << "不相等" << endl;
}
else
cout<<"相等" << endl;
return 0;
}
方法二:重载“==”运算符:
class Info
{
public:
Info(int a, int b)
{
this->a = a;
this->b = b;
}
bool operator== (Info &A)
{
if (A.a == this->a && A.b == this->b)
return true;
else
return false;
}
int a;
int b;
};
template<typename T>
bool Compare(T a, T b)
{
if (a == b)
return true;
else
return false;
}
int main()
{
Info A(2, 1);
Info B(2, 1);
if (!Compare(A, B))
cout << "不相等" << endl;
else
cout<<"相等" << endl;
return 0;
}
类模板:
类模板定义:
类模板与函数模板的作用类似,类模板可以将类中的成员属性的类型设置为通用数据类型。以此可以让类应用更加广泛。
下面是一个简单的类模板的创建:
template<class G,class T>
class Info
{
public:
Info(T a, G b)
{
this->a = a;
this->b = b;
}
T a;
G b;
};
int main()
{
Info<int, int> A(2,3);
return 0;
}
类模板的注意事项:
1.类模板不能选择使用让编译器自动推导数据类型的方式
2.类模板的参数列表可以有默认参数......
//template<class T,class G=char>
//模板的参数列表可以有默认参数,即可以提早定义一个数据类型....
template<class T, class G>
class Info
{
public:
Info(T a,G b)
{
this->m_a = a;
this->m_b = b;
}
T m_a;
G m_b;
};
int main()
{
//Info A(2, 5);//不能让编译器自动推导数据类型,只能够显式声明模板的数据类型
Info<int, int> A(2,5);
return 0;
}
3.函数模板成员函数的创建时机
函数模板中的成员函数只会在调用模板时创建,它不同于普通的类,一个普通的类会在创建类时就创建成员函数。所以我们可以利用这个特性,大大扩展模板函数的应用范围。
class C1
{
public:
int mem1=2;
};
class C2
{
public:
int mem2=1;
};
template<typename T>
class Info
{
public:
T m_class;
int func1()
{
return m_class.mem1;
}
int func2()
{
m_class.mem2;
}
};
int main()
{
Info<C1>A;
cout << A.func1() << endl;
return 0;
}
4.类模板的对象作为形参
我们往往有时候会将用类模板创建出来的类对象传递到某个函数中,但是函数的形参应该写为什么数据类型呢?因为我们的对象是用类模板创建的,所以我们不得不考虑这个问题,为了解决这个问题,C++支持函数形参的数据类型有三种接收的方法:
我们先创建一个类模板并且写一个对象:
template<class T>
class Info
{
public:
Info()
{
a = 20;
}
void Show()
{
cout<<this->a << endl;
}
T a;
};
int main()
{
Info<int>A;
return 0;
}
1.我们可以将形参指定具体数据类型:
void print01(Info<int> A)
{
A.Show();
}
2.我们可以将函数形参的模板类里的数据类型也模板化:
template<class T>
void print02(Info<T> A)
{
A.Show();
}
3.我们可以直接将整个形参的数据类型模板化:
template<class T>
void print03(T A)
{
A.Show();
}
5.类模板的继承问题:
首先我们创建一个父类模板,要是我们想要再让一个子类去继承这个父类,我们就要注意对于父类模板中的通用数据类型的处理问题:
1.子类继承时直接指明父类模板中通用数据类型的具体类型
template<typename T>
class Fath
{
public:
T a;
};
class Son2 :public Fath<int>
{
};
2.子类也变成一个类模板
template<typename T>
class Fath
{
public:
T a;
};
template<class T,class G>
class Son:public Fath<T>
{
public:
G b;
};
!!!查看数据类型的方法:
头文件:<typeinfo>
语法:typeid(数据类型).name();
cout<<"char的数据类型:" << typeid(char).name() << endl;
cout <<"int的数据类型:" << typeid(int).name() << endl;
6.函数模板中的成员函数类外实现问题:
我们知道,普通的类成员函数要在类外实现,只需要在类内写个函数声明,并且在类外实现函数时加上作用域即可。而类模板的成员函数类外实现不同,它需要在作用域的 “ :: ”前加上参数列表。
template<class T>
class Info
{
public:
void func();
//{
// cout<<"this is Info" << endl;
//}
};
template<class T>//类外实现
void Info<T>::func()
{
cout << "this is Info" << endl;
}
int main()
{
Info<int> A;
A.func();
return 0;
}
7.类模板分文件编写问题:
我们知道,类模板的成员函数是在调用函数的时候创建。而如果我们将一个类模板的函数声明放在一个头文件,而将成员函数的类外实现放在另一个源文件中,这样会带来问题。因为此时类模板的成员函数没有创建。
主函数文件:
类模板头文件:
模板成员函数实现源文件:
而这样运行会带来问题:
就是因为类模板里面的成员函数没有创建。而解决这个问题,有两种方法:
方法1:直接将类模板和类模板里成员函数的实现放在同一个头文件中,并且将这个头文件后缀改为为.hpp。这是一个约定俗成的习惯,说明里面是个类模板。
方法二:直接包含类模板成员函数的源文件。因为类模板无法找到创建成员函数,所以直接包含源文件。
8.类模板与全局函数做友元:
全局函数在类模板中做友元,全局函数的实现可以在类模板中或者类模板外部。
1.全局函数实现在类模板中:
template<class T>
class Info
{
public:
friend void func(Info A)
{
cout<<A.m_name<<endl;
}
Info(T a)
{
m_name = a;
}
private:
T m_name;
};
int main()
{
Info<string> A("梁");
func(A);
return 0;
}
2.全局函数实现在类模板外:
全局函数做友元类外实现的几点注意事项:
1.类内全局函数声明要在函数名后加“ < > ”表面这个是个函数模板;
2.函数模板实现要在类创造前;
3.因为全局友元函数创建在类之前,而我们传进去了类模板。所以要提早声明类模板的存在;
4.全局函数不是类模板作用于下,不用加作用域;
template<class T>
class Info;
template<class T>
void func(Info<T> A)
{
cout<<A.m_name<<endl;
}
template<class T>
class Info
{
public:
friend void func<>(Info A);
Info(T a)
{
m_name = a;
}
private:
T m_name;
};
int main()
{
Info<string> A("梁");
func(A);
return 0;
}