一、模板初阶总结
学完模板初阶之后,可以总结以下3点:
1.可以通过修改模板参数来控制容器内部的数据类型;
2.可以控制模板参数实现某种设计逻辑,如:适配器模式;
3.可以通过类模板实例化出来类型,然后实例化出来函数对象,在函数体内部使用函数对象实现自主切换比较方式,如仿函数;
二、模板进阶
1.class与typename的区别
template<class Container>
void Print(const Container& con)
{
//此处必须使用typename修饰
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
编译器是从上往下执行,会向上检查各种声明和定义,但是此时的Container是不明确的,没有实例化。编译器知道的是Container是一个类型,类型::这种语法后面的内容可能是静态成员变量,静态成员函数,内嵌类型,内部类,而Container::const_iterator 如果是类型就支持 类型 it = con.begin(),如果不是,如静态成员变量,静态成员函数就会语法出错。使用了typename就告诉编译器是类型,这样语法就合乎逻辑了。
总之,如果不是实例化,类型::内嵌类型就使用typename修饰。
2.非类型模板参数
2.1静态数组
#define N 10;
template<class T>
class A
{
private:
int arr[N];
};
使用这样的方式,同时生成多个对象的数组大小是相同的,不符合泛型编程。
template<class T, size_t N = 10>
class A
{
private:
int arr[N];
};
可以使用非类型模板参数,但是非类型模板参数仅仅支持整型家族,不支持浮点型,就和成员常量一样。这里的N是一个常量。
编译器会按需实例化,实例化之后进入检查语法。
bitmap(位图)还有,c++11的array就是用了非类型模板参数。
3.array
array是一个固定大小的序列容器,数组在栈上,成员变量是一个静态数组和size;
3.1接口函数
没有插入删除之类的接口,大小是固定的。
3.2array的优势
可能array唯一的优势就是能够很快的检查出来越界。而原生数组不能检查越界读,可以检查部分越界写。但是vector<int> v(10,1);v[10]也可以检查越界,所以array的设计用处不大。
4.如何看待c++发展
c++是比较贴近底层的语言,不像Java还需要在jvm上运行。但是c++委员会比较划水,到现在也没有出网络库还得自己写套接字,同时还更新了较为鸡肋的东西,如array容器和原生数组基本相同。
5.模板的特化
模板实例化出来的类都是不一样的,特化出来的类模板可以生成内容与普通类模板完全不同的类。
5.1函数模板的特化
使用场景如:想让指针按照指向的内容进行比较这样需要特殊化处理的场景。编译器会按照更匹配的原则来进行实例化。
template <class T>
bool Less(T a, T b)
{
return a < b;
}
template <>//标准模板的特化
bool Less<int *>(int *a, int *b)
{
return *a < *b;
}
//当然最好直接写成函数重载.
5.2类模板的特化
template <class T>
class A
{
public:
A(){cout<<"class A"<<endl};
};
template <>//类模板就必须按照模板的特化来完成
class A<int>
{
public:
A(){cout<<"class A<int>"<<endl};
};
对于大部分比较,可以用仿函数进行控制比较,但若让指针按照指向的内容进行比较,原来得专门再写一个仿函数进行运算符重载,现在可以使用模板的特化对特殊类型进行处理,不需要手动传递新的仿函数进行实例化;
5.3模板的全特化和偏特化
template <class T1, class T2>
struct A
{
A(T1 a, T2 b) { cout << "struct A" << endl; }
};
template <>//全特化
struct A<int, double>
{
A(int a, double b) { cout << "struct A<int, double>" << endl; }
};
template <class T2>//偏特化1.部分类型的特化成具体类型
struct A<T2, double>
{
A(int a, double b) { cout << "struct A<T2, double>" << endl; }
};
template <class T1,class T2>//偏特化2.部分类型的进一步限制,缩小范围
struct A<T1, T2*>
{
A(int a, double b) { cout << "struct A<T1, T2*>" << endl; }
};
总结:如果想要对某一个类型进行特殊处理就是使用全特化,而对一批类型进行特殊处理就使用偏特化。
5.4特化的使用场景
1.迭代器的萃取就是使用了模板的特化;
2.哈希表的k默认不可以使用自定义类型,而string可以就是使用了模板的特化。
6.模板的分离编译
6.1现象
#include <deque>
namespace Stack
{
template <class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T &val);
void pop();
const T &top();
size_t size();
bool empty();
private:
Container con_;
};
}
namespace Stack
{
template <class T, class Container>
void stack<T, Container>::push(const T &val)
{
con_.push_back(val);
}
template <class T, class Container>
void stack<T, Container>::pop()
{
con_.pop_back();
}
template <class T, class Container>
const T &stack<T, Container>::top()
{
return con_.back();
}
template <class T, class Container>
size_t stack<T, Container>::size()
{
return con_.size();
}
template <class T, class Container>
bool stack<T, Container>::empty()
{
return con_.empty();
}
}
使用模板后进行分离编译,会发生链接错误。因为在stack.cc文件中,模板未实例化出来之前,相当于编译器没有写,所以编译阶段就没有形成符号,进行符号汇总,之后链接阶段无法进行符号表合并,形成有效符号表。而test.cc文件中一般都会实例化类出来,比如stack<int>,会在编译阶段进行符号汇总。
即编译阶段如果进行了模板的实例化,就会生成符号汇总,才可以在汇编阶段生成符号表,如果有定义,生成的就是有效的,没定义就是无效的,最后链接阶段会把所有的符号表合并,存在有效的就链接成功,没有就失败。
6.2解决方式
6.2.1.专用化类
//stack.cc文件最后加如下内容,但是缺点就是使用一个类型就得显式实例化一个类。
//这样做会使得stack.cc文件在编译阶段能够形成符号汇总,有了这个条件就可以在汇编阶段生成符号表,因为此文件中有定义,所以这个符号表就是有效的,最终就会链接成功。
template
class stack<int>;//实例化生成类
template <>//不能这样写,这样就是模板的特化
class Stack::stack<int>;
6.2.2在同一个文件进行分离
对这类型文件有人将其命名为“xxx.hpp”、“xxx.hxx”、“xxx.hh”,但是这只是一种暗示”.h和.cpp“,“.h和.cxx”,“.h和.cc”放到了一起,没有强制规定。
//这个文件中没有进行符号汇总,但是进行了分离且有定义,能保证生成有效的符号表。
//若其他文件使用此文件时,只要在其他文件进行显式实例化,就可以直接保证其他文件汇编生成的符号表是有效的,链接成功。
#include <deque>
namespace Stack
{
template <class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T &val);
void pop();
const T &top();
size_t size();
bool empty();
private:
Container con_;
};
}
namespace Stack
{
template <class T, class Container>
void stack<T, Container>::push(const T &val)
{
con_.push_back(val);
}
template <class T, class Container>
void stack<T, Container>::pop()
{
con_.pop_back();
}
template <class T, class Container>
const T &stack<T, Container>::top()
{
return con_.back();
}
template <class T, class Container>
size_t stack<T, Container>::size()
{
return con_.size();
}
template <class T, class Container>
bool stack<T, Container>::empty()
{
return con_.empty();
}
}
7.模板总结
模板实例化,对于同种类型只会生成一个类。
优点:
1.模板的出现使得开发效率显著提高,只需要自己写一份模板,编译器就自动生成各种代码,不会让用户手动写冗余的代码,十分便捷;
2.代码更加灵活,适配器,仿函数,可通过改变模板参数实现,冗余部分直接替换;
缺点:
1.会导致编译时间变长,模板实例化的过程是编译器来完成,需要时间;
2.调试时,调试信息难以理解,调试信息过长;