泛型
c 中引进了泛型的概念,而引用泛型也是为了解决了C语言中对不同类型的参数需要实现多个不同参数的麻烦。 而泛型只是提供一个模板而已,对于不同的函数或者类会自动实例化相应的函数或者类。
模板
模板分为
函数模板
和类模板
。
函数模板
基本语法:
template<typename T,......>
,typename
也可以换成class
,而T
可以随便改变,不叫T也是可以的。
- 对于c语言的函数,我们需要写不同类型的参数。即使c 中引入了函数重载,也是需要写多个重载函数。而模板的出现解决了这个问题。 下面就演示一下:
cpptemplate<typename T>
T Add(const T& a, const T& b)
{
return a b;
}
当传的是
int
参数的时候,会自动推演出T为int,并且实例化出该函数,而这些工作都是编译器为我们做的。
但是
有时候需要我们显示的显示出类型。比如传这样的参数
Add(1.1,2)
,那么编译器就无法推演出来。此时就需要我们显示的传入类型。Add<double>(1.1,2)
,就是在函数名字的后面加上一个<>
,里面是显示传的类型。
代码语言:javascript复制当我们有显示类型的函数,还有一个模板参数的时候,那么会调用哪一个呢?结论是,当传入的参数是那个显示写的函数的时候,会调用显示写的,没有再去调用模板。
cpptemplate<typename T>
T Add(const T& a, const T& b)
{
cout << "模板" << endl;
return a b;
}
int Add(int& a, int& b)
{
cout << "非模板" << endl;
return a b;
}
测试代码:
int x = 1, y = 2; Add(x, y);
注意: 有时候会失败,可能的原因是权限问题,比如权限的放大会报错。欢迎评论区讨论。这里就不放相应的代码了。虽然我已经遇到过了。
类模板
代码语言:javascript复制为什么引用模板上文已经说了,这里就不再叙述。 本博主觉得和函数模板差别不是很大,只不过要显示的写出类型。类型随意,可以是自定义类型也可以为内置类型。类模板的定义和声明是不可以分在两个文件中的。 这里演示一个栈的类吧!
cpptemplate<class T>
class Stack
{
public:
Stack(size_t capacity=4)//构造函数
:_a(nullptr)
, _top(0)
, _capacity(0)
{
if (capacity > 0)
{
_a = new T[capacity];
_capacity = capacity;
}
}
void Push(T x)//插入
{
if (_top == _capacity)
{
int NewCapacity = _capacity == 0 ? 4 : _capacity * 4;
T* temp = new T[NewCapacity];
if (_capacity == 0)
{
_a = temp;
_capacity = NewCapacity;
}
else
{
memcpy(temp, _a, sizeof(T) * _capacity);
_a = temp;
_capacity = NewCapacity;
}
}
_a[_top] = x;
_top;
}
void Pop()//删除
{
assert(_top > 0);
--_top;
}
~Stack()//析构
{
delete[] _a;
_a = nullptr;
_capacity = _top = 0;
}
private:
T* _a;
size_t _top;
size_t _capacity;
};
int main()
{
Stack<int> p1;//显示传入类型
Stack<int> p2(100);
return 0;
}
非类型模板参数
非类型的模板参数就是:这个模板的形参是一个常量。
代码语言:javascript复制cpptemplate<class T,int N=20>//这里的int N=20就是一个非类型的模板参数
class A
{
T name[N];
};
int main()
{
A<int,30> a;
return 0;
}
对于这个常量我们也是有要求,要求不能是浮点数,类对象,字符串
模板特化
什么是模板特化?为什么要引入模板特化? 先回答第一个问题,什么是模板特化——在原先已有模板的情况下,将参数T写成具体的类型。 特化的形式:
代码语言:javascript复制cpptemplate<class T>//已有的模板
class A
{
.........
}
//模板特化
template<>
class A<int*>
{
......
}
代码语言:javascript复制cpptemplate<class T>
bool comp(T a,T b)
{
return a>b;
}
template<>
bool comp<int>(int a,int b)
{
return a>b;
}
对于类的模板的特化,还有一些其他的情况: 部分特化:
代码语言:javascript复制cpptemplate<class T1,class T2>
class A
{
T1 a;
T2 n;
};
//只把第二个参数类型进行了特化处理
template<class T>
class A<T,int>
{
T a;
int b;
};
为什么要模板特化呢? 因为一些特殊的场景,比如比较大小的时候,我们要比较两个数的大小,但是如果此时传递的是指针那么就不能比较了,或者说要重新写一个,那么此时就可以特化一下。
代码语言:javascript复制cppbool comp(T a, T b)
{
return a > b;
}
int main()
{
int x = 1, y = 2;
cout << comp(x, y) << endl;
cout << comp(&x, &y) << endl;
return 0;
}
结果是0,1。我们想的是即使传的是地址也能进行比较,而不是对地址进行比较。 下面这样就可以了。
代码语言:javascript复制cpptemplate<>
bool comp<int*>(int* a, int* b)
{
return *a > *b;
}
模板的分离编译
模板是不支持分离编译的,即在模板的声明和定义不在同一个文件里面。 为什么它不支持分离编译呢? 通常情况下,当分离编译的时候,声明是放在.cpp里面的,定义是放在.h。 下面是代码变成可执行文件的过程:(简化) 预编译:.h文件的内容在.cpp中展开 编译:.cpp文件变成汇编代码,此时的模板.cpp里面参数T的类型不确定,所以不会有相应的指令代码。 汇编:二进制文件 链接:把所有的.cpp文件链接在一起生成可执行文件。 那么在链接的时候就会出现问题,我们只能找到声明,不能找到定义。有人会说,不是有.cpp里面的定义吗?——模板参数在编译的时候没有生成具体类型,所有找不到。