目录
第一节:模板是什么
第二节:函数模板
2-1.使用场景
2-2.使用方法
2-3.模板原理
2-4.使用细节
第三节:类模板
3-1.使用场景
3-2.使用方法
3-3.类模板中成员函数的声明与定义分离
3-4.使用细节
第一节:模板是什么
现实生活中的模具,照着模具可以得到形状相同的模型,然后使用不同颜色的材料就可以得到不同颜色的模型。
模板也一样,编译器根据模板可以得到大体相似的模型,它们只有细微的区别。模板分为函数模板、类模板,下面一个一个来介绍。
第二节:函数模板
2-1.使用场景
函数模板就是可以实例化出函数的模板,对于一些功能相似,但是类型不同的参数的函数有奇效,例如要交换两个数的值,如果两个数都是整型:
void Swap(int& a, int& b) {int tmp = a;a = b;b = tmp;
}
如果两个数都是浮点型:
void Swap(float& a, float& b) {float tmp = a;a = b;b = tmp;
}
2-2.使用方法
对比以上两段代码可以发现,函数功能都是交换两个数,所以将类型 int 替换成 float 即可,除了int和float,还有char、double、自定义类型等类型,全部写出来的话就太多了,既然逻辑一样,那么就可以用一个模板函数来代替这么多不同参数的函数:
template<class T>
void Swap(T& a, T& b) {T tmp = a;a = b;b = tmp;
}
template标识这是一个模板,而class T就是我们需要给这个模板提供的类型,例如现在需要交换int类型的两个数:
template<class T>
void Swap(T& a, T& b) {T tmp = a;a = b;b = tmp;
}
int main() {int a = 2, b = 3;Swap<int>(a, b);std::cout << "a:" << a << std::endl;std::cout << "b:" << b << std::endl;return 0;
}
就像函数传参一样将 int 传给模板,模板的 T 接收到之后将 T 换成 int 实例化出了一个新的函数。
如果我又想交换两个 float 类型的数:
int main() {float a = 2.1, b = 3.2;Swap<float>(a, b);std::cout << "a:" << a << std::endl;std::cout << "b:" << b << std::endl;return 0;
}
除了使用<类型>显示传入类型外,函数模板也会自动识别传入参数的类型并实例化相应的函数:
template<class T>
void Swap(T& a, T& b) {T tmp = a;a = b;b = tmp;
}
int main() {int a = 2, b = 3;Swap(a, b);std::cout << "a:" << a << std::endl;std::cout << "b:" << b << std::endl;return 0;
}
2-3.模板原理
需要注意的是模板实例化出的函数不是同一个函数,即Swap<int>和Swap<float>虽然都使用了模板,但是调用的不是同一个函数,而是编译器在编译阶段利用模板自己写了以下两个函数,和我们自己写的没有区别。
void Swap(int& a, int& b) {int tmp = a;a = b;b = tmp;
}
void Swap(float& a, float& b) {float tmp = a;a = b;b = tmp;
}
相当于编译器帮助我们写了我们该写的代码。
2-4.使用细节
模板函数也有很多使用上的细节,如果有现成的函数与函数模板冲突时,会优先调用现成的函数:
template<class T>
void func(T x) {std::cout << "调用了函数模板:" << x << std::endl;
}void func(int x) {std::cout << "调用了现成函数:" << x << std::endl;
}int main() {int a = 99;func(a);return 0;
}
模板名和现有函数名都是 func,但是会优先调用已经有的函数,而不会实例化一个再调用。
当参数类型与现成的函数不匹配时,会使用函数模板:
template<class T>
void func(T x) {std::cout << "调用了函数模板:" << x << std::endl;
}void func(int x) {std::cout << "调用了现成函数:" << x << std::endl;
}int main() {float a = 99; // int->floatfunc(a);return 0;
}
如果想要强制使用函数模板,可以加上<类型>进行显示实例化:
template<class T>
void func(T x) {std::cout << "调用了函数模板:" << x << std::endl;
}void func(int x) {std::cout << "调用了现成函数:" << x << std::endl;
}int main() {int a = 99;func<int>(a); // 显式实例化return 0;
}
第三节:类模板
3-1.使用场景
类模板主要用于一些容器,比如vector、queue、map等,其中vector类似于C语言中的数组,C语言的数组的元素可以是char、int等,vector就是对传统数组的封装,为了能够像传统数组一样作用与多种类型,故其使用了类模板。
3-2.使用方法
使用方法与函数模板相似:
template<class T>
class A {
public:void PrintVal() {std::cout << _val << std::endl;}T _val;
};
想要创建一个 _val 为 float 类型的类,只能使用显式实例化:
int main() {A<float> obj;return 0;
}
3-3.类模板中成员函数的声明与定义分离
类模板实例化出的类的成员函数也可以进行声明与定义分离,需要标注函数来自哪个类域:
template<class T>
class A {
public:void PrintVal(); // 成员函数声明T _val;
};template<class T> // 成员函数声明定义
void A<T>::PrintVal() {std::cout << _val << std::endl;
}
3-4.使用细节
来自同一模板、类型不同的两个对象不属于同一类,例如:
int main() {A<int> a1;A<float> a2;return 0;
}
虽然a1、a2都是由模板A而来,但却是A实例化出的两个类,这两个类再实例化而来,所以a1、a2不属于同一类。
第四节:下期预告
下一次将详细介绍string容器,主要是迭代器的实现。