【C++初阶】C++模板编程入门:探索泛型编程的奥秘

简介: 【C++初阶】C++模板编程入门:探索泛型编程的奥秘

前言:在C++编程世界中,模板是一个强大的工具,它使得程序员能够编写更加通用、灵活和可重用的代码。通过模板,我们可以编写与类型无关的代码,这些代码可以在编译时根据所需的具体类型进行实例化。本文将带你走进C++模板的初阶世界,探索泛型编程的基石

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


📒1. 为什么需要模板?

我们来看看之前如何实现一个交换函数

// 函数重载
void Swap(int& left, int& right)
{
   int temp = left;
   left = right;
   right = temp;
}

void Swap(double& left, double& right)
{
   double temp = left;
   left = right;
   right = temp;
}

void Swap(char& left, char& right)
{
   char temp = left;
   left = right;
   right = temp;
}
int main()
{
  int a = 1, b = 9;
  double c = 1.1, d = 8.9;
  char e = 'a', f = 'b';
  Swap(a, b); Swap(c, d); Swap(e, f);
  cout << a << " " << b << endl;
  cout << c << " " << d << endl;
  cout << e << " " << f << endl;
  return 0;
}
  • 我们虽然能用函数重载来解决这个问题,但是函数代码冗余
  • 仅仅是类型的不同,每当出现新类型时,就需要增加对应的函数
  • 代码的可维护性比较低,一个出错可能所有的重载均出错

在编程中,我们经常遇到需要处理不同类型数据的场景。为了处理这些类型,我们通常需要编写多个函数或类,每个函数或类都针对特定的数据类型进行实现。这不仅增加了代码的冗余度,也降低了代码的可维护性和可重用性。模板的引入就是为了解决这个问题。通过使用模板,我们可以编写与类型无关的代码,这些代码可以在编译时根据所需的具体类型进行实例化,从而生成针对特定类型的代码


📒2. 函数模板

🌈函数模板概念

根据上面的示例代码我们能够用模板修改

template<typename T>
void Swap(T& left, T& right)
{
   T temp = left;
   left = right;
   right = temp;
}
格式:template<typename T>  T 表示类型

template < typename T> 声明了一个模板参数T,它表示任意类型 。在函数参数和返回类型中,我们都使用了T,这样该函数就可以处理任何类型的数据了

有了这个函数就能实现上述三个交换函数

注意: typename是用来定义模板参数关键字,也可以使用class

(切记:不能使用struct代替class)


🌈函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此


🌈函数模板的实例化

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

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


🌙隐式实例化

隐式实例化:让编译器根据实参推演模板参数的实际类型

// 隐式实例化
template<class T>
T Add(const T& left, const T& right)
{
  return left + right;
}
int main()
{
  int a1 = 10, a2 = 20;
  double d1 = 10.0, d2 = 20.0;
  Add(a1, a2);
  Add(d1, d2);
  // 在这里要注意一点,在模板中,编译器一般不会进行类型转换操作
  //Add(a1, d1); false
  // 虽然编译器不会进行类型转换,但是我们能自己来强制转化或者显式实例化
  Add(a1, (int)d1);
  return 0;
}


模板参数列表中只有一个T时,无法将两个类型不同的放在一起推演,编译器无法确定此处到底该将T确定什么类型而报错


🌞显式实例化

显式实例化:在函数名后的<>中指定模板参数的实际类型

// 显式实例化
template<class T>
T Add(const T& left, const T& right)
{
  return left + right;
}
int main()
{
  int a1 = 10, a2 = 20;
  double d1 = 10.0, d2 = 20.0;
  // 显式实例化
  Add<int>(a1, d1);
  return 0;
}

注意:

  • 如果类型不匹配,编译器会尝试进行隐式类型转换
  • 如果无法转换成功编译器将会报错

🌈模板参数的匹配原则

模板参数的匹配原则:

  • 🌹一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
  • 🌸对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  • 🌻模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
  Add(1314, 520); // 与非模板函数匹配,优先调用非模板函数
  Add<int>(1314.0, 520.0); // 调用编译器特化的Add版本
}

int Add能够和T Add同时存在,在调用函数时若参数和非模板函数匹配,那么编译器会优先调用非模板函数若非模板函数;不匹配或模板函数更匹配,那么编译器会优先调用模板函数


📒3. 类模板

类模板是对一批仅成员数据类型不同的类的抽象。通过为这一批类组成的类家族创建一个类模板,并提供一套程序代码,程序员可以生成多种具体的类,从而显著提高编程效率

🎈类模板的定义格式

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

我们来定义一个简单的顺序表

template<class T>
class Vector
{
public:
  Vector(size_t capacity = 10)
    : _pData(new T[capacity])
    , _size(0)
    , _capacity(capacity)
  {}
  // 使用析构函数演示:在类中声明,在类外定义。
  ~Vector();

  T& operator[](size_t pos)
  {
    assert(pos < _size);
    return _pData[pos];
  }
private:
  T* _pData;
  size_t _size;
  size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T> //这里不要忘记带上
Vector<T>::~Vector()
{
  if (_pData)
    delete[] _pData;
  _size = _capacity = 0;
}

关于类模板其实就是将原来实际类型的位置变成T

🎩类模板的实例化

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

关于类模板的实例化必须显示实例化,类模板没有隐式推演

// Vector类名,Vector<int>才是类型
Vector<int> v1;
Vector<double> v2;

🔥4. 总结拓展

拓展:🐫驼峰命名法

🐫驼峰命名法(CamelCase): 是电脑程序编写时常用的一套命名规则,通过混合使用大小写字母来构成变量、函数、类名等的名字。这种命名方式有助于提高代码的可读性和可维护性

驼峰命名法在多种编程语言中都有广泛应用,如Java、C#、JavaScript、Python等。在编写代码时,根据命名对象的类型(如变量、函数、类等)选择合适的驼峰命名法可以提高代码的可读性和可维护性!!!


🐼命名规则: 当变量名、函数名或类名由多个单词组成时,第一个单词以小写字母开始,从第二个单词开始以后的每个单词的首字母都采用大写字母。

// 例如:
myFirstName
printEmployeePaychecks

🐋驼峰命名法的分类

🐟小驼峰法(lower camel case):

  • 命名规则:第一个单词首字母小写,后续单词首字母大写
  • 使用场景:常用于变量名、方法名、属性名
  • 示例:myStudentCount、getUserName()、userModel

🐬大驼峰法(upper camel case):

  • 命名规则:所有单词的首字母均大写。
  • 使用场景:常用于类名、接口名、常量名等。
  • 示例:DataBaseUser、UserService

🐷优点

  • 可读性好: 驼峰命名法通过大小写来区分单词,使得代码更容易阅读和理解
  • 易于书写和记忆: 采用统一的命名规则可以提高代码的一致性和可维护性
  • 符合编程语言的习惯: 通过明确的命名规则,可以减少变量、函数、类等名称之间的冲突

🐨注意事项

  • 在使用驼峰命名法时,需要注意单词之间的分隔,避免使用下划线或其他特殊字符
  • 对于类名、接口名等,建议使用大驼峰法,以突出其重要性
  • 在编写代码时,应保持命名的一致性,避免混用不同的命名方式

总之,驼峰命名法是一种简单、清晰、易读的命名方式,在程序编写中被广泛使用。通过合理使用驼峰命名法,可以提高代码的可读性和可维护性,降低编程错误率


总结:

在完成C++模板初阶的学习之后,我们可以深刻地感受到模板在C++编程中的强大与灵活性。模板作为C++编程语言的一个重要特性,为我们提供了一种在编译时生成特定类型代码的机制,从而实现了代码的重用和泛型编程。下节我们将进入C++ STL,对STL进行深入发掘


目录
相关文章
|
2天前
|
编译器 程序员 C++
C++一分钟之-模板基础:泛型编程
【6月更文挑战第21天】C++模板,泛型编程的关键,让代码跨类型工作,增强重用与灵活性。理解模板基础,如函数和类模板,注意避免特化与偏特化的混淆、编译时膨胀及复杂的错误调试。通过明确特化目的、限制模板使用及应用现代C++技术来优化。示例展示了模板如何自动或显式推导类型。模板元编程虽强大,但初学者宜从基础开始。正确使用模板,提升代码质量,同时保持简洁。
21 3
|
3天前
|
C++
C++类和类模板——入门
C++类和类模板——入门
9 1
|
5天前
|
存储 C++ 容器
C++一分钟之-变量与数据类型入门
【6月更文挑战第18天】**C++编程基础:变量与数据类型概览** 了解变量(存储数据的容器)和数据类型是编程入门的关键。声明变量如`int age = 25;`,注意初始化和类型匹配。基本数据类型包括整型(int等)、浮点型(float、double)、字符型(char)和布尔型(bool)。理解类型范围和精度,使用字面量后缀增强可读性。深入学习数组、指针、结构体和类,以及动态内存管理,避免数组越界和内存泄漏。不断实践以巩固理论知识。
18 1
|
8天前
|
算法 安全 编译器
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
22 1
|
3天前
|
C++
C++一分钟之-类与对象初步
【6月更文挑战第20天】C++的类是对象的蓝图,封装数据和操作。对象是类的实例。关注访问权限、构造析构函数的使用,以及内存管理(深拷贝VS浅拷贝)。示例展示了如何创建和使用`Point`类对象。通过实践和理解原理,掌握面向对象编程基础。
30 2
C++一分钟之-类与对象初步
|
4天前
|
存储 编译器 C++
|
5天前
|
数据安全/隐私保护 C++
C++ 中的类是一种用户定义的数据类型,用于表示具有相似特征和行为的对象的模板。
C++ 中的类是一种用户定义的数据类型,用于表示具有相似特征和行为的对象的模板。
|
8天前
|
存储 编译器 C++
【C++初阶】—— 类和对象 (中)
【C++初阶】—— 类和对象 (中)
20 3
|
8天前
|
编译器 C++
【C++初阶】—— 类和对象 (下)
【C++初阶】—— 类和对象 (下)
10 2
|
8天前
|
存储 编译器 C语言
【C++初阶】—— 类和对象 (上)
【C++初阶】—— 类和对象 (上)
14 1