当前位置: 首页 > article >正文

【C++指南】模板 深度解析

         💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《C++指南》

                                  期待您的关注

 

47f09392526c71b5885ec838a3ea7ffe.gif

目录

1. 引言

2. 模板的基本概念

3. 函数模板

3.1 定义和语法

3.2 函数模板实例化

3.3 隐式实例化

3.4 显式实例化

 3.5 模板函数的匹配规则

4. 类模板

4.1 定义和语法

4.2 成员函数的定义

4.3 模板参数的默认值

5. 模板的高级用法

5.1 模板特化

5.2 模板模板参数

6. 实战案例

6.1 函数模板示例

6.2 类模板示例

7. 常见问题与注意事项

8. 结语

9. 参考文献


 

 

1. 引言

C++模板是一种强大的泛型编程工具,它允许程序员编写独立于具体类型的代码。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

 

通过模板,我们可以创建通用的函数和类,这些函数和类可以适用于多种数据类型,从而提高代码的复用性和灵活性

 

本文将详细介绍C++模板的基本概念和使用方法。

 

2. 模板的基本概念

模板是C++的一种特性,用于创建函数或类的通用形式,这些形式可以应用于多种数据类型。

模板允许开发者编写一次代码,然后用不同的类型实例化,从而生成具体的函数或类。

 

3. 函数模板

3.1 定义和语法

函数模板的定义使用关键字template,后跟模板参数列表,然后是函数声明或定义。模板参数列表通常包含一个或多个类型参数。

template<typename T1, typename T2,......,typename Tn>

返回值类型 函数名(参数列表){}
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

注意

typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

 

3.2 函数模板实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。

模板参数实例化分为:隐式实例化 和显式实例化。

 

3.3 隐式实例化

当调用函数模板时,编译器会根据传递的参数类型自动推导出模板参数的具体类型。

这种实例化,称为隐式实例化,如下方示例:

#include<iostream>

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 3, y = 4;
    double a = 1.1, b = 2.2;

    int maxInt = max(x, y);    // T 被推导为 int
    double maxDouble = max(a, b); // T 被推导为 double

    std::cout << "Max of " << x << " and " << y << " is " << maxInt << std::endl;
    std::cout << "Max of " << a << " and " << b << " is " << maxDouble << std::endl;

    return 0;
}

要注意的是:如果传递的参数不能让编译器正确推导出实例化的函数,就会报错

如下方示例:

#include<iostream>

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 3, y = 4;
    double a = 1.1, b = 2.2;

    int maxInt = max(x, a);    // 编译报错


    std::cout << "Max of " << x << " and " << y << " is " << maxInt << std::endl;

    return 0;
}

注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要 背黑锅

此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化

 int maxInt = max(x, (int)a);    //强制类型转换

 

3.4 显式实例化

显式指定模板参数的类型,可以使用尖括号<>来指定。

int main() {
    int x = 3, y = 4;
    double a = 1.1, b = 2.2;

    int maxInt = max<int>(x, y);    // 显式指定 T 为 int
    double maxDouble = max<double>(a, b); // 显式指定 T 为 double

    std::cout << "Max of " << x << " and " << y << " is " << maxInt << std::endl;
    std::cout << "Max of " << a << " and " << b << " is " << maxDouble << std::endl;

    return 0;
}

 

 3.5 模板函数的匹配规则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这 个非模板函数。对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。普通函数的优先级高于模板函数
  2. 如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
 // 专门处理int的加法函数

  int Add(int left, int right)
 {
      return left + right;
 }
  
  // 通用加法函数

  template<class T>

  T Add(T left, T right)
 {
      return left + right;
 }
  
  void Test()
 {
      Add(1, 2);       // 与非模板函数匹配,编译器不需要特化

      Add<int>(1, 2);  // 调用编译器特化的Add版本

 }

 

4. 类模板

4.1 定义和语法

类模板的定义类似于函数模板,使用关键字template,后跟模板参数列表,然后是类的定义。

template<class T1, class T2, ..., class Tn> 

class 类模板名

{
 // 类内成员定义

};    

 示例代码:

template <typename T>
class Stack {
private:
    T* items; // 数组存储元素
    int top;  // 栈顶索引
    int size; // 栈的最大容量
public:
    Stack(int sz) : size(sz), top(-1) {
        items = new T[size];
    }
    ~Stack() {
        delete[] items;
    }
    void push(const T& item);
    T pop();
    bool isEmpty() const;
};

 

4.2 成员函数的定义

类模板的成员函数可以在类内定义,也可以在类外定义。

注意:模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误

// 在类内定义
template <typename T>
class Stack {
public:
    void push(const T& item) {
        if (top < size - 1) {
            items[++top] = item;
        } else {
            std::cout << "Stack overflow" << std::endl;
        }
    }

    T pop() {
        if (top >= 0) {
            return items[top--];
        } else {
            std::cout << "Stack underflow" << std::endl;
            return T(); // 返回默认构造的对象
        }
    }

    bool isEmpty() const {
        return top == -1;
    }
};

// 在类外定义
template <typename T>
void Stack<T>::push(const T& item) {
    if (top < size - 1) {
        items[++top] = item;
    } else {
        std::cout << "Stack overflow" << std::endl;
    }
}

template <typename T>
T Stack<T>::pop() {
    if (top >= 0) {
        return items[top--];
    } else {
        std::cout << "Stack underflow" << std::endl;
        return T(); // 返回默认构造的对象
    }
}

template <typename T>
bool Stack<T>::isEmpty() const {
    return top == -1;
}

 

4.3 模板参数的默认值

模板参数可以有默认值,这样在实例化时可以省略某些参数。

template <typename T, int Size = 100>
class Stack {
private:
    T* items; // 数组存储元素
    int top;  // 栈顶索引
    int size; // 栈的最大容量
public:
    Stack() : size(Size), top(-1) {
        items = new T[size];
    }
    ~Stack() {
        delete[] items;
    }
    void push(const T& item);
    T pop();
    bool isEmpty() const;
};

注意:

模板参数可以是类型参数(如 typename T)或非类型参数(如 int N)。

非类型参数必须是编译时常量表达式,并且其类型通常是整型、枚举类型、指针类型或引用类型(指向对象的引用)

 

这里的 int Size = 100 实际上是一个非类型模板参数,它有一个默认值。这意味着当实例化这个模板类时,如果不提供第二个参数,Size 将默认为100。但是,请注意,这个参数是模板级别的,而不是对象级别的。也就是说,一旦模板被实例化(例如 Stack<int> 或 Stack<int, 200>),该实例的 Size 值就是固定的,不能在运行时改变

 

5. 模板的高级用法

5.1 模板特化

模板特化允许为特定的类型提供特殊的实现。

// 全特化
template <>
class Stack<char> {
private:
    char* items; // 数组存储元素
    int top;     // 栈顶索引
    int size;    // 栈的最大容量
public:
    Stack(int sz) : size(sz), top(-1) {
        items = new char[size];
    }
    ~Stack() {
        delete[] items;
    }
    void push(char item);
    char pop();
    bool isEmpty() const;
};

// 部分特化(仅限类模板)
template <typename T, int Size>
class Stack<T*, Size> {
private:
    T** items; // 数组存储元素
    int top;   // 栈顶索引
    int size;  // 栈的最大容量
public:
    Stack() : size(Size), top(-1) {
        items = new T*[size];
    }
    ~Stack() {
        delete[] items;
    }
    void push(T* item);
    T* pop();
    bool isEmpty() const;
};

5.2 模板模板参数

模板模板参数允许将一个模板作为另一个模板的参数。

template <template <typename, int> class Container, typename T, int Size>
class MyClass {
    Container<T, Size> container;
public:
    void add(const T& item) {
        container.push(item);
    }
    T remove() {
        return container.pop();
    }
};

 

 

6. 实战案例

6.1 函数模板示例

编写一个通用的数组排序函数。

#include <algorithm>

template <typename T>
void sortArray(T arr[], int size) {
    std::sort(arr, arr + size);
}

int main() {
    int intArr[] = {5, 2, 8, 1, 9};
    double doubleArr[] = {3.1, 2.2, 1.1, 5.5, 4.4};

    sortArray(intArr, 5);
    sortArray(doubleArr, 5);

    for (int i : intArr) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    for (double d : doubleArr) {
        std::cout << d << " ";
    }
    std::cout << std::endl;

    return 0;
}

6.2 类模板示例

编写一个通用的链表类。

template <typename T>
class LinkedList {
private:
    struct Node {
        T data;
        Node* next;
        Node(T val) : data(val), next(nullptr) {}
    };
    Node* head;
public:
    LinkedList() : head(nullptr) {}
    ~LinkedList() {
        clear();
    }
    void insert(T value);
    void remove(T value);
    bool contains(T value) const;
    void clear();
};

template <typename T>
void LinkedList<T>::insert(T value) {
    Node* newNode = new Node(value);
    if (!head) {
        head = newNode;
    } else {
        Node* current = head;
        while (current->next) {
            current = current->next;
        }
        current->next = newNode;
    }
}

template <typename T>
void LinkedList<T>::remove(T value) {
    Node* current = head;
    Node* previous = nullptr;
    while (current) {
        if (current->data == value) {
            if (previous) {
                previous->next = current->next;
            } else {
                head = current->next;
            }
            delete current;
            return;
        }
        previous = current;
        current = current->next;
    }
}

template <typename T>
bool LinkedList<T>::contains(T value) const {
    Node* current = head;
    while (current) {
        if (current->data == value) {
            return true;
        }
        current = current->next;
    }
    return false;
}

template <typename T>
void LinkedList<T>::clear() {
    Node* current = head;
    while (current) {
        Node* next = current->next;
        delete current;
        current = next;
    }
    head = nullptr;
}

int main() {
    LinkedList<int> list;
    list.insert(1);
    list.insert(2);
    list.insert(3);

    std::cout << "Contains 2: " << list.contains(2) << std::endl;
    list.remove(2);
    std::cout << "Contains 2: " << list.contains(2) << std::endl;

    return 0;
}

 

7. 常见问题与注意事项

  • 编译时间增加:复杂的模板可能会显著增加编译时间。
  • 错误信息复杂:模板错误可能导致难以理解的错误消息。
  • 代码膨胀:模板的过度使用可能会导致目标文件中出现大量重复的代码。

 

8. 结语

C++模板是实现泛型编程的重要手段,它不仅增强了代码的复用性和可维护性,还提高了程序的执行效率。通过本文的学习,希望读者能够掌握C++模板的基本概念和使用方法,并在实际编程中灵活运用。

 

9. 参考文献

  • Stroustrup, B. (2013). The C++ Programming Language. 4th Edition.
  • Josuttis, N. M. (2012). The C++ Standard Library: A Tutorial and Reference. 2nd Edition.
  • C++ Standard Library Documentation

希望这篇文章对你有所帮助,祝你在学习C++模板的过程中取得更大的进步!

 


http://www.kler.cn/a/501790.html

相关文章:

  • 不同音频振幅dBFS计算方法
  • 战略与规划方法——深入解析波士顿矩阵(BCG Matrix):分析产品组合的关键工具
  • 数据分析-使用Excel透视图/表分析禅道数据
  • C++语言的计算机基础
  • Elasticsearch学习(1) : 简介、索引库操作、文档操作、RestAPI、RestClient操作
  • Vue sm3国密 IE模式报错处理
  • 如何使用队列规则(Qdisc)发送数据包
  • Git | git reset命令详解
  • python安装完成后可以进行的后续步骤和注意事项
  • leetcode 2270. 分割数组的方案数 中等
  • 【WPS】【WORDEXCEL】【VB】实现微软WORD自动更正的效果
  • windows wsl ubuntu22 远程桌面连接
  • QT跨平台应用程序开发框架(1)—— 环境搭建
  • Redis集群的键分布机制
  • Y3编辑器地图教程:ORPG教程、防守图教程
  • 扩散模型学习
  • Arthas监控方法内部调用路径,并输出方法路径上的每个节点上耗时
  • 计算机网络之---端口与套接字
  • 机器学习之K-mean算法理解及实现
  • Java中的反射机制及其应用场景
  • Day05-后端Web基础——TomcatServletHTTP协议SpringBootWeb入门
  • YOLOv8从菜鸟到精通(二):YOLOv8数据标注以及模型训练
  • CentOS7下Hadoop集群分布式安装详细图文教程
  • [Qt]常用控件介绍-按钮类控件
  • 【论文阅读】Workload Dependent Performance Evaluation of the Linux 2.6 I/O Schedulers
  • MySQL--2.1MySQL的六种日志文件