编程细节:
模板类的编写和非模板类编写上有个很大区别,类定义和声明不可以分开写,成员函数的定义要写在头文件中!因为这个问题坑了好久!!!
|--------------------------------------------------------------------------------------------------------
原理阐述:
为什么需要template理念?
这些高级的语法都是为了使得编程更简单!减少代码冗余!
之前的文章中,讲过函数模板,函数实现的内容相同,只有数据类型不同!针对这样的情况,我们将数据抽离出来以类型参数代替。面向对象的理念是将具有继承关系的类的方法抽离出来,视情况实现一份或是多份。总之,目标是能不自己写代码就不写,利用编译器为我们提供的机制,易读更安全!
template 参数可分为类型参数和非类型参数!分别来说说吧!
类型参数:
将类设计中所依赖的数据类型抽离出作为类型参数!实例化时将根据具体的类型生成不同的特殊类。数据类型可以是内建类型、自定义类型!其中一个比较特别的自定义类型,函数对象类型!不同的参数类型,也能决定函数实现的内容了!
当我们的参数类型是函数对象时,在模板类的内部可以使用不同的函数实现来完成我们的类设计!这已经不是最初哪个简单地与数据类型无法的模板类设计初衷了,而是使得模板类可实现的内容更广泛!
先来份简单的抽离数据类型:
其实上面的编程是存在缺陷的,我们并不知道实际的数据类型是什么,类内有实现比较运算符吗?不清楚啊!这说明我们的类设计不安全,如何解决呢?在使用该函数对象时,必须要指出比较方式!比较方式由用户实现(函数对象类型),为此,在实现比较元素的类的类型参数多加一个参数(实例化为函数对象)进行比较方式的确定。下图中有一个编程问题,调用模板函数对象的时候一定要初始化后再传递参数!原写为**Comp(val,_val)**在visual studio 2015 编译产生错误!应该为Comp()(val,_val)!!!先创建对象才能调用方法!
#include<functional> #include<string> //默认参数又是一个模板类,类型与这个模板类的参数类型有关 template<class EleType,class OperatorType=std::less<EleType>> class Compare { private: EleType _val; public: Compare(const EleType &v):_val(v){} bool operator()(const EleType & c) { //OperatorType a; //return a(_val, c); return OperatorType()(_val, c); } void set_value(EleType & c) { _val = c; } const EleType& get_value() const { return _val; } }; //具体类,就是用于比较字符串的 class OperatorString { public: bool operator()(const std::string & a, const std::string & b) { return a.size() < b.size(); } }; #include<vector> #include<algorithm> #include<iostream> #include"compare.h" int main() { //ompare<int> a(10); std::vector<int > b = { 4,3,56,7,2 ,3}; auto it1 = find_if(b.begin(), b.end(), Compare<int>(3)); std::cout<<*it1<<std::endl; std::vector < std::string > a = { "kdfj","lxy","lxy" }; //sort(a.begin(), a.end(),Compare); auto it = a.begin(); //auto temp = a.begin(); while (( it= find_if(it, a.end(), Compare<std::string, OperatorString>("a"))) != a.end()) std::cout << *it++ << " "; std::cout << std::endl; std::cin.get(); return 0; }
另一个例子,体现了参数类型是用户自定义类型!而在模板类的设计中可以直接使用类型参数的方法,在实例化为不同的类型后,这么一个模板类可表现出多个方法实现内容不同的类!感觉脑洞好大!!!好灵活!!!
模板类的设计同普通类相似,支持继承(多态)、友元等概念。
template<class T> std::ostream & operator<<(std::ostream & os, BinaryTree<T> &a); //模板函数作为友元函数 template<class T> std::ostream & operator<<(std::ostream & os, BinaryTree<T> &a) { a.display(); return os; } template<class T> class BinaryTree { friend std::ostream & operator<<(std::ostream & os, BinaryTree<T> &a); //模板函数作为友元函数写法 前面的模板声明可省略不写 };
模板类的继承,比较特别地,基类类使用派生类的类型参数或是非类型参数。需要搞清的地方是,使用模板类的时候,需要清楚地知道何时写明参数列表,如何写,含义是什么?
类型参数列表何时需要写出来?
若在模板类的定义中或是类作用域内,可以不特意写出类型参数列表,除此之外,均要明确写出参数类型列表。下面由于在类的定义内,没有写出类型的参数列表。
非类型参数:
常量表达式,类型已知,数值不知,可以在类中的任意位置使用该非类型参数(常量,只可读不可修改)。模板类依赖于一个常量,在多个方法的实现都需要这个常量的协助!甚至说有了这个常量,使得我们的派生类的设计更简单,而这个常量可以表示在数据成员或是类型参数中,对应着两种设计理念。
带有默认值的template参数,默认值的使用要求与函数默认值相同,从最右侧起赋予默认值!
全局域中的函数地址也是个常量,因此声明函数指针变量作为非类型参数,在模板类中通过该指针便可以调用多个不同的函数。也是模板功能的体现!
下面是个关于非类型参数模板类的使用,同时包含了继承、多态的应用,非模板的设计在这里都是通用的!
#include<vector> #include<iostream> template<int len,int begin> class Numsequence{ public: virtual ~Numsequence(){} int get_value(int pos); std::ostream &display(std::ostream&os=std::cout); static int max_size() { return max_s; } protected: Numsequence(std::vector<int> *p) :point_to_vector(p) {} const static int max_s = 1024; virtual void gen_ele(int pos) = 0; std::vector<int> *point_to_vector; bool is_legal_pos(int pos, int size); }; template<int len,int begin> const int Numsequence<len,begin>:: max_s = 1024; template<int len,int begin> class Fon :public Numsequence<len, begin> { public: Fon():Numsequence<len,begin>(&values){ gen_ele(len + begin - 1); } protected: std::vector<int> values; virtual void gen_ele(int pos); }; template<int len,int begin> int Numsequence<len, begin>::get_value(int pos) { if (is_legal_pos(pos, (*point_to_vector).size())) return (*point_to_vector)[pos]; else return -1; } template<int len,int begin> std::ostream & Numsequence<len, begin>::display(std::ostream&os) { if (is_legal_pos(len + begin - 1, (*point_to_vector).size())) { for (int i = 0; i < len + begin; i++) { os<<(*point_to_vector)[i]<<" "; } os << std::endl; } return os; } template<int len,int begin> bool Numsequence<len, begin>::is_legal_pos(int pos, int size) { if (pos<0 || pos>max_s) return false; else if(pos>size) { gen_ele(pos); } return true; } template<int len,int begin> void Fon<len, begin>::gen_ele(int pos) { //std::cout << values.size()-1 << std::endl; //if() while (int(values.size() )-1< pos) { //std::cout << "aaa" << std::endl; if (values.size() == 1 || values.size() == 0) values.push_back(1); else { values.push_back(values[values.size() - 1] + values[values.size() - 2]); } } } #include"numSequence.h" template<int len,int begin> std::ostream & operator<<(std::ostream & os,Numsequence<len,begin> & a); int main() { Fon<3, 0> a; // a.display(); std:: cout << a; std::cin.get(); return 0; } //体现了多态 template<int len, int begin> std::ostream & operator<<(std::ostream & os, Numsequence<len, begin> & a) { return a.display(os); }
模板类的定义:
需要注意模板类的成员函数在类外定义的写法,如下所示,类型作用域之后的类型可以不加参数类型列表:
使用模板类对象的函数定义:
使用参数类型作为函数参数,由于不知道参数类型是内建类型还是class类型,因此使用引用类型最靠谱!同理在构造函数内,使用初始化列表最靠谱!
如果希望一个函数能处理未具体化的模板类,而不希望采取实例化的类模板对象后编写操作他们的多个函数(函数的实现是类似的)这种方式,我们可以使用模板函数,仍使得模板类是抽象的,便能利用一个模板函数处理多个不同的实例化对象!这里就将模板类和模板函数的使用打通了!
成员模板函数:
类似于将类的成员函数声明为某个类的友元成员函数,也可以将类的成员函数声明为模板函数,又或是将模板类的成员函数声明为一个模板函数(需不需要声明为模板函数,就看所需的参数类型)
这种情况的存在是某一个成员函数需要应对多种不同的数据类型,模板函数的具体化与类的具体化不同之处在于类的具体化必须要显示指出具体类型而函数的具体化根据参数类型即可确定。注意下面两个的区别:
#include "stdafx.h" #include<iostream> //封装输出流 class Print { private: std::ostream& os;//必须使用引用 public: Print(std::ostream &c):os(c){} template<class T> std::ostream & print(const T & value,char tag='\n') { os << value << tag; return os; } }; int main() { Print p(std::cout); p.print("hello");//根据函数参数类型实例化模板函数 p.print(1); std::cin.get(); return 0; }
#include<iostream> template<class T> class BinaryTree; template<class T> class Node { friend class BinaryTree<T>; public: Node(const T &v):_value(v),_left(nullptr),_right(nullptr),count(1){} void insert_value(const T &v); private: T _value; int count; Node * _left; Node * _right; void left_to_right(Node *l, Node * r); void remove_root(Node*&p); void remove_value(Node *&p,T &v); void preorder(Node<T> *); }; template<class T> class BinaryTree { friend std::ostream & operator<<(std::ostream & os, BinaryTree<T> &a); //模板函数作为友元函数写法 public: BinaryTree():_root(nullptr){} void insert(const T &v); void display(); void remove(T &v); bool isempty() { return _root == nullptr; } private: Node<T> *_root; }; template<class T> void BinaryTree<T>::insert(const T &v) { if (_root == nullptr) _root = new Node<T>(v); else { _root->insert_value(v); } } template<class T> void BinaryTree<T>::remove(T &v) { if (_root->_value == v) _root->remove_root(_root); else { _root->remove_value(_root, v); } } template<class T> void Node<T>::remove_root(Node*&p) { if (_right) { p = _right; if (p->_left) left_to_right(_left, _right); else p->_left = _left; } else { p = _left; } delete this; } template<class T> void Node<T>::left_to_right(Node *l, Node *r)//这里不改变指针的值,只改变指针所指向的内容 故参数可以不使用引用类型 { while (r->_left) r = r->_left; r->_left = l; } template<class T> void Node<T>::insert_value(const T &v) { if (_value == v) { count++; return;//这里需要再查一下 return到哪里去了?? } else if (_value < v) { if (!_right) _right = new Node(v); else _right->insert_value(v); } else { if (!_left) _left = new Node(v); else _left->insert_value(v); } } template<class T> void Node<T>::remove_value(Node *&p, T &v) { if (_value < v) { if (!_right) return; else _right->remove_value(_right, v); } else if (_value > v) { if (!_left) return; else _left->remove_value(_left, v); } else { Node * temp = p; if (_right) { p = _right; if (_left) { if (p->_left) left_to_right(_left, _right); else p->_left = _left; } } else { p = _left; } delete temp; } } template<class T> void Node<T>::preorder(Node<T> *p) { if (p) { std::cout << p->_value << " "; if (p->_left) preorder(p->_left); if (p->_right) preorder(p->_right); } } template<class T> void BinaryTree<T>::display() { _root->preorder(_root); std::cout << std::endl; } #include"tree.h" int main() { BinaryTree<int>a; a.insert(2); a.insert(3); return 0; }
模板函数常在模板类型作为函数参数时被需要,由于具体模板类型不知道,要么指定明确的模板类型,要么利用模板函数。
模板函数作为模板类的友元函数要格外注意别丢了参数类型的声明!
下面这个例子就充分说明了利用模板类作为函数参数的用法和易错点!!!
#include<iostream> template<class Eletype> class Matrix { //函数模板应该如何声明为类的友元函数! 加上模板修饰! template<class Eletype> friend Matrix<Eletype> operator+(const Matrix<Eletype> & a, const Matrix<Eletype> &b); template<class Eletype> friend Matrix<Eletype> operator*(const Matrix<Eletype> &a, const Matrix<Eletype> &b); /*friend Matrix<Eletype> operator+(const Matrix<Eletype> & a, const Matrix<Eletype> &b); friend Matrix<Eletype> operator*(const Matrix<Eletype> &a, const Matrix<Eletype> &b);*/ private: int _rows; int _cols; Eletype *p; public: Matrix(int r, int c) { _rows = r; _cols = c; int total = r*c; p = new Eletype[total]; for (int i = 0; i < total; i++) { p[i] = Eletype(); } } ~Matrix() { delete[]p; } Matrix(const Matrix & copy); Matrix & operator=(const Matrix &c); int rows() const { return _rows; } int cols() const { return _cols; } std::ostream & display(std::ostream & os = std::cout) const; Matrix & operator+=(const Matrix & a); Eletype& operator()(int r, int c)//r c取值从0开始 { return p[r*_cols + c]; } const Eletype& operator()(int r, int c) const//函数对象的定义 重载() { return p[r*_cols + c]; } bool is_same(const Matrix & c) const { if (_rows == c._rows&&_cols == c._cols) return true; else return false; } }; template<class Eletype> Matrix<Eletype>::Matrix(const Matrix & copy) { int total = copy.cols()*copy.rows(); _rows = copy._rows; _cols = copy._cols; p = new Eletype[total]; for (int i = 0; i < total; i++) p[i] = copy.p[i];//由于在matrix的作用域内因而可以访问私有成员 } template<class Eletype> Matrix<Eletype> & Matrix<Eletype>::operator=(const Matrix &c) { if (this == &c) return *this; delete[]p; _cols = c._cols; _rows = c._rows; int total = _cols*_rows; p = new Eletype[total]; for (int i = 0; i < total; i++) { p[i] = c.p[i]; } return *this; } template<class Eletype> std::ostream & Matrix<Eletype>::display(std::ostream & os = std::cout) const { int total = _cols*_rows; for (int i = 0; i < total; i++) { os << p[i] << ((i%_cols-1) ? ' ' : '\n');//条件表达式两个不同的取值类型要相同 } return os; } template<class Eletype> Matrix<Eletype> & Matrix<Eletype>::operator+=(const Matrix & a) { if (is_same(a)) { int total = _cols*_rows; for (int i = 0; i < total; i++) { p[i] += a.p[i]; } } return *this; } #include"matrix.h" #include<vector> //在使用一个模板类作为函数参数的函数时 通常需要将这个函数声明为模板函数(处理操作相同时所采用的方法)!不然呢 !一个个具体化模板类后写不同的函数吗??? //由于不知道模板类的具体类型 因而可以定义为模板函数 也可以指定具体类型 但这样不是麻烦吗 需要为不同的类型都编写函数 //问题关键是!在此处为模板函数后 在类中声明为友元函数的写法 就不是一个普通的函数 而不是一个模板函数 无论声明的类是不是抽象类 模板函数都要加上一堆的修饰 template<class Eletype> Matrix<Eletype> operator+(const Matrix<Eletype> & a, const Matrix<Eletype> &b) { Matrix<Eletype> result(a); result += b;//尽可能的利用类中已经实现的方法 ,这样更简洁和安全 ,测试工作就少了! return result; } template<class Eletype> Matrix<Eletype> operator*(const Matrix<Eletype> &a, const Matrix<Eletype> &b) { Matrix<Eletype> temp = Matrix<Eletype>(a._rows, b._cols); //理解多重循环! //目标矩阵乘法运算 两层循环来控制矩阵的各个元素 (外层两个循环)利用这两个变量 是否还需要其他变量结合矩阵乘法运算规则就可以写出下面的循环 多层循环需要利用前面的循环变量 for (int i = 0; i < a._rows; i++) { for (int j = 0; j < a._cols; j++) { temp(i, j) = 0; for (int k = 0; k < b._rows; k++) temp(i, j) += a(i, k) * b(k, j); } } return temp; } int main() { std::vector<int> a = { 1,3,4,5 }; Matrix <int >b(2, 2); for (int i = 0; i < b.rows(); i++) for (int j = 0; j < b.cols(); j++) b(i, j) = a[i*b.rows() + j]; b.display(); b = b*b; b.display(); Matrix<int> c(b); c += b; c.display(); Matrix <int>result(2, 2); result = c+b; result.display(); result = result*b; result.display(); std::cin.get(); return 0; }