C++模板初阶

简介: C++模板初阶

泛型编程

在一个项目中,我们可能需要交换不同类型的数据。虽然C++支持函数重载,解决了C语言中函数名不能相同的问题,但是代码复用率任然极低

void Swap(int& ra, int& rb)
{
  int tmp = ra;
  ra = rb;
  rb = tmp;
}
void Swap(double& c, double& d)
{
  double tmp = c;
  c = d;
  d = tmp;
}
int main()
{
  int a = 10, b = 20;
  Swap(a, b);
  cout << a << " " << b << endl;
  //但是如果我们要交换浮点数类型,就要重新写一个函数
  double c = 12.1, d = 13.2;
  Swap(c, d);
  return 0;
}

为了提高编写效率,C++引入了一个叫做泛型编程的概念,所谓泛型编程就是编写与类型无关的通用代码,模板是泛型编程的基础。

函数模板

1.函数模板的使用

函数模板与普通函数编写几乎没有很大的区别,只是用一个泛型来代表函数的类型,一个函数模板代表的是一个函数家族,不受类型限制

template<typename T>//这里的typename可以使用class来代替,这个T也可以用其他字符来代替
//  不过typename的可读性会有一点提高,因为typename本身就是类型名称的缩写
void Swap(T& left, T& right)
{
  T tmp = left;
  left = right;
  right = tmp;
}
int main()
{
  int a = 10;
  int b = 20;
  Swap(a, b);
  cout << a << " " << b << endl;
    double c=10.2,d=13.3;
    Swap(c,d);
    cout<<c<<" "<<d<<endl;
  return 0;
}

那么这里有个问题,交换整形和浮点型的函数是从哪来,是调用的模板吗?

地址不同也就是表明它们调用的不是同一个函数,所以说它们并不是通过调用函数模板来解决问题的,而是调用的函数模板根据传参的类型经由编译器推演以后实例化出来的函数

2.不同类型的传参处理

1.强制类型转换

既然函数模板是编译器根据我所传的参数自动推演而来,那么一个函数模板是否可以处理两个不同类型的参数呢?以如下代码为例:

template<typename T>
T Add(T& left, T& rigth)
{
  return left + right;
}
int main()
{
  int a = 10, b = 20;
  cout<<Add(a, b)<<endl;
  double c = 10.0;
  cout << Add(a, (int)c) << endl;
  return 0;
}

为啥我在这里强制类型转换(double转到int)也不行?这里可以参考前面说过的隐式类型转换,在强制类型转换的过程中,中间产生了一个临时变量,这个临时变量具有常性,而上面所写的Swap函数参数并没有加const,也就是说有权限放大的风险(只有指针和引用才会涉及到权限) 。所以只要对参数加上const就可以使这段代码成功跑过:

2.显示实例化

除了强制类型转换以外,还可以在传参时对模板的参数显示实例化明确的告诉编译器应当产生什么类型的函数,这个时候如果传参是两个不同类型,就会发生隐式类型转换,隐式类型转换,转换的过程中会产生一个临时变量,而这个临时变量具有常性,所以代码也要加const修饰

template<typename T>
T Add(const T&left,const T&right)
{
  return left + right;
}
int main()
{
  int a = 10, b = 20;
  double c = 10.0;
  cout << Add<int>(a, c) << endl;
  cout << Add<double>(c, b) << endl;
  return 0;
}

3.多参数模板

上述的模板中,只使用了一个模板参数,所以也就是一个函数模板只能同时对一个类型进行推演,但是如果在函数模板中使用多个参数,自然就可以同时对不同的类型进行推演:

template <typename T1,typename T2>
T1 Add(T1& left, T2& right)
{
  return left + right;
}
int main()
{
  int a = 10, b = 20;
  double c = 10.2, d = 20.3;
  cout << Add(a, b) << endl;
  cout<<Add(a, c)<<endl;
  return 0;
}

可以看到当我在Add函数使用两个模板参数时对于不同类型参数的传参不用我做任何处理,编译器有足够的泛型参数对两个不同的类型进行推演,不过返回值还是只能是两个类型中的一个。

3.模板可以和实例函数同时存在,编译器优先调用实例函数

template <typename T1,typename T2>
T1 Add(T1& left, T2& right)
{
  return left + right;
}
int Add(int left, int right)
{
  return left + right;
}
int main()
{
  int a = 10, b = 20;
  double c = 10.2, d = 20.3;
  cout << Add(a, b) << endl;
  cout<<Add(a, c)<<endl;
  return 0;
}

1.可以看到模板函数和实例函数同时存在编译器并不会报错,这是因为模板函数和实例函数的函数名修饰规则不同


2.模板函数和实例函数同时存在时,编译器优先调用实例函数


3.如果模板可以生成更匹配的函数,则选择模板函数

可以看到这里因为两个参数的类型不同,所以编译器选择了模板函数。


类模板

在之前我们写一个类就只能实例化出一个类型的类,尽管可以通过typedef来获得一些便利,但是当我们同时需要多个类型的类时,就会存在大量的重复代码,为了解决这个问题,类模板应运而生。

template<typename T>
class Stack
{
public:
  Stack(int capacity = 4)
    :_capacity(capacity)
    , _top(0)
  {
    _a = (T*)malloc(sizeof(T) * _capacity);
    if (_a == NULL)
    {
      perror("malloc fail\n");
      exit(-1);
    }
  }
  ~Stack()
  {
    free(_a);
    _a = NULL;
    _top = _capacity = 0;
  }
  void Push(T x)
  {
    _a[_top++] = x;
  }
private:
  T* _a;
  int _top;
  int _capacity;
};//当然这个栈并不完善但是在这里讲解也足够使用

1.类模板需要显示实例化

int main()
{
  Stack<int> st;
  st.Push(1);
  Stack<double> stt;
  st.Push(2.3);
}

对于函数模板编译器可以根据传参来自动推演类型,但类模板没有推演时机所以必须要我们显示实例化。

模板使用参数不同,对于类而言就是不同的类型,也就是说st!=stt。

2.类模板不能声明定义分离

类模板如果定义和声明分离就会出现链接错误:因为类模板没有推演时机必须要我们显示实例化,如果将定义和声明分离就会出现在定义的地方没有实例化,而在使用的地方虽然有实例化但是没有定义。

总之就是我在Test.cpp文件中实例化了该模板并调用,但是向上查找却未找到定义,因此就发生了链接错误。

解决办法:

1.实例化的地方没有定义我们不能增加定义否则代码冗余,那就让定义的地方实例化:

template class Stack<int>//显示实例化为整形,可以放在任意位置

2.不将声明和定义分离,全部放在.h文件中(因为该文件中含有定义,所以有些人又将该文件叫.hpp)

非类型模板参数

C语言通过宏来定义数组大小已经是最方便的静态数组了,尽管如此在我们同时需要多个数组时它们的大小和类型都是一样的,但C++可以通过类型参数和非类型参数联合来达到获得不同类型和大小的数组。

template<typename T,size_t N> //T是类型参数,N是非类型参数
class Array
{
private:
  T _Array[N];
};
int main()
{
  Array<int, 10> arr1;
  Array<double, 20> arr2;
  return 0;
}

不过非类型模板参数只支持整型常量,浮点型、变量、类对象等都不行。

相关文章
|
6月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
10月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
306 0
|
10月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
261 0
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
编译器 C++
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
㉿㉿㉿c++模板的初阶(通俗易懂简化版)㉿㉿㉿
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
224 0
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
492 4
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
258 3
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
224 0