前言:在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进行深入发掘