
书上写得没错。我去查了一下资料,这个特性叫“模板特化”(Template Specialization)。我也是学生,学艺不精,也没搞过什么大型的项目,这个特性我从未用过。C++特性真的太丰富了。
简单地说,当你有了一个模板后,您可以针对某种具体的情况进行特殊的处理。譬如说您有一个模板函数:
template <typename T1, typename T2>
void fun(T1 a, T2 b)
{
cout << "模板函数: " << a << " " << b << endl;
}
但如果您想对T1为int,T2为char的进行特殊的处理,您可以在原来的基础上,像这样具体化地声明函数:
template <>
void fun<int, char>(int a, char b)
{
cout << "特化的模板函数:" << a << " " << b << endl;
}
这样,编译器会针对传入参数为int、char的fun函数调用我们具体化的函数,而不使用普适的模板函数。
值得注意的是,必须要先有一个函数模板,您才能对它进行特化。您之前的代码缺少模板函数,而您直接对一个不存在的模板函数进行“特化”,肯定是不能编译通过的。
这里我给出一个简单的实例,您可以复制到您的开发环境上运行。建议您在第20行设置断点,然后单步调试,观察函数的调用关系和输出,然后注释掉第4到第8行(含第8行),重新编译。我想这将有助于您更好地理解这个知识点。
#include <iostream>
using namespace std;
template <typename T1, typename T2>
void fun(T1 a, T2 b)
{
cout << "模板函数: " << a << " " << b << endl;
}
template <>
void fun<int, char>(int a, char b)
{
cout << "特化的模板函数:" << a << " " << b << endl;
}
int main(void)
{
int x = 1, y = 0;
char a = 'x', b = 'y';
fun(x, y);
fun(x, b);
fun(b, x);
fun(x, 'b');
fun(a, b);
fun<int, int>(a, b);
fun<int, int>(x, b);
return 0;
}
鄙人才疏学浅,无法给您严谨的讲解模板函数的具体特性。您可以参阅:您的《C++ Primer Plus》、、C++模板特化与偏特化等资料。建议您仿照着资料和上面的例子,自己再在原有的代码上修改,这样才能获得提高。
你说的不是模板的具体化,而是模板的特化或者偏特化,在这道题里就是模板的特化。
比如
#include<iostream>
#include<string>
using namespace std;
const int LIM=5;
template<typename T>
T maxn(T a[],int N)
{
T Max;
Max=a[0];
for(int i=1;i<N;i++)
{
if(Max<a[i])
Max=a[i];
}
return Max;
}
template<>
string maxn<string>(string ss[], int N)
{
string s = "hello,world!";
return s;
}
int main()
{
int a[6]={1,3,5,9,2,4},a1;
double b[4]={621,236,316,665},b1;
a1=maxn(a,6);
b1=maxn(b,4);
string ss[] = {"wolrd,hello!"};
string str = maxn(ss, 10);
cout<<a1<<endl;
cout<<b1<<endl;
cout << str << endl;
system("pause");
return 0;
}
首先测试#ifndef #define #endif 在头文件编译中到底能够起到什么作用能?1能够控制整个工程对于该头文件的包含,也就是说对于添加了#ifndef #define #endif
的头文件一个工程只会包含一次该头文件。2只能控制一个c文件只包含这个头文件一次。下面我们定义一个testGlobalh文件和两个包含该h文件的cpp文件,testGlobalcpp和
testGlobal2cpp内容如下:
testGlobalh
#ifndef TESTGLOBAL_H#define TESTGLOBAL_Hint GlobalCount;#endif
testGlobalcpp
#include "testGlobalh"
testGlobal2cpp
#include "testGlobalh"
#include <iostream>
void main()
{
std::cout<<GlobalCount<<std::endl;
}
此时编译器会提示,redefined这说明两个C文件中都直接包含了头文件的内容。
接下来我们分析#ifndef #define #endif在防止一个C文件将同一个头文件包含两次的作用。这里重新定义一个文件testGlobal2h
#ifndef TESTGLOBAL2_H
#define TESTGLOBAL2_H
#include "testGlobalh"
int GlobalCount2 = 5;
#endif
将testGlobalh和testGlobal2h都包含到testGlobalcpp中,并清除testGlobal2cpp,此时可以顺利编译。可见#ifndef的声明只对一个cpp文件而言。
基于上述问题,一般在使用全局变量时,都将全局变量定义到某一个cpp文件中,在h文件使用extern从而避免出现重定义的问题。
对于h文件中的宏定义是有编译器直接替换为常值的,不会编译到obj文件中,所以不会出现重复定义。同时也可以在h文件中定义结构体,枚举因为这些都是由
有编译器处理,直接在编译时候替换的,所以也不会报错。
实际上有关全局变量重定义的问题在《深入理解计算机系统》一书中有叙述:链接器在解析全局符号时,会遵循以下原则:
1不允许有多个强符号
2强弱符号都有选强
3多个弱符号,任选其中一个
强符号是指:函数和已初始化的全局变量;成员函数如果定义在类外面,那么是强符号;模板函数的特化版本是强的;
弱符号是指:未初始化的全局变量;成员函数如果定义在类里面,那么是弱符号;模板类中的成员函数无论定义在类内还是类外,都是弱类型;模板函数是弱类型;
实际上强弱符号判断是可能出现问题的。因为编译器实际 *** 作的全局变量可能并不是你希望的,可能造成错误。
如果想在全局变量重复定义就发出警告的话,对于GCC可以添加GCC-warn-common选项。通过我的测试在CCS的c编译器上面,在同一个c文件中重复定义变量
不会报错编译器是按照强弱符号进行判断的,但是要是在不同文件中定义了相同的变量就会报错。而使用g++编译的C++程序,后缀cpp上述两种情况都会报错,通不
过编译。
通过以上分析,在定义全局变量不应该定义在h文件中。
因为通用的版本不符合要求,我列出几个暂时能想到的原因:
比如为了优化,例子就是std::vector<T>和std::vector<bool>(不清楚可以看源码,后面的是一个特化版本)。
还有可能是对不同的类型(或值)有不同的实现。
比如:
test<bool>可能在test<true>下有某个typedef,在test<false>中则没有。这不是故意构造出来给你看的,你可以看一下c++11或者boost库中,什么is_same, enable_if等等都有用到这种(或类似)的技巧。
参考读物:
《Thinking in C++: vol1 & vol2》 Bruce Eckel - wwwbruceeckelcom
《C++ Template》 David Vandevoorde, Nicolai M Josuttis
推荐读物:
《modern C++ design》Andrei Alexandrescu
1 介绍
模板是一个包含有未指定类型的函数或类,因此模板并不是一个真正的函数或类,而是代表了一组函数或类,当为模板函数或类指定了一种类型时,就生成了此模板的一个实例,这个 *** 作叫做模板实例化(instantiation)。也可以为某一种类型提供不同于模板的定义,这个称为特化定义(specialization)。对于有多个类型参数的模板,还可以只指定一部分类型,这个称为偏特化定义(partial specialization)。
模板不是实体,因此模板的声明和定义通常都放在头文件中。
2 函数模板
21 声明
template<class T>
inline void func(T param)
{
}
22 函数模板不支持默认类型参数,但支持函数的默认参数
template<class T/ = type,不可以 />
void func(T param, int size = sizeof(T))
{
}
23 函数模板显式实例化声明
template void func<int>(int param);
显式实例化后的函数模板不能再有不能转换的类型的调用
显式实例化后的函数模板不能被特化定义
24 特化定义函数模板
template<>
void func<int>(int param) //函数模板只支持全特化
{
}
特化后是一个实体函数,不再是模板,放在头文件中会导致重定义
25 函数模板重载
template<class T>
void func(T param, int i)
{
}
template<>
void func(ClassName param, int i)
{
}
void func(int i)
{
}
这几种func函数的定义,匹配的顺序是非模板函数 -> 特化函数模板 -> 基函数模板。如果不像func<int>(100)这样指定函数模板的参数类型的话,编译器会为函数模板推测出一种类型,如根据100为int类型,编译器会自动调用func<int>,称为隐式特化,而前者则成为显式特化。
为什么函数模板要特化呢?这是因为有时候函数模板并不能处理所有情况,对于个别情况就可以使用特化来替换掉原来模板。
3 类模板
31 声明
template<class T>
class ClassName
{
};
32 类方法类外定义
template<class T>
ReturnType ClassName<T>::Func()
{
};
33 非类型(nontype)模板参数
如template<size_t _Nb> class bitset {}就是使用size_t类型参数设置位的个数。
可以是常量整数类型(包括枚举)或外部链接(external linkage)对象的指针或引用作为模板参数,不能是浮点型和类对象。
char s = "hello"; //s不可以,直接使用"hello"亦不可以,因为两个"hello"可能为不同地址
char s[] = "hello"; //s可以
ClassName obj; //obj可以
ClassName<int> obj; //obj可以
什么是一个外部链接对象呢?
关于这个问题,专门转载了SpitFire同志的一篇文章《内部链接和外部链接》到博客中
总的说来,要作为参数必须满足:1、在编译时和链接时可以求值; 2、这个参数如果是指针,则它所指的变量,如果在两个cpp中定义会出链接错误。
不同的参数值构成不同的类型,bitset<100>和bitset<200>是两个类型
34 默认参数
template<class T = int, int param = 100>
class ClassName
{
};
35 以模板为类型参数的类模板声明
template<class T>
class Array;
template<class T1, template<class / 可以省略类型名 / > class T2>
class ClassName
{
T2<T1> m_o;
}
如果T2是带默认参数的:template<class U, int i = 100> class T2; 则在以它为类型参数的类中必须再次指明默认值,如果两个默认值不同以再次声明的为准。
template<class T, int i = 100>
class Array;
template<class T1, template<class, int = 100> class T2>
class ClassName
{
T2<T1> m_u;
}
使用:
ClassName<int, Array> obj;
36 特化模板类
template<class A, class B>
class ClassName
{
};
361 全特化
template<>
class ClassName<int, double>
{
}; //特化后是一个实体类,不再是模板
362 偏特化
template<class B>
class ClassName<int, B>
{
}; //特化后是一个实体类,仍是模板
template<class A, class B>
class ClassName<A, B>
{
};
363 类成员函数特化
template<class T>
class ClassName
{
public: void f();
};
template<>
inline void class ClassName<int> :: f()
{
} //特化类ClassName<int>的f(),T为int类型时优先调用此定义
37 模板类里的静态变量
template<class T>
class ClassName
{
static T a;
}
定义:
int ClassName<int>::a = 100; / 定义一个与类模板的类型参数对应的静态变量 /
main()
{
ClassName<int> o;
}
38 typename关键词
381 表明紧跟在后面的是类型,而不是其他(如静态变量)
typename vector<T>::iterator it;
382 定义新类型
typedef typename vetor<T>::iterator Iterator_Type;
383 代替模板中的class关键词
template<typename T>
class ClassName
{
};
39 模板类中的成员函数模板
template<class T>
class ClassName
{
public:
template<class T1, class T2> T m_func(T1 a, T2 b);
}
类外定义:
template<class T>
template<class T1, class T2>
T ClassName<T>::m_func(T1 a, T2 b)
{
}
特化
template<>
template<>
int ClassName<int>::m_func(int a, int b) //类必须被一起全特化
{
}
调用:
ClassName<int> o;
otemplate m_func<int, vector<int> >(100, vector<int>()); //调用时实例化
310 继承模板类
template<class T>
class Chlid : public Parent<T>
{
};
3101 特化继承
class ClassName : public vector<int>
{
};
311 explicit关键字
explicit Y(const X& x);
X x; Y y(x); //显式转换
X x; Y y = x; //隐式转换,编译不通过
第一条语句通过使用显式从X类型转换生成了一个Y类型的对象,后一条语句则使用了隐式转换创建。由于使用了explicit要求必须使用显式,所以编译没有通过。
4 总结
模板加强了使用C++编写可复用代码的能力,但是想学好模板需要花费很大的功夫和精力的,而最难的就是将模板和原有C++面向对象的技术,如继承和多态等,相互融会贯通。
template < typename T >
T max( T a, T b )
{
return a < b b : a;
}
这个 max 函数就是一个模板函数,它可以传入一个 “类型”的参数,以便实现任意类型求最大值的效果。假设我们这样使用它:
int x=5, y=10;
int z=max <int>( x, y );
这时候发生了什么呢?我们传入的“类型参数”是int,因此编译器在编译这段代码时会使用 int 来构造一个新函数:
int max( int a, int b )
{
return a < b b : a;
}
后面的事就和编译普通的函数一样了,C++编译器继续使用强类型系统编译这个函数,由强类型系统来检查这个函数是否正确。
这个过程叫做模板的“特化”,它发生在编译期,当编译器发现模板函数、模板类被使用(注意,不是定义)的时候进行的。这个系统实际上比较像宏,但是比宏更为智能。
很明显,编译器必须知道模板如何特化这个函数,因此模板函数的实现,必须在“使用点”之前,因此模板库只能通过头文件库的形式来提供。
模板编译的特点就是你没有用到的代码永远不会编译
但模板特化例外特化会将你所指定的类型的模板按照你指定的方式编译
由于你没有调用Stack<T>的pop或top函数所以你Stack<T>的关于out_of_rangs的拼写错误根本没有编译也就没有编译错误
但Stack<std::string>的特化确是需要编译的所以有编译错误
修改为out_of_range就好了
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)