本博文主要讨论函数模板与类模板以及其简单应用。
1)、作用:函数模板和类模板都可以看做是一种代码产生器,往里面放入具体的类型,得到具体化的函数或者class。
2)、编译(分为两步):
a):实例化之前,先检查模板本身语法是否正确;
b):根据 函数调用或者类模板调用 ,先去实例化模板代码,产生具体的函数/类。
也就是说, 没有函数调用或者类类型对象声明,就不会实例化模板代码,在目标文件obj中找不到模板的痕迹。
3):优缺点
模板的缺点是代码膨胀,编译速度慢,而优点是运行速度快。
一、函数模板:
1)、简单操作,代码实现如下:
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 using namespace std; 5 6 template <typename T> 7 //修改办法2: 8 //T max(const T &a, const T &b) 9 const T &max(const T &a, const T &b) 10 { 11 return a > b ? a: b; 12 } 13 14 int main(int argc, const char *argv[]) 15 { 16 cout << ::max(7, 42) << endl; //ok 17 18 cout << ::max(5.5, 7.8) << endl; //ok 19 20 string s1 = "hello"; 21 string s2 = "world"; 22 cout <<::max(s1, s2)<< endl; //ok 23 24 //cout << ::max("hello", "worldd");//error char[6],char[7] 25 //虽然都是字符数组,但是长度也是参数的一部分,故两者不是同一类型 26 27 //cout << ::max(3, 4.5) << endl;//error 28 //会发生编译错误,因为编译器推断第一个类型为int,第二个为double,没有一个模板符合这个要求,这样就会发生强制转换而产生一个局部的中间变量。这个变量的引用作为return的返回值;即此时我们引用了一个局部变量,这时会产生错误. 29 //修改办法1: 30 cout <<::max<int>(3, 6.5) << endl; 31 cout << ::max(3, static_cast<int>(6.5)) << endl; 32 33 return 0; 34 }
2)、一个非模板函数可以和一个同名的函数模板同时存在;两者可以因为参数不同而构成重载;
模板函数重载时,现则函数版本的一些特点:
a):条件相同时,选择非模板函数;例如 ::max(7,42);
b):在强制类型转化,与可行的实例化模板之间,优先选择实例化模板;例如::max(7.0,43.5),::max(‘a’,‘b’);
c):若实例化版本不可行,则尝试普通函数的转化,例如::max(‘a’。42.7)
d)参数是指针时,优先选择可实例化模板的引用版本,若不存在,则优先选择指针版本;
e):总之,尽可能采用最匹配、开销最小的版本。
示例代码及注释如下:
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 using namespace std; 5 6 //调用策略--->精准调用 7 const int &max(const int &a, const int &b) 8 { 9 cout << "num.1"<< endl; 10 return a> b? a: b; 11 } 12 13 template <typename T> 14 const T *max(const T *a, const T *b) 15 { 16 cout <<"num.4" <<endl; 17 return *a > *b ? a:b ; 18 } 19 20 template <typename T> 21 const T &max(const T &a,const T &b) 22 { 23 cout << "num.2" << endl; 24 return a> b? a: b; 25 } 26 27 template <typename T> 28 const T &max(const T &a, const T &b, const T &c) 29 { 30 cout << "num.3"<< endl; 31 return ::max(::max(a, b), c); 32 } 33 34 int main(int argc, const char *argv[]) 35 { 36 cout <<::max(7, 42, 68) << endl; //3 1 1 37 cout <<::max(7.0, 43.6) << endl; //2 38 cout <<::max(‘a‘,‘b‘) << endl;//2 char 39 cout <<::max(7, 42) << endl;//2 40 cout <<::max<>(7,42) << endl; //2 特定模板 41 cout << ::max<double>(7,42)<< endl;//2 42 cout <<::max(‘a‘, 43.7)<<endl; //1 强制转换 43 44 int a = 7; 45 int b = 89; 46 int *p1 = &a; 47 int *p2 = &b; 48 cout << ::max(p1, p2) << endl;//2 传引用,减少开销 49 50 return 0; 51 }
3)、值传递与引用传递的区别:
a):值传递与引用传递对于形参而言,本质区别在于是否产生了局部变量;
b):对于返回值而言,其区别在于, 返回时 是否产生了 临时变量。
c): 对于非引用类型 的参数, 在实参演绎的过程中,会出现 从数组衰退成指针(decay),从而丢失长度信息;而引用类型 则不会引发 衰退 decay。
在模板函数重载中, 不要混合使用值传递和引用传递,而且要尽可能使用引用。
示例代码及注释如下:
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 #include <string.h> 5 using namespace std; 6 7 //传引用 8 template <typename T> 9 const T &max(const T &a, const T &b) 10 { 11 cout <<"num.1" << endl; 12 return a> b? a: b; 13 } 14 15 //传值 16 //修改1-->改为传引用 17 //const char *&max(const char *&a, const char *&b) 18 const char *max(const char *a, const char *b) 19 { 20 cout <<"num.2" << endl; 21 return ::strcmp(a, b)> 0? a: b; 22 } 23 24 //传引用 25 //修改2-->改为传值 26 //const T max(const T &a, const T &b, const T &c) 27 template <typename T> 28 const T &max(const T &a, const T &b, const T &c) 29 { 30 cout <<"num.3" << endl; 31 return ::max(::max(a, b), c);//return 临时变量 32 //这里将临时变量的引用返回出去,可能导致错误 33 //const char*tmp=::max(::max(s1,s2),s3) 34 } 35 36 int main(int argc, const char *argv[]) 37 { 38 cout <<::max(7, 42, 68) << endl; 39 40 const char *s1 = "beij"; 41 const char *s2 = "shangh"; 42 const char *s3 = "shenzh"; 43 44 cout <<::max(s1, s2, s3) << endl; 45 46 return 0; 47 }
二、类模板:
简单实现如下;
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 using namespace std; 5 6 template <typename T> 7 class Test 8 { 9 public: 10 Test(const T &s); 11 void print()const; 12 13 private: 14 T data_; 15 }; 16 17 template <typename T>//若在类内部,则不必加Test<T> 18 Test<T>::Test(const T &s) 19 :data_(s) 20 { } 21 22 template <typename T> 23 void Test<T>::print()const 24 { 25 cout << data_ << endl; 26 } 27 28 int main(int argc, const char *argv[]) 29 { 30 Test<int> t(12) ; // 比较vector<int> vec 31 t.print(); 32 33 Test<string> t2("world"); 34 t2.print(); 35 36 return 0; 37 }
注意:STL库中,vector就是一个典型的类模板,vector<int>和vector<string>是两个完全不同的类,同样,vector 不是一个完整的类名;vector必须具备 copy和assignment能力;
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 using namespace std; 5 6 //class with no copy, no assignment 7 class Test 8 { 9 public: 10 Test() {}; 11 ~Test() {}; 12 13 private: 14 Test(const Test &); 15 void operator=(const Test &); 16 }; 17 18 int main(int argc, const char *argv[]) 19 { 20 vector<Test> vec; //运行此句时,没有产生错误 21 Test t; 22 23 vec.push_back(t);//此句产生错误。vector必须具备 copy和assignment 能力 24 return 0; 25 }
2):下面我们用stack栈的简单实现来说明类模板的一些问题;
1 //Stack.hpp 2 #ifndef STACK_H_ 3 #define STACK_H_ 4 5 #include <vector> 6 #include <stdexcept> 7 8 template <typename T> 9 class Stack 10 { 11 public: 12 void push(const T &t); 13 void pop();//出栈 14 T top()const; //查看栈顶 15 bool empty()const 16 { return elems_.empty(); } 17 18 private: 19 std::vector<T> elems_; 20 }; 21 22 template <typename T> 23 void Stack<T>::push(const T &t)//将t放入vector中 24 { 25 elems_.push_back(t); 26 } 27 28 template <typename T> 29 void Stack<T>::pop() 30 { 31 if(! elems_.empty()) 32 elems_.pop_back(); 33 else 34 throw std::out_of_range("out of range"); 35 } 36 37 template <typename T> 38 T Stack<T>::top()const 39 { 40 if(! elems_.empty()) 41 return elems_.back(); 42 else 43 throw std::out_of_range("out of range"); 44 } 45 #endif
main.cpp
1 //main.cpp 2 #include "Stack.hpp" 3 #include <iostream> 4 #include <string> 5 #include <vector> 6 using namespace std; 7 8 int main(int argc, const char *argv[]) 9 { 10 try 11 { 12 Stack<int> st; 13 st.push(7); 14 cout << st.top() << endl; 15 16 st.pop(); 17 st.pop(); //throw 18 } 19 catch(exception &e) 20 { 21 cout << e.what()<< endl; 22 } 23 return 0; 24 }
注意以上我们将Stack 的声明与定义放在同一个文件中,原因是:将Stack 拆分成 h 和cpp 文件,构建时产生了链接错误;
a):模板的调用时机和代码的实例化必须放在同一时期;
b):编译stack.cpp时,编译器找不到任何用户调用的代码,所以得到的 stack.o 文件为空, 使用 nm -A stack.o | grep (函数)
c): 编译main.cpp时,编译器获取用户的调用,了解到应该去实例化 特定部分 代码 ,但是main.cpp中仅包含 .h 文件,编译器只能找到 模板的某些函数的声明,找不到其定义及实现。 所以推迟到 链接时期;
d);链接时期,由于 stack.o 文件为空,需要链接的代码没有产生。。
3);模板的特化:
1 //模板的特例化 2 #include "Stack.hpp" 3 #include <iostream> 4 #include <deque> 5 using namespace std; 6 7 template <> //模板特化 8 class Stack<string> 9 { 10 public: 11 void push(const string &s) 12 { 13 elems_.push_back(s); 14 } 15 16 void pop() 17 { 18 elems_.pop_back(); 19 } 20 string top()const 21 { 22 return elems_.back(); 23 } 24 bool empty()const 25 { 26 return elems_.empty(); 27 } 28 private: 29 std::deque<string> elems_; 30 }; 31 32 int main(int argc, const char *argv[]) 33 { 34 try 35 { 36 Stack<string> st; 37 st.push("hello"); 38 } 39 catch(exception &e) 40 { 41 cout << e.what() << endl; 42 } 43 44 return 0; 45 }
4):模板参数不仅可以为 类型, 而且可以为非类型(数值),需要注意的是,数值也是类名的一部分,例如 Stack<int, 5 > 和 Stack<int , 10> 不是同一类型。因此,二者的对象无法相互赋值。
缺省的模板参数:
Stack.hpp
1 #include "Stack.hpp" 2 #include <iostream> 3 #include <deque> 4 using namespace std; 5 6 int main(int argc, const char *argv[]) 7 { 8 try 9 { 10 Stack<int> st; 11 st.push(7); 12 cout << st.top() << endl; 13 st.pop(); 14 15 Stack<string, deque<string> > st2; 16 st2.push("hello"); 17 st2.push("world"); 18 19 while(!st2.empty()) 20 { 21 cout << st2.top() << endl; 22 st2.pop(); 23 } 24 } 25 catch(exception &e) 26 { 27 cout << e.what()<< endl; 28 } 29 return 0; 30 }
main.cpp
1 #include "Stack.hpp" 2 #include <iostream> 3 #include <deque> 4 using namespace std; 5 6 int main(int argc, const char *argv[]) 7 { 8 try 9 { 10 Stack<int> st; 11 st.push(7); 12 cout << st.top() << endl; 13 st.pop(); 14 15 Stack<string, deque<string> > st2; 16 st2.push("hello"); 17 st2.push("world"); 18 19 while(!st2.empty()) 20 { 21 cout << st2.top() << endl; 22 st2.pop(); 23 } 24 } 25 catch(exception &e) 26 { 27 cout << e.what()<< endl; 28 } 29 return 0; 30 }
------------
非类型模板实现如下:
Stack.hpp
1 #ifndef STACK_H_ 2 #define STACK_H_ 3 4 #include <vector> 5 #include <stdexcept> 6 7 //非类型模板形参--->此处int 8 template <typename T, int MAXSIZE> 9 class Stack 10 { 11 public: 12 Stack(); 13 void push(const T &t); 14 void pop();//出栈 15 T top()const; //查看栈顶 16 17 bool empty()const 18 { return numElems_ == 0; } 19 bool full()const 20 { return numElems_ ==MAXSIZE; } 21 22 private: 23 T elems_[MAXSIZE]; 24 int numElems_;//当前元素数量 25 }; 26 27 template <typename T, int MAXSIZE > 28 Stack<T,MAXSIZE>::Stack() 29 :numElems_(0) 30 { 31 32 } 33 template <typename T, int MAXSIZE > 34 void Stack<T,MAXSIZE>::push(const T &elem)//将t放入vector中 35 { 36 if(full())//成员函数的调用 37 throw std::runtime_error("full"); 38 elems_[numElems_++] =elem ; 39 } 40 41 template <typename T, int MAXSIZE > 42 void Stack<T,MAXSIZE>::pop() 43 { 44 if(!empty()) 45 --numElems_;//数据还存在 46 else 47 throw std::out_of_range("out of range"); 48 } 49 50 template <typename T, int MAXSIZE > 51 T Stack<T,MAXSIZE>::top()const 52 { 53 return elems_[numElems_ -1]; 54 } 55 #endif
main.cpp:
1 #include "Stack.hpp" 2 #include <iostream> 3 #include <deque> 4 using namespace std; 5 6 int main(int argc, const char *argv[]) 7 { 8 try 9 { 10 Stack<int, 5> st; 11 st.push(14); 12 st.push(34); 13 st.push(45); 14 st.push(9); 15 16 cout << st.empty()<< endl; 17 18 Stack<int, 10> st2; // st 与 st2 类型不同 19 } 20 catch(exception &e) 21 { 22 cout << e.what()<< endl; 23 } 24 return 0; 25 }