- 意 思
- 使物体成固定型状的模具
- 拼 音
- mú bǎn
- 中文名
- 模板
- 外文名
- Template
-
2019-07-04 21:10:42
类模板和模板类
所谓类模板,实际上是建立一个通用类,其数据成员、成员函数的返回值类型和形参类型不具体指定,用一个虚拟的类型来代表。使用类模板定义对象时,系统会实参的类型来取代类模板中虚拟类型从而实现了不同类的功能。
定义一个类模板与定义函数模板的格式类似,必须以关键字template开始,后面是尖括号括起来的模板参数,然后是类名,其格式如下:
template <typename 类型参数> class 类名{ 类成员声明 }; 或者 template <class 类型参数> class 类名{ 类成员声明 };
-
template:是一个声明模板的关键字,它表明声明一个模板
-
类型参数:通常用C++标识符表示,如T、Type等,实际上是一个虚拟的类型名,现在未指定它是哪一种具体的类型,但使用类模板时,必须将类型参数实例化。
-
typename和class的作用相同,都是表示其后面的参数是一个虚拟的类名(即类型参数).
在类声明中,欲采用通用数据类型的数据成员、成员函数的参数或返回类型前面需要加上类型参数。
如建立一个用来实现求两个数最大值的类模板
template<typename T> //模板声明,其中T为类型参数 class Compare{ public: Compare(T i,T j) { x = i; y = j; } T max() { return (x>y)?x:y; } private: T x,y; };
用类模板定义对象时,采用以下形式:
类模板名<实际类型名>对象名[(实参表列)];
因此,使用上面求最大值的类型模板的主函数可写成:
int main() { Compare<int>com1(3,7); Compare<double>com2(12.34,56.78); Compare<char>com3('a','x'); cout<<"其中的最大值是:"<<com1.max()<<endl; cout<<"其中的最大值是:"<<com2.max()<<endl; cout<<"其中的最大值是:"<<com3.max()<<endl; return 0; }
例6.6 类模板compare的使用举例
#include<iostream.h> template<typename T> //模板声明,其中T为类型参数 class Compare{ public: Compare(T i,T j) { x = i; y = j; } T max() { return (x>y)?x:y; } private: T x,y; }; int main() { Compare<int>com1(3,7); //用类模板定义对象com1,此时T被int替代 Compare<double>com2(12.34,56.78); //用类模板定义对象com2,此时T被double替代 Compare<char>com3('a','x'); //用类模板定义对象com3,此时T被char替代 cout<<"其中的最大值是:"<<com1.max()<<endl; cout<<"其中的最大值是:"<<com2.max()<<endl; cout<<"其中的最大值是:"<<com3.max()<<endl; return 0; }
程序运行结果是:
其中的最大值是:7 其中的最大值是:56.78 其中的最大值是:x
在以上例子中,成员函数(其中含有类型参数)是定义类体内的。但是,类模板中的成员函数,也可以在类模板外定义。此时,若成员函数中有参数类型存在,则C++有一些特殊的规定:
(1)需要在成员函数定义之前进行模板声明; (2)在成员函数名前缀上"类名<类型参数>::";
在类模板外定义成员函数的一般形式如下:
temlate<typename 类型参数> 函数类型 类名<类型参数>::成员函数名(形参表) { 函数体; } 如上题中成员函数max在类模板外定义时,应该写成: template<typename T> T Compare<T>::max() { return (x>y)?x:y; }
//例6.7 在类模板外定义成员函数函数举例。
#include<iostream.h> template<typename T> //模板声明,其中T为类型参数 class Compare{ public: Compare(T i,T j) { x = i; y = j; } T max(); private: T x,y; }; template<class T> T Compare<T>::max() { return (x>y)?x:y; } int main() { Compare<int>com1(3,7); //用类模板定义对象com1,此时T被int替代 Compare<double>com2(12.34,56.78); //用类模板定义对象com2,此时T被double替代 Compare<char>com3('a','x'); //用类模板定义对象com3,此时T被char替代 cout<<"其中的最大值是:"<<com1.max()<<endl; cout<<"其中的最大值是:"<<com2.max()<<endl; cout<<"其中的最大值是:"<<com3.max()<<endl; return 0; }
/*
程序运行结果是:其中的最大值是:7 其中的最大值是:56.78 其中的最大值是:x
此例中,类模板Compare经实例化后生成了3个类型分别为int、double、char的模板类,这3个模板类
经实例化后又生成了3个对象com1、com2、com3。类模板代表了一类类,模板类表示某一具体的类。关系如下:类模板 Compare<T> 实例化成模板类:Compare<int> Compare<double> Compare<char> 实例化模板类对象:com1 com2 com3
例6.8 类模板Stack的使用举例。
#include<iostream.h> const int size=10; template<class T> //模板声明,其中T为类型参数 class Stack{ //类模板为Stack public: void init() { tos=0; } void push(T ob); //声明成员函数push的原型,函数参数类型为T类型 T pop(); //声明成员函数pop的原型,其返回值类型为T类型 private: T stack[size]; //数组类型为T,即是自可取任意类型 int tos; }; template<class T> //模板声明 void Stack<T>::push(T ob) //在类模板体外定义成员函数push { if(tos==size) { cout<<"Stack is full"<<endl; return; } stack[tos]=ob; tos++; } template<typename T> //模板声明 T Stack<T>::pop() //在类模板体外定义成员函数push { if(tos==0) { cout<<"Stack is empty"<<endl; return 0; } tos--; return stack[tos]; } int main() { //定义字符堆栈 Stack<char> s1; //用类模板定义对象s,此时T被char取代 s1.init(); s1.push('a'); s1.push('b'); s1.push('c'); for(int i=0;i<3;i++){cout<<"pop s1:"<<s1.pop()<<endl;} //定义整型堆栈 Stack<int> s2; //用类模板定义对象s,此时T被int取代 s2.init(); s2.push(1); s2.push(3); s2.push(5); for(int i=0;i<3;i++){cout<<"pop s2:"<<s2.pop()<<endl;} return 0; }
/*
程序运行结果是:pop s1:c pop s1:b pop s1:a pop s2:5 pop s2:3 pop s2:1
说明:
-
在每一个类模板定义之前,都需要在前面加上模板声明,如
template
或
template
并且,类模板在使用时,必须在模板类名字后面缀上<类型参数> ,如
Stack -
如同模板函数一样,模板类也可以有多个类型参数。
例6.9 有两个类型参数的类模板举例
#include<iostream.h> template<class QQ,class T> //声明模板,具有T1,T2两个类型参数 class Myclass{ //定义模板类Myclass public: Myclass(QQ a,T b); void show(); private: QQ x; T y; }; template<typename QQ,typename T> Myclass<QQ,T>::Myclass(QQ a,T b) { x = a; y = b; } template<class QQ,class T> void Myclass<QQ,T>::show() { cout<<"x="<<x<<","<<"y="<<y<<endl; } int main() { Myclass <int,double>m1(12,0.15); //用类模板定义对象m1,此时T1,T2分别被int、double取代 Myclass <int,char*>m2(12,"This a test."); //用类模板定义对象m2,此时T1,T2分别被int,char*取代 m1.show(); m2.show(); return 0; } /* 程序运行结果是: x=12,y=0.15 x=12,y=This a test. */
程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!
更多C++相关知识体系,请移步C++知识目录。更多相关内容 -
-
QT自制精美Ui模板系列(一)桃子风格模板 - 二次开发专用
2020-09-03 13:21:18本模板为美化QT界面系列中的一个,整体风格为桃子风格(效果预览链接:https://blog.csdn.net/hwc3737/article/details/108377607),搭配美化大部分常用控件,可直接使用。同时自带部分有用系统功能(最小化托盘... -
最全ppt模板
2017-12-20 10:05:37ppt模板ppt模板ppt模板ppt模板ppt模板ppt模板ppt模板ppt模板 -
elsevier 爱思唯尔 系列期刊的word模板,template,单栏,双栏
2019-10-12 21:44:41elsevier 爱思唯尔 系列期刊的word模板,单栏,双栏,包括2个文件,一个是单栏的word,另一个是双栏的word。 -
各类excel甘特图模板
2019-02-14 20:51:02各类excel甘特图模板 -
软件开发文档模板(全套)
2018-05-18 11:42:451、可行性研究报告 2、项目开发计划 3、需求规格说明书 4、概要设计说明书 5、详细设计说明书 6、用户操作手册 7、测试计划 8、测试分析报告 9、开发进度月报 10、项目开发总结报告 ...13、软件修改报告 -
easyUI多套模板(美化包)
2018-07-24 10:16:40easyUI多套模板(美化包),其中有一套以及使用在公司的项目里面了,用在项目中最好可以简单的全局修改皮肤色调,有附教程如何修改皮肤颜色 -
程序员年终总结PPT模板
2018-12-27 16:42:58程序员年终总结PPT模板,年度写ppt必备模板大全,赶快下载吧 -
接口文档标准模板-含Word和excel两种
2018-02-06 09:36:51接口文档标准的模板,包含Word和excel两种模板。满足各种语言接口需要。 -
国际学术会议poster模板(5个)
2017-11-10 15:55:56国际学术会议poster模板(5个),选择适合自己模板。 国际学术会议poster模板(5个),选择适合自己模板。 国际学术会议poster模板(5个),选择适合自己模板。 -
C++——模板(超详细的模板解析)
2022-03-28 17:29:35超详细的C++模板知识列表,持续维护。模板是一个非常强大的C++功能,STL的各种组件也是基于模板的。所以,无论是写程序了,还是读程序,都有必要了解一下C++的模板。
关于什么是模板或者模板的基本定义,这里就不讲述了,本篇文章主要罗列出在使用模板过程中的一些问题和模板一些令人头疼的语法,并配合简单的demo,如果你只是希望查阅语法或者了解一些知识点,这篇文章可能会帮到你。
声明:使用了using namespace std。对于应该包含进来的头文件,不再显示的声明。文中所有demo均经过测试。本文章基于《C++ Primer Plus》和《C++ Prime》。
目录
模板的基本声明和定义
模板的声明
template <typename T> int compare (T t1, T t2); template <typename T> class compare;
定义一个模板函数
template <typename T> int compare(T & t1, T & t2) { if(t1 > t2) return 1; if(t1 == t2) return 0; if(t1 < t2) return -1; }
定义一个模板类
template <typename T> class compare { private: T _val; public: explicit compare(T & val) : _val(val) { } explicit compare(T && val) : _val(val) { } bool operator==(T & t) { return _val == t; } };
模板参数作用域
就如同其他的函数参数一样,或者是变量一样,就是普通的作用域规则。
using T = int; T a = 10; template <typename T> class A;
模板声明里的T不是上面的int而是模板参数。
template <typename T> class A { U val; //error template<typename U> class B; };
关于模板工作原理
模板定义并不是真正的定义了一个函数或者类,而是编译器根据程序员缩写的模板和形参来自己写出一个对应版本的定义,这个过程叫做模板实例化。编译器成成的版本通常被称为模板的实例。编译器为程序员生成对应版本的具体过程。类似宏替换。
模板类在没有调用之前是不会生成代码的。
由于编译器并不会直接编译模板本身,所以模板的定义通常放在头文件中。
非类型模板参数
顾名思义,模板参数不是一个类型而是一个具体的值——这个值是常量表达式。
当一个模板被实例化时,,非类型参数被一个用户提供的或者编译器推断出的值所代替。正因为模板在编译阶段编译器为我们生成一个对应的版本,所以其值应该能够编译时确定,那么他应该是一个常量或者常量表达式。
有一句话说:C++的强大在于他的编译器强大,下面这个例子就是很好的说明。
template <size_t N, size_t M> int str_compare(const char (&str1)[N], const char (&str2)[M]) { return strcmp(str1,str2); }
使用方法
str_compare("hello","nihao")
为什么???我们甚至没有用<>来传递模板参数。这是因为编译器在编译阶段已经帮助我们计算好了应该开辟多大空间的数组。我们也可以指定长度。N,M只是隐式的传入进去。
编译器也可以自动帮助我们推断参数时什么类型,从而不用显示的调用模板函数,对于上面的compare函数,我们可以这样调用,前提时保证参数类型相同。
compare(10,20);
非类型模板参数的范围
整形,指针或者左值引用都是一个非类型模板参数。
我们可以想到,对于指针或者引用,应当保证实参必须具有静态的生存期,保证其不会被释放。
inline和constexp
放在模板之后,函数之前即可
template <typename T> inline int compare(T t1, T t2);
在模板类中使用模板类
这个应该很好理解,根据自己的需求,我们可以这样定义
template <typename T> class A { private: vector<T> vec; };
也可以这样定义
template <typename T> class B { private: vector<int> vec; };
友元与模板类
通过上面编译器为模板生成具体代码的原理可以看出这样有什么不同
template <typename N> class C friend A<N>; friend B<int>;
由于具体的原理类似宏替换,每个对应的C<N>都有友元A<N>和B<int>、
即有这样友元关系C<int> A<int> B<int>, C<string> A<string> B<string>以此类推。
还有这样的模板友元——所有的实例化都是其友元
template <typename N> class C template <typename T> friend class D;
但是没有这样的写法
template <typename T> friend class D<T>;
或者这样的写法
template <typename T> friend D<T>;
模板允许模板参数为自己的友元
首先说明,模板允许内置类型为自己的友元。
friend int;
这样写是完全正确的,但是实际上有什么意义呢?
还是有意义的,我们可以这样写
template <typename T> class People { friend T; };
这样就保证了在传入内置类型的时候不会有错误。
默认模板实参
用法和函数的默认参数基本相同
template <typename T = int> class A;
默认的情况下T就是int
A<> a; // T is int
: : 二义性的解决
对于普通类的:: ,我们可以知道它究竟是一个类还是一个静态成员,就像下面这样。
string::size_type a; string::npos;
对于模板类来说,我们还是知道表达的是什么,但是已经说过了,模板类在没有 调用之前不会生成代码,这可坏了。对于T::mem,究竟是什么呢?是静态成员?还是一个类型的typedef?
对于这个问题,使用typename修饰。
当我们希望通知编译器一个名字表示一个类型时,使用且必须使用关键字typename,来表示其是一个类型。
于是,我们可以写出这样的代码。
template <typename T> typename T::val_typefunc ();
表的不是一个静态数据成员而是一个类型。
或者这样的代码
typedef typename T::mem s_type;
表示s_type是一个类型的别名而不是数据成员的别名。
如果转到string::size_type的定义,可以看见他是一个typename 的 typedef。
类模板成员函数
本质上就是个函数,只要掌握了模板的工作原理,我们我们就可以轻松的写出类模板成员函数。
class Math { public: template <typename N> inline static N sqrt(N); }; template<typename N> N Math::sqrt(N val) { return val * val; }
首先来一点一点解析
这是一个模板函数,返回值为N类型,所以,模板语法写在前面,让编译器知道应该返回类型,紧接着就是返回类型,返回类型同上都是写在比较靠前的位置。接着就是函数的标签。
对于定义来说,应该知道是哪个类下的函数,所以和普通的方法一样加上一个作用域即可。
假如把类写成这样呢?
template <typename N> class Math { public: inline static N sqrt(N); };
那方法的定义应该是写成这样的。
template<typename N> N Math<N>::sqrt(N val) { return val * val; }
这里就可以看出
前面说到的,模板不是一个具体的类,而是根据这个模板编译器生成对应的版本。
对于每一个版本,都是不同的类。就像重载函数一样,即便参数个数和函数的具体算法完全一样,但类型不同他们也是不同的函数,只不过函数名相同而已。
那么就应该可以得到每个版本的类都对应的一个相应版本的静态成员。所以Math<N>::这样写也就很好理解了。
类模板的成员模板
我已经不知道用什么语言来下面的代码了。但是我们知道了一些事情。
无论是定义还是声明,模板语法的优先级是最高的,不同模板的优先级又根据其声明顺序来判断,其次是函数修饰,然后是返回值。根据这个原则我们可以轻松的解析这个函数。
template <typename T> class A { public: template <typename It> A<T> sum(It _begin, It _end); }; template <typename T> //最外层模板 template <typename It> //内层模板 A<T> //返回值 A<T>::sum(It _begin, It _end)//函数标签 {} //算法实现 //不妨写的更美观一点 template <typename T> template <typename It> A<T> A<T>::sum(It _begin, It _end){ }
注意:上面的代码和下面的代码写的足够复杂,下面的代码对其进行一些小小的修改。
具体的用法,虽然下面的例子看起来有些造作,但是还是能说明一些问题的
#include <iostream> #include <cstring> #include <vector> using namespace std; template <typename T> class A { private: vector<T> vec; public: template <typename It> T sum(It _begin, It _end); A(initializer_list<T> initlist) { for(auto it = initlist.begin();it != initlist.end();++it) { vec.push_back(*it); } } typename vector<T>::iterator begin() { return vec.begin(); } typename vector<T>::iterator end() { return vec.end(); } }; template <typename T> template <typename It> T A<T>::sum(It _begin, It _end) { T tot ; memset(&tot,0,sizeof (T)); while(_begin != _end) { tot += *_begin++; } return tot; } int main() { A<int> a {1,2,3,4}; cout << a.sum(a.begin(),a.end()); return 0; }
虽然这样的语法很是令人头疼,但是多用即可熟练,或者说使用类型别名来避免这样的问题,并且最好不要把学习精力放在语法上——在没有熟悉语法之前。
实例化优化
当模板被调用时才会被编译,那么就会存在这样一种情况——相同地实例化可能出现在多个文件对象中。当两个或多个独立编译地源文件适用了相同地模板,并提供了相同地模板参数时,每个文件中就都会有该模板的一个实例。
为了解决这种问题,我们可以控制显示实例化。具体的做法如下
用关键字extern显示的实例化声明
extern template class A<string>; //声明 template int compare(const int &, const int &); //定义
将一个实例化声明为extern就表示承诺在程序的其他位置有该实例化的一个非extern声明(定义)。
由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何用此实例化版本的代码之前。
解释的来说:因为文件其他处已将有一个实例化——编译器生成好的或者是自己定义的,由于编译单元为.cpp文件,所以在一个文件中实例号的代码并不能用于另一个文件,这就显着很捞。而extern正是解决这个问题的。
类型转换和参数推断
与非普通地类型转换一样,模板类在传递模板参数的时候也会进行相应的转换,只不过这种转换增添了更多的规则,参数推断和类型转换的关系是非常紧密的。
类型转换这里的问题如果想要清楚的了解,那恐怕是非常可怕的,我有时候在想,通过这么多的转换规则 ,我们就可见一斑的看出C++的设计是多么的巧妙。虽然这的知识点很乱,但其实只要抓住隐藏在这背后的观念就能清晰的对付各种转换了。
其中一个规则是:如果能够进行安全转换,那么编译器可以隐式转换
最经典的一个例子就是non-const 到const的转换。
为了展示的方便,会忽略掉一些代码
template<typename T> bool func(const T t1, const T t2) { return less<T>()(t1,t2); } ... int a = 10; const int b = 20; func<int>(a,b);
这里的int转为cosnt int是允许的,因为这是按值传递,并且non-const 转为 cosnt也不会带来什么坏处。因此,编译器会执行这样的转换。
我们不妨修改一下这个函数
bool func(const T & t1, const T & t2); bool func(T & t1, T & t2);
上述第一个声明会正常调用——虽然是引用,和上面的是同样的道理。
而第二个却不会正常调用——因为b是一个cosnt 将要转换为non-const,我中转换是不安全的,所以编译不允许这样的转换。
假设我们这样调用两个函数
func<int>(10,20);
同样的,对于第一个是允许的——虽然讲右值绑定到左值引用上,但是我们用const修饰形参,保证其不会改变,所以编译器同意这样的转换。
而对于第二个,编译器则不允许这样的转换,因为我们的形参是non-const的,不能保证不修改形参的值,形参正好又是一个引用,这样可以修改实参的值——恰好实参是一个右值——是不允许被修改的,所以编译器不允许这样的转换。
基于上面的转换规则,我们可以知道,如果函数形参不是引用类型,则可以对数组或者函数类型的实参应用正常的指针转换。
上面的是C++ Primer的原文,实际上笔者在学习的过程中,发现了其错误。
先来看一下代码
template <typename T> bool func(const T & t1, const T & t2); ... int a[10]; int b[10]; func(a,b);
这样是可以的。那这样呢?
template <typename T> T func(const T & t1, const T & t2);
template<typename T1, typename T2, typename T3> T1 sum(T2 t2, T3 t3) { return t2 + t3; } ... sum<long long>(10,200); //or sum<long long, int, int>();
就不可以了,这个声明对应着C++ Primer的声明。
为什么一样的形参列表只有返回值不同编译器就会发出警告,这是为什么。
其实我们到目前为止的讨论,都适用于普通函数,模板的本质其实也是模板为我们生成对应的版本,为了解开上面的疑惑,我们可以先来复习以下引用的知识。
int *& ref_apple_point = &apple; //error int * const & ref_apple_point_const = &apple; //ok
根据这两行代码我们可以得到一些启示。
因为数组名是一个常量,const T & t1这样的形参是可以接受的。但对于返回值来说,可就麻烦了。返回值为内置数据类型的模板函数,对于这个问题,这没有什么好说的。返回类型为T的模板函数,他返回的是一个什么具体类型呢?首先T被u推断为* const,那么返回类型也应该是 *const
在这里我们先留下一个悬念,当我们理解和编译器是如何推断T是何种类型的时候,这个问题可能就会迎刃而解。
返回值类型推断
在编译器遇见函数列表之前,所有的形参都是不存在的,那么我们需要使用这样的尾置返回类型。
auto func(It & _beg, It & _end) -> decltype(*_beg) { //... auto sum = *_beg; sum = 0; for_each(_beg,_end,[&sum](const int & val){ sum+= val;}); //... return sum; }
这样的代码还是有一些问题的,如果我们要返回一个拷贝而不是引用呢?要用到一个类型转换模板工具。
remove_reference<> 移除引用——关于其他的类型转换,不再本文章讨论范围内读者可自行查阅。
这个模板类有一个名为type的public成员,能够获得相应的类型。所以我们可以这样写
template <typename It> auto func(It & _beg, It & _end) -> typename remove_reference<decltype(*_beg)>::type //don't forget typename { //... auto sum = *_beg; sum = 0; for_each(_beg,_end,[&sum](const int & val){ sum+= val;}); //... return sum; }
在某些情况下我们可以指定返回u类型,例如
template<typename T1, typename T2, typename T3> T1 sum(T2 t2, T3 t3) { return t2 + t3; } sum<long long>(10,200); //or sum<long long, int, int>();
显示模板参数按从左到右的顺序一次匹配。
兼容类型的模板问题
有这样的代码
template<typename T> T sum(T t1, T t2) { return t1 + t2; } sum(10,3.14);
虽然int和double兼容,但是只有一个类型参数,编译器傻了,T为int?精度会丢失,肯定是不可行的,T为double?貌似也不行,这样会导致数据溢出。无奈我们只好这样了。
template<typename T1, typename T2> ??? sum(T1 t1, T2 t2) { return t1 + t2; }
至于返回类型,全交给程序员来规定,或者用尾部返回类型。
函数指针实参推断
有趣的是,虽然在未实例化之前,编译器没有生成具体的代码,但我们仍然可以进行函数指针绑定的操作。
template <typename T> int compare(const T & t1, const T & t2) { } int (*pf_int)(const int &,const int &) = compare;
同样的我们也可以将模板函数作为回调函数进行传参,但此时可能会产生二义性,所以注意显示的写出模板参数。
当参数是一个函数模板实例的地址时,程序上下文必须满足:对于每个模板参数,能唯一确定其类型的值。
模板实参推断
这里是重中之重!!!重中之重!!!
很多的模板问题都与此有关。
关于const和&的问题,我们上面已经讲过了。这里再进行进一步的说明。
左值引用
template <typename T> void func1(T &) { } template <typename T> void func2(const T &) { } void aa() { int a = 10; const int b = 20; func1(a); //T is int func1(b); //T is const int func2(a); //T is int func2(b); //T is int func2(10); //T is int }
还是比较有意思的,看func2(b)的调用,虽然我们将const int类型传入进去,但是编译器为我们推导的还是int,原因应该和参数类型有关,如果编译器为我们推导的是const int ,那么const const int是不合法的,所以只好为我们推倒为int,即使我们调用时候的类型是const int。
右值引用
template <typename T> void func(T &&); func(10); //T is int func(b); //b is a left_val T is ???
我们可以根据引用折叠可以推断出类型。
引用折叠和万能引用
众所周知在非模板函数中可以使用const & 来接受任意类型参数,在模板中,也有类似这样的万能引用,就是&&。知道了这样的原因是有着引用折叠得的存在。
先说结论:在传递参数的过程中,无论多么复杂的引用传参,最后都会被折叠为& 或者 &&.
如果我们间接创建了一个引用的引用,则这些引用形成折叠。除了右值引用的右值引用会被折叠为一个右值引用,剩下全部折叠为一个左值引用。即
T& &, T& &&, T&& &都会折叠为T&
T&& &&会被折叠为&&
这就意味着我们 可以解释上面的问题。
当我们将一个左值传递给一个右值引时候,编译器推断T的类型为&。注意是T的类型为左值引用,不是整个形参是T &。
所以
func(b); //b is a left_val T is int&
上述的两个规则导致了
如果一个函数参数是一个指向模板类型参数的右值引用,则他可以被绑定到一个左值。
如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数将被参数将被实例化为一个普通左值引用参数。
这两个规则又暗示了我们——我们可以将任意类型的实参传递给参数为右值引用的函数。
当代码中涉及的类型可能是非引用类型,也可能是引用类型的时候,编写正确的代码就变得异常困难(虽然remove_reference这样的转换类型对我们可能有所帮助)。
PS:由于这里的知识是在是很乱,笔者在写这里的时候也实在无能为力,所以大量了引用C++ Primer的原文。但是有一点可以保证——笔者在这里写的demo虽然没有什么实际意义仅用于演示——但是也能说明一些问题。
如果读者对模板的细节想以探究经,可以翻越C++ Primer——中文第五版P508-P610。
如果想巩固这里的语法,可以作相应的配套习题。
std::move
折磨的篇章终于过去了,让我们用好奇心来看一看std::move这个工具。
短小精悍的std::move定义
如下
template <typename T> typename remove_reference<T>::type && move(T && t) { return static_cast<typename remove_reference<T>::type&&>(t); }
std::move的解析
因为move可以接受任意对象,所以应当是一个模板类。
既然我们要保证返回一个右值,那我们应当明确的得到一个非左右值引用类型——即普通类型。
那么就可以先移除引用再加上右值引用——这样保证了返回一个右值引用对应了
typename remove_reference<T>::type &&
既然接受任意一个对象,那美可以用&&来接受实参,对应
move(T && t)
我们只需要返回一个右值即可,所以只有一个return语句。
我们回想以下为什么要使用std::move——获得一个右值进行移动构造?又或者是仅仅需要一个右值?不管出于什么原因,最终的目的就是为了优化程序,所以通过形参创建一个额外的右值并返回这样是不可取的,是脱裤子放屁,所以我们要使用这条语句
static_cast<typename remove_reference<T>::type&&>(t);
通常情况下,static——cast用于合法的类型转换,但是又一种情况例外,虽然一个左值不能隐式转换为右值,但是可以使用static_cat将其显示的转换为右值——前提我们先移除对象身上的引用效果。
模板和重载
模板也是可以被重载的,只要没有二义性。像在C++库中,存在着大量的模板重载技术,或者是可变模板参数中,也存在着模板的重载。
对于实例化的选择,遵循以下的规则。
1.对于一个调用,其候选函数是所有可行的实例化
2.可行函数按类型转换来排序。当然,可用于函数模板调用和的类型转换是非常有限的。
3.和普通函数一样,如果恰又一个函数比任何其他函数都更好的匹配,则选择此函数。
4.如果多个函数提供了同样好的匹配
1)优先选择非模板函数
2)没有非模板函数我选择更加特例化的模板
3)否则有二意性
正确的定义一组重载的函数模板需要对类型键的关系以及模板幻术允许的优先的实参类型转换有着深刻的理解。
注意:虽然非模板函数的优先级很高——但那也是没有对应模板匹配的情况下,所以,在重载模板的时候仔细观察和思考。
所以我们为了适配字符串的比较,可以写出这样的代码
template<size_t N, size_t M> int compare(const char str1[N], const char str2[M]) { return strcmp(str1,str2); } //或者 int compare(const char * const str1, const char * const str2) { return strcmp(str1,str2); }
根据上面的匹配规则,我们还可以递归的调用模板类自己实现某些功能
template<typename T> string debug_rep(const T & t) { ostringstream ret; ret << t ; return ret.str(); } template<typename T> string debug_rep(const T * p) { ostringstream ret; ret << "pointer :" << p; if(p != nullptr) ret << " " << debug_rep(*p); else ret << " nullptr"; return ret.str(); } //适配C风格字符串 string debug_reo(char * p) { return debug_rep(string(p)); //这是一个右值,不能获取其地址 } string debug_rep(const char * p) { return debug_rep(string(p)); } int main() { string s("hello"); string* ps = &s; cout << debug_rep(ps) << endl << debug_rep(s); return 0; }
运行结果
模板函数匹配的特殊性
难道没有发现一个异常的地方吗?对于指针版本的调用,可以这样实例化两个函数
string debug_rep(const string* & t); string debug_rep(string * p);
对于普通函数,这是无疑的二义性,但是模板会选择特例化高的,原因是const T&可以实例化任何类型,而const T * p只能实例化指针类型——特例化程度更高。所以会调用后者。
这就说明了:不要将普通函数的匹配机制应用于模板函数匹配机制——虽然两者很像,但是还是有某些地方是不一样的。
注意重载模板声明顺序
由于模板的特性,使得其可以递归的调用自己的不同版本,但是注意要调用的版本一定要事先声明或者定义,否则可能出现函数不匹配的情况
我们把适配char * 接口的字符串的函数放到最前面,我们发现编译器会右值河阳的错误。
No matching function for call to 'debug_rep'
调用“debug_rep”没有匹配的函数
或者我们将两个debug_rep的模板版本调换以下顺序。
Call to function 'debug_rep' that is neither visible in the template definition nor found by argument-dependent lookup
调用函数“debug_rep”,该函数在模板定义中既不可见,也不通过参数相关查找找到
模板特例化
我们编写的模板,不可能保证对于所有的类型都能适用——compare函数就是很经典的例子,对于两个指针类型,仅仅是毫无意义的比较。这时候我们用到模板特例化的技术可以很好的解决这样的问题。
由于less的底层是使用<来比较的,所以less并没有适配字符指针。那么,我们可以编写这样的模板特例化。
template <> //表示一个模板特例化——语法规定 int compare(const char * const & str1, const char * const & str2) //具体的类型 { return strcmp(str1,str2); }
可以看出,模板特例化的尖括号中没有任何说明,所以模板特例化要对所有模板那参数都进行特例化。
注意,上面的特例化只能处理字符指针,不能处理数组或者字符串面量——这和函数匹配机制有关。这个特例化仅仅接受char*以及其const版本,虽然字符数组的名字就是他的地址,但是在模板中会被解释为一个字符数组的引用,更加的精准匹配。如果想要支持字符面量(本质上是字符数组)和字符数组,请写一个重载的模板函数——见模板重载。
我们可以使用调试观察是如何推断实参类型的
推断为一个数组的引用——这显然比将数组转换为指针再进行匹配更加精确。
特例化和重载 的区别
特例化就是一个特殊的实例化——模板的实例化,所以,特例化仅仅是模板的一个实例化,不会影响函数匹配。
并且,模板特例化一定要保证模板的之前的声明或者定义。如果不这样做——编译器不会报错,但是会有一些令人匪夷所思的地方。模板会由编译器实例化,而不是调用自己特例化版本——这种错误往往很难查找。所以,记住一个规则:特例化一个模板,一定要保证其在原模板的定义域中。
类模板特例化
这里引用《C++ Primer》的例子——对其做一些解析。
hash容器是能够进行十分快速的查找容器,像hash_map, hash_set等。他们的底层使用什么来映射哈希值呢? hash模板类。hash - C++ Reference (cplusplus.com)
那么对于我们自定义的类型来说,没有其对应算法,为了能够使用我们的自定义类型,我们可以定义一个其特例化版本。
一个hash的特例化必须包括
一个重载的调用运算符,接受一个容器关键字类型的对象,返回一个size_t——用于映射对象的哈希值。
两个类型成员,result_type, argument_type,分别调用运算符返回类型和参数类型。
默认构造函数和拷贝赋值运算符。
于是我们可以写出如下的代码。
class Book { friend class std::hash<Book>; //hash使用了私有成员,所以将其声明为友元 private: int book_id; string book_name; public: Book() = default; Book(const int a, const string & _name) : book_id(a), book_name(_name) { } bool operator==(const Book & b) const { return book_id == b.book_id; } };
我们首先定义一个Book类,然后提供其==运算符确保hash模板能够自持我们的自定义类型。
随后我们在std命名空间中特例化一个和hash,
namespace std { template <> struct hash<Book> { //必须提供的成员 typedef size_t result_type; typedef Book argument_type; size_t operator()(const Book & b) const; }; size_t hash<Book>::operator()(const Book &b) const { //自定义如何组织hash_val return hash<string>()(b.book_name) ^ hash<int>()(b.book_id); } }
之后,我们就可以使用unordered_set/map,来操纵我们的自定义类型了。
为了能够让自定义数据类型的特例化能够被正常使用,应该将其放在类声明对应的头文件中,或者用别的头文件将其包含进来。
部分模板特例化
我们可以指定一部分而非所有的模板参数,或者是参数的一部分而非全部特性。一个模板的部分特例化本身是一个模板,使用它时用户还必须为哪些在特例化版本呢中未指定的模板参数提供实参。
部分特例化一部分模板参数特例化,没有特例化的部分额外的提供实参。
标准库的remove_reference 就是使用一系列的特例化完成其功能的,我们将其转到定义。
这里部分特例化的时参数的引用性质。
template<typename _Tp> struct remove_reference { typedef _Tp type; }; template<typename _Tp> struct remove_reference<_Tp&> { typedef _Tp type; }; template<typename _Tp> struct remove_reference<_Tp&&> { typedef _Tp type; };
具体的语法:
在正常的模板声明之后,在类的后面使用尖括号<>放入要特例化的实参,这些实参于原始模板中的参数按照位置对应。也就是对应着上面源码中的
struct remove_reference<_Tp&&>
注意:模板部分特例化并不是对于某个单单的模板参数特例化,也可能是模板参数属性的特例化,这点对于理解类模板部分特例化十分重要
注意:我们只能部分特例化类模板,而不能部分特例化模板函数。
例如我们定义一个泛用的模板类和实例化一个其专门用来处理指针的类
template <typename T> class A; template <typename T> class A<T *>;
特例化成员
我们可以只特例化特定的成员函数而不是特例化整个模板类。
template <typename T> class A { private: T val; public: A(const T & t) : val(t) { } void func(); }; template<> void A<int>::func() { }
那我们这样得到的就是对于A<int>的实例化下的一个特例化func——这个func只在int的实例化版本生效。也就是说,实例化int版本的A其对应的func是我们特例化的这个版本,而其他成员还是正常的实例化。
可变参数模板
声明:关于模板的递归调用,都有一个基线函数,只不过是没有写出。
关于可变参数模板,一部分引用我之前的文章,再在这里做一些补充
可变模板参数 variadic template
包 packet
模板参数包 template parameter packet
函数参数包 function paremeter packet
详情见这篇文章的可变模板参数。
补充:
sizeof...运算符
能够获得包中参数的个数
template<typename T, typename... Args> void var_fun(const T & t, const Args&... args) { //cout << t; cout << "element numbers of packs is " << sizeof...(Args); //var_fun(args...); }
包拓展
拓展 packs expand
包拓展简单的来说将他分解为其构成的元素,如果说将参数变为包的过成类比为压缩未见,那么包拓展就是解压文件,但包拓展不仅仅是包展开。
当拓展一个包时,我们还要提供用于,每个拓展元素的模式。拓展一个包就是将它费解为构成的原书,对每个元素应用模式,获得拓展后的列表。我们通过在模式右边防一个省略号...来触发拓展操作 。
什么是模式?
在实际生活中,当我们说以一种模式打开某个东西,或者是什么模式打开时。指定的是固有的模式,比如说性能模式,均衡模式等。而抱拓展的模式更像是对于每个元素都调用一次相应的函数,包拓展需要我们自定义模式——其实就是一个函数,返回值为包中的一个元素应用模式后的结果,所有这样的结果组合在一起,也就是包以这个模式(函数)展开。
看一下标准库的配置器中是如何使用的展开
noexcept(noexcept(::new((void *)__p) _Up(std::forward<_Args>(__args)...)))
这是一个函数异常声明的部分,当用一个包构造一个元素的时候不会抛出异常,仅当,使用转发模式对参数包进行展开的时候不抛出异常。
var_func(args...); //默认的包展开 //注释部分的...不为关键字,和C++语法没有任何关系 //相当于这样{ele1, ele2, ele3, ... ,elen} var_fun(mul(2,args)...); //带有模式的包展开 //第二种展开模式相当于这样 //{ mul(2,ele0),mul(2,ele1),mul(2,ele2), ... mul(2,elen) }
具体实验
template <typename T> void print(const T & t) { cout << t << endl; } template <typename T, typename... Args> void print(const T &t ,const Args... args) { cout << t << endl; print(args...); } template <typename T> int up(T & t) { t *= 2; return t; } template <typename... Args> void func(Args&&... args) { print(up(args)...); } int main() { func(1,2,3,4,5); return 0; }
运行结果
可变模板参数的具体作用
可变模板参数可以说是一个核弹,比如tuple就是使用其实现的,模板类tuple以私有继承的方式继承它自己并结合模板部分特例化。如下
template<typename T, typename ... Args> class tuple<T,Args...> : private tuple<Args...> { //something };
还是很奇妙的,具体详情请观看侯捷老师的视频——bilibili :
模板技巧
模板的功能还是很强大的,我们有必要学习一些模板技巧。
转发
什么是转发?
某些函数需要将其一个或多个实参连同类型不变的传递给其他函数。这个过程就叫转发。
很形象,一个函数把数据原封不动的传递给另一个函数,就是转发。
什么时候会用到转发呢?比如说我们有这样的一个函数,在容器尾部直接使用我们穿进来的参数构造一个元素,这个时候使用转发就是很有必要的。如果我们不适用转发技术,可能会造成变量的复制,也许有的时候这个函数能正常使用,但是有的时候我们就需要引用来做事,所以这样做留下的错误的隐患。
假设有func1(int &,args) fun2 work(args,int&);
我们需要传进func1一个整形,经过func2的中间媒介,传入work,并在work中改变那个变量。
读者可以试一下func2中使用什么样的参数,经过怎样的变换可以对原来的参数的性质原封不动的传递给work。这是比较简单的情况了。STL的部分函数实现会有恐怖的调用层次,如果不使用转发技术后果可想而知。
使用std::forward
要说转发一定离不开std::forward
forward返回实参类型的右值引用。它和move很像,但前者是返回给定类型的右值引用,如果给定的类型是左值引用也返回其右值引用——左值引用,并且其必须显式的指定模板参数;而move无论模板参数类型是什么都返回一个右值引用(只能是右值引用),因为前面已经看到了move的实现方法。
于是我们可以定义下面的转发函数
template <typename F, typename T1, typename T2> void fun(F f, T1 && t1, T2 && t2) { f(std::forward<T2>(t2), std::forward<T1>(t1)); }
使用右值引用作为模板参数——确保接受任意对象,并保证其能保持原来的性质不变(见引用折叠)。在发送参数的过程中获得对应类型的右值——确保其传递给函数的参数的性质不变(见引用折叠)。
更简单的来说,上述的写法是对于所有类型的对象,无论进行何种参数传递,其参数的性质都不会改变的通用情况。
转发参数包
根据上面转发的关键字,我们可以知道,在进行转发的时候应该以何种模式进行包展开。
(Args&& ... args) //以右值引用展开参数包 std::forward<Args>(args)... //将包中的每一个元素应用于forward
所以我们可以这样做
template<typename ... Args> void buffer_fun(Args &&... args) { work(std::forward<Args>(args)...); }
make_shared的工作原理
std::make_shred就是基于转发参数包实现的。
让我们先来回忆make_shared的其中一个使用方法。
make_shared<T> name (args);
很明显的可以推断其应该使用用可变模板参数,我们转到其定义
template<typename _Tp, typename... _Args> inline shared_ptr<_Tp> make_shared(_Args&&... __args) { typedef typename std::remove_cv<_Tp>::type _Tp_nc; return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(), std::forward<_Args>(__args)...); }
可以看见,其使用部分特例化和可变参数模板,将包转发给了std::allocate_shared进行空间分配,我们进一步的转到std::allocate_shared中可以看见其有进一步的将其转发给了其他的模板
template<typename _Tp, typename _Alloc, typename... _Args> inline shared_ptr<_Tp> allocate_shared(const _Alloc& __a, _Args&&... __args) { static_assert(!is_array<_Tp>::value, "make_shared<T[]> not supported"); return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{__a}, std::forward<_Args>(__args)...); }
至此,C++的模板的基础知识点大抵应该是都讲完了,如果日后有一些杂项补充的话会更在下面。
后来的话: 这篇文章有一些地方讲的还是比较不清晰的,现在已经修正了一部分,并且增添了对一部分知识点的代码,日后也会慢慢修改。如果你发现本篇文章的错误或者对本篇文章有什么建议可以评论区留言或者私信笔者。
——2022.6.1
-
700个H5网站模板源码
2018-05-16 09:46:13一共700个,都是H5网站,适合纯静态,没有后台,可以作为各行业展示型使用 -
9套大气漂亮的html+css网站模板、网页设计源码
2018-01-03 01:31:191.html班级网页设计模板 2.html动漫网页设计模板 3.html个人网页设计模板 4.html化妆品网页设计模板 5.html咖啡网页设计模板 6.html旅游网页设计模板 7.html商城网页设计模板 8.html书店网页设计模板 9.html公司网页... -
高逼格PPT模板20套.rar
2020-03-18 17:25:53高逼格41套ppt模板,模板多内容全面,各式各样都有,(本人也是自己在用的,看过绝对不后悔)肯定有你喜欢的一套.因为ppt模板都很好,,比较大,只上传了1-20套, 另外20套在另一个资源里上传,,觉得好的可以再下载. -
个人简历模板
2018-12-12 15:47:36优质简历模板,目前最前全的模板收藏,需要换工作的小伙伴们可以试试 -
103套PPT模板.zip
2020-12-05 16:30:34读本科常用的100套PPT模板,里面的模板能应用于答辩、汇报、经验分享,风格各异,可以根据自己的喜欢进行挑选 -
非常全的 AXURE 模板源码(含WEB、手机)
2016-09-12 22:33:39Axure超全模板盘点,拿来即用,费了很大劲采下载下来,包括网站、后台、电商三大类。既有WEB的,也有手机的。 -
精美的ppt模板2
2013-09-18 11:27:19灵活调用模板PowerPoint提供的模板非常丰富,可以根据需要灵活选用:选择“文件”→“新建”,在打开的任务窗格中可以看到它提供了“新建”、“根据现有演示文稿新建”和“根据模板新建”三种调用模板的方式。... -
EPLAN项目模板
2015-09-18 15:05:51EPLAN软件的项目模板,给遇到相关问题的朋友 -
Axure后台管理系统框架原型模板
2018-01-28 00:35:26Axure后台管理系统框架原型模板,高保真效果,希望对大家有帮助 -
C++函数模板(模板函数)详解
2019-07-04 16:03:01C++函数模板(模板函数)详解定义用法:函数模板的原理延申用法2.1为什么需要类模板2.2单个类模板语法2.3继承中的类模板语法案例1:案例2:2.4类模板的基础语法2.5类模板语法知识体系梳理1.所有的类模板函数写在类的...C++函数模板(模板函数)详解
定义
函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。(好吧,咱也听不懂,直接上用法吧?)
用法:
面向对象的继承和多态机制有效提高了程序的可重用性和可扩充性。在程序的可重用性方面,程序员还希望得到更多支持。举一个最简单的例子,为了交换两个整型变量的值,需要写下面的 Swap 函数:
void Swap(int & x, int & y) { int tmp = x; x = y; y = tmp; }
为了交换两个 double 型变量的值,还需要编写下面的 Swap 函数:
void Swap (double & xr double & y) { double tmp = x; x = y; y = tmp; }
如果还要交换两个 char 型变量的值,交换两个 CStudent 类对象的值……都需要再编写 Swap 函数。而这些 Swap 函数除了处理的数据类型不同外,形式上都是一样的。能否只写一遍 Swap 函数,就能用来交换各种类型的变量的值呢?继承和多态显然无法解决这个问题。因此,“模板”的概念就应运而生了。
众所周知,有了“模子”后,用“模子”来批量制造陶瓷、塑料、金属制品等就变得容易了。程序设计语言中的模板就是用来批量生成功能和形式都几乎相同的代码的。有了模板,编译器就能在需要的时候,根据模板自动生成程序的代码。从同一个模板自动生成的代码,形式几乎是一样的。
函数模板的原理
C++ 语言支持模板。有了模板,可以只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Sawp 函数,用以交换不同类型变量的值。
在 C++ 中,模板分为函数模板和类模板两种。
- 函数模板是用于生成函数;
- 类模板则是用于生成类的。
函数模板的写法如下:
template <class 类型参数1, class类型参数2, ...> 返回值类型 模板名(形参表) { 函数体 }
其中的 class 关键字也可以用 typename 关键字替换,例如:
template <typename 类型参数1, typename 类型参数2, ...>
函数模板看上去就像一个函数。前面提到的 Swap 模板的写法如下:
template <class T> void Swap(T & x, T & y) { T tmp = x; x = y; y = tmp; }
T 是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。
例如下面的程序:
#include <iostream> using namespace std; template<class T> void Swap(T & x, T & y) { T tmp = x; x = y; y = tmp; } int main() { int n = 1, m = 2; Swap(n, m); //编译器自动生成 void Swap (int &, int &)函数 double f = 1.2, g = 2.3; Swap(f, g); //编译器自动生成 void Swap (double &, double &)函数 return 0; }
编译器在编译到Swap(n, m);时找不到函数 Swap 的定义,但是发现实参 n、m 都是 int 类型的,用 int 类型替换 Swap 模板中的 T 能得到下面的函数:
void Swap (int & x, int & y) { int tmp = x; x = y; y = tmp; }
该函数可以匹配Swap(n, m);这条语句。于是编译器就自动用 int 替换 Swap 模板中的 T,生成上面的 Swap 函数,将该 Swap 函数的源代码加入程序中一起编译,并且将Swap(n, m);编译成对自动生成的 Swap 函数的调用。
同理,编译器在编译到Swap(f, g);时会用 double 替换 Swap 模板中的 T,自动生成以下 Swap 函数:
void Swap(double & x, double & y) { double tmp = x; x = y; y = tmp; }
然后再将Swap(f, g);编译成对该 Swap 函数的调用。
编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在某些编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误。
编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以用:
模板名<实际类型参数1, 实际类型参数2, ...>
的方式告诉编译器应该如何实例化模板函数。例如下面的程序:
#include <iostream> using namespace std; template <class T> T Inc(int n) { return 1 + n; } int main() { cout << Inc<double>(4) / 2; return 0; }
Inc(4)指明了此处实例化的模板函数原型应为:
double Inc(double);
编译器不会因为实参 4 是 int 类型,就生成原型为 int Inc(int) 的函数。因此,上面程序输出的结果是 2.5 而非 2。
函数模板中可以有不止一个类型参数。例如,下面这个函数模板的写法是合法的:
template <class Tl, class T2> T2 print(T1 argl, T2 arg2) { cout << arg1 << " " << arg2 << endl; return arg2; }
【实例】一个求数组中最大元素的函数模板
例题:设计一个分数类 CFraction,再设计一个名为 MaxElement 的函数模板,能够求数组中最大的元素,并用该模板求一个 CFmction 数组中的最大元素。示例程序如下:
#include <iostream> using namespace std; template <class T> T MaxElement(T a[], int size) //size是数组元素个数 { T tmpMax = a[0]; for (int i = 1; i < size; ++i) if (tmpMax < a[i]) tmpMax = a[i]; return tmpMax; } class CFraction //分数类 { int numerator; //分子 int denominator; //分母 public: CFraction(int n, int d) :numerator(n), denominator(d) { }; bool operator <(const CFraction & f) const {//为避免除法产生的浮点误差,用乘法判断两个分数的大小关系 if (denominator * f.denominator > 0) return numerator * f.denominator < denominator * f.numerator; else return numerator * f.denominator > denominator * f.numerator; } bool operator == (const CFraction & f) const {//为避免除法产生的浮点误差,用乘法判断两个分数是否相等 return numerator * f.denominator == denominator * f.numerator; } friend ostream & operator <<(ostream & o, const CFraction & f); }; ostream & operator <<(ostream & o, const CFraction & f) {//重载 << 使得分数对象可以通过cout输出 o << f.numerator << "/" << f.denominator; //输出"分子/分母" 形式 return o; } int main() { int a[5] = { 1,5,2,3,4 }; CFraction f[4] = { CFraction(8,6),CFraction(-8,4), CFraction(3,2), CFraction(5,6) }; cout << MaxElement(a, 5) << endl; cout << MaxElement(f, 4) << endl; return 0; }
编译到第 41 行时,根据实参 a 的类型,编译器通过 MaxElement 模板自动生成了一个 MaxElement 函数,原型为:
int MaxElement(int a[], int size);
编译到第 42 行时,根据 f 的类型,编译器又生成一个 MaxElement 函数,原型为:
CFraction MaxElement(CFraction a[], int size);
在该函数中,用到了<比较两个 CFraction 对象的大小。如果没有对<进行适当的重载,编译时就会出错。
从 MaxElement 模板的写法可以看出,在函数模板中,类型参数不但可以用来定义参数的类型,还能用于定义局部变量和函数模板的返回值。
延申用法
2.1为什么需要类模板
-
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
-
类模板用于实现类所需数据的类型参数化
-
类模板在表示如数组、表、图等数据结构显得特别重要,
-
这些数据结构的表示和算法不受所包含的元素类型的影响
2.2单个类模板语法
1 //类的类型参数化 抽象的类 2 //单个类模板 3 template<typename T> 4 class A 5 { 6 public: 7 A(T t) 8 { 9 this->t = t; 10 } 11 12 T &getT() 13 { 14 return t; 15 } 16 protected: 17 public: 18 T t; 19 }; 20 void main() 21 { 22 //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 23 A<int> a(100); 24 a.getT(); 25 printAA(a); 26 return ; 27 }
2.3继承中的类模板语法
案例1:
1 //结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> 2 // 3 class B : public A<int> 4 { 5 public: 6 B(int i) : A<int>(i) 7 { 8 9 } 10 void printB() 11 { 12 cout<<"A:"<<t<<endl; 13 } 14 protected: 15 private: 16 }; 17 18 //模板与上继承 19 //怎么样从基类继承 20 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数 21 void pintBB(B &b) 22 { 23 b.printB(); 24 } 25 void printAA(A<int> &a) //类模板做函数参数 26 { 27 // 28 a.getT(); 29 } 30 31 void main() 32 { 33 A<int> a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 34 a.getT(); 35 printAA(a); 36 37 B b(10); 38 b.printB(); 39 40 41 cout<<"hello..."<<endl; 42 system("pause"); 43 return ; 44 }
案例2:
1 #include<iostream> 2 using namespace std; 3 //A编程模板类--类型参数化 4 /* 5 类模板的定义 类模板的使用 类模板做函数参数 6 */ 7 template <typename T> 8 class A 9 { 10 public: 11 A(T a = 0) 12 { 13 this->a = a; 14 } 15 public: 16 void printA() 17 { 18 cout << "a:" << a << endl; 19 } 20 protected: 21 T a; 22 private: 23 24 }; 25 //从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的 26 //要知道父类所占的内存多少 27 class B :public A<int> 28 { 29 public: 30 B(int a =10, int b =20):A<int>(a) 31 { 32 this->b = b; 33 } 34 void printB() 35 { 36 cout << "a:" << a << "b:" << b << endl; 37 } 38 protected: 39 private: 40 int b; 41 42 }; 43 //从模板类派生模板类 44 template <typename T> 45 class C :public A<T> 46 { 47 48 public: 49 C(T c,T a) : A<T>(a) 50 { 51 this->c = c; 52 } 53 void printC() 54 { 55 cout << "c:" << c << endl; 56 } 57 protected: 58 T c; 59 private: 60 61 }; 62 63 void main() 64 { 65 //B b1(1, 2); 66 //b1.printB(); 67 C<int> c1(1,2); 68 c1.printC(); 69 }
2.4类模板的基础语法
1 #include<iostream> 2 using namespace std; 3 //A编程模板类--类型参数化 4 /* 5 类模板的定义 类模板的使用 类模板做函数参数 6 */ 7 template <typename T> 8 class A 9 { 10 public: 11 A(T a = 0) 12 { 13 this->a = a; 14 } 15 public: 16 void printA() 17 { 18 cout << "a:" << a << endl; 19 } 20 protected: 21 private: 22 T a; 23 }; 24 //参数 C++编译器具体的类 25 void UseA(A<int> &a) 26 { 27 a.printA(); 28 } 29 void main() 30 { 31 //模板类本身就是抽象的,具体的类,具体的变量 32 A<int> a1(11),a2(22),a3(33);//模板类是抽象的, 需要类型具体化 33 //a1.printA(); 34 35 UseA(a1); 36 UseA(a2); 37 UseA(a3); 38 }
2.5类模板语法知识体系梳理
1.所有的类模板函数写在类的内部
代码:
复数类:
1 #include<iostream> 2 using namespace std; 3 template <typename T> 4 class Complex 5 { 6 public: 7 friend Complex MySub(Complex &c1, Complex &c2) 8 { 9 Complex tmp(c1.a-c2.a, c1.b-c2.b); 10 return tmp; 11 } 12 13 friend ostream & operator<< (ostream &out, Complex &c3) 14 { 15 out << c3.a << "+" << c3.b <<"i"<< endl; 16 return out; 17 } 18 Complex(T a, T b) 19 { 20 this->a = a; 21 this->b = b; 22 } 23 Complex operator+(Complex &c2) 24 { 25 Complex tmp(a + c2.a, b + c2.b); 26 return tmp; 27 } 28 void printCom() 29 { 30 cout << "a:" << a << " b:" << b << endl; 31 } 32 protected: 33 private: 34 T a; 35 T b; 36 }; 37 38 /* 39 重载运算符的正规写法: 40 重载左移<< 右移>> 只能用友元函数,其他的运算符重载都要用成员函数,不要滥用友元函数 41 */ 42 //ostream & operator<< (ostream &out, Complex &c3) 43 //{ 44 // out<< "a:" << c3.a << " b:" << c3.b << endl; 45 // return out; 46 //} 47 void main() 48 { 49 Complex<int> c1(1,2); 50 Complex<int> c2(3, 4); 51 52 Complex<int> c3 = c1 + c2;//重载加号运算符 53 54 c3.printCom(); 55 56 //重载左移运算符 57 cout << c3 << endl; 58 59 { 60 Complex<int> c4 = MySub(c1 , c2); 61 62 cout << c4 << endl; 63 } 64 system("pause"); 65 }
2.所有的类模板函数写在类的外部,在一个cpp中
注意:
复制代码
//构造函数 没有问题-
普通函数 没有问题
-
友元函数:用友元函数重载 << >>
friend ostream& operator<< (ostream &out, Complex &c3) ;
-
友元函数:友元函数不是实现函数重载(非 << >>)
1 需要在类前增加 类的前置声明 函数的前置声明
template<typename T> class Complex; template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
2 类的内部声明 必须写成:
friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
3 友元函数实现 必须写成:
template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) { Complex<T> tmp(c1.a - c2.a, c1.b-c2.b); return tmp; }
4 友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2); cout<<c4;
结论:友元函数只用来进行 左移 友移操作符重载。
复数类:
代码:
1 #include<iostream> 2 using namespace std; 3 4 template<typename T> 5 class Complex; 6 template<typename T> 7 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); 8 9 template <typename T> 10 class Complex 11 { 12 public: 13 friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); 14 15 friend ostream & operator<< <T>(ostream &out, Complex &c3); 16 Complex(T a, T b); 17 void printCom(); 18 Complex operator+(Complex &c2); 19 Complex operator-(Complex &c2); 20 21 protected: 22 private: 23 T a; 24 T b; 25 }; 26 27 //构造函数的实现,写在了外部 28 template <typename T> 29 Complex<T>::Complex(T a, T b) 30 { 31 this->a = a; 32 this->b = b; 33 } 34 35 template <typename T> 36 void Complex<T>::printCom() 37 { 38 cout << "a:" << a << " b:" << b << endl; 39 } 40 //成员函数实现加号运算符重载 41 template <typename T> 42 Complex<T> Complex<T>::operator+(Complex<T> &c2) 43 { 44 Complex tmp(a + c2.a, b + c2.b); 45 return tmp; 46 } 47 template <typename T> 48 Complex<T> Complex<T>::operator-(Complex<T> &c2) 49 { 50 Complex(a-c2.a,a-c2.b); 51 return tmp; 52 } 53 //友元函数实现<<左移运算符重载 54 55 /* 56 严重性 代码 说明 项目 文件 行 禁止显示状态 57 错误 C2768 “operator <<”: 非法使用显式模板参数 泛型编程课堂操练 58 59 错误的本质:两次编译的函数头,第一次编译的函数头,和第二次编译的函数有不一样 60 */ 61 template <typename T> 62 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T 63 { 64 out << c3.a << "+" << c3.b << "i" << endl; 65 return out; 66 } 67 68 69 // 70 template <typename T> 71 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) 72 { 73 Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); 74 return tmp; 75 } 76 77 void main() 78 { 79 Complex<int> c1(1, 2); 80 Complex<int> c2(3, 4); 81 82 Complex<int> c3 = c1 + c2;//重载加号运算符 83 84 c3.printCom(); 85 86 //重载左移运算符 87 cout << c3 << endl; 88 89 { 90 Complex<int> c4 = mySub<int>(c1, c2); 91 92 cout << c4 << endl; 93 } 94 system("pause"); 95 }
所有的类模板函数写在类的外部,在不同的.h和.cpp中
也就是类模板函数说明和类模板实现分开//类模板函数
构造函数
普通成员函数
友元函数
用友元函数重载<<>>;
用友元函数重载非<< >>
demo_complex.cpp
1 #include"demo_09complex.h" 2 #include<iostream> 3 using namespace std; 4 5 template <typename T> 6 Complex<T>::Complex(T a, T b) 7 { 8 this->a = a; 9 this->b = b; 10 } 11 12 template <typename T> 13 void Complex<T>::printCom() 14 { 15 cout << "a:" << a << " b:" << b << endl; 16 } 17 //成员函数实现加号运算符重载 18 template <typename T> 19 Complex<T> Complex<T>::operator+(Complex<T> &c2) 20 { 21 Complex tmp(a + c2.a, b + c2.b); 22 return tmp; 23 } 24 //template <typename T> 25 //Complex<T> Complex<T>::operator-(Complex<T> &c2) 26 //{ 27 // Complex(a - c2.a, a - c2.b); 28 // return tmp; 29 //} 30 template <typename T> 31 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T 32 { 33 out << c3.a << "+" << c3.b << "i" << endl; 34 return out; 35 } 36 37 38 // 39 //template <typename T> 40 //Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) 41 //{ 42 // Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); 43 // return tmp; 44 //}
demo_09complex.h
1 #pragma once 2 #include<iostream> 3 using namespace std; 4 template <typename T> 5 class Complex 6 { 7 public: 8 //friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); 9 10 friend ostream & operator<< <T>(ostream &out, Complex &c3); 11 Complex(T a, T b); 12 void printCom(); 13 Complex operator+(Complex &c2); 14 //Complex operator-(Complex &c2); 15 16 protected: 17 private: 18 T a; 19 T b; 20 };
demo_09complex_text.cpp
1 #include"demo_09complex.h" 2 #include"demo_09complex.cpp" 3 4 #include<iostream> 5 using namespace std; 6 7 void main() 8 { 9 Complex<int> c1(1, 2); 10 Complex<int> c2(3, 4); 11 12 Complex<int> c3 = c1 + c2;//重载加号运算符 13 14 c3.printCom(); 15 16 //重载左移运算符 17 cout << c3 << endl; 18 19 /*{ 20 Complex<int> c4 = mySub<int>(c1, c2); 21 22 cout << c4 << endl; 23 }*/ 24 system("pause"); 25 }
2.5总结
归纳以上的介绍,可以这样声明和使用类模板:
- 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
- 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
- 在类声明前面加入一行,格式为:
template <class 虚拟类型参数> 如: template <class numtype> //注意本行末尾无分号 class Compare {…}; //类体
- 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名; 类模板名<实际类型名> 对象名(实参表列); 如: Compare<int> cmp; Compare<int> cmp(3,7);
- 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数> 函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
-
类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2> class someclass {…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
-
和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
-
模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
2.6类模板中的static关键字
从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
每个模板类有自己的类模板的static数据成员副本1 #include<iostream> 2 using namespace std; 3 template <typename T> 4 class AA 5 { 6 public: 7 static T m_a; 8 protected: 9 private: 10 }; 11 template <typename T> 12 T AA<T>::m_a =0; 13 void main() 14 { 15 AA<int> a1, a2, a3; 16 a1.m_a = 10; 17 a2.m_a++; 18 a3.m_a++; 19 cout << AA<int>::m_a << endl; 20 21 AA<char> b1, b2, b3; 22 b1.m_a = 'a'; 23 b2.m_a++; 24 b3.m_a++; 25 cout << AA<char>::m_a << endl; 26 27 //m_a是每个类型的类,去使用,手工写两个类 int char 28 system("pause"); 29 }
案例2:以下来自:C++类模板遇上static关键字
1 #include <iostream> 2 using namespace std; 3 4 template<typename T> 5 class Obj{ 6 public: 7 static T m_t; 8 }; 9 10 template<typename T> 11 T Obj<T>::m_t = 0; 12 13 int main04(){ 14 Obj<int> i1,i2,i3; 15 i1.m_t = 10; 16 i2.m_t++; 17 i3.m_t++; 18 cout << Obj<int>::m_t<<endl; 19 20 Obj<float> f1,f2,f3; 21 f1.m_t = 10; 22 f2.m_t++; 23 f3.m_t++; 24 cout << Obj<float>::m_t<<endl; 25 26 Obj<char> c1,c2,c3; 27 c1.m_t = 'a'; 28 c2.m_t++; 29 c3.m_t++; 30 cout << Obj<char>::m_t<<endl; 31 }
当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。
2.7类模板在项目开发中的应用
小结
-
模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
-
模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
-
同一个类属参数可以用于多个模板。
-
类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
-
模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。
-
模板称为模板函数;实例化的类模板称为模板类。
-
函数模板可以用多种方式重载。
-
类模板可以在类层次中使用 。
训练题
1) 请设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。
设计:
-
类模板 构造函数 拷贝构造函数 << [] 重载=操作符
-
a2=a1
-
实现
2) 请仔细思考:
a) 如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作
b) 如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?
1 class Teacher 2 { 3 friend ostream & operator<<(ostream &out, const Teacher &obj); 4 public: 5 Teacher(char *name, int age) 6 { 7 this->age = age; 8 strcpy(this->name, name); 9 } 10 11 Teacher() 12 { 13 this->age = 0; 14 strcpy(this->name, ""); 15 } 16 17 private: 18 int age; 19 char name[32]; 20 }; 21 22 23 class Teacher 24 { 25 friend ostream & operator<<(ostream &out, const Teacher &obj); 26 public: 27 Teacher(char *name, int age) 28 { 29 this->age = age; 30 strcpy(this->name, name); 31 } 32 33 Teacher() 34 { 35 this->age = 0; 36 strcpy(this->name, ""); 37 } 38 39 private: 40 int age; 41 char *pname; 42 };
结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。
结论2:需要Teacher封装的函数有:
-
重写拷贝构造函数
-
重载等号操作符
-
重载左移操作符。
理论提高:
所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。
3) 请从数组模板中进行派生
1 //演示从模板类 派生 一般类 2 #include "MyVector.cpp" 3 4 class MyArray01 : public MyVector<double> 5 { 6 public: 7 MyArray01(int len) : MyVector<double>(len) 8 { 9 ; 10 } 11 protected: 12 private: 13 }; 14 15 16 //演示从模板类 派生 模板类 //BoundArray 17 template <typename T> 18 class MyArray02 : public MyVector<T> 19 { 20 public: 21 MyArray02(int len) : MyVector<double>(len) 22 { 23 ; 24 } 25 protected: 26 private: 27 }; 28 测试案例: 29 30 //演示 从模板类 继承 模板类 31 void main() 32 { 33 MyArray02<double> dArray2(10); 34 dArray2[1] = 3.15; 35 36 } 37 38 39 //演示 从模板类 继承 一般类 40 void main11() 41 { 42 MyArray01 d_array(10); 43 44 for (int i=0; i<d_array.getLen(); i++) 45 { 46 d_array[i] = 3.15; 47 } 48 49 for (int i=0; i<d_array.getLen(); i++) 50 { 51 cout << d_array[i] << " "; 52 } 53 54 cout<<"hello..."<<endl; 55 system("pause"); 56 return ; 57 }
作业:
封装你自己的数组类;设计被存储的元素为类对象;
思考:类对象的类,应该实现的功能。
-
优化Teacher类, 属性变成 char *panme, 构造函数里面 分配内存
-
优化Teacher类,析构函数 释放panme指向的内存空间
-
优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
-
优化Teacher类,在Teacher增加 <<
-
在模板数组类中,存int char Teacher Teacher*(指针类型)
zuoye.h
1 #pragma once 2 3 template <typename T> 4 class MyVector 5 { 6 7 //friend ostream & operator<< <T>(ostream &out, const MyVector &obj); 8 public: 9 MyVector(int size = 0);//构造函数 10 MyVector(const MyVector &obj);//copy构造函数 11 ~MyVector(); 12 public: 13 T& operator [](int index); 14 MyVector &operator=(const MyVector &obj); 15 16 17 int getLen() 18 { 19 return m_len; 20 } 21 protected: 22 private: 23 T *m_space; 24 int m_len; 25 26 };
zuoye_test12.cpp
1 #include"zuoye.h" 2 #include"zuoye12.cpp" 3 #include<iostream> 4 using namespace std; 5 class Teacher 6 { 7 public: 8 Teacher() 9 { 10 age = 33; 11 m_p = new char[1]; 12 strcpy(m_p, " "); 13 } 14 15 Teacher(char *name, int age) 16 { 17 this->age = age; 18 m_p = new char[strlen(name)+1]; 19 strcpy(this->m_p, name); 20 21 } 22 Teacher(const Teacher &obj) 23 { 24 m_p = new char[strlen(obj.m_p) + 1]; 25 strcpy(this->m_p, obj.m_p); 26 age = obj.age; 27 } 28 ~Teacher() 29 { 30 if (m_p!=NULL) 31 { 32 delete[] m_p; 33 m_p = NULL; 34 } 35 } 36 void printT() 37 { 38 cout << m_p << ", " << age; 39 } 40 public: 41 //重载<< == 42 friend ostream & operator<<(ostream &out,Teacher &t); 43 Teacher & operator=(const Teacher &obj) 44 { 45 //1 46 if (m_p!=NULL) 47 { 48 delete[] m_p; 49 m_p = NULL; 50 age = 33; 51 } 52 //2 53 m_p = new char[strlen(obj.m_p) + 1]; 54 age = obj.age; 55 //3 56 strcpy(this->m_p, obj.m_p); 57 return *this; 58 } 59 protected: 60 private: 61 int age; 62 //char name[32]; 63 char *m_p; 64 }; 65 ostream & operator<<(ostream &out, Teacher &t) 66 { 67 out << t.m_p << ", " << t.age << endl; 68 return out; 69 } 70 71 void main() 72 { 73 Teacher t1("t1", 31), t2("t2", 32); 74 75 MyVector<Teacher *> Tarray(2); 76 77 Tarray[0] = &t1; 78 Tarray[1] = &t2; 79 for (int i = 0; i < 2; i++) 80 { 81 Teacher *tmp = Tarray[i]; 82 tmp->printT(); 83 } 84 system("pause"); 85 } 86 void main123() 87 { 88 Teacher t1("t1", 31), t2("t2", 32); 89 MyVector<Teacher> Tarray(2); 90 Tarray[0] = t1; 91 Tarray[1] = t2; 92 for (int i = 0; i < 2; i++) 93 { 94 Teacher tmp = Tarray[i]; 95 tmp.printT(); 96 } 97 system("pause"); 98 } 99 void main112() 100 { 101 MyVector<int> myv1(10); 102 myv1[0] = 'a'; 103 myv1[1] = 'b'; 104 myv1[2] = 'c'; 105 myv1[3] = 'd'; 106 myv1[4] = 'e'; 107 //cout << myv1; 108 MyVector<int> myv2 = myv1; 109 } 110 111 112 void main111() 113 { 114 MyVector<int> myv1(10); 115 for (int i = 0; i < myv1.getLen(); i++) 116 { 117 myv1[i] = i + 1; 118 cout << myv1[i] << " "; 119 } 120 121 MyVector<int> myv2 = myv1; 122 for (int i = 0; i < myv2.getLen(); i++) 123 { 124 myv2[i] = i + 1; 125 cout << myv2[i] << " "; 126 } 127 128 //cout << myv2 << endl;//重载左移运算符 129 130 system("pause"); 131 }
zuoye12.cpp
1 #include"zuoye.h" 2 #include<iostream> 3 using namespace std; 4 5 template <typename T> 6 ostream & operator<<(ostream &out, const MyVector<T> &obj) 7 { 8 for (int i = 0; i<obj.m_len; i++) 9 { 10 out << obj.m_space[i] << " "; 11 } 12 out << endl; 13 return out; 14 } 15 //构造函数 16 template <typename T> 17 MyVector<T>::MyVector(int size = 0) 18 { 19 m_space = new T[size]; 20 m_len = size; 21 } 22 //MyVector<int> myv2 = myv1; 23 template <typename T> 24 MyVector<T>::MyVector(const MyVector &obj) 25 { 26 //根据大小分配内存 27 m_len = obj.m_len; 28 m_space = new T[m_len]; 29 //copy数据 30 for (int i = 0; i<m_len; i++) 31 { 32 m_space[i] = obj.m_space[i]; 33 } 34 } 35 template <typename T> 36 MyVector<T>::~MyVector() 37 { 38 if (m_space != NULL) 39 { 40 delete[] m_space; 41 m_space = NULL; 42 m_len = 0; 43 44 } 45 } 46 template <typename T> 47 T& MyVector<T>::operator [](int index) 48 { 49 return m_space[index]; 50 } 51 template <typename T> 52 MyVector<T> & MyVector<T>::operator=(const MyVector<T> &obj) 53 { 54 //先把a2的内存释放掉 55 if (m_space != NULL) 56 { 57 delete[] m_space; 58 m_space = NULL; 59 m_len = 0; 60 61 } 62 63 //根据a1分配内存 64 m_len = obj.m_len; 65 m_space = new T[m_len]; 66 67 //copy数据 68 for (i = 0; i<m_len; i += ) 69 { 70 m_space[i] = obj.m_space[i]; 71 } 72 return *this;//a2= a1 返回a2的自身 73 }
参考一些资料,加上一些见解,如果有雷同,纯属巧合。
更多C++相关知识体系,请移步C++知识目录。 -
学术会议poster模板
2015-07-02 13:59:58poster模板的合集,可以使用ppt方便地进行修改 -
软件测试用例模板(经典)免费分享
2017-03-14 10:31:58免费的软件测试模板,免费分享 -
软件需求规格说明书模板(超详细)
2018-02-02 16:59:36很详细规范的实例软件需求说明书,标准规范,自用参考 -
Winform可视化打印模板设计工具(含源码)
2014-08-25 11:10:36同一个系统中,为了解决不同的客户可能需要设计不同的单据打印模板,实现此方法可能是: 1、设计不同的自带RDLC报表文件,根据当前客户加载不同的报表并打印 2、GDI+绘图 和 打印组件 ,不同的客户创建不同的绘图... -
图像处理(8) : 模板匹配
2021-06-08 21:47:06模板匹配指的是通过模板图像与测试图像之间的比较,找到测试图像上与模板图像相似的部分,这是通过计算模板图像与测试图像中目标的相似度来实现的,可以快速地在测试图像中定位出预定义的目标。匹配的主要思路是... 模板匹配指的是通过模板图像与测试图像之间的比较,找到测试图像上与模板图像相似的部分,这是通过计算模板图像与测试图像中目标的相似度来实现的,可以快速地在测试图像中定位出预定义的目标。匹配的主要思路是使用一个目标原型,根据它创建一个模板,在测试图像中搜索与该模板图像最相似的目标,并寻找与该模板的均值或方差最接近的区域。
通过模板匹配可以得到目标的相似度,旋转角度,行列坐标,缩放大小等。
针对不同的图像特征和检测环境,有多种模板匹配算法。如何选择合适的模板匹配算法,取决于具体的图像数据和匹配任务。只有理解这些算法的原理和适用场景后,才能根据项目的需要选择合适的算法。
一、基于灰度值的模板匹配
基于灰度值的模板匹配是最经典的模板匹配算法,也是最早提出来的模板匹配算法。这种算法的根本思想是,计算模板图像与检测图像之间的像素灰度差值的绝对值总和(SAD方法)或者平方差总和(SSD方法)。
其原理是:首先选择一块ROI(感兴趣区域)作为模板图像,生成基于灰度值的模板。然后将检测图像与模板图像进行粗匹配,在检测图像与模板图像中任选一点,采取隔点搜索的方式计算二者灰度的相似性,这样粗匹配一遍得到粗相关点。接下来进行精匹配,将得到的粗相关点作为中心点,用最小二乘法寻找二者之间的最优匹配点。
由于这种方法是利用模板图像的所有灰度值进行匹配,但在光照发生变化的情况下灰度值会产生强烈的变化,因此该方法不能适应光照发生变化的情况,也不能用于多通道图像的匹配,一般只用于简单图像的匹配。
灰度匹配算子原型如下:
create_template(Template : : FirstError, NumLevel, Optimize, GrayValues : TemplateID)
以下是一个实例:
dev_clear_window () read_image (Image, 'C:/Users/Administrator/Desktop/test22.png') rgb1_to_gray (Image, GrayImage) gen_circle (ROI_0, 84, 180, 20) reduce_domain (GrayImage, ROI_0, ImageReduced) create_template (ImageReduced, 5, 4, 'sort', 'original', TemplateID) threshold (GrayImage, Region, 128, 255) connection (Region, ConnectedRegions) select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 1000, 50000) add_channels (SelectedRegions, GrayImage, GrayRegions) best_match (GrayRegions, TemplateID, 40, 'false', Row, Column, Error1) tuple_gen_const (|Row|, 20, Radius) dev_set_line_width (3) gen_circle_contour_xld (Circles, Row, Column, Radius, 0, 6.28318, 'positive', 1) dev_display(GrayImage) dev_display(Circles)
这种方法适用于目标图像光照比较稳定的情况,多数情况下还是优先考虑基于相关性的匹配和基于形状的匹配。只有针对极少数的简单图像,才会考虑基于灰度值的匹配。
二、基于相关性的模板匹配
基于相关性的模板匹配其实是另一种基于灰度值的匹配,不过它的特点是使用一种归一化的互相关匹配(Normalized Cross Correlation,NCC)来衡量模板图像和检测图像之间的关系,因此,在光照方面受的影响比较小。与经典的基于灰度值的匹配算法不同的是,它的速度要快很多。与基于形状模板的匹配算法相比,它的优势是对一些形状有细微变化的、纹理复杂的或者是聚焦模糊的检测图像都能检索得到。
其原理是:把模板图像中的所有像素按列顺序组成一个行向量a,即模板的特征向量,然后在检测图像上寻找与模板最匹配的区域b,通过计算两个向量的夹角,来衡量匹配的概率,如下面所示:
以下是一个实例:
read_image (Image, 'cap_exposure/cap_exposure_03') dev_close_window () dev_open_window_fit_image (Image, 0, 0, -1, -1, WindowHandle) set_display_font (WindowHandle, 16, 'mono', 'true', 'false') dev_update_off () gen_circle (Circle, 246, 336, 150) area_center (Circle, Area, RowRef, ColumnRef) reduce_domain (Image, Circle, ImageReduced) create_ncc_model (ImageReduced, 'auto', 0, 0, 'auto', 'use_polarity', ModelID) dev_set_draw ('margin') dev_display (Image) dev_set_color ('yellow') dev_display (Circle) disp_message (WindowHandle, 'Trained NCC model', 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () Rows := [] Cols := [] for J := 1 to 10 by 1 read_image (Image, 'cap_exposure/cap_exposure_' + J$'02') find_ncc_model (Image, ModelID, 0, 0, 0.5, 1, 0.5, 'true', 0, Row, Column, Angle, Score) Rows := [Rows,Row] Cols := [Cols,Column] dev_display (Image) dev_display_ncc_matching_results (ModelID, 'green', Row, Column, Angle, 0) disp_message (WindowHandle, 'Found NCC model', 'window', 12, 12, 'black', 'true') if (J < 10) disp_continue_message (WindowHandle, 'black', 'true') endif stop () endfor
选取一块圆形区域作为模板图像,并根据其灰度值创建模板。当检测图像亮度和形状变化很大还是能够匹配得到。
该方法不但能适应光照变化,对小范围的遮挡和缺失也同样适用,同时还适用于聚焦不清的图像和形状变形,因此在实际应用中比较广泛。但是,该方法也有其局限性,如果与参考图像相比,检测图像的位移、旋转或者缩放比较大,可能会导致匹配失败。
三、基于形状的模板匹配
基于形状的模板匹配,也称为基于边缘方向梯度的匹配,是一种最常用也最前沿的模板匹配算法。该算法以物体边缘的梯度相关性作为匹配标准,原理是提取ROI中的边缘特征,结合灰度信息创建模板,并根据模板的大小和清晰度的要求生成多层级的图像金字塔模型。接着在图像金字塔层中自上而下逐层搜索模板图像,直到搜索到最底层或得到确定的匹配结果为止。
以下是一个实例:
dev_update_pc ('off') dev_update_window ('off') dev_update_var ('off') read_image (Image, 'green-dot') get_image_size (Image, Width, Height) dev_close_window () dev_open_window (0, 0, Width, Height, 'black', WindowHandle) dev_set_color ('red') dev_display (Image) threshold (Image, Region, 0, 128) connection (Region, ConnectedRegions) select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 10000, 20000) fill_up (SelectedRegions, RegionFillUp) dilation_circle (RegionFillUp, RegionDilation, 5.5) reduce_domain (Image, RegionDilation, ImageReduced) create_scaled_shape_model (ImageReduced, 5, rad(-45), rad(90), 'auto', 0.8, 1.0, 'auto', 'none', 'ignore_global_polarity', 40, 10, ModelID) get_shape_model_contours (Model, ModelID, 1) area_center (RegionFillUp, Area, RowRef, ColumnRef) vector_angle_to_rigid (0, 0, 0, RowRef, ColumnRef, 0, HomMat2D) affine_trans_contour_xld (Model, ModelTrans, HomMat2D) dev_display (Image) dev_display (ModelTrans) read_image (ImageSearch, 'green-dots') dev_display (ImageSearch) dev_set_line_width (3) find_scaled_shape_model (ImageSearch, ModelID, rad(-45), rad(90), 0.8, 1.0, 0.5, 0, 0.5, 'least_squares', 5, 0.8, Row, Column, Angle, Scale, Score) for I := 0 to |Score| - 1 by 1 hom_mat2d_identity (HomMat2DIdentity) hom_mat2d_translate (HomMat2DIdentity, Row[I], Column[I], HomMat2DTranslate) hom_mat2d_rotate (HomMat2DTranslate, Angle[I], Row[I], Column[I], HomMat2DRotate) hom_mat2d_scale (HomMat2DRotate, Scale[I], Scale[I], Row[I], Column[I], HomMat2DScale) affine_trans_contour_xld (Model, ModelTrans, HomMat2DScale) dev_display (ModelTrans) endfor
该方法使用边缘特征定位物体,对于很多干扰因素不敏感,如光照和图像的灰度变化,甚至可以支持局部边缘缺失、杂乱场景、噪声、失焦和轻微形变的模型。更进一步说,它甚至可以支持多个模板同步进行搜索。但是,在搜索过程中,如果目标图像发生大的旋转或缩放,则会影响搜索的结果,因此不适用于旋转和缩放比较大的情况。
四、基于组件的模板匹配
基于组件的模板匹配可以说是基于形状的模板匹配的加强版,加强的地方在于,这种方法允许模板中包含多个目标,并且允许目标之间存在相对运动(位移和旋转)。这决定了这种方式不适用于尺寸缩放的情况。由于有多个ROI,且需要检测多个ROI之间的相对运动关系,因此这种方法与基于形状的模板匹配相比要稍微复杂一点,且不适用于失焦图像和轻微形变的目标。
以下是一个实例:
dev_update_off () dev_close_window () read_image (ModelImage, 'modules/modules_model') dev_open_window_fit_image (ModelImage, 0, 0, -1, -1, WindowHandle) dev_display (ModelImage) set_display_font (WindowHandle, 16, 'mono', 'true', 'false') Message := 'This program shows how to use the' Message[1] := 'component-based matching' Message[2] := 'to locate a compound object' disp_message (WindowHandle, Message, 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () * * Define the regions for the components gen_rectangle2 (ComponentRegions, 318, 109, -1.62, 34, 19) gen_rectangle2 (Rectangle2, 342, 238, -1.63, 32, 17) gen_rectangle2 (Rectangle3, 355, 505, 1.41, 25, 17) gen_rectangle2 (Rectangle4, 247, 448, 0, 14, 8) gen_rectangle2 (Rectangle5, 237, 537, -1.57, 13, 10) concat_obj (ComponentRegions, Rectangle2, ComponentRegions) concat_obj (ComponentRegions, Rectangle3, ComponentRegions) concat_obj (ComponentRegions, Rectangle4, ComponentRegions) concat_obj (ComponentRegions, Rectangle5, ComponentRegions) dev_set_colored (12) dev_set_draw ('margin') dev_set_line_width (2) dev_display (ModelImage) dev_display (ComponentRegions) set_display_font (WindowHandle, 16, 'mono', 'true', 'false') disp_message (WindowHandle, 'Regions of the components', 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () * * Create the component model by explicitly specifying the relations create_component_model (ModelImage, ComponentRegions, 20, 20, rad(25), 0, rad(360), 15, 40, 15, 10, 0.8, [4,3,3,3,3], 0, 'none', 'use_polarity', 'true', ComponentModelID, RootRanking) * * Find the component model in a run-time image ImageName := 'modules/modules_' for I := 1 to 12 by 1 read_image (SearchImage, ImageName + I$'.2d') find_component_model (SearchImage, ComponentModelID, RootRanking, 0, rad(360), 0.5, 0, 0.5, 'stop_search', 'search_from_best', 'none', 0.8, 'interpolation', 0, 0.8, ModelStart, ModelEnd, Score, RowComp, ColumnComp, AngleComp, ScoreComp, ModelComp) dev_display (SearchImage) * Display the found component models for Match := 0 to |ModelStart| - 1 by 1 dev_set_line_width (1) get_found_component_model (FoundComponents, ComponentModelID, ModelStart, ModelEnd, RowComp, ColumnComp, AngleComp, ScoreComp, ModelComp, Match, 'false', RowCompInst, ColumnCompInst, AngleCompInst, ScoreCompInst) dev_display (FoundComponents) endfor disp_message (WindowHandle, 'Found component models', 'window', 12, 12, 'black', 'true') * If the program shall stop after every image, the following lines * must be activated if (I < 12) disp_continue_message (WindowHandle, 'black', 'true') endif stop () endfor
本例在图中选取了几个元器组件作为模板图像,并根据其形状和相对关系创建了模板,图像在旋转的情况下仍得到了理想的匹配结果。
基于组件的模板匹配适用于组成部件之间有相对运动的物体,使用边缘特征定位物体,对于很多干扰因素不敏感,如光照变化、混乱无序等。其适用于多通道图像,不适用于纹理图像、聚焦不清的图像和形状变形的图像。
五、基于形变的模板匹配
形变分为两种,一种是基于目标局部的形变,另一种是由于透视关系而产生的形变。基于形变的模板匹配也是一种基于形状的匹配方法,但不同的是,其返回结果中不仅包括轻微形变的形状、形变的位置和参数,还有描述形变的参数,如旋转角度、缩放倍数等。
基于透视的形变可以返回一个二维投影变换矩阵。如果是在相机标定的情况下,通过相机参数,还可以计算出目标的三维位姿。
以下是一个实例:
dev_update_off () dev_close_window () read_image (ModelImage, 'food/peanut_chocolate_candies_model') dev_open_window_fit_image (ModelImage, 0, 0, -1, -1, WindowHandle) set_display_font (WindowHandle, 16, 'mono', 'true', 'false') * * 创建一个模板 create_local_deformable_model (ModelImage, 'auto', [], [], 'auto', 1, [], 'auto', 1, [], 'auto', 'none', 'use_polarity', 65, 25, [], [], ModelID) get_deformable_model_contours (ModelContours, ModelID, 1) area_center (ModelImage, Area, Row, Column) hom_mat2d_identity (HomMat2DIdentity) hom_mat2d_translate (HomMat2DIdentity, Row, Column, HomMat2DTranslate) affine_trans_contour_xld (ModelContours, ContoursAffineTrans, HomMat2DTranslate) dev_set_line_width (2) dev_set_color ('yellow') dev_display (ModelImage) dev_display (ContoursAffineTrans) disp_message (WindowHandle, 'Model image and contours', 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () Smoothness := 19 NumImages := 12 for Index := 1 to NumImages by 1 read_image (Image, 'food/peanut_chocolate_candies_' + Index$'02') dev_resize_window_fit_image (Image, 0, 0, -1, -1) dev_display (Image) disp_message (WindowHandle, 'Search ...', 'window', 12, 12, 'black', 'true') count_seconds (S1) * 查找 find_local_deformable_model (Image, ImageRectified, VectorField, DeformedContours, ModelID, rad(-120), rad(60), 1, 1, 1, 1, 0.3, 0, 0.7, 0, 0.1, ['vector_field','deformed_contours'], ['deformation_smoothness','expand_border','subpixel'], [Smoothness,0,0], Score, Row, Column) count_seconds (S2) Time := S2 - S1 * 显示结果 gen_warped_mesh (VectorField, WarpedMesh, Smoothness) dev_set_line_width (1) dev_set_color ('yellow') dev_display (WarpedMesh) Found[Index] := |Score| dev_set_line_width (2) dev_set_color ('green') dev_display (DeformedContours) disp_message (WindowHandle, |Score| + ' matches found in ' + Time$'1.2f' + ' s', 'window', 12, 12, 'black', 'true') disp_message (WindowHandle, 'Score: ' + Score$'.2f', 'image', 350, Column - 80, 'black', 'true') if (Index < NumImages) disp_continue_message (WindowHandle, 'black', 'true') stop () endif endfor
六、基于描述符的模板匹配
与基于透视形变的模板匹配类似,基于描述符的模板匹配能够在物体处于透视形变的状态下进行匹配,并且已标定和未标定的相机图像都适用。与透视形变不同的是,它的模板不是根据边缘轮廓创建的,而是根据特征点创建的。
点的位置或相邻像素的灰度信息等都可以作为描述符。有纹理的平面图形非常适用于这种方法,尤其是对于旋转倾斜等场景中的匹配可以得到非常理想的结果。
以下是一个实例:
dev_close_window () *读取参考图像,这里的参考图像应只包含识别的关键区域,用于创建模板 read_image (ImageLabel, 'data/labelShape-0') *设置窗口参数用于显示图像 get_image_size (ImageLabel, Width, Height) dev_open_window (0, 0, Width, Height, 'black', WindowHandle1) dev_set_draw ('margin') dev_display (ImageLabel) *设置用于存储特征点和感兴趣区域的变量 NumPoints := [] RowRoi := [10,10,Height - 10,Height - 10] ColRoi := [10,Width - 10,Width - 10,10] *将参考图像中的除边缘外的区域都设为感兴趣区域。因为参考图像已经近似于匹配的纹理样本 gen_rectangle1 (Rectangle, 10, 10, Height - 10, Width - 10) *显示参考图像上选择的ROI区域 dev_set_line_width (4) dev_display (Rectangle) stop () *将感兴趣区域剪裁为模板图像 reduce_domain (ImageLabel, Rectangle, ImageReduced) dev_clear_window () dev_display (ImageLabel) *创建基于描述符的模板 create_uncalib_descriptor_model (ImageReduced, 'harris_binomial', [], [], ['min_rot','max_rot','min_scale','max_scale'], [-90,90,0.2,1.1], 42, ModelID) *设置模型的原点,为了后面获取坐标作参照 set_descriptor_model_origin (ModelID, -Height / 2, -Width / 2) *获取模型中特征点的位置 get_descriptor_model_points (ModelID, 'model', 'all', Row_D, Col_D) *将模型中计算出的特征点存入NumPoints变量中 NumPoints := [NumPoints,|Row_D|] *读取测试图像,这里读取的是单通道灰度图像,因此省略了通道转化的步骤 read_image (ImageGray, 'data/labelShape-1') dev_resize_window_fit_image (ImageGray, 0, 0, -1, -1) dev_display (ImageGray) *对描述符特征点进行匹配 find_uncalib_descriptor_model (ImageGray, ModelID, 'threshold', 800, ['min_score_descr','guided_matching'], [0.003,'on'], 0.25, 1, 'num_points', HomMat2D, Score) *显示匹配结果,将特征点用不同的颜色绘制出来 if ((|HomMat2D| > 0) and (Score > NumPoints[0] / 4)) get_descriptor_model_points (ModelID, 'search', 0, Row, Col) *创建十字标识符 gen_cross_contour_xld (Cross, Row, Col, 6, 0.785398) projective_trans_region (Rectangle, TransRegion, HomMat2D, 'bilinear') projective_trans_pixel (HomMat2D, RowRoi, ColRoi, RowTrans, ColTrans) angle_ll (RowTrans[2], ColTrans[2], RowTrans[1], ColTrans[1], RowTrans[1], ColTrans[1], RowTrans[0], ColTrans[0], Angle) Angle := deg(Angle) if (Angle > 70 and Angle < 110) area_center (TransRegion, Area, Row, Column) dev_set_color ('green') dev_set_line_width (4) dev_display (TransRegion) dev_set_colored (6) dev_display (Cross) endif endif stop () *匹配结束,释放模板资源 clear_descriptor_model (ModelID)
图中五颜六色的点就是特征点,基于描述符的模板匹配只能用于有纹理的图像。
七、基于点的模板匹配
基于点的模板匹配主要是用在三维匹配中,通过寻找图像中对应的特征点,进行两幅重叠图像的拼接等操作,在相机标定的情况下被广泛应用。其主要原理是通过提取两幅图像之间的重要特征点实现匹配。把这些特征点作为匹配的输入,输出部分则是两幅图像之间的映射关系,支持图像的位移、旋转、缩放和透视形变。
同时,也可以把两幅图中的一幅作为模板,另一幅看作检测图像的一个实例。该方法在透视形变的情况下无须标定就能完成匹配,但是运行时间会增加,增加的时间主要来自特征点的提取。
八、模板匹配方法总结
选择适合的模板匹配方法可以事半功倍。
(1)基于灰度值的模板匹配
适用于目标区域灰度值比较稳定,检测图像与模板图像相似度高,且具有相同的外界条件的场景。不适用于杂乱场景、遮挡、光照变化、尺寸缩放及多通道图像。
(2)基于相关性的模板匹配
适用于失焦图像、轻微形变、线性光照变化及轮廓模糊的图像,对纹理图像尤为支持。不适用于杂乱场景、遮挡、非线性光线变化、大幅的旋转、尺寸缩放和多通道图像。
(3)基于形状的模板匹配
适用于目标轮廓比较清晰的场景。适用于杂乱场景、遮挡、非线性光照变化、尺寸缩放、失焦和轻微形变的图像,以及多通道图像和多个模板的同步匹配,不适用于纹理复杂的图像。
(4)基于组件的模板匹配
适用于多个目标的匹配场景。适用于杂乱场景、遮挡、非线性光照变化的图像,以及多通道图像和多个模板的同步匹配。不适用于纹理复杂的图像,以及失焦和形变的图像。
(5)基于局部形变的模板匹配
适用于杂乱场景、遮挡、非线性光照变化、尺寸缩放和轻微局部形变的图像,以及多通道图像.
(6)基于透视形变的模板匹配
适用于杂乱场景、遮挡、非线性光照变化、尺寸缩放、失焦和透视形变的图像,以及多通道图像。但是对于纹理图像的支持不够好。
(7)基于描述符的模板匹配
适用于杂乱场景、遮挡、非线性光照变化、尺寸缩放、轻微局部形变、透视形变的图像,以及有纹理的图像,不适用于失焦和多通道图像。
(8)基于点的模板匹配
用于在多幅部分重合的图像之间建立对应的关系。
-
基于模板匹配的字符识别
2014-06-24 15:28:08本文主要要实现字符识别,识别方法是用模板匹配。内容包含模板,待识别字符,完整程序。希望大家能够帮助大家。 -
65个车牌号字符模板20*40
2014-12-27 20:59:0365个车牌号字符模板20*40,不含字母I和O,以为可能跟数字1和0混淆。全部为二值化图片,背景色为黑,前景色为白。可用于车牌号识别技术中的模板匹配。 -
博客《word常规模板设置》配套模板
2016-08-17 20:18:42博客《word常规模板设置》中使用到的配套模板。 博客地址:http://blog.csdn.net/deirjie/article/details/52225048