目录
1.问题引入
再次回到编写交换函数上;我们之前说过函数重载:不同的参数类型下,我们可以通过函数重载使得同一功能的不同类型函数变成同一个名字,想要交换int类型,ok,C中可能还要起名字swapint来区分是int类型的交换,但是C++中直接swap即可,只要后面的重载参数不一样就可以了。但是如果我又要的是double类型的交换函数,我又自己写了一个,要char类型的,我又双叒叕写一个重载函数。但是这样不能很累吗?明明功能一模一样,只是参数不同而已,每次要用就又得写一个,重复的代码大大提高了,这样太麻烦了吧?
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
void Swap(double& left, double& right)
{
double tmp = left;
left = right;
right = tmp;
}
//上面的代码重复度极高,说明这写代码实现很低效。
int main()
{
int a = 0, b = 1;
Swap(a, b);
double c = 1.1, d = 2.2;
Swap(c, d);
return 0;
}
2.模板
写一个“模具“函数,除了类型不同,其他代码中的内在逻辑完全相同。此时调用这个模板即可使得系统自动生成函数节省我们的时间和精力。
1.函数模板
用template<class T>、template<typename T>定义模板中的抽象类型。
template<typename T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 0, b = 1;
Swap(a, b);
double c = 1.1, d = 2.2;
Swap(c, d);
return 0;
}
在表层理解下,两个都是调用模板函数;其实本质是编译器通过我们写的函数模板写出来int和double类型的函数,而真正被调用的函数其实是编译器自己写的不同的两个函数。此过程称为“函数模板的实例化”。实例化是指我们用的不同种函数决定的。这种由编译器自己产生的实例化是“自动推演实例化”。
那么如果我想把a(int)和c(double)呢,这样可以实现吗?答案是不可以实现,因为编译器不会帮你强制转换类型,它只会在实例化前判断你的类型是什么,两个类型不同的根本不可以。
那我们应该如何实现参数不同类型的函数呢?1.传入前函数强制类型转化 2.模板函数显示实例化
1.传入前函数强制类型转化
template<typename T>
T add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 1, a2 = 2;
double b1 = 1.1, b2 = 2.2;
cout << add(a1,(int)b1) << endl;
cout << add((double)a1,b1) << endl;
return 0;
}
2.模板函数显示实例化
template<typename T>
T add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 1, a2 = 2;
double b1 = 1.1, b2 = 2.2;
cout << add<int>(a1, b1) << endl;
cout << add<double>(a1, b1) << endl;
return 0;
}
具体的函数和模板的函数可以同时存在,系统发现我们写的函数完全满足功能时,系统调用我们写的函数,不生成实例化模板函数。如果就想调用系统生成的函数,那么使用显式实例化就可以,而且编译器不会报错,实例化的函数和自己写的函数可以同时存在,说明模板函数和普通的函数修饰规则有所区分。
不同类型的模板
template<typename T1,typename T2>
T1 add(const T1& left, const T2& right)
{
return left + right;
}
int main()
{
int a1 = 1, a2 = 2;
double b1 = 1.1, b2 = 2.2;
cout << add(a1, b1) << endl;
cout << add(a1, b1) << endl;
return 0;
}
2.类模板
1.引出
在C种实现栈的数据结构,我们会出现typedef来定义类型,方便改动栈保存数据的类型。
typedef int STDataType;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = )" <<capacity<<endl;
_a = (STDataType*)malloc(sizeof(STDataType)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
int _top;
int _capacity;
};
确实方便了一些,比如我要int类型,那么typedef定义int,要double就定义double,但是如果我要的不只是一个类型的栈呢?假设我int类型和double类型都要,那是不是意味着我又得在写一个功能逻辑完全一样,关键是类不支持重载,使用还得把名字定义成不同的类,单单是类型不同的栈来浪费时间呢?所以针对这个问题我们可以使用类模板实现。
2.实现
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()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
T* _a;
int _top;
int _capacity;
};
int main()
{
Stack<int> a1;
return 0;
}
类模板没有推演的时机,所以想要得到自己想要的类,需要通过显示实例化得到。模板实例化出不同类型的类不是同一个类。
3.自定义类型通过符号重载变成数组的形式
重载运算符[],使得让自定义类型也获跟数组差不多的访问方式。
#define N 10
template<typename T>
class arr
{
public:
T& operator[](size_t i)
{
return _a[i];
}
private:
T _a[N];
int size;
};
int main()
{
arr<int> a1;
for (size_t i = 0; i < N; ++i)
a1[i] = i;
for (size_t i = 0; i < N; ++i)
cout << a1[i] << " ";
cout << endl;
for (size_t i = 0; i < N; ++i)
a1[i]++;
for (size_t i = 0; i < N; ++i)
cout << a1[i] << " ";
cout << endl;
return 0;
}
数组最怕的就是越界访问操作,但是重载[]运算符时,我们可以手动判断该类在访问数据时是否越界。原生数组的越界访问是抽查报错,而读取访问则直接不管,其实这样十分危险。但是重载了,那么assert就会直接判断,无论对数据读写。
T& operator[](size_t i)
{
assert(i < N); //越界判断条件
return _a[i];
}