三、类模板
1.类模板定义
当有多个类时,其功能相同,仅仅数据类型不同,就可以使用类模板:
1. template <class T1, class T2, ……, class Tn> 2. class 类模板名 3. { 4. //类内成员定义 5. };
例如对于链表,以下代码只能插入整型结点,因为限定了VDataType的类型为int型:
1. #include<iostream> 2. #include<assert.h> 3. using namespace std; 4. 5. typedef int VDataType;//限定了VDataType的类型只能为int型 6. class vector 7. { 8. public: 9. //构造函数 10. vector() 11. :_a(nullptr) 12. , _size(0) 13. , _capacity(0) 14. 15. {} 16. 17. //析构函数 18. ~vector() 19. { 20. delete[] _a; 21. _a = nullptr; 22. _size = _capacity = 0; 23. } 24. 25. //读写第pos个位置的元素 26. int& operator[](size_t pos) 27. { 28. assert(pos < _size); 29. return _a[pos]; 30. } 31. 32. //求数组大小 33. size_t size() 34. { 35. return _size; 36. } 37. 38. //插入节点 39. void push_back(const int& x) 40. { 41. if (_size == _capacity) 42. { 43. int newCapacty = _capacity == 0 ? 4 : _capacity * 2; 44. int* tmp = new int[newCapacty]; 45. if (_a) 46. { 47. memcpy(tmp, _a, sizeof(int) * _size); 48. delete[] _a; 49. } 50. _a = tmp; 51. _capacity = newCapacty; 52. } 53. 54. _a[_size] = x; 55. ++_size; 56. } 57. 58. private: 59. VDataType* _a; 60. int _size; 61. int _capacity; 62. };
但是现在如果想借助这一份代码,让链表v1存int型数据,让另一个链表v2存double型数据,该怎样实现呢?使用类模板:
1. #include<iostream> 2. #include<assert.h> 3. using namespace std; 4. 5. template<class T>//类模板定义 6. class vector 7. { 8. public: 9. //构造函数 10. vector() 11. :_a(nullptr) 12. , _size(0) 13. , _capacity(0) 14. 15. {} 16. 17. //析构函数 18. ~vector() 19. { 20. delete[] _a; 21. _a = nullptr; 22. _size = _capacity = 0; 23. } 24. 25. //读写第pos个位置的元素 26. T& operator[](size_t pos) 27. { 28. assert(pos < _size); 29. return _a[pos]; 30. } 31. 32. //求数组大小 33. size_t size() 34. { 35. return _size; 36. } 37. 38. //插入节点 39. void push_back(const T& x) //指定参数类型 40. { 41. if (_size == _capacity) 42. { 43. int newCapacty = _capacity == 0 ? 4 : _capacity * 2; 44. T* tmp = new T[newCapacty];//指定tmp类型,以便后面赋值给_a 45. if (_a) 46. { 47. memcpy(tmp, _a, sizeof(int) * _size); 48. delete[] _a; 49. } 50. _a = tmp; 51. _capacity = newCapacty; 52. } 53. 54. _a[_size] = x; 55. ++_size; 56. } 57. 58. private: 59. T* _a; //指定成员变量类型 60. int _size; 61. int _capacity; 62. };
vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具。以上仅仅只是类模板的显式指定,如何向v1中插入int型数据,向v2中插入double型数据呢?
1. int main() 2. { 3. delia::vector<int> v1; //v1存放int型数据,生成T是int型的vector 4. v1.push_back(1); 5. v1.push_back(2); 6. 7. //v1[i]等价于v1.operator()[i] 8. for (size_t i = 0; i < v1.size(); i++) 9. { 10. cout << v1[i] << " "; 11. } 12. cout << endl; 13. 14. for (size_t i = 0; i < v1.size(); i++) 15. { 16. v1[i] *= 2; 17. cout << v1[i] << " "; 18. } 19. cout << endl; 20. 21. delia::vector<double> v2; //v2存放double型数据,生成T是double型的vector 22. v2.push_back(1.1); 23. v2.push_back(2.2); 24. 25. return 0; 26. }
由于库里面有vector头文件,假如在库里包含了#include的头文件,编译器就不知道到底要调库里的vector还是我们自己写的vector。把库里的vector放在std命名空间中,把自己写的vector放在另一个命名空间中,就能把两个vector隔离开了:
1. #include<iostream> 2. #include<vector> 3. #include<assert.h> 4. using namespace std; 5. 6. namespace delia 7. { 8. template<class T>//类模板定义 9. class vector 10. { 11. public: 12. //构造函数 13. vector() 14. :_a(nullptr) 15. , _size(0) 16. , _capacity(0) 17. 18. {} 19. 20. //析构函数 21. ~vector() 22. { 23. delete[] _a; 24. _a = nullptr; 25. _size = _capacity = 0; 26. } 27. 28. //读写第pos个位置的元素 29. T& operator[](size_t pos) 30. { 31. assert(pos < _size); 32. return _a[pos]; 33. } 34. 35. //求数组大小 36. size_t size() 37. { 38. return _size; 39. } 40. 41. //插入节点 42. void push_back(const T& x) //指定参数类型 43. { 44. if (_size == _capacity) 45. { 46. int newCapacty = _capacity == 0 ? 4 : _capacity * 2; 47. T* tmp = new T[newCapacty];//指定tmp类型,以便后面赋值给_a 48. if (_a) 49. { 50. memcpy(tmp, _a, sizeof(int) * _size); 51. delete[] _a; 52. } 53. _a = tmp; 54. _capacity = newCapacty; 55. } 56. 57. _a[_size] = x; 58. ++_size; 59. } 60. 61. private: 62. T* _a; //指定成员变量类型 63. int _size; 64. int _capacity; 65. }; 66. }
现在可以打印某个位置的数据了:
1. int main() 2. { 3. delia::vector<int> v1; //v1存放int型数据,生成T是int型的vector 4. v1.push_back(1); 5. v1.push_back(2); 6. 7. //v1[i]等价于v1.operator()[i] 8. for (size_t i = 0; i < v1.size(); i++) 9. { 10. cout << v1[i] << " "; 11. } 12. cout << endl; 13. 14. for (size_t i = 0; i < v1.size(); i++) 15. { 16. v1[i] *= 2; 17. cout << v1[i] << " "; 18. } 19. cout << endl; 20. 21. delia::vector<double> v2; //v2存放double型数据,生成T是double型的vector 22. v2.push_back(1.1); 23. v2.push_back(2.2); 24. 25. return 0; 26. }
如果函数声明和定义分开,比如在类里面声明函数,但是在类外面定义函数,需要在函数定义的位置再定义一个模板参数,让编译器知道T是一个模板类型,否则编译器识别不了T是从哪里来的,是什么:
1. #include<iostream> 2. #include<vector> 3. #include<assert.h> 4. using namespace std; 5. 6. namespace delia 7. { 8. template<class T>//类模板定义 9. class vector 10. { 11. public: 12. //构造函数 13. vector() 14. :_a(nullptr) 15. , _size(0) 16. , _capacity(0) 17. 18. {} 19. 20. //析构函数 21. ~vector() 22. { 23. delete[] _a; 24. _a = nullptr; 25. _size = _capacity = 0; 26. } 27. 28. //类里面,读写第pos个位置的元素:函数声明 29. T& operator[](size_t pos); 30. 31. //求数组大小 32. size_t size() 33. { 34. return _size; 35. } 36. 37. //插入节点 38. void push_back(const T& x) //指定参数类型 39. { 40. if (_size == _capacity) 41. { 42. int newCapacty = _capacity == 0 ? 4 : _capacity * 2; 43. T* tmp = new T[newCapacty]; 44. if (_a) 45. { 46. memcpy(tmp, _a, sizeof(int) * _size); 47. delete[] _a; 48. } 49. _a = tmp; 50. _capacity = newCapacty; 51. } 52. 53. _a[_size] = x; 54. ++_size; 55. } 56. 57. private: 58. T* _a; //指定成员变量类型 59. int _size; 60. int _capacity; 61. }; 62. 63. //类外面,读写第pos个位置的元素:函数定义 64. template<class T>//用模板类型指定了具体类型 65. T& vector<T>:: operator[](size_t pos) 66. { 67. assert(pos < _size); 68. return _a[pos]; 69. } 70. }
2.类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
delia::vector<int> v1; //vector是类模板名字,vector<int>才是类型