C++ | C++模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码

模板是创建泛型类或者函数蓝图公式库容器,比如迭代器算法,都是泛型编程的例子,使用的是模板的概念。

每个容器都有一个单一的定义,比如向量,我们可以定义许多不同类型的向量,比如​​vector​​​ <​​int​​​> 或​​vector​​​ <​​string​​>。

函数模板

// type是函数所使用的数据类型的占位符名称,这个名称可以在函数定义中使用。
template <typename type> ret-type func-name(parameter list)
{
// 函数主体
}

​<typename type1,typename type1,...>​​:类型参数可以有多个,以逗号’​,​​'分隔,类型参数列表以​​<>​​包围。

函数名(形参列表):​​ret-type func-name(parameter list)​​;

实例1(函数重载):

/*******************************************************************
* > File Name: func-overload.cpp
* > Create Time: 2021年09月20日 9:27:47
******************************************************************/
#include <iostream>
using namespace std;

inline int const& MAX(int const& a, int const& b)
{
return a < b ? b : a; /* 返回两个int型数中的较大数 */
}

inline float const& MAX(float const& a, float const& b)
{
return a < b ? b : a; /* 返回两个float型数中的较大数 */
}

inline char const& MAX(char const& a, char const& b)
{
return a < b ? b : a; /* 返回两个char型数中的较大数 */
}

inline bool const& MAX(bool const& a, bool const& b)
{
return a < b ? b : a; /* 返回两个bool型数中的较大数 */
}

int main(int argc, char* argv[])
{
int i = 10;
int j = 20;
cout << "MAX(i, j): " << MAX(i, j) << endl;

float d1 = 20.01;
float d2 = 99.999;
cout << "MAX(d1, d2): " << MAX(d1, d2) << endl;

bool b1 = true;
bool b2 = false;
cout << "MAX(b1, b2): " << MAX(b1, b2) << endl;

char s1 = 'A';
char s2 = 'a';
cout << "MAX(s1, s2): " << MAX(s1, s2) << endl;

return 0;
}

编译、运行:

PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> make
g++ -o func-overload func-overload.cpp -g -Wall
PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> .\func-overload.exe
MAX(i, j): 20
MAX(d1, d2): 99.999
MAX(b1, b2): 1
MAX(s1, s2): a

通过函数重载定义四个名字相同、参数列表不同的函数。本质上来说,定义了三个功能相同、函数体相同的函数,只是数据类型不同而已。

​通过函数模板,我们可以将四个函数压缩成一个函数。​

数据的值,可以通过函数参数传递,在函数定义时数据的值是未知的,只有等到函数调用时接收了实参才能决定其值,这就是值的参数化

在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指定具体的数据类型,当发生函数调用时,编译器可以根据传入的参数自动推断数据类型。这就是类型的参数化

(​​value​​)和类型(​​type​​)是数据的两个主要特征,它们在C++中可以被参数化。

函数模板(​​Function​​),实际是建立了一个通用函数,它所用到的数据类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(标识符来占位),等到函数调用时再根据传入的实参来逆推出真正的类型。

​typename​​关键字

也可以使用​​class​​​关键字代替,最开始使用​​class​​​来指明类型参数,后续才引入了​​typename​​关键字。


实例2(函数模板):

/*******************************************************************
* > File Name: func-template.cpp
* > Create Time: 2021年09月20日 9:19:33
******************************************************************/
#include <iostream>
#include <string>
using namespace std;

template <typename T> /* 模板头 */
inline T const& MAX(T const& a, T const& b) /* 函数头 */
{
return a < b ? b : a; /* 返回较大的值 */
}

int main(int argc, char* argv[])
{
int i = 10;
int j = 20;
cout << "MAX(i, j): " << MAX(i, j) << endl;

double d1 = 20.01;
double d2 = 99.999;
cout << "MAX(d1, d2): " << MAX(d1, d2) << endl;

string s1 = "ABCD";
string s2 = "abcd";
cout << "MAX(s1, s2): " << MAX(s1, s2) << endl;

return 0;
}

编译、运行:

PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> make
g++ -o func-template func-template.cpp -g -Wall
PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> .\func-template.exe
MAX(i, j): 20
MAX(d1, d2): 99.999
MAX(s1, s2): abcd

类模板

template <typename type-param1,typename type-param2,...>
class class-name
{
//TODO:
};

实例3:

/*******************************************************************
* > File Name: template-class.cpp
* > Create Time: 2021年09月20日 10:52:37
******************************************************************/
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
using namespace std;

// 定义了类Stack<>,实现了范型方法对元素进行出、入栈的操作
template <class T>
class Stack
{
private:
vector<T> elems; // 元素
protected:
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{
return elems.empty();
};
};

template <class T> /* 模板头 */
void Stack<T>::push(T const& elem) /* 函数头 */
{
elems.push_back(elem);
}

template <class T> /* 模板头 */
void Stack<T>::pop()/* 函数头 */
{
if(elems.empty()){
throw out_of_range("Stack<>::top(): empty stack");
}

elems.pop_back();
}

template <class T> /* 模板头 */
T Stack<T>::top() const/* 函数头 */
{
if(elems.empty()){
throw out_of_range("Stack<>::top(): empty stack");
}

return elems.back();
}

int main(int argc, char* argv[])
{
try{
Stack<int> intStack;
Stack<string> stringStack;

intStack.push(7);
cout << intStack.top() << endl;

stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();

}catch(exception const& ex){
cerr << "Exception" << ex.what() << endl;
return -1;
}

return 0;
}

编译、运行:

PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> make
g++ -o template-class template-class.cpp -g -Wall
PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> .\template-class.exe
7
hello
ExceptionStack<>::top(): empty stack

实例4:

/*******************************************************************
* > File Name: template-class1.cpp
* > Create Time: 2021年09月20日 13:29:05
******************************************************************/
#include <iostream>
using namespace std;

template<class T1, class T2> /* 模板头 */
class Point{ /* 声明一个类Point */
public:
Point(T1 x, T2 y):m_x(x), m_y(y){} /* 构造函数 */
public:
T1 getX() const; /* 获取x坐标 */
void setX(T1 x); /* 设置x坐标 */
T2 getY() const; /* 获取y坐标 */
void setY(T2 y); /* 设置y坐标 */
private:
T1 m_x; // x坐标
T2 m_y; // y坐标
};

template<class T1, class T2> /* 模板头 */
T1 Point<T1, T2>::getX() const /* 函数头,设置x坐标 */{
return m_x;
}

template<class T1, class T2>
void Point<T1, T2>::setX(T1 x){ /* 设置x坐标 */
m_x = x;
}

template<class T1, class T2>
T2 Point<T1, T2>::getY() const{ /* 设置y坐标 */
return m_y;
}

template<class T1, class T2>
void Point<T1, T2>::setY(T2 y){ /* 设置x坐标 */
m_y = y;
}

int main(int argc, char* argv[])
{
Point<int, int> p1(10, 20); // 和函数模板不同,在实例化需要显示指明数据类型
cout << "x = " << p1.getX() << ", y = " << p1.getY() << endl;

Point<int, char *>p2(10, "东经180度");
cout << "x = " << p2.getX() << ", y = " << p2.getY() << endl;

/* 赋值号两边都要指明具体的数据类型,且保持一致 */
Point<char*, char*> *p3 = new Point<char *, char *>("东经180度", "北纬210度");
cout << "x = " << p3->getX() << ", y = " << p3->getY() << endl;

return 0;
}

编译、运行:

PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> make
g++ -o template-class1 template-class1.cpp -g -Wall
template-class1.cpp: 在函数‘int main(int, char**)’中:
template-class1.cpp:49:30: 警告:ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
49 | Point<int, char *>p2(10, "东经180度");
| ^~~~~~~~~~~
template-class1.cpp:52:57: 警告:ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
52 | Point<char*, char*> *p3 = new Point<char *, char *>("东经180度", "北纬210度");
| ^~~~~~~~~~~
template-class1.cpp:52:70: 警告:ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
52 | Point<char*, char*> *p3 = new Point<char *, char *>("东经180度", "北纬210度");
| ^~~~~~~~~~~
PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> .\template-class1.exe
x = 10, y = 20
x = 10, y = 东经180度
x = 东经180度, y = 北纬210度

实例5(可变数组):

/*******************************************************************
* > File Name: template-class-arr.cpp
* > Create Time: 2021年09月20日 15:10:16
******************************************************************/
#include <iostream>
#include <cstring>
using namespace std;

template <class T> /* 模板头 */
class Carray{ /* Carray类 */
int size; // 数组元素的个数
T *ptr; // 指向动态分配的数组
public:
Carray(int n = 0); // n表示数组个数
Carray(Carray& a);
~Carray(); /* 析构函数 */

void push_back(const T& a); /* 加入新的元素 */
Carray& operator=(const Carray& a); /* 重载操作符= */
T length() {return size;}; /* 获取数组的大小 */
T& operator[](int i) /* 重载[],支持下标访问 */
{
return ptr[i];
}
};

template <class T>
Carray<T>::Carray(int n):size(n)/* 为类成员size赋值为n */
{
if(0 == n){ /* 数组元素为0 */
ptr = NULL;
}else{
ptr = new T[n];/* 申请空间 */
}
}

template <class T>
Carray<T>::Carray(Carray& a)
{
if(!a.ptr){ /* a.ptr为NULL,重置可变数组 */
ptr = NULL;
size = 0;
return;
}

ptr = new T[a.size]; /* 申请空间 */
memcpy(ptr, a.ptr, sizeof(T)*a.size); /* 复制a.ptr到ptr */
size = a.size; /* 给size赋值 */
}

template <class T>
Carray<T>::~Carray()
{
if(ptr){
delete [] ptr; /* 释放ptr所指向的数组空间 */
}
}

template <class T>
Carray<T> & Carray<T>::operator=(const Carray& a)
{
if(this == &a){ // 防止a=a的情况导致出现的错误
return *this;
}

if(a.ptr == NULL){
if(ptr){ /* 不为空 */
delete [] ptr; /* 释放ptr指向的数组空间 */
}

ptr = NULL;
size = 0; /* 让数组的个数为0 */
return *this;
}
if(size < a.size){ /* 空间不够大 */
if(ptr){/* 不为空 */
delete [] ptr;/* 释放ptr指向的数组空间 */
}
ptr = new T[a.size]; /* 申请空间 */
}
memcpy(ptr, a.ptr, sizeof(T)*a, size); /* 拷贝a.ptr的数据到ptr */
size = a.size;
return *this;
}

template <class T>
void Carray<T>::push_back(const T& v)
{
if(ptr)
{
T* tmPtr = new T[size + 1]; /* 重新分配空间 */
memcpy(tmPtr, ptr, sizeof(T)*size); /* 拷贝原数组内容 */
delete []ptr; /* 释放原数组的空间 */
ptr = tmPtr; /* ptr指向新的位置 */
}
else /* 数组本来是空的 */
{
ptr = new T[1];/* 申请一个元素的空间 */
}

ptr[size ++ ] = v; /* 加入新的数组元素 */
}

int main(int argc, char* argv[])
{
Carray <int> a;
for(int i = 0; i< 5; ++i){
a.push_back(i); /* 赋值 */
}

for(int i = 0; i< a.length(); ++i){
cout << a[i] << " "; /* 打印、输出 */
}
cout << endl;

return 0;
}

编译、运行:

PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> make
g++ -o template-class-arr template-class-arr.cpp -g -Wall
PS D:\2.SoftTools\cygwin64\home\fly\workSpace\cplusplus\day4> .\template-class-arr.exe
0 1 2 3 4

强类型语言

强类型语言在定义变量时需要显式地指明数据类型,并且一旦为变量指明了某种数据类型,该变量以后就不能赋予其他类型的数据了,除非经过强制类型转换隐式类型转换

弱类型语言

弱类型语言在定义变量时不需要显式地指明数据类型编译器(解释器)会根据赋给变量的数据自动推导出类型,并且可以赋给变量不同类型的数据。

不管是强类型语言还是弱类型语言,在编译器(解释器)内部都有一个类型系统来维护变量的各种信息。

STL

STL​(Standard Template Library,标准模板库)就是 C++ 对数据结构进行封装后的称呼。

C++模板和泛型程序设计

​​C++模板:函数、结构体、类 模板实现​​