【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进行深入发掘


目录
相关文章
|
22天前
|
存储 缓存 C++
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 的奥秘,从入门到高效编程
|
11天前
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
28 0
|
22天前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
22天前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
22天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4天前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
30 6
|
24天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
22天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
22天前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。