当我们要实现swap函数交换两个变量时,我们需要写每种类型参数的函数满足不同类型的变量。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
这个时候就导致代码比较冗余,我们怎么才能实现一个通用的交换函数呢?
C++提出了泛型编程,写跟类型无关的通用的代码,是代码复用的手段——模板。模板分为函数模板和类模板,我们此时写一个函数模板来写一个通用的swap函数。
template<typename T>//模板参数-》类型
//template<calss T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
int main()
{
int a = 2;
int b = 3;
swap(a,b);//推演
}
此时的main函数调用swap时此时并不是直接调用函数,模板根据调用的参数推演出函数,这个过程在预编译阶段就发生了,本质是我们写了模板,编译器通过模板推演出类或者函数。
注:当模板内部的内容出现编译错误且模板此时没有调用和实例化,此时不会报错编译会通过。
template<typename T>//模板参数-》类型
//template<calss T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp //此时去掉一个分号
}
int main()
{//main函数不去调用模板
int a = 2;
int b = 3;
}//并没有出错 编译通过 运行成功
上面swap是实现的一个模板函数,接下来我们来实现一个栈,应用类模板,对比C/C++:
(1)用C语言的方法实现一个栈:
typedef int STDateType;
typedef struct Stack_C
{
STDateType* _a;
int _size;
int _capacity;
}Stack_C;
void Stack_CInit(Stack_C* ps);
void Stack_CDestory(Stack_C* ps);
void Stack_CPush(Stack_C* ps, STDateType x);
void Stack_CPop(Stack_C* ps);
//...
创建一个栈,入栈:
int main()
{
Stack_C st_c;
Stack_CInit(&st_c);
Stack_CPush(&st_c, 1);
Stack_CPush(&st_c, 2);
Stack_CPush(&st_c, 3);
//非法修改:
st_c._capacity = 0;
Stack_CDestory(&st_c);
}
C语言实现有很多缺点:
----必须要在对栈操作前初始化,对栈使用完销毁。
----非法修改,容量和长度被随意修改导致混乱。
----如果我们想定义存储不同数据类型的栈就要重新定义结构体,操作不便。
(2)C++解决了这些问题:
----C++在类的对象创建后会自动调用类的构造函数初始化,在进程结束时会调用析构函数销毁。
----C++的封装性使得类外不能随意更改私有的成员变量,更加安全。
----C++提供了类模板,可以写和类型无关的通用代码,可以存储不同的数据类型。
template<class T>
class Stack_CPP
{
public:
Stack_CPP() {}
~Stack_CPP() {}
Push(STDateType x) {}
private:
T* _a;
size_t _size;
size_t _capacity;
};
int main()
{
Stack_CPP<int> st_cpp_int;//<int>类型
Stack_CPP<double> st_cpp_double;
st_cpp_int.Push(1);//隐含的this指针
st_cpp_double.Push(2.4);
}
练习:实现一个vector的类模板
此时需要辨析两个概念:
----引用传参:引用传参在函数中便于对实参进行修改;对于自定义类型,引用传参可以减少参数传递时的拷贝构造,使程序运行效率更高。
----引用传返回值:函数在传返回值时会拷贝一个临时对象将其返回,因为是临时对象所以具有常性,不可更改,此时我们用引用返回时即免去了一次拷贝又能对原数进行修改,在vector的operator[ ]中,我们需要对要访问的第i个数组元素进行获取或者更改,所以需要引用返回。
template<class T>
class vector//顺序表,可以动态增长的数组
{
public:
vector()
:_a(nullptr)
, _size(0)
, _capacity(0)
{}
vector(size_t n)
:_a(new T[n])
, _size(0)
, _capacity(n)
{}
~vector()
{
delete[] _a;
_a = nullptr;
_size = _capacity = 0;
}
//在类里面声明,类外面定义
void push_back(const T& x);//如果T是自定义类型可以免去拷贝构造
void push_pop_back();
size_t size()
{
return _size;
}
T& operator [](size_t i)
{
assert(i < _size);//判断有效访问
return _a[i];
}
private:
T* _a;
size_t _size;
size_t _capacity;
};
template<class T>
void vector<T>::push_back(const T& x)
{//如果空间不够,需要进行增容
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
T* tmp = new T[newcapacity];//new一个新大小的数组
if (_a)//如果_a数组不为空,就把数组_a的内容按照字节拷贝给tmp
{
memcpy(tmp, _a, sizeof(T) * _size);//void *memcpy(void *destin, void *source, unsigned n);
delete[] _a;//释放掉_a所指的空间
}
_a = tmp;//令_a指向新的空间
_capacity = newcapacity;//更新空间大小
}
_a[_size] = x;
++_size;
}
模板的隐式调用和显式调用
在调用函数模板或者类模板时,如果用<>指明了数据类型,编译器会根据提供的类型推生成对应函数或类,这是显式调用模板;如果没有指明类型,编译器会通过传数据的类型推演,这是隐式调用模板。如果传的参数类型不一致就会报错,但此时要是显式调用就会将不符合类型的数据进行隐式类型转换使其满足。
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 2, a2 = 20;
double d1 = 10.0, d2 = 12.3;
Add(a1, a2);
Add(d1, d2);
Add<int>(a1, d2);//隐式类型的转换
}