C++中的模板及其特化相关知识(详细完整)

模板

  • 模板是将一个事物的结构规律予以固定化标准化的成果,它体现的是结构形式的标准化。

C++与模板

  • 利用模板的思想是泛型编程—即不考虑具体数据类型的编程方式,即功能相同时,不需要重复编写代码
  • 模板是建立通用的与数据类型无关的算法的重要手段。C++国际标准ISO 14882将模板正式引入标准库,要求用模板类取代传统C++中定义的类。
  • c++集合了过程式语言,通用语言,面向对象语言的众多特点。模板是通用语言的特性,模板又叫参数化类型(parametrized types)。

看一个例子:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

//模板函数
template<typename T>
void M_add(T x1, T x2) {
    cout << x1 << " + " << x2 << " = " << num1 + num2 << endl;
}


//模板类
template<typename T>
class M_Class {
public:
    static void multi(T x1, T x2) {
        cout << x1 << " + " << x2 << " = " << num1 * num2 << endl;
    }
};


int main() {
    //整型
    int x1 = 1;
    int x2 = 2;
    M_add<int>(x1, x2);   //模板函数实例
    M_Class<int>::multi(x1, x2);   //模板类实例
    //浮点型
    double num3 = 3.1;
    double num4 = 4.2;
    M_add<double>(num3, num4);    //模板函数实例
    M_Class<double>::multi(num3, num4);  //模板类实例
    return 0;
}

其中的void M_add(T x1, T x2) 为模板函数,class M_Class为模板类

C++中的函数模板

  1. 函数模板是一组函数的抽象描述,它不是一个实实在在的函数,函数模板 不会编译成任何目标代码。函数模板必须先实例化成模板函数,这些模板函 数再程序运行时会进行编译和链接,然后产生相应的目标代码。简单来说,函数模板就是实现代码的重用,图个省事。
  2. 函数模板的关键字为template
  3. 模板的定义。以下是模板定义的一般格式:
template <class any>//class 也可以换成typename,后者是新来的。
void swap(any &a,any &b)
{
......
}

调用时,就像调用普通函数那样调用模板函数,之后的工作编译器会为你摆平。比如:swap(x,y);

  • 在使用函数模板时有两种方式
    • 自动类型推到调用 Swap(a, b)
    • 具体类型显示调用 Swap(a, b)
template < class T>
 void max(T& x,T& y)  //函数模板
 {  
 	cout <<((x>y)?x:y);  
 }
 void func(int i,int j)  
 {
 	max(i,j);  //自动推到调用
 	max<int>(i,j);  //具体类型显示调用
 }
  • 函数模板的错误使用
 template < class T>
 void max(T& x,T& y)  //函数模板
 {  
 	cout<<((x>y)?x:y);  
 }
 void func(int i,char j)  //正常的函数
 {
 	max(i,i);  //实例化的模板函数,正确调用
 	max(i,j);  //实例化的模板函数,但错误调用
 }

上面例子中第二个实例化的模板函数出现了错误:在这里插入图片描述
这是因为在实例化的过程中要按照声明时的要求进行,函数模板声明了两个形参是同一类型,第二个函数的参数i为int类型而j为char类型,所以编译产生错误,函数模板是不允许隐式类型转换的,调用时类型必须严格匹配

  • 多参数调用
    函数模板还可以定义任意多个不同的类型参数,但是对于多参数函数模板:
    • 编译器是无法自动推导返回值类型的
    • 可以从左向右部分指定类型参数
#include <iostream>
 
using namespace std;
 
template <typename T1, typename T2, typename T3>
T1 add(T2 a, T3 b)
{
    T1 ret;
    ret = static_cast<T1>(a + b);
    return ret;
}
 
void main()
{
    int c = 12;
    float d = 23.4;
    //cout << add(c, d) << endl;	//error,无法自动推导函数返回值
    cout << add<float>(c, d) << endl;	//返回值在第一个类型参数中指定
    cout << add<int, int, float>(c, d) << endl;
    system("pause");
}
  • 函数模板跟普通函数一样,也可以被重载
    • C++编译器优先考虑普通函数
    • 如果函数模板可以产生一个更好的匹配,那么就选择函数模板
    • 也可以通过空模板实参列表<>限定编译器只匹配函数模板
      限定用法:func<>(x,y); (使用时)
#include <iostream>

using namespace std;

//函数模板
template < class T>
 void max(T& x,T& y)  //函数模板
 {  
 	cout <<((x>y)?x:y);  
 }
//函数模板的重载
template < class T>
 void max(T x,T y)  //函数模板
 {  
 	cout << x;  
 }
int main(){
      max((int*)0,(int*)0) ;
      max((int)0,(int)0);
        return 0;
}

函数的特化

使用模板时会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数(就是写出一个模板函数专门给该类型使用)

template<class T>
bool Isequal(T& p1, T& p2){
	return p1 == p2;
}

这个模板函数在遇到字符型数据就用不成了,所以特化一个函数模板

template<> // 此处不添加类型模板,直接使用空即可
bool Isequal<char*>(char*& p1, char*& p2){
	return strcmp(p1, p2) == 0;
}

注意:

  1. 使用模板特化时,必须要先有基础的模板函数(就是上面第一个模板函数)
  2. 使用特换模板函数时格式有要求:
    1.template 后直接跟<> 里面不用写类型
    2.函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型
  3. 特化的函数的函数名,参数列表要和原基础的模板函数想相同,避免不必要的错误

C++中的类模板

  • c++中的类模板的写法如下
template <class类塑参数1, class类型参数2, ...>
class 类模板名{
    成员函数和成员变量
};
  • 类模板中的成员函数放到类模板定义外面写时的语法如下
template <类型参数表>
返回值类型  类模板名<类型参数名列表>::成员函数名(参数表)
{
    ...
}
  • 用类模板定义对象的写法如下:
    有参数:
    类模板名<真实类型参数表> 对象名(构造函数实际参数表);
    无参数:
    类模板名 <真实类型参数表> 对象名;

例子:

#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Pair
{
public:
    T1 key;  //关键字
    T2 value;  //值
    Pair(T1 k,T2 v):key(k),value(v) { };
    bool operator < (const Pair<T1,T2> & p) const;
};
template<class T1,class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> & p) const
//Pair的成员函数 operator <
{ //"小"的意思就是关键字小
    return key < p.key;
}
int main()
{
    Pair<string,int> student("Tom",19); //实例化出一个类 Pair<string,int>
    cout << student.key << " " << student.value;
    return 0;
}
  • 函数模板作为类模板成员
    类模板中的成员函数还可以是一个函数模板。成员函数模板只有在被调用时才会被实例化。例如下面的程序:
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
    template <class T2>
    void Func(T2 t) { cout << t; }  //成员函数模板
};
int main()
{
    A<int> a;
    a.Func('K');  //成员函数模板Func被实例化
    a.Func("hello");
    return 0;
}

类的特化

前面提到函数模板能被重载,而类模板不可以,我们可以通过特化来实现相似的效果,从而可以透明地获得具有更高效率的代码。
特化也分为了两种,全特化和偏特化:

  • 全特化
    全特化即将所有的模板类型都进行特化,全特化是模板的一个唯一特例,指定的模板实参列表必须和相应的模板参数列表一一对应
    我们不能用一个非类型值来替换模类型参数。但是如果模板参数具有缺省模板实参,那么用来题还实参就是可选的。
template <class T1, class T2>
class Test
{
}

//全特化
template <>  //此处同函数模板的特化一样不用写内容
class Test<int , char>{
    
}
  • 偏特化
    偏特化感觉像是介于普通模板和全特化之间,只存在部分的类型明确化,而非将模板唯一化。
    是一种部分特化:就是只对函数模板的一部分模板类型进行特化
    再次划重点 函数模板不能被偏特化
#include <iostream>
using namespace std;

template<typename T1, typename T2>
class A
{
        public:
                void function(T1 value1, T2 value2)
                {
                        cout<<"value1 = "<<value1<<endl;
                        cout<<"value2 = "<<value2<<endl;
                }
};

template<typename T>
class A<T, double>
{ // 部分类型明确化,为偏特化类
        public:
                void function(T value1, double value2)
                {
                        cout<<"Value = "<<value1<<endl;
                        cout<<"doubleValue = "<<value2<<endl;
                }
};

int main()
{
        A<char, double> a;
        a.function('a', 12.3);
        return 0;
}

类的特化优先级

对主版本模板类、全特化类、偏特化类的调用优先级从高到低进行排序是:
全特化类>偏特化类>主版本模板类
这样的优先级顺序对性能也是最好的。

C++类模板中的静态成员

类模板中可以定义静态成员,从该类模板实例化得到的所有类都包含同样的静态成员。

#include <iostream>
using namespace std;
template <class T>
class A
{
private:
	static int count;
public:
	A() { count ++; }
	~A() { count -- ; };
	A( A & ) { count ++ ; }
	static void PrintCount() { cout << count << endl; }
};

template<> int A<int>::count = 0;
template<> int A<double>::count = 0;
int main()
{
	A<int> ia;				//模板类ia
	A<double> da;			//模板类da
	ia.PrintCount();		//只创建一个模板类对象,名为ia
	da.PrintCount();		//只创建一个模板类对象,名为da。
	return 0;
}

不同类型的类模板共享的静态成员变量不是同一块。

类模板下运算符重载的两种用法

1.

template <typename T>
class Complex
{
	friend ostream &operator <<<T>(ostream &out,const Complex &c);
private:
	T m_a;
	T m_b;
public:
	Complex(T a,T b);
	void print();
	
};

friend ostream &operator <<(ostream &out,const Complex &c);

这句的放在运算符<<后是因为该模板类中的成员变量都为T类;
2.

template <typename T>
class Myarray
{
private:
	int m_len;
	T *m_data;
public:
	//Myarray();
	Myarray(int l);
	Myarray(const Myarray &a);
	~Myarray();
	template <typename U>
	friend ostream &operator <<(ostream &out,const Myarray<U> &a);
	T &operator [](int index);
};

friend ostream &operator <<(ostream &out,const Myarray &a);

而此句的类模板中有一个成员变量是int型已确定,因此将放入参数Myarray后。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值