[STL] 2_类模板

目录

1.类模板实例化

2. 模板类的头文件和源文件分离 

3. 类模板的友元函数

4. 类模板继承

5. 类模板的静态成员

6.用模板实现的字符串管理类


1.类模板实例化

模板类不存在于程序中,程序中存在的是有具体的数据类型的实例化的类,下面是最简单的模板类实例化测试的例子。

#include <iostream>
using namespace std;

template <class T>
class Person {
public:
	Person(T id, T age) {
		m_id = id;
		m_age = age;
	}

	void Show() {
		cout << "id: " << m_id << ", age: " << m_age <<endl;
	}

private:
	T m_id;
	T m_age;
};

int main()
{
	Person<int> p(1001, 25);
	p.Show();

	system("pause");
	return 0;

}

2. 模板类的头文件和源文件分离 

我们在用普通类的时候,一般是将类的声明放在头文件中,将类的实现放在源文件中,那么对于模板类来说,可以这样做吗?例如上面这个例子,第一种是把类模板的定义全部放在头文件中,第二种是把类模板的声明放在头文件中,在源文件中实现该类模板,然后在实例化的时候包含头文件,这两者有什么区别吗?

答案是:第二种会产生链接错误!先看代码。

Animal.h

#pragma once

template <class T>
class Animal
{
public:
	//template <class T>
	//friend ostream & operator << (ostream & os, Animal<T> &p);
	
	template <class T>
	friend void PrintAnimal(Animal<T> &p);

	Animal(T id, T age);

	void Show();

private:
	T m_id;
	T m_age;
};

Animal.cpp

#include "Animal.h"

template <class T>
void PrintAnimal(Animal<T> &p)
{
	cout << "age: " << p.m_age << ", id: " << p.m_id <<endl;
}

template <class T>
Animal<T>::Animal(T id, T age)
{
	m_id = id;
	m_age = age;
}

template <class T>
void Animal<T>::Show()
{
	cout << "age: " << m_age << ", id: " << m_id <<endl;
}

main

#include <iostream>
#include "Animal.h"
using namespace std;

void Test()
{
	Animal<int> Cat(12, 78);
	Cat.Show();
	
}

int main()
{
	Test();

	system("pause");
	return 0;
}

实际上这和模板机制,C++的编译机制有关,独立编译每一个cpp模块生成各个独立的链接目标文件,当遇到函数调用的时候,在当前文件找不到该函数,就生成一个符号,所以在main文件中调用头文件中声明的类模板,但是类模板的实现在源文件中,编译器就区其他模块找类模板的实现,然而类模板的各个成员都是函数模板!它也是需要两次编译的(参考上一章对函数模板编译的解释)!所以编译器找到的是未经过实例化的各个方法的实现,也就没有一个成熟的可供链接的目标文件,就发生了链接错误咯!

解决方案:在实例化的地方包含cpp源文件而不是h文件,或者更合理一点,把cpp文件改成hpp后缀,表示实现的是类模板,在主函数中包含该hpp文件而非h文件,再或者,干脆把所有的类模板的实现都放在h文件中做好了。

所以在使用类模板的时候,一定要明白一点:类模板和普通类有很大的区别,它是需要两步编译才能被编译成一个具体的类对象的,第一步是根据数据类型实例化出来一个普通的模板类,然后再对这个普通模板类编译!

3. 类模板的友元函数

友元函数首先是一个外部函数,所以在类模板中声明一个友元函数,就要把它当成一个外来者对待

template <class T>
class Animal
{
public:
	template <class T>
	friend ostream & operator << (ostream & os, Animal<T> &p);
	
	template <class T>
	friend void PrintAnimal(Animal<T> &p);


	Animal(T id, T age)
	{
		m_id = id;
		m_age = age;
	}

	void Show()
	{
		cout << "age: " << m_age << ", id: " << m_id <<endl;
	}

private:
	T m_id;
	T m_age;
};

在类模板外部实现友元函数

//1.友元方法重载左移运算符
template <class T>
ostream & operator << (ostream & os, Animal<T> &p)
{
	os << "age: " << p.m_age << ", id" << p.m_id << endl;
	return os;
}

//2.友元方法普通函数
template <class T>
void PrintAnimal(Animal<T> &p)
{
	cout << "age: " << p.m_age << ", id" << p.m_id << endl;
}

4. 类模板继承

基类 

template <class T>
class Animal {
public:
	Animal()
	{
		m_age = 0;
	}

	Animal(T id, T age)
	{
		m_id = id;
		m_age = age;
	}
	
	void Print() {
		cout << m_age <<  "岁的动物!";
	}

private:
	T m_age;
	T m_id;
	
};

派生类1:普通类

需要指定类模板基类的数据类型,以实例化出来一个可以正常继承的普通类!

class Cat : public Animal<int> {
	Cat() {
	
	}
};

派生类2:类模板

与上一种不同,这种情况不需要一个实例化的模板类,也就不需要指定数据类型了。

template<class T>
class Dog : public Animal<T> {
public:
    Dog() {}
};

用类模板派生的时候,必须要分清楚派生出来的是普通类还是类模板,说白了派生普通类是需要拐一个弯的:先实例化后派生!

5. 类模板的静态成员

类模板的static成员,在实例化生成的的每一个模板类中,都有属于自己的static成员,这些模板类之间的static成员是不一样的,但是某一个模板类的static成员,就是所有的类对象共享的了。

template <class T>
class Test{
public:
	static int a;
};

//static成员类外初始化
template<class T> int Test<T>::a = 0;

int main()
{
	Test<int> p1, p2, p3;
	Test<char> pp1, pp2, pp3;

	p1.a = 10;
	pp1.a = 11;

	cout << p1.a << " " << p2.a << " " << p3.a <<endl; //模板类1有自己的static成员
 	cout << pp1.a << " "<< pp2.a << " " << pp3.a <<endl; //模板类2有自己的static成员

	system("pause");
	return 0;
}

运行结果

说明每一个实例化出来的模板类都有属于自己的静态成员。

6.用模板实现的字符串管理类

 

template <class T>
class MyArray {
public:
	//默认构造
	MyArray(int capacity) {
		m_capacity = capacity;
		m_size = 0;
		m_addr = new T[m_capacity];
	}

	//拷贝构造
	MyArray(const MyArray<T> & other)
	{
		m_capacity = other.m_capacity; 
		m_size = other.m_size;
		
		m_addr = new T[m_capacity]; //为类申请新的内存空间
		for(int i = 0; i < m_size; i++)
		{
			//拷贝数据
			m_addr[i] = other.m_addr[i];
		}
	}

	//析构函数
	~MyArray()
	{
		if(NULL != m_addr)
		{
			delete [] m_addr;
		}
	}
	
	//下标运算符重载
	T &operator[](int index)
	{
		return m_addr[index];
	}
	
	//复制运算符重载
	MyArray<T> &operator=(const MyArray<T> &other)
	{
		if(NULL != m_addr)
		{
			delete [] m_addr; //释放原有空间
		}
		
		m_capacity = other.m_capacity; 
		m_size = other.m_size;
		
		m_addr = new T[m_capacity]; //为类申请新的内存空间
		for(int i = 0; i < m_size; i++)
		{
			//拷贝数据
			m_addr[i] = other.m_addr[i];
		}

		return *this;
	}
	
	//尾部添加元素
	void PushBack(T &element)
	{
		if(m_size >= m_capacity)
		{
			return;
		}
	
		//调用拷贝构造函数
		//1. 如果T是某个类,则该类对象必须能够被拷贝
		//2. 容器都是值寓意,而非引用寓意,向容器中放元素,都是放入元素的拷贝,而非元素本身
		//3. 如果元素的成员有指针,注意深拷贝与浅拷贝的关系
		m_addr[m_size] = element;
		m_size++;
	}

	//这个重载的功能是对右值取引用
	void PushBack(T &&element)
	{
		if(m_size >= m_capacity)
		{
			return;
		}
		
		m_addr[m_size] = element;
		m_size++;
	}

	int GetSize()
	{
		return m_size;
	}

private:
	int m_capacity;	//容量
	int m_size;		//元素个数
	T *m_addr;		//地址
};

测试函数

void Test()
{
	MyArray<int> marray(20);
	int a = 10, b = 20, c = 30, d = 40;
	marray.PushBack(a);	
	marray.PushBack(b);	
	marray.PushBack(c);	
	marray.PushBack(d);

	marray.PushBack(100); //错误,右值不能取引用
	marray.PushBack(200); //C++11标准,可以对右值取引用 见void PushBack(T &&element)
	marray.PushBack(300);

	for(int i = 0; i < marray.GetSize(); i++)
	{
		cout << marray[i] << " ";
	}
	cout << endl;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值