【C++】C++类模板和函数模板

模板

  • C++另一种编程思想称为泛型编程,主要利用的技术就是模板。
  • C++提供两种模板机制:函数模板和类模板。
  • 编译器对类模板处理方式和函数模板相同,都是进行2次编译。

1.类模板

1.1 类模板语法

 类模板的使用,定义对象时,必须显式指定类模板类型,因为编译器无法推导类型,使用具体类型来定义对象。类模板通常应用于数据结构方面,使得类的实现不在关注数据元素的具体类型,而只关注需要实现的功能。例vector,list,queue等STL数据结构。

语法
template < typename T1,typename T2 >
class Operator                                            //正常的类模板
{
public:
        void add(T1 a, T2 b)
       {
              cout<<"add(T1 a, T2 b)"<<endl;
              cout<<a+b<<endl;
       }
};
/*使用main.cpp*/
int main()
{
	Operator<int,float>  Op; //使用必须显示使用
	Op.add(1,1.5);
	return 0;
}


1.2类模板的特例化

 类模板和函数模板都可以指定特殊的类和函数。其中,函数模板没有部分特化。在编译时,会根据对象定义的类模板类型,首先去匹配完全特化,再来匹配部分特化,最后匹配正常的类模板。

Example
#include <iostream>

using namespace std; 

template < typename T1,typename T2 >  
class Operator                                            //正常的类模板
{
public:
        void add(T1 a, T2 b)
       {
              cout<<"add(T1 a, T2 b)"<<endl;
              cout<<a+b<<endl;
       }
};

template < typename T >                              
class Operator<T,T>                                //部分特化的类模板,当两个参数都一样,调用这个
{
public:
         void add(T a, T b)
       {
              cout<<"add(T a, T b)"<<endl;
              cout<<a+b<<endl;
       }
};

template < typename T1,typename T2 >  
class Operator<T1*,T2*>                                   //部分特化的类模板,当两个参数都是指针,调用这个
{
public:
        void add(T1* a, T2* b)
       {
              cout<<"add(T1* a, T2* b)"<<endl;
              cout<<*a+*b<<endl;
       }
};

template < >  
class Operator<void*,void*>                             //完全特化的类模板,当两个参数都是void*,调用这个
{
public:
        void add(void* a, void* b)
       {
              cout<<"add(void* a, void* b)"<<endl;
              cout<<"add void* Error"<<endl;                 //void*无法进行加法
       }
};

int main()
{
       int *p1 = new int(1);
       float *p2 = new float(1.25);

       Operator<int,float>  Op1;        //匹配正常的类模板:class Operator      
       Op1.add(1,1.5);

       Operator<int,int>  Op2;          //匹配部分特化的类模板:class Operator<T,T>
       Op2.add(1,4);

       Operator<int*,float*>  Op3;      //匹配部分特化的类模板:class Operator<T1*,T2*>      
       Op3.add(p1,p2);

       Operator<void*,void*>  Op4;      //匹配完全特化的类模板:class Operator<void*,void*>
       Op4.add(NULL,NULL);  

       delete p1;
       delete p2;

       return 0;
}

1.3模板类中数值型模板参数

 在泛型编程中,数据的值和类型都被参数化。在第二次编译时,编译器会根据模板中的类型参数<实参.>去推导形参的值与类型;也就是说,模板不仅支持值的传递,还支持类型的传递,这就是模板与普通函数的最大区别了。

Example
template <typename T, int N> 
void func()
{
    T a[N];  // 使用模板参数定义局部数组;
}
	
func<double, 10>();  // 使用模板时,数值型参数必须是常量,不能是变量;

总之,合法的数值型模板参数必须满足,在编译阶段确保数值型模板参数是唯一确定的。例如如下几种就不可以:

  • 在发生调用时,变量不能作为数值型模板参数;(在编译时,变量的值还没有唯一确定;如 int a = 10; func<double, a>(); 编译失败)
  • 在模板声明时,浮点数不能作为数值型模板参数;(浮点数本身就不精确;如 template <typename T, double N> 编译失败 )
  • 类对象不能作为数值型模板参数;

记录一下 使用数值型参数 最高效求1->n前n项和:

前n项和
template < int N >
class Sum
{
public:
	// 编译器会将该const常量放入符号表中,此时可以用在数值型参数表中;
	// static 修饰属于整个类,通过类可以直接使用;
	// 递归使用
	//该值在该模板类使用时就自动生成并随着程序运行加载到内存中。只有消耗读取内存的时间,所以最高效。
    static const int VALUE = Sum<N-1>::VALUE + N;
};
template < >
class Sum < 1 >
{
public:

    static const int VALUE = 1;
};

int main()
{
    cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
    cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
    return 0;
}

2.函数模板

2.1 函数模板语法

语法
/*
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
   T     --- 通用的数据类型,名称可以替换,通常为大写字母
*/
template<typename T>或template<class T>	

2.2 函数模板的使用

  • 隐式调用
  • 显式调用
Example
//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;
	//利用模板实现交换
	//1、自动类型推导
	mySwap(a, b);
	//2、显示指定类型
	mySwap<int>(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

}

int main() {

	test01();

	system("pause");

	return 0;
}

  • 函数模板利用关键字 template
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 模板的目的是为了提高复用性,将类型参数化

2.3 多参数函数模板

 如果返回值参数作为了模板类型,则必须需要指定返回值模板类型,工程中一般都将返回值参数作为第一个模板类型。因为编译器无法推导出返回值类型,可以从左向右部分指定类型参数。
Example
#include <iostream>

using namespace std;

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

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

	int a = Add<int>(1, 1.5);
	cout << a << endl;                  //2

	float b = Add<float, int, float>(1, 1.5);
	cout << b << endl;                  //2.5

	return 0;
}

2.4 函数模板的重载

  • (1)函数模板可以像普通函数一样被重载
  • (2)函数模板不接受隐式转换
  • (3)当有函数模板,以及普通重载函数时,编译器会优先考虑普通函数
  • (4)如果普通函数的参数无法匹配,编译器会尝试进行隐式转换,若转换成功,便调用普通函数
  • (5)若转换失败,编译器便调用函数模板
  • (6)可以通过空模板实参列表来限定编译器只匹配函数模板,即<>内为空
Example
#include <iostream>
  
using namespace std; 

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

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


int Max(int a,int b)                //重载普通函数 
{
    cout<<"int Max(int a,int b)"<<endl;   
    return a > b ? a : b;
}  
 
int main()
{  
    int a=0;
    int b=1;
    
    cout<<"a:b="<<Max(a,b) <<endl ;        //调用普通函数 Max(int,int)
    
    cout<<"a:b="<<Max<>(a,b)<<endl;        //通过模板参数表 调用 函数模板 Max(int,int)
    
    cout<<"1.5:2.0="<<Max(1.5,2.0)<<endl;   
     //由于两个参数默认都是double,所以无法隐式转换,则调用函数模板 Max(double,double)
    
    int *p1 = new int(1);
    int *p2 = new int(2); 
 
    cout<<"*p1:*p2="<<Max(p1,p2)<<endl;  // 调用重载函数模板 Max(int* ,int* )    
    
    cout<<"'a',100="<< Max('a',100)<<endl;        
    //将char类型进行隐式转换,从而调用普通函数 Max(int,int)
    
    
    delete p1;
    delete p2; 
    
    return 0;
}

2.5 函数模板的特例化

 函数模板的特例化:就是指特殊的实例化,将函数模板或类模板实例化为特殊的类型,通过模板特例化可以定制在特定模板参数下的函数模板/类模板实现,或者禁用特定模板的函数模板/类模板。

Example
#include <iostream>

using namespace std;

//普通模板
template<class T1, class T2>
bool Compare(T1 a, T2 b)
{
	cout << "普通模板" << endl;
	return a == b;
}

//函数模板特化
template<>
bool Compare(const char* a, const char* b)
{
	cout << "函数模板特化" << endl;
	return strcmp(a, b) == 0;
}

int main()
{
	cout << Compare(1, 2) << endl;
	cout << Compare("ab", "ab") << endl;
	return 0;
}

2.6 函数模板的理解

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

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

int main()
{
	void(*FPii)(int&, int&);

	FPii = Swap;                   //函数指针FPii

	void(*FPff)(float&, float&);

	FPff = Swap;                  //函数指针FPff

	cout << static_cast<void *>(FPii) << endl; //003511AE
	cout << static_cast<void *>(FPff) << endl; //00351055
	//Swap是两个函数,分别在不同的地址 
	return 0;
}
函数模板必须要确定类型才可以使用
//利用模板提供通用的交换函数
template<class T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}


// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';

	mySwap(a, b); // 正确,可以推导出一致的T
	//mySwap(a, c); // 错误,推导不出一致的T类型
}


// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
	cout << "func 调用" << endl;
}

void test02()
{
	//func(); //错误,模板不能独立使用,必须确定出T的类型
	func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}

int main() {

	test01();
	test02();

	system("pause");

	return 0;
}
posted @ 2022-01-27 23:07  一半丶  阅读(333)  评论(0编辑  收藏  举报