C++解析(26):函数模板与类模板
0.目录
1.函数模板
- 1.1 函数模板与泛型编程
- 1.2 多参数函数模板
- 1.3 函数重载遇上函数模板
2.类模板
3.小结
1.函数模板
1.1 函数模板与泛型编程
C++中有几种交换变量的方法?
交换变量的方法——定义宏代码块 vs 定义函数:
- 定义宏代码块
- 优点:代码复用,适合所有的类型
- 缺点:编译器不知道宏的存在,缺少类型检查
- 定义函数
- 优点:真正的函数调用,编译器对类型进行检查
- 缺点:根据类型重复定义函数,无法代码复用
C++中有没有解决方案集合两种方法的优点?
泛型编程的概念——不考虑具体数据类型的编程方式
函数模板:
- 一种特殊的函数可用不同类型进行调用
- 看起来和普通函数很相似,区别是类型可被参数化
函数模板的语法规则:
- template关键字用于声明开始进行泛型编程
- typename关键字用于声明泛型类型
函数模板的使用:
- 自动类型推导调用
- 具体类型显示调用
示例——使用函数模板:
#include <iostream>
using namespace std;
template < typename T >
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template < typename T >
void Sort(T a[], int len)
{
for(int i=0; i<len; i++)
{
for(int j=i; j<len; j++)
{
if( a[i] > a[j] )
{
Swap(a[i], a[j]);
}
}
}
}
template < typename T >
void Println(T a[], int len)
{
for(int i=0; i<len; i++)
{
cout << a[i] << ", ";
}
cout << endl;
}
int main()
{
int a[5] = {5, 3, 2, 4, 1};
Println(a, 5);
Sort(a, 5);
Println(a, 5);
string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};
Println(s, 5);
Sort(s, 5);
Println(s, 5);
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
5, 3, 2, 4, 1,
1, 2, 3, 4, 5,
Java, C++, Pascal, Ruby, Basic,
Basic, C++, Java, Pascal, Ruby,
函数模板深入理解:
- 编译器从函数模板通过具体类型产生不同的函数
- 编译器会对函数模板进行两次编译
- 对模板代码本身进行编译
- 对参数替换后的代码进行编译
注意事项:
- 函数模板本身不允许隐式类型转换
- 自动推导类型时,必须严格匹配
- 显示类型指定时,能够进行隐式类型转换
示例——编译器从函数模板通过具体类型产生不同的函数:
#include <iostream>
using namespace std;
template < typename T >
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
typedef void(FuncI)(int&, int&);
typedef void(FuncD)(double&, double&);
int main()
{
FuncI* pi = Swap; // 编译器自动推导 T 为 int
FuncD* pd = Swap; // 编译器自动推导 T 为 double
cout << "pi = " << reinterpret_cast<void*>(pi) << endl;
cout << "pd = " << reinterpret_cast<void*>(pd) << endl;
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
pi = 0x40091e
pd = 0x40094a
可以看到,编译器通过函数模板产生了两个地址不同的实实在在的函数!
1.2 多参数函数模板
多参数函数模板——函数模板可以定义任意多个不同的类型参数:
对于多参数函数模板:
- 无法自动推导返回值类型
- 可以从左向右部分指定类型参数
示例——多参数函数模板:
#include <iostream>
using namespace std;
template
< typename T1, typename T2, typename T3 >
T1 Add(T2 a, T3 b)
{
return static_cast<T1>(a + b);
}
int main()
{
// T1 = int, T2 = double, T3 = double
int r1 = Add<int>(0.5, 0.8);
// T1 = double, T2 = float, T3 = double
double r2 = Add<double, float>(0.5, 0.8);
// T1 = float, T2 = float, T3 = float
float r3 = Add<float, float, float>(0.5, 0.8);
cout << "r1 = " << r1 << endl; // r1 = 1
cout << "r2 = " << r2 << endl; // r2 = 1.3
cout << "r3 = " << r3 << endl; // r3 = 1.3
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
r1 = 1
r2 = 1.3
r3 = 1.3
1.3 函数重载遇上函数模板
当函数重载遇上函数模板会发生什么?
函数模板可以像普通函数一样被重载:
- C++编译器优先考虑普通函数
- 如果函数模板可以产生一个更好的匹配,那么选择模板
- 可以通过空模板实参列表限定编译器只匹配模板
示例——重载函数模板:
#include <iostream>
using namespace std;
template < typename T >
T Max(T a, T b)
{
cout << "T Max(T a, T b)" << endl;
return a > b ? a : b;
}
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, T c)
{
cout << "T Max(T a, T b, T c)" << endl;
return Max(Max(a, b), c);
}
int main()
{
int a = 1;
int b = 2;
cout << Max(a, b) << endl; // 普通函数 Max(int, int)
cout << Max<>(a, b) << endl; // 函数模板 Max<int>(int, int)
cout << endl;
cout << Max(3.0, 4.0) << endl; // 函数模板 Max<double>(double, double)
cout << Max(5.0, 6.0, 7.0) << endl; // 函数模板 Max<double>(double, double, double)
cout << endl;
cout << Max('a', 100) << endl; // 普通函数 Max(int, int)
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
int Max(int a, int b)
2
T Max(T a, T b)
2
T Max(T a, T b)
4
T Max(T a, T b, T c)
T Max(T a, T b)
T Max(T a, T b)
7
int Max(int a, int b)
100
2.类模板
2.1 类模板
在C++中是否能够将泛型的思想应用于类?
类模板:
- 一些类主要用于存储和组织数据元素
- 类中数据组织的方式和数据元素的具体类型无关
- 如:数组类,链表类,Stack类,Queue类,等
C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。
C++中的类模板:
- 以相同的方式处理不同的类型
- 在类声明前使用template进行标识
- <typename T>用于说明类中使用的泛指类型 T
类模板的应用:
- 只能显示指定具体类型,无法自动推导
- 使用具体类型<Type>定义对象
- 声明的泛指类型 T 可以出现在类模板的任意地方
- 编译器对类模板的处理方式和函数模板相同
- 从类模板通过具体类型产生不同的类
- 在声明的地方对类模板代码本身进行编译
- 在使用的地方对参数替换后的代码进行编译
示例——类模板:
#include <iostream>
using namespace std;
template < typename T >
class Operator
{
public:
T add(T a, T b) { return a + b; }
T minus(T a, T b) { return a - b; }
T multiply(T a, T b) { return a * b; }
T divide(T a, T b) { return a / b; }
};
string operator-(string& l, string& r)
{
return "Minus";
}
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
Operator<string> op2;
cout << op2.add("Hello", "World") << endl;
cout << op2.minus("Hello", "World") << endl;
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
3
HelloWorld
Minus
类模板的工程应用:
- 类模板必须在头文件中定义
- 类模板不能分开实现在不同的文件中
- 类模板外部定义的成员函数需要加上模板<>声明
示例——模板类的工程应用:
// Operator.h
#ifndef _OPERATOR_H_
#define _OPERATOR_H_
template < typename T >
class Operator
{
public:
T add(T a, T b);
T minus(T a, T b);
T multiply(T a, T b);
T divide(T a, T b);
};
template < typename T >
T Operator<T>::add(T a, T b)
{
return a + b;
}
template < typename T >
T Operator<T>::minus(T a, T b)
{
return a - b;
}
template < typename T >
T Operator<T>::multiply(T a, T b)
{
return a * b;
}
template < typename T >
T Operator<T>::divide(T a, T b)
{
return a / b;
}
#endif
// test.cpp
#include <iostream>
#include "Operator.h"
using namespace std;
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
cout << op1.multiply(4, 5) << endl;
cout << op1.minus(5, 6) << endl;
cout << op1.divide(10, 5) << endl;
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
3
20
-1
2
2.2 多参数类模板与特化
类模板可以定义任意多个不同的类型参数:
类模板可以被特化:
- 指定类模板的特定实现
- 部分类型参数必须显示指定
- 根据类型参数分开实现类模板
类模板的特化类型:
- 部分特化——用特定规则约束类型参数
- 完全特化——完全显示指定类型参数
示例——类模板的特化:
#include <iostream>
using namespace std;
template
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
template
< typename T1, typename T2 >
class Test < T1*, T2* > // 关于指针的特化实现
{
public:
void add(T1* a, T2* b)
{
cout << "void add(T1* a, T2* b)" << endl;
cout << *a + *b << endl;
}
};
template
< typename T >
class Test < T, T > // 当 Test 类模板的两个类型参数完全相同时,使用这个实现
{
public:
void add(T a, T b)
{
cout << "void add(T a, T b)" << endl;
cout << a + b << endl;
}
void print()
{
cout << "class Test < T, T >" << endl;
}
};
template
< >
class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
int main()
{
Test<int, float> t1;
Test<long, long> t2;
Test<void*, void*> t3;
t1.add(1, 2.5);
cout << endl;
t2.add(5, 5);
t2.print();
cout << endl;
t3.add(NULL, NULL);
cout << endl;
Test<int*, double*> t4;
int a = 1;
double b = 0.1;
t4.add(&a, &b);
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
void add(T1 a, T2 b)
3.5
void add(T a, T b)
10
class Test < T, T >
void add(void* a, void* b)
Error to add void* param...
void add(T1* a, T2* b)
1.1
类模板特化注意事项:
- 特化只是模板的分开实现
- 本质上是同一个类模板
- 特化类模板的使用方式是统一的
- 必须显示指定每一个类型参数
2.3 特化的深度分析
类模板特化与重定义有区别吗?函数模板可以被特化吗?
重定义和特化的不同:
- 重定义
- 一个类模板和一个新类(或者两个类模板)
- 使用的时候需要考虑如何选择的问题
- 特化
- 以统一的方式使用内模板和特化类
- 编译器自动优先选择特化类
函数模板只支持类型参数完全特化:
示例——函数模板完全特化与函数重载:
#include <iostream>
using namespace std;
template
< typename T >
bool Equal(T a, T b)
{
cout << "bool Equal(T a, T b)" << endl;
return a == b;
}
template
< >
bool Equal<double>(double a, double b)
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal<double>(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
bool Equal(double a, double b)
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
int main()
{
cout << Equal( 1, 1 ) << endl;
cout << Equal( 0.001, 0.001 ) << endl;
cout << Equal<>( 0.001, 0.001 ) << endl;
return 0;
}
运行结果为:
[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out
bool Equal(T a, T b)
1
bool Equal(double a, double b)
1
bool Equal<double>(double a, double b)
1
工程中的建议:
当需要重载函数模板时,优先考虑使用模板特化;当模板特化无法满足需求,再使用函数重载!
3.小结
- 函数模板是泛型编程在C++中的应用方式之一
- 函数模板能够根据实参对参数类型进行推导
- 函数模板支持显示的指定参数类型
- 函数模板是C++中重要的代码复用方式
- 函数模板通过具体类型产生不同的函数
- 函数模板可以定义任意多个不同的类型参数
- 函数模板中的返回值类型必须显示指定
- 函数模板可以像普通函数一样被重载
- 泛型编程的思想可以应用于类
- 类模板以相同的方式处理不同类型的数据
- 类模板非常适用于编写数据结构相关的代码
- 类模板在使用时只能显示指定类型
- 类模板可以定义任意多个不同的类型参数
- 类模板可以被部分特化和完全特化
- 特化的本质是模板的分开实现
- 函数模板只支持完全特化
- 工程中使用模板特化代替类(函数)重定义
C++解析(26):函数模板与类模板的更多相关文章
- [Reprint] C++函数模板与类模板实例解析
这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下 本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...
- C++_进阶之函数模板_类模板
C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...
- C++复习:函数模板和类模板
前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...
- C++进阶-1-模板基础(函数模板、类模板)
C++进阶 模板 1.1 函数模板 1 #include<iostream> 2 using namespace std; 3 4 // 模板 5 6 // 模板的简单实例 7 // 要求 ...
- 【校招面试 之 C/C++】第2题 函数模板、类模板、特化、偏特化
1.C++模板 说到C++模板特化与偏特化,就不得不简要的先说说C++中的模板.我们都知道,强类型的程序设计迫使我们为逻辑结构相同而具体数据类型不同的对象编写模式一致的代码,而无法抽取其中的共性,这样 ...
- C++学习之函数模板与类模板
泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上操作多种数据类型,泛型是一般化并可重复使用的意思.泛型编程最初诞生于C++中,目的是为了实现C++ ...
- C++ 函数模板与类模板(使用 Qt 开发编译环境)
注意:本文中代码均使用 Qt 开发编译环境,如有疑问和建议欢迎随时留言. 模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数多态性.所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一 ...
- C++ 模板常见特性(函数模板、类模板)
背景 C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量. C++ 提高代码的可重用性主要有两方面: 继承 模板 继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的 ...
- 学习C++模板,类模板
当我们使用向量时,会经常使用形如:vector<int> a的式子.这个表达式就是一个类模板实例化的例子,vector是一个类模板,我们给他传递模板参数(见<>里),然后创建一 ...
随机推荐
- POJ2513_Colored Sticks_KEY
题目传送门 题目大意:给你若干根木棍,每根木棍有前后两种颜色,连接两根木棍需要前后颜色相同,求能否将所有木棍连接在一起. Solution: 不要将木棍看成点,将颜色看成点. 其实就是求是否存在欧拉路 ...
- 1563: [NOI2009]诗人小G
1563: [NOI2009]诗人小G https://lydsy.com/JudgeOnline/problem.php?id=1563 分析: 直接转移f[i]=f[j]+cost(i,j),co ...
- iOS 影音新格式 HEIF HEVC
苹果在 iOS 11 的发布会上,推出了两种新的媒体格式 HEIF HEVC,都是为了保证画质的情况下,大大减少视频.照片的大小. 一.简介 HEVC全称 High Efficiency Video ...
- appium+python自动化☞环境搭建
前言:appium可以说是做app最火的一个自动化框架,它的主要优势是支持android和ios,另外脚本语言也是支持java和Python.略懂Python,所以接下来的教程是 appium+pyt ...
- url乱码问题
//url乱码,有时候要解码2次才能成功 String url=URLDecoder.decode(URLDecoder.decode(returnUrl, "UTF-8"),&q ...
- shell命令之at 执行一次性定时任务的用法
大家都知道crontab是执行定时任务的命令,那么at又是什么呢? 其实at也是定时任务命令,不同的是crontab是执行循环任务,at执行一次性任务 首先说下时间例子 Minute at no ...
- angularjs工作原理解析
个人觉得,要很好的理解AngularJS的运行机制,才能尽可能避免掉到坑里面去.在这篇文章中,我将根据网上的资料和自己的理解对AngularJS的在启动后,每一步都做了些什么,做一个比较清楚详细的解析 ...
- C++ map 遍历
#include <iostream> #include <map> using namespace std; int main(){ map<int,int> m ...
- YQCB冲刺周第一天
团队讨论的照片 任务看板为 今天小组成员讨论了每个页面的排版,每个页面的跳转,以及页面的排版. 今天准备编写登录界面的.注册界面的代码. 遇到的困难是用户记账时选择的分类标准很多,最后将其整合,删减.
- python struct详解
转载:https://www.cnblogs.com/gala/archive/2011/09/22/2184801.html 有的时候需要用python处理二进制数据,比如,存取文件,socket操 ...