泛型编程可以理解为和具体类型无关的编程。
类模板一般没有推演时机,函数模板是实参传递形参,推演函数模板。
类模板一般显示实例化。
st1和st2是两个不同类型,它俩大小也有可能不同。
比如下面T对于st1和st2分别是double和int,可见两个类的大小是肯定不同的,st1和st2是两种不同的类。
它们是一个类模板实例化出来的,但是模板参数不同,它们就是不同的类型。
下面例子可以证明。
#include <iostream>
#include <assert.h>
using namespace std;
template <typename T>
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = )" <<capacity<<endl;
_a = (T*)malloc(sizeof(T)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
Stack(const Stack& s)
{
T* news = new T[s._capacity];
_a = news;
_top = 0;
_capacity = s._capacity;
for (int i = 0;i < s._top;i++)
{
_a[_top++] = s._a[i];
}
}
void Swap(Stack& s)
{
std::swap(_a, s._a);
std::swap(_top, s._top);
std::swap(_capacity, s._capacity);
}
Stack& operator = (Stack& s)
{
if (this != &s)
{
Stack tmp(s);
Swap(tmp);
}
return *this;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
void Push(const T& x)
{
// ....
// 扩容
_a[_top++] = x;
}
private:
T* _a;
int _top;
int _capacity;
};
int main()
{
Stack<double> st1;
st1.Push(1.1);
st1.Push(1.2);
Stack<int> st2;
st2.Push(1);
Stack<double> st3;
st3.Push(1.1);
st3 = st1;
//st1.Push(1.2);
st3 = st2;
}
注意,每个类模板前都要加template<typename T>,不是一个程序只写一个就行。这是对模板的声明。
再来使用类模板的例子。
#include<iostream>
#include<assert.h>
#define N 10
using namespace std;
template<typename T>
class Array
{
public:
inline T& operator[](int i)
{
assert(i < N);//保证了越界能百分百查找到,普通数组方括号越界是抽查可能会查找不到
return _a[i];
}
private:
T _a[N];
};
int main()
{
Array<int> a;
for (int i = 0;i < 10;i++)
{
a[i] = i;
}
for (int i = 0;i < N;i++)
{
cout << a[i] << ' ';
}
cout << endl;
for (int i = 0;i < 10;i++)
{
a[i]++ ;
}
for (int i = 0;i < N;i++)
{
cout << a[i] << ' ';
}
cout << endl;
return 0;
}
模板不支持分离编译,会在链接阶段报错,说明不是语法的问题(缺少;算语法问题 ),而是找不到的问题。
编译阶段,cpp中Stack和Push要找定义的地址,但此时只有.h,.h中没有定义地址,只有声明地址,但编译阶段编译器会通过,链接时,编译器会找定义的地址,编译器会在Stack.o的符号表里找定义的地址,但却找不到。
把Stack.h中的Push去掉,就没有链接错误,是编译错误,因为编译阶段,Test.cpp没有找到Push的声明或定义,会直接报错。
用的地方实例化了,有实例化的地方的文件只有声明,但此文件没有类的定义,有定义的地方没有实例化,无法生成对应的函数放进符号表里,总之,被调用的模板类及函数不知道T是什么,不能正确被调用。
但有定义的文件中却没有实例化。
第一种 解决方式是在有定义的文件中显示实例化。
template需要加上,需要表明它是个模板。
但这个方式有一个问题,那就是只要换类型就需要在有定义的文件中再补一个实例化。
第二种解决方式是把定义放入自己写的.h头文件中。因为此时就不需要链接去找,.h头文件中就有这定义,.h已经被.cpp包含,预处理阶段就会展开,此时.cpp文件有声明和定义,声明和定义都被实例化了,就不存在链接的问题。
只有声明没有定义,编译器会在链接时去找定义,造成如上问题,而这里编译时就找到了定义的地址,(而且这个地址是已经实例化后的)。
inline不会进符号表,但不允许声明和定义分离,这里解决不了问题。