C++初阶:模板初阶

1. 模板的引入

我们来看下面这几个函数:

void swap(int& left, int& right)
{
    int tmp = left;
    left = right;
    right = tmp;
}

void swap(char& left, char& right)
{
    char tmp = left;
    left = right;
    right = tmp;
}

void swap(double& left, double& right)
{
    double tmp = left;
    left = right;
    right = tmp;
}
  1. C++中引入了函数重载,简化了我们对上述功能相同,细节不同函数的调用方式。
  2. 可是,从定义的角度出发,这写有着大量重复性逻辑代码,编写起来无疑是十分繁琐与冗余的。
  3. 上述几个函数,除了参数的类型之外没有任何的区别,因此,我们是否可以进行无关类型只针对逻辑的编程呢,由是,C++提出了泛型编程的概念引入了无关类型的编程方式,模板。

2. 函数模板与类模板

2.1 函数模板

  1. 正如其名,这种方式只是提供了一个模板,其并不是真正的函数,只有在被调用时,其类型才会确定下来,生成对应类型的函数。
  1. 模板参数的定义方式如下:(可定义多个模板参数)
template<typename T1, typename T2,.....>
返回值 函数名(参数列表)
{
	//实现
}
  1. 函数模板生成的函数不是同一份,生成的函数都是被调用时,根据模板参数生成出来的。因此,对模板的使用只是我们将原本自己去做的事情交给了编译器去做,减少了我们的繁复不必要的操作。
template<typename T>
void swap(T& left, T& right)
{
    T tmp = left;
    left = right;
    right = tmp;
}

在这里插入图片描述

4, 多参数类型模板参数的定义

template<typename T1, typename T2>
void Print(T1 num1, T2 num2)
{
	cout << num1 << ' ' << num2 << endl;
}

2.2 模板调用方式

  1. 推演实例化调用:根据所给出的参数类型进行模板参数类型的推演
int num1 = 10;
int num2 = 20;
swap(num1, num2);

char c1 = 'a';
char c2 = 'b';
swap(c1, c2);

double num3 = 11.1;
double num4 = 22.2;
swap(num3, num4);
  1. 显示实例化调用的使用场景:
    <1> 当模板无法根据参数类型推演出应该实例化的类型
    <2> 当函数参数的类型,没有包含模板参数,无法进行推演
template <typename T>
T Add(T left, T right)
{
	return left + right;
}

//无法推导出应该使用哪种类型,进行哪种类型的转换
cout << Add(10, 12.3) << endl;

//没有模板参数类型的函数参数,没有推导的依据
template <typename T>
T* f(int n)
{
	T* data = new T[n];	
}
  1. 显示实例化的调用方式:
//定义方式:
[函数]<类型>(参数列表)

//模板参数类型为int,向int做类型转换
Add<int>(10, 12.3);
//模板参数类型为double,向double做类型转换
Add<double>(10, 12.3);

//返回int类型的指针,指向一段大小为10的动态开辟的空间
int* pa = f<int>(10);

2.3 函数模板与普通函数的调用优先性

  1. 当同时存在类型符合的普通函数,与函数模板时,编译器会去调用哪一个,是否会发生冲突。
    <1> 当存在类型符合存在的普通函数时,优先调用符合的普通函数(可以直接使用,不再生成)
    <2> 当普通函数与调用的参数类型不同,则回调用函数模板
    <3> 当不存在函数模板时,普通函数的类型与函数调用参数的类型不同时,会将参数转换为函数参数类型,然后再对函数进行调用
//场景1:
//优先
int Add(int num1, int num2)
{
	return num1 + num2;
}

template<typename T>
T Add(T num1, T num2)
{
	return num1 + num2;
}

cout << Add(10, 20) << endl;

//场景2:
double Add(double num1, double num2)
{
	return num1 + num2;
}

//优先
template<typename T>
T Add(T num1, T num2)
{
	return num1 + num2;
}

cout << Add(10, 20) << endl;

//场景3:
//类型转换为double后进行调用
double Add(double num1, double num2)
{
	return num1 + num2;
}

cout << Add(10, 20) << endl;

2.4 类模板

  1. 函数之外,C++引入的类(自定义类型),也有着自己需要模板的应用场景
#define STDataType int;

class Stack
{
public:
	Stack(int n = 4)
	{
		data = new STDataType[n];
		top = 0;
		capacity = n;
	}

	~Stack()
	{
		delete[] data;
		capacity = 0;
		top = 0;
	}

private:
	STDataType* data;
	int top;
	int capacity;
};

int main()
{
	//宏的方式可以提高代码复用率
	//存放int类型的数据
	Stack st1;
	//存放double类型的数据
	Stack st2;

	return 0;
}

宏的定义方式虽然可以提高代码的复用率,但以上的场景其无法解决,即同时存在两种存储不同类型的栈。

  1. 类模板的定义方式
//typename ,class关键字皆可,现阶段没有区别
//template <typename T>
template <class T1, class T2,......>
class [类名]
{
	//实现
}

2.5 类模板的构造函数,类模板声明与定义分离

  1. 我们在之前类与对象的学习中,学习过类的默认成员函数,其中构造函数的函数名为类的名称,那么类模板的构造函数的函数名应该是什么呢。
  2. 构造函数的名称为类名,而类模板[类名]<实例化调用类型>为类型名,因此,构造函数名与普通类相同。
  1. 类模板的成员函数声明与定义分离方式:
    <1> 必须在同一文件下,否则报错
    <2> 分离出的定义部分,必须也要加模板参数的声明,声明类域时要使用类型名,具体如下
template<class T>
class [类名]
{
public:
	//函数声明
	[返回值] [函数名]();
}

//定义分离:
template<class T>
[返回值] [类名<T>]::[函数名]()
{
	//实现
}

示例:

//定义在头文件中,头文件被多次展开会重复定义
template<class T>
class Stack
{
public:
    Stack(int n = 4);

    ~Stack()
    {
        delete[] data;
        capacity = 0;
        top = 0;
    }

private:
    T* data;
    int top;
    int capacity;
};

template<class T>
Stack<T>::Stack(int n)
{
    data = new T[n];
    top = 0;
    capacity = n;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值