从零开始学C++(7)----模版

目录

1.模版的概念

2.函数模版

1.函数模版的定义

2.函数模版实例化

1.隐式实例化

2.显式实例化

3.显式具体化

3.函数模版重载

3.类模版

1.类模版定义与实例化

2.类模版的派生

1.类模版派生普通类

2.类模版派生类模版

3.普通类派生类模版

3.类模版与友元函数

1.非模版友元函数

2.约束模版友元函数

3.非约束模版友元函数

4.模版的参数

1.类型参数

2.非类型参数

3.模版类型参数

5.模版特化

1.全特化

2.偏特化


1.模版的概念

        在C++程序中,声明变量,函数,对象等实体,程序设计者需要指定数据类型,让编译器在程序运行之前进行类型检查并分配内存,以提高程序运行的安全性和效率,但是这种强类型的编程方式往往会导致程序设计者为逻辑结构相同而具体数据类型不同的对象编写模式一致的代码。

        为此C++标准提供了模版机制,用于定义数据类型不同但逻辑结构相同的数据对象的通用行为,在模版中,运算对象的类型不是实际的数据类型,而是一种参数化的类型,带参数类型的函数为函数模版,带参数类型的类称为类模版。程序运行时,模版的参数由实际参数的数据类型决定,编译器会根据实际参数的数据类型生成相应的一段可运行代码,这个过程称为模版实例化,函数模版生成的实例称为模版函数,类模版生成的实例称为模版类。

2.函数模版

        函数模版是函数的抽象,函数参数的类型在调用过程中才被确定。

1.函数模版的定义

        语法格式如下:

template<typename 类型占位符>
返回值类型 函数名(参数列表)
{
    ...
}

        上述格式中,template是声明模版的关键字,<>中的参数称为模版参数;typename关键字用于标识模版参数,可以用class代替,模版参数不能为空,可以有多个模版参数,函数模版参数列表中的数据类型要使用<>中的参数名表示。

#include<iostream>
using namespace std;
template<typename T>
T add(T t1,T t2)
{
    return t1+t2;
}
int main()
{
    cout<<add(3,4)<<endl;
    cout<<add(3.6,4.9)<<endl;
    return 0;
}
7
8.5

2.函数模版实例化

        函数模版不会减少可执行程序的大小,因为编译器会根据调用时的参数类型进行相应的实例化,可分为隐式实例化和显式实例化。

1.隐式实例化

        隐式实例化是根据函数调用时传入的参数的数据类型确定模版参数T的类型。根据传入的实参推演出模版参数类型是int。

int add(int t1,int t2)
{
    return t1+t2;
}

2.显式实例化

        隐式实例化不能为同一个模版参数指定两种不同的类型,需要显式实例化解决,<>中是显式实例化的数据类型。

template int add<int>(int t1,int t2);
#include<iostream>
using namespace std;
template<typename T>
T add(T t1,T t2)
{
    return t1+t2;
}
template int add<int>(int t1,int t2);
int main()
{
    cout<<add<int>(3,'B')<<endl;
    cout<<add(3.6,4.9)<<endl;
    return 0;
}

3.显式具体化

        函数模版的显式具体化是对函数模版的重新定义,格式如下:

template<> 函数返回值类型 函数名<实例化类型>(参数列表)
{
    //重新定义
}

        显式实例化只需要显式声明模版参数的类型而不需要重新定义函数模版的实现,而显式具体化需要重新定义函数模版的实现,例如交换两个数据的函数模版。

template<typename T>
void swap(T &t1,T &t2)
{
    T temp=t1;
    t1=t2;
    t2=temp;
}

但现在有如下结构体定义:

struct Student
{
    int id;
    char name[40];
    float score;
};

现在要交换两个学生的id,但又不想交换学生的姓名,成绩等其他信息,可以重新定义函数模版只交换结构体的部分数据成员。

template<> void swap<Student>(Student& st1,Student& st2)
{
    int temp=st1.id;
    st1.id=st2.id;
    st2.id=temp;
}

如果函数有多个函数原型,则编译器在选择函数调用时,非模版函数优先于模版函数,显式具体化模版优先于函数模版。

3.函数模版重载

        函数模版可以进行实例化,以支持不同类型的参数,不同类型的参数调用会产生一系列重载函数,此外函数模版本身也可以被重载,即名称相同的函数模版可以具有不同的函数模版定义,当进行函数调用时,编译器根据实参的类型与个数决定调用哪个函数模版实例化函数。

#include<iostream>
using namespace std;
int max(const int& a,const int& b)
{
    return a>b?a:b;
}
template<typename T>
T max(const int& t1,const int& t2)
{
    return t1>t2?t1:t2;
}
template<typename T>
T max(const int& t1,const int& t2,const int& t3)
{
    return max(max(t1,t2),t3);
}
int main()
{
    cout<<max(1,2)<<endl;
    cout<<max(1,2,3)<<endl;
    cout<<max('a','e')<<endl;
    cout<<max(6,3.2)<<endl;
    return 0;
}

使用函数模版要注意一下问题:

  1. <>中的每一个类型参数在函数模版参数列表中必须至少使用一次。
  2. 全局作用域中声明的与模版参数同名的对象,函数或类型,在函数模版中将被隐藏。
  3. 函数模版中声明的对象或类型不能与模版参数同名。
  4. 模版参数名在同一模版参数列表中只能使用一次,但可在多个函数模版声明或定义之间重复使用。
  5. 模版的定义和多处声明所使用的模版参数名不是必须相同。
  6. 如果函数模版有多个模版参数,则每个模版参数前都必须使用关键字class或typename修饰。

3.类模版

1.类模版定义与实例化

        类模版是针对成员数据类型不同的类的抽象,他不是一个具体实现的类,而是一个类型的类,一个类模版可以生成多种具体的类,格式如下:

template<typename 类型占位符>
class 类名
{
    ...
}

        类模版的模版参数不能为空,一旦声明类模版,就可以用类模版的参数名声明类中的成员变量和成员函数,即在类中使用数据类型的地方都可以使用模版参数来声明。

template<typename T>
class A
{
public:
    T a;
    T b;
    T func(T a,T b);
};

        定义了类模版就要使用类模版创建对象以及实现类中的成员函数,这个过程其实也是类模版实例化的过程,实例化出的具体类称为模版类,用模版类创建对象在A后加一个<>,在里面表明相应的类型。这样类A中凡是用到模版参数的地方都会被具体类型替换。

template<typename T1,typename T2>
class B
{
public:
    T1 a;
    T2 b;
    T1 func(T1 a,T2& b);
};
B<int,string> b;

        使用类模版时,必须要为模版参数显式指定实参,不存在实参推演过程,也就是说不存在将10推演为int,必须要在<>中指定int类型。类模版在实例化时,带有模版参数的成员函数并不会跟着实例化,它们在被调用时才会被实例化。

2.类模版的派生

        类模版的派生一般有三种情况:类模版派生普通类,类模版派生类模版,普通类派生类模版

1.类模版派生普通类

        在C++中,可以从任意一个类模版派生一个普通类,派生过程中,类模版先实例化出一个模版类,这个模版类作为基类派生出普通类。

template<typename T>
class Base
{
private:
    T x;
    T y;
public:
    Base();
    Base(T x,T y);
    ~Base();
};
class Derive:public Base<double>
{
private:
    double num;
public:
    Derive(double a,double b,double c):num(c),Base<double>(a,b){}
};

2.类模版派生类模版

        派生类模版的模版参数受基类模版的模版参数影响。

template<typename T>
class Base
{
public:
    T _a;
public:
    Base(T n):_a(n){}
    T get() const{return _a;}
};
template<typename T,typename U>
class Derive:public Base<U>
{
public:
    U _b;
public:
    Derive(T t,U u):Base<T>(t),_b(u){}
    U sum() const{return _b+U(Base::get());}
};

3.普通类派生类模版

        普通类也可以派生类模版,普通类派生类模版可以把现存库中的类转换为通用的类模版,但这种方式并不常用。

3.类模版与友元函数

1.非模版友元函数

        非模版友元函数就是将一个普通函数声明为友元函数。

template<typename T>
class A
{
    T _t;
public:
    friend void func();
};

还可以将带有模版类参数的函数声明为友元函数。

template<typename T>
class A
{
    T _t;
public:
    friend void show(const A<T>& a);
};

调用带有模版类参数的友元函数时,友元函数必须显式具体化,指明友元函数要引用的参数的类型。

#include<iostream>
using namespace std;
template<typename T>
class A
{
    T _item;
    static int _count;
public:
    A(const T& t):_item(t){_count++;}
    ~A(){_count--;}
    friend void func();
    friend void show(const A<T>& a);
};
template<typename T>
int A<T>::_count=0;
void func()
{
    cout<<"int count:"<<A<int>::_count<<";";
    cout<<"double count:"<<A<double>::_count<<";"<<endl;
}
void show(const A<int>& a){cout<<"int:"<<a._item<<endl;}
void show(const A<double>& a){cout<<"double:"<<a._item<<endl;}
int main()
{
    func();
    A<int> a(10);
    func();
    A<double> b(1.2);
    show(a);
    show(b);
    return 0;
}

2.约束模版友元函数

        约束模版友元函数是将一个函数模版声明为类的友元函数,函数模版的实例化类型取决于类模版被实例化时的类型,类模版实例化时产生与之匹配的具体化友元函数,在使用约束1模版友元函数时,首先需要在类模版定义的前面声明函数模版。声明函数模版之后,在类模版中将函数声明为友元函数,在声明友元函数时,函数模版要实现具体化。即函数模版的模版参数要与类模版的模版参数保持一致,以便类模版实例化时产生与之匹配的具体化友元函数。

#include<iostream>
using namespace std;
template<typename T>
void func();
template<typename T>
void show(T& t);
template<typename U>
class A
{
private:
    U _item;
    static int _count;
public:
    A(const U& u):_item(u){_count++;}
    ~A(){_count--;}
    friend void func<U>();
    friend void show<>(A<U>& a);
};
template<typename T>
int A<T>::_count=0;
template<typename T>
void func()
{
    cout<<"template size:"<<sizeof(A<T>)<<";";
    cout<<"template func():"<<A<T>::_count<<endl;
}
template<typename T>
void show(T& t){cout<<t._item<<endl;}
int main()
{
    func<int>();
    A<int> a(10);
    A<int> b(20);
    A<double> c(1.2);
    show(a);
    show(b);
    show(c);
    cout<<"func<int>output:\n";
    func<int>();
    cout<<"func<double>()output:\n";
    func<double>();
    return 0;
}
template size:4;template func():0
10
20
1.2
func<int>output:
template size:4;template func():2
func<double>()output:
template size:8;template func():1

3.非约束模版友元函数

        非1约束模版友元函数是将函数模版声明为类模版的友元函数,但函数模版的模版参数不受类模版影响。

template<typename T>
class A
{
    template<typename U,typename V>
    friend void show(U& u,V& v);
};

4.模版的参数

1.类型参数

        由class或者typename标记的参数,称为类型参数。

template<typename T>
T add(T t1,T t2);

2.非类型参数

        非类型参数时指内置类型参数。

template<typename T,int a>
class A
{
};

        int a就是非类型参数,非类型模版参数为函数模版或类模版预定义一些常量,在模版实例化时,也要求实参必须是常量,即确切的数据值,非类型参数只能是整形,字符型或枚举,指针,引用类型。非类型参数在所有实例中都具有相同的值,而类型参数在不同的实例中具有不同的值。

在使用非类型参数时,有几点需要注意

  1. 调用非类型参数必须是常量表达式,即必须能在编译时计算出结果。
  2. 任何局部对象,局部变量的地址都不是常量表达式,不能用作非类型的实参,全局指针类型,全局变量也不是常量表达式,也不能用作非类型的实参。
  3. sizeof表达示结果可以用作非类型的实参。
  4. 非类型参数一般不用于函数模版。

3.模版类型参数

模版类型参数就是模版的参数为另一个模版,格式如下:

template<typename T,template<typename U,typename Z> class A>
class Parameter
{
    A<T,T> a;
};

        只有类模版可以作为模版参数,参数声明中必须要有关键字class。

5.模版特化

        特化就是将泛型的东西具体化,模版特化就是为已有的模版参数进行具体化的指定,使得不受任何约束的模版参数受到特点约束或完全被指定。

        通过模版特化可以优化基于某种特定类型的实现,或者克服某种特定类型在实例化模版时出现的不足,如该类型没有提供某种操作。

template<typename T>
class Special
{
public:
    Special(T a,T b)
    {
        _a=a;
        _b=b;
    }
    T compare()
    {
        return _a>_b?_a:_b;
    }
private:
    T _a;
    T _b;
};

1.全特化

        全特化就是将模版中的模版参数全部指定为确定的类型,其标准就是产生出完全确定的东西,对于类模版,包括类的所有成员函数都要进行特化,进行类模版特化时,需要将类的成员函数重新定义为普通成员函数。在全特化时,首先使用template<>进行全特化声明,然后重新定义需要全特化的类模版,并指定特化类型。

#include<iostream>
#include <cstring>
using namespace std;
template<typename T>
class Special
{
public:
    Special(T a,T b)
    {
        _a=a;
        _b=b;
    }
    T compare()
    {
        cout<<"类模版"<<endl;
        return _a>_b?_a:_b;
    }
private:
    T _a;
    T _b;
};
template<>
class Special<const char*>
{
public:
    Special(const char* a,const char* b)
    {
        _a=a;
        _b=b;
    }
    const char* compare()
    {
        cout<<"类模版特化"<<endl;
        if(strcmp(_a,_b)>0)
            return _a;
        else
            return _b;
    }
private:
    const char* _a;
    const char* _b;
};
int main()
{
    Special<string> s1("hello","nihao");
    cout<<s1.compare()<<endl;
    Special<const char*> s2("hello","nihao");
    cout<<s2.compare()<<endl;
    return 0;
}
类模版
nihao
类模版特化
nihao

2.偏特化

        偏特化就是模版中的模版参数没有被全部指定,需要编译器在编译器在编译器在编译时进行确定。

template<typename T,typename U>
class A
{
};

将其中一个模版参数特化为int类型,另一个参数由用户指定。

template<typename T>
class A<T,int>
{
};

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值