红魔咖啡馆

头发越掉越多,头发越掉越少

0%

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

模板与泛型编程

模板

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

机制:函数模板与类模板

函数模板

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

语法

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);
}