C++ 模板基础
- template<class T>
- void swap(T& a, T& b)
- {....}
实现机理
模板,从字面意思上大致就能知道它只是一个模子,用的时候,还需我们进行一番修饰,也就是说写好的函数模板还不是直接使用的,只有在实例化的时候,编译器才会推演出我们要用的代码,推演的依据就正是我们传入的参数了。(这里,你可以去验证一下,比如先不调用函数,此时将模板代码中的一句分号去掉,编译器并不会报错)。实质上,说的通俗一点,实例化的时候,如int a =1,b=2; swap(a, b)就是编译器编译时直接用int将T替换,以此实例化一份代码出来。
模板显示实例化
前面我们值得使用函数模板时,编译器会针对参数的类型推演出相应的代码。如果我们在调用一个模板函数的时候,在函数名后面加上<类型>,这时候系统不需要从形参这里推断类型了。它直接使用尖括号里面的类型就ok了。返回类型形式是:函数名<类型>(参数列表),接下来我们来验证一下
- template<class T>
- void Swap(T& a, T&b)
- {
- T tmp = a;
- a = b;
- b = tmp;
- }
- void Test( )
- {
- int a =1, b=2;
- Swap<int>(a, b); //显示实例化
- cout<<a<<" "<<b<<endl;
- }
值得注意是此时模板中形参只有一种,所以调用swap函数不要传入两种类型不同的实参,这是不合法的。
相对来讲,模板函数算比较简单的,下面来看看模板类
它和模板函数类似,其格式如下
- template<class 形参名1, class 形参名2, ...class 形参名n>
- class 类名
- { ... };
对于类模板,实例化时,模板形参的类型必须在类名后的尖括号中明确指定。举个例子,如A<10> m, 这样是绝对不行的,类模板中是不存在实参类型的推断;也不能直接A a这样定义,道理很明显,这样编译器没法推演。
然后在类模板外部定义成员函数的方法为:
- template<模板形参列表>
- 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
比如有两个模板形参T1,T2的类A中含有一个void f()函数,则定义该函数的语法为:
template<class T1,class T2> void A<T1,T2>::f(){}。
注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
来个实例,实现一个简易带头双向链表:
- /*************************************************************************
- > File Name: List.cc
- > Author: tp
- > Mail:
- > Created Time: Sat 05 May 2018 08:39:46 PM CST
- ************************************************************************/
- #include <iostream>
- #include <string>
- #include <cassert>
- using namespace std;
- template<class T>
- struct ListNode
- {
- T _val;
- ListNode* _prev;
- ListNode* _next;
- ListNode(const T& x)
- :_val(x), _prev( NULL),_next( NULL)
- { }
- };
- template<class T>
- class List
- {
- typedef ListNode<T> Node;
- public:
- List()
- :_head(new Node(T()))
- {
- _head->_prev = _head;
- _head->_next = _head;
- }
- void PushBack(const T& x)
- {
- Insert(_head, x);
- }
- void PopBack()
- {
- Erase( _head->_prev);
- }
- void PushFront(const T& x)
- {
- Insert(_head->_next, x);
- }
- void PopFront()
- {
- Erase(_head->_next);
- }
- void Insert(Node* pos, const T& x)
- {
- assert( pos);
- Node* prev = pos->_prev;
- //prev newnode pos
- Node* newnode = new Node(x);
- prev->_next = newnode;
- newnode->_prev = prev;
- newnode->_next = pos;
- pos->_prev = newnode;
- }
- void Erase(Node* pos)
- {
- assert( pos && pos != _head);
- //能删头结点?
- Node* prev = pos->_prev;
- Node* next = pos->_next;
- delete pos;
- prev->_next = next;
- next->_prev = prev;
- }
- bool Empty()
- {
- return _head == _head->_next;
- }
- Node* Find(const T& x)
- {
- Node* cur = _head->_next;
- while( cur != _head)
- {
- if( cur->_val == x)
- return cur;
- cur = cur->_next;
- }
- return NULL;
- }
- //以O( 1)的时间复杂度, 多开4字节进行计数,不能让头结点存计数,因为类型不确定
- //这里普通做法
- size_t Size()
- {
- size_t count = 0;
- Node* cur = _head->_next;
- while( cur != _head)
- {
- ++count;
- cur = cur->_next;
- }
- return count;
- }
- void Clear( )
- {
- Node* cur = _head->_next;
- while(cur != _head)
- {
- Node* next = cur->_next;
- delete cur;
- cur = next;
- }
- }
- ~List( )
- {
- Clear();
- _head = NULL;
- }
- void Print();
- protected:
- Node* _head;
- };
- template<class T>
- void List<T>::Print()
- {
- Node* cur = _head->_next;
- while( cur != _head)
- {
- cout<<cur->_val<<" ";
- cur = cur->_next;
- }
- cout<<endl;
- }
- int main( )
- {
- List<int> l;
- l.PushBack(1);
- l.PushBack(2);
- l.PushBack(3);
- l.PushBack(4);
- l.PopBack( );
- l.Print();
- cout<<l.Size( )<<endl;
- List<string> l1;
- l1.PushBack( " 11");
- l1.PushBack( " 1i32");
- l1.PushBack( " 1112019jhja");
- l1.PushBack( " 11");
- l1.Print( );
- return 0;
- }
模板中的形参
非类型模板形参:模板的非类型形参也就是内置类型形参
如:template<class T, int a> class A{};其中int a 就是非类型的模板形式参。非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
- #include <iostream>
- using namespace std;
- template<class T, int b>
- class A
- {
- public:
- A()
- :a(b){}
- void show( )
- {cout<<a<<endl;}
- protected:
- T a;
- };
- int main(void)
- {
- const int i = 10;
- A<int, i> a;
- a.show();
- return 0;
- }
1. 调用非类型模板形参的实参必须是一个常量表达式,即它必须能在编译时计算出结果。sizeof表达式的结果是一个常量表达式,全局变量的地址或引用,全局对象的地址或引用const类型变量也是常量表达式 ,它们可以用作非类型模板形参的实参。
2. 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针则是可以的。
3. 任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
4、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量
比如:template <class T, int a> class A{};
如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
5. 非类型模板形参的形参和实参间所允许的转换:
1. 类模板的类型形参默认值形式为:template<class T1, class T2=int> class A{...},这样来为模板中的第二个形参T2提供int型的默认值。
2、模板形参可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。
3、类模板类型形参添加默认值的规则和函数默认参数规则一样。如果有多个类型形参,参数从右向左连续的缺省,因为要符合的参数从右向左的入栈规则。比如template<class T1=int, class T2>class A{};就是错误的,因为T1给出了默认值,而T2没有设定。
4、在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。
比如template<class T1, class T2=int> class A{public: void func();};
定义方法为template<class T1,class T2> void A<T1,T2>::func(){},将int省略掉。
大致了解了上面模板的一些用法,我们可以来实现一个东西——适配器
- //紧接上面List的头文件
- ..................
- template <class T, template<class>class Container>
- class Vector
- {
- public:
- void Push( const T& x)
- {
- _con.PushBack(x);
- }
- void Pop()
- {
- _con.PopBack();
- }
- size_t Size()
- {
- return _con.Size();
- }
- bool Empty( )
- {
- return _con.Empty( );
- }
- void Print( )
- {
- _con.Print( );
- }
- private:
- Container<T> _con;
- };
- void test( )
- {
- Vector<string, List> v;
- v.Push("hello");
- v.Print( );
- cout<<v.Empty();
- }
这里用到了上面实现的双向链表,以此简陋地来模拟STL里面的vector(其实这里用链表版本并不合适,STL里面用到是顺序表)。
然后来看看模板参数里的这个template<class> class Container ,第一次看的话,可能你我多少会有些小慌,毕竟出来个这么长的怪物。细细来看 其实就好理解了,template<class>指明了这是模板参数且是模板类类型,后面取了名字叫Container。所以这就相当于声明Container是一个模板类类型的类模板参数。这算是一种固定搭配,念着有些拗口,记住就好。
然后就是下面的 Container<T> _con; 它意思就是创建一个Container的对象,而我们也知道这个对象也是模板类对象,所以把<T>给它传进去。然后我们现在来说一下这个代码有什么用?
其实这里的Vector类相当于一个适配器。适配器有一种“让一种事物的行为类似于另外一种事物行为”的机制,它对容器进行包装,使其表现出另外一种行为。针对不同类型的数据,在存、删数据时,我们不用再去实现不同版本的线性表;我们直接去用List类里面的函数,具体就是直接定义一个List的对象,然后直接拿走进行使用,最后让Vector管理不同类型数据,进而完成适配;再比如STL里面一个管理Int数据的栈,它的实现是stack<int, vector<int> >的,其内部其实是使用顺序容器vector<int>来存储数据(相当于是vector<int>表现出了栈的行为)。这些都是灵活的复用的体现。
最后,模板不支持分离编译
还是用上面的交换函数举例,不过这时我们将.h 和.cpp文件分开。
- ***************Swap.h************
- #include <iostream>
- using namespace std;
- template<class T>
- void Swap(T& a, T& b);
- ***************Swap.cpp************
- #include "Swap.h"
- template<class T>
- void Swap(T& a, T&b)
- {
- T tmp = a;
- a = b;
- b = tmp;
- }
- ***************main.cpp************
- #include "Swap.h"
- int main(void)
- {
- int a =1, b=2;
- Swap<int>(a, b);
- cout<<a<<" "<<b<<endl;
- return 0;
- }
- 在模板头文件 xxx.h 里面显示实例化,针对模板类,就在模板类的定义后面添加 template class List<int>; 一般不推荐这种方法,一方面老编译器可能不支持,另一方面实例化依赖调用者。
- 将声明和定义放到一个文件 "xxx.hpp" 里面,(这样编译时头文件展开,实例化代码便很轻松了)。通常我们会使用这种方法,但这种方法也有缺陷,因为实际开发中,.h文件是别人看的,而开发者并不希望别人细致了解内部的具体实现。
C++ 模板基础的更多相关文章
- C++ template —— 深入模板基础(二)
上一篇C++ template —— 模板基础(一)讲解了有关C++模板的大多数概念,日常C++程序设计中所遇到的很多问题,都可以从这部分教程得到解答.本篇中我们深入语言特性.------------ ...
- C++模板编程-模板基础重点
模板基础 1.模板参数自动推导,如果是已知的参数类型与个数,这调用模板时可以不写类型. Cout<<max<int>(1,3);可以写为Cout<<max(1,3) ...
- Myeclipse Templates详解(一) —— Java模板基础
目录 Templates简介 MyEclipse自带Templates详解 新建Template 自定义Template 因为自己比较懒,尤其是对敲重复代码比较厌恶,所以经常喜欢用快捷键和模板,Mye ...
- Django模板-基础知识
上一篇中带参数的URLconf虽然可以做到传参动态显示内容,但是最终现实的内容还是硬编码到Python代码中的 def hours_ahead(request,phours): try: phours ...
- MVC开发T4代码生成之一----文本模板基础
T4文本模板 T4全写为Text Template Transformation Toolkit,是一种编程辅助工具,用来使程序代码自(懒)动(猿)生(福)成(利)的工具.MVC开发中大量使用了T4模 ...
- [转]C++ template —— 模板基础(一)
<C++ Template>对Template各个方面进行了较为深度详细的解析,故而本系列博客按书本的各章顺序编排,并只作为简单的读书笔记,详细讲解请购买原版书籍(绝对物超所值).---- ...
- C++ template —— 模板基础(一)
<C++ Template>对Template各个方面进行了较为深度详细的解析,故而本系列博客按书本的各章顺序编排,并只作为简单的读书笔记,详细讲解请购买原版书籍(绝对物超所值).---- ...
- smarty模板基础
将前台后台隔离,前台控制显示,后台控制逻辑/内容,与cms类似 原理: 用户访问text.php页面,后台调用类smarty.class.php显示静态模板;
- smarty模板基础1
smarty模板的作用可以让前端和后端分离(也就是前端的显示页面和后端的php代码). smarty模板的核心是一个类,下载好的模板中有这么几个重要的文件夹 (1)libs核心文件夹(2)int.in ...
- smarty模板基础2
Smarty自带了一些内置函数,这些内置函数是Smarty模板引擎的组成部分.他们被编译成相应的内嵌PHP代码,以获得最大性能. 您创建的自定义函数不能与内置函数同名,也不必修改这些内置函数. 其中一 ...
随机推荐
- 【Python】Talk Python To Me Podcast播客
这是Python相关的一个播客,通过播客的形式给大家讲述python那点事,相关的链接都会列出来,有一些是由文本内容的,如果听不太懂就看看英文原文.不fanqiang的情况下,网页打开没有问题,但是播 ...
- Java 反射之JDK动态代理
Proxy提供用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类.如果我们在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类:如果需要为一个或多个接口动态的 ...
- Eclipse集成Android_NDK
Eclipse集成Android NDK说明 2 为什么要用NDK? 以下内容引用自<Pro Android Games> Some Java develo ...
- MT6575 3G切换2G
因为了节省成本,需要从现在的3G方案切换置2G方案,做的修改,做个笔记. 一: 将MTK给过来的补丁编译出如下文件. 二:在mediatek/custom/common/modem/ 路径下增加一个 ...
- 解决winform窗体闪烁问题
如果你在Form中绘图的话,不论是不是采用的双缓存,都会看到图片在更新的时候都会不断地闪烁,解决方法就是在这个窗体的构造函数中增加以下三行代码: 请在构造函数里面底下加上如下几行: SetStyle( ...
- logcat不显示信息
挨个试其中的解决办法最终解决了 我的设置居然是error 这样肯定显示不了啊 换成verbos 解决了这个问题.希望帮到大家 部分华为手机不显示log问题原因是手机底层开关没打开. http://h ...
- ERP-非财务人员的财务培训教(一.一)------基本会计知识
一.基本会计知识 第一节 会计是企业的语言 反映企业经济状况的两组会计语言词汇 四个层次的会计语言规则 财务会计报告的组成 会计语言要素 会计工作主要是把企业杂乱的会计数据归纳整理,加工编制成有用的财 ...
- 在Windows使用git工具将代码同步至github(作者:ying1989920)
[ps]git是一个分布式代码管理工具,类似于svn,方便协同开发,git里面有所谓的仓库(用来存放代码的),分为本地和线上,线上的你可以自己搭建,不想搭建的话github就给你提供了. [关于 ...
- css3属性(1)
text-transform语法: text-transform : none | capitalize| uppercase| lowercase 参数: none : 无转换发生 capitali ...
- 点击劫持漏洞之理解 python打造一个挖掘点击劫持漏洞的脚本
前言: 放假了,上个星期刚刚学习完点击劫持漏洞.没来的及写笔记,今天放学总结了一下 并写了一个检测点击劫持的脚本.点击劫持脚本说一下哈.= =原本是打算把网站源码 中的js也爬出来将一些防御的代码匹配 ...