技术分享(三)- 初步谈谈C++泛型编程

目录

 

前言

泛型编程

为什么需要泛型编程

给个经典例子

函数模板

例子

为什么函数模板能够执行不同的类型参数

特别说明

类模板

例子

为什么类模板能够执行不同的类型参数

类模板特化


前言

本文为之前工作中写了一些技术分享文档之一,主要是谈谈C++泛型编程,颗粒度略细,主要从模板的角度去谈。本文由于是较早写的笔记,因此可能引用了一些博文,如有发现,请跟我这边说,我这边会在文末补充引用。

泛型编程

  • 实现方式:在C++中,主要用函数模板和类模板

  • 作用:可以使编写的代码被很多不同的类型所共享,大大提高了代码的重用性

为什么需要泛型编程

给个经典例子

编写一个工具函数:实现两个数的转换

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

以上使用重载的方式,带来什么问题呢?

  • 是否符合需求?万一,我还需要一个float类型呢?

  • 如果需求有变,a或者b其中一个不能为0呢?就重复修改三个函数

  • 以上只是一个简单的函数,如果更复杂的话,重复劳动,容易出错,而且还带来很大的维护和调试工作量

以下为使用模板来写(typename与class作用完全一样,一般使用typename会多一些):

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

 

函数模板

常用于实现工具函数

例子

写一个简单的数组打印

template < typename T >
void arrPrint(T a[], int len)
{
     for(int i=0; i<len; i++)
    {
        cout << a[i] << ", ";
    }
    cout << endl;
}

调用

int a[5] = {5, 3, 2, 4, 1};
arrPrint<int>(a, 5);

或者说一个常用的获取数组长度的函数模板

template<class T>
int arrLength(T& arr)
{
    return sizeof(arr) / sizeof(arr[0]);
}

为什么函数模板能够执行不同的类型参数

实际上,编译器对函数模板进行了两次编译

  • 第一次编译时,首先去检查函数模板本身有没有语法错误

  • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数

  • 所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数

换句话说,是根据我们的调用来生成函数,这个函数叫模板函数

怎么验证?

直接使用函数指针来打印一下指向的地址即可,或者,查看编译后的汇编代码也可以

特别说明

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

  • 函数模板不接受隐式转换

  • 当有函数模板,以及普通重载函数时,编译器会优先考虑普通函数

  • 可以通过空模板实参列表来限定编译器只匹配函数模板

template<typename T>
void example(T& x, T& y)
{
    std::cout << "模板函数" << std::endl;
}
​
template<typename T1, typename T2>
void example(T1& x, T2& y)
{
    std::cout << "重载模板函数" << std::endl;
}
​
void example(int &x, char& y)
{
    std::cout << "普通函数" << std::endl;
}

调用

int a = 0, b = 0;
char c = 0;
​
//调用重载模板函数
example<int>(a, b);
example<int, char>(a, c);
​
// 函数模板不接受隐式转换
example<int>(a, b);  //报错,不提供隐式转换
​
//优先考虑普通函数
example(a, c);       
​
//如果想执行模板函数
example<>(a, c);

类模板

类模板通常应用于数据结构方面,使得类的实现不在关注数据元素的具体类型,而只关注需要实现的功能 比如: 数组类,链表类,Queue类,Stack类等

类模板中的函数声明和实现都必须在同一个文件,有时为了区分,一般文件名后缀为.hpp

例子

template <typename T1, typename T2>
class Example
{
public:
    T1 key;    
    T2 value;  
    Example(T1 k, T2 v) :key(k), value(v) { };
    void Func(T1 t) 
    {
        std::cout << "普通成员函数" << std::endl;
    }
    template <class T3>
    void Func(T3 t) 
    {
        std::cout << "成员函数模板" << std::endl;
    }
};

调用:

std::string str = "hello";
int a = 10;
Example<std::string, int> example(str, a);
​
example.Func(a);   //调用普通成员函数
example.Func<>(a); //调用成员函数模板
example.Func(str); //调用成员函数模板

为什么类模板能够执行不同的类型参数

和函数模板类似

类模板特化

类似于函数重载,存在多个相同的类名,但是模板类型都不一致

  • 完全特化:表示显示指定类型参数,模板声明只需写成template<>,并在类名右侧指定参数

  • 部分特化:表示通过特定规则约束类型参数,模板声明和类似,并在类名右侧指定参数

template <typename T1, typename T2>  
class Example                        
{
public:
    Example(T1 a, T2 b)
    {
        std::cout << "普通类模板" << std::endl;
    }
};
​
template <>                        
class Example<int, int>          
{
public:
    Example(int a, int b)
    {
        std::cout << "完全特化" << std::endl;
    }
​
};
​
template <typename T>                      
class Example< T&, T&>         
{
public:
    Example(T& a, T& b)
    {
        std::cout << "部分特化" << std::endl;
    }
};

调用:

int a = 3, b = 4;
float c = 4.5;
Example<int, float> ex1(a, c);
Example<int, int> ex2(a, b);
Example<int &, int &> ex3(a, b);

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值