C++(十四)模板进阶

 模板进阶

在之前我们学习过模板的基本使用,不熟悉的小伙伴可以翻看:C++(七)模板

在这里我们就直接更深入的学习模板

一、非类型模板参数

首先我们明确一点:模板分为类型模板非类型模板

所以参数也分别对应为类型模板参数非类型模板参数 

1. 类型模板(Type Template)

类型模板是最常见的模板类型,它允许你为类或函数定义参数化的类型。这种模板参数代表一种数据类型,编译时会根据你提供的实际类型进行实例化。

示例:

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

int main() {
    int x = 5, y = 10;
    double a = 2.5, b = 4.5;

    // 使用类型模板实例化函数(显式)            隐式也没有问题,为了易于观察写作显式
    int int_result = add<int>(x, y);          // int add(int, int)
    double double_result = add<double>(a, b); // double add(double, double)

    return 0;
}

 在上面的例子中,add函数是一个类型模板,T是一个类型参数。这个模板可以处理任何数据类型,如intdouble,而无需为每种类型编写不同的函数(交给编译器去做)。

在这里面 T就是类型模板参数 ,T会自动识别(或指定)成int或者double等类型。

这就是一个最为简单直观的类型模板。

2. 非类型模板(Non-Type Template)

非类型模板的参数不是类型,而是常量表达式(如整数、指针、引用等)。这些参数在模板实例化时被指定,并且通常用来影响模板内部的实现。

非类型模板的参数就为: 非类型模板参数 

什么意思呢?我们用一个最简单的例子看看非类型模板的使用:

template <int N>
int multiply(int a) {
    return a * N;
}

int main() {
    int result1 = multiply<5>(2);     // 5 * 2 = 10
    int result2 = multiply<10>(3);    // 10 * 3 = 30

    return 0;
}

 在这个例子中,multiply函数模板有一个非类型模板参数N,它是一个整型常量。在实例化时,N的值被替换为实际的常量值,如510

注意:

  • 非类型模板参数都被定义为常量表达式(const expression)这意味着它们的值在编译时是已知的。
  • 常见的非类型模板参数类型包括:

    • 整型常量 (int, char, bool, 等)
    • 枚举类型 (enum)
    • 指针类型 (指向对象或函数的指针)
    • 引用类型 (对象的引用)
    • 浮点数常量 (C++20及更高版本,现在大部分的编译器是不支持浮点型的!!!)
    • 类的静态成员

在这里主要介绍整型常量,使用相对比较多:

1.1.1整形常量

示例:

#include<iostream>
template <int N = 10>
int multiply(int a) {
    return a * N;
}

int main() {
    int result1 = multiply<5>(2);     // 5 * 2 = 10
    int result2 = multiply(3);        // 10 * 3 = 30
    std::cout << result1 <<std::endl;
    std::cout << result2;

    return 0;
}

程序运行结果:

30
10

        在这段程序里面,N可以给上缺省值,这也是没有问题的。

        只需要,且非常重要的一点:编译时常量,非类型模板参数必须是编译时常量,这意味着它们的值在编译时已确定。这里的N一定是一个常量且在编译时已确定,在函数中不可被修改!!!

错误用法:

template <int N = 10>
int multiply(int a) {
    N=30;
    return a * N;
}

int main() {
    int result1 = multiply<5>(2);     // 5 * 2 = 10
    int result2 = multiply(3);        // 10 * 3 = 30

    return 0;
}

程序运行结果:

下面的几种用法并非很常用,可适当时候翻看:

1.1.2.枚举类型

 示例:

#include<iostream>
enum Color { 
    Red, 
    Green, 
    Blue 
};

template <Color C>
class ColorPrinter {
public:
    void print() const {
        //关键字:constexpr,用于表示一个表达式或函数可以在编译时求值
        /* 如果 C 是 Red,则只会编译输出 "Red\n" 的代码块,其他分支不会编译。
         * 如果 C 是 Green,则只会编译输出 "Green\n" 的代码块。
         * 如果 C 是 Blue,则只会编译输出 "Blue\n" 的代码块。
         */
        if constexpr (C == Red) 
        {
            std::cout << "Red\n";
        }
        else if (C == Green) 
        {
            std::cout << "Green\n";
        }
        else if (C == Blue) 
        {
            std::cout << "Blue\n";
        }
    }
};

int main() {
    ColorPrinter<Red> redPrinter;
    redPrinter.print();     // 输出 "Red"
    return 0;
}

代码运行结果:

Red
1.1.3 指针和引用

        指针和引用类型的非类型模板参数用于传递对象的地址或引用。

示例:

#include<iostream>
template <int* ptr>
class PointerWrapper {
public:
    void print() const
    {
        std::cout << "Value: " << *ptr << "\n";
    }
};
int x = 5;
int main() {
    
    PointerWrapper<&x> wrapper;
    wrapper.print(); // 输出 "Value: 5"
    return 0;
}

运行结果: 

Value: 5

引用版本:

#include <iostream>

template <int& ref>  // 使用引用作为非类型模板参数
class PointerWrapper {
public:
    void print() const {
        std::cout << "Value: " << ref << "\n";  // 直接输出引用的值
    }
};

int x = 5;

int main() {
    PointerWrapper<x> wrapper;  // 使用引用的非类型模板参数
    wrapper.print(); // 输出 "Value: 5"
    return 0;
}

运行结果: 

Value: 5
1.1.4 类的静态成员

因为现在很多编译器尚未支持浮点数常量,所以这里直接跳过了。

下面直接演示静态成员:

#include<iostream>
class MyClass {
public:
    // 声明静态常量成员变量
    static const int value ;
};
// 类外定义静态常量成员变量
const int MyClass::value = 42;

template <int N>
class StaticValuePrinter {
public:
    void print() const 
    {
        std::cout << "Value: " << N << "\n";
    }
};

int main() {
    StaticValuePrinter<MyClass::value> printer;
    printer.print(); // 输出 "Value: 42"
    return 0;
}

运行结果:

Value: 42

二、类模板特化 

  • 什么是类模板特化? 
  • 模板特化有什么用?

我们的学习围绕上述问题进行展开:

1.模板特化的定义

        类模板特化(Class Template Specialization)是C++模板编程中的一个重要特性,允许针对特定的模板参数提供专门化的实现

在我们学习的模板的过程中,分为类模板和函数模板,函数特化很自然的也分为

  •      类模板特化 
  •      函数模板特化

 2.类模板特化:

那模板特化到底什么意思?看以下代码:

#include <iostream>

// 通用的类模板
template <typename T>
class MyClass {
public:
    void print() const 
    {
        std::cout << "Generic template\n";
    }
};

// 对int类型的特化 (可以看作为模板开的特殊通道,只要是int就走这边)
template <>
class MyClass<int> {
public:
    void print() const 
    {
        std::cout << "Specialized template for int\n";
    }
};

int main() {
    MyClass<double> obj1;  // 使用通用模板
    obj1.print();          // 输出: Generic template

    MyClass<int> obj2;     // 使用模板特化
    obj2.print();          // 输出: Specialized template for int

    return 0;
}
  • 通用模板 MyClass<T> 提供了一个 print 方法,适用于任何类型的 T
  • MyClass<int>MyClass 的完全特化版本,专门针对 int 类型,并提供了不同的 print 方法。(可以看作int的特殊通道,只要是int就走这里)

仔细阅读以上代码可以看出

类模板特化的写法

1.必须先定义一个基本的类模板

2.使用 template<> 声明完全特化的模板或者声明部分特化,在模板参数中指定部分条件。

3.为特定的模板参数提供不同的实现。


上面又提到了2个概念:

  • 完全特化
  • 部分特化

那么什么是完全特化?什么是部分特化呢?

说起来也简单:

  • 完全特化(Full Specialization):为特定的模板参数提供完全不同的实现。
  • 部分特化(Partial Specialization):为一部分模板参数提供特定的实现,而不是所有的模板参数。

上面我们举的第一个例子里面就是完全特化,就是将基础模板的全部参数都提供特定的实现,上面示例代码基础模板的全部参数就 T:将其特化成int

template <typename T>

完全特化:

template <>
class MyClass<int> 
{
 ... ...
};

理解了上面内容之后我们再看看什么是部分特化:

首先一个示例:(完全特化)

#include <iostream>

// 通用的类模板
template <typename T1, typename T2>
class MyClass {
public:
    void print() const {
        std::cout << "基础 MyClass \n";
    }
};

// 完全特化:针对类型 int 和 double 的组合
template <>
class MyClass<int, double> 
{
public:
    void print() const {
        std::cout << "完全特化 <int double>\n";
    }
};

int main() {
    MyClass<int, double> obj1; // 使用完全特化
    obj1.print(); // 输出

    MyClass<int, int> obj2;    // 使用通用模板
    obj2.print(); // 输出

    return 0;
}

 运行结果:

完全特化 <int double>
基础 MyClass

观察以上代码,和第一个例子没有本质的不同只不过是类模板参数多了一个。

完全特化,MyClass<int, double> 针对 intdouble 的组合提供了特定的实现。这种实现会完全替代通用模板,当模板参数是 intdouble 时,编译器会选择这个特化版本(走这个特殊通道)。


基于上面代码,如果让它是一个部分特化应该怎么写呢?

示例(部分特化):

template <typename T1, typename T2>
class MyClass {
public:
    void print() const {
        std::cout << "基础 MyClass \n";
    }
};

// 部分特化:当两个类型相同
template <typename T2>
class MyClass<int, T2> {
public:
    void print() const {
        std::cout << "部分特化 \n";
    }
};

int main() {
    MyClass<int, int> obj1; // 使用部分特化
    obj1.print(); 

    MyClass<double, double> obj2;    // 使用基础模板
    obj2.print();

    return 0;
}

运行结果: 

部分特化
基础 MyClass

部分特化MyClass<int, T2> 部分特化了 T1intT2 保持泛化。这意味着当 T1int 时,不论 T2 是什么类型,都会使用这个部分特化的版本(T1是int 优先走这边)。

在部分特化中的写法是值得我们注意的

如果想要只特化T2,T1还是使用任意类型,语法也是支持的

// 部分特化:当第二个类型是 int 时
template <typename T1>
class MyClass<T1, int>
{
... ...
};

 那么如果全特化和部分特化同时存在,那main函数里面的对象会走哪一个呢?

先说结论:哪个更匹配走哪一个!!!

书面来说模板特化的规则:

  • 完全特化:最具体,针对一个完全确定的类型组合,如 MyClass<int, double>
  • 部分特化:相对于通用模板来说更具体,但仍然可以匹配多种类型组合,如 MyClass<int, T2>MyClass<T, T>
  • 通用模板:最不具体,适用于任何类型组合。

代码演示:(大家可以先思考下面代码obj1,obj2,obj3分别的输出是什么再看运行结果)

#include <iostream>
template <typename T1, typename T2>
class MyClass {
public:
    void print() const {
        std::cout << "基础 MyClass \n";
    }
};

// 部分特化:当两个类型相同
template <typename T>
class MyClass<T, T> {
public:
    void print() const {
        std::cout << "部分特化 \n";
    }
};

// 部分特化:当第一个类型是 int 时
template <typename T2>
class MyClass<int, T2> {
public:
    void print() const {
        std::cout << "部分特化 <int, T2>\n";
    }
};

// 完全特化:针对类型 int 和 double 的组合
template <>
class MyClass<int, double>
{
public:
    void print() const {
        std::cout << "完全特化 <int double>\n";
    }
};

int main() {
    MyClass<int, double> obj1;        
    obj1.print();

    MyClass<int, char> obj2;           
    obj2.print();

    MyClass<short, double> obj3;
    obj3.print();

    return 0;
}

运行结果:

完全特化 <int double>
部分特化 <int, T2>
基础 MyClass

关于类模板特化这里就先展示这么多,小伙伴有疑问可以多多实践,多敲敲研究更多的变化。

3.函数模板特化 

        学习了上面类模板的使用,函数模板的使用也是如法炮制,只是在代码的书写使用中略微有点差别。

函数模板特化的使用步骤:

  • 定义通用的函数模板(在特化之前必须先有一个基础的函数模板)
  • template<> 声明和定义特化的模板函数

【注意】:函数模板特化不支持部分模板特化,只有全特化

示例:

#include <iostream>
#include <string>

// 1. 定义一个通用的函数模板
template <typename T>
void print(T value) {
    std::cout << "基础: " << value << std::endl;
}

// 2. 特化函数模板(特化int类型)
template <>
void print<int>(int value) {
    std::cout << "特化int类型: " << value << std::endl;
}

// 2. 特化函数模板(特化std::string类型)
template <>
void print<std::string>(std::string value) {
    std::cout << "特化std::string类型: " << value << std::endl;
}

int main() {
    // 3. 调用模板特化函数
    print(10);                      // 调用特化的int版本
    print(3.14);                    // 调用通用版本
    print("Hello");                 // 调用通用版本
    print(std::string("Hello"));    // 调用特化的std::string版本

    return 0;
}

如果理解了类模板的特化,函数模板的特化应该还是很好理解的,知道大家需要注意代码实践写的时候,语法应该是这么写的。

关于模板进阶暂时就提及这么多下面个大家提供一个脑图,帮助大家更清晰的学习理解模板。

 

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值