首页 > 代码库 > C++学习之模板 ----函数模板、类模板

C++学习之模板 ----函数模板、类模板

本博文主要讨论函数模板与类模板以及其简单应用。

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; //ok17 18     cout << ::max(5.5, 7.8) << endl; //ok19 20     string s1 = "hello"; 21     string s2 = "world";22     cout <<::max(s1, s2)<< endl; //ok23     24     //cout << ::max("hello", "worldd");//error char[6],char[7] 25     //虽然都是字符数组,但是长度也是参数的一部分,故两者不是同一类型26     27     //cout << ::max(3, 4.5) << endl;//error28     //会发生编译错误,因为编译器推断第一个类型为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 137     cout <<::max(7.0, 43.6) << endl; //238     cout <<::max(a,b) << endl;//2 char39     cout <<::max(7, 42) << endl;//240     cout <<::max<>(7,42) << endl; //2 特定模板41     cout << ::max<double>(7,42)<< endl;//242     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()const24 {25     cout << data_ << endl;26 }27 28 int main(int argc, const char *argv[])29 {30     Test<int> t(12) ; // 比较vector<int> vec31     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 Stack10 {   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     else34         throw std::out_of_range("out of range");35 }36 37 template <typename T>38 T Stack<T>::top()const39 {40     if(! elems_.empty())41         return elems_.back();42     else43         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     try11     {12         Stack<int> st;13         st.push(7);14         cout << st.top() << endl;15 16         st.pop();17         st.pop(); //throw18     }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()const21         {22             return elems_.back();23         }24         bool empty()const25         {26             return elems_.empty();27         }28     private:29         std::deque<string>  elems_;30 };31 32 int main(int argc, const char *argv[])33 {34     try35     {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 Stack10 {   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     else47         throw std::out_of_range("out of range");48 }49 50 template <typename T, int MAXSIZE >51 T Stack<T,MAXSIZE>::top()const52 {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 }

 

C++学习之模板 ----函数模板、类模板