【C++】模板与泛型编程

  • ~7.30K 字
  1. 1. 模板与泛型编程
    1. 1.1. 模板
    2. 1.2. 函数模板
      1. 1.2.1. 语法
      2. 1.2.2. 注意
      3. 1.2.3. 与普通函数的区别
      4. 1.2.4. 调用规则
    3. 1.3. 类模板
      1. 1.3.1. 语法
      2. 1.3.2. 与函数模板的区别
      3. 1.3.3. 创建时机
      4. 1.3.4. 类模板对象做函数参数
      5. 1.3.5. 类模板与继承
    4. 1.4. 模板特化
      1. 1.4.1. 函数模板特化
      2. 1.4.2. 类模板成员函数类外实现
      3. 1.4.3. 类模板分文件编写
      4. 1.4.4. 类模板与友元

模板与泛型编程

模板

概念:建立通用模具,大大提高复用性,利用模板进行编程的思想被称为泛型编程

机制:函数模板与类模板

函数模板

建立一个通用函数,返回值类型与形参类型可以不具体指定,用虚拟类型代表

语法

1
2
template<typename T>
/*函数声明或定义*/
  • template:声明创建模板
  • typename:表示后面的符号是一种数据类型
  • T:通用数据类型,名称可以替换,但通常是大写字母
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
template<typename T> // 声明模板
void swapt(T &a, T &b){
T temp = a;
a = b;
b = temp;

}
int main(){
int a = 20;
int b = 10;
// 方法一:自动类型推导
swapt(a,b);
// 方法二:显式指定类型
swapt<int>(a,b);
cout << a << " " << b;

}

注意

  • 自动类型推导时,必须推导出一致的数据类型T才能使用

    1
    2
    3
    int a;
    char c;
    swapt(a,c); // 出错
  • 模板必须要确定出T的数据类型才能用

    1
    2
    3
    4
    5
    template<typename T>
    void f(){
    return ;
    }
    f(); // 报错
  • typename可以用class替换(class可以声明函数模板和类模板)

与普通函数的区别

  • 普通函数调用可以发生隐式类型转换
  • 函数模板使用自动类型推导时不可以发生隐式类型转换
  • 使用显式指定类型时可以发生隐式类型转换

调用规则

  • 普通函数与函数模板都可以实现时,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板
  • 函数模板也可以发生重载
  • 如果函数模板可以产生更好的匹配,则优先调用函数模板
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
#include<iostream>
using namespace std;
void print(int a, int b){
cout <<"普通函数调用"<<endl;
cout << a <<" "<< b << endl;
}
template<typename T>
void print(T a, T b){
cout <<"函数模板调用"<<endl;
cout << a <<" "<< b << endl;
}
template<typename T>
void print(T a, T b, T c){
cout <<"重载函数模板调用"<<endl;
cout << a <<" "<< b <<" "<<c << endl;
}
int main(){
int a = 10;
int b = 10;
print(a,b);
// 更好匹配
char c = '1';
char d = '2';
print(c,d);
// 强制调用
print<>(a,b);
//重载调用
int e = 10;
print(a, b, e);
}

类模板

建立一个通用类,类中的成员数据类型可以用虚拟类型代表

语法

1
2
template<typename T>
/*类*/
  • template:声明创建模板
  • typename:表示后面的符号是一种数据类型
  • 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
#include<iostream>
using namespace std;

template<class NameType, class AgeType>
class person{
public:
person(NameType name, AgeType age){
this->age = age;
this->name = name;
}
void show(){
cout <<"name: "<<this->name<<endl;
cout <<"age: "<<this->age<<endl;
}
NameType name;
AgeType age;
};

int main(){
// 使用模板参数列表指定类型
person<string, int> p1("Jack", 18);
p1.show();


}

与函数模板的区别

  • 类模板没有自动类型推导的方式

    不能写person p("a", 1);这种写法,只能显式指定

  • 类模板在模板参数列表中可以有默认参数

    template<class NameType, class AgeType = int>可以指定默认的数据类型

创建时机

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建,因为调用时才能确定数据类型

类模板对象做函数参数

传入方式:

  • 指定传入类型:直接显示对象的数据类型
  • 参数模板化:将对象中的参数变为模板进行传递
  • 整个类模板化:将这个对象类型模板化进行传递
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
#include<iostream>
using namespace std;

template<class NameType, class AgeType>
class person{
public:
person(NameType name, AgeType age){
this->age = age;
this->name = name;
}
void show(){
cout <<"name: "<<this->name<<endl;
cout <<"age: "<<this->age<<endl;
}
NameType name;
AgeType age;
};
// 指定传入类型
void printp1(person<string, int> &p){
p.show();
}
// 参数模板化
template<class T1, class T2>
void printp2(person<T1, T2> &p){
cout <<"T1: "<<typeid(T1).name()<<endl;
cout <<"T2: "<<typeid(T2).name()<<endl;
p.show();
}
// 整个类模板化
template<class T>
void printp3(T &p){
cout <<"T: "<<typeid(T).name()<<endl;
p.show();
}

int main(){
person<string, int> p("Jack", 18);
printp1(p);
printp2(p);
printp3(p);

}

类模板与继承

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

    1
    2
    3
    4
    5
    6
    7
    template<class T>
    class base{
    T m;
    };
    class son:public base<int>{

    };
  • 若想灵活指定父类中的T类型,子类也需要变成类模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<class T>
    class base{
    T m;
    };
    template<class T1, class T2>
    class son:public base<T2>{
    T1 obj;
    };
    int main(){
    son<int, char> s1; // 此时int为T1对应的obj的类型,char为T2对应父类中T的类型
    }

模板特化

有些特定数据类型需要一些特定的实现方式,需要具体化实现

函数模板特化

在需要特化的函数前添加template<>

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
#include<iostream>
using namespace std;
class person{
public:
person(string name, int age){
this->age = age;
this->name = name;
}

string name;
int age;
};
template<typename T>
bool cmp(T &a, T &b){
if (a==b){
return true;
}
else{
return false;
}
}
// 使用具体化person的版本实现代码,具体化优先调用
template<> bool cmp(person &p1, person &p2){
if (p1.name==p2.name&&p1.age ==p2.age) return true;
else return false;
}
int main(){
person p1("Jack", 10);
person p2("Jack", 10);
if (cmp(p1,p2)){
cout <<"a==b"<<endl;
}
else {
cout <<"a!=b"<<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
29
30
#include<iostream>
using namespace std;

template<class NameType, class AgeType>
class person{
public:
person(NameType name, AgeType age);
void show();
NameType name;
AgeType age;
};

// 类外实现
template<class NameType, class AgeType>
person<NameType, AgeType>::person(NameType name, AgeType age){
this->age = age;
this->name = name;
}

template<class NameType, class AgeType>
void person<NameType, AgeType>::show(){
cout <<"name: " <<this->name << endl;
cout <<"age: "<<this->age<<endl;
}

int main(){
person<string, int> p("Jack", 18);
p.show();

}

类模板分文件编写

由于类模板中成员函数在调用时才创建,这导致了分文件编写时链接不到,有两个方法

  • 直接引入.cpp文件

  • 将声明和实现写在同一个文件中,改后缀名为.hpp

常用第二种

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
#pragma once
#include<iostream>
using namespace std;

template<class NameType, class AgeType>
class person{
public:
person(NameType name, AgeType age);
void show();
NameType name;
AgeType age;
};

// 类外实现
template<class NameType, class AgeType>
person<NameType, AgeType>::person(NameType name, AgeType age){
this->age = age;
this->name = name;
}

template<class NameType, class AgeType>
void person<NameType, AgeType>::show(){
cout <<"name: " <<this->name << endl;
cout <<"age: "<<this->age<<endl;
}

(hpp文件)

1
2
3
4
5
6
7
8
9
#include<iostream>
using namespace std;
#include "person.hpp"

int main(){
person<string, int> p("Jack", 18);
p.show();

}

(调用文件)

类模板与友元

使用场景

  • 全局函数类内实现:直接类内声明友元
  • 全局函数类外实现:需要提前让编译器知道全局函数的存在
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
#include<iostream>
using namespace std;
// 前向声明 类与模板函数
template<class NameType, class AgeType>
class person;
template<class NameType, class AgeType>
void printPerson2(person<NameType, AgeType> p);

template<class NameType, class AgeType>
class person{
// 类内实现
friend void printPerson(person<NameType, AgeType> p){
cout <<"name: " <<p.name << endl;
cout <<"age: "<<p.age<<endl;
}
// 类外实现 需要让编译器提前知道这个函数的存在
friend void printPerson2<>(person<NameType, AgeType> p);
public:

person(NameType name, AgeType age){
this->age = age;
this->name = name;
}
private:
NameType name;
AgeType age;
};

// 类外实现
template<class NameType, class AgeType>
void printPerson2(person<NameType, AgeType> p){
cout << "name: " << p.name << endl;
cout << "age: " << p.age << endl;
}

int main(){
person<string, int> p("Jack", 18);
printPerson(p);
printPerson2(p);
}
赞助喵
非常感谢您的喜欢!
赞助喵