C++语言—模板

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

函数模板

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

//函数模板定义格式
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);
}

编译器的基层处理: 

C++语言—模板

很清楚地看到两次调用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为什么不行

C++语言—模板

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

C++语言—模板

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

C++语言—模板

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

C++语言—模板

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

C++语言—模板

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