1.类模板之类型参数
对于类模板来说,编译器无法进行自动类型推导,只能显式指定类型。
模板名称加上模板参数列表才是类名称,例如 SeqStack
是模板名称,SeqStack<T>
是类名称,实际上,我们在写代码时想用的肯定是类名 SeqStack<T>
,但为了简便,在类模板内部可以直接使用模板名 SeqStack
并不需要提供模板参数列表 <T>
。
建议:构造函数名和析构函数名不用加模板参数列表 <T>
,其它出现模板的地方都加上模板参数列表 <T>
。
注意:在老标准中,只能为类模板提供默认模板参数;在 C++11 新标准中,函数模板和类模板都可以提供默认模板参数。
1.1 实现按两倍方式扩容的顺序栈
#include <iostream>
using namespace std;
template <typename T>
class SeqStack
{
public:
SeqStack(int size = 10)
: m_data(new T[size])
, m_top(0)
, m_size(size)
{ }
~SeqStack()
{
delete[] m_data;
m_data = nullptr;
}
SeqStack(const SeqStack<T>& stack)
: m_top(stack.m_top)
, m_size(stack.m_size)
{
m_data = new T[m_size];
for (int i = 0; i < m_top; ++i) // 不要用memcopy进行拷贝,因为不知道T是什么类型
{
m_data[i] = stack.m_data[i];
}
}
SeqStack<T>& operator=(const SeqStack<T>& stack)
{
if (this == &stack) return *this;
delete[] m_data;
m_top = stack.m_top;
m_size = stack.m_size;
m_data = new T[m_size];
for (int i = 0; i < m_top; ++i) // 不要用memcopy进行拷贝,因为不知道T是什么类型
{
m_data[i] = stack.m_data[i];
}
return *this;
}
void push(const T& val)
{
if (full()) expand();
m_data[m_top++] = val;
}
void pop()
{
if (empty()) return;
--m_top;
}
// 如果某个方法只涉及读而不涉及写,我们尽量把它实现成const常方法,这样一来,普通对象可以调用,常对象也可以调用。
T top() const
{
if (empty()) throw "stack is empty!"; // 抛异常也代表函数逻辑结束
return m_data[m_top - 1];
}
bool full() const
{
return m_top == m_size;
}
bool empty() const
{
return m_top == 0;
}
private:
void expand()
{
T* new_data = new T[m_size * 2];
for (int i = 0; i < m_top; ++i)
{
new_data[i] = m_data[i];
}
delete[] m_data;
m_data = new_data;
m_size *= 2;
}
private:
T* m_data;
int m_top;
int m_size;
};
int main()
{
SeqStack<int> stk; // 显式类型指定
stk.push(10);
stk.push(20);
stk.push(30);
stk.pop();
cout << stk.top() << endl;
return 0;
}
1.2 类模板的成员函数
如上代码所示,类模板成员函数的实现可以写在类模板定义的里面。
需要注意的是,类模板的成员函数具有和这个类模板相同的模板参数,如果要把类模板成员函数的实现写在类模板定义的外面,那么必须以关键字 template
开始,后面加上模板参数列表,同时用带模板参数列表的类名修饰类模板的成员函数名。
SeqStack.h
#ifndef __SEQSTACK_H
#define __SEQSTACK_H
template <typename T>
class SeqStack
{
public:
SeqStack(int size = 10);
~SeqStack();
SeqStack(const SeqStack<T>& stack);
SeqStack<T>& operator=(const SeqStack<T>& stack);
void push(const T& val);
void pop();
T top() const;
bool full() const;
bool empty() const;
private:
void expand();
private:
T* m_data;
int m_top;
int m_size;
};
template <typename T>
SeqStack<T>::SeqStack(int size)
: m_data(new T[size])
, m_top(0)
, m_size(size)
{}
template <typename T>
SeqStack<T>::~SeqStack()
{
delete[] m_data;
m_data = nullptr;
}
template <typename T>
SeqStack<T>::SeqStack(const SeqStack<T>& stack)
: m_top(stack.m_top)
, m_size(stack.m_size)
{
m_data = new T[m_size];
for (int i = 0; i < m_top; ++i) // 不要用memcopy进行拷贝,因为不知道T是什么类型
{
m_data[i] = stack.m_data[i];
}
}
template <typename T>
SeqStack<T>& SeqStack<T>::operator=(const SeqStack<T>& stack)
{
if (this == &stack) return *this;
delete[] m_data;
m_top = stack.m_top;
m_size = stack.m_size;
m_data = new T[m_size];
for (int i = 0; i < m_top; ++i) // 不要用memcopy进行拷贝,因为不知道T是什么类型
{
m_data[i] = stack.m_data[i];
}
return *this;
}
template <typename T>
void SeqStack<T>::push(const T& val)
{
if (full()) expand();
m_data[m_top++] = val;
}
template <typename T>
void SeqStack<T>::pop()
{
if (empty()) return;
--m_top;
}
// 如果某个方法只涉及读而不涉及写,我们尽量把它实现成const常方法,这样一来,普通对象可以调用,常对象也可以调用。
template <typename T>
T SeqStack<T>::top() const
{
if (empty()) throw "stack is empty!"; // 抛异常也代表函数逻辑结束
return m_data[m_top - 1];
}
template <typename T>
bool SeqStack<T>::full() const
{
return m_top == m_size;
}
template <typename T>
bool SeqStack<T>::empty() const
{
return m_top == 0;
}
template <typename T>
void SeqStack<T>::expand()
{
T* new_data = new T[m_size * 2];
for (int i = 0; i < m_top; ++i)
{
new_data[i] = m_data[i];
}
delete[] m_data;
m_data = new_data;
m_size *= 2;
}
#endif
main.cpp
#include <iostream>
#include "SeqStack.h"
using namespace std;
int main()
{
SeqStack<int> stk(15); // 显式类型指定
stk.push(10);
stk.push(20);
stk.push(30);
stk.pop();
cout << stk.top() << endl;
return 0;
}
2.类模板➡模板的实例化➡模板类
类模板:是不进行编译的,因为类型还不知道。
模板的实例化:在编译时,当编译器看到模板定义时,并不会立即产生代码,只有在看到模板调用时,编译器才会产生对应的实例。也就是说,模板如果没有被使用,是不会被实例化出来的。
类模板的选择性实例化:一个类模板可能有很多成员函数,但如果后续没有使用到某个成员函数,则这个成员函数不会被实例化。
模板类:是要被编译器所编译的。
3.类模板之非类型参数
#include <iostream>
using namespace std;
template <typename T, int SIZE = 10>
class SeqStack
{
public:
SeqStack()
: m_data(new T[SIZE])
, m_top(0)
, m_size(SIZE)
{
}
~SeqStack()
{
delete[] m_data;
m_data = nullptr;
}
SeqStack(const SeqStack<T>& stack)
: m_top(stack.m_top)
, m_size(stack.m_size)
{
m_data = new T[m_size];
for (int i = 0; i < m_top; ++i) // 不要用memcopy进行拷贝,因为不知道T是什么类型
{
m_data[i] = stack.m_data[i];
}
}
SeqStack<T>& operator=(const SeqStack<T>& stack)
{
if (this == &stack) return *this;
delete[] m_data;
m_top = stack.m_top;
m_size = stack.m_size;
m_data = new T[m_size];
for (int i = 0; i < m_top; ++i) // 不要用memcopy进行拷贝,因为不知道T是什么类型
{
m_data[i] = stack.m_data[i];
}
return *this;
}
void push(const T& val)
{
if (full()) expand();
m_data[m_top++] = val;
}
void pop()
{
if (empty()) return;
--m_top;
}
// 如果某个方法只涉及读而不涉及写,我们尽量把它实现成const常方法,这样一来,普通对象可以调用,常对象也可以调用。
T top() const
{
if (empty()) throw "stack is empty!"; // 抛异常也代表函数逻辑结束
return m_data[m_top - 1];
}
bool full() const
{
return m_top == m_size;
}
bool empty() const
{
return m_top == 0;
}
private:
void expand()
{
T* new_data = new T[m_size * 2];
for (int i = 0; i < m_top; ++i)
{
new_data[i] = m_data[i];
}
delete[] m_data;
m_data = new_data;
m_size *= 2;
}
private:
T* m_data;
int m_top;
int m_size;
};
int main()
{
SeqStack<int, 15> stk; // 显式参数指定
stk.push(10);
stk.push(20);
stk.push(30);
stk.pop();
cout << stk.top() << endl;
return 0;
}