本文最后更新于:2021年2月5日 晚上
概览:C++函数模板、类模板,类模板与继承、类模板成员函数实现。
模板就是建立通用的模具,来提高代码的复用性。
模板不能够直接使用,它只是框架。此外模板并非万能。
函数模板基本语法
语法:template<typename T>
或者template<class T>
.然后接一个函数。
typename
或者class
均可,看个人的使用习惯。
T
这个也可以随意替换,代表通用模板类型。
- 函数模板可以由多个类型,使用都逗号分割即可。
template<class T,class P,class M>
。
- 函数模板这句话的声明只对其后紧挨着的第一个函数有效。
函数模板的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| void swap(int& a, int& b) { int temp = a; a = b; b = temp; }
template<typename T> void myswap(T& a, T& b) { T temp = a; a = b; b = temp; }
void test1() {
int a = 10; int b = 20;
swap(a, b); cout << "a = " << a << " , b = " << b << endl;
int c = 10; int d = 20;
myswap(c,d); cout << "c = " << c << " , d = " << d << endl;
char e = 'A'; char f = 'B';
myswap<char>(e,f); cout << "e = " << e << " , f = " << f << endl;
}
|
显然,使用函数模板的优点是:将参数类型化,从而提高代码的复用性。
使用函数模板的两种方式:
- 自动类型推导,
myswap(c,d);
不指定类型,让编译器自行推导。
- 显式指定类型,
myswap<char>(e,f);
,显式的在模板参数列表中指定类型。
- 推荐使用显式指定类型的方式。
函数模板使用规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| template<typename T> void myswap(T& a, T& b) { T temp = a; a = b; b = temp; }
template<typename T> void func() { cout << "func" << endl; }
template<typename T> void func(T a) { cout << a << " func" << endl; }
void test2() {
int a = 10; char b = 'A';
func<int>();
func<int>(a);
}
|
- 对于模板参数类型T,必须推导出一致的数据类型才能够使用。
- 模板必须确定T的数据类型,才可以使用。
- 函数模板可以重载。
普通函数与函数模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int add(int a, int b) { return a + b; }
template<typename T> T t_add(T a, T b) { return a + b; }
void test3() { int a = 1; int b = 2; char c = 'A';
cout<<add(a, b)<<endl; cout<<add(a, c)<<endl;
cout << t_add<int>(a, c)<<endl; }
|
区别
- 普通函数调用时可以发生自动类型转换,即隐式类型转换。
- 函数模板调用时,若是自动类型推导,就不会发生隐式类型转换。
- 函数模板使用显式类型调用时,可以发生类型转换。
普通函数与模板函数调用规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void myfunc(int a) { cout << "普通函数" << endl; }
template<typename T> void myfunc(T a) { cout << "模板函数" << endl; }
void test4() {
int a = 10;
myfunc(a);
myfunc<>(a);
char b = 'B';
myfunc(b);
}
|
- 当普通函数和模板函数均可调用调用时,优先调用普通函数。
- 可以通过空模板参数列表来强制调用函数模板。
- 当函数模板可能产生更好的匹配时,优先调用函数模板。
一般的建议是:提供了模板函数就不要再提供普通函数了,容易出现二义性。
函数模板的局限性
模板函数并非万能。例如
1 2 3 4 5
| template<class T> void f(T a, T b) { if(a > b) { ... } }
|
当T为自定的类时,通常会无法正常运行。
自定义数据类型的解决
方式一:类重载比较运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Person { public: int id; bool operator>(const Person& p) { if (this->id > p.id) return true; else return false; } };
template<typename T> void cmp(T &a, T& b) { if (a > b) cout << ">" << endl; else cout << "!>" << endl; }
void test5() { Person p1 = { 10 }; Person p2 = { 12 };
cmp(p1, p2); }
|
缺点就是可能要对每一个比较运算符都需要进行重载。
方式二:具体化模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Person { public: int id; };
template<typename T> void cmp(T &a, T& b) { if (a > b) cout << ">" << endl; else cout << "!>" << endl; }
template<> void cmp(Person &a, Person &b) { if (a.id > b.id) cout << ">" << endl; else cout << "!>" << endl; }
void test5() { Person p1 = { 10 }; Person p2 = { 12 };
cmp(p1, p2); }
|
具体化模板写法:template<> 函数返回值类型 函数名(具体类型 参数)
类模板
语法:template<typename T>
或者template<class T>
.然后接一个类。
具体规则和函数模板类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template<class T1,class T2> class Person { public: Person(T1 name,T2 age):m_name(name),m_age(age) {}
void showInfo() { cout << "name:" << m_name << ", age:" << m_age << endl;
cout << "name type:" << typeid(m_name).name() << endl; cout << "age type:" << typeid(m_age).name() << endl; }
T1 m_name; T2 m_age; };
void test1() { Person<string, int> p1 = Person<string, int>("Cc",18); p1.showInfo(); }
|
输出结果:
1 2 3
| name:Cc, age:18 name type:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > age type:int
|
类模板与函数模板
区别
- 类模板必须使用显式指定类型,没有自动类型的推导方式。
- 函数模板如上面所示,可以有隐式类型推导。
而可以通过在模板参数列表中设置默认参数来减少指定的类型.
1 2 3 4 5 6
| template<class T1,class T2 = int> class Person {……};
Person<string> p1 = Person<string>("Cc",18);
|
函数模板可在类模板中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template<class T> class Animal { public: Animal(T id) :m_id(id) {}
template<typename T2> void func(T2 t2) { cout << t2 << " " << m_id << endl; }
private: T m_id; };
void test2() {
Animal<int> animal(249);
animal.func<string>("Id为");
}
|
类模板中成员函数的创建时机
- 普通类中的成员函数在一开始的时候就可以创建。
- 而类模板中的成员函数在调用时才会生成,所以有些代码编辑器检查不出问题,只有编译生成时才会检查出问题。
不同类型—类模板的不兼容
1 2 3 4
| Person<string, int> *p1; Person<string, double> p2("Bb", 12);
p1 = &p2;
|
类模板对象做函数参数
当类模板实例化出来的对象作为函数参数时,传参的方式有:
- 传入指定的类型,即直接显式的设置为对象的数据类型。使用较为广泛。
- 将参数模板化,将对象中的参数变为模板进行传递。
- 将整个类模板化,直接将这个对象类型模板化传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| template<class T1,class T2> class Person { public: Person(T1 name,T2 age):m_name(name),m_age(age) {}
void showInfo() { cout << "name:" << m_name << ", age:" << m_age << endl;
cout << "name type:" << typeid(m_name).name() << endl; cout << "age type:" << typeid(m_age).name() << endl; }
T1 m_name; T2 m_age; };
void myfunc1(Person<string,int> &p) { p.showInfo(); }
template<typename T1,typename T2> void myfunc2(Person<T1,T2> &p) { p.showInfo(); }
template<typename T> void myfunc3(T &t) { t.showInfo(); }
void test3() { Person<string, int> p("Cc", 18);
myfunc1(p); myfunc2<string,int>(p); myfunc3<Person<string, int>>(p);
}
|
类模板与继承
如果基类是类模板,派生类继承时要指明基类T的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| template<class T> class Base1 { T m; };
class Son1 :public Base1<int> {};
class Base2 { int m; };
template<class T> class Son2:public Base2{ T v; };
template<class T> class Base3 { T m; };
template<class T> class Son3 :public Base3<int> { T n; };
template<class T> class Base4 { T m; };
template<class BT,class ST> class Son4 :public Base4<BT> { ST n; };
void test4() {
Son1 son1;
Son2<int> son2;
Son3<char> son3;
Son4<int,char> son4; }
|
类模板成员函数类外实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| template<class NameType,class IdType> class Person { public: Person(NameType name, IdType id);
void showInfo();
private: NameType m_name; IdType m_id; };
template<class NameType, class IdType> Person<NameType, IdType>::Person(NameType name, IdType id) { m_name = name; m_id = id; }
template<class NameType, class IdType> void Person<NameType, IdType>::showInfo() { cout << "name: " << m_name << " , id: " << m_id << endl; }
|
- 类外实现的时候,要把模板的声明加上,而且作用域限定符前的类要加上类名以及模板参数列表。
产生问题:类模板分文件编写
上面的代码,进行分文件编写时:
类的定义,Person.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #pragma once
template<class NameType,class IdType> class Person { public: Person(NameType name, IdType id);
void showInfo();
private: NameType m_name; IdType m_id; };
|
类的实现,Person.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "Person.h"
#include<iostream> #include <string> using namespace std;
template<class NameType, class IdType> Person<NameType, IdType>::Person(NameType name, IdType id) { m_name = name; m_id = id; }
template<class NameType, class IdType> void Person<NameType, IdType>::showInfo() { cout << "name: " << m_name << " , id: " << m_id << endl; }
|
main函数调用:
1 2 3 4 5 6 7 8 9 10 11 12
| #include <iostream> using namespace std;
#include "Person.h"
int main() {
Person<string, int> p("VS",2017); p.showInfo();
return 0; }
|
执行时,编译器会报错误,链接时错误:无法解析的外部符号……。
产生错误原因:类模板中的成员函数的创建时机是在调用阶段,分文件编写时,链接不到这些成员函数。
解决方式:
- 在
main函数
那里包含.cpp
文件。
- 将这个模板类的声明与实现写在同一个文件里,并将文件后缀改为
.hpp
。.hpp
是约定成俗的习惯,也是主流的解决方式。
一般来说含有模板类的文件都不应该进行分文件编程,它们的声明与实现都应该放在同一个文件之中,并使用.hpp
这种文件格式进行存储。
STL中有许多都是采用的这种方式。
类模板与友元
友元函数,全局类内实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template<class T> class Person {
friend void showInfo(Person<T> &p) { cout << p.m_name << endl; }
public: Person(T name) :m_name(name) {}
private: T m_name; };
void test1() { Person<string> p("Bob");
showInfo(p); }
|
如上,在类内的全局函数加上friend
即可。
友元函数,全局类外实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| template<class T> class Man;
template<class T> void showManInfo(Man<T> &m) { cout << m.m_name << endl; }
template<class T> class Man {
template<class T> friend void showManInfo(Man<T> &m);
public: Man(T name) :m_name(name) {}
private: T m_name; };
void test2() { Man<string> m("Alice");
showManInfo(m); }
|
类外实现比较复杂,为了能让编译器识别必须先放类的声明,再放函数,再放类的实现。
关于类外实现时 类内友元声明的写法:
- 第一种如上所示,
template<class T>
友元声明前必须加这个,否则执行时会报错,但是据说这种方式在Linux上不适用。
- 第二种方式:
friend void showManInfo <T>(Man<T> &m);
,这种在全平台均适用。
这两种方式仅类内友元声明时方式不同,类外的实现写法一模一样,没有任何差别。
建议使用全局函数做类内的实现,用法简单,而且编译器可以直接识别。
类模板与静态成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template<class T> class Cat { public: Cat(T name) :m_name(name) {}
static int sta_a;
static T sta_b;
static void changeSta_a() { sta_a++; cout << "sta_a=" << sta_a << endl; }
T m_name; };
template<class T> int Cat<T>::sta_a = 0;
void test3() { Cat<int> c1(1); Cat<string> c2("123");
c1.changeSta_a(); c1.changeSta_a();
c2.changeSta_a();
}
|
- 静态数据成员初始化的时候,记得加上
template<class T>
以及作用域Cat<T>
。
从上述代码可以看到,从类模板实例化的每一个模板类都有自己的类模板数据成员,该模板的所有对象共享一个static
的数据成员。