C++ 模板(函数模板与类模板)
模板
模板介绍
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
:定义虚拟类型关键字,也可以使用class
T
:定义的一个虚拟的类型,在这里暂时不确定是什么类型,等到调用这个函数的时候就可以确定了
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;
}
函数模板与普通函数
函数模板和普通函数在调用的时候需要注意:
- 普通函数的调用是可以发生自动类型转换的;而函数模板调用不允许发生自动类型转换
- 如果调用函数的时候,实参既可以匹配普通函数,又可以匹配函数模板,则优先匹配普通函数
函数模板重载
函数模板虽然很通用,但并不是万能的,有时候也会出现不适配的情况
template<class T>
bool compare(const T& t1, const T& t2) {
return t1 > t2;
}
对于上述函数模板,如果比较的类型设置为自定义类型(如Person类型),则会出现无法比较的问题。
而函数模板的重载,就是为了解决特定对象的问题,通过函数模板的重载,可以为这些特定的数据类型提供具象化的模板
class 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;
}
类模板做函数参数
template<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;
}
类模板继承
// 定义模板类
template<typename T>
class Animal {
public:
T arg;
};
// 普通类继承模板类的时候,必须明确指定类型
class Dog: Animal<int> {
// 这里继承到的arg的数据类型是int
};
template<typename E>
class Person: Animal<E> {
// 这里继承到的arg的数据类型是E
};
类模板类外实现
template<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++ 模板(函数模板与类模板)的更多相关文章
- C++—模板(2)类模板与其特化
我们以顺序表为例来说明,普通顺序表的定义如下: typedef int DataType; //typedef char DataType; class SeqList { private : Dat ...
- C++ 模板的编译 以及 类模板内部的实例化
在C++中.编译器在看到模板的定义的时候.并不马上产生代码,仅仅有在看到用到模板时,比方调用了模板函数 或者 定义了类模板的 对象的时候.编译器才产生特定类型的代码. 一般而言,在调用函数的时候,仅仅 ...
- 类模板、Stack的类模板实现(自定义链栈方式,自定义数组方式)
一.类模板 类模板:将类定义中的数据类型参数化 类模板实际上是函数模板的推广,可以用相同的类模板来组建任意类型的对象集合 (一).类模板的定义 template <类型形参表> clas ...
- C++_进阶之函数模板_类模板
C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...
- C++复习:函数模板和类模板
前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...
- C++ 函数模板与类模板(使用 Qt 开发编译环境)
注意:本文中代码均使用 Qt 开发编译环境,如有疑问和建议欢迎随时留言. 模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数多态性.所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一 ...
- [Reprint] C++函数模板与类模板实例解析
这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下 本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...
- c++的函数模板和类模板
函数模板和普通函数区别结论: 函数模板不允许自动类型转化 普通函数能够进行自动类型转换 函数模板和普通函数在一起,调用规则: 1 函数模板可以像普通函数一样被重载 2 C++编译器优先考虑普通函数 3 ...
- 类模板语法知识体系梳理(包含大量常犯错误demo,尤其滥用友元函数的错误)
demo 1 #include <iostream> #include <cstdio> using namespace std; //template <typenam ...
- [C++]模板类和模板函数
参考: C++ 中模板使用详解 C++模板详解 概念 为了避免因重载函数定义不全面而带来的调用错误,引入了模板机制 定义 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模 ...
随机推荐
- [oeasy]python0083_[趣味拓展]字体样式_正常_加亮_变暗_控制序列
字体样式 回忆上次内容 上次了解了 一个新的转义模式 \033 逃逸控制字符 esc esc 让输出 退出 标准输出流 进行 控制信息的设置 可以 清屏 也可以 设置光标输出的位置 还能做 ...
- oeasy教您玩转vim - 45 - # 按行编辑
按行编辑 回忆上节课内容 上次我们主要就是综合运用 很好玩的,更快速的解决问题 进行计算 ctrl+a,将具体的数字加1 ctrl+x,将具体的数字减1 5ctrl+a,将具体的数字加5 一次命令 ...
- 调试 Node.js
调试 Node.js 调试器 调试器是一种软件工具,用于通过分析方法观察和控制程序的执行流 设计目标:帮助找出 bug 的根本原因,并帮助你解决它 工作方式:将程序托管在自己的执行进程中或者作为附加到 ...
- iOS开发基础136-防暴力点击
要在Objective-C中创建一个高度可复用的工具类,以防止按钮的暴力点击,并且使用切面编程(AOP)的方式,我们可以考虑使用Aspects这个库来实现方法的拦截.以下是具体的实现步骤: 第一步:引 ...
- python_xecel
移动并重命名工作簿 1 from pathlib import Path # 导入pathlib模块的path类 2 import time 3 4 # Press the green button ...
- 如何自动实现本地AD中禁用的用户从地址列表中隐藏掉?
我的博客园:https://www.cnblogs.com/CQman/ 如何自动实现本地AD中禁用的用户从地址列表中隐藏掉? 需求信息: 用户本地AD用户通过ADConnect同步到O365,用户想 ...
- 【MySQL】MGR高可用搭建
MySQL8.0.27如何安装 https://www.cnblogs.com/mindzone/p/15450312.html 部署过程中各种问题可参考的解决方案 我遇见的搭建问题,解决方案参考下面 ...
- 【Kafka】03 Shell 操作
查看Kafka主题列表 $KAFKA_HOME/bin/kafka-topics.sh \ --zookeeper centos7-02:2181,centos7-03:2181,centos7-04 ...
- 【JavaScript】下滑线命名转驼峰命名处理
同事写接口返回的JSON属性名称始终不一致,一会下划线一会驼峰 然后自己封装了一个: function toHump(name){ var newName = name.toLowerCase(); ...
- 所在单位近日购入Dell poweredge T640型号服务器,安装Ubuntu18.04.5 server操作系统,服务器万兆网卡,网线连接到千兆交换机上,不能识别网卡——解决方案
如题目所说: 所在单位近日购入Dell poweredge T640型号服务器,安装Ubuntu18.04.5 server操作系统,服务器万兆网卡,网线连接到千兆交换机上,不能识别网卡. 服务器 ...