在平时的工作和学习过程中,经常会用到泛型,这里对泛型和模板进行一下梳理,以便理解和使用。
template。
假如设计一个两个参数的函数,用来求两个对象的乘积,在实践中我们可能需要定义n多个函数
int multiplication(int a,int b){return a+b;}
char multiplication(char a,char b){return a+b;}
float multiplication(float a,float b){return a+b;}
...
这些函数几乎相同,唯一的区别就是形参类型不同,在这种情况下,不必定义多个函数,只需要在模板中定义一次即可。
在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同的函数功能。
模板是泛型编程的基础,泛型编程是指独立与任何类型的代码编程。
模板是一种对类型进行参数化的工具,有两种形式:函数模板和类模板。
函数模板 针对仅参数类型不同的函数
定义格式
template<typename Type>
Type funName(Type val)
{
//Code
}
模板形参表使用typename或者class定义都可以,没有任何区别,为了区分类的定义,一般使用typename.
//对于任意类型的两个对象相加,的函数模板
template<typename T>
T multiplication(T a,T b)
{
return a * b;
}
函数模板调用
对于函数模板,有两种调用方式
显示类型调用 需要在函数调用处的函数名后面加上类型参数
multiplication(2,4);
自动类型推导 根据参数的类型进行推导,但是两个参数的类型必须一致,否则会报错。
multiplication(‘a’,‘c’);
那么需要传两个不一样的参数要怎么做呢?写两个模板类型即可:
template <typename T,typename U>
auto multiplication(T a,U b)
{
return a * b;
}
函数模板:不提供隐式类型转换,必须是严格匹配。
普通函数:提供隐式类型转换。
template<typename T>
void multiplication(T a,T b)
{
cout<<"模板函数"<< a * b <<endl;
}
show('A',65); //“void showSum(T,T)”: 未能从“char”为“T”推导 模板 参数
show<int>('A',65); //显示指定模板类型后,‘A’可以转换到int
函数模板和普通函数构成重载时,调用规则:
template<typename T>
void multiplication(T a,T b)
{
cout<<"模板函数(2)"<<a*b<<endl;
}
template<typename T>
void sum(T a,T b,T c)
{
cout<<"模板函数(3)"<<a*b*c<<endl;
}
void sum(int a,int c)
{
cout<<"普通函数"<<a*c<<endl;
}
void Test()
{
multiplication(1,2); //当函数模板和普通函数参数都符合时,优先选择普通函数
multiplication<>(1,2); //若显示使用模板函数,则使用<>类型列表
multiplication(3.0,4.2); //如果函数模板产生更好的匹配,则使用函数模板
multiplication(5.0,6.5,8.2); //只有一个合适
multiplication('a',12); //调用普通函数,可以隐式类型转换
}
类模板与函数模板的定义和使用类似。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
//模板的参数类型定义写在类的定义之前,在类里的任意位置都可以使用
template<typename T>
class Factory
{
public:
Factory(T val) :_value(val){}
void Factory()
{
cout << _value << endl;
}
private:
T _value;
};
类模板用于实现类所需数据的类型参数化
类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。
定义一个类模板非常简单,重要的是如何去用类模板定义对象
如果没有指定模板的参数列表,编译器是会报错的
Factory d(20); //error C155: “Factory”: 使用 类 模板 需要 模板 参数列表
指定参数列表只需要在类名的后面加上<类型>即可
Factory<string> d("aaa");
Factory<string> d1(string("bbb"));
实际上,类模板的使用就是将类模板实例化成一个具体的类。
类模板不代表一个具体的、实际的类,而代表一类类。
只有那些被调用的成员函数,才会产生这些函数的实例化代码。对于类模板,成员函数只有在被使用的时候才会被实例化。显然,这样可以节省空间和时间;
如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。
来个示例
#include
#include
using namespace std;
template<typename T>
class Object
{
public:
Object(int size)
:_capacity(size),_size(0),_base(nullptr)
{
if (_capacity == 0)
_capacity = 1;
_base = new T[_capacity]{T()};
}
T& operator[](int index)
{
if (index < 0 || index >= _capacity)
{
//return T(); //不能返回临时对象的引用,对于int() 是一个0
throw std::out_of_range("Object 越界"); //抛异常是最合适的
}
return _base[index];
}
private:
T* _base;
int _size;
int _capacity;
};
int main()
{
Object<int> arr(10);
for (size_t i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
cout << endl;
Object<string> arr1(10);
arr1[0] = string("cdef");
for (size_t i = 0; i < 10; i++)
{
cout << arr1[i] << " ";
}
return 0;
}
子类从模板类继承的时候,需要让编译器知道,父类的数据类型具体是什么(数据类型的本质:如何分配内存空间)
template<typename T>
class Factory
{
public:
Factory(T val) :_value(val){}
void factory()
{
cout << _value << endl;
}
protected:
T _value;
};
class A:public Factory<int> //指定具体类型
{
public:
using Factory<int>::Factory;
void show()
{
cout << "A" <<" "<<_value<< endl;
}
};
template<typename T>
class Factory
{
public:
Factory(T val) :_value(val){}
void factory()
{
cout << _value << endl;
}
protected:
T _value;
};
template<typename U>
class A:public Factory<U>
{
public:
using Factory<U>::Factory;
void show()
{
cout << "A" <<" "<<_value<< endl;
}
};
代码看起来没有问题,但是在子类中使用父类的成员,会提示找不到标识符
void show()
{
cout << "A" <<" "<<_value<< endl; //error C2065: “_value”: 未声明的标识符
}
解决办法
1,通过this指针访问:this->_value
2,通过父类访问: Factory< U >::_value
提到特化这个概念,就想到泛化的概念。模板函数的T参数只能传入类类型的参数;特化函数的参数只能传入对应的参数类型。
假设有一个比较两个对象的模板函数
template<typename T>
int compare(T a, T b)
{
cout << "T" << endl;
return a == b ? 0 : (a > b ? 1 : -1);
}
对于支持operator== 和 operator>操作的类型, 包括基本的int,float,double等类型,是完全没有问题的,但是它不能用来比较字符串(char*),因为这个函数比较的是串指针,而不是字符串本身。
cout << compare("A", "a") << endl; //类型是const char*,比较的是地址,需要做特化版本才能比较
特化版本:
template<> //必须写,不然就是重载函数,而不是函数模板,特化版本了
inline int compare(const char* str1, const char* str2)
{
cout << "const char *" << endl;
return strcmp(str1, str2);
}
或者
//test.h
template<>
int compare(const char* str1, const char* str2);
//test.cpp
template<>
int compare(const char* str1, const char* str2)
{
cout << "特化 const char *" << endl;
return strcmp(str1, str2);
}
这样,就能正确比较字符串了。
全特化:所有类型模板参数都用具体类型代表,特化版本模板参数列表为空 template<>
template<typename T,typename U>
struct Test
{
void show()
{
cout<<"非特化版本"<<endl;
}
};
//全特化版本
template<>
struct Test<int,int>
{
void show()
{
cout<<"int,int特化版本"<<endl;
}
};
//特化版本可以有任意多个
template<>
struct Test<double,string>
{
void show()
{
cout<<"double,string特化版本"<<endl;
}
};
//测试
int main()
{
Test<int,int> t;
t.show(); //int,int特化版本
Test<double, string> t1;
t1.show(); //double,string特化版本
Test<char, char> t2;
t2.show(); //非特化版本
return 0;
}
局部特化(偏特化):指定一部分模板参数用具体类型代替
从模板参数数量上
//从模板参数数量上
template<typename T,typename U>
struct Test
{
void show()
{
cout<<"非特化版本"<<endl;
}
};
//局部特化
template<typename U>
struct Test<double,U>
{
void show()
{
cout<<"非特化版本"<<endl;
}
};
//测试
int main()
{
Test<char,string> tt;
tt.show(); //局部特化版本
return 0;
}
从模板参数范围上(int -> int&)
//从模板参数范围上
template<typename T>
struct Test
{
void show()
{
cout<<"非特化版本"<<endl;
}
};
//const T
template<typename T>
struct Test<T&>
{
void show()
{
cout<<"T&特化版本"<<endl;
}
};
//T*
template<typename T>
struct Test<T*>
{
void show()
{
cout<<"T*特化版本"<<endl;
}
};
//测试
int main()
{
Test<int> test;
test.show(); //非特化版本
Test<int*> test1;
test1.show(); //T*特化版本
Test<int&> test2;
test2.show(); //T&特化版本
return 0;
}
泛型编程和面向对象编程,都依赖与某种形式的多态。面向对象编程的多态性在运行时应用于存在继承关系的类。在泛型编程中,编写的代码可以用作多种类型的对象。面向对象编程所依赖的多态性称为运行时多态性,而泛型编程所依赖的多态性称为编译时多态性或参数式多态性。
通过上面这些介绍,相信会对模板与泛型理解的更深入。