C++——模板初阶与泛型编程

简介: C++——模板初阶与泛型编程

0000000000000000000000000000000000000000000000000000000.png

目录


引例

函数模板

函数模板的概念

函数模板的格式

函数模板的原理

函数模板的实例化

隐式实例化

显式实例化

一个疑问

模板参数的匹配原则

类模板

类模板的定义格式

类模板的实例化


文章导读


本章我们将学习模板,与泛型编程初次见面。C++相比于C语言有这么多丰富的接口与类型,都源于泛型编程。本章的内容为模板初阶知识,为接下来的STL学习打下坚实的基础。


正文


引例


如何实现一个通用的交换函数(swap)呢?我们已经学过函数重载的使用,函数重载在这种场景下貌似作用很大。

void Swap(int& a, int& b)
{
  auto tmp = a;
  a = b;
  b = tmp;
}
void Swap(double& a, double& b)
{
  auto tmp = a;
  a = b;
  b = tmp;
}
void Swap(char& a, char& b)
{
  auto tmp = a;
  a = b;
  b = tmp;
}
//...

虽然函数重载可以实现,但是有点吃力。最糟心的事儿是明明这些函数几乎都长一个样子,仅仅是参数类型不同而已,但是每多一个类型就得写一个对应的重载函数。


那么有什么办法能做到一劳永逸呢?能否做到告诉编译器:我给你一个模板,你来帮我实现每个函数,只是换一下参数类型就OK了?


这就是我们今天要讲到的模板——模板是泛型编程的基础。模板不仅适用于函数,也适用于类。


函数模板


函数模板的概念


函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。


函数模板的格式


template<typename T1, typename T2,......,typename TN>
返回值类型 函数名(参数列表)
{
  //...函数体
}

🌼示例1

template<typename T>
void Swap(T& a,T& b)
{
  auto tmp = a;
  a = b;
  b = tmp;
}

🍁注意事项

  1. typename后面的内容不一定是T,可自由指定;
  2. typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class);

🌼示例2

template<class N>
void Swap(N& a, N& b)
{
  auto tmp = a;
  a = b;
  b = tmp;
}


函数模板的原理


函数模板是一个描述的过程一幅设计图一个模板,并不是真正的函数。编译器会根据模板使用特定方式产生具体类型函数。所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做(想想印刷术的原理)。

5.png

  • 在编译器编译阶段,编译器需要根据传入的实参类型推演生成对应类型的函数以供调用。

比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。


函数模板的实例化


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


隐式实例化


编译器根据实参推演模板参数的实际类型。

🌼示例1

template<class T>
T Add(T a, T b)
{
  return  a + b;
}
void Test()
{
  int a = 10;
  int b = 100;
  cout << Add(a, b) << endl;
}

6.png


显式实例化


在函数名后的<>中指定模板参数的实际类型

🌼错误示例

template<class T>
T Add(T a, T b)
{
  return  a + b;
}
void Test()
{
  int a = 10;
  double d = 3.14;
  cout << Add(a, d) << endl;
}

🍁错误原因

因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a将T推演为int,通过实参d将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。


在模板中,编译器一般不会进行类型转换操作。

7.png

🌼正确的做法1

显式实例化:就是在告诉编译器:不用你推演了,我已经指定这个类型了。

template<class T>
T Add(T a, T b)
{
  return  a + b;
}
void Test()
{
  int a = 10;
  double d = 3.14;
  cout << Add<int>(a, d) << endl;
}

🌼正确的做法2

使用多个模板参数

template<class T,class Y>
Y Add(T a, Y b)
{
  return  a + b;
}
void Test()
{
  int a = 10;
  double d = 3.14;
  cout << Add<int>(a, d) << endl;
}

8.png


一个疑问


很多小伙伴初次理解模板都会有一个疑问——请问如下情况中,两次调用的swap函数是一个函数吗?或者说,两次调用的函数在同一个地址空间吗?

template<class N>
void Swap(N& a, N& b)
{
  auto tmp = a;
  a = b;
  b = tmp;
}
void Test()
{
  int a = 10;
  int b = 100;
  Swap(a,b);
  char c1 = 'a';
  char c2 = 'b';
  Swap(c1, c2);
}


我们不妨先来看看汇编代码吧。如下图所示,很显然两次调用的函数并不是一个函数(一个类型为Swap< int >,一个为Swap< char >)。事实上,虽然我们看不见这两份函数的代码,但是它们实实在在的产生了。

9.png


模板参数的匹配原则


  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

🌼示例1

template<class T>
T Add(T a, T b)
{
  return  a + b;
}
int Add(int a, int b)
{
  return a + b;
}
void Test()
{
  int a = 10;
  int b = 100;
  cout << Add<int>(a, b) << endl;
}


对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。

🌼示例2


template<class T>
T Add(T a, T b)
{
  cout << "模板" << endl;
  return  a + b;
}
int Add(int a, int b)
{
  cout << "非模板" << endl;
  return a + b;
}
void Test()
{
  int a = 10;
  int b = 100;
  double d = 3.14;
  cout << Add(a, b) << endl;//使用非模板函数
  cout << Add(a, d) << endl;//使用模板
}

10.png

  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

类模板


同样的,对于也有同样的套路。


类模板的定义格式


template<class T1, class T2, ..., class Tn>
class 类模板名
{
  // 类内成员定义
};

🌼示例

template<class T>
class Vector
{
public:
  //...
private:
  T* _a;
  size_t _size;
  size_t _capacity;
};


类模板的实例化


类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

🌼示例

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2
目录
打赏
0
0
0
0
1
分享
相关文章
4步实现C++插件化编程,轻松实现功能定制与扩展(2)
本文是《4步实现C++插件化编程》的延伸,重点介绍了新增的插件“热拔插”功能。通过`inotify`接口监控指定路径下的文件变动,结合`epoll`实现非阻塞监听,动态加载或卸载插件。核心设计包括`SprDirWatch`工具类封装`inotify`,以及`PluginManager`管理插件生命周期。验证部分展示了插件加载与卸载的日志及模块状态,确保功能稳定可靠。优化过程中解决了动态链接库句柄泄露问题,强调了采纳用户建议的重要性。
33 5
4步实现C++插件化编程,轻松实现功能定制与扩展(2)
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
2月前
|
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
34 0
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
45 16
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等