跳到主要内容

模板

模板是一个类或者一个函数,我们用一组类型或值对其进行参数化。 我们用模板表示那些最好理解为通用事务的概念. 然后通过指定参数(例如vector指定元素类型是double)生成特定的类型.

参数化类型

对于之前使用的双精度浮点数向量, 只要将其改成template并且用一个类型参数替换特定类型的double,就能将其泛化.

template <typename T>
class Cluster {
private:
T* it;
int its_num;
public:
explicit Cluster(int s);
~Cluster(){ delete[] it; };

//cope and move
T& operator[](int i);
const T& operator[](int i) const;
int size() const { return its_num;}
}

template <typename T>
Cluster<T>::Cluster(int s){
if(s < 0) throw Negative_size{};
it = new T[s];
its_num = s;
}

template <typename T>
const T& Vector<T>::operator[](int i) const {
if(i < 0 size() <= i) throw out_of_range{"Vector::operator[]"};
return elem[i];
}

前缀template<typename T>指出T是该声明的参数. 它是数学上对所有类型T成立的更精确的C++表达.

Cluster<char> vc(200);
Cluster<string> vc(17);
Cluster<int> vc(45);

类似的也能将列表,向量,映射(关联数组),无序映射(哈希表)等定义成模板.

模板是一种编译时机制, 因此与人工打造的代码相比,不会产生任何额外的运行时开销.

一个模板加上一组模板实参被称为一个实例化(instantiation/specialization). 在编译过程中靠后的实例化时间, 编译器为程序中用到的每一个实例生成相应的代码. 对生成的代码会进行类型检查.使得生成的代码和手写的代码是一样安全.

约束模板参数(C++20)

template <Element T>
class Cluster{
private:
T* items;
int size;
}

Element是一个谓词, 检查T是否具有Vector要求的所有性质. 这种谓词叫做概念. 概念所说明的模板参数叫做约束参数. 参数约束的的模板叫做约束模板

Cluster<int> c;
Cluster<thread> v; //错误我们不能拷贝一个标准线程

值模板参数

template <typename T, int N>
struct Buffer{
using value_type = T;
constexpr int size() {return N};
T[N]
};

其中别名value_type和constexpr函数的目的是令用户可以只读地访问模板参数. 值参数在很多场景下非常有用. 我们可以用buffer创建任意大小地缓冲区,而不必使用动态内存.

Buffer<char,1024> glob; //全局字符缓冲区(静态分配)

void fct(){
Buffer<int,10> buf; //局部地整数缓冲区, 在栈上
}

模板参数推断

标准库模板pair的使用:

pair<int,double> p {1,5.2};

每次需要指出模板参数类型是很麻烦的。

auto p = make_pair(1,5.2); //p是一个pair<int,double>

参数化操作

模板地用途远不止用元素类型参数化容器. 特别是模板广泛用于参数化标准库中的类型和算法

函数模板

对于任何可用范围for遍历的序列(如容器), 可编写如下函数计算和元素值的和:

template<typename Sequence, typename Value>
Value sum (const Sequence& s, Value v){
for(auto x : s) v+=x;
return v;
};

模板参数Value和函数参数v使得调用者可以指定累加器(用于求和的变量)的类型和初始值:

void use(Vector<int>& vi, list<double>& id, vector<complex<double>>& vc){
int x = sum(vi,0); //整数向量的和累加到整数
double d = sum(vi,0.0); //整数向量的和累加到双精度浮点数
double dd = sum(id,0.0); //双精度浮点数链表的和
auto z = sum(vc,complex{0.0,0.0}) //complex<double> 向量的和
};

这里的sum()可以看作是标准库accumulate()的简化版本;

函数对象

function object functor函子

一个可以携带数据并像函数一样调用的对象

template <typename T>
class LessThan{
const T val;
public:
LessThan(const T& v): val{v}{};
bool operator()(const T& x) const {return x < val;} //调用函数符
};

其中operator()的函数实现了函数调用,或应用运算符(); 可以为某些参数类型LessThan类型的命名变量

LessThan it {42};
LessThan its {"Backus"s};
LessThan<string> its2 {"Backus"};

void function(int n, const string & s){
bool b1 = it(n); //如果n<42, 真
bool b2 = its(s); // 如果s< "Backus" true
}

这样的函数对象经常作为算法的参数出现. 例如可以像下面这样统计有多个值令谓词predicate返回true:

template <typename C ,typename P>
int count(const C& c,P pred){
int cnt = 0;
for(const auto & x : c){
if(pred(x)) ++cnt;
return cnt;
}
}

lambda表达式

函数对象的简写形式

之前我们将我们将LessThan的定义和使用分离开. 这样做不太方便

C++提供了一种隐式生成函数对象的表示方法

void f(const vector<int>& vec, const list<string>& lst, int x, const string & s){
cout << "number of values less than " << x << ":" << count(vec,[&](int a){return a < x;}) << endl;
cout << "number of values less than " << s << ":" << count(lst,[&](const string & a){return a < s;}) << endl;
}

[&](int a){return a < x;}这种表示方法被称为lambda表达式. 他声称一个和LessThan<int>{x}完全一样的函数对象. [&]是一个捕获列表. 它指出lambda体使用的所有局部名字将通过引用访问. 如果希望只捕获x, 则写作[&x]; 如果希望给生成的函数对象传递一个x的拷贝,则写成[=x]. 什么也不捕获写作[]. 捕获所有通过引用访问的局部名字是[&],捕获所有以值访问的局部名字是[=];

虽然lambda简单便捷,但晦涩难懂. 对于复杂的操作, 我们更愿意给该操作起一个名字.

template<typename C,typename Oper>
void forall(C& c, Oper op){
for(auto& x: c){
op(x);
}
};

void user2(){
vector<unique_ptr<Shape>> v;
while (cin)
v.push_back(read_shape(cin));
for_all(v,[](unique_ptr<Shape> & ps){ps->draw();});
for_all(v,[](unique_ptr<Shape> & ps){ps->rotate(45);});

}
Loading Comments...