类模板
Stack 类
我们先给出我们之前实现的栈结构:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
class Stack
{
public:
Stack(int size = 1024)
{
space = new int[size];
top = 0;
}
~Stack() {
delete[]space;
}
bool isEmpty() {
return top == 0;
}
bool isFull() {
return top == 1024;
}
void push(int data) {
space[top++] = data;
}
int pop() {
return space[--top];
}
private:
int* space;
int top;
};
int main()
{
Stack s(100);
for (int i = 0; i < 10; ++i) {
if (!s.isFull())
s.push(i);
}
while (!s.isEmpty())
cout << s.pop() << endl;
return 0;
}
运行结果为:
上面代码中如果我们要对于栈结构进行泛化,最主要的就是对于int * space 存储空间的泛化。
Stack 类模板化,可以 push 和 pop 不同的数据类型。主要由几个因素需要把控。
那么只需要保持栈中的空间元素类型,压入元素类型,弹出元素类型,三者保持一致就能够实现泛化。
我们在函数模板中使用的时候先写函数模板,然后进行函数模板的实例化形成模板函数,然后对于实例化之后的模板函数进行调用。
myswap() --> myswap() --> myswap()(a,b)
函数模板 --> 模板函数 --> 函数调用
对比到类模板就是:
类模板 --> 模板类 --> 类对象的创建
stack --> stack --> stack s(100)
类模板格式
template<typename T> class ClassName
{
void func(T );
};
template<typename T> void ClassName<T>::func(T)
{
}
类模板的应用
我们现在对于上面的栈类进行泛化:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
template <typename T>
class Stack
{
public:
Stack(int size = 1024)
{
space = new T[size];
top = 0;
}
~Stack() {
delete[]space;
}
bool isEmpty() {
return top == 0;
}
bool isFull() {
return top == 1024;
}
void push(T data) {
space[top++] = data;
}
T pop() {
return space[--top];
}
private:
T* space;
int top;
};
int main()
{
Stack<int> s;//类模板的实例化 生成模板类 并且创建类对象
for (int i = 0; i < 10; i++)
{
if (!s.isFull())
s.push(i * 11);
}
while(!s.isEmpty())
{
cout << s.pop() << endl;
}
return 0;
}
运行结果为:
类内函数的声明和定义分开的实现:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
template <typename T> //类模板
class Stack
{
public:
Stack(int size = 1024);
~Stack();
bool isEmpty();
bool isFull();
void push(T data);
T pop();
private:
T* space;
int top;
};
template <typename T>
Stack<T>::Stack(int size)
{
space = new T[size];
top = 0;
void push(T data);
}
template <typename T>
Stack<T>::~Stack()
{
delete[]space;
}
template <typename T>
bool Stack<T>::isEmpty()
{
return top == 0;
}
template <typename T>
bool Stack<T>::isFull()
{
return top == 1024;
}
template <typename T>
void Stack<T>::push(T data)
{
space[top++] = data;
}
template <typename T>
T Stack<T>::pop() {
return space[--top];
}
int main()
{
Stack<int> s;//类模板的实例化 生成模板类 并且创建类对象
for (int i = 0; i < 10; i++)
{
if (!s.isFull())
s.push(i * 10);
}
while (!s.isEmpty())
{
cout << s.pop() << endl;
}
return 0;
}
运行结果为:
类模板的多文件实现
main.cpp
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "mystack.h"
#include "mystack.cpp"
using namespace std;
int main()
{
Stack<int> s;//类模板的实例化 生成模板类 并且创建类对象
for (int i = 0; i < 10; i++)
{
if (!s.isFull())
s.push(i * 10);
}
while (!s.isEmpty())
{
cout << s.pop() << endl;
}
return 0;
}
mystack.cpp
#include "mystack.h"
template <typename T>
Stack<T>::Stack(int size)
{
space = new T[size];
top = 0;
void push(T data);
}
template <typename T>
Stack<T>::~Stack()
{
delete[]space;
}
template <typename T>
bool Stack<T>::isEmpty()
{
return top == 0;
}
template <typename T>
bool Stack<T>::isFull()
{
return top == 1024;
}
template <typename T>
void Stack<T>::push(T data)
{
space[top++] = data;
}
template <typename T>
T Stack<T>::pop() {
return space[--top];
}
mystack.h
#pragma once
template <typename T> //类模板
class Stack
{
public:
Stack(int size = 1024);
~Stack();
bool isEmpty();
bool isFull();
void push(T data);
T pop();
private:
T* space;
int top;
};
运行结果为:
这里需要强调的是,我们在实现多文件变成的时候并不是简单的把之前类内函数的定义和实现分开,然后在多文件中编写,而是需要注意需要在main函数中加入#include “mystack.cpp”才能够编译成功和运行。
那么为什么是需要加上#include “mystack.cpp”
我们在类模板的友元中进行说明。
类模板的友元
类模板中的友元在.h
友元函数,实现在.h 文件中并不多见,但在模板中这样使用友元,就是一种常规则用法。
main.cpp
#include <iostream>
#include "mylist.h"
#include "mylist.cpp"
using namespace std;
using namespace listspace;
int main()
{
GenericList<int> first_list(2);
first_list.add(1);
first_list.add(2);
cout <<"first_list"<< first_list << endl;
GenericList<char> second_list(10);
second_list.add('A');
second_list.add('B');
second_list.add('C');
cout << second_list<<second_list << endl;
return 0;
}
mylist.cpp
#ifndef __MYLIST_ CPP__
#define __MYLIST_ CPP__
#include <iostream>
#include <cstdlib>
#include "mylist.h"
using namespace std;
namespace listspace {
template<class ItemType>
GenericList<ItemType>::GenericList(int max)
: _maxLength(max), _curIdx(0)
{
_item = new ItemType[max];
}
template<class ItemType>
GenericList<ItemType>::~GenericList()
{
delete[] _item;
}
template<class ItemType>
int GenericList<ItemType>::length() const
{
return (_curIdx);
}
template<class ItemType>
void GenericList<ItemType>::add(ItemType new_item)
{
if (full())
{
cout << "Error: adding to a full list.\n";
exit(1);
}
else
{
_item[_curIdx] = new_item;
_curIdx = _curIdx + 1;
}
}
template<class ItemType>
bool GenericList<ItemType>::full() const
{
return (_curIdx == _maxLength);
}
template<class ItemType>
void GenericList<ItemType>::erase()
{
_curIdx = 0;
}
}
#endif
mylist.h
#ifndef __MYLIST_H H__
#define __MYLIST_H H__
#include <iostream>
#include <ostream>
using namespace std;
namespace listspace
{
template<class ItemType>
class GenericList
{
public:
GenericList(int max);
~GenericList();
int length() const;
void add(ItemType new_item);
bool full() const;
void erase();
friend ostream& operator<<(ostream & out,GenericList<ItemType>& list)
{
for (int i = 0; i < list._curIdx; i++)
{
out << list._item[i];
}
return out;
}
private:
ItemType* _item;
int _maxLength;
int _curIdx;
};
}//listspace
#endif //__MYLIST_H__
运行结果为:
类模板中的友元在.cpp
友元函数,实现在.cpp 中,需要在类模板前声明,其前还需要类模板的声明。类内用<>将其声明为空。
mylist.cpp
#ifndef __MYLIST_ CPP__
#define __MYLIST_ CPP__
#include <iostream>
#include <cstdlib>
#include "mylist.h"
using namespace std;
namespace listspace {
template<class ItemType>
GenericList<ItemType>::GenericList(int max)
: _maxLength(max), _curIdx(0)
{
_item = new ItemType[max];
}
template<class ItemType>
GenericList<ItemType>::~GenericList()
{
delete[] _item;
}
template<class ItemType>
int GenericList<ItemType>::length() const
{
return (_curIdx);
}
template<class ItemType>
void GenericList<ItemType>::add(ItemType new_item)
{
if (full())
{
cout << "Error: adding to a full list.\n";
exit(1);
}
else
{
_item[_curIdx] = new_item;
_curIdx = _curIdx + 1;
}
}
template<class ItemType>
bool GenericList<ItemType>::full() const
{
return (_curIdx == _maxLength);
}
template<class ItemType>
void GenericList<ItemType>::erase()
{
_curIdx = 0;
}
template<class ItemType>
ostream& operator<< (ostream& out, GenericList<ItemType> & list)
{
for (int i = 0; i < list._curIdx; i++)
{
out << list._item[i];
}
return out;
}
}
#endif
mylist.h
#ifndef __MYLIST_H H__
#define __MYLIST_H H__
#include <iostream>
#include <ostream>
using namespace std;
namespace listspace
{
template<class ItemType>
class GenericList;
template<class ItemType>
ostream& operator<<(ostream& out, GenericList<ItemType>& list);
template<class ItemType>
class GenericList
{
public:
GenericList(int max);
~GenericList();
int length() const;
void add(ItemType new_item);
bool full() const;
void erase();
friend ostream& operator<< <>(ostream& out, GenericList<ItemType>& list);
private:
ItemType* _item;
int _maxLength;
int _curIdx;
};
}//listspace
#endif //__MYLIST_H__
main.cpp
#include <iostream>
#include "mylist.h"
#include "mylist.cpp"
using namespace std;
using namespace listspace;
int main()
{
GenericList<int> first_list(2);
first_list.add(1);
first_list.add(2);
cout <<"first_list"<< first_list << endl;
GenericList<char> second_list(10);
second_list.add('A');
second_list.add('B');
second_list.add('C');
cout << second_list<<second_list << endl;
return 0;
}
运行结果为:
在类完中的cpp实现的时候需要进行以下操作:
① 在类中声明<> 表明是一个空体声明
② 在类外实现 和其他函数实现相同 需要加上类型模板
③ 在类的声明的前面,对类模板的友元函数作前向声明。并且在其前面作类的前向声明
所以一般建议读者在.h文件中实现类模板的友元
但是呢,我们平时在使用到时候很少会有#include “mylist.cpp”这样的操作,所以我们引入hpp
hpp
由于编译器需要通过这些"模板"为实例化类型生成实际的方法代码,因此任何使用了模板的源代码文件中,编译器都应该能同时访问类模板定义和方法定义。
C++中的编译是以文件为单位的,然后链接阶段完成链接。如果模板的声明与实现分开,这种机制显然会导致看不到模板的全貌,而致编译失败。所以常将类模板定义和方法定义放到一起,该类模板文件的后缀常为.hpp,以示与普通文件的区别。在使用的时候,#include"xx.hpp"。
代码演示:
mylist.hpp
#ifndef __MYLIST_H H__
#define __MYLIST_H H__
#include <iostream>
#include <ostream>
using namespace std;
namespace listspace
{
template<class ItemType>
class GenericList;
template<class ItemType>
ostream& operator<<(ostream& out, GenericList<ItemType>& list);
template<class ItemType>
class GenericList
{
public:
GenericList(int max);
~GenericList();
int length() const;
void add(ItemType new_item);
bool full() const;
void erase();
friend ostream& operator<< <>(ostream& out, GenericList<ItemType>& list);
private:
ItemType* _item;
int _maxLength;
int _curIdx;
};
}//listspace
#endif //__MYLIST_H__
#ifndef __MYLIST_ CPP__
#define __MYLIST_ CPP__
#include <iostream>
#include <cstdlib>
#include "mylist.h"
using namespace std;
namespace listspace {
template<class ItemType>
GenericList<ItemType>::GenericList(int max)
: _maxLength(max), _curIdx(0)
{
_item = new ItemType[max];
}
template<class ItemType>
GenericList<ItemType>::~GenericList()
{
delete[] _item;
}
template<class ItemType>
int GenericList<ItemType>::length() const
{
return (_curIdx);
}
template<class ItemType>
void GenericList<ItemType>::add(ItemType new_item)
{
if (full())
{
cout << "Error: adding to a full list.\n";
exit(1);
}
else
{
_item[_curIdx] = new_item;
_curIdx = _curIdx + 1;
}
}
template<class ItemType>
bool GenericList<ItemType>::full() const
{
return (_curIdx == _maxLength);
}
template<class ItemType>
void GenericList<ItemType>::erase()
{
_curIdx = 0;
}
template<class ItemType>
ostream& operator<< (ostream& out, GenericList<ItemType>& list)
{
for (int i = 0; i < list._curIdx; i++)
{
out << list._item[i];
}
return out;
}
}
#endif
main.cpp
#include <iostream>
#include "mylist.hpp"
using namespace std;
using namespace listspace;
int main()
{
GenericList<int> first_list(2);
first_list.add(1);
first_list.add(2);
cout <<"first_list"<< first_list << endl;
GenericList<char> second_list(10);
second_list.add('A');
second_list.add('B');
second_list.add('C');
cout << second_list<<second_list << endl;
return 0;
}
运行结果为:
模板通过会将声明和实现放在一个文件中,及就是.hpp中。这样做的原因就是,C语言和C++的编译模式是按照文件进行编译的。模板在进行实例化的时候,是需要看到整个模板的,及就是模板的定义和实现的全部代码。所以如果只有.h的话,实例化的时候仅仅只是看到模板的声明,并不能看到模板的实现,所以需要使用.hpp来实现。把模板的声明和实现放在一起。
也就是说在任何需要实例化的地方,都需要看看到模板的全部的声明和定义。