Skip to content

模板

c++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其 函数类型和形参类型不具体制定用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。

C++提供两种模板机制:函数模板类模板

1.函数模版

1.1 模版的定义

c++
template <typename T>返回类型 函数名(T 参数)
  • template:用于定义模板的关键字
  • typename:是个关键字,用于定义虚拟类型
  • T:虚拟类型,可以是任意字符,通常我们会使用一个大写的字母
    • 这个虚拟类型是在函数调用时传过来的一个类型修饰符,代表参数是这个类型的

1.2 模版的使用

使用模板是在调用函数时,在函数名与参数的中间,使用 <>,把参数传过去

c++
函数名< 参数一类型, 参数二类型 >(参数一, 参数二)

1.2.1 显式调用

具体写入的参数数量,以及类型

c++
template <typename T, typename U> void add(T num1, U num2) {
	cout << num1 + num2 << endl;
}

int main() {
	add<int,int>(10, 20);
	add<float, float>(10, 20.3);
}

1.2.3 自动推导

若不写 <>,c++会根据传入的参数自动推导类型

c++
template <typename T, typename U> void add(T num1, U num2) {
	cout << num1 + num2 << endl;
}

int main() {
	add(10, 20);
	add(10, 20.3);
}

image-20240725110201460

若实际给的类型少于模板定义的类型数量,那么实际给的类型,按照虚拟类型列表中的顺序去指定,没有指定的虚拟类型,将按照实参的类型来推导。

c++
template <typename T, typename U> void add(T num1, U num2) {
	cout << num1 + num2 << endl;
}

int main() {
	add<int>(10, 20);
	add<int>(10, 20.3);
}

image-20240725111007694

1.3 参数的默认值

模板定义可以和函数前面分行写

若调用函数时,不指定参数类型,则使用默认参数类型

c++
template <typename T = int, typename U = int> 
void add(T num1, U num2) {
	cout << num1 + num2 << endl;
}

int main() {
	add(10, 20);
	add<int ,double>(10, 20.3);
}

2.类模板

2.1 语法

2.1.1 定义模板

c++
template <class T,class U>
class MyClass{
    // ...
}

2.1.2 使用模板

使用类模板创建对象时,必须手动指定类型,不能再通过推导来确定类型

c++
MyClass<int, int> myclass;

2.2 类模板作为函数参数

普通函数中,类模板作为参数,类型必须要明确

c++
#include <iostream>
using namespace std;

template<class T,class U>
class Cal {
private:
	T num1;
	U num2;
public:
	T add() {
		return num1 + num2;
	}
	Cal(T num1,U num2) :num1(num1),num2(num2){}
	
};

void showRes(Cal<int,int> & cal) {
	cout << cal.add() << endl;
}

int main() {
	Cal<int, int> c(123,456);
	showRes(c);
}

为了更好的封装,我们可以搭配上函数模板使用

c++
#include <iostream>
using namespace std;

template<class T,class U>
class Cal {
private:
	T num1;
	U num2;
public:
	T add() {
		return num1 + num2;
	}
	Cal(T num1,U num2) :num1(num1),num2(num2){}
	
};

template <typename T,typename U>
void showRes(Cal<T,U> & cal) {
	cout << cal.add() << endl;
}

int main() {
	Cal<int, int> c(123,456);
	showRes(c);


	Cal<double, double> c2(11.1, 22.2);
	showRes(c2);
}

2.3 类模板继承

普通的类继承嘞模板,需要指定父类中的虚拟类型

c++
#include <iostream>
using namespace std;

template <class T>
class Animal {
public:
	T arg;
};

class Dog :public Animal<int> {

};

int main() {
	
	Dog dog;
	dog.arg = 10;
}

为了更好的封装,我们可以搭配上类模板使用

c++
#include <iostream>
using namespace std;

template <class T>
class Animal {
public:
	T arg;
};

class Dog :public Animal<int> {};

template <class U>
class Cat :public Animal<U> {};

int main() {
	Dog dog;
	dog.arg = 10;

	Cat<string> cat;
	cat.arg = "泥嚎";
}

2.4 类外实现类模板函数

类模板类中的函数,在类外定义,需要给每一个函数定义模板

c++
class Calculator {
private:
	T num1;
	U num2;
public:
	Calculator();
	Calculator(T num1,U num2);
	T add(T num1, U num2){}
};

template<class T, class U>
Calculator<T, U>::Calculator() {
	cout << "无参构造" << endl;
}

template<class T, class U>
Calculator<T, U>::Calculator(T num1, U num2) {
	this->num1 = num1;
	this->num2 = num2;
}

template<class T, class U>
T Calculator<T, U>::add(T num1, U num2) {
	return num1+num2
}

2.5 头文件和源文件分离

当定义类模板时,使用头文件和源文件分离的方法时

c++
#pragma once
#include <iostream>
using namespace std;

template<class T, class U>
class Calculator {
private:
	T num1;
	U num2;
public:
	Calculator();
	Calculator(T num1, U num2);

	void added();
};
c++
#include "main.h"

template<class T, class U>
Calculator<T, U>::Calculator() {
	cout << "无参构造" << endl;
}

template<class T, class U>
Calculator<T, U>::Calculator(T num1, U num2) {
	this->num1 = num1;
	this->num2 = num2;
}

template<class T, class U>
inline void Calculator<T, U>::added() {
	cout << this->num1 + this->num2 << endl;
}
c++
#include <iostream>
#include "main.h"

int main() {
	Calculator<int, int> cal;
}

这么使用我们发现会出现错误

虽然我们引入了.h 文件,但是 模板类中的函数是是在调用的时候才创建的

因此在编译阶段编译器不会管 .cpp 文件中对应的实现部分而到了我们使用这个函数的时候,发现这个函数已经创建了,声明完成,但是没有实现,因此就报错了。

相当于我们只在 .h 文件中声明了函数,但是没有实现。

解决方案:

  • 使用#include 引入.cpp 文件
  • 将类的声明和实现部分放到一个文件中,也就是 h 和 cpp 结合,放在 .hpp 文件中
c++
#pragma once
#include <iostream>
using namespace std;

template<class T, class U>
class Calculator {
private:
	T num1;
	U num2;
public:
	Calculator();
	Calculator(T num1, U num2);

	void added();
};

template<class T, class U>
Calculator<T, U>::Calculator() {
	cout << "无参构造" << endl;
}

template<class T, class U>
Calculator<T, U>::Calculator(T num1, U num2) {
	this->num1 = num1;
	this->num2 = num2;
}

template<class T, class U>
inline void Calculator<T, U>::added() {
	cout << this->num1 + this->num2 << endl;
}
c++
#include <iostream>
#include "test.hpp"

int main() {
	Calculator<int, int> cal;
}

2.6 类模板与友元函数

2.6.1 全局友元类内实现

c++
#include <iostream>
using namespace std;

template <class T>
class Room {
	friend void visitRoom(const Room<T>& room) {
		cout << room.area << endl;
	}
private:
	T area;

public:
	Room(T area):area(area){}
};

int main() {
	Room<int> room(200);
	visitRoom(room);
}

2.6.2 全局友元类外实现

c++
#include <iostream>
using namespace std;

template <class T>
class Room;

template <class T>
void visitRoom(const Room<T>& room) {
	cout << room.area << endl;
}

template <class T>
class Room {
	friend void visitRoom<>(const Room<T>& room);
private:
	T area;

public:
	Room(T area):area(area){}
};

int main() {
	Room<int> room(200);
	visitRoom(room);
}

注意

  1. 在类内函数名后要加上 <>,代表这是个模板函数,不然会被识别成普通函数
  2. 友元模板函数的定义需要定义在类的上方,不然类内会找不到此函数
  3. 以防友元函数中找不到类,需要在文件上方声明类

3.内联函数

inline 函数是 C++中为了提高程序运行效率而设计的一种特殊函数。当编译器遇到一个 inline 修饰的函数时,它会尝试将该函数的代码嵌入到每一个调用点,以减少函数调用的开销(如保存寄存器、堆栈帧的创建和销毁等)。

3.1 基本语法

在返回值类型的前面,用 inline 关键字修饰

c++
inline 返回值类型 函数名(参数列表) {
    // 函数体
}

例如

c++
inline int max(int x, int y) {
    return x > y ? x : y;
}

3.2 inline 函数的适用场景

1. 函数体较小

对于只有几行代码的小函数,使用 inline 可以避免函数调用的开销,提高执行效率。尤其是一些频繁调用的函数,如访问器(getter)和修改器(setter)。

c++
class Point {
private:
    int x, y;
public:
    inline int getX() const { return x; }
    inline void setX(int newX) { x = newX; }
    // ...
};

2. 模板函数

模板函数通常也应该声明为 inline。因为模板函数在编译时需要根据不同的模板参数实例化出不同的函数版本,inline 可以帮助编译器更好地处理这一过程,避免重复的函数体代码。

c++
template <typename T>
inline T const& max(T const& a, T const& b) {
    return a > b ? a : b;
}

3. 类的成员函数

类的成员函数,特别是那些访问私有成员或受保护成员的函数,往往适合声明为 inline。这样可以减少函数调用的开销,提高类的访问效率。

C++中在类内定义的所有函数都自动称为内联函数,类的成员函数的定义直接写在类的声明中时,不需要 inline 关键字

但是在类外定义成员函数时,需要手动设置为内联函数

4. 递归函数慎用

递归函数通常不适合声明为 inline,因为 inline 函数通常需要在编译时就展开,而递归函数的展开可能会导致编译后的代码体积急剧增加,甚至可能超出编译器的限制。

3.3 限制和注意事项

1. 编译器的选择

inline 只是一个向编译器的建议,编译器可以选择忽略这个建议。通常,编译器会根据函数的复杂度和调用频率来决定是否内联一个函数。因此,即使你声明了一个函数为 inline,也不能保证它一定会被内联。

2. 虚函数和内联

虚函数不适合声明为 inline。因为虚函数的调用需要通过虚函数表来确定实际调用的函数,这一过程与 inline 的展开机制相冲突。

3. 静态链接和动态链接

inline 函数通常会在每个调用点静态展开,这意味着它们不会通过动态链接库(DLL)的边界。因此,如果你打算在多个模块或库之间共享函数,那么 inline 可能不是最佳选择。

4. 代码膨胀

过度使用 inline 可能会导致编译后的代码体积显著增加,这可能会影响到程序的加载时间和内存占用。因此,在使用 inline 时需要权衡其带来的性能提升和代码膨胀之间的利弊。

MIT Licensed