文章目录
- 1 概述
- 2 函数模板
- 2.1 使用函数模板
- 2.2 函数模板注意事项
- 2.3 普通函数和函数模板的区别
- 2.4 普通函数与函数模板的调用规则
- 2.5 模板的局限性
- 2 类模板
- 2.1 类模板语法
- 2.2 类模板和函数模板的区别
- 2.3 类模板中成员函数的创建时机
- 2.4 类模板与继承
- 2.5 类模板成员函数类外实现
- 2.6 类模板份文件编写
- 2.7 类模板与友元
1 概述
模板是一种较为通用的模具,不能单独使用
C++除了面向对象编程之外,还有一种是泛型编程,使用的就是模板
C++中提供了两种模板:函数模板,类模板
2 函数模板
2.1 使用函数模板
函数模板的作用是建立一个通用的函数,其返回值类型和形参类型可以不具体定制,用一个虚拟的类型来代表
语法:
template<typename T>
函数
template – 声明创建模板
typename – 表明其后面的符号是一种数据类型,可以用class代替
T – 通用数据类型,名称可以替换
#include <iostream>using namespace std;template<typename T>
void mySwap(T &a, T &b) {T temp = a;a = b;b = temp;
}int main() {int a = 10;int b = 20;mySwap(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;string c = "111";string d = "222";mySwap<string>(c, d);cout << "c = " << c << endl;cout << "d = " << d << endl;return 0;
}
使用模板有两种方式,一种是自动推导类型,一种是显示指定类型
这里使用模板,可以传入多种类型的参数,当int类型参数传入的时候,T被推导成int类型,当string类型参数传入的时候,T被推导成string类型。
2.2 函数模板注意事项
函数模板定义于函数前,只能用于其后的第一函数,且函数模板必须推导出一致的类型
int a = 10;
char b = 'a';
mySwap(a, b);
编译失败,因为推导不出一致的类型
template<typename T>
void func() {cout << "func" << endl;
}int main() {
// func();func<int>();
}
必须推导出具体的类型,函数模板才能够正常调用,否则编译失败
2.3 普通函数和函数模板的区别
普通函数可以发生自动类型转换,函数模板如果使用自动类型推导,则不会发生类型的隐式转换。如果函数模板使用显示类型,则可以发生隐式转换
template<typename T>
int addNum(T a, T b) {return a + b;
}int main() {int a = 10;int b = 20;addNum(a, b);//addNum(a, c);char c = 'a';addNum<int>(a, c);
}
2.4 普通函数与函数模板的调用规则
- 1.如果函数模板和普通函数都可以实现,优先调用普通函数
- 2.可以通过空模板参数列表来强制调用函数模板
- 3.函数模板也可以发生重载
- 4.如果函数模板可以产生更好的匹配,优先调用函数模板
#include <iostream>using namespace std;void print(int a, int b) {cout << "普通函数" << endl;
}template<typename T>
void print(T a, T b) {cout << "函数模板" << endl;
}template<typename T>
void print(T a, T b, T c) {cout << "重载的函数模板" << endl;
}int main() {int a = 10;int b = 20;// 调用普通函数print(a,b);// 调用函数模板print<>(a,b);// 调用重载的函数模板int c = 30;print(a, b, c);// 调用函数模板char d = 'a';char e = 'b';print(d, e);
}
实践中,如果提供了函数模板就没有必要提供普通函数了,以免造成二义性
2.5 模板的局限性
模板并不是完成的
template<typename T>
void f(T a, T b) {a = b;
}
对于上述的赋值操作,如果传入的参数是数组,则无法实现
template<class T>
void f(T a, T b) {if (a > b) { ... }
}
如果传入的参数是自定义的类对象,则上面的模板也无法运行
对于上面的这种问题,C++提供了一种函数模板的重载方式,可以为这些特定的类型提供具象化的模板。
#include <iostream>using namespace std;class Person {
public:Person(string name, int age) {this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};template<class T>
bool myCompare(T &a, T &b) {if (a == b) {return true;} else {return false;}
}// 提供具象化的模板函数重载
template<>
bool myCompare(Person &p1, Person &p2) {if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) {return true;} else {return false;}
}int main() {int a = 10;int b = 10;cout << myCompare(a, b) << endl;Person p1("zhangsan", 23);Person p2("lisi", 23);cout << myCompare(p1, p2) << endl;return 0;
}
使用具体化模板,可以解决自定义类型的通用化
2 类模板
2.1 类模板语法
类模板的作用:创建一个通用的类,类中成员变量的类型可以不具体指定,用虚拟类型来代表
语法:
template<typename T>
类
示例:
#include <iostream>using namespace std;template<class NameType, class AgeType>
class Person {
public:Person(NameType name, AgeType age) {m_Name = name;m_Age = age;}void showPerson() {cout << "name = " << m_Name << " , age = " << m_Age << endl;}NameType m_Name;AgeType m_Age;
};int main() {Person<string, int> person("zhangsan", 21);person.showPerson();return 0;
}
创建了一个类模板,在创建对象的时候,可以通过显示的限定数据类型来让模板中的通用类型拥有具体的类型,也可以让编译器自动进行推导
Person person("zhangsan", 21);
2.2 类模板和函数模板的区别
类模板在模板参数定义的时候可以有默认值
#include <iostream>using namespace std;template<class NameType, class AgeType = int>
class Person {
public:Person(NameType name, AgeType age) {m_Name = name;m_Age = age;}void showPerson() {cout << "name = " << m_Name << " , age = " << m_Age << endl;}NameType m_Name;AgeType m_Age;
};int main() {Person<string> person("zhangsan", 20);person.showPerson();return 0;
}
设置模板默认值,在使用模板类时,可以不需要限定默认参数的类型
对于默认参数类型,可以在显示限定的时候覆盖默认类型
Person<string, double> person("zhangsan", 22.3);
2.3 类模板中成员函数的创建时机
类模板中成员函数创建是在调用时才创建的
#include <iostream>using namespace std;class Person1 {
public:void showPerson1() {cout << "Person1 show" << endl;}
};class Person2 {
public:void showPerson2() {cout << "Person2 show" << endl;}
};template<class T>
class MyClass {
public:T obj;//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成void fun1() { obj.showPerson1(); }void fun2() { obj.showPerson2(); }
};int main() {MyClass<Person1> m;m.fun1();// m.fun2();//编译会出错,说明函数调用才会去创建成员函数return 0;
}
当不调用fun2的时候,代码能够编译通过,调用的时候就会报错,因为调用的时候才会识别到T被初始化为Person1类型,而Person2中没有showPerson2函数。
2.4 类模板与继承
类模板与继承相关时,需要注意
- 父类如果存在类模板,子类在声明的时候需要指定父类中的模板类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中T的类型,子类需变为类模板
#include <iostream>using namespace std;template<class T>
class Base
{T m;
};// class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public Base<int> //必须指定一个类型
{
};
void test01()
{Son c;
}//类模板继承类模板 ,可以用T2指定父类中的T类型
template<class T1, class T2>
class Son2 :public Base<T2>
{
public:Son2(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;}
};void test02()
{Son2<int, char> child1;
}int main() {test01();test02();return 0;
}
继承类模板,必须显示指定其类型,如果不显示指定,可以将需要指定父类的类型也声明成一个模板,在子类构造的时候传入。
2.5 类模板成员函数类外实现
#include <iostream>
#include <string>using namespace std;//类模板中成员函数类外实现
template<class T1, class T2>
class Person {
public://成员函数类内声明Person(T1 name, T2 age);void showPerson();public:T1 m_Name;T2 m_Age;
};//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;
}//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}int main() {Person<string, int> p("Tom", 20);p.showPerson();return 0;
}
类模板成员函数的类外实现,除了需要重新声明模板外还需要限定他是一个类模板(使用<>)
2.6 类模板份文件编写
问题
- 类模板中成员函数创建时机在调用阶段,导致份文件编写时链接不到
解决 - 直接包含.cpp文件
- 将声明和实现放到一个文件中,并更改后缀为.hpp,hpp为约定名
推荐使用第二种方式
#ifndef STLDEMO1_PERSON_HPP
#define STLDEMO1_PERSON_HPP#include<iostream>using namespace std;template<class T1, class T2>
class Person {
public://成员函数类内声明Person(T1 name, T2 age);void showPerson();public:T1 m_Name;T2 m_Age;
};//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;
}//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}#endif
创建hpp文件,将声明和实现放到一个文件中进行导入。因为分文件导入,调用的时候才创建函数,最开始的时候只包含了头文件,没有生成对应的函数,在调用的时候才生成,所以找不到函数。
2.7 类模板与友元
可以分为两种,一种是全局函数类内实现,一种是全局函数类外实现
1、全局函数类内实现
#include <iostream>
#include <string>using namespace std;template<class T1, class T2>
class Person {//1、全局函数配合友元 类内实现friend void printPerson(Person<T1, T2> &p) {cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;}public:Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;}private:T1 m_Name;T2 m_Age;
};int main() {Person<string, int> p("Jerry", 30);printPerson(p);return 0;
}
直接类内实现,并调用
类外实现较为复杂一些
#include <iostream>
#include <string>using namespace std;template<class T1, class T2>
class Person;template<class T1, class T2>
void printPerson(Person<T1, T2> &p) {cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}template<class T1, class T2>
class Person {friend void printPerson<>(Person<T1, T2> &p);
public:Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;}private:T1 m_Name;T2 m_Age;
};int main() {Person<string, int> p("Jerry", 30);printPerson(p);return 0;
}
友元定义的时候需要加<>让编译器识别该友元函数是一个模板函数。否则编译失败,其他的调整就是让编译器能够识别到对应函数或类,能够编译通过即可。