C++【模板进阶】

目录

1. 模板的一些使用的细节 

2. 非类型模板参数

3. 模板的特化

4. 模板的分离编译

写在最后:


1. 模板的一些使用的细节 

我们直接来看一下场景:

#include 
#include 
#include 
using namespace std;

template
void Print(const Container& v) {
	Container::const_iterator it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

void test1() {
	vector v{ 1, 2, 3, 4 };
	list lt{ 1, 2, 3, 4 };
	Print(lt);
}

int main()
{
	test1();

	return 0;
}

这段代码我们是实现了一个泛型,让这个 Print 拥有了遍历各种容器的能力,

但是这段代码是会被编译器报错的:

C++【模板进阶】_第1张图片

编译器这里告诉给我们很清楚了,叫我们在 const_iterator 类型前面加个 typename

template
void Print(const Container& v) {
	typename Container::const_iterator it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

加上之后,确实是能跑过了:

C++【模板进阶】_第2张图片

那这是为什么呢?

因为编译器不知道你这个模板参数::const_iterator 究竟是一个类型还是一个对象(类型),

这里我可以举一个例子:

class A {
public:
	static int const_iterator;
};

如果有这样一个类,可以用 A::const_iterator,

那么编译器有怎么知道该怎么实例化呢?

所以你需要加上一个 typename 告诉编译器,你是一个类型,

这样编译器就让你过了。

但其实还有一种更方便的写法:

template
void Print(const Container& v) {
	auto it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

是的,欢迎来到 auto 宣传片~,auto 一定是一个类型,所以编译器就不会报错了。

其实我们在学习优先级队列的时候就遇到过这样的场景:

他下面的这一段就是 typename 的应用场景,

当我们用到模板参数来取内嵌类型的时候,都要用 typename 提醒一下编译器我们是一个类型。 

2. 非类型模板参数

我们直接来看场景,

比如说我们想要实现一个静态的栈:

让栈1 的容量是 10 而栈2 的容量是 100 ,显然做不到。

#define N 10

template
class stack {
private:
	T _a[N];
	int _top;
};

void test2() {
	stack st1;
	stack st2;
}

这个时候我们可以用非类型模板参数来实现:(是的我们以前的叫做类型模板参数)

template
class stack {
private:
	T _a[N];
	int _top;
};

void test2() {
	stack st1;
	stack st2;
}

这样就可以通过模板来控制这个值了。

3. 模板的特化

我们直接来看一个场景:

template
int Com(T a, T b) {
	return a > b;
}

// 模板的特化
template<>
int Com(int* a, int* b) {
	return *a > *b;
}

void test3() {
	int a = 1, b = 0;
	cout << Com(a, b) << endl;
	cout << Com(&a, &b) << endl;
}

如果我们想对一种特殊的情况进行不同的操作的时候,

我们可以使用模板特化的技术,因为模板会根据最匹配的一种来进行实例化。

但是,其实这种情况我们重载一个 T* 类型的函数似乎也能够做到,

那我们再来看一个例子:

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

void test4() {
	Date d1;
	Date d2;

输出:

C++【模板进阶】_第3张图片

假如我想让用 这个类模板的时候使用不同的逻辑,

我们就可以使用模板特化:

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

template<>
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

void test4() {
	Date d1;
	Date d2;
}

输出:

C++【模板进阶】_第4张图片

我们可以看到传不同的模板参数,就可以走不同的逻辑。

我们上面的操作是全特化,其实我们可以部分特化,也就是偏特化:

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

void test4() {
	Date d1;
	Date d2;
}

这样的操作也是支持的。

偏特化其实还支持一种操作,就是对某些类型进行限制:

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

template
class Date {
public:
	Date() { cout << "Date" << endl; }
private:
};

void test4() {
	Date d1;
	Date d2;
	Date d3;
}

输出:

C++【模板进阶】_第5张图片

 就像这种情况。 

4. 模板的分离编译

模板是不支持分离编译的。

来看这个例子:

Stack.h 声明:

C++【模板进阶】_第6张图片

 Stack.cpp 定义

C++【模板进阶】_第7张图片

 test.cpp 调用:

C++【模板进阶】_第8张图片

 出现链接错误:

实际上,模板还没实例化,导致编译器链接的时候找不到定义。

那有没有什么方式可以解决呢?

C++【模板进阶】_第9张图片

可以在这里进行显示实例化操作,这样就可以支持了。 

但是这种方式也不好用,如果别人再传一个 double 类型,

又得写一个显示实例化 double 的操作,还是很麻烦的。

所以如果使用模板的话比较建议的是如果有很大的函数,

可以在同一个文件里面声明和定义分离(一个在类内一个在类外)

写在最后:

以上就是本篇文章的内容了,感谢你的阅读。

如果感到有所收获的话可以给博主点一个哦。

如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~

你可能感兴趣的:(C++学习,c++,开发语言)