C++ 模板基础知识
2015-02-03 17:31
211 查看
1. 模板定义
1.1 函数模板
函数模板是一个独立于类型的函数,可作为一种方式产生函数的特定版本。
模板定义以关键字template开始,后接模板形参表(不可为空)。
模板的形参可以分为:类型形参和非类型形参,编译器会为我们推测出用什么类型绑定类型形参以及用什么值绑定非类型形参
函数模板可以声明为inline,说明符放在形参表之后,返回类型之前,不可放在template之前
1.2 类模板
类模板同样以关键字template开头,后跟模板形参表。
使用类模板时,需要显示指定模板形参对应的实参,这点与函数模板不同。
1.3 模板形参
模板形参的名字没有意义,可以给模板形参赋予的唯一区别是区别形参是类型形参(表示未知类型)还是非类型形参(表示未知值)
模板形参作用域:模板形参之后到模板声明或定义的末尾处。
模板形参名字限制:不能再模板内部重用,这同时意味着模板形参的名字在统一模板中只能出现一次。
模板声明:模板可以只声明不定义,声明必须指出函数或类是一个模板,每个形参之前必须带上关键字,不可省略。
1.4 模板类型形参
typename和class关键字没有区别。
由于除定义数据成员或函数成员外,类还可以定义类型成员,因此模板函数中也可以使用这样的类型,但必须告诉编译器其为一个类型,因为编译器默认认定为数据成员。
通过在名字前加上typename作为前缀,可以告诉编译器将成员当做类型。这一声明给实例化func的类型增加了一个职责:哪些类型必须具有名为size_type的类型成员。
1.5 非类型模板实参
在调用函数时,非类型实参将用值代替。
对于模板的非类型实参而言,求值结果相同的表达式将认为是等价的。
上面两次调用只需实例化一次。
编译模板时,编译器一般会在三个阶段标志错误:
第一阶段:编译模板定义本身;
第二阶段:编译器见到模板的使用时;
第三个阶段:实例化的时候。
2. 实例化
2.1 类的实例化
类模板的每次实例化都会产生一个独立的类类型。
要使用类模板,就必须显示指定模板实参。
使用函数模板时,编译器通常会为我们推断模板实参。
2.2 模板实参推断
从函数实参推断模板实参的类型和值得过程叫做模板实参推断。
多个类型形参的实参必须完全匹配。
第一个参数推断为short,但第二个参数推断为int,因此调用有误。
若想要允许实参的常规转换,则函数必须用两个类型形参来定义。
一般来说,不会转换实参以匹配已有的实例化,而会产生新实例。编译器只会执行两种类型转换:const转换 & 数组或函数到指针的转换。
用普通类型定义的形参可以使用常规转换。
可以使用函数模板对函数指针进行初始化和赋值。
2.3 函数模板的显示形参
在返回类型中使用类型形参的一种方式是引入第三个模板形参。
这种办法有一个缺点,调用者必须在每次调用时为该形参提供实参。在以逗号分割、尖括号扩住的列表中指定显示模板实参,显示模板实参的列表出现在函数名之后、实参表之前。
显示模板实参从左到右与对应模板参数相匹配,若书写形式如下:
就必须显式为所有三个形参指定实参。
显示模板实参的另一个应用是在函数模板的指针消除二义性:
3. 模板编译模型
一般来说,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中,而模板不同:要进行实例化,编译器必须能够访问定义模板的源代码。
3.1 包含编译模型
编译器必须看到用到的所有模板的定义。一般而言,通过在头文件中include进源文件,可以解决该问题。
3.2 分别编译模型
这种模型,编译器会自动跟踪定义。我们必须让编译器知道模板的定义,可以通过export关键字来做这件事。
export关键字指明给定的定义可能会需要在其他文件中产生实例化。
一个模板只能定义并导出一次,一般对函数模板在定义时指明export。
对于类模板,头文件中的类定义体不应出现export关键字,如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。
导出类的成员自动声明为导出的,也可以定义个别成员为导出的,这种情况下,export关键字应在被导出的特定成员定义出,导出成员函数的定义不必在使用成员时可见。而非导出成员的定义应放在定义类模板的头文件中。
4. 类模板成员
通常,使用类模板名字的时候需要指定模板形参除非在类模板的作用域内,可以用它的非限定名字引用该类。
4.1 类模板成员函数
必须以关键字template开头,后接类的模板形参表;
必须指出它是哪个类的成员;
类名必须包含其模板形参;
以上三个规则可以看出,在类外定义的Queue类的成员函数的开头为:
类模板成员函数本身也是函数模板,其只有为程序所用时才执行实例化,其模板形参由调用该函数的对象的类型确定。
4.2 非类型形参的模板实参
像任意类模板一样,使用screen类型时必须显式声明形参值。
非类型模板的实参必须是编译时常量表达式。
4.3 类模板中的友元声明
普通友元:非模板类或非模板函数可以是类模板的友元。
一般模板的友元关系:友元可以是类模板或函数模板。实质上,这是建立了一种一对多的友元映射,即Bar任意的实例,其友元的所有实例都是它友元。
特定的模板友元关系:类也可以授予对特定实例的访问权。
还有一种比较常见的形式:
编译器将友元声明当做类或函数的声明对待,但是如果没有告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
4.5 成员模板
任意类可以拥有自身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。
在类内部定义成员模板:成员声明的开头是自己的模板形参表:
成员模板也可以定义在类或类模板的外部。
定义在类外的成员模板需要两个模板形参表。
成员模板也仅仅在使用时被实例化。
4.6 类模板的static成员
类模板可以像其他类一样声明static成员。
模板所有给定实例化的所有对象共享同一个static成员,如Foo<int>的任意对象共享同一个static成员,Foo<string>的任意对象共享同一个static成员。
类模板的static成员在使用时实例化,且使用时必须引用实际的实例化。
4.7 模板特化
模板特化:该定义中一个或多个模板实参的实际类型和实际值是指定的。特化形式如下,
关键字template后面接一对空的尖括号<>
再接模板名和一对尖括号,括号中指定这个特化定义的模板形参
函数形参表
函数体。
特化的声明必须与对应的模板相匹配,常规定义方式如下:
如果可以从函数形参表推断出模板实参,则不必显式指定模板实参,因此定义可以简化为:
如果在模板的特化过程中缺少了template<>,则其将重载函数的非模板版本。当定义非模板参数时,对实参应用常规转换,当特化模板的时候,对实参类型不应用转换。
对于具有同一模板实参集的同一模板,程序不能既有显式的特化又有实例化,类模板成员的定义不会用于创建显式特化成员的定义。
类模板的特化与函数模板特化相似:
类特化外部定义成员时,成员之前不能加template<>标记,正确形式如下:
我们可以特化类模板的某个成员而不是整个类,成员特化的声明与任何其它函数模板特化一样,必须以空的模板形参表开头。
类模板的部分特化:类模板的部分特化本身也是模板,其形式如下:
其所有的特性均和模板一致。
4.8 重载与函数模板
函数模板可以重载:可以定义有相同名字但形参数目活类型不同的多个函数模板,也可以定义与函数模板名字相同的普通非模板函数。
当函数模板的实例化与普通函数产生二义性时,去掉实例化的函数模板,重新对调用函数进行排序,查找最合适的函数。
1.1 函数模板
函数模板是一个独立于类型的函数,可作为一种方式产生函数的特定版本。
模板定义以关键字template开始,后接模板形参表(不可为空)。
模板的形参可以分为:类型形参和非类型形参,编译器会为我们推测出用什么类型绑定类型形参以及用什么值绑定非类型形参
函数模板可以声明为inline,说明符放在形参表之后,返回类型之前,不可放在template之前
template <typename T> inline Tmin(const T&, const T&);
1.2 类模板
类模板同样以关键字template开头,后跟模板形参表。
使用类模板时,需要显示指定模板形参对应的实参,这点与函数模板不同。
template<class T> class Queue{ <span style="white-space:pre"> </span>T data; }<pre name="code" class="cpp">Queue<int> qi;
1.3 模板形参
模板形参的名字没有意义,可以给模板形参赋予的唯一区别是区别形参是类型形参(表示未知类型)还是非类型形参(表示未知值)
模板形参作用域:模板形参之后到模板声明或定义的末尾处。
模板形参名字限制:不能再模板内部重用,这同时意味着模板形参的名字在统一模板中只能出现一次。
模板声明:模板可以只声明不定义,声明必须指出函数或类是一个模板,每个形参之前必须带上关键字,不可省略。
1.4 模板类型形参
typename和class关键字没有区别。
由于除定义数据成员或函数成员外,类还可以定义类型成员,因此模板函数中也可以使用这样的类型,但必须告诉编译器其为一个类型,因为编译器默认认定为数据成员。
template <class Parm,class U> Parm func(Parm *array, U value){ <span style="white-space:pre"> </span>typenameParm::size_type *p; }
通过在名字前加上typename作为前缀,可以告诉编译器将成员当做类型。这一声明给实例化func的类型增加了一个职责:哪些类型必须具有名为size_type的类型成员。
1.5 非类型模板实参
在调用函数时,非类型实参将用值代替。
template <class T, size_t N> void array_init(T(¶m) ){ for(size_t i = 0; i != N; ++i){ <span style="white-space:pre"> </span>parm[i]= 0; <span style="white-space:pre"> </span>} } int x[42]; double y[10] array_init(x); array_init(y);
对于模板的非类型实参而言,求值结果相同的表达式将认为是等价的。
int x[42]; const int sz =40; int y[sz+2]; array_init(x); array_init(y);
上面两次调用只需实例化一次。
编译模板时,编译器一般会在三个阶段标志错误:
第一阶段:编译模板定义本身;
第二阶段:编译器见到模板的使用时;
第三个阶段:实例化的时候。
2. 实例化
2.1 类的实例化
类模板的每次实例化都会产生一个独立的类类型。
要使用类模板,就必须显示指定模板实参。
使用函数模板时,编译器通常会为我们推断模板实参。
2.2 模板实参推断
从函数实参推断模板实参的类型和值得过程叫做模板实参推断。
多个类型形参的实参必须完全匹配。
template <typename T> int compare(const T& v1, constT& v2){ } int main(){ <span style="white-space:pre"> </span>short si; <span style="white-space:pre"> </span>compare(si,1024); <span style="white-space:pre"> </span>return 0; }
第一个参数推断为short,但第二个参数推断为int,因此调用有误。
若想要允许实参的常规转换,则函数必须用两个类型形参来定义。
一般来说,不会转换实参以匹配已有的实例化,而会产生新实例。编译器只会执行两种类型转换:const转换 & 数组或函数到指针的转换。
template <typename T> Tfobj(T,T); template <typename T> Tfref(const T&, const T&); string s1(“zhou”); const string s2(“another value”); fobj(s1,s2);//实参被复制,是否const无所谓 fref(s1.s3);//对引用形参而言,实参转换为const可接受 int a[10],b[42]; fobj(a,b);//两个数组都转换为指针,fobj的实参类型为int* fref(a,b);//形参为引用类型,数组不能转换为指针,出错。
用普通类型定义的形参可以使用常规转换。
可以使用函数模板对函数指针进行初始化和赋值。
template <typename T> int compare(constT&,const T&); int (*pf1) (const int&,const int&) = compare;
2.3 函数模板的显示形参
在返回类型中使用类型形参的一种方式是引入第三个模板形参。
template <class T1, class T2,class T3> T1 sum(T2,T3);
这种办法有一个缺点,调用者必须在每次调用时为该形参提供实参。在以逗号分割、尖括号扩住的列表中指定显示模板实参,显示模板实参的列表出现在函数名之后、实参表之前。
long val = sum<long> (I,lng);
显示模板实参从左到右与对应模板参数相匹配,若书写形式如下:
template <class T1, class T2, class T4> T3 alternative_sum(T2,T1)
就必须显式为所有三个形参指定实参。
显示模板实参的另一个应用是在函数模板的指针消除二义性:
template <typename T> intcompare(const T&, const T&); void func(int(*)(const int&,constint&); void func(int(*)(conststring&,const string&); func(compare<int>);
3. 模板编译模型
一般来说,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中,而模板不同:要进行实例化,编译器必须能够访问定义模板的源代码。
3.1 包含编译模型
编译器必须看到用到的所有模板的定义。一般而言,通过在头文件中include进源文件,可以解决该问题。
3.2 分别编译模型
这种模型,编译器会自动跟踪定义。我们必须让编译器知道模板的定义,可以通过export关键字来做这件事。
export关键字指明给定的定义可能会需要在其他文件中产生实例化。
一个模板只能定义并导出一次,一般对函数模板在定义时指明export。
export template <typename Type> Type sum(Type t1, Type t2);
对于类模板,头文件中的类定义体不应出现export关键字,如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。
//header template <class Type> classQueue{…}; //implement export template <class Type>class Queue;
导出类的成员自动声明为导出的,也可以定义个别成员为导出的,这种情况下,export关键字应在被导出的特定成员定义出,导出成员函数的定义不必在使用成员时可见。而非导出成员的定义应放在定义类模板的头文件中。
4. 类模板成员
通常,使用类模板名字的时候需要指定模板形参除非在类模板的作用域内,可以用它的非限定名字引用该类。
4.1 类模板成员函数
必须以关键字template开头,后接类的模板形参表;
必须指出它是哪个类的成员;
类名必须包含其模板形参;
以上三个规则可以看出,在类外定义的Queue类的成员函数的开头为:
template <class T> ret-type Queue<T>::member-name;
类模板成员函数本身也是函数模板,其只有为程序所用时才执行实例化,其模板形参由调用该函数的对象的类型确定。
4.2 非类型形参的模板实参
像任意类模板一样,使用screen类型时必须显式声明形参值。
template <int hi, int wid> class Screen{ };<pre name="code" class="cpp">Screen<24,80> sample;
非类型模板的实参必须是编译时常量表达式。
4.3 类模板中的友元声明
普通友元:非模板类或非模板函数可以是类模板的友元。
template <class Type> classBar{ <span style="white-space:pre"> </span>friendclass FooBar; <span style="white-space:pre"> </span>friendvoid fcn(); };
一般模板的友元关系:友元可以是类模板或函数模板。实质上,这是建立了一种一对多的友元映射,即Bar任意的实例,其友元的所有实例都是它友元。
template <class Type> classBar{ <span style="white-space:pre"> </span>template<class T> friend class Foo1; <span style="white-space:pre"> </span>template<class T>friend void temp_fcn(const T&); };
特定的模板友元关系:类也可以授予对特定实例的访问权。
template <class T> class Foo2; template <class T>voidtemp_fcn2(const T&); template <class Type> classBar{ <span style="white-space:pre"> </span>friendclass Foo2<char*>; <span style="white-space:pre"> </span>friendvoid temp_fcn2<char*>(char* const &); };
还有一种比较常见的形式:
template <class Type> classBar{ <span style="white-space:pre"> </span>friendclass Foo2<Type>; <span style="white-space:pre"> </span>friendvoid temp_fcn2<Type>(const Type&); };
编译器将友元声明当做类或函数的声明对待,但是如果没有告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
4.5 成员模板
任意类可以拥有自身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。
在类内部定义成员模板:成员声明的开头是自己的模板形参表:
template <class Type> classQueue{ <span style="white-space:pre"> </span>template<class T> <span style="white-space:pre"> </span>Queue(Tbeg, T end){} };
成员模板也可以定义在类或类模板的外部。
template <class T> template<class Iter> void Queue<T>::assign(Iter beg,Iter end){ destroy(); };
定义在类外的成员模板需要两个模板形参表。
成员模板也仅仅在使用时被实例化。
4.6 类模板的static成员
类模板可以像其他类一样声明static成员。
模板所有给定实例化的所有对象共享同一个static成员,如Foo<int>的任意对象共享同一个static成员,Foo<string>的任意对象共享同一个static成员。
类模板的static成员在使用时实例化,且使用时必须引用实际的实例化。
Foo<int>::count();同时,与其他static成员一样,必须在类外部出现数据成员的定义,必须指出其是类模板的成员:
template <class T> size_tFoo<T>::ctr = 0;
4.7 模板特化
模板特化:该定义中一个或多个模板实参的实际类型和实际值是指定的。特化形式如下,
关键字template后面接一对空的尖括号<>
再接模板名和一对尖括号,括号中指定这个特化定义的模板形参
函数形参表
函数体。
特化的声明必须与对应的模板相匹配,常规定义方式如下:
template <> int compare<const char*>(constchar * const &,const char* const &);
如果可以从函数形参表推断出模板实参,则不必显式指定模板实参,因此定义可以简化为:
template <> int compare (const char * const&,const char* const &);
如果在模板的特化过程中缺少了template<>,则其将重载函数的非模板版本。当定义非模板参数时,对实参应用常规转换,当特化模板的时候,对实参类型不应用转换。
对于具有同一模板实参集的同一模板,程序不能既有显式的特化又有实例化,类模板成员的定义不会用于创建显式特化成员的定义。
类模板的特化与函数模板特化相似:
template <> class Queue<constchar*>{ <span style="white-space:pre"> </span>voidpush(const char*); }
类特化外部定义成员时,成员之前不能加template<>标记,正确形式如下:
void Queue<constchar*>::push(const char* val){};
我们可以特化类模板的某个成员而不是整个类,成员特化的声明与任何其它函数模板特化一样,必须以空的模板形参表开头。
template<> void Queue<constchar*>::push(const char* const&);
类模板的部分特化:类模板的部分特化本身也是模板,其形式如下:
template <class T1, class T2> class some_template{ } template<class T1> class some_template<T1,int>{ }
其所有的特性均和模板一致。
4.8 重载与函数模板
函数模板可以重载:可以定义有相同名字但形参数目活类型不同的多个函数模板,也可以定义与函数模板名字相同的普通非模板函数。
当函数模板的实例化与普通函数产生二义性时,去掉实例化的函数模板,重新对调用函数进行排序,查找最合适的函数。
相关文章推荐
- C++_类模板基础知识
- [c++][template]模板基础知识
- c++之基础知识(1)定义类的对象;析构函数和构造函数常用模板
- C++_函数模板基础知识
- C++ 函数模板基础知识
- C++_类模板基础知识 (下)
- C++_函数模板基础知识
- C++基础——关于模板的技巧性基础知识(typename、成员模板、模板的模板参数)
- C++学习(4)--基础知识(4)--关于const
- C++基础小知识[2]---堆与栈的区别[转]
- C++的基础知识
- pascal→→c++常用基础小知识
- 基础知识C++
- 关于HOOK基础知识(windows API ,C++)
- C/C++ 基础知识学习笔记 (不断更新中)
- c++基础知识
- C++ 基础小知识学习[1]
- c++基础知识---名字空间
- 自己总结C/C++的一些容易被遗忘的基础知识!
- 汇编基础知识 - [C/C++]