1.typename的双重性
在模板中typename和class是相同的,但是在有一种情况下是不同的
我们来看这两串代码
这两个类型的区别无非在于
一个是明确的类型vector<int>
一个是不明确的模板类型T
编译器在检查
vector<int>::const_iterator it = v.begin();
T::const_iterator it = v.begin();
编译的时候不知道T::const_iterator到底是类型还是变量(对象)
而且也不知道去哪找(这个地方的T,编译的时候没有实例化)
就有两种情况
1.静态成员变量(对象)
2.内部类或者typedef嵌套的类型
像这个地方 你很难确定这个x是一个指针还是说const_iterator这个变量乘x
包括下面的这个T::TT既可以是内部类也可也是静态变量!
class T
{
public:
class TT
{
};
private:
static int a;
};
int main(void) {
T::TT a;//T::TT是内部类
return 0;
}
class T
{
public:
static int TT;
};
int main(void)
{
T::TT;//T::TT静态变量
return 0;
}
我怎么知道 T::const_iterator 是类型还是变量
如果是类型 那没问题
但是如果是变量呢? 那这个代码就有问题了呀!
但是vector<int>::const_iterator it明确告诉我们要去vector<int>这个域里面去找it
并且可以判断vector<int> iterator是一个明确的类型 而不是变量
但是对于T::const_iterator ,这个时候我们在前面+一个typename就可以了
typename的意思就是告诉编译器这里是类型,等模板实例化后再去找
(这个地方就不可以用class了)
但是思考一下这个地方我直接auto:it可不可以?
当然可以 auto的作用是推断出类型 也就意味着默认这个it就是类型而不是变量了!
但是在有些地方我们也不能用auto比如 我要去类模板里面取内嵌的值
typename的一般性规则
template<typename C>
void print1st(const C& container)
{
if (container.size() > 1)
{
C::const_iterator iter(container.begin());//局部变量iter是从属名称
int value = *iter;//局部变量value是非从属名称
std::cout << value;
}
}
template 内出现的名称(代码中的const_iterator)如果相依于某个 template参数(代码中的C),称之为从属名称(dependent names)。如果从属名称在class内呈嵌套状,我们称它为嵌套从属名称(nested dependent name)。C::const_iterator就是这样一个名称。实际上它还是个嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且指涉某类型。
***缺省情况下嵌套从属名称***
2. 非类型模板参数
#define N=100;
template<class T>
class Stack
{
private:
T _a[N];
T _top;
};
int main(void)
{
Stack<int> s1;
Stack<int> s2;
return 0;
}
我们来看这段代码,这个地方就不是很灵活 比如说我s1要开10个int空间
s2要开20个空间怎么办呢?
你可能说可以通过函数传N 但是静态数组的那个N是常量啊!
模板提供了一个很好的方式,模板的参数除了类型,还可以传变量(非类型形参)
template<class T,size_t N>
class Stack
{
private:
T _a[N];
T _top;
};
int main(void)
{
Stack<int,10> s1;
Stack<int,20> s2;
return 0;
}
这样就可以更好的实现泛型编程了
但是同样也有许多要注意的
1.传的参数只能是整型的
2.传的参数在模板中将参数当作常量来使用
像有些容器用的就是非类型形参
3.array
array是C++11的一个容器是一个定长数组
和普通的数组比 它对越界的检查非常严格 越界读写都能检查
普通数组 不能检查越界读 部分越界写可以检查
和vector<int>比 array在栈区上 vector<int>在堆区上 且array不用扩容
比vector<int>少一个capacity
4.模板特化
我们对模版的一些特例有时候要特殊处理
函数模板选择的方法很多 比如函数重载
template<class T>
bool Less(T left, T right)
{
return left < right;
}
//特殊处理方式1 函数重载 优先级最高
bool Less(int* left, int* right)
{
return left < right;
}
//特殊处理2 函数重载 //优先级再其次
template<class T>
bool Less(T* left, T* right)
{
return left <right;
}
//特殊处理3 模板特化
template<> //优先级其次
bool Less(int* left, int* right)
{
return left < right;
}
int main(void)
{
cout << Less(1, 2) << endl;
int a = 3, b = 1;
cout << Less(&a, &b) << endl;
//像这个地方如果我们直接这样就是比较两个地址的大小
//但是如果我们想比较这两个指针所指向的数的大小就要去特殊处理
return 0;
}
那么对于类模板呢?类可不能重载 类就只能用模板特化了
template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
};
//特例
template<>
class Date<int,double>
{
public:
Date()
{
cout << "Date<int,double>" << endl;
}
};
int main(void)
{
Date<int,int> d1;
Date<int, double>d2;
//这个地方我们想让d2这个地方变成一个特例
//打印的是Date(int,double)而不是Date<T1,T2>
return 0;
}
特化的前提必须有原模版
特化是在原模板的基础上进行特化
全特化就是所有模板参数特化
偏特化(半特化)有两个作用
(1)对部分模板参数进行特化
(2)对某些类型进行一些限制
template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
};
//特例 全特化
template<>
class Date<int,double>
{
public:
Date()
{
cout << "Date<int,double>" << endl;
}
};
//半特化(片特化) 特化部分参数
// 第二个模板参数是double就走这个 但是对于<int,double>优先级不如上面那个全特化
template<class T1>
class Date<T1,double>
{
public:
Date()
{
cout << "Date<T1,double>" << endl;
}
};
//半特化(片特化) 对某些类型的进一步限制
//这个地方就特化 计算只匹配<指针,指针>
template<class T1,class T2>
class Date<T1*, T2*>
{
public:
Date()
{
cout << "Date<T1*,T2*>" << endl;
}
};
int main(void)
{
Date<int,int> d1;
Date<int, double>d2;
//这个地方我们想让d2这个地方变成一个特例
//打印的是Date(int,double)而不是Date<T1,T2>
Date<double, double>d3;
return 0;
}
严格来说模板特化的类 不算新的类 因为它不独立存在
但是模板特化类的内容可以和原类不一样 包括成员函数和变量
但是不一样容易出问题 按照需求写就可以了
但是你实例化出来的东西算新的类型(就像vector<int>和vector<double>是两个类型一样)
5.模板的定义声明分离
这个地方为什么push不行?
原因是push没有实例化 编译器怎么知道这个T是什么类型 怎么生成地址给call呢?
怎么解决?
在函数定义的文件显示实例化
template
class stack<int>;
但是这个方式有坑
如果我要的不算stack<int>而是stack<double>呢?
那就要再写一个,治标不治本 !
最好的办法就是把定义和声明写在同一个文件内
6.模板总结