1、引入类模板
C++中可以将模板的思想应用于类,使得类可以不关注具体操作的数据类型,而只关注类所需要实现的功能。
C++中的类模板
- 提供一种特殊的类以相同的行为处理不同的类型
- 在类声明前使用template进行标识
- < typename T> 用于说明类中使用的泛指类型 T
- 在模板类外部定义成员函数的实现时,需要加上template < typename T>的声明,并且类作用域为:classname< T >::
示例:
#include <iostream>
using namespace std;
template <typename T>
class Test
{
public:
T add(T a, T b);
T minus(T a, T b);
};
template < typename T>
T Test<T>::add(T a, T b)
{
return a + b;
}
template < typename T>
T Test<T>::minus(T a, T b)
{
return a - b;
}
int main()
{
Test<int> ti;
cout << ti.add(4,5) << endl;
cout << ti.minus(4,5) << endl;
Test<double> td;
cout << td.add(3.14,5.0) << endl;
cout << td.minus(3.14,5.0) << endl;
return 0;
}
Tip:
- 声明的泛指类型 T 可用于声明成员变量和成员函数
- 编译器对类模板的处理方式和函数模板相同
- 编译器从类模板通过具体类型产生不同的类
- 编译器在声明的地方对类模板代码本身进行编译,这个编译只是进行语法等检查,并不生成实际的汇编代码,因为类所处理的具体类型都还未知
- 编译器在使用的地方对参数替换后的代码进行编译,该编译会生成具体的汇编代码
2、类模板的应用
如上面的示例,主函数如下:
int main()
{
Test<int> ti; //必须使用具体类型定义对象
cout << ti.add(4,5) << endl;
cout << ti.minus(4,5) << endl;
Test<double> td; //必须使用具体类型定义对象
cout << td.add(3.14,5.0) << endl;
cout << td.minus(3.14,5.0) << endl;
return 0;
}
Tip:
对于模板类来说,定义对象时必须指定明确的类型,它不能进行类型推导!
3、类模板的工程应用
一般工程中,我们常把源文件分为.h头文件和.cpp实现文件,但在模板类中,这一方法却边得不可行!
示例:
将上述程序分离,拆分成main.cpp test.h test.cpp文件:
test.h
#ifndef _TEST_H_
#define _TEST_H_
template <typename T>
class Test
{
public:
T add(T a, T b);
T minus(T a, T b);
};
#endif
test.cpp
#include "test.h"
template < typename T>
T Test<T>::add(T a, T b)
{
return a + b;
}
template < typename T>
T Test<T>::minus(T a, T b)
{
return a - b;
}
main.cpp
#include <iostream>
#include "test.h"
using namespace std;
int main()
{
Test<int> ti; //必须使用具体类型定义对象
cout << ti.add(4,5) << endl;
cout << ti.minus(4,5) << endl;
Test<double> td; //必须使用具体类型定义对象
cout << td.add(3.14,5.0) << endl;
cout << td.minus(3.14,5.0) << endl;
return 0;
}
使用如下命令编译:
g++ -Wall -g main.cpp test.cpp -o main
编译器提示错误:
Tip:
出现这种错误的原因是,由于类模板的编译机制不同,所以不能像普通类一样分开实现后,在使用时只包含头文件!
现在,做一些改动,把main.cpp文件中包含的test.h头文件直接换成test.cpp,然后再次编译;会发现编译通过了!但是这样做又确实不好,该如何是好?
- 一种方法:直接把声明和定义都放在头文件中;这种做法可行,但不利于声明和定义分离!
- 另一种方法:把声明放在头文件中,把定义放在.hpp文件中,使用时直接包含.hpp文件,换一个方式实现声明和定义分离!
示例:
test.h 不改变,main.cpp 中直接包含test.hpp即可!
test.hpp
#ifndef _TEST_DEF_H_ //防止重复包含
#define _TEST_DEF_H_
#include "test.h"
template < typename T>
T Test<T>::add(T a, T b)
{
return a + b;
}
template < typename T>
T Test<T>::minus(T a, T b)
{
return a - b;
}
#endif
Tip:
只有被调用的类模板成员函数才被编译器生成可执行代码!!!
4、类模板的特化
类模板可以被特化,用template < > 声明一个类时,表示这是一个特化类!
示例:
特化类模板的意义
当类模板在处理某种特定类型有缺陷时,可以通过类模板的特化来克服处理这种特定类型带来的不足!
注意:
编译器优先选择特化类生成对象!!
示例:
#include <iostream>
using namespace std;
template <typename T>
class Test
{
public:
T test(T t)
{
cout << "T test(T t)" << endl;
return t * t;
}
};
//该类相当于我们手动生成的上面模板类的一个特例
template <>
class Test<int>
{
public:
int test(int t)
{
cout << "int test(int t)" << endl;
return t * t;
}
};
int main()
{
Test<int> ti; //必须使用具体类型定义对象,优先匹配特化类
cout << ti.test(4) << endl;
Test<double> td; //必须使用具体类型定义对象
cout << td.test(3.14) << endl;
return 0;
}
另外,上面的class Test< int >类其实也是一个模板类,只不过我们让它成为了一个特例,其实我们可以通过继承,让它变成普通类!
#include <iostream>
using namespace std;
template <typename T>
class Test
{
public:
T test(T t)
{
cout << "T test(T t)" << endl;
return t * t;
}
};
//该类相当于我们手动生成的上面模板类的一个特例
template <>
class Test<int>
{
public:
int test(int t)
{
cout << "int test(int t)" << endl;
return t * t;
}
};
//继承自特化类
class myTest : public Test<int>
{
};
int main()
{
Test<int> ti; //必须使用具体类型定义对象,优先匹配特化类
cout << ti.test(4) << endl;
myTest mt; //直接实例化,并调用成员函数
cout << mt.test(4) << endl;
return 0;
}