1.泛型编程
所谓的泛型编程,指的是我们编写程序的时候,是独立于任何特定的类型来编程程序,那么什么叫做独立于 任何特定的类型呢?
如:
设计一个数组类型,这个数组有可能只能存放int类型的元素,但是我们在代码中使用这个数组的时 候,却不一定总是int类型的元素,可能需要数组能够存放任意类型的元素也就是说,数组只是一个 通用的概念,不应该局限于某一种特定的类型
所以,数组元素的类型,应该可以使用某一种方式独立表示,类似于函数的参数。可以在实例化数组的时候,指定数组中的元素类型
泛型编程的定义
泛型编程是计算机科学中的一个分支,它允许一个值取不同的数据类型,并强调使用这种技术的编程风 格。泛型编程的目标是推出一种针对算法、数据结构和内存分配机制的分类方法,以及其他能够带来高 度可重用性、模块化和可用性的软件工具。
泛型编程强调算法的重要性,它使用最少的有关数据抽象的假设,并尽可能将具体算法提升到抽象的层 次,同时保持效率。通过这种方式,泛型编程表示成高度通用和抽象的组件集合,这些组件可以以多种 方式组合在一起,形成高效、具体的程序。
泛型编程在多个场景中有广泛应用,包括集合类和数据结构、自定义数据结构、泛型方法、接口和抽象 类以及异常处理等。在集合类和数据结构中,泛型允许创建存储特定类型元素的集合,并在编译时捕获 类型错误。在自定义数据结构中,泛型可以创建适应不同类型数据的通用数据结构。泛型方法则允许在 方法级别使用泛型,为只需要在特定方法中使用泛型的情况提供便利。
总的来说,泛型编程提供了一种灵活且高效的方式来处理不同数据类型,提高了软件的可重用性和模块 化程度。
泛型编程的优点
- 代码重用性:泛型编程可以编写通用的代码,这些代码可以在不同类型上进行操作,而无需为每种类型 单独编写代码。这大大提高了代码的重用性,减少了代码的冗余,从而降低了开发和维护成本。
- 类型安全:泛型编程在编译时执行类型检查,这有助于捕获大部分类型相关的错误。这意味着在运行 时,由于类型不匹配而引发的异常会大大减少,从而提高了代码的安全性和稳定性。
- 性能优化:泛型编程允许在编译时进行类型优化,减少运行时的类型转换和装箱/拆箱操作。这对于性能 敏感的应用程序尤为重要,因为它可以显著提高代码的执行效率。
- 抽象性:泛型编程提供了一种更高层次的抽象,它隐藏了实现细节,使得代码更易于理解和维护。通过 使用泛型,程序员可以专注于算法和数据结构的本质,而不是陷入具体的类型细节中。
- 灵活性:泛型编程提供了更大的灵活性,允许程序员在编译时定义和修改类型,从而根据需求调整程序 的行为。这使得泛型编程成为处理多种数据类型和适应不同场景的强大工具。
- 简化代码:泛型代码通常更加清晰和易读,因为它减少了不必要的类型转换和类型检查,使得代码的结 构更加简洁和直观。
- 泛型编程的核心思想是将数据类型视为参数,通过参数化类型来编写代码,使得代码能够在多种数据类 型上操作。这种技术使得程序员能够编写完全一般化并可重复使用的算法,而不需要针对每种数据类型 都编写特定的代码。
泛型编程如何实现呢? 使用模板实现。
2.模版
(1)模版定义
在C++中,使用模板实现泛型编程,或者说使用模板来表示一个通用的概念
模板是一种泛型编程的工具,它允许程序员创建一种可以处理任何数据类型的类或函数,而无需针对每 种数据类型分别编写代码。通过模板,C++程序可以以一种统一的方式处理多种数据类型,提高了代码 的重用性和灵活性
模板允许我们在定义函数或者类的时候,将类型作为一个参数,编译器后面可以根据你提供的类型自动生成 特定的函数或者类
简单的说,模板就是用来创建一个类或者一个函数的公式(蓝图)
模板可以分为两种:
函数模板,可以通过这个模板创建具体类型的函数,其参数和返回值类型也可以是任意的。
类模板,可以用来创建具体类型的类,其成员变量和成员函数可以使用任意类型。
在定义模板时,需要使用 template 关键字,并指定一个或多个类型参数。这些类型参数在模板实例化 时会被具体的类型所替换。
通过使用模板,程序员可以编写更加通用和灵活的代码,减少代码冗余,提高代码的可维护性和可重用 性。同时,模板也可以提高代码的性能,因为编译器在编译时会根据具体的类型生成相应的代码,避免 了运行时类型转换的开销。
需要注意的是,模板不是一种运行时的特性,而是一种编译时的特性。编译器会根据模板的定义和实例 化请求生成具体的代码。因此,在使用模板时,需要确保模板的定义在实例化之前已经被编译器看到。
(2)函数模版
函数模板允许我们编写一个函数,该函数可以处理任意类型的数据。而无需针对每种数据类型分别编写 函数。
函数模板实际上是建立一个通用函数,其函数类型和形参类型中的全部或部分类型不具体指定,用一个 虚拟的类型来代替。这个通用函数称为函数模板,函数体相同的函数都可以用这个模板来代替。
函数模板定义格式:
template<typename T1, typename T2...> //模板头
返回值类型 函数名(参数列表)
{
//函数体;
}
- template 是C++语言中用于定义模板的关键字
- T1,T2:是一个类型参数的形参名字,在后面的函数定义中,可以直接使用这个类型参数
- 如果该函数模板涉及到多个类型参数,只需要在尖括号中以逗号隔开即可
- 在函数模板调用的时候,编译器会自动的推导类型参数去匹配函数模板
函数模板调用格式:
函数模板名<参数类型>(实参列表);
//其中<参数类型> 是类型参数列表,它告诉编译器在实例化模板时应该使用哪种类型。在某些情况下,编译
//器能够自动推导出类型参数,这时可以省略
示例:定义一个函数模板,用于计算两个对象的和
#include<iostream>
using namespace std;
template<typename T1, typename T2> //T只是一个名字,表示一种类型
T1 sum(T1 , T2 );
template<typename T1, typename T2>
T1 sum(T1 a, T2 b)
{
return a + b; //T类型的对象本身是可以相加的
}
int main()
{
cout << sum<int>(1, 2) << endl;//<int>可以省略
cout << sum('0', 'A') << endl;
string s1 = "abc";
string s2 = "def";
cout << sum(s1, s2) << endl;
cout << sum(1, 'a') << endl;
return 0;
}
(3)类模版
C++中除了支持函数模板还支持类模板(class template)
在C++中,类模板是一种特殊的类,它允许程序员定义一种可以用于多种数据类型的类,而不必为每种 数据类型都重新编写一个类。通过使用类模板,我们可以在不重复编写代码的情况下创建适用于不同类 型的类实例。
函数模板中定义的类型参数可以在函数的声明和定义中使用,类模板中定义的类型参数可以在类的声明 和实现中使用
类模板的目的是将数据的类型参数化,让类可以更加的通用,类模板的主要优势在于其灵活性和代码重用 性。通过使用类模板,我们可以编写出高度通用的代码,这些代码可以处理多种数据类型,而无需为每 个数据类型都编写特定的类。这不仅可以减少代码量,还可以提高代码的可读性和可维护性。
类模板定义格式:
template<typename T1,typename T2>
class 类名
{
public:
...
private:
...
protected:
...
};
类模板和函数模板都是以template开头,后面跟上类型参数,类型参数不能为空
多个类型参数可以使用逗号隔开
一旦声明了类模板,就可以将类型参数用于类的成员变量和成员函数
include<iostream>
#include"test_1.h"
using namespace std;
class A
{
public:
A(int num = 0) : m_num(num)
{
}
friend ostream& operator<<(ostream& os, A& a)
{
os << a.m_num;
return os;
}
private:
int m_num;
};
//使用模板类定义一个数组
template<typename T>
class Array
{
public:
Array(int cnt) : m_parr(new T[cnt]), m_cnt(cnt)
{}
~Array()
{
delete[]m_parr;
}
//获取数组大小
int getSize()
{
return m_cnt;
}
//访问元素
T& operator[](int index)
{
if (index < 0 || index > m_cnt)
{
cout << "下标输入有误" << endl;
exit(-1);
}
return m_parr[index];
}
//拷贝构造
Array(const Array& that)
{
m_cnt = that.m_cnt;
m_parr = new T[m_cnt];
for (int i = 0; i < m_cnt; i++)
{
m_parr[i] = that.m_parr[i];
}
}
//拷贝赋值
Array& operator=(const Array& that)
{
m_cnt = that.m_cnt;
if (this != &that)
{
//释放旧内存
delete[]m_parr;
//分配新内存
m_parr = new T[m_cnt];
for (int i = 0; i < m_cnt; i++)
{
m_parr[i] = that.m_parr[i];
}
}
return *this;
}
private:
T* m_parr;//数组首地址
int m_cnt;//数组元素个数
};
在这个例子中, typename T 是模板参数,它可以在类中使用 T 来表示任意数据类型。当创建类模 板的实例时,需要将 T 替换为实际的数据类型。
类模板的使用方式:
调用函数模板只需要提供实际参数即可,编译器会自动的根据用户提供的值去推导值的类型,在自动的生成 相应的函数版本,然后调用。
但是我们在使用类模板的时候,必须显示的指定类型参数
格式:
模板类名<实际类型1,实际类型2> 对象名;
一旦类模板被实例化,就可以像使用普通类一样创建对象,并调用其成员函数和访问其成员变量。 每次使用不同的类型参数实例化类模板时,都会生成一个独立的类
int main()
{
Array<int> intArray(5);//类模板的使用
for (int i = 0;i < intArray.getSize();i++)
{
intArray[i] = i;
}
// 输出整型数组的内容
for (int i = 0; i < intArray.getSize(); ++i)
{
std::cout << intArray[i] << " ";
}
std::cout << endl;
Array<int> intArray1 = intArray;
// 输出整型数组的内容
for (int i = 0; i < intArray1.getSize(); ++i)
{
std::cout << intArray1[i] << " ";
}
std::cout << endl;
Array<int> intArray2(3);
intArray2 = intArray;
for (int i = 0; i < intArray2.getSize(); ++i)
{
std::cout << intArray2[i] << " ";
}
std::cout << endl;
std::cout << "-----------------" << endl;
Array<A> a(5);
for (int i = 0; i < a.getSize(); i++)
{
a[i] = i;
}
for (int i = 0; i < a.getSize(); i++)
{
std::cout << a[i] << " ";
}
return 0;
}
注意:
如果模板类的成员函数在类外实现,则该成员函数必须有模板声明,并且作用域也必须指定模板 参数
如果模板函数和类模板的声明和定义分开,可以将模板函数和类模板的声明写在.h文件中,而 将定义写在.cpp文件中。
但是,模板函数和类模板的定义和调用必须在同一个文件中(上述例子中,类模板的声明在 test_1.h文件中,定义和调用都在test.cpp文件中,是没问题的。但是如果将模板函数和类模 板的定有写在test_1.cpp,而调用写在test.cpp中就会报错)
在模板声明中,使用关键字typename来说明类型参数的名字。但是在早期的C++中,曾经使用的 关键字叫做class。但是在新标准中,我们建议使用typename
(4)函数模版和类模版的结合
类模板和函数模板在C++中可以共同使用,以实现更高级别的泛型编程。通过结合类模板和函数模板, 我们可以创建出既能够处理多种数据类型,又能够执行多种操作的灵活且可重用的代码。
在上述的案例中,我们想要将给数组赋值以及打印数组的操作放在模板函数中完成
Init函数写在类模板内,print写在类模板外
void Init(int size);//类模板内
template<typename T> void print(Array<T>& arr, int size);//类模板外
template<typename T>//定义
void Array<T>::Init(int size)
{
for (int i = 0; i < size; i++)
{
m_parr[i] = i;
}
}
template<typename T>
void print(Array<T>& arr, int size)
{
for (int i = 0; i < size; i++)
{
std::cout << arr[i] << " ";
}
}
Array<int> intArray(5);
//for (int i = 0; i < intArray.getSize(); i++)
//{
// intArray[i] = i;
//}
intArray.Init(intArray.getSize());//调用
//Init(intArray, intArray.getSize());
// 输出整型数组的内容
//for (int i = 0; i < intArray.getSize(); ++i)
//{
// std::cout << intArray[i] << " ";
//}
print(intArray, intArray.getSize());//调用
std::cout << endl;
以上内容均为作者个人见解,如有错误,还请读者斧正