C++ 函数模板与类模板知识点总结

ops/2024/10/20 16:46:29/

一、基础概念

1.1 什么是模板

模板是 C++ 的一个特性,使得你可以编写与类型无关的代码。模板通过定义类型参数,使得同一段代码可以适用于多种数据类型。

二、函数模板

2.1 定义与语法
  • 基本定义

  • template <typename T>
    T functionName(T arg) {
        // 函数体
    }

  • 示例代码

  • #include <iostream>
    using namespace std;

    // 定义一个函数模板用于加法
    template <typename T>
    T add(T a, T b) {
        return a + b; // 返回两数之和
    }

    int main() {
        cout << add(5, 10) << endl;          // T 被推导为 int
        cout << add(5.5, 10.5) << endl;      // T 被推导为 double
        return 0;
    }

2.2 使用场景与示例
2.2.1 数学运算

函数模板适合处理不同数值类型的加法、乘法等操作。

// 数学运算示例
template <typename T>
T multiply(T a, T b) {
    return a * b; // 返回两数之积
}

int main() {
    cout << multiply(3, 4) << endl;         // 输出: 12
    cout << multiply(2.5, 4.0) << endl;     // 输出: 10.0
    return 0;
}

2.2.2 排序算法

可以实现一个通用的排序函数,适用于任何可比较的类型。

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

// 排序函数模板
template <typename T>
void sortArray(vector<T>& arr) {
    sort(arr.begin(), arr.end()); // 使用 STL 的 sort 函数
}

int main() {
    vector<int> intArr = {3, 1, 4, 1, 5};
    sortArray(intArr);
    for (const auto& num : intArr) {
        cout << num << " "; // 输出: 1 1 3 4 5
    }
    cout << endl;

    vector<string> strArr = {"banana", "apple", "cherry"};
    sortArray(strArr);
    for (const auto& str : strArr) {
        cout << str << " "; // 输出: apple banana cherry
    }
    return 0;
}

2.2.3 容器操作

自定义容器类以处理任意数据类型。

#include <iostream>
#include <vector>
using namespace std;

// 自定义容器类模板
template <typename T>
class MyContainer {
public:
    void add(const T& item) {
        items.push_back(item); // 添加元素
    }

    void display() const {
        for (const auto& item : items) {
            cout << item << " "; // 输出所有元素
        }
        cout << endl;
    }

private:
    vector<T> items; // 内部存储
};

int main() {
    MyContainer<int> intContainer;
    intContainer.add(1);
    intContainer.add(2);
    intContainer.display(); // 输出: 1 2

    MyContainer<string> strContainer;
    strContainer.add("Hello");
    strContainer.add("World");
    strContainer.display(); // 输出: Hello World

    return 0;
}

2.3 错误处理
  • 类型不匹配
  • // 如果传入不支持的类型,将导致编译错误
    cout << add("Hello", "World"); // 错误:不支持字符串相加
  • 未定义行为
  • template <typename T>
    T divide(T a, T b) {
        return a / b; // 如果 b 为 0,将导致运行时错误
    }
     
2.4 特化
  • 完全特化
  • template <>
    double add<double>(double a, double b) {
        // 针对 double 类型的特化实现
        return a + b; // 可增加特定的逻辑
    }
  • 偏特化
  • template <typename T>
    class MyClass<T*> { // 针对指针类型的特化
    public:
        void print() {
            cout << "This is a pointer type." << endl;
        }
    };
2.5 常见问题
  1. 模板参数不明确

// 当存在多重重载时,编译器可能无法推断参数类型
template <typename T>
void func(T a, T b);

func(1, 2); // 如果同时存在 func(int, float) 会导致编译错误

复杂的错误信息

  • 编译器给出的模板错误信息常常难以理解,特别是当模板嵌套时。

三、类模板

3.1 定义与语法
  • 基本定义
  • template <typename T>
    class ClassName {
        // 成员变量和成员函数
    };
  • #include <iostream>
    using namespace std;

    // 类模板定义,包含一个类型为 T 的成员变量
    template <typename T>
    class Box {
    public:
        Box(T val) : value(val) {} // 构造函数
        T getValue() const { return value; } // 返回值的成员函数

    private:
        T value; // 成员变量
    };

    int main() {
        Box<int> intBox(123); // 使用 int 类型
        Box<string> strBox("Hello"); // 使用 string 类型

        cout << intBox.getValue() << endl; // 输出: 123
        cout << strBox.getValue() << endl; // 输出: Hello
        return 0;
    }

3.2 使用场景与示例
3.2.1 自定义数据结构

适合创建可以处理不同类型的自定义数据结构,如栈、队列。

#include <iostream>
#include <stack>
using namespace std;

// 自定义栈类模板
template <typename T>
class MyStack {
public:
    void push(const T& item) {
        s.push(item);
    }

    void pop() {
        if (!s.empty()) {
            s.pop();
        }
    }

    T top() const {
        return s.top();
    }

private:
    stack<T> s; // 内部使用标准栈
};

int main() {
    MyStack<int> intStack;
    intStack.push(1);
    intStack.push(2);
    cout << intStack.top() << endl; // 输出: 2
    intStack.pop();

    MyStack<string> strStack;
    strStack.push("Hello");
    strStack.push("World");
    cout << strStack.top() << endl; // 输出: World

    return 0;
}

3.3 错误处理
  • 类型不匹配
  • // 使用不支持的类型将导致编译错误
    MyStack<double> doubleStack;
    doubleStack.push("Hello"); // 错误:类型不匹配
3.4 特化
  • 类特化
  • template <>
    class Box<int> { // 针对 int 类型的特化
    public:
        void print() {
            cout << "This is an integer box." << endl;
        }
    };
3.5 常见问题
  1. 友元声明

template <typename T>
class MyClass;

template <typename T>
class FriendClass {
    friend class MyClass<T>; // 友元模板
};
    2.继承问题

template <typename T>
class Base {
    // 基类模板
};

template <typename T>
class Derived : public Base<T> {
    // 派生类模板
};

四、进阶知识

4.1 可变参数模板
  • 支持接受不定数量的参数。
  • #include <iostream>
    using namespace std;

    // 可变参数模板
    template <typename... Args>
    void print(Args... args) {
        (cout << ... << args) << endl; // 使用折叠表达式输出参数
    }

    int main() {
        print(1, 2, 3.5, "Hello"); // 输出: 123.5Hello
        return 0;
    }

4.2 模板元编程
  • 使用模板进行编译时计算。
  • #include <iostream>
    using namespace std;

    // 计算阶乘的模板
    template <int N>
    struct Factorial {
        static const int value = N * Factorial<N - 1>::value; // 递归计算
    };

    template <>
    struct Factorial<0> { // 基础情况
        static const int value = 1;
    };

    int main() {
        cout << "Factorial of 5: " << Factorial<5>::value << endl; // 输出: 120
        return 0;
    }

4.3 C++20 新特性:概念
  • 用于约束模板参数类型,提高代码可读性和安全性
  • #include <iostream>
    #include <concepts>
    using namespace std;

    // 定义一个概念,要求类型 T 必须支持加法
    template <typename T>
    concept Addable = requires(T a, T b) {
        { a + b };
    };

    // 使用概念限制模板参数
    template <Addable T>
    T add(T a, T b) {
        return a + b; // 只允许支持加法的类型
    }

    int main() {
        cout << add(5, 10) << endl; // 输出: 15
        return 0;
    }

五、最佳实践与常见陷阱

  1. 避免过度特化

    • 过度使用特化会使代码更复杂,增加维护难度。
    • 示例:// 不建议为每个类型都特化
      template <>
      class Box<double> {
          // 不必要的特化
      };
  2. 合理使用 decltypeauto:使用 decltype 可以根据表达式自动推导类型。

template <typename T>
void func(T arg) {
    decltype(arg) newArg = arg; // 根据 arg 类型推导 newArg
}

 使用类型特征

  • 在模板中使用类型特征可以增加代码的类型安全性。
  • 示例:#include <type_traits>
    template <typename T>
    void func(T arg) {
        static_assert(std::is_integral<T>::value, "Type must be integral"); // 仅支持整型
    }
  • 简化模板参数

    • 复杂的模板参数会导致代码可读性下降,尽量使用简单的类型或自定义类型。
  • 调试复杂模板

    • 使用 static_assert 捕捉错误。
    • 示例:template <typename T>
      class MyClass {
      public:
          static_assert(std::is_integral<T>::value, "T must be an integral type"); // 仅支持整型
      };

六.补充知识

特化与偏特化

1. 特化(Full Specialization)

定义: 特化是指为特定类型提供完全不同的实现。当你为一个特定类型定义一个模板的特殊版本时,称为特化。

理解: 假设你有一个模板函数或类,它可以处理多种类型,但你希望为特定的类型(如 intdouble)提供特定的实现,以优化性能或改变逻辑。

#include <iostream>
using namespace std;

// 基本函数模板
template <typename T>
T add(T a, T b) {
    return a + b; // 通用实现
}

// 完全特化:针对 int 类型的特化
template <>
int add<int>(int a, int b) {
    cout << "Using specialized add for int" << endl;
    return a + b; // 特化实现
}

int main() {
    cout << add(5, 10) << endl; // 使用特化,输出: Using specialized add for int
    cout << add(2.5, 3.5) << endl; // 使用通用实现,输出: 6
    return 0;
}

2. 偏特化(Partial Specialization)

定义: 偏特化是指对模板的部分参数进行特化,而不必提供所有参数的具体类型。它通常用于模板类。

理解: 偏特化可以让你为一组类型提供特殊的处理,而不是针对单一类型。这种特化可以帮助你处理更具体的情况。

示例

#include <iostream>
using namespace std;

// 基本类模板
template <typename T>
class MyClass {
public:
    void show() {
        cout << "Generic MyClass" << endl;
    }
};

// 偏特化:当类型为指针时
template <typename T>
class MyClass<T*> {
public:
    void show() {
        cout << "MyClass specialized for pointers" << endl;
    }
};

int main() {
    MyClass<int> obj1; // 使用通用实现
    obj1.show(); // 输出: Generic MyClass

    MyClass<int*> obj2; // 使用偏特化
    obj2.show(); // 输出: MyClass specialized for pointers

    return 0;
}

3. 使用场景
  • 特化
    • 当你需要对某些类型有不同的处理逻辑时,例如当 intdouble 处理方式不同。
  • 偏特化
    • 当你想要为某一类类型(如所有指针类型)提供特定实现时,可以使用偏特化,而无需为每个具体类型实现一遍。
4. 总结
  • 特化是为单一特定类型定义的实现,通常用于优化和改变逻辑。
  • 偏特化是为某一类型的子集定义的实现,允许更灵活的处理多种类型。

http://www.ppmy.cn/ops/121889.html

相关文章

【AI自然语言处理应用】通过API调用通义晓蜜CCAI-对话分析AIO应用

通义晓蜜CCAI-对话分析AIO 对话分析AIO&#xff0c;即对话分析all-in-one API&#xff0c;是基于深度调优的对话大模型&#xff0c; 为营销服类产品提供智能化升级所需的生成式摘要总结、质检、分析等能力的官方应用。 面向对象&#xff1a;开发者、自研企业、传统呼叫中心采购…

开发指南067-单元测试

平台中单元测试使用两个工具&#xff1a; 1、接口类&#xff1a;使用swagger。 swagger前面介绍已经很多了&#xff0c;不再累述。注意下token的设置即可&#xff0c;否则会报未登录&#xff0c;无法调用该接口。当然也可以修改代码&#xff0c;屏蔽校验。但是屏蔽校验无法获取…

什么是数据挖掘?初学者指南

引言 在信息时代的今天&#xff0c;我们生活在一个数据爆炸的世界中。从社交媒体的帖子到在线购物记 录&#xff0c;从医疗健康数据到金融交易信息&#xff0c;数据无处不在。这些数据的增长速度之快&#xff0c;使得从数据中提 取有价值信息的需求变得日益迫切。 数据挖掘&a…

【黑马点评】2 商户查询缓存

【黑马点评】2 商户查询缓存 2 商户查询缓存2.1 添加商户缓存2.1.1 添加商户信息缓存 --修改ShopController中的queryShopById方法2.1.2 添加商户类别缓存&#xff08;作业&#xff09;--修改ShopController中的queryTypeList方法 2.2 缓存更新策略2.2.1 数据库缓存不一致解决方…

ubuntu2204操作系统使用可执行文件方式安装docker-compose记录

文章目录 前言一、版本信息二、操作步骤2.1 确认版本2.2 下载部署2.官网参考3.docker-compose版本 总结 前言 记录一下在ubuntu操作系统上使用下载可执行文件方式部署docker-compose的记录。 一、版本信息 操作系统版本&#xff1a; docker-compose版本&#xff1a; 备注&…

Oracle 数据库安装及配置

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

0/1背包与完全背包差异分析

文章目录 定义差异分析代码实现分析总结 定义差异分析 0/1背包问题&#xff1a;有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。完全背包问题&am…

记一次控件提升后,运行却不显示的Bug

.h文件 #ifndef VOLUMETOOLBTN_H #define VOLUMETOOLBTN_H#include <QToolButton> #include <memory>class VolumeToolBtn : public QToolButton { Q_OBJECTpublic:explicit VolumeToolBtn(QWidget *parent nullptr);~VolumeToolBtn() override;void initUi(); p…