C++模板---泛型编程

第2章 函数模板

函数模板提供了一种函数行为,该函数行为可以用多种不同的类型进行调用;也就是说,函数模板代表一个函数族、它表示看起来和普通的函数很相似,唯一的区别就是有些函数元素是未确定的:这些元素将在使用时被参数化

定义模板:

template 
inline T const& max(T const&a, T const& b)
{
    return a < b ? b:a; 
}

实参推导:

当我们为某些实参调用一个诸如max()的模板时,模板参数可以由我们所传递的实参来决定。如果我们传递了两个int给参数类型T const&,那么C++编译器能够得出结论:T必须是int。这里不允许进行自动类型转换,每个T都必须正确地匹配。

template
inline T const& max(T const& a,T const& b);

max(4,7);		//OK:两个实参的类型都是int
max(4,4,2);		//ERROR:第一个T是int,而第2个T是double

有3种方法可以用来处理上面这个错误:

(1)对实参进行强制类型转换,使它们可以相互匹配:

​ max( static_cast(4), 4.2); //OK

(2)显示指定(或限定)T的类型

​ max(4,4,2); //OK

(3)指定两个参数可以具有不同的类型

第3章 类模板

类模板Stack的实现:

#include 
#include 

template 
class Stack{
    private:
    	std::vector elems;		//存储元素的容器
    public:
        void push(T const&); 
    	void pop();
    	T top() const;
    	bool empty() const{
            return elems.empty();
        }
};
template 
void Stack::push(T const& elem)
{
	elems.push_back(elem);
}
template 
void Stack::pop()
{
	if(elems.empty()){
		throw std::out_of_range("Stack<>::pop():empty stack");
    }
    elems.pop_back();
}
template 
T Stack::top() const
{
	if(elems.empty()){
		throw std::out_of_range("Stack<>::pop():empty stack");
    }
    elems.back();
}

类模板的特化:

为了特化一个类模板,必须在起始处声明一个template<>,接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:

template<>
class Stack{
    ...
}

进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代:

void Stack::push(std::string const& elem)
{
    elems.push_back(elem);
}

局部特化:

类模板可以被局部特化。可以在特定的环境下指定类模板的特定实现,并且要求某些模板参数仍然必须由用户来定义。如类模板:

template 
class Myclass{
    ...
};

可以有下面几种局部特化:

//局部特化:两个模板参数具有相同的类型
template 
class MyClass{
    ...
};
//局部特化:第2个模板参数的类型是int
template
class MyClass{
    ...
};
//局部特化:两个模板参数都是指针类型
template
class MyClass{
    ...
};

如果有多个局部特化同等程度地匹配某个声明,那么就称该声明具有二义性:

MyClass m;			//ERROR:同等程度地匹配MyClass和MyClass
MyClass m;		//ERROR:同等程度地匹配MyClass和MyClass

为了解决第2中二义性,可以另外提供一个指向相同类型指针的特化:

template
class MyClass{
    ...
};

缺省模板实参:

如在类Stack<>中,可以把用于管理元素的容器定义为第2个模板参数,并且使用std::vector<>作为它的缺省值

template  >
class Stack{
    private:
    	CONT elems;		//包含元素的容器
    public:
    	void push(T const&);
    	void pop();
    	T top() const;
    	bool empty() const{
            return elems.empty();
        }
};

template
void Stack::push(T const& elem)
{
    elems.push_back(elem);
}

如果你只传递第一个类型实参给这个类模板,那么将会利用vector来管理stack的元素

  • 为了使用类模板,可以传入某个具体类型作为模板实参;然后编译器将会基于该类型来实例化类模板
  • 可以用某种特定类型特化类模板
  • 可以用某种特定类型局部特化类模板
  • 可以为类模板的参数定义缺省值,这些值还可以引用之前的模板参数

第4章 非类型模板参数

对于函数模板和类模板,模板参数并不局限于类型,普通纸也可以作为模板参数。在基于类型参数的模板中,你定义了一些具体未加确定的代码,直到代码被调用时这些细节才被真正确定。然而,这里我们面对的这些细节是值value,而不是类型。当要使用基于值的模板时,你必须显式地指定这些值,才能够对模板进行实例化,并获得最终代码。

可以使用元素数目固定的数组来实现stack,优点是:无论是由你来亲自管理内存,还是由标准容器来管理内存,都可以避免内存管理开销。然而,决定一个栈的最佳容量是很困难的。一个好的解决办法就是:让栈的用户亲自制定数组的大小,并把它作为所需要的栈元素的最大个数。

#include 
#define MAXSIZE 1024

template
class Stack{
    private:
    	T elmes[MAXSIZE];		//包含元素的数组
    	int numElems;			//元素的当前总个数
    public:
    	Stack();
    void push(T const&);
    void pop();
    T top() const;  
};

template
Stack::Stack():numElems(0)
{
	//do nothing
}

MAXSIZE是新加入的第2个模板参数,类型为int;它指定了数组最多可包含的栈元素的个数。

为了使用这个模板,你需要同时指定元素的类型和个数(即栈的最大容量):

Stack int20Stack;

同样,我们可以为模板参数指定缺省值:

template
class Stack{
    ...
};

第5章 技巧性基础知识

1.关键字typename

通常而言,当某个依赖模板参数的名称是一个类型时,就应该使用typename

考虑一个typename的典型应用,即在模板代码中 访问STL容器的迭代器

//打印STL容器的元素
template 
void printColl(T const& coll)
{
	typename T::const_iterator pos;					//用于迭代coll的迭代器
    typename T::const_iterator end(coll.end());		//结束位置
    
    for(pos==coll.begin();pos!=end;++pos){
        std::cout<<*pos<<" ";
    }
    std::cout<

为了访问模板类型为T的const_iterator类型,需要在声明开始处使用关键字typename来加以限定,如:

typename T::const_iterator pos;

2.成员模板

对于元素类型不同的栈,你不能对它们进行相互赋值,即使这两种(元素的)类型之间存在隐式类型转换。

缺省赋值运算符要求两边具有相同的类型,当元素类型不同时,两个栈的类型显然不同,不能符合缺省赋值运算符的要求。

然而,通过定义一个身为模板的赋值运算符,针对元素类型可以转换的两个栈就可以进行相互赋值。

template
class Stack{
    private:
    	std::deque elems;	//存储元素的容器
    public:
    	void push(T const&);
    	void pop();
    
    //使用元素类型为T2的栈进行赋值
    template
    Stack& operator= (Stack const&);
};

3.使用字符串作为函数模板的实参

有时,把字符串传递给函数模板的引用参数会导致出人意料的运行结果

#include 

//注意:引用参数
template 
inline T const& max1(T const& a,T const& b)
{
    return a < b ? b:a; 
}
int main()
{
    std::string s;
    max1("apple","peach");		//OK:相同类型的实参
    max1("apple","tomato");		//ERROR:不同类型的实参
    max1("apple",s);			//ERROR:不同类型的实参
    return 0;
}

问题在于:由于长度的区别,这些字符串属于不同的数组类型。也就是说,‘apple’和‘peach’具有相同的类型char const[6];然而‘tomato’的类型则是:char const[7].

模板实例化是一个通过使用具体值替换模板实参,从模板产生出普通类、函数或者成员函数的过程。这个过程最后获得的实体(比如类、函数或者成员函数)就是我们通常所说的特化(specialization)。对于仍然具有模板参数的特化,我们称之为局部特化(partial specialization)

template 
class MyClass{
    ...
};
template 		//局部特化
class MyClass{
    ...
};

泛型编程

1.函数模板

通过template关键字来声明使用模板,typename关键字来定义模板类型

template 	//声明使用模板,并定义T是一个模板类型

void swap(T& a,T& b)
{
	T c = a;
    a = b;
    b = c;
}

当我们使用int类型参数来调用swap,则T就会自动转换为int类型

函数模板的使用:分为自动调用和显示调用

int a = 0;
int b = 1;
swap(a,b);			//自动调用,编译器根据a和b的类型来推导

float c = 0;
float d = 1;
swap(c,d);	//显示调用,告诉编译器,调用的参数是float类型

初探函数模板:

写两个函数模板,一个用来排序数组,一个用来打印数组

#include 
#include 
using namespace std;

template 
void Sort(T a[],int len)
{
    for(int i = 1;i
void Println(T a[],int len)
{
    for(int i = 0;i(a,5);  // 显示调用,告诉编译器,调用的参数是int类型
    string s[5] = {"Java","C++","Pascal","Ruby","Basic"};
    Sort(s,5);
    Println(s,5);
  
    return 0;
}

深入理解函数模板:

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

  • 其实编译器对函数模板进行了两次编译
  • 第一次编译时,首先检查函数模板本身有没有语法错误
  • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。
  • 所以函数模板,其实只是一个模具,当我们调用它时编译器就会给我们生成真正的函数。

试验函数模板是否生成真正的函数:通过两个不同类型的函数指针指向函数模板,然后打印指针地址是否一致

#include 

using namespace std;

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

int main()
{
    void (*FPii)(int&,int&);
    FPii = Swap;        //函数指针FPii指向Swap函数
    
    void (*FPff)(float&,float&);
    FPff = Swap;        //函数指针FPff指向Swap函数

    cout<(FPii)<(FPff)<(Swap)<

多参数函数模板

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

template 
T1 Add(T2 a,T3 b)
{
	return static_cast(a+b);
}

注意:

  • 工程中一般都将返回值参数作为第一个模板类型

  • 如果返回值类型作为模板类型,则必须指定返回值模板类型。因为编译器无法推导出返回值类型

  • 可以从左向右部分指定类型参数

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

开始试验多参数函数模板:

#include 

using namespace std;

template       
T1 Add(T2 a,T3 b)
{
       return static_cast(a+b);      
}

int main()
{
     int a = Add(1,1.5);       //该行编译出错,没有指定返回值类型
       int a = Add(1,1.5);
       cout<(1,1.5);
       cout<

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xT5Cr7D-1647411362275)(F:\C++\泛型编程\image-20220316103523833.png)]

#include 
  
using namespace std; 

template         
T Max(T a,T b)
{
    cout<<"T Max(T a,T b)"< b ? a : b;
} 

template         
T Max(T* a,T* b)                    //重载函数模板 
{
    cout<<"T Max(T* a,T* b)"< *b ? *a : *b;
} 


int Max(int a,int b)                //重载普通函数 
{
    cout<<"int Max(int a,int b)"< b ? a : b;
}  
 
int main()
{  
    int a=0;
    int b=1;
    
    cout<<"a:b="<template 
class operator
{
public:
    T op(T a,T b);
};

类模板的使用:

  • 定义对象时,必须制定类模板类型,因为编译器无法推导类型
  • 使用具体类型 来定义对象
operator op1;
operator op2;
int i = op1.op(1,2);
string s = op2.op("D.T.","Software");

初探类模板:

//实现不同类型的加减乘除
#include  
#include 
using namespace std;

template 
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 op1;      //定义对象时,需要指定类模板类型
    cout< op2;
    cout <

类模板的工程应用

  • 类模板必须在.h头文件中定义
  • 类模板的成员函数不能分开在不同的文件中实现
  • 类模板外部定义的成员函数和模板函数一样,还需要加上模板template声明,以及结构体声明
#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 :: add(T a, T b)  //同时加上结构体声明
{
       return a+b;
}

template < typename T >          
T  Operator :: minus(T a, T b)
{
       return a-b;
}
template < typename T >          
T  Operator :: multiply(T a, T b)
{
       return a*b;
}

template < typename T >          
T  Operator :: divide(T a, T b)
{
       return a/b;
}

#endif

多参数类模板:

类模板可以定义任意多个不同的类型参数,同时还要必须指定每个模板参数

template 
class Operator
{
public:
    void add(T1 a,T2 b);
};

template
void Operator::add(T1 a,T2 b)
{
    cout<<(a+b)< op1;    //定义op1对象时,必须指定类模板类型
    op1.add(2,2.1);
    return 0;
}

类模板也可以像函数重载一样,类模板通过特化的方式实现特殊情况。

类模板特化:

  • 表示可以存在多个相同的类名,但是模板类型都不一致(和函数重载的参数类似)
  • 特化类型有完全特化和部分特化两种类型
  • 完全特化表示显示指定类型参数,模板声明只需写成template<>,并在类名右侧指定参数
template < typename T1,typename T2 >  //声明的模板参数个数为2个
class Operator                        //正常的类模板
{
public:
        void add(T1 a, T2 b)
       {
              cout<                           //不需要指定模板类型,因为是完全特化的类模板
class Operator< int , int>           //指定类型参数,必须为2个参数,和正常类模板参数个数一致

{                                               

public:

void add(int a, int b)

{

cout< Op1;        //匹配完全特化类模板:class Operator< int,int>

       Operator Op2;     //匹配正常的类模板

       return 0;

}
  • 部分特化表示通过特定规则约束类型参数,和模板声明类似,并在类名右侧指定参数,比如:
template < typename T1,typename T2 >           //声明的模板参数个数为2个
class Operator                                 //正常的类模板
{
public:
        void add(T1 a, T2 b)
       {
              cout<          //有指定模板类型以及指定参数,所以是部分特化的类模板             
class Operator< T* ,T*>          //指定类型参数,必须为2个参数,和正常类模板参数个数一致
{
public:
  void add(T* a, T* b)
  {
              cout<<*a+*b< Op1;            //匹配部分特化: class Operator< T* ,T*>
     Operator Op2;           //匹配正常的类模板: class Operator     
return 0;
}

编译时,会根据对象定义的类模板类型,首先去匹配完全特化再来匹配部分特化,最后匹配正常的类模板

初探类模板特化:

#include 

using namespace std; 

template < typename T1,typename T2 >  
class Operator                                            //正常的类模板
{
public:
        void add(T1 a, T2 b)
       {
              cout<<"add(T1 a, T2 b)"<                              
class Operator                                //部分特化的类模板,当两个参数都一样,调用这个
{
public:
         void add(T a, T b)
       {
              cout<<"add(T a, T b)"<  
class Operator                                   //部分特化的类模板,当两个参数都是指针,调用这个
{
public:
        void add(T1* a, T2* b)
       {
              cout<<"add(T1* a, T2* b)"<  
class Operator                             //完全特化的类模板,当两个参数都是void*,调用这个
{
public:
        void add(void* a, void* b)
       {
              cout<<"add(void* a, void* b)"<  Op1;        //匹配正常的类模板:class Operator      
       Op1.add(1,1.5);
       Operator  Op2;          //匹配部分特化的类模板:class Operator
       Op2.add(1,4);
       Operator  Op3;      //匹配部分特化的类模板:class Operator      
       Op3.add(p1,p2);
       Operator  Op4;      //匹配完全特化的类模板:class Operator
       Op4.add(NULL,NULL);  
       delete p1;
       delete p2;
       return 0;
}

数值型模板参数:

之前,我们学习的模板参数都是带泛型的(表示不同类型),其实模板参数也可以是数值型参数

template

void func()
{
    T a[N];	//使用模板参数定义局部数组
}

func();

数值型模板参数必须在编译时被唯一确定

变量在运行期间是可变的,所以不能作为模板参数。类对象(可变)

通过数值参数的类模板来求 1+2+3+…+N的值

#include 
using namespace std;

template 
class Sum
{
public:
    static const int VALUE = Sum::VALUE + N;

};

template < >
class Sum < 1 >
{
public:
    static const int VALUE = 1;
};

int main()
{
    cout<<"1+2+3+...+10= "<::VALUE<#include 
using namespace std;

template 
class Sum
{
public:
    static const int VALUE = Sum::VALUE + N;

};

template < >
class Sum < 1 >
{
public:
    static const int VALUE = 1;
};

int main()
{
    cout<<"1+2+3+...+10= "<::VALUE<

你可能感兴趣的:(C/C++,c++,开发语言)