C++进阶教程之模板3--一些知识的填充(霜之小刀)
QQ:2279557541
Email:lihn1011@163.com
博客地址:http://blog.csdn.net/lihn1987
优酷主页:http://i.youku.com/shuangzhixiaodao
1. typename的另外一种用途
前面的几期内容中,我们知道了typename的一个功能,就是用于声明模板参数中的类型参数。比如
template<typename T>
void func(){
....
}
但是typename还有另外一种用途。用于定义后面跟着的是一个类型。
比如我们定义一个模板.
#include <vector>
template<typename T>
class TestClass
{
private:
T::iterator iter;
};
int main()
{
TestClass<std::vector<int>> a;
system("pause");
return 0;
}
我们想这个模板接受的模板参数都是标准库中的容器,然后在容易中定义一个容器的迭代器(iterator)类型的变量。
但是我们发现程序报错了。为何报错?
他把T::iterator看成了iterator是T的一个静态成员变量。。。。
那么怎样让编译器知道T::iterator是一种类型呢?就是在前面加一个typename告知编译器,后面跟着的是一个类型。
template<typename T>
class TestClass
{
private:
typename T::iterator iter;
};
int main()
{
TestClass<std::vector<int>> a;
system("pause");
return 0;
}
目前看到的这种情况是发生在成员变量的定义时,那要是在函数中定义依赖模板参数T的成员类型的变量会发生什么情况呢?
template<typename T>
class TestClass
{
public:
void function()
{
T::iterator pos;
}
private:
typename T::iterator iter;
};
int main()
{
TestClass<std::vector<int>> a;
a.function();
system("pause");
return 0;
}
我所使用的vs2015的编译器并没有报错。但是为了防止其他的一些编译器会报错,我们可以养成一种习惯,就是任何依赖传入模板参数的类型前都加一个typename。
2. 模板的模板参数
听着就有点绕,这一节也会比较绕。
比如我们写一个类,想用来封装一个标准库的容器。
我们期望的使用方式是这样的
MyClass<int, std::vector>
就是分装一个用std::vector<int>来存储数据的类型。
#include <iostream>
#include <string>
#include <vector>
#include <list>
template<typename T>
class MyList:public std::list<T>
{
};
template<typename T>
class MyVector :public std::vector<T>
{
};
template <typename T1, template<typename TMP> class T2>
class TestClass
{
private:
T2<T1> m_list;
};
int main()
{
TestClass<int, MyVector> a;
system("pause");
return 0;
}
首先代码中先写了两个模板类MyList与MyVector分别继承自std::list与std::vector,这里为什么要写这两个完全没有用单单是继承的模板类呢?
这个问题我们放到后面再讲,这里只需要把MyList当成std::list,把MyVector当成std::vector的功能即可。
然后看我们的TestClass,他的模板参数是这样定义的
template <typename T1, template<typename TMP> class T2>
其中第一个参数容易理解,但是第二个参数怎么理解呢?因为我们传入的不是一个固定的类型,而是一个模板类的名称,所以这里就要先声明一个模板类
template<typename TMP> class T2
这就是我们定义的模板类,其中T2就是我们那个模板类的名称,而TMP其实是完全没有用到的,可以省略因此我们这里可以吧这一行改为
template <typename T1, template<typename> class T2>
这就是模板的模板参数。
本来呢说到这其实就已经完了,但是我们看这段代码不觉得很别扭么?
为什么非要用MyList与MyVector???如果我们直接用标准库中的容器会如何?
于是我们把
TestClass<int, MyVector> a;
改为
TestClass<int, std::vector> a;
结果,编译器报错!!!!为何????
于是看一下std::vector的定义为
template<class _Ty,
class _Alloc = allocator<_Ty> >
class vector{
.....此处省略无数代码
}
原来他的模板定义不止有一个参数
于是我们修改下我们的TestClass
然后源代码变为:
#include <iostream>
#include <string>
#include <vector>
#include <list>
template <typename T1, template<typename TMP, typename _Alloc = std::allocator<TMP>> class T2>
class TestClass
{
private:
T2<T1> m_list;
};
int main()
{
TestClass<int, std::vector> a;
system("pause");
return 0;
}
这样貌似简介多了,再也没有中间的那个什么MyList,MyVector这种鬼了。但是我们看看模板参数这一行
template <typename T1, template<typename TMP, typename _Alloc = std::allocator<TMP>> class T2>
这个里面的TMP由于后面要用到,没法省略了,但是那个_Alloc貌似没啥用了啊,这个能省么???
尝试一下改为
template <typename T1, template<typename TMP, typename = std::allocator<TMP>> class T2>
没错!果然是可以省略的!
至此,我们的需求终于实现了,这一节的内容也就讲完了
3. 模板中字符串的推导
首先先看下面这个例子
template<typename T>
void func1(T a, T b)
{
std::cout << typeid(T).name() << std::endl;
}
template<typename T>
void func2(T& a, T& b)
{
std::cout << typeid(T).name() << std::endl;
}
int main()
{
func1("aaa", "bbb");
func1("aaa", "cccc");
func2("aaa", "bbb");
//func2("aaa", "cccc");编译器报错
system("pause");
return 0;
}
输出为:
char const *
char const *
char const [4]
这种输出的原因是,当模板参数传入的类型不是引用时,数组会自动被推导为指针,而第二个模板由于函数模板由于传入的是引用,则使用了原来的数组类型,所以备注释报错的那一行中的”aaa”,”cccc”得类型分别是char const [4]和 char const[5]的类型。
这里多讲一点,字符串常量,也即是我们代码里直接写的”aaa”这种,其实编译器会将其识别为一个char const [4]的数组,而数组里的四个元素分别为字符’a’,’a’,’a’,’a’,’\0’
相关视频请在我的优酷主页中查找。