【C++】-- 初级模板

        一个模板就是一个创建类或函数的蓝图或者说公式,其需要我们提供足够的信息,用以将蓝图(模板)准换为特定的类或是函数。(把重复的事交给编译器去做)

目录

1. 泛型编程

2. 函数模板

2.1 函数模板概念与格式

2.2 函数模板的原理

2.3 函数模板错误

2.4 模板参数的匹配原则

3. 类模板

3.1 类模板的定义格式

3.2 类模板的使用


1. 泛型编程

        模板 --> 泛型编程,泛型编程(Generic Programming) 指在针对于多种数据类型上皆可操作。之前的C语言上是针对具体的类型进行编程,就会有很多的限制。于是C语言上,有一个很让人头疼的一个函数:Swap

void Swap(int& e1, int& e2)
{
        int tmp = e1;
        e1= e2;
        e2= tmp;
}

void Swap(double& e1, double& e2)
{
        double tmp = e1;
        e1= e2;
        e2= tmp;
}

// …………

        一个工程里,针对于不同的类型交换我们需要创建不同的Swap函数,但他们的实现原理又是类似的。

2. 函数模板

2.1 函数模板概念与格式

        函数模板是用于生成函数。只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Swap 函数,用以交换不同类型变量的值。

函数模板的写法如下:

        模板定义以template开始,后跟一个模板参数列表,里用逗号分割一至多个模板参数

template <class 类型参数1, class类型参数2, ...>
返回值类型  模板名形参表 )
{
    函数体;
}

        其中的class 关键字也可以用typename 关键字替换。(切记:不能使用struct代替class)

template <typename 类型参数1, typename 类型参数2, ...>

返回值类型  模板名形参表 )
{
    函数体;

Note:在模板定义中,模板参数列表不能为空!

使用举例:

// 泛型编程 -- 模板
// 模板类型(定义:模板类型)
template<typename T>
void Swap(T& e1, T& e2)
{
        T tmp = e1;
        e1= e2;
        e2= tmp;
}

        T代表一个模板类型(虚拟类型),不是一个具体的类型,他只是一个名称,叫什么不重要。(可以随意取名字)

2.2 函数模板的原理

        模板参数列表的作用很像函数参数列表。函数列表定义了若干特定类型的局部变量,但并未指出如何初始化它们。在运行时,调用者提供实参来初始化形参

template<class T>
void Swap(T& e1, T& e2) 
{
    T tmp = e1;
    e1 = e2;
    e2 = tmp;
}


int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Swap(a1, a2);
    Swap(d1, d2);
}

Note: Swap(a1, a2)与Swap(d1, d2)所使用的函数不是同一个!

        其是通过我们所提供的模板函数,创建相对于其成立的函数,所以绝对不是同一个函数实现。并且,他们所调用指令是不一样的(不同的类型),调用的栈帧空间不一样。

        使用的函数不是同一个,是通过函数模板的实例化

        模板的原理:将本应该我们所实现的事交给编译器去做。

        针对于不同的类型,其实实际上还是进行了不同函数的创建,只不过是将,我们所应该创建的函数省去,只写一个模板,让编译器去创建。这样,也必然会让编译的时间变的更加长一点(编译器的工作更多了一些)。就是,我们干更少的活,编译器干更多的活。

2.3 函数模板错误

        模板经过模板参数推演,推演参数实例化后才会生成代码。于是就会导致多种错误。

template<class T>
T Add(const T& e1, const T& e2)
{
	return e1 + e2;
}

int main()
{
	cout << Add(10, 1.12) << endl; // 推演实例化报错
}

        其无法像我们平时所写的函数一样,进行隐形转换。因为在模板参数推演时,会因为无法推演出准确类型,导致推演实例化报错

        有三种方式解决这个问题:

  • 传递前就进行转换。

        编译器自动推演,隐式实例化。将其转换为同一个类型后,让编译器推演类型。

template<class T>
T Add(const T& e1,const T& e2)
{
	return e1 + e2;
}

int main()
{
	cout << Add(10, (int)1.12) << endl;
	// cout << Add((double)10, 1.12) << endl;
}
  • 指定模板参数推演的类型

        显示实例化。不让编译器推演了,直接指定一个类型。

template<class T>
T Add(const T& e1, const T& e2)
{
	return e1 + e2;
}

int main()
{
	cout << Add<int>(10, 1.12) << endl; // 指定T的类型(模板参数有几个就指定几个)
	// count << Add<double>(10, 1.12) << endl;
}
  • 设置个数对应的模板参数

       针对不同类型设置不同对应的模板参数,避免一个模板参数对应多个类型。

template<class T1, class T2>
T2 Add(const T1& e1, const T2& e2)
{
	return e1 + e2;
}

int main()
{
	cout << Add(10, 1.12) << endl;
}

template<class T>
T Func(int e)
{
	return e + 10;
}
int main()
{
	Func(10);
	return 0;
}

         参数是跟T没有关系的,编译器没有办法自动推演,必须要显示实例化,才可以调用。

template<class T>
T Func(int e)
{
	return e + 10;
}

int main()
{
	Func<int>(10);
	return 0;
}

        Note:保证传递给模板的实参支持模板所要求的操作,以及所进行的操作在模板中能正确工作,是调用者的职责。

2.4 模板参数的匹配原则

  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
// 专门处理int的加法函数
int Add(int e1, int e2)
{
	return e1 + e2;
}

// 通用加法函数
template<class T>
T Add(T e1, T e2)
{
	return e1 + e2;
}

int main()
{

	cout << Add(1, 2) << endl; // 与非模板函数匹配,编译器不需要特化
	cout << Add<int>(1, 2) << endl; // 调用编译器特化的Add版本
	return 0;
}

         Add<int>(1, 2)的调用还可以被实例化为T为int类型的非模板函数。

  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。
// 专门处理int的加法函数
int Add(int e1, int e2)
{
	return e1 + e2;
}

// 通用加法函数
template<class T>
T Add(T e1, T e2)
{
	return e1 + e2;
}

int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 1.2) << endl;
	return 0;
}
        编译器会先看,有没有匹配的函数,如果有会优先调用,没有才会利用模板进行实例化。 (有现成用现成)

3. 类模板

        类模板(Class template)是用来生成类的蓝图的。与函数模板的不同之处是,编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在模板名后的尖括号中提供额外信息,用来代替模板参数的模板实参列表。

3.1 类模板的定义格式

类模板的写法如下:

        模板定义以template开始,后跟一个模板参数列表,里用逗号分割一至多个模板参数

template<class T1, class T2, ..., class Tn>

class 类模板名
{
        // 类内成员定义
};

        其中的class 关键字也可以用typename 关键字替换。(切记:不能使用struct代替class)

template<typenameT1, typenameT2, ..., typenameTn>

class 类模板名
{
        // 类内成员定义
};

Note:在模板定义中,模板参数列表不能为空!

3.2 类模板的使用

template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
		:_a(nullptr)
		, _top(0)
		, _capacity(0)
	{
		if (capacity > 0)
		{
			_a = new T[capacity];
			_capacity = capacity;
			_top = 0;
		}
	}
	//…………

private:
	T* _a;
	size_t _top;
	size_t _capacity;
};

int main()
{
    // 类模板都是显示实例化
    // 虽然他们用了一个类模板,但是Stack<int>,Stack<char> 是两个类型
	Stack<int> st1;
	Stack<char> st1;
	return 0;
}

Note: 类模板的使用,必须在模板名后的尖括号中提供信息

  • 将函数定义在类外 

        同一个文件中,模板支持声明与定义分离。

template<typename T>
class Stack
{
    //…………
};

template<typename T>
void Stack<T>::Push(const T& n)
{
	//…………
}

int main()
{
	// 类模板都是显示实例化
	// 虽然他们用了一个类模板,但是Stack<int>,Stack<char> 是两个类型
	Stack<int> st1;
	Stack<char> st2;
	
	st1.Push(1);
	//…………

	return 0;
}

Note:模板不支持分离编译:声明放在.h(头文件)  定义放在.cpp(源文件) 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川入

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值