C++语言—模板

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/z_x_m_m_q/article/details/82991365

泛型编程:编写与类型无关的代码,模板是泛型编程的基础,模板分为模板函数和模板类。

函数模板

函数模板与类型无关,在使用时编译器根据传入的实参帮我们推演出特定类型的函数版本(产生模板特定类型的过程称为模板的实例化)。

//函数模板定义格式
template<typename T>
//template<class T>
//typename是用来定义模板参数的关键字,也可以用class代替(这里不能用struct)
void swap(T& x,T& y)
{
	T temp = x;
	x = y;
	y = temp;
}

void test()
{
	int a, b;
	a = 10;
	b = 20;
	double c, d;
	c = 1.1;
	d = 2.2;
//函数模板的实例化
	//隐式实例化:
	swap(a,b);
	swap(c,d);

	//swap(a,d);  //此处编译器无法确定到底该将T确定为int还是double类型而报错
	//处理方式:显示实例化
	swap<double>(a,d);
}

编译器的基层处理: 

很清楚地看到两次调用call的是不同的函数,这两个不同的函数真是编译器根据我们传入的实参帮我们推演出的两个不同的函数模板类型。

类模板

这里实现一个完整的Seqlist(顺序表)模板,能存储int、double类型的元素,还能存储string类对象:

//类名:Seqlist
//类型:Seqlist<T>

template<class T>
class Seqlist
{
public:

	Seqlist()  //构造函数
		:_array(NULL)
		,_size(0)
		,_capacity(0)
	{}

	~Seqlist()  //析构函数
	{
		if (_array)
		{
			delete[] _array;
			_size = _capacity = 0;
		}
	}

	//Seqlist s2 = s1;
	//Seqlist(const Seqlist& s) //把参数类型换成类名也是可以的,不建议用
	Seqlist(const Seqlist<T>& s)  //拷贝构造函数
	{
		if (s._size)
		{
			_array = new T[s._size];
			 _size = _capacity = s._size;

			 //memcpy(_array, s._array, sizeof(T)*s._size);  //T == string就不行
 			 for (size_t i = 0; i < _size; ++i)
			 {
				 _array[i] = s._array[i];
			 }
		}
		else
		{
			_array = NULL;
			_size = _capacity = 0;
		}
	}

	//s2 = s1;
	//Seqlist<T>& operator=(const Seqlist& s)  //把参数类型换成类名也是可以的,不建议用
	Seqlist<T>& operator=(const Seqlist<T>& s)  //赋值运算符重载函数
	{
		if (&s != this)
		{
			if (s._size)
			{
				delete[] _array;
				_array = new T[s._size];
				_size = _capacity = s._size;

				//memcpy(_array, s._array, sizeof(T)*s._size);  //T == string就不行
				for (size_t i = 0; i < _size; ++i)
				{
					_array[i] = s._array[i];
				}
			}
			else
			{
				_size = 0;
			}
		}
		return *this;
	}

	void PushBack(const T& x)
	{
		Insert(_size,x);
	}

	void PopBack()
	{
		Erase(_size-1);
	}

	void Insert(size_t pos, const T& x)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 3 : 2 * _capacity;
			T* newarray = new T[newcapacity];

			//memcpy(newarray,_array,sizeof(T)*_size);  //T == string就不行
			for (size_t i = 0; i < _size; ++i)
			{
				newarray[i] = _array[i];
			}
			delete[] _array;

			_array = newarray;
			_capacity = newcapacity;
		}

		size_t end = _size;
		while (pos < end) {
			_array[end] = _array[end-1];
			--end;
		}

		_array[pos] = x;
		++_size;
	}
	void Erase(size_t pos)
	{
		assert(pos<_size);

		size_t cur = pos;
		while (cur < _size-1){
			_array[cur] = _array[cur+1];
			++cur;
		}

		--_size;
	}
	T& operator[](size_t pos)
	{
		assert(pos < _size);

		return _array[pos];
	}

	const T& operator[](size_t pos) const
	{
		return _array[pos];
	}

	size_t Size()
	{
		return _size;
	}
protected:
	T* _array;
	size_t _size;
	size_t _capacity;
};

void test()
{
	Seqlist<int> s1;
	
	s1.PushBack(1);
	s1.PushBack(2); 
	s1.PushBack(3); 
	s1.PushBack(4);	
	
	for (size_t i = 0;i<s1.Size();++i)
	{
		cout << s1[i] << "  ";
	}
	cout << endl;

	Seqlist<string> s2;
	s2.PushBack("111");
	s2.PushBack("222");
	s2.PushBack("333");
	s2.PushBack("444");
}

这里解释一下用memcpy为什么不行

面对int、double等基本类型,将数据元素从一块内存块拷贝到另一块内存块没有问题,拷贝后释放旧内存块也是没有问题的

扫描二维码关注公众号,回复: 4367865 查看本文章

 当类型变为string时,首先对string对象的存储模型如下

 这是你用memcpy拷贝(浅拷贝)后的结果如下

当释放掉旧内存块时,新内存块中所有指针都变成了野指针, 最终string对象调用析构函数时程序就会崩掉。这里的拷贝应该是深拷贝(按循环中那样赋值时,string类会帮我们完成以下工作)

等拷贝完成,释放旧内存块对拷贝的新对象没有影响,最终的析构也能顺利进行。 

这里实现一个完整的 List(双向带头循环链表)模板:

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;

	ListNode(const T& x)
		:_data(x)
		,_next(NULL)
		,_prev(NULL)
	{}
};

template<class T>
class List
{
	typedef ListNode<T> Node;

public:
	List()  //构造函数
	{
		this->_head = new Node(T());  //T():是一个T类型的无名对象

		_head->_next = _head;
		_head->_prev = _head;
	}

	~List()  //析构函数
	{
		if (_head)
		{
			Clear();

			delete _head;  //记得处理头结点嗷
			_head = NULL;
		}
	}

	void Clear()  //清理除头节点以外的所有结点
	{
		Node* cur = _head->_next;
		Node* next = NULL;
		while (cur != _head) {
			next = cur->_next;

			delete cur;
			cur = next;
		}

		_head->_next = _head->_prev = NULL;  //注意:如果没有这一步处理,头结点的两个指针都是野指针
	}
	List(const List& l)  //拷贝构造函数
	{
		if (l.size())  //这里不判断也可以,我只是想从另一个角度带大家思考
		{
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;

			Node* cur = l._head->_next;
			while (cur != l._head) {
				PushBack(cur->_data);  //"思考内联"

				cur = cur->_next;
			}
		}
		else  //说明只有头结点
		{
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}
	}

	List& operator=(List l)  //赋值运算符重载函数
	{
		swap(_head,l._head);  //现代写法,交换完毕会调用一次析构函数,认真思考

		return *this;
	}

	void PushBack(const T& x)  //链表尾插
	{
		Insert(_head,x);  //"思考内联"
	}

	void PopBack()  //链表尾删
	{
		Erase(_head->_prev);  //"思考内联"
	}

	void PushFront(const T& x)  //链表头插
	{
		Insert(_head->_next,x);  //"思考内联"
	}

	void PopFront()  //链表头删
	{
		Erase(_head->_next);  //"思考内联"
	}

	void Insert(Node* pos, const T& x)  //pos位置之前插入新结点,注意思考没有头结点的情况
	{
		assert(pos);

		Node* newnode = new Node(T());  //它会调用构造函数
		newnode->_data = x;
		Node* prev = pos->_prev;

		newnode->_prev = prev;
		prev->_next = newnode;

		newnode->_next = pos;
		pos->_prev = newnode;
	}

	void Erase(Node* pos)  //删除pos位置的结点
	{
		assert(pos);

		if (_head->_next == _head)   //说明只有头结点
			return;

		Node* prev = pos->_prev;
		Node* next = pos->_next;

		delete pos;
		pos = NULL;

		prev->_next = next;
		next->_prev = prev;
	}

	const size_t size() const  //结点个数(不包括头结点)
	{
		Node* cur = _head->_next;
		size_t count = 0;

		while (cur != _head) {
			++count;

			cur = cur->_next;
		}

		return count;
	}

	//void Print()  //打印链表,只是为了调试时方便
	//{
	//	Node* cur = _head->_next;

	//	while (cur != _head) {
	//		cout << cur->_data << "  ";

	//		cur = cur->_next;
	//	}

	//	cout << endl;
	//}
private:
	Node* _head;
};

容器适配器:

实现栈——容器适配器
//template<class T,class Container>
template<class T,class Container = Seqlist<int>>  //缺省参数
class Stack
{
public:
	void push(const T& x)  //进栈
	{
		_con.PushBack(x);
	}

	void pop()  //出栈
	{
		_con.PopBack();
	}

	const T& Top()  //取栈顶元素
	{
		return _con.Top();
	}

	bool Empty()  //判断空
	{
		return _con.Empty();
	}

	//void Print()  //栈中没有该操作,此处只是为了直观看而设计的
	//{
	//	_con.Print();
	//}
private:
	Container _con;
};

void test()
{
	Stack<int> s1; 
	//Stack<int,Seqlist<int>> s1;
        //Stack<char,Seqlist<char> s2>;

	s1.push(1);
	s1.push(2); 
	s1.push(3);
	s1.push(4);
	//s1.Print();

	s1.pop();
	//s1.Print();
}

非类型的类模板参数:

//template<typename T,size_t MAX_SIZE>
template<typename T, size_t MAX_SIZE = 10>  //带缺省模板参数
class Seqlist
{
public:
private:
	T _array[MAX_SIZE];
	size_t _size;
};

void test()
{
	Seqlist<int, 20> s1;
	//Seqlist<int> s1;
}

非类型的模板函数参数:

//template<typename T,int count>
template<typename T, int count = 10>  //带缺省...
T Add(const T& x)
{
	return x + count;
}

void test()
{
	printf("%d\n",Add<int>(1));
	printf("%d\n",Add<int,199>(1));
}

浮点数和类对象是不能作为非类型模板参数的:

//template<typename T,string s1>
//template<typename T,double d1>

猜你喜欢

转载自blog.csdn.net/z_x_m_m_q/article/details/82991365
今日推荐