【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想

简介: 【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想

🧩<<仿函数>>

📕1. 仿函数的概念

概念: 仿函数(functor)是一个编程术语,其核心概念是指通过实现一个特定的类,使得这个类的使用看上去像一个函数

具体来说,仿函数是一个类或结构体,它重载了operator()运算符,从而使得这
个类的对象可以像函数一样被调用


📚2. 仿函数的用途

在我们当前学习的所有知识中,仿函数的用途貌似只涉及到了在STL中的使用,比如我们刚刚了解过的std::priority_queue,还是之前了解过的std::sort可以通过传递仿函数作为参数来指定自定义的比较、排序、映射等操作。这使得STL算法更加灵活和可重用

优先级队列中的这个排序的操作其实就是通过仿函数比较出来的


仿函数在priority_queue模拟实现中的使用代码示例

建议结合上一期内容阅读

// 仿函数的定义
template<class T>
class Less
{
public:
  // 重载operator()
  bool operator()(const T& x, const T& y)
  {
    return x < y;
  }
};

template<class T>
class Greater
{
public:
  // 重载operator()
  bool operator()(const T& x, const T& y)
  {
    return x > y;
  }
};

// priority_queue的模板参数 -> Compare就代表调用的仿函数
template<class T, class Container = vector<T>, class Compare = Less<T>>

注意:在库里面less表示升序,greater则表示降序,默认情况下使用的时less升序

Sort算法中仿函数的使用代码示例

int main()
{
  vector<int> v = { 7,4,1,2,8,9,4,5 };
  sort(v.begin(), v.end());
  cout << "less: ";
  for (auto e : v)
  {
    cout <<  e << " ";
  }
  cout << endl;
  cout << "greater: ";
  sort(v.begin(), v.end(),greater<int>());
  for (auto e : v)
  {
    cout <<  e << " ";
  }
  cout << endl;
  return 0;
}


仿函数的单独使用你可以就把他想象成一个函数

template<class T>
class Less
{
public:
  bool operator()(const T& x, const T& y)
  {
    return x < y;
  }
};
int main()
{
  Less<int> L;
  cout << L(1, 6) << endl;;
  return 0;
}

总结来说,仿函数是一种强大的编程工具,它允许开发者将功能封装在类中,并通过重载operator()运算符来使这些类的对象具有类似函数的行为。这种灵活性使得仿函数在代码复用、状态保存以及STL算法中使用等方面具有广泛的应用价值


🧩<<模板>>

📕1. 非类型模板参数

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

  • 类型形参:即出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
  • 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

我们在之前学的所有模板用的基本上都是类型形参,也就是你传什么,他就用什么,而非类型形参则是相当于固定了一个模板参数的类型

类型形参

template<class T, class T>
// ... 其他待添加内容 ...

非类型形参

template<typename T, size_t N = 10>  
class Array {  
public:  
    T data[N];  
    // ... 其他成员函数 ...  
};  
  
int main() {  
    Array<int, 10> arr; // 创建一个大小为10的整数数组  
    // ... 使用arr ...  
    return 0;  
}

注意:

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

📚2. 模板的特化

概念: 模板的特化(Template Specialization):在C++中是一种技术,它允许我们为模板的特定类型或值提供定制化的实现。这种技术对于满足特定需求或提高性能非常有用

模板特化主要可以分为两种类型:类模板特化 ,函数模板特化


⭐函数模板特化

函数模板的特化步骤:

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 基础的函数模板 -> 函数模板,参数匹配
template<class T>
bool Less(const T& x, const T& y)
{
  return x < y;
}
// 函数模板特化 -> 对Less函数模板进行特化
template<>
bool Less<double>(const double& x, const double& y)
{
  return x < y;
}
int main()
{
  cout << Less(1.1, 6.6) << endl; // 调用特化之后的版本,而不走模板生成了
  cout << Less(1, 6) << endl;
  return 0;
}

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


⭐类模板特化

类模板特化(Class Template Specialization)是C++模板编程中的一种机制,它允许我们为类模板的特定类型或类型组合提供专门的定义。在默认情况下,类模板会为所有类型提供通用的实现,但有时候,我们可能希望对某些特定的类型提供不同的实现。这时,就可以使用类模板特化来实现


🌞全特化

全特化是针对类模板的所有模板参数提供专门的定义。全特化的语法与类模板的定义类似,但是需要在尖括号中指定具体的类型

类模板全特化代码示例

template<class T>
class pxt 
{
public:
    void print() 
    {
        cout << "print()" << endl;
    }
};

// 全特化版本,针对int类型  
template<>
class pxt <int>
{
public:
    void print() 
    {
        cout << "print<int>()" << endl;
    }
};

int main() {
    pxt<int> P;
    P.print(); // 输出 "print<int>()"  

    pxt<double> T;
    T.print(); // 输出 "print()"  
    return 0;
}

🌙偏特化

偏特化允许我们对类模板的部分模板参数提供专门的定义。这意味着我们可以为模板参数列表中的一部分参数指定具体的类型,而让其他参数保持通用

template<class T1, class T2>
class pxt
{
public:
    void print()
    {
        cout << "print<T1, T2>()" << endl;
    }
};

// 偏特化版本,针对T2为int的情况  
template<class T1>
class pxt <T1, int>
{
public:
    void print()
    {
        cout << "print<T1, int>()" << endl;
    }
};

int main() {
    pxt<double, int> P;
    P.print(); // 输出 "print<T1, int>()" 

    pxt<double, double> T;
    T.print(); // 输出 "print<T1, T2>()"
    return 0;
}

偏特化能将参数类型特化成不同的类型

// 两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>

// 两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>

注意:

  • 类模板特化不能增加新的成员变量,只能对成员函数进行特化
  • 特化的优先级高于通用模板。当存在多个可用的特化版本时,编译器会选择最匹配的特化版本
  • 在编写类模板特化时,要特别注意避免名称冲突和歧义
  • 类模板特化在编译器进行类型推导和实例化时会被考虑,因此它们应该被定义在模板定义所在的同一命名空间内(或者在模板定义之前的某个地方)

📜3. 模板分离编译

概念: 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式


🍂模板的分离编译

// a.h
template<class T>
T Add(const T& left, const T& right); // 声明

// a.cpp
// 定义
template<class T>
T Add(const T& left, const T& right)
{
  return left + right;
}
// 显示实例化(不推荐)
//template
//int Add(const int& left, const int& right);

// main.cpp
#include"a.h"
int main()
{
  Add(1, 2);
  Add(1.0, 2.0);
  return 0;
}


当程序在编译链接时,编译器找到函数模板地址的,这两个函数当时并没有实例化,所以会导致链接时报错


🍁解决方法

如果遇到模板分离编译相关的问题,常见的解决方法有两种:

  • 将声明和定义放到一个文件(如“xxx.hpp”或“xxx.h”)里面。这是推荐的方法,因为它可以避免分离编译带来的潜在问题
  • 在模板定义位置显式实例化。这种方法不实用,通常不推荐使用,因为它可能导致不必要的代码冗余和编译时间增加。

📒4. 模板总结

🔥【优点】

  • 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  • 增强了代码的灵活性

💧【缺陷】

  • 模板会导致代码膨胀问题,也会导致编译时间变长
  • 出现模板编译错误时,错误信息非常凌乱,不易定位错误

📖5. 总结

当我们在编程的旅途中深入探索C++的模板和仿函数(Function Objects,也称为函数对象或仿函数对象)时,我们不禁被它们强大的灵活性和表达能力所震撼。模板和仿函数是C++标准库和许多现代编程范式中不可或缺的一部分,它们为我们提供了编写可重用、类型安全且易于维护的代码的强大工具

  • 通过模板,我们可以编写出与类型无关的代码,使得代码更加通用和灵活。无论是容器类、算法还是其他高级抽象,模板都扮演着核心角色。模板进阶的学习不仅仅是理解如何编写模板代码,更重要的是理解如何设计出能够优雅地处理各种类型的模板结构和算法
  • 而仿函数则为我们提供了一种以对象方式表示和操作函数行为的途径。通过重载operator(),我们可以将函数行为封装在类中,从而可以像操作普通对象一样操作函数。这种能力让我们能够在算法和数据结构中更加灵活地运用函数,同时也为我们提供了更多的控制和定制选项

最后我鼓励大家保持对模板和仿函数的学习热情,不断探索和实践它们的强大功能。通过不断的学习和实践,我们不仅能够提升自己的编程技能,还能够为C++社区的发展贡献自己的力量。让我们一起在模板和仿函数的道路上不断前行,探索编程的无限可能!

谢谢大家支持本篇到这里就结束了,祝大家天天开心!


目录
相关文章
|
2月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
67 4
|
2月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
37 3
|
3月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
400 13
|
2月前
|
消息中间件 存储 安全
|
2月前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
35 0
|
3月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
24 1
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
66 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
118 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
123 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
161 4