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 函数模板与函数的区别

  1. 函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码
  2. 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数一样只将声明放在头文件中。
  3. 函数指针也只能指向模板的实例,而不能指向模板本身。

3. 非类型模板形参

  1. 模板的非类型形参就是内置类型形参,如template<class T ,int a>class B{};其中int a就是非类型的模板
  2. 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板内部是常量
  3. 非类型模板的形参只能是整型,指针和引用,double,string,string**这样的类型是不允许的,但是double &,double *对象的引用或指针是允许的
  4. 调用非类型模板形参的实参必须是一个常量表达式,也就是说他必须能在编译时计算出结果
  5. 任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
  6. 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
  7. sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
  8. 当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
  9. 非类型形参一般不应用于函数模板中
  10. 非类型模板形参的形参和实参间允许以下转换。

数组到指针,函数到指针:如: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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值