目录
一、模板的好处与注意事项
二、 声明定义为什么不能不放一起?
模板的好处从下面代码可以体现:
template
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{int a = 3;int b = 4;Swap(a, b);cout << a << " " << b << endl;double c = 2.4;double d = 3.5;Swap(c, d);cout << c << " " << d << endl;return 0;
}
以上不用写两遍Swap,Swapint与Swapdouble,只需利用模板,然后由编译器进行转换处理即可。
再看一段代码:
template
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.0, d2 = 20.0;Add(a1, a2);Add(d1, d2);Add(a1, d2);return 0;
}
注意:
1. typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
2. 对于语句Add(a1, d2);
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。
此时有三种处理方式:1. 用户自己来强制转化 2. 使用显式实例化3.使用多个模板参数
1) cout << Add(a1, (int)d2) << endl;
2) 使用显式实例化;cout << Add
显式实例化:在函数名后的<>中指定模板参数的实际类型
template
T Add(const T& left, const T& right)
{return left + right;
}
int main(void)
{int a = 10;double b = 22.4;// 显式实例化cout << Add(a, b) << endl;//显式实例化:在函数名后的<>中指定模板参数的实际类型return 0;
}
3)使用多个模板参数
注意:这样也是无法推导出模板参数类型的,不要想当然的认为能推导出返回值是int*。
template
T* func(int n)
{return new T[n];
}
int main()
{//int* p = func(10);//未能为T推导模板参数double* p = func(10);return 0;
}
/*函数模板的类型一般是编译器根据实参传递给形参,推演出来的
如果不能自动推演,那么我们就需要显示实例化,指定模板参数*/
对于如下代码需要注意:类模板中函数放在类外进行定义时,需要加模板参数列表
Vector
此外对于类的实例化:
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector才是类型
Vector s1;
Vector s2;
//动态顺序表//注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具template
class Vector
{
public:Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}// 使用析构函数演示:在类中声明,在类外定义。~Vector();
private:T* _pData;size_t _size;size_t _capacity;
};
template
Vector::~Vector()
{if (_pData)delete[] _pData;_size = _capacity = 0;
}
再来看一段代码,当存在专门处理int的函数时
从上运行结果可以知道他不会去调用模板。
如果显示调用,就会去调用模板。
test.c
#include "template.h"int main()
{Rect rect(1.1f, 2.2f, 3.3f, 4.4f);rect.display();return 0;
}
template.h
#pragma once
#include
using namespace std;template
class Rect
{
public:Rect(T l = 0.0f, T t = 0.0f, T r = 0.0f, T b = 0.0f) :left_(l), top_(t), right_(r), bottom_(b) {}void display();
private:T left_;T top_;T right_;T bottom_;
};
template.cpp
#include "template.h"
template
void Rect::display()
{std::cout << left_ << " " << top_ << " " << right_<< " " << bottom_ << std::endl;
}
编译没有错误:
但是CTRL+f5出错:
1. 一个C++项目分为若干个cpp文件和h文件,每个cpp文件单独编译成每个的目标文件,最终将每个cpp文件连接在一起组成最后的单一的可执行文件。这里最重要的点就是:编译是相对于每个cpp文件而言的。
2. 在分离式编译的环境下,编译器编译某一个cpp文件时并不知道另一个cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于链接器)
3. 在没有实例化之前,编译器都是不知道T是什么的。
3. 类Rect, 其类定义式写在tempalte.h,类的实现体写在template.cpp中。由第一点可以知道template.cpp和test.cpp在编译的时候都要展开template.h,于是在test.cpp中出现了声明,在template.cpp中出现了声明与定义,但是两个cpp是相对独立,从第二点可以知道两个cpp是相对独立的,且由第三点,实例化是在test.cpp中的main函数中实现,在test.cpp中只存在声明,编译是可以通过的,但是在链接的过程中,需要找到Rect的实现部分。我们会说不是在template.cpp的编译的时候,展开了头文件,得到了声明和定义吗?为什么不行了?
是因为在template.cpp中由于编译器不知道T的实参是什么,并没有对其进行处理。因为cpp是独立编译的,因此,Rect的实现自然并没有被编译,链接也就自然而然地因找不到而出错。
4. 这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来。所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部链接的符号并期待链接器能够将符号的地址决议出来。然而当实现该模板的cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程中就找不到一行模板实例的二进制代码,于是链接器也黔驴技穷了。
5. 也就是说,模板如果将类声明和类实现进行分离,那么分离式编译模式会导致在链接的时候出现问题。如下代码就运行通过。