一,函数模板
1.函数模板的概念
- C++中提供了函数模板,所谓函数模板,实际上是建立一个通用函数,其函数的返回值类型和函数的参数类型不具体指定,用一个虚拟的类型来表示。这个通用函数就被称为函数的模板。
- 当我们在开发中,经常会遇到一些函数体实现方式类似的函数,例如交换两个字符,交换两个数字函数,这两个函数的函数体实现是一样的,凡是这样的函数,我们都可以通过函数模板的方式来只定义一次。在调用函数时,系统会根据实参的数值类型来取代模板中的虚拟类型。再代入函数体中进行计算,这样就实现了定义一个函数模板,可以实现不同功能的函数。
- 模板把函数或者类要处理的数据类型参数化,表现为参数的多态化。
- 模板用于表达逻辑结构相同,但具体数据类型不同的通用行为。
2.函数模板的推演
# include<iostream> using namespace std; /* 交换两个整数 */ void myswap(int &a, int &b) { int tmp = 0; tmp = a; a = b; b = tmp; } /* 交换两个字符 */ void myswap(char &a, char &b) { char tmp; tmp = a; a = b; b = tmp; } /* * 我们发现上面两个函数的实现逻辑结构相同,要处理的数据类型不同, * 而函数模板恰恰是将数据类型参数的一种机制,所以此处我们在这里定义函数模板 */ template<typename T> void myswap(T &t1, T &t2) { T tmp; tmp = t1; t1 = t2; t2 = tmp; } int main() { int a = 10; int b = 20; char c = 'C'; char d = 'D'; myswap(a, b); myswap(c, d); cout << "a = " << a << ",b = " << b << endl; cout << "c = " << c << ",d = " << d << endl; /* 函数模板方式来处理 */ myswap<int>(a, b); myswap<char>(c, d); cout << "a = " << a << ",b = " << b << endl; cout << "c = " << c << ",d = " << d << endl; return 0; }
3.函数模板的形式
在需要抽象化成模板的函数前面添加:template <typename T1,typename T2......>,在函数体内部要处理的数据类型定义为模板参数中的抽象数据类型即可。
4.函数模板的调用方式
- 通过显式类型调用的方式,即myswap<int>(a,b)这种形式,显式的声明要处理的数据类型。
- 通过数据类型自动推导的方式,即myswap(a,b)这种形式,模板函数会自动匹配传入参数的数据类型。
5.普通函数和函数模板在一起时调用规则
- 函数模板不允许自动类型转换,普通函数可以进行自动类型转换,例如char会转换成int类型。
- 函数模板可以向普通函数那样进行重载。
- 编译器会优先使用普通函数,然后再使用函数模板。
- 可以通过空模板实参列表的方式限定编译器只能通过模板匹配。myswap<>(a,b)
6.C++编译器模板机制剖析
- 编译器并不是把函数模板处理成能够处理任意类的函数。
- 编译器通过函数模板所传递的数据类型产生不同的函数。
- 编译器对函数模板进行两次编译,在定义函数模板的地方对代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
二,类模板
1.为什么需要类模板
类模板的含义与函数模板类似,所谓的类模板是在多个类中的逻辑操作和功能是相同的,仅仅数据的类型不同,我们将数据类型抽象化就形成了类模板。类模板通常用在数据结构和算法这一块,这样在存储数据和对数据进行操作的时候,可以抽象化要操作的数据。形成泛型编程。
2.单个类模板的语法
# include<iostream> using namespace std; /* 定义类模板 */ template<typename T> class A { private: T t; public: A(T t) { this->t = t; } void setT(T t) { this->t = t; } T getT() { return this->t; } }; int main() { A<int> a(10); int res1 = a.getT(); cout << res1 << endl; a.setT(200); int res2 = a.getT(); cout << res2 << endl; return 0; }
3.继承中的类模板
# include<iostream> using namespace std; /* 类模板 */ template<typename T> class Parent { private: T t; public: Parent(T t) { this->t = t; } void getT() { cout << t << endl; } }; /* 类模板继承中的子类为普通类 */ class Child1 :public Parent<int> { public: Child1(int t):Parent(t) { } }; /* 类模板中继承的子类为类模板 */ template<typename T> class Child2 :public Parent<T> { public: Child2(T t) :Parent(t) { } }; int main() { /* 子类为普通类 */ Child1 c1(10); c1.getT(); /* 子类为派生类 */ Child2<char> c2('A'); c2.getT(); return 0; }
4.类模板的定义和类模板的实现不在同一个文件中
- 类模板中的友元函数要特别注意包含<T>,否则会报错,但是实现文件中不需要包含。
- 在使用类模板的时候,引入的头文件是.cpp而不是.h,否则友元函数重载的操作符会报错。
- 如果存储的数据是自定义的类,需要我们把自定义的类重写拷贝构造函数和重载赋值操作符才能保证元素的正确被复制。
三,类模板的应用
1.头文件(Vector.h)
# pragma once # include<iostream> using namespace std; template<typename T> class Vector { private: T * space; int size; public: Vector(int size = 0); Vector(const Vector<T>& v); ~Vector(); public: T& operator[](int index); Vector<T>& operator=(Vector<T>& v); public: friend ostream& operator<<<T>(ostream& out, Vector<T>& v); };
2.实现文件(Vector.cpp)
# include<iostream> # include"Vector.h" using namespace std; template<typename T> Vector<T>::Vector(int size) { this->space = new T[size]; this->size = size; } template<typename T> Vector<T>::Vector(const Vector<T>& v) { this->space = new T[v.size]; this->size = v.size; for (int i = 0; i < v.size; i++) { this->space[i] = v.space[i]; } } template<typename T> Vector<T>::~Vector() { if (this->space != NULL) { delete[] this->space; this->space = NULL; this->size = 0; } } template<typename T> T& Vector<T>::operator[](int index) { return this->space[index]; } template<typename T> Vector<T>& Vector<T>::operator=(Vector<T>& v) { if (this->space != NULL) { delete[] this->space; this->space = NULL; this->size = 0; } this->space = new T[v.size]; this->size = v.size; for (int i = 0; i < v.size; i++) { this->space[i] = v.space[i]; } return *this; } template<typename T> ostream& operator<<(ostream& out, Vector<T>& v) { for (int i = 0; i < v.size; i++) { out << v[i] << " "; } return out; }
3.测试文件(Test.cpp)
# include<iostream> # include"Vector.cpp" using namespace std; class Student { private: char * name; int age; public: Student() { this->name = new char[1]; strcpy(this->name, ""); this->age = 0; } Student(const Student& stu) { this->name = new char[strlen(name) + 1]; strcpy(this->name, name); this->age = age; } Student(char * name, int age) { this->name = new char[strlen(name) + 1]; strcpy(this->name, name); this->age = age; } ~Student() { if (this->name != NULL) { delete[] this->name; this->name = NULL; } } Student& operator=(Student& stu) { if (this->name != NULL) { delete[] this->name; this->name = NULL; } this->name = new char[strlen(stu.name) + 1]; strcpy(this->name, stu.name); this->age = stu.age; return *this; } friend ostream& operator<<(ostream& cout, Student& stu); }; ostream& operator<<(ostream& cout, Student& stu) { cout << "name:" << stu.name << ",age:" << stu.age << endl; return cout; } int main() { Vector<int> v1(10); for (int i = 0; i < 10; i++) { v1[i] = i; } cout << v1 << endl; Vector<int> v2(5); for (int i = 0; i < 5; i++) { v2[i] = i; } cout << v2 << endl; v1 = v2; cout << v1 << endl; Vector<int> v3 = v1; cout << v3 << endl; Student t1("刘备", 49); Student t2("关羽", 38); Student t3("张飞", 34); Vector<Student> vTeacher(3); vTeacher[0] = t1; vTeacher[1] = t2; vTeacher[2] = t3; cout << vTeacher << endl; return 0; }