C++知识点复习 ---- 模板相关

一. 函数模板的概念和意义

1.问题引入:

C++中有几种交换变量的方法? 定义宏代码块 VS 定义函数
代码示例:

#include <iostream>
#include<string>
using namespace std;
#define SWAP(t,a,b)   \    //定义宏
do                  \
{                   \
    t c = a;        \
    a = b;          \
    b = c;          \
}while(0);
 
void Swap(int& a, int& b)   //定义函数
{
    int c = a;
    a = b;
    b = c;
}
void Swap(double& a, double& b)
{
    double c = a;
    a = b;
    b = c;
}
void Swap(string& a, string& b)
{
    string c = a;
    a = b;
    b = c;
}
int main()
{
    int a = 0;
    int b = 1;  
    Swap(a, b);
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    
    double m = 2;
    double n = 3;
    Swap(m, n);
    cout << "m = " << m << endl;
    cout << "n = " << n << endl;
    
    string d = "Hello";
    string t = "World";
    Swap(d, t);
    cout << "d = " << d << endl;
    cout << "t = " << t << endl;
    return 0;
}
运行结果
a = 1
b = 0
m = 3
n = 2
d = Hello
t = World

定义宏代码块:
优点:代码复用,适合所有的类型
缺点:编译器不知道宏的存在,缺少类型检查
定义函数:
优点:真正的函数调用,编译器对类型进行检查
缺点:根据类型重复定义函数,无法代码复用

2.泛型编程

泛型编程集合了以上两种方法的优点

泛型编程的概念:
不考虑具体数据类型的编程方式

对于Swap函数可以考虑下面的泛型写法:

void Swap(T& a, T& b)
{
    T t = a;
    a = b;
    b = t;
}

Swap泛型写法中T不是一个具体的数据类型,而是泛指任意的数据类型。

C++中的泛型编程
函数模板:
(1)一种特殊的函数可用不同类型进行调用
(2)看起来和普通函数相似,区别是类型可被参数化

template<typename T>
void Swap(T& a, T& b)
{
    T t = a;
    a = b;
    b = t;
}

函数模板的语法规则:
(1)template关键字用于声明开始进行泛型编程。
(2)typename关键字用于声明泛指类型,这里是告诉编译器T是一个泛指类型。

函数模板的使用:
(1)自动类型推导调用
(2)具体类型显式调用

int a = 0;
int b = 1;
swap(a,b);  //自动推导

float c = 2;
float d = 3;
swap<float>(c,d);  //显式调用

函数模板示例:

初体验函数模板:
#include <iostream>
#include<string>
using namespace std;
template <typename T>
void Swap(T& a, T& b)
{
    T c = a;
    a = b;
    b = c;
}
int main()
{
    int a = 0;
    int b = 1;
   
    Swap(a, b);    //Swap<int>(a,b);
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    double m = 2;
    double n = 3;
    Swap(m, n);    //Swap<double>(a,b);
    cout << "m = " << m << endl;
    cout << "n = " << n << endl;
    string d = "Hello";
    string t = "World";
    Swap(d, t);    //Swap<string>(a,b);
    cout << "d = " << d << endl;
    cout << "t = " << t << endl;
    return 0;
}

深度体验函数模板:
#include <iostream>
#include<string>
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;
}
运行结果
5,3,2,4,1,
1,2,3,4,5,
Java,C++,Pascal,Ruby,Basic,
Basic,C++,Java,Pascal,Ruby,

小结:
函数模板是泛型编程在C++中的应用方式之一
函数模板能够根据实参对参数类型进行推导
函数模板支持显示的指定参数类型
函数模板是C++中重要的代码复用方式

3.函数模板的深入理解

  • 编译器从函数模板通过具体类型产生不同的函数
  • 编译器会对函数模板进行两次编译
    (1)对模板代码本身进行编译
    (2)对参数替换后的代码进行编译

注意事项:
函数模板本身不允许隐式类型转换
(1)自动推导类型时,必须严格匹配
(2)显示类型指定时,能够进行隐式类型转换

函数模板的本质示例:
编译器会根据函数模板产生真实的函数

#include <iostream>
#include<string>
using namespace std;
class Test
{
    Test(const Test&); //private类型,不能进行拷贝构造函数
public:
    Test()
    {
    }
};
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&);
typedef void(FuncT)(Test&, Test&);
int main()
{
    FuncI* pi = Swap;//编译器自动推导T为int
    FuncD* pd = Swap;//编译器自动推导T为double
    //FuncT* pt = Swap;//编译器自动推导T为Test
    cout << "pi = " << reinterpret_cast<void*>(pi) << endl;
    cout << "pd = " << reinterpret_cast<void*>(pd) << endl;  
    //cout << "pt = " << reinterpret_cast<void*>(pt) << endl;
    return 0;
}
进行分析:
class Test
{
    Test(const Test&);
public:
    Test()
    {
    }
};
Test(const Test&);没有这句话时,运行结果
pi = 0x80487e4
pd = 0x8048806
pt = 0x8048828
Test(const Test&);有这句话时,Test为私有成员,运行结果
57-1.cpp: In instantiation of ‘void Swap(T&, T&) [with T = Test]:
57-1.cpp:31:17:   required from here
57-1.cpp:8:5: error: ‘Test::Test(const Test&)’ is private
     Test(const Test&);
     ^
57-1.cpp:18:11: error: within this context
     T c = a;
所以把这两句话注释掉
//FuncT* pt = Swap;//编译器自动推导T为Test
//cout << "pt = " << reinterpret_cast<void*>(pt) << endl;

4.多参数函数模板

函数模板可以定义任意多个不同的参数类型

template <typename T1, typename T2, typename T3>
T1 Add(T2 a, T3 b)
{
    return static_cast<T1>(a + b);
}
---->
int r = Add<int, float, float>(0.5, 0.8);

对于多参数函数模板:
(1)无法自动推导返回值类型
(2)可以从左向右部分指定类型参数

//T1 = int, T2 = double, T3 = double
int r1 = Add<int>(0.5, 0.8);

//T1 = int, T2 = float, T3 = double
int r2 = Add<int, float>(0.5, 0.8);

//T1 = int, T2 = float, T3 = double
int r3 = Add<int, float, float>(0.5, 0.8);

工程中将返回值参数作为第一个参数类型!

多参数函数模板示例:

#include <iostream>
#include <string>
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;
}
运行结果
r1 = 1
r2 = 1.3
r3 = 1.3

5.重载函数模板
函数模板可以像普通函数一样被重载

  • C++编译器优先考虑普通函数
  • 如果函数模板可以产生一个更好的匹配,那么选择模板
  • 可以通过空模板实参列表限定编译器只匹配模板
int r1 = Max(1, 2); //优先考虑
double r2 = Max<>(0.5, 0.8); // Max<>限定编译器只匹配函数模板

重载函数模板示例:

#include <iostream>
#include <string>
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)  //将重载Max模板
{
    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 << 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 << Max('a', 100) << endl;        //普通函数Max(int, int)
    
    return 0;
}
运行结果
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

小结:
函数模板通过具体类型产生不同的函数
函数模板可以定义任意多个不同的类型参数
函数模板中的返回值类型必须显式指定
函数模板可以像普通函数一样被重载

二.类模板的概念和意义

1.类模板

  • 一些类主要用于存储和组织数据元素
  • 类中数据组织的方式和数据元素的具体类型无关
    如:数组类,链表类,Stack类,Queue类,等

C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。

C++中的类模板:

  • 以相同的方式处理不同的类型
  • 在类声明前使用template进行标识
  • typename T 用于说明类中使用的泛指类型T
template <typename T>
class Operator
{
public:
    T op(T a, T b);
};

类模板的应用:

  • 只能显式指定具体类型, 无法自动推导
  • 使用具体类型<Type>定义对象
Operator<int> op1;
Operator<string> op2;
int i = op1.op(1,2);
string s = op2.op("Hello", "World");

编译器对类模板的处理:

  • 声明的泛指类型T可以出现在类模板的任意地方
  • 编译器对类模板的处理方式和函数模板相同
    (1)从类模板通过具体类型产生不同的类
    (2)在声明的地方对类模板代码本身进行编译
    (3)在使用的地方对参数替换后的代码进行编译

类模板示例:

#include<iostream>
#include<string>
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;
}
运行结果
3
Hello World
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

main.cpp
#include<iostream>
#include<string>
#include "Operator.h"
using namespace std;
int main()
{
    Operator<int> op1;
    cout << op1.add(1,2) << endl;
    cout << op1.minus(5,6) << endl;
    cout << op1.multiply(4,5) << endl;
    cout << op1.divide(10,5) << endl;
    return 0;
}
运行结果
3
-1
20
2

小结:
泛型编程的思想可以应用于类
类模板以相同的方式处理不同类型的数据
类模板非常适用于编写数据结构相关的代码
类模板在使用时只能显式指定类型

2.多参数类模板

类模板可以定义任意多个不同的类型参数

template <typename T1, typename T2>
class Test
{
public:
    void add(T1 a, T2 b);
}
---->
Test<int, float> t;

类模板可以被特化:

  • 指定类模板的特定实现
  • 部分类型参数必须显示指定
  • 根据类型参数分开实现类模板
template <typename T1, typename T2>
class Test
{
};
----> 部分特化
template <typename T>
class Test <T, T>
{
}

类模板的特化类型:

  • 部分特化:用特定规则约束类型参数
  • 完全特化:完全显式指定类型参数
template <typename T1, typename T2>
class Test
{
};
----> 完全特化
template < >
class Test <int, int>
{
}

类模板的特化示例: !!重要 !!

#include<iostream>
#include<string>
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 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;
    }
};

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;
    }
};

int main()
{
    Test<int, float> t1;
    Test<long, long> t2;
    Test<void*, void*> t3;
    
    t1.add(1,2.5);    
    t2.add(5,5);
    t2.print();
    t3.add(NULL, NULL);
    
    Test<int*, double*> t4;
    int a = 1;
    double b = 0.1;
    t4.add(&a, &b);
    return 0;
}
运行结果
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

类模板特化注意事项:

  • 特化只是模板的分开实现,本质上是同一个模板
  • 特化类模板的使用方式是统一的,必须显式指定每一个类型参数

3.特化的深度分析

重定义和特化的不同:

  • 重定义:
    (1)一个类模板和一个新类(或者两个类模板)
    (2)使用的时候需要考虑如何选择的问题
  • 特化:
    (1)以统一的方式使用类模板和特化类
    (2)编译器自动优先选择特化类

函数模板只支持类型参数完全特化,不支持部分特化:

template <typename T>  //函数模板定义
bool Equal(T a, T b)
{
    return a == b;
}
template < >  //函数模板完全特化
bool Equal<void*>(void* a, void* b)
{
    return a == b;
}

示例:

#include<iostream>
#include<string>
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 < >
class Test <void* , void*>
{
public:
    void add(void* a, void* b)
    {
	cout << "void  add(void* a, void* b)" << endl;
	cout << "Error to add void* param..." << endl;
    }
};
*/
class Test_Void //重定义
{
public:
    void add(void* a, void* b)
    {
		cout << "void  add(void* a, void* b)" << endl;
		cout << "Error to add void* param..." << endl;
    }
};

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.00000000001;
    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.00000000001;
    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<double>(0.001, 0.001) << endl;和cout << Equal<>(0.001, 0.001) << endl;两种写法是等价的
    //cout << Equal(0.001, 0.001) << endl;//调用全局重载函数,因为编译器优先选择
    cout << Equal<>(0.001, 0.001) << endl;//告诉编译器放弃重载函数,直接使用模板完全特化
    return 0;
}
运行结果
bool Equal(T a, T b) 
1
bool Equal<double>(double a, double b)//函数模板的完全特化实现 
1

工程中的建议:
当需要重载函数模板时,优先考虑使用模板特化;
当模板特化无法满足需求,再使用函数重载!

小结:
类模板可以定义任意多个不同的类型参数
类模板可以被部分特化和完全特化
特化的本质是模板的分开实现
函数模板只支持完全特化
工程中使用模板特化代替类(函数)重定义

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值