C++语法之模板

本文首发于个人博客

模板(template)

  • 泛型,是一种将类型参数化以达到代码复用的技术,很多语言中都有,例如Java,Swift等,C++中使用模板来实现泛型

  • 模板的使用格式如下

    • template <typename\class T>
    • typename和class是等价的
  • 模板没有被使用时,是不会被实例化出来的

  • 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误

  • 一般将模板的声明和实现统一放到一个.hpp文件中

函数模板

格式为

1
2
3
4
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{
函数体
}

或者和下面是一样的

1
2
3
4
template <typename 形参名,typename 形参名,......> 返回类型 函数名(参数列表)
{
函数体
}

例如我们有2个函数,分别计算int 类型和 double类型的加法运算

1
2
3
4
5
6
7
int add(int a ,int b){
return a+b;
}

double add(double a ,double b){
return a+b;
}

上面的每个函数只能实现一种数据类型的运算,所以我们用模板就可以用一个函数来表示

1
2
3
4
template <typename T>
T add(T a, T b) {
return a + b;
}

使用的时候

1
2
3
4
5
6
int main(){
int a = 10;
int b = 20;
int c = add<int>(a, b);
return 0;
}

我们也可以把类型去掉,这样调用函数的时候,会隐式转换

1
2
3
4
5
6
int main(){
int a = 10;
int b = 20;
int c = add(a, b);
return 0;
}

多参数模板

  • 有时候我们的可以多参数,并且是不同类型的

例如:

1
2
3
4
template <typename T1,typename T2>
T2 add(T1 a, T2 b) {
return a + b;
}

但是一般来说,为了避免生成不必要的中间变量,以及为了代码安全性,上面代码可以改成如下

1
2
3
4
template <typename T1,typename T2>
T2 add(const T1 &a,const T2 &b) {
return a + b;
}

类模板

不仅仅有函数模板,而且还有类模板
例如,我们自定义一个数组类,可以接收各种类型的数据

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#pragma once
#include <iostream>
using namespace std;

template <typename Item>
class Array {
friend ostream &operator<<<>(ostream &, const Array<Item> &);
// 用于指向首元素
Item *m_data;
// 元素个数
int m_size;
// 容量
int m_capacity;
void checkIndex(int index);
public:
Array(int capacity = 0);
~Array();
void add(Item value);
void remove(int index);
void insert(int index, Item value);
Item get(int index);
int size();
Item operator[](int index);
};

template <typename Item>
Array<Item>::Array(int capacity) {
m_capacity = (capacity > 0) ? capacity : 10;

// 申请堆空间
m_data = new Item[m_capacity];
}

template <typename Item>
Array<Item>::~Array() {
if (m_data == NULL) return;
delete[] m_data;
}

template <typename Item>
void Array<Item>::checkIndex(int index) {
if (index < 0 || index >= m_size) {
// 报错:抛异常
throw "数组下标越界";
}
}

template <typename Item>
void Array<Item>::add(Item value) {
if (m_size == m_capacity) {
// 扩容
/*
1.申请一块更大的新空间
2.将旧空间的数据拷贝到新空间
3.释放旧空间
*/

// 这里只做简单的打印
cout << "空间不够" << endl;
return;
}

m_data[m_size++] = value;
}

template <typename Item>
void Array<Item>::remove(int index) {
checkIndex(index);

}

template <typename Item>
void Array<Item>::insert(int index, Item value) {

}

template <typename Item>
Item Array<Item>::get(int index) {
checkIndex(index);

return m_data[index];
}

template <typename Item>
int Array<Item>::size() {
return m_size;
}

template <typename Item>
Item Array<Item>::operator[](int index) {
return get(index);
}

template <typename Item>
ostream &operator<<<>(ostream &cout, const Array<Item> &array) {
cout << "[";

for (int i = 0; i < array.m_size; i++) {
if (i != 0) {
cout << ", ";
}
cout << array.m_data[i];
}

return cout << "]";
}

使用的时候

定义坐标类Point

1
2
3
4
5
6
7
class Point {
friend ostream &operator<<(ostream &, const Point &);
int m_x;
int m_y;
public:
Point(int x = 0, int y = 0) :m_x(x), m_y(y) {}
};

这个数组里面可以存放Point类型的

1
2
3
4
5
6
Array<Point> array;
array.add(Point(1, 2));
array.add(Point(3, 4));

array.remove(0);
array.get(0);

也可以存放int类型数据

1
2
3
4
Array<int> array;
array.add(10);
array.add(20);
array.add(30);

类模板中的友元函数

上的代码中已经包含了友元函数,是运算符号<>重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//声明
friend ostream &operator<<<>(ostream &, const Array<Item> &);


//实现
template <typename Item>
ostream &operator<<<>(ostream &cout, const Array<Item> &array) {
cout << "[";

for (int i = 0; i < array.m_size; i++) {
if (i != 0) {
cout << ", ";
}
cout << array.m_data[i];
}

return cout << "]";
}

如下使用,就可以按照我们重载的方式来打印array的数值了。

1
2
3
4
5
6
7
8
9
int main() {
Array<Point> array;
array.add(Point(1, 2));
array.add(Point(3, 4));
array.remove(0);
array.get(0);

cout << array << endl;
}