C++模板——函数模板与类模板

1.泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

2.函数模板

2.1函数模板的概念

函数模板代表了一个函数家族,该函数模板与类型无关。在使用时被参数化。

2.2函数模板的格式

template<typename T1,typename T2,typename Tn>

  • 这里定义的是类型
template<typename T1,typename T2,,typename Tn>
返回类型 函数名(参数列表)
{undefined
  //函数体
}

另外,class Ttypename T都可以

template<class T> //两个都可以,以后基本用这个
template<typename T>//两个都可以

2.4函数模板的实例

template<class T>
void Swap(T& x1,T& x2)
{
	T x=x1;
    x1=x2;
    x2=x;
}
int main()
{
	int a=0,b=1;
    Swap(a,b);
    double c=1.11,d=2.22;
    Swap(c,d);
    char e='a',f='b';
    Swap(e,f);
    return 0;
}

2.3函数模板的原理

image-20220121151635957

当我们在VS中进行调式函数模板的时候,请问下面调用的是否是同一个函数?

从调试的时候看起来是同一个,但是实际上不是同一个。

image-20220121153333162

  1. 这个Swap实际上不是一个函数,是一个模板,用这个模板去生成要的函数
  2. 我们不能调用函数模板,调用的是函数模板实例化生成的对应类型的函数
  3. 编译器帮我们生成了对应的两个函数。

这里做的叫做模板的实例化

  1. 在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如,当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于double类型也是如此。

模板原理:我们写了模板,编译器通过调用函数模板和类模板的类型,实例化出对应的函数或者类。

template<class T>
void Swap(T& x1,T& x2)
{
	T x=x1;
    x1=x2;
    x2=x;
}
int main()
{
	int a=0,b=1;
    Swap(a,b);
    double c=1.11,d=2.22;
    Swap(c,d);
    return 0;
}

因此既然是编译阶段发生的,所以对于只是单纯地检查语法问题。下列代码实际是可以编译通过的。

template<typename T>
void Swap(T& x1,T &x2)
{
    T x=x1;
    x1=x2;
    x2 =x1 ///这里没有;也能编译通过。因为编译器不编译模具,只编译实例化
        //内部实现不报错,架子还是会检查的
}//但是没}会报错
int main()
{
    //没有调用不会实例化
}

2.4函数模板的实例化

2.4.1隐式实例化

隐式实例化:让编译器根据实参推演模板参数的实际类型

一般的场景为:

template<class T>
T Add(const T& left,const T&right)
{
    return left+right;
}
int main()
{
    int a1=10,a2=20;
    double d1=10.0,d2=20.0;
    ///隐式实例化(T的类型是编译器自己推导的)
    Add(a1,a2);
    Add(d1,d2);
 
}

而对于下述推导出来两个不同的类型参数却使用同一个T的情况就会报错。

template<class T>
T Add(const T& left,const T&right)
{
    return left+right;
}
int main()
{
	int a1=10,a2=20;
    double d1=10.0;double d2=20.0;
    Add(a1,d1);//error
}

因为在编译期间,编译器根据实参推演模板参数的实际类型时,根据实参a将T推演为int,根据实参b将T推演为double,但是模板参数列表中只有一个T,编译器无法确定此处应该将T确定为int还是double。
 此时,我们有两种处理方式,第一种就是我们在传参时将b强制转换为int类型,第二种就是使用下面说到的显示实例化。

强制转化类型处理

template<class T>
T Add(const T& left,const T&right)
{
    return left+right;
}
int main()
{
	int a1=10,a2=20;
    double d1=10.0;double d2=20.0;
    Add(a1,(int)d1);	//solution1:仍然隐式实例化
    //solution2:显示实例化,指定T的类型
    Add<int>(a1,d1);   
}
2.4.2显式实例化

显示实例化:在函数名后的<>中指定模板参数的实际类型

template<class T>
T Add(const T& left,const T&right)
{
    return left+right;
}
int main()
{
	int a1=10,a2=20;
    double d1=10.0;double d2=20.0;
    //solution2:显示实例化,指定T的类型
    Add<int>(a1,d1);   
}

注意:使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,则编译器将会报错。

2.5模板的参数匹配原则

2.5.1非模板函数和同名函数模板

要考虑到编译器编译阶段生成实例化是需要成本的,所以没有显式规定function<type>默认会使用非模板函数。

显式规定:

//专门处理int的加法函数
int Add(int left,int right)
{
	return left+right;
}
//通用加法
template<class T>
T Add(T left,T right)
{
	return left+right;
}
int main()
{
	Add(1,2); //与非模板函数匹配,编译器不需要特化.实例化要额外花费
    Add<int>(1,2); //调用编译器特化的Add版本.显示实例化
}

image-20220121155531345

#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
	return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{
	return x + y;
}
int main()
{
	int a = Add(10, 20); 
	int b = Add(2, 2.2); 
	return 0;
}

image-20220121155410595

2.5.2模板函数不允许自动类型转换
// 专门处理int的加法函数
int Add(int left, int right)
{
    return left + right;
}
// 通用加法函数
template<class T1, class T2>
    T1 Add(T1 left, T2 right)
{
    return left + right;
}
int main()
{	
    int a =Add( 2,2.2 );
    return 0;
}

image-20220121155630620

因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。

3.类模板

3.1类模板的定义格式

template<class T1,class T2,,class Tn>
class 类模板名
{undefined
  //类内成员声明
};
template<class T>
class Score
{
public:
	void Print()
	{
		cout << "物理:" << _A << endl;
		cout << "化学:" << _B << endl;
		cout << "生物:" << _C << endl;
	}
private:
	T _A
	T _B;
	T _C;
};

注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。

template<class T>
class Vector
{ 
public :
    ...
     //在类中声明,在类外定义。
    ~Vector();
private:
    T* _pData;
    size_t _size;
    size_t _capacity; 
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
    Vector<T>::~Vector()
    {
        if(_pData)
            delete[] _pData;
        _size = _capacity = 0;
    }

3.2类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

template<class T>
class vector
{
  public:
    void push_back(const T&x);
  private:
    ...
};
void vector<T>::push_back(const T&x){} //vector<T>是类型
int main()
{
    // Vector类名,Vector<int>才是类型
    Vector<int> s1;
    Vector<double> s2;
}

4.C++模板对C的改进

  • C语言如何实现一个Stack

    • 如何定义

      struct Stack_C
      {
          int* a;
          int _size; //_top
          int _capacity;
      };
      
    • 如何解决里面定死的int

      • typedef xxx STDateType
        struct Stack_C
        {
            int* a;
            int _size; //_top
            int _capacity;
        };
        typedef struct Stack_C Stack_C;
        
        void Stack_CInit(Stack_C* ps);
        void Stack_CDestroy(Stack_C* ps);
        void Stack_CPush(Stack_C* ps,STDateType x);
        void Stac_CkPop(Stack_C* ps);    
        
    • 如何使用

      • typedef xxx STDateType
        struct Stack_C
        {
            int* a;
            int _size; //_top
            int _capacity;
        };
        typedef struct Stack_C Stack_C;
        
        void Stack_CInit(Stack_C* ps);
        void Stack_CDestroy(Stack_C* ps);
        void Stack_CPush(Stack_C* ps,STDateType x);
        void Stac_CkPop(Stack_C* ps);    
        
        int main()
        {
            Stack_C st_c;
            Stack_CInit(&st_c);
            Stack_CDestroy(&st_c);
            Stack_CPush(&st_c,1);
            Stack_CPush(&st_c,2);
            Stack_CPush(&st_c,3);
            Stack_CPush(&st_c,4);
            
            
            ///非法修改
            st_c._capacity=0;//没法强制用的人不允许这么做
        }
        
      • C语言在这里出现的问题

        • 忘记初始化
        • 没有封装,谁都可以修改结构体的数据
        • 如果想同时定义两个栈,一个栈存int,一个栈存double,做不到。
  • C++如何解决

    template<class T>
    class Stack_CPP
    {
        public:
        	Stack_CPP()
            {
                
            }
        	~Stack_CPP()
            {
                
            }
        	void Push(T num)
            {
                
            }
        private:
        	T* _a;
        	int _size;
        	int _capacity;
    };
    int main()
    {
        Stack_CPP st_cpp;
        st_cpp.Push(1);//实际上也是两个参数,隐含的this指针,这里本质一样
    }
    
    • 忘记初始化

      • 构造函数自动调用
    • 没有封装,谁都可以修改结构体的数据

      • 加了类访问限定符
    • 同时定义两个栈,一个栈存int,一个栈存double

      • 模板
    • 本质是为了解决C语言没有解决好的地方

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值