【C++】模板详解

前言:在之前的学习我们发现我们无时无刻都用到模板这个东西,但是博主一直没有进行讲解,今天我们就一次性对模板进行一个整体的学习与讲解。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述



什么是模板

模板是C++中的一种编程工具,用于在编译时实现泛型编程。它允许程序员编写与特定数据类型无关的代码,以便在不同的情况下重用和适应不同的数据类型。

模板可以用来定义函数模板和类模板。函数模板允许在不同的数据类型上重复使用同一段代码,而类模板允许在需要时使用不同的数据类型实例化。

使用模板的好处是可以提高代码的重用性和灵活性。它允许程序员编写通用的算法和数据结构,可以适应不同类型的数据,而无需为每种类型编写不同的代码。

下面我们可以看一个例子:

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}
...
...
...

在上面例子中我们不难发现,当我们想写一个进行交换的代码的时候,仅仅因为传过来的数据类型,我们就要写一堆重复的代码,因此模板的作用就充分的体现出来了。模板使用特定的语法来定义和使用。例如,函数模板的定义使用关键字"template"和模板参数来表示通用的数据类型。类模板的定义使用类似的语法,将模板参数放在类名后面的尖括号中。

使用模板时,程序员可以在编译时指定模板参数的具体类型,从而生成特定的代码实例。这个过程被称为模板实例化。

总之,模板是C++中一种强大的机制,允许程序员编写通用的代码,可以适应不同的数据类型。通过使用模板,可以提高代码的重用性和灵活性,使程序更加可维护和可扩展。


函数模板

(1)什么是函数模板
在C++中,函数模板可以用来定义通用的函数,可以根据不同的数据类型自动生成相应的函数代码。
函数模板的定义方法

template <typename T>
返回类型 函数名(参数列表) {
   // 函数体
}

其中,typename T为模板参数,可以是任何合法的类型,返回类型为函数的返回类型,函数名为函数的名称,参数列表为函数的参数列表。

下面是一个函数模板的示例,用于交换两个变量的值(基本解决了上面代码重复的问题):

template <typename T>
void swap(T& left, T& right)
//这里的T代表的模板的类型名,可以通过传过来的数据识别数据类型,编译器会自动转成相应的类型 
{
	T temp = left;
	left = right;
	right = temp;
}

说明:

  1. template是声明模板的关键字,告诉编译器开始泛型编程。
  2. 尖括号<>中的typename是定义形参的关键字,用来说明其后的形参名为类型 参数,(模板形参)。Typename(建议用)可以用class关键字代替,两者没有区别。
  3. 模板形参(类属参数)不能为空(俗成约定用一个大写英文字母表示),且在函 数定义部分的参数列表中至少出现一次。与函数形参类似,可以用在函数定义的各 个位置:返回值、形参列表和函数体。
  4. 函数定义部分:与普通函数定义方式相同,只是参数列表中的数据类型要使用 尖括,号<>中的模板形参名来说明。当然也可以使用一般的类型参数

(2)函数模板的隐式类型转化
根据函数调用时传入的数据类型,推演出模板形参。因此

template <typename T>
void my_swap(T& left, T& right) {
	T temp = left;
	left = right;
	right = temp;
}
int main()
{
	int a = 3, b = 5;
	my_swap(a, b);//根据调用的类型会自动转变
	cout << a << " " << b << endl;
	double a1 = 3.4, b1 = 5.3;
	my_swap(a1, b1);
	cout << a1 << " " << b1 << endl;
	return 0;
}

(3)函数模板的实例化
C++中的函数模板实例化是指在调用函数模板时,根据具体的参数类型生成对应的函数实例的过程。函数模板的实例化可以通过两种方式来实现:显式实例化和隐式实例化。

  1. 显式实例化:在使用函数模板之前,使用template关键字加上具体的参数类型来显式地实例化函数模板。例如:
template <typename T>
void foo(T value) {
    // 函数模板的定义
}

// 显式实例化函数模板
template void foo<int>(int);

这样就会生成一个具体的函数实例,用于处理整型参数。

  1. 隐式实例化:当调用函数模板时,编译器会根据传入的实参类型自动推导出所需的函数实例。例如:
template <typename T>
void foo(T value) {
    // 函数模板的定义
}

int main() {
    // 隐式实例化函数模板
    foo(42);  // 参数为整型,编译器会自动推导出foo<int>(int)
    foo(3.14);  // 参数为浮点型,编译器会自动推导出foo<double>(double)
}

在调用foo(42)foo(3.14)时,编译器会根据参数类型自动实例化函数模板,并生成对应的函数实例。

需要注意的是,编译器只会根据需要实例化的函数模板参数类型生成对应的函数实例,而不会为所有可能的函数模板参数类型都生成函数实例,这样可以避免生成过多的代码和增加编译时间。


(4)函数模板的重载
函数模板的重载是指在同一个作用域内定义多个函数模板,它们具有相同的函数名但参数列表不同,以实现对不同类型的参数进行不同的处理。

函数模板的重载可以有两种形式:非模板函数重载和函数模板特化重载。

  1. 非模板函数重载:在函数模板之外定义一个与函数模板同名但参数列表不同的非模板函数。例如
template <typename T>
void foo(T value) {
    // 函数模板的定义
}

void foo(int value) {
    // 非模板函数的定义
}

int main() {
    foo(42);  // 调用非模板函数foo(int)
    foo(3.14);  // 调用函数模板foo<T>(T)
}

注意:当函数模板和普通函数都符合调用时,优先选择普通函数。

(5)函数模板的特化

函数模板特化是指针对特定类型的参数,定义一个特定的函数模板版本。可以为某个具体类型的参数定义一个特定的函数模板,以提供更具体的实现或逻辑(通俗的理解为一个特定的类型指定一种处理方式)。

template <typename T>
void print(T value) {
    std::cout << "Generic template function: " << value << std::endl;
}

template <>
void print<int>(int value)//对于int类型进行特化处理 
{
    std::cout << "Specialized template function for int: " << value << std::endl;
}

int main() {
    print(5.5);      // 调用通用模板函数
    print(10);       // 调用特化的模板函数
    print("Hello");  // 调用通用模板函数

    return 0;
}

输出结果:

Generic template function: 5.5
Specialized template function for int: 10
Generic template function: Hello

在上述示例中,我们首先定义了一个名为print的函数模板,用于打印不同类型的参数。然后我们对print函数模板进行了特化,使其具有对int类型参数的特定实现。在main函数中,我们分别调用了print函数模板,并输出了相应的结果。我们可以看到,对于int类型的参数,调用的是特化的版本,而对于其他类型的参数,调用的是通用版本。


类模板

C++中的类模板(class template)是一种用来定义通用类的工具。类模板可以根据参数化的类型生成不同的类。通过类模板,我们可以在不同的类型上实例化一个类,从而实现代码的复用和泛化(类模板的定义方式和函数模板大同小异)。

template <typename T>
class ClassName {
    // 类成员和成员函数的定义
};

上面的代码中,template 表示这是一个类模板,T是一个类型参数。在类中,我们可以使用T作为类型来定义成员变量、成员函数等。

在使用类模板时,需要在实例化时指定类型的具体值。可以通过在类名后加上的方式来实例化一个类模板,其中Type是具体的类型。

下面是一个使用类模板的例子:

template <typename T>//类模板
class Pair {
private:
    T _first;
    T _second;

public:
    Pair(T first, T second) {
        _first = first;
        _second = second;
    }

    T getFirst() {
        return _first;
    }

    T getSecond() {
        return _second;
    }
};

int main() {
    Pair<int> pair(1, 2);//类模板的实例化
    cout << pair.getFirst() << " " << pair.getSecond() << endl;

    Pair<string> pair2("Hello", "World");
    cout << pair2.getFirst() << " " << pair2.getSecond() << endl;

    return 0;
}

在上面的例子中,Pair是一个类模板,可以根据参数化的类型来生成不同的类。在main函数中,我们分别使用int和string作为类型参数来实例化了Pair类,并通过调用getFirst和getSecond函数来获取成员变量的值。

注: 与函数模板不同的是,类模板在实例化时,必须在尖括号中为模板形参显式 地指明数据类型(实参),编译器不能根据给定的数据推演出数据类型。即:不存 在将整型值10推演为int类型传递给模板形参的实参推演过程,必须要在<>中指 定int类型。


类模板的特化

在C++中,类模板可以通过特化来更具体地定义模板参数的类型。类模板特化有两种方式:全特化和偏特化。

全特化:当模板参数的具体类型已知时,可以使用全特化来定义类模板的特定实现。全特化使用特定的类型替换模板参数,并提供对应的实现代码(和前面函数模板的特化类似,对指定的类型进行特殊处理)。

全特化的语法如下:

template<typename T>
class MyTemplate { 
    // 模板的通用实现
};

template<>
class MyTemplate<int> {
    // 对于T为int类型的特化实现
};

template<>
class MyTemplate<double> {
    // 对于T为double类型的特化实现
};

类模板的偏特化是指对类模板的部分模板参数进行特化,而保持其他模板参数为通用实现的一种技术。偏特化允许根据特定的条件或类型,定义不同的特化实现。

偏特化有两种形式:主模板偏特化和部分特化

  1. 主模板偏特化:主模板偏特化是通过指定一个或多个特定的类型来对模板参数进行特化,保持其他参数为通用实现。主模板偏特化的语法如下:
template<typename T, typename U>
class MyTemplate {
    // 主模板的通用实现
};

template<typename U>
class MyTemplate<int, U>//相当于当T为int类型时,对该模板进行了偏特化 
{
    // 对T为int类型的特化实现
};

在上述示例中,我们对MyTemplate的第一个模板参数T进行了特化,当Tint时,使用特化实现。

  1. 部分特化:部分特化是通过指定特定的条件来对模板参数进行特化,保持其他参数为通用实现。部分特化的语法如下:
template<typename T, typename U>
class MyTemplate {
    // 主模板的通用实现
};

template<typename T>
class MyTemplate<T, int> {
    // 对U为int类型的特化实现
};

在上述示例中,我们对MyTemplate的第二个模板参数U进行了特化,当Uint时,使用特化实现。

使用类模板的偏特化时,需要注意以下几点:

  • 偏特化的实现必须在主模板定义之后。
  • 一个模板只能有一个主模板(通用实现),但可以有多个偏特化版本,每个偏特化版本针对不同的条件进行特化。
  • 在使用类模板时,编译器会自动选择与提供的模板参数最匹配的特化版本。

通过偏特化,我们可以根据特定的类型或条件,对类模板进行更具体的实现,以满足特殊的需求或提高性能。


非类型模板参数

C++中的非类型模板参数是指在模板定义中可以使用的常量表达式作为参数的一种方式。这些参数可以是整数、枚举类型、指针、引用等,但不能是浮点数、类类型或成员指针。

非类型模板参数可以在编译时提供,它们的值在编译期间是确定的,并且在生成代码时被替换为实际的值。这种编译时常量化的机制可以用于优化代码和提供更灵活的模板实例化。

非类型模板参数的语法如下:

template <typename T, int N>
class MyClass {
    // 类模板定义
};

上述示例中,N是一个非类型模板参数,它的类型是int。在实例化MyClass时,必须通过一个常量表达式来提供一个整数值作为模板参数。

使用非类型模板参数时,需要注意以下几点:

  • 非类型模板参数必须是一个常量表达式,可以在编译时求值。
  • 非类型模板参数必须在编译时已知,不能在运行时才确定。
  • 非类型模板参数可以用于模板参数的类型推导,编译器可以自动推导模板参数的类型,无需显式指定。
  • 非类型模板参数可以用于数组长度、类型选择、编译时分支等,提供了更灵活的模板实例化方式。

使用非类型模板参数: 可以在类模板的定义中使用非类型模板参数,如下所示:

template <typename T, int N>
class MyClass {
public:
    void foo() {
        std::cout << N << std::endl;
    }
};

在上述示例中,foo函数输出了非类型模板参数N的值。

通过使用非类型模板参数,可以在编译期间根据常量表达式的值来生成特定的模板实例化,从而提供更灵活和优化的代码。


好啦,今天的内容就到这里啦,下期内容预告C++中的继承.


结语:后面就要进入C++进阶的内容了,难度又慢慢提升了大家一起努力!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卫卫周大胖;

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值