原文链接:https://www.cnblogs.com/1873cy/p/18398002
模板
模板介绍#
C++提供了函数模板(function template)。所谓函数模板。实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡事函数体相同的函数都可以使用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同的函数功能。
C++提供了两种模板机制:函数模板和类模板
意义:
- 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
- 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
函数模板#
下面举例说明一下函数模板的具体用法。
假设有两个需求:
- 设计一个函数,实现两个
int
变量的值的交换 - 设计一个函数,实现两个
double
变量的值的交换
void mySwap(int &a, int &b){ int tmp = a; a = b; b = tmp; } void mySwap(double &a, double &b){ double tmp = a; a = b; b = tmp; }
如果还有需求要交换其他类型的变量,还需要继续添加函数,而代码几乎一致。并且一旦需求有变更,每个函数都要一个一个修改,非常繁琐且重复。
发现两个函数逻辑结构完全相同,只有变量类型不同,如果能设计一个通用的函数,能够把类型当做参数传递到这个函数中,就简单多了——这就是函数模板!
定义#
- 用法:
template<typename T>
template
:模板关键字typename
:定义虚拟类型关键字,也可以使用classT
:定义的一个虚拟的类型,在这里暂时不确定是什么类型,等到调用这个函数的时候就可以确定了
template<typename T> | |
void mySwap(T &a, T &b){ | |
T tmp = a; | |
a = b; | |
b = tmp; | |
} | |
int main(){ | |
int a = 10, b = 20; | |
double x = 3.14, y = 9.9; | |
// 显式指定类型 | |
mySwap<int>(a, b); | |
// 可以自动根据实参的类型进行推导 | |
mySwap(a, b); | |
mySwap(x, y); | |
cout << "a:" << a << endl; | |
cout << "b:" << b << endl; | |
cout << "x:" << x << endl; | |
cout << "y:" << y << endl; | |
system("pause"); | |
return 0; | |
} |
函数模板使用案例#
- 需求
- 定义一个函数模板,实现对数组中元素进行升序排序
- 定义一个函数模板,实现将一个数组中元素拼接成为字符串返回
// 定义一个函数模板,实现对数组中元素进行升序排序 | |
template<class T> | |
void mySort(T arr[], int len){ | |
for (int i = 0; i < len; i++){ | |
for (int j = i; j < len; j++){ | |
if (arr[i] > arr[j]){ | |
T tmp = arr[i]; | |
arr[i] = arr[j]; | |
arr[j] = tmp; | |
} | |
} | |
} | |
} | |
// 定义一个函数模板,实现将一个数组中元素拼接成为字符串返回 | |
template<class T> | |
void showArray(T arr[], int len){ | |
cout << "["; | |
for (int i = 0; i < len - 1; i++){ | |
cout << arr[i] << ","; | |
} | |
cout << arr[len - 1] << "]" << endl; | |
} | |
int main(){ | |
// 定义一个int[] | |
int array1[] = { 1, 3, 5, 7, 9, 0, 8, 6, 4, 2 }; | |
int len1 = sizeof(array1) / sizeof(int); | |
mySort(array1, len1); | |
showArray(array1, len1); | |
// 定义一个double[] | |
double array2[] = { 3.14, 9.28, 3, 3.44, -9.2, 8.22 }; | |
int len2 = sizeof(array2) / sizeof(double); | |
mySort(array2, len2); | |
showArray(array2, len2); | |
// 定义一个char[] | |
char array3[] = { 'a', 'l', '1', 'm', 'k' }; | |
int len3 = sizeof(array3) / sizeof(char); | |
mySort(array3, len3); | |
showArray(array3, len3); | |
system("pause"); | |
return 0; | |
} |
函数模板与普通函数#
函数模板和普通函数在调用的时候需要注意:
- 普通函数的调用是可以发生自动类型转换的;而函数模板调用不允许发生自动类型转换
- 如果调用函数的时候,实参既可以匹配普通函数,又可以匹配函数模板,则优先匹配普通函数
函数模板重载#
函数模板虽然很通用,但并不是万能的,有时候也会出现不适配的情况
Copytemplate<class T> | |
bool compare(const T& t1, const T& t2) { | |
return t1 > t2; | |
} |
对于上述函数模板,如果比较的类型设置为自定义类型(如Person类型),则会出现无法比较的问题。
而函数模板的重载,就是为了解决特定对象的问题,通过函数模板的重载,可以为这些特定的数据类型提供具象化的模板
Copyclass Person { | |
public: | |
int age; | |
}; | |
template<class T> | |
bool compare(const T& t1, const T& t2) { | |
return t1 > t2; | |
} | |
template<> | |
bool compare<Person>(const Person& p1, const Person& p2) { | |
return p1.age > p2.age; | |
} | |
int main() { | |
Person p1; | |
p1.age = 15; | |
Person p2; | |
p2.age = 12; | |
cout << compare(p1, p2) << endl; | |
return 0; | |
} |
类模板#
类模板和函数模板的定义和使用基本是一样的,但是类模板和函数模板还是有点区别:
- 类模板不能自动类型推导
template<class T1, class T2 = int> // 可以使用 = 指定默认类型 | |
class NumberOperator { | |
public: | |
T1 num1; | |
T2 num2; | |
void cal() { | |
cout << num1 + num2 << endl; | |
} | |
}; | |
int main() { | |
// 创建对象,不能类型推导,只能自己指定类型 | |
NumberOperator<int, int> op1; | |
op1.num1 = 10; | |
op1.num2 = 20; | |
op1.cal(); | |
// 创建对象 | |
NumberOperator<double> op2; // 默认 T2 = int | |
op2.num1 = 3.14; | |
op2.num2 = 10; | |
op2.cal(); | |
return 0; | |
} |
类模板做函数参数#
Copytemplate<class T1, class T2 = int> | |
class NumberOperation { | |
public: | |
T1 num1; | |
T2 num2; | |
void cal() { | |
cout << num1 + num2 << endl; | |
} | |
}; | |
// 参数中明确模板类 | |
void useNumberOperation(NumberOperation<int, int>& op) { | |
op.cal(); | |
} | |
// 参数中使用模板 | |
template<typename T1, typename T2> | |
void useNumberOperation02(NumberOperation<T1, T2>& op) { | |
op.cal(); | |
} | |
int main() { | |
// 参数明确模板类调用 | |
NumberOperation<int, int> op; | |
op.num1 = 10; | |
op.num2 = 20; | |
useNumberOperation(op); | |
// 参数模板 | |
NumberOperation<double, int> op2; | |
op2.num1 = 10.5; | |
op2.num2 = 5; | |
useNumberOperation02(op2); | |
return 0; | |
} |
类模板继承#
Copy// 定义模板类 | |
template<typename T> | |
class Animal { | |
public: | |
T arg; | |
}; | |
// 普通类继承模板类的时候,必须明确指定类型 | |
class Dog: Animal<int> { | |
// 这里继承到的arg的数据类型是int | |
}; | |
template<typename E> | |
class Person: Animal<E> { | |
// 这里继承到的arg的数据类型是E | |
}; |
类模板类外实现#
Copytemplate<typename T, typename M> | |
class NumberCalculator { | |
private: | |
T n1; | |
M n2; | |
public: | |
NumberCalculator() {} | |
NumberCalculator(T n1, M n2); | |
void add(); | |
}; | |
// 构造函数类外实现 | |
template<typename T, typename M> | |
NumberCalculator<T, M>::NumberCalculator(T n1, M n2) { | |
this->n1 = n1; | |
this->n2 = n2; | |
} | |
// 普通函数类外实现 | |
template<typename T, typename M> | |
NumberCalculator<T, M>::add() { | |
cout << n1 + n2 << endl; | |
} |
类模板头文件和源文件分离问题#
我们在写程序时,很多时候需要将类的声明和实现分开来写。将类的声明部分写到.h
文件中,将类的实现部分写到.cpp
文件中。在使用到这个类的时候,直接包含.h
文件即可。但当一个类是模板类时,这样做会出现问题。
我们虽然引入了.h
文件,但是模板类中的函数是在调用的时候才会创建,因此在编译阶段不会管对应的.cpp
文件中的实现部分。
而到了使用这个函数的时候,发现这个函数已经创建了但是没有实现,编译器也会因此而报错。相当于我们只是在.h
中声明了函数,但是并没有实现。
解决办法:
- 使用
#include
引入.cpp
文件 - 或是将类的声明和实现放到一个文件中(这个文件我们习惯上会定义为
.hpp
文件,但并不是绝对的,只是一个习惯和约定的问题。)
模板
模板介绍#
C++提供了函数模板(function template)。所谓函数模板。实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡事函数体相同的函数都可以使用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同的函数功能。
C++提供了两种模板机制:函数模板和类模板
意义:
- 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
- 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
函数模板#
下面举例说明一下函数模板的具体用法。
假设有两个需求:
- 设计一个函数,实现两个
int
变量的值的交换 - 设计一个函数,实现两个
double
变量的值的交换
void mySwap(int &a, int &b){ | |
int tmp = a; | |
a = b; | |
b = tmp; | |
} | |
void mySwap(double &a, double &b){ | |
double tmp = a; | |
a = b; | |
b = tmp; | |
} |
如果还有需求要交换其他类型的变量,还需要继续添加函数,而代码几乎一致。并且一旦需求有变更,每个函数都要一个一个修改,非常繁琐且重复。
发现两个函数逻辑结构完全相同,只有变量类型不同,如果能设计一个通用的函数,能够把类型当做参数传递到这个函数中,就简单多了——这就是函数模板!
定义#
- 用法:
template<typename T>
template
:模板关键字typename
:定义虚拟类型关键字,也可以使用classT
:定义的一个虚拟的类型,在这里暂时不确定是什么类型,等到调用这个函数的时候就可以确定了
template<typename T> | |
void mySwap(T &a, T &b){ | |
T tmp = a; | |
a = b; | |
b = tmp; | |
} | |
int main(){ | |
int a = 10, b = 20; | |
double x = 3.14, y = 9.9; | |
// 显式指定类型 | |
mySwap<int>(a, b); | |
// 可以自动根据实参的类型进行推导 | |
mySwap(a, b); | |
mySwap(x, y); | |
cout << "a:" << a << endl; | |
cout << "b:" << b << endl; | |
cout << "x:" << x << endl; | |
cout << "y:" << y << endl; | |
system("pause"); | |
return 0; | |
} |
函数模板使用案例#
- 需求
- 定义一个函数模板,实现对数组中元素进行升序排序
- 定义一个函数模板,实现将一个数组中元素拼接成为字符串返回
// 定义一个函数模板,实现对数组中元素进行升序排序 | |
template<class T> | |
void mySort(T arr[], int len){ | |
for (int i = 0; i < len; i++){ | |
for (int j = i; j < len; j++){ | |
if (arr[i] > arr[j]){ | |
T tmp = arr[i]; | |
arr[i] = arr[j]; | |
arr[j] = tmp; | |
} | |
} | |
} | |
} | |
// 定义一个函数模板,实现将一个数组中元素拼接成为字符串返回 | |
template<class T> | |
void showArray(T arr[], int len){ | |
cout << "["; | |
for (int i = 0; i < len - 1; i++){ | |
cout << arr[i] << ","; | |
} | |
cout << arr[len - 1] << "]" << endl; | |
} | |
int main(){ | |
// 定义一个int[] | |
int array1[] = { 1, 3, 5, 7, 9, 0, 8, 6, 4, 2 }; | |
int len1 = sizeof(array1) / sizeof(int); | |
mySort(array1, len1); | |
showArray(array1, len1); | |
// 定义一个double[] | |
double array2[] = { 3.14, 9.28, 3, 3.44, -9.2, 8.22 }; | |
int len2 = sizeof(array2) / sizeof(double); | |
mySort(array2, len2); | |
showArray(array2, len2); | |
// 定义一个char[] | |
char array3[] = { 'a', 'l', '1', 'm', 'k' }; | |
int len3 = sizeof(array3) / sizeof(char); | |
mySort(array3, len3); | |
showArray(array3, len3); | |
system("pause"); | |
return 0; | |
} |
函数模板与普通函数#
函数模板和普通函数在调用的时候需要注意:
- 普通函数的调用是可以发生自动类型转换的;而函数模板调用不允许发生自动类型转换
- 如果调用函数的时候,实参既可以匹配普通函数,又可以匹配函数模板,则优先匹配普通函数
函数模板重载#
函数模板虽然很通用,但并不是万能的,有时候也会出现不适配的情况
Copytemplate<class T> | |
bool compare(const T& t1, const T& t2) { | |
return t1 > t2; | |
} |
对于上述函数模板,如果比较的类型设置为自定义类型(如Person类型),则会出现无法比较的问题。
而函数模板的重载,就是为了解决特定对象的问题,通过函数模板的重载,可以为这些特定的数据类型提供具象化的模板
Copyclass Person { | |
public: | |
int age; | |
}; | |
template<class T> | |
bool compare(const T& t1, const T& t2) { | |
return t1 > t2; | |
} | |
template<> | |
bool compare<Person>(const Person& p1, const Person& p2) { | |
return p1.age > p2.age; | |
} | |
int main() { | |
Person p1; | |
p1.age = 15; | |
Person p2; | |
p2.age = 12; | |
cout << compare(p1, p2) << endl; | |
return 0; | |
} |
类模板#
类模板和函数模板的定义和使用基本是一样的,但是类模板和函数模板还是有点区别:
- 类模板不能自动类型推导
template<class T1, class T2 = int> // 可以使用 = 指定默认类型 | |
class NumberOperator { | |
public: | |
T1 num1; | |
T2 num2; | |
void cal() { | |
cout << num1 + num2 << endl; | |
} | |
}; | |
int main() { | |
// 创建对象,不能类型推导,只能自己指定类型 | |
NumberOperator<int, int> op1; | |
op1.num1 = 10; | |
op1.num2 = 20; | |
op1.cal(); | |
// 创建对象 | |
NumberOperator<double> op2; // 默认 T2 = int | |
op2.num1 = 3.14; | |
op2.num2 = 10; | |
op2.cal(); | |
return 0; | |
} |
类模板做函数参数#
Copytemplate<class T1, class T2 = int> | |
class NumberOperation { | |
public: | |
T1 num1; | |
T2 num2; | |
void cal() { | |
cout << num1 + num2 << endl; | |
} | |
}; | |
// 参数中明确模板类 | |
void useNumberOperation(NumberOperation<int, int>& op) { | |
op.cal(); | |
} | |
// 参数中使用模板 | |
template<typename T1, typename T2> | |
void useNumberOperation02(NumberOperation<T1, T2>& op) { | |
op.cal(); | |
} | |
int main() { | |
// 参数明确模板类调用 | |
NumberOperation<int, int> op; | |
op.num1 = 10; | |
op.num2 = 20; | |
useNumberOperation(op); | |
// 参数模板 | |
NumberOperation<double, int> op2; | |
op2.num1 = 10.5; | |
op2.num2 = 5; | |
useNumberOperation02(op2); | |
return 0; | |
} |
类模板继承#
Copy// 定义模板类 | |
template<typename T> | |
class Animal { | |
public: | |
T arg; | |
}; | |
// 普通类继承模板类的时候,必须明确指定类型 | |
class Dog: Animal<int> { | |
// 这里继承到的arg的数据类型是int | |
}; | |
template<typename E> | |
class Person: Animal<E> { | |
// 这里继承到的arg的数据类型是E | |
}; |
类模板类外实现#
Copytemplate<typename T, typename M> | |
class NumberCalculator { | |
private: | |
T n1; | |
M n2; | |
public: | |
NumberCalculator() {} | |
NumberCalculator(T n1, M n2); | |
void add(); | |
}; | |
// 构造函数类外实现 | |
template<typename T, typename M> | |
NumberCalculator<T, M>::NumberCalculator(T n1, M n2) { | |
this->n1 = n1; | |
this->n2 = n2; | |
} | |
// 普通函数类外实现 | |
template<typename T, typename M> | |
NumberCalculator<T, M>::add() { | |
cout << n1 + n2 << endl; | |
} |
类模板头文件和源文件分离问题#
我们在写程序时,很多时候需要将类的声明和实现分开来写。将类的声明部分写到.h
文件中,将类的实现部分写到.cpp
文件中。在使用到这个类的时候,直接包含.h
文件即可。但当一个类是模板类时,这样做会出现问题。
我们虽然引入了.h
文件,但是模板类中的函数是在调用的时候才会创建,因此在编译阶段不会管对应的.cpp
文件中的实现部分。
而到了使用这个函数的时候,发现这个函数已经创建了但是没有实现,编译器也会因此而报错。相当于我们只是在.h
中声明了函数,但是并没有实现。
解决办法:
- 使用
#include
引入.cpp
文件 - 或是将类的声明和实现放到一个文件中(这个文件我们习惯上会定义为
.hpp
文件,但并不是绝对的,只是一个习惯和约定的问题。)