C++:模板
1. 前言
C++最重要的特征之一就是代码重用,为了实现这一操作,代码必须具有通用性。通用代码不受数据类型的影响,并且可以自动适应数据类型的变化。为了实现以上操作,我们需要运用到模板,模板C++支持参数化程序设计的工具,通过其可以实现参数多态性。参数多态性就是将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。
2. 模板函数
函数模板用于生成函数,函数模板的定义形式:
template<模板参数表>
类型名 函数名(参数表){
函数体定义
}
2.1 示例
2.1.1 输出不同类型的队列
例子:
#include<iostream>
using namespace std;
template <class T>
void outputArray(const T* array, int count) {
for (int i = 0; i < count; i++)
cout << array[i] << "";
cout << endl;
}
int main() {
const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20;
int a[A_COUNT] = { 1,2,3,4,5,6,7,8 };
double b[B_COUNT] = { 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8 };
char c[C_COUNT] = "welcome to see you!";
cout << "a array contains:" << endl;
outputArray(a, A_COUNT);
cout << "b array contains:" << endl;
outputArray(b, B_COUNT);
cout << "c array contains:" << endl;
outputArray(c, C_COUNT);
return 0;
}
结果:
函数模板中声明了类形参数T,表示一种抽象类型。当编译器检测到程序中调用函数outputArray时,便用outputArray的第一个实参的类型替换掉整个模板定义中的T,并建立用来输出指定类型数组的一个完整函数,然后在编译新建的函数。
2.1.2 比较大小(compare)
代码:
int compare(const Type& v1, const Type& v2) {
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;
}
int main() {
const char* cp1 = "world", * cp2 = "hi";
int i1 = 1, i2 = 2;
cout << compare(i2, i1) << endl;
cout<<compare(cp1, cp2)<<endl;
return 0;
}
结果:
同上。
2.2 函数模板与函数的区别
- 函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码
- 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数一样只将声明放在头文件中。
- 函数指针也只能指向模板的实例,而不能指向模板本身。
3. 非类型模板形参
- 模板的非类型形参就是内置类型形参,如
template<class T ,int a>class B{};
其中int a就是非类型的模板 - 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板内部是常量
- 非类型模板的形参只能是整型,指针和引用,
double,string,string**
这样的类型是不允许的,但是double &,double *
对象的引用或指针是允许的 - 调用非类型模板形参的实参必须是一个常量表达式,也就是说他必须能在编译时计算出结果
- 任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
- 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
- sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
- 当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
- 非类型形参一般不应用于函数模板中
- 非类型模板形参的形参和实参间允许以下转换。
数组到指针,函数到指针:如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换。
const修饰符的转换:如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
提升转换:如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int 的提升转换
整值转换:如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。
常规转换
4. 模板类
使用类模板使用户可以为类定义的一种模式,使得类中的某些数据成员、某些成员函数的参数,返回值或局部变量能取不同类型。
4.1 定义
template <模板参数表>
class 类名{
类成员声明
}
注:类中类成员的声明的方法与普通类的定义几乎相同,只是在他的各个成员(数据成员和函数成员)中通常要用到模板的类型参数T
在类模板以外定义其成员函数的形式:
template<模板参数表>
类型名 类目<模板参数标识符列表>::函数名(参数表)
使用模板类来建立对象时,应按如下形式声明:
模板名<模板参数表> 对象名1,...,对象名 n;
4.2 示例:queue
代码:
queue.cpp
#include "queue.h"
#include<iostream>
#include<vector>
#include<string.h>
using namespace std;
template<>
void Queue<const char*>::push(const char* const& str) {
char* pVal = new char[strlen(str) + 1];
strcpy(pVal, str);
QueueItem<const char*>* p = new QueueItem<const char*>(pVal);
if (empty()) {
head = tail = p;
}
else {
tail->next = p;
tail = p;
}
}
template<>
void Queue<const char*>::pop() {
if (empty()) {
return ;
}
QueueItem<const char*>* p = head;
head = head->next;
delete[]p->item;
delete p;
}
template<>
int compare(const char* const &a, const char* const &b) {
return strcmp(a, b);
}
//void testTemplate()
int main() {
double a = 1.2;
double b = 1.5;
cout << compare(a, b)<<endl;
Queue<int> qt;
double d = 3.3;
qt.push(1);
qt.push(d);
qt.push(10);
cout << qt<<endl;
short data[5] = { 0,3,5,7 };
Queue<int> qi(data, data + 5);
cout << qi<<" "<<endl;
while (!qi.empty()) {
cout << qi.front() << "";
qi.pop();
}
vector<int> vi(data, data + 5);
qi.assign(vi.begin(), vi.end());
cout << endl;
cout << qi;
Queue<const char*> qst;
char str[10];
strcpy(str, "I am ");
qst.push(str);
strcpy(str, "Jiawen ");
qst.push(str);
strcpy(str, "Zheng");
qst.push(str);
cout << endl << qst<<endl;
char str1[10];
strcpy(str, "abc");
strcpy(str1, "abe");
cout << "1 2 " <<endl<<"比较结果:"<< compare<const char*>(str, str1) << endl;
cout << "2 1 " <<endl<<"比较结果:"<< compare<const char *>(str1, str) << endl;
}
queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
using namespace std;
template<class Type> class Queue;
template<class Type> class QueueItem {
QueueItem(const Type& t) :item(t), next(0) {}
Type item;
QueueItem* next;
friend class Queue<Type>;
friend ostream& operator<<(ostream& os, const Queue<Type>& q);
public:
QueueItem<Type>* operator++() {
return next;
}
Type& operator*() {
return item;
}
};
template<class Type> class Queue {
public:
//队列初始化
Queue() :head(0), tail(0) {}
Queue(const Queue& q) :head(0), tail(0) {
copy_items(q);
}
//It 模板,可以适应所有类型的变量
template<class It>
Queue(It beg, It end) : head(0), tail(0) {
copy_items(beg, end);
}
template<class It> void assign(It beg, It end);
Queue& operator=(const Queue&);
~Queue() {
destroy();
}
Type& front() {
return head->item;
}
const Type& front() const {
return head->item;
}
void push(const Type&);
void pop();
bool empty() const {
return head == 0;
}
friend ostream& operator<<(ostream& os, const Queue<Type>& q) {
os << "<";
QueueItem<Type>* p;
for (p = q.head; p; p = p->next) {
os << p->item << "";
}
os << ">";
return os;
}
const QueueItem<Type>* Head() const {
return head;
}
const QueueItem<Type>* End() const {
return (tail == NULL) ? NULL : tail->naxt;
}
private:
QueueItem<Type>* head;
QueueItem<Type>* tail;
void destroy();
void copy_items(const Queue&);
template<class It> void copy_items(It beg, It end);
};
template <class Type>void Queue<Type>::destroy() {
while (!empty()) {
pop();
}
}
template <class Type> void Queue<Type>::pop() {
QueueItem<Type>* p = head;
head = head->next;
delete p;
}
template <class Type> void Queue<Type>::push(const Type& val) {
QueueItem<Type>* pt = new QueueItem<Type>(val);
if (empty()) {
head = tail = pt;
}
else {
tail->next = pt;
tail = pt;
}
}
template<>
void Queue<const char*>::push(const char* const& val);
template <>
void Queue<const char*>::pop();
template <class Type>
void Queue<Type>::copy_items(const Queue& orig) {
for (QueueItem<Type>* pt = orig.head; pt; pt = pt->next) {
push(pt->item);
}
}
template <class Type>
Queue<Type>& Queue<Type>::operator=(const Queue& q) {
destroy();
copy_items(q);
}
template <class Type> template<class It> void Queue<Type>::assign(It beg, It end) {
destroy();
copy_items(beg, end);
}
template <class Type> template<class It> void Queue<Type>::copy_items(It beg, It end) {
while (beg != end) {
push(*beg);
++beg;
}
}
template <class Type>
int compare(const Type& v1, const Type& v2) {
if (v1 < v2) return -1;
else if (v1 > v2) return 1;
else return 0;
}
template <>
int compare<const char*>(const char* const& v1, const char* const& v2);
#endif // QUEUE_H
结果:
出现的问题:
在编写int compare(const char* const &a, const char* const &b)
函数时一直出现未找到,实例的错误,原因是头函数中的定义是:
template <>
int compare<const char*>(const char* const& v1, const char* const& v2);
而我在cpp文件中写的是:
template<>
int compare(const char* const a, const char* const b) {
return strcmp(a, b);
}
少了一个&,所以还是要细心点
4.3 函数模板特化
申明在头文件中,实现在cpp文件中
- 申明(头文件中)
template <>
int compare<const char*>(const char* const& v1, const char* const& v2);
- 实现(cpp文件中)
template<>
int compare(const char* const a, const char* const b) {
return strcmp(a, b);
}
5. 智能指针AutoPtr
5.1 AutoPtr
auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。auto_ptr不支持new 数组。
\头文件为:#include<memory>
5.2 示例
代码:
Autoptr.h
#ifndef AUTOPTR_H
#define AUTOPTR_H
#include<iostream>
using namespace std;
template<class T>
class AutoPtr {
public:
AutoPtr(T* pData);//构造函数
AutoPtr(const AutoPtr<T>& h);
AutoPtr<T>& operator = (const AutoPtr<T>& h);
~AutoPtr();
friend ostream& operator<<(ostream& os, const AutoPtr<T>& h) {
os << &h << '\n';
return os;
}
//运算符重载
T* operator->() {
return m_pData;
}
//返回的指针不允许改变
const T* operator->() const {
return m_pData;
}
T& operator*() {
return *m_pData;
}
const T& operator*() const {
return *m_pData;
}
private:
void decrUser();//减一操作
T* m_pData;//数据
int* m_nUser; //用户数,用*直接指向user
};
//template<class T>
//ostream& operator<< (ostream& os, const T& m);
//构造函数
template<class T>
AutoPtr<T>::AutoPtr(T* pData) {
m_pData = pData;
m_nUser = new int(1);//记录用户数,该语句表示,new出一个int并且初始化为1
/*()和[]的区别:
[]:数组
():初始化*/
}
//拷贝操作
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& h) {
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++;
}
//生命周期结束,析构函数,用户数减一
template<class T>
AutoPtr<T>::~AutoPtr() {
decrUser();
}
template<class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h) {
if (this == &h) return* this;
decrUser();
m_pData = h.m_pData;
m_nUser = h.m_nUser;
(*m_nUser)++;
return *this;
}
//减少用户数
template<class T>
void AutoPtr<T>::decrUser() {
--(*m_nUser);
if ((*m_nUser) == 0) {//当用户数为0时
delete m_pData;
m_pData = 0;
delete m_nUser;
m_nUser = 0;
}
}
#endif
autoptr.cpp
#include"CMatrix.h"
#include"autoptr.h"
#include<stdio.h>
#include<iostream>
using namespace std;
void TestAutoPtr() {
AutoPtr<CMatrix> m1(new CMatrix);
double data[6] = { 1,2,3,4,5,6 };
m1->Create(2, 3, data);
cout << m1;
AutoPtr<CMatrix> m2(m1);
(*m2).Set(0, 0, 10);
cout << m1 << m2;
}
int main() {
TestAutoPtr();
return 0;
}
结果:
问题:
在进行测试时<<
报错
解决:这里是将<<重载成智能指针类型,然后重载时出现重载函数写错以及字母大小写写错的问题,应为:
friend ostream& operator<<(ostream& os, const AutoPtr<T>& h) {
os << &h << '\n';
return os;
}
总结
模板是C++支持参数化多态性的工具,函数模板实现理类型参数化,将函数处理的数据类型作为参数,提高了代码的可重用性。类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值能取不同类型。
参考
1
2