目录
- 一、函数模板
-
- 1.基础语法
- 2.普通函数和函数模板
-
- [2.1 两者区别](#2.1 两者区别)
- [2.2 两者之间的调用规则](#2.2 两者之间的调用规则)
- 3.模板的局限性
- 二、类模板
一、函数模板
1.基础语法
如果代码逻辑相同,可以将参数列表的数据类型进行抽象
c
template <typename T>
返回类型 函数名(参数列表) {
// 函数体
}
//如果想要定义多种数据类型,可在参数列表多次定义,即
template <typename T,typename W>
返回类型 函数名(参数列表) {
// 函数体
}
其中,template 是声明模板的关键字,typename (也可以用 class)用于指定类型参数 T,T 代表一个通用类型。在函数定义中,可以使用 T 作为一种数据类型
c
#include <iostream>
using namespace std;
// 定义一个函数模板,用于交换两个变量的值
template <typename T>
void swapValues(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int num1 = 10, num2 = 20;
cout << "交换前: num1 = " << num1 << ", num2 = " << num2 <<endl;
//1.自动推导类型为int
swapValues(num1, num2);
cout << "交换后: num1 = " << num1 << ", num2 = " << num2 << endl;
double d1 = 3.14, d2 = 2.71;
cout << "交换前: d1 = " << d1 << ", d2 = " << d2 << endl;
//2.显示指定类型为double
swapValues<double>(d1, d2);
cout << "交换后: d1 = " << d1 << ", d2 = " << d2 << endl;
return 0;
}
注意事项:
- 自动类型推导,必须推导出一致的数据类型才可以使用,即函数模板的变量类型必须和实际数据类型一致
- 模板必须要确定出T的数据类型才可以使用,因为内存分配时必须确定出函数模板的数据类型;即便函数模板中没使用这个数据类型也可以随意指定一个数据类型才能调用这个函数模板,如:
c
#include <iostream>
using namespace std;
// 定义一个函数模板,用于交换两个变量的值
template <typename T >
void func()
{
cout << "函数模板的调用" << endl;
}
int main()
{
func<int>();//随意指定了一个int类型
}
2.普通函数和函数模板
2.1 两者区别
- 使用普通函数时如果对不同类型进行操作时,编译器会自动对数据类型进行类型推导,即隐式类型转换
- 使用函数模板时,当使用自动类型推导时就不会发生隐式类型转换,会报错没有匹配函数类型;
当使用显示指定类型时,编译器就知道对不匹配的数据类型进行类型转换
2.2 两者之间的调用规则
- 当函数模板和普通函数名字相同时,优先调用普通函数
- 如果要强制调用函数模板,可以使用空模板参数列表来强行调用函数模板,下方代码由于没使用抽象出的数据类型T,所以随意指定了一个int类型
c
#include <iostream>
using namespace std;
template <typename T >
void func()
{
cout << "函数模板的调用" << endl;
}
void func()
{
cout << "普通函数的调用" << endl;
}
int main()
{
func<int>();
}
3.函数模板也可以发生重载
4.如果产生了更好的匹配,即如果调用普通函数需要发生类型转换,那么就会直接调用函数模板
c
#include <iostream>
using namespace std;
template <typename T >
void func(T a)
{
cout << "函数模板的调用" << endl;
}
void func(int a)
{
cout << "普通函数的调用" << endl;
}
int main()
{
char a='a';
func(a);//由于调用普通函数需要类型转换,就直接调用函数模板
}
3.模板的局限性
这里只针对模板对于一些数组或者类作为参数的情况,函数模板无法正确运行了;我们可以运用以前学过的运算符重载,不过那会显得非常麻烦,所以这里就有了函数模板的重载,即提供一个具体化的模板
c
#include <iostream>
#include<string>
using namespace std;
//对比两个变量是否相等
template<class T>
void is_equal(T& a, T& b)
{
if (a == b)cout << "a==b" << endl;
else cout << "a!=b" << endl;
}
class person
{
public:
person(string name, int age)
{
this->name = name;
this->age = age;
}
string name;
int age;
};
template<> void is_equal(person & p1, person & p2)
{
if (p1.name == p2.name&&p1.age==p2.age)cout << "p1==p2" << endl;
else cout << "p1!=p2" << endl;
}
int main()
{
person p1("tom", 10);
person p2("jero", 20);
is_equal(p1, p2);
}
二、类模板
1.类模板语法
类模板其实就是和函数模板类似,不过是将成员变量的数据类型进行了抽象,下面是一个简单例子:
c
#include <iostream>
#include<string>
using namespace std;
//对比两个变量是否相等
template<class nameType,class ageType>
class person
{
private:
nameType name;
ageType age;
public:
person(nameType name, ageType age)
{
this->name = name;
this->age = age;
}
void show()
{
cout << "name:" << this->name << endl << "age:" << this->age << endl;
}
};
int main()
{
person<string, int> p1("tom", 10);//显示指定类型
p1.show();
}
需要注意的是类模板和函数模板不一样,类模板没有自动类型转换,必须我们进行显示指定类型;其次类模板在模板参数列表中可以有默认参数,即在类模板参数列表定义中可以设置一个默认参数template<class nameType,class ageType=int>
,这和之前的默认函数参数规则是一样的,不过要注意的是如果我们全部使用了默认参数也要在调用时写上空列表即<>
2.成员函数的创建时机
c
#include <iostream>
#include<string>
using namespace std;
class person1
{
public:
void show1()
{
cout << "person1 show" << endl;
}
};
class person2
{
public:
void show2()
{
cout << "person2 show" << endl;
}
};
template<class T>
class person
{
public:
T obj;
void func1() { obj.show1(); }
void func2() { obj.show2(); }
};
int main()
{
person<person1> p1;
p1.func1();
//p1.func2();报错:已经指定了person1类型,无法调用person2中的内容
}
上方代码证明了在不指定模板中的具体类型时就不会创建成员函数,因为还无法知道这个类是指代哪一个具体类;而普通函数或者成员函数一般是在编译阶段就全部编译了
3.类模板对象做函数参数
以下是类模板对象作为函数参数的三种传参方式
c
#include <iostream>
#include<string>
using namespace std;
template<class nameType,class ageType>
class person
{
public:
nameType name;
ageType age;
person(nameType name,ageType age)
{
this->name = name;
this->age = age;
}
void show()
{
cout << this->name << endl << this->age << endl;
}
};
//1.指定传入类型
void print1(person<string, int>& p1)
{
cout << "函数1的调用" << endl;
p1.show();
}
//2.参数模板化
template<class t1,class t2>
void print2(person<t1, t2>& p2)
{
cout << "函数2的调用" << endl;
p2.show();
}
//3.整个类模板化
template<class t>
void print3(t& p3)
{
cout << "函数3的调用" << endl;
p3.show();
}
int main()
{
person<string, int>p1("tom", 20);
print3<person<string,int>>(p1);
//或者直接让函数模板进行自动推导
//即print3(p1);
}
实际开发中最常用的还是第一种直接指定参数类型
4.类模板与继承
- 当子类继承的父类是一个类模板时,子类在声明的时候一定要指定出父类中模板参数的类型,如果不指定编译器就无法分配内存
c
#include <iostream>
#include<string>
using namespace std;
template<class T>
class base
{
T name;
};
class son :public base<string>//必须指定基类模板参数类型
{
//类成员......
};
- 如果想灵活指定出父类中的类型,子类也需要声明为类模板
c
#include <iostream>
#include<string>
using namespace std;
template<class T>
class base
{
T name;
};
template<class T1,class T>
class son :public base<T>//将基类模板参数指定为子类模板参数,使其数据类型更加灵活
{
T1 age;
};
int main()
{
son<string, int>son1;
}
5.类模板成员函数的类外实现
不仅要告知编译器模板参数的存在,还要告知其不为普通函数的类外实现,而是函数模板的类外实现
c
#include <iostream>
#include<string>
using namespace std;
template<class T>
class person
{
public:
person(T name)
{
this->name = name;
}
T name;
void show();
};
template<class T>//告知模板参数的存在
void person<T>::show()//告知其为类模板的类外实现
{
cout << this->name << endl;
}
int main()
{
person<string>p1("tom");
p1.show();
}
6.类模板的分文件编写
- 问题:类模板份文件编写时会产生无法解析的外部命令
- 本质原因:类模板中的成员函数的创建(编译)时机是在函数的调用阶段,如果只包含了头文件,就无法在将函数的具体实现编译出来,自然就会导致无法解析的外部命令
- 解决方案:直接包含.cpp文件,这样就可以将.cpp和.h同时进行编译,因为在.cpp中是#include'.h'的;或者将两个文件写在一起并将后缀改为.hpp,这是一个约定俗成的规则
7.类模板和友元
如果是全局函数在类内实现友元函数那么直接实现就可以了,但是如果是在类外实现友元函数,我们需要提前让编译器知道全局函数的存在
全局函数做友元类内实现:
c
#include <iostream>
#include<string>
using namespace std;
template<class T>
class person
{
public:
person(T name)
{
this->name = name;
}
private:
//全局函数类内实现
friend void show(person<T>p1)
{
cout << p1.name << endl;
}
T name;
};
template<class T>
void show(person<T>p1);
int main()
{
person<string>p1("tom");
show(p1);
}
全局函数做友元类外实现:
c
#include <iostream>
#include <string>
using namespace std;
// 提前声明 person 类模板
template<class T>
class person;
// 提前声明 show 函数模板
template<class T>
void show(person<T> p1);
template<class T>
class person
{
public:
person(T name)
{
this->name = name;
}
private:
// 全局函数是一个函数模板,所以这里加了<>
friend void show<>(person<T> p1);
T name;
};
// 实现 show 函数模板
template<class T>
void show(person<T> p1)
{
cout << p1.name << endl;
}
int main()
{
person<string> p1("tom");
show(p1);
return 0;
}
- 在这个代码中,将 show 函数模板的声明提前到 person 类定义之前是为了让编译器在解析 person 类中的 friend 声明时能够找到 show 函数模板的声明,可以在 person 类定义之前提前声明 show 函数模板,这样编译器在解析 person 类中的 friend 声明时就能找到 show 函数模板的声明;
- 这里会有人产生疑问,为什么之前学习普通函数就可以将类内的友元声明视为函数声明,而函数模板就不行,这是因为之前提到过的模板的编译时机是调用时再编译,所以我们需要在对函数模板进行友元声明时就知道这个函数模板的完整定义,以便生成正确的代码,如果在调用友元函数模板时编译器还没看到函数模板的定义,就无法正确实例化,从而导致编译错误