目录
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;
}