引言
上一篇文章,我们聊了C++的函数模板,今天我们来聊聊类模板。
一、类模板
1.1 作用
建立一个通用类,类中的成员和数据类型可以不具体指定,使用一个虚拟的类型来代替。
语法:
template<typename T> 类声明或者定义
说明:
template —— 表明要创建类模板。
typename —— 表示一种数据类型,可以用 class 代替, T 就是具体的名称,通常为大写字母。
1.2 代码示例
#include <iostream> #include <string> using namespace std; // 类模板 template<class nameType, class ageType> class Person{ public: Person(nameType name, ageType age){ this->m_name = name; this->m_age = age; } nameType m_name; ageType m_age; }; int main(int argc, char* argv[]) { Person<string, int> p1("panda", 20); std::cout << "p1.name: " << p1.m_name << " p1.age: "<< p1.m_age<< std::endl; return 0; }
1.2 类模板与函数模板的区别
- 类模板没有自动类型的推到使用方法
- 类模板可以再模板参数列表中有默认参数类型
// 类模板 template<class nameType, class ageType = int> // ageType类型默认为int class Person{ public: Person(nameType name, ageType age){ this->m_name = name; this->m_age = age; } nameType m_name; ageType m_age; }; int main(int argc, char* argv[]) { Person<string, int> p1("panda", 20); std::cout << "p1.name: " << p1.m_name << " p1.age: "<< p1.m_age<< std::endl; // Person p("panda", 20); // 错误,不能自己推导数据类型 Person<string> p2("lwang", 20); std::cout << "p2.name: " << p2.m_name << " p2.age: "<< p2.m_age<< std::endl; return 0; }
运行结果:
1.3 类模板中成员函数创建时机
- 普通类函数成员一开始就可以创建出来
- 类模板中的函数成员只有在调用的时候才创建
示例:
class Student{ public: void showStudentInfo() { std::cout << "Student show." << std::endl; } }; class Teacher{ public: void showTeacherInfo() { std::cout << "Teacher show." << std::endl; } }; template<class T> class Person{ public: T obj; // 成员函数 void func1() { obj.showStudentInfo(); } void func2() { obj.showTeacherInfo(); } }; int main(int argc, char* argv[]) { Person<Student> pStu; pStu.func1(); // pStu.func2(); // 在确认Person的T的类型为Student后,编译器发现obj没有func2,编译报错 Person<Teacher> pTe; // pTe.func1(); // 在确认Person的T的类型为Teacher后,编译器发现obj没有func1,编译报错 pTe.func2(); return 0; }
运行结果:
1.4 类模板对象作为函数的参数如何传递
类模板实例化出的对象,作为函数的参数传递的方式有三种:
- 显示指定传入的类型
- 参数模板化
- 整个类模板化
template<class nameType, class ageType = int> // ageType类型默认为int class Person{ public: Person(nameType name, ageType age){ this->m_name = name; this->m_age = age; } nameType m_name; ageType m_age; }; void showPerson1(Person<string> &p) { std::cout << "p.name: " << p.m_name << " p.age: "<< p.m_age<< std::endl; } template<class T1, class T2> void showPerson2(Person<T1, T2>& p) { std::cout << "p.name: " << p.m_name << " p.age: "<< p.m_age<< std::endl; // std::cout << "T1 type is: " << typeid(T1).name() << std::endl; // std::cout << "T2 type is: " << typeid(T2).name() << std::endl; } template<class T> void showPerson3(T& p) { std::cout << "p.name: " << p.m_name << " p.age: "<< p.m_age<< std::endl; // std::cout << "T type is: " << typeid(T).name() << std::endl; } int main(int argc, char* argv[]) { // 1. 显式指定参数类型 Person<string> p("panda", 20); showPerson1(p); Person<string, int> p1("lwang", 35); // 2. 参数模板化 showPerson2(p1); // 3. 整个类模板化 Person<string, int> p2("lyz", 18); showPerson3(p2); return 0; }
运行结果:
1.4 类模板与继承
// 类模板父类 template<class T> class Base{ public: T m_a; }; // 1. 子类必须知道父类T的类型 // class Derive : public Base{}; // 错误 class Derive : public Base<char>{ public: Derive(){ std::cout << "T type is: " << typeid(m_a).name() << std::endl; } }; // 2.如果想灵活指定父类中的参数类型,子类也必须为类模板 template<class T1, class T2> class Derice_II: public Base<T2>{ public: Derice_II(){ std::cout << "T1 type is: " << typeid(T1).name() << std::endl; std::cout << "T2 type is: " << typeid(T2).name() << std::endl; } T1 m_b; }; int main(int argc, char* argv[]) { Derive d; Derice_II<int, char> d_II; // T1-->int T2-->char return 0; }
运行结果:
其中 c 表示 char 类型, i 表示 int 类型。
小结:如果父类是类模板,子类需要指定父类中T的类型。
1.5 类模板成员函数的类外实现
// 类模板 template<class nameType, class ageType> class Person{ public: Person(nameType name, ageType age); // 类内声明 // Person(nameType name, ageType age) // 类内实现 // { // this->m_name = name; // this->m_age = age; // } void show(); // 类内声明 // void show() // 类内实现 // { // std::cout << "name: " << m_name << " age: "<< m_age << std::endl; // } nameType m_name; ageType m_age; }; // 构造函数的类外实现 template<class nameType, class ageType> // Person::Person(string name, int age) // 普通类的构造函数实现 Person<nameType, ageType>::Person(nameType name, ageType age) { this->m_name = name; this->m_age = age; } // 普通成员函数的类外实现 template<class nameType, class ageType> // Person::show() // 普通类的构造函数实现 void Person<nameType, ageType>::show(){ std::cout << "name: " << m_name << " age: "<< m_age << std::endl; } int main(int argc, char* argv[]) { Person<string, int> p("panda", 20); p.show(); return 0; }
1.6 类模板分文件编写存在的问题及解决
由于类模板中的成员函数是在调用阶段才创建,导致分文件编写时,链接不到。
解决方案:
- 直接包含.cpp源文件
- 将声明和实现写在一个文件里,命名为.hpp
1.6.1 分文件编写
Person.h 头文件
#program once #include // 类模板 template<class nameType, class ageType> class Person{ public: Person(nameType name, ageType age); void show(); nameType m_name; ageType m_age; };
Person.cpp 源文件
#include "Person.h" // 构造函数的类外实现 template<class nameType, class ageType> Person<nameType, ageType>::Person(nameType name, ageType age) { this->m_name = name; this->m_age = age; } // 普通成员函数的类外实现 template<class nameType, class ageType> void Person<nameType, ageType>::show(){ std::cout << "name: " << m_name << " age: "<< m_age << std::endl; }
1.6.2 合并为一个.hpp文件
Person.hpp 源文件
#include <iostream> using namespace std; template<class nameType, class ageType> class Person{ public: Person(nameType name, ageType age); void show(); nameType m_name; ageType m_age; }; // 构造函数的类外实现 template<class nameType, class ageType> Person<nameType, ageType>::Person(nameType name, ageType age) { this->m_name = name; this->m_age = age; } // 普通成员函数的类外实现 template<class nameType, class ageType> void Person<nameType, ageType>::show(){ std::cout << "name: " << m_name << " age: "<< m_age << std::endl; }
测试代码:
#include <iostream> #include <string> #include "Person.hpp" int main(int argc, char* argv[]) { Person<string, int> p("panda", 20); p.show(); return 0; }
1.7 类模板与友元函数
#include <iostream> #include <string> // 提前声明类模板Person template<class nameType, class ageType> class Person; // 这是OuterPrintPerson函数模板的实现,写在前面让编译器先知道有该函数模板的存在 // 又因为该函数模板用到了类模板Person,还要提前声明类模板Person template<class nameType, class ageType> void OuterPrintPerson(Person<nameType, ageType>& p){ std::cout << "OuterPrintPerson -- name: " << p.m_name << " age: "<< p.m_age << std::endl; } // 类模板 template<class nameType, class ageType> class Person{ public: Person(nameType name, ageType age){ this->m_name = name; this->m_age = age; } // 全局函数类内实现 friend void InternalPrintPerson(Person<nameType, ageType>& p){ std::cout << "InternalPrintPerson -- name: " << p.m_name << " age: "<< p.m_age << std::endl; } // 全局函数类外实现 // OuterPrintPerson<>添加<>空模板参数列表,表示这是OuterPrintPerson函数模板的声明 // 如果是类外实现,需要让编译器提前知道该函数模板的存在 friend void OuterPrintPerson<>(Person<nameType, ageType>& p); // 函数声明 private: nameType m_name; ageType m_age; }; int main(int argc, char* argv[]) { Person<string, int> p("panda", 20); InternalPrintPerson(p); OuterPrintPerson(p); return 0; }
运行结果:
小结:
- 全局函数类内实现,直接在类内声明友元即可
- 劝酒函数类外实现,需要让编译器知道全局函数的存在