C++初阶之模板深化讲解(上)

简介: 非类型模板(Non-Type Template)是 C++ 中的一种模板形式,它允许你在模板中传递除了类型以外的其他值,比如整数、枚举、指针等。这些参数可以在编译时被解析,用于生成模板的实例化版本。

非类型模板

非类型模板(Non-Type Template)是 C++ 中的一种模板形式,它允许你在模板中传递除了类型以外的其他值,比如整数、枚举、指针等。这些参数可以在编译时被解析,用于生成模板的实例化版本。

非类型模板参数(Non-Type Template Parameter)是在模板声明中,作为参数的一部分,而不是类型的一部分。它们可以是常量表达式,例如整数常量、枚举、指针、引用等。非类型模板参数的值必须在编译时是已知的,因为它们用于生成模板的特定实例。

模板参数分类类型形参与非类型形参。

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

使用非类型模板参数的一个典型示例是实现一个固定大小的数组容器,其中数组的大小在编译时由模板参数指定。

例如,以下是一个使用非类型模板参数的示例:

template <typename T, int Size>
class FixedSizeArray {
private:
    T data[Size];
public:
    // ...
};
// 实例化一个大小为 10 的 FixedSizeArray,存储 int 类型
FixedSizeArray<int, 10> intArray;

在这个示例中,int Size 就是一个非类型模板参数,它确定了 FixedSizeArray 的数组大小。在编译时,intArray 的大小将被确定为 10,因为在实例化时传递了 10Size 参数。

注意:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的
  2. 非类型的模板参数必须在编译期就能确认结果。

模板的特化

模板的特化Template Specialization)是 C++ 中一种允许你为特定类型或特定条件提供定制化实现的机制。模板特化允许你在针对某些特定类型或条件的情况下,提供一个专门的模板实现,以覆盖通用模板的行为。

在一般的模板中,你可能会为不同类型的数据提供一个通用的模板实现。然而,在某些情况下,特定类型可能需要特殊处理或定制化的行为。这时,你可以通过模板特化来为这些特定类型提供特殊实现。

模板特化分为两种类型:

类模板特化(Class Template Specialization):针对特定类型的特殊实现。

函数模板特化(Function Template Specialization):针对特定类型的特殊实现。

在某些情况下,如果没有使用模板的特化,可能会导致编译器选择错误的实现,从而产生不明确的错误或意外的行为。一个常见的情况是函数模板的参数匹配不明确,编译器无法确定应该使用哪个模板实现。这种情况下,特化可以提供明确的信息,帮助编译器正确选择实现。

以下是一个示例,展示在没有模板特化的情况下可能会发生的问题:

#include <iostream>
template <typename T>
void printType(T value) {
    std::cout << "Generic template" << std::endl;
}
// 模板特化版本,针对 int 类型
template <>
void printType<int>(int value) {
    std::cout << "Specialized template for int" << std::endl;
}
int main() {
    int num = 5;
    printType(num);  // 编译错误,不明确的调用
    return 0;
}

在上面的示例中,没有模板特化的情况下,编译器无法确定应该调用哪个模板实现,因为 printType 函数可以接受任何类型的参数。这会导致编译错误,因为编译器无法根据上下文来选择正确的实现。

通过为 printType<int> 提供特化版本,我们可以明确告诉编译器在处理 int 类型参数时应该使用哪个实现。这种情况下,特化可以解决不明确调用的问题,确保编译器选择正确的实现。

1.函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

函数模板特化的示例:

#include <iostream>
// 通用的函数模板
template <typename T>
T add(T a, T b) {
    return a + b;
}
// 函数模板的特化版本,针对 int 类型
template <>
int add(int a, int b) {
    std::cout << "Specialized version for int" << std::endl;
    return a + b + 10;
}
int main() {
    int result1 = add(5, 3);    // 调用通用版本
    std::cout << "Result 1: " << result1 << std::endl;  // 输出: 8
    int result2 = add<int>(5, 3);  // 调用特化版本
    std::cout << "Result 2: " << result2 << std::endl;  // 输出: Specialized version for int
                                                        // Result 2: 18
    return 0;
}

在上面的示例中,add 是一个函数模板,用于计算两个数的和。通过为 add<int> 提供特化版本,我们覆盖了默认的行为,并在特定类型下进行了定制化的操作。在 main 函数中,我们展示了如何调用通用版本和特化版本,以及它们的输出结果。

注意一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

2.类模板特化

在 C++ 中,类模板特化是指为特定类型或条件提供定制化的类模板实现。有两种类型的类模板特化:全特化(Full Specialization)和偏特化(Partial Specialization)。

全特化Full Specialization):全特化是针对特定类型的完整特化。当你为某个特定类型提供了一个完整的特化版本,它将会覆盖通用的类模板定义。全特化的语法是在模板名后面添加 <> 并指定特化的类型。

示例:

// 通用的类模板
template <typename T>
class MyTemplate {
public:
    void print() {
        std::cout << "Generic Template" << std::endl;
    }
};
// 类模板的全特化版本,针对 int 类型
template <>
class MyTemplate<int> {
public:
    void print() {
        std::cout << "Specialized Template for int" << std::endl;
    }
};

偏特化Partial Specialization):偏特化是在某些条件下对模板参数进行特化,通常用于更细粒度的定制。偏特化允许你为某些特定情况提供特化版本,而不是为每个类型提供完整的特化版本。偏特化的语法是在模板名后面添加 <>,并在尖括号中指定要特化的参数。

示例:

// 通用的类模板
template <typename T, typename U>
class Pair {
public:
    Pair(T first, U second) : first_(first), second_(second) {}
    void print() {
        std::cout << "Generic Pair: " << first_ << ", " << second_ << std::endl;
    }
private:
    T first_;
    U second_;
};
// 类模板的偏特化版本,针对两个相同类型的参数
template <typename T>
class Pair<T, T> {
public:
    Pair(T first, T second) : first_(first), second_(second) {}
    void print() {
        std::cout << "Specialized Pair for same type: " << first_ << ", " << second_ << std::endl;
    }
private:
    T first_;
    T second_;
};

在上述示例中,全特化针对整型类型提供了特化版本,而偏特化则针对两个相同类型的参数提供了特化版本。

总结:

全特化:为特定类型提供完整特化版本。

偏特化:为特定情况或条件提供特化版本。

通过使用模板特化,你可以在需要定制化行为的情况下,为类模板提供精确的实现,增强了模板的灵活性和适用性。

类模板允许我们为不同的数据类型提供相同的代码结构,以适应多种类型的需求。然而,在某些情况下,编译器可能无法推断出如何比较不同类型的对象。这就是为什么在下面的示例中,如果不使用类模板的特化,编译器可能无法正确排序日期类的原因。

在默认情况下,std::sort 函数使用元素的<运算符来比较元素的大小。对于基本类型(如整数)或支持 < 运算符的类型,这是没有问题的。但对于自定义类型(如日期类),编译器无法知道如何执行比较。

通过使用类模板的特化,我们为不同类型的日期类提供了明确的比较逻辑,即 operator< 运算符的重载。这告诉编译器如何在特定情况下比较日期对象,使得 std::sort 函数能够正确工作。

特化类模板允许我们在需要的地方为特定类型提供定制的实现,从而解决编译器无法推断的问题,确保程序能够正确运行。

下面是一个关于日期类的示例,使用类模板特化来比较大小,并通过 std::sort 函数对日期进行排序

在示例中,我们定义了一个 Date 类模板,然后为 Date 类提供了特化版本,用于实现日期的比较操作:

#include <iostream>
#include <vector>
#include <algorithm>
// 通用的类模板
template <typename T>
class Date {
public:
    Date(T year, T month, T day) : year_(year), month_(month), day_(day) {}
    // 比较运算符
    bool operator<(const Date& other) const {
        if (year_ != other.year_) return year_ < other.year_;
        if (month_ != other.month_) return month_ < other.month_;
        return day_ < other.day_;
    }
    void print() {
        std::cout << year_ << "-" << month_ << "-" << day_ << std::endl;
    }
private:
    T year_;
    T month_;
    T day_;
};
// 类模板的特化版本,用于 int 类型
template <>
class Date<int> {
public:
    Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
    bool operator<(const Date<int>& other) const {
        if (year_ != other.year_) return year_ < other.year_;
        if (month_ != other.month_) return month_ < other.month_;
        return day_ < other.day_;
    }
    void print() {
        std::cout << year_ << "-" << month_ << "-" << day_ << std::endl;
    }
private:
    int year_;
    int month_;
    int day_;
};
int main() {
    std::vector<Date<int>> dates = {
        {2022, 8, 15},
        {2021, 12, 25},
        {2022, 1, 1},
        {2022, 3, 20}
    };
    std::cout << "Before sorting:" << std::endl;
    for (const auto& date : dates) {
        date.print();
    }
    std::sort(dates.begin(), dates.end());
    std::cout << "After sorting:" << std::endl;
    for (const auto& date : dates) {
        date.print();
    }
    return 0;
}

在上面的示例中,我们首先定义了一个通用的 Date 类模板,用于存储年、月、日信息。然后,我们为 Date<int> 提供了特化版本,用于实现基于 int 类型的日期比较操作。

main 函数中,我们创建了一个存储日期的 std::vector,然后使用 std::sort 函数对日期进行排序。由于我们为 Date<int> 提供了特化版本,它能够正确比较日期并进行排序。最终,我们分别输出排序前后的日期顺序。

相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
101 10
|
1月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
18 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
46 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
80 2
|
1月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
39 2
|
1月前
|
存储 算法 编译器
【C++】初识C++模板与STL
【C++】初识C++模板与STL
|
1月前
|
编译器 C++
【C++】模板进阶:深入解析模板特化
【C++】模板进阶:深入解析模板特化
|
2天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
15 2
|
8天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
33 5
|
14天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
46 4
下一篇
无影云桌面