Xander's Wiki


  • Home

  • Tags

  • Categories

  • Archives

  • Search

2.C++式类型转换

Posted on 2018-04-23 | In More Effective C++

(本节内容在Effective C++ 28中亦有涉及)

前言

 
类型转换这种东西c语言就有,但是过于粗鲁(允许任何类型之间的相互转换)也难以识别(因为它用圆括号和标识符)
c++引入了四种类型转换,分别是static_cast,const_cast,dynamic_cast以及reinterpret_cast.


四种类型转换的作用

static_cast

强制隐式类型转换,其特点在于无法去除const属性。

const_cast

const_cast用于去除表达式的const或volatileness属性。

dynamic_cast

dynamic_cast可以把指向bc的指针或者引用改为指向dc的,转换失败则返回空指针(转换指针)或者抛出异常(转换引用)实例如下:

1
2
3
4
5
6
7
8
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
Widget *pw;
...//指向某个SpecialWidget对象
update(dynamic_cast<SpecialWidget*>(pw));
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));

reinterpret_cast

reinterpret_cast的操作结果几乎都是执行期定义(很难移植),一般用作函数指针类型之间的切换,实例如下:

1
2
typedef void (*FuncPtr)(); //一个函数指针,该函数没有参数 返回值类型为void
FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳10个FuncPtrs指针的数组

当前我们希望把下面这个函数指针放入数组内:int dosth()当然这必须要使用类型转换,于是有:
1
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);

47.在需要类型转换时请为模板定义非成员函数

Posted on 2018-04-23 | In Effective C++

前言

 
Effective C++ 25已经描述过为何仅有non-member函数才有能力对所有实参执行隐式转换。本节我们将类型转换拓展到template范围。


问题实例

 
在上一次的实例中,我们以Rational class与operator*讨论了在所有实参身上执行隐式转换。本次实例则将它们模板化。

1
2
3
4
5
6
7
8
9
template<typename T>
class Rational{
public:
Rational(const T& numerator = 0,const T& denominator = 1);
const T numerator() const;
const T denominator() const;
}
template<typename T>
const rational<T> operator*(const rational<T> &lhs,const rational<T> &rhs) {}

我们预期以下代码也会通过编译,因为它们在非模板时确实编译成功了:
1
2
Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf *2;//error 编译失败

我们应该能够意识到,模板化的Rational中的某些东西似乎与non-template版本不同。


问题剖析

 
在非模板情况下,编译器知道我们尝试去调用接受两个Rational参数的operator*,但在这里,编译器不知道我们想调用哪个函数。
它在试图想出什么函数可以被名为operator*的template具现化出来。它知道它们应该可以具现出某个名为operator*且接受两个rational<T>参数的函数,但为了完成这一具现化行为,必须先算出T是什么。编译器无法做到。

实参推导

1
2
Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf *2;

operator*的第一参数被声明为rational<T>,而传递给operator*的第一实参oneHalf是Rational<int>,那可以肯定T是int.关键在于第二实参接受的应该是一个Rational<int>,但实际传入的却是int。T的推导无法确定。
template实参推导过程中并不考虑隐式类型转换。这就是报错的关键。尽管隐式转换在函数调用过程中的确被使用,但在能调用一个函数之前,它必须已经存在(已被推导出并具现化)。


问题解决

friend声明

template class内的friend声明式可以指涉某个特定函数,那意味着class Rational<T>可以声明operator*是它的一个friend函数。class templates并不依赖于template实参推导(实参推导仅发生在function templates身上),所以编译器总是能在class rational<T>具现化时得知T.因此,令Rational<T> class 声明适当的operator*为其friend函数,可以简化整个问题:

1
2
3
4
5
6
7
template <typename T>
class Rational{
public:
friend const rational operator*(const Rational& lhs,const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(const Rational<T>&lhs,const Rational<T>&rhs);

当oneHalf被声明为一个Rational<T>时,rational<int>则被具现化,而作为具现化的一部分,operator*也完成了自动声明,后者作为一个non-template function,编译器可以在调用时使用隐式转换。

连接

friend声明帮助我们完成了编译,但上述代码并不能连接,原因很简单,只有声明是而没有定义式。我们也无法在class外部完成operator* template定义,因为既然在Rational template中声明,就必须在其中定义。因此必须把函数本体移动到声明式中:

1
2
3
4
5
6
7
8
template <typename T>
class Rational{
public:
friend const rational operator*(const Rational& lhs,const Rational& rhs){
return Rational(lhs.numerator()*rhs.numerator(),
lhs.denominator()*rhs.denominator());//返回值优化
}
};

至此,编译并连接成功,混合式计算正常运行。


解决方案剖析

 
这项技术的关键点在于:我们使用friend并不是因为我们想访问non-public成员。我们为了让类型转换可以用于所有实参,需要一个non-member函数。为了令它自动具现化,我们需要它位于class内部。综合二者,我们得到了friend.

众所周知,定义在class内部的函数都申请inline,包括friend函数。为了避免可能带来的高成本,一般而言,我们会令它不做任何事情,只是调用class外的一个辅助函数。

辅助函数

Rational是一个template意味着上述的辅助函数通常也是一个template。我们定义了Rational的头文件代码如下:

1
2
3
4
5
6
7
8
9
template<typename T> class Rational;
template<typename T> const Rational<T> doMutiply(const Rational<T> &,......);
template <typename T>
class Rational{
public:
friend const rational operator*(const Rational& lhs,const Rational& rhs){
return doMutiply(lhs,rhs);
}
};


总结

 
当我们编写一个class template,而它提供的“与此template相关”函数需要“所有参数支持隐式转换时”,请将那些函数定义为friend函数,并在class template中定义它们。

1.指针与引用的区别

Posted on 2018-04-22 | In More Effective C++

前言

 
指针与引用似乎完全不同,前者是某个对象的地址,后者是某个对象的别名,但是功能却似乎一样,都是间接引用其他对象。那么何时用指针何时用引用呢?


空值

 
不存在指向空值的引用,但是存在指向空值的指针。
但可能会有人写出这种程序:

1
char *pc = nullptr;char & rc = *pc;

其结果未定义,在C++的世界里我们不讨论这种无聊且低级的错误。
引用不存在空值,所以必须要初始化,而指针则不用,虽然这样很危险。
引用不存在空值这一特性说明其有效性比指针高,因为我们在指针的使用中总需要判断其非空,但引用则无需如此。


重新赋值

 
指针可以被重新赋值以指向另一个不同的对象。但引用则总是指向在初始化时被指定的对象,无法改变。


在何种情况下使用它们

指针

使用指针的情况是:

  • 可能会不指向任何对象
  • 可能需要改变绑定

引用

使用引用的情况是:

  • 一旦绑定后不再更改
  • 重载某个操作符

假设需要自定义operator[],其功能是返回一个目标对象,其能被赋值:

1
2
vector<int> v(10);
v[5]=10;

如果operator[]返回值不是引用而是指针,则最后一句必须写为 *v[5]=10,这看起来有点像一个数组指针,容易造成不必要的误解。

46.运用member function template接受所有兼容类型

Posted on 2018-04-21 | In Effective C++

问题实例

 
所谓智能指针,指的是一些行为像指针的对象,它们可以提供指针没有的机能。STL容器的迭代器几乎总是智能指针,你不会奢望使用operator++将指针从list的一个node移至另一个node,但是迭代器可以。

真实指针有一个很好用的功能:支持隐式转换。举例来说:

  • derived class指针可以隐式转为base class指针
  • 指向non-const对象的指针可以指向const对象

既然智能指针是行为如同原始指针的对象,那么我们当然希望智能指针也具备上述功能。具体来说,我们希望下述代码能够通过编译:

1
2
3
4
5
6
7
8
9
class Top;
class Middle:public Top;
template<typename T>
class Smartptr{//自定义的智能指针
public:
explicit Smartptr(T* realPtr);
}
Smartptr<Top> pt1 =Smart<Middle>(new Middle);
Smartptr<const Top> pt2 = pt1;

但是,同一个template的不同具现化之间并不存在继承关系,所以,smartptr<top>与smartptr<middle>完全无关,为了达到我们期望的smart classes之间的转换能力,我们必须明确地编写它们。


解决方案

 
我们关注的重点是应该如何编写智能指针的构造函数。经过分析不难得出结论:我们永远无法写出所有需要的构造函数。我们真正需要撰写的是一个构造模板。
构造函数模板具体如下:

1
2
3
4
5
6
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other);
}


方案剖析

 
该构造函数模板声明:对于任何类型T和任何类型U,总可以根据一个SmartPtr<U>对象来构造一个SmartPtr<T>对象。我们把这种构造函数称为泛化copy构造函数。之所以不用explicit声明,是因为原始指针之间的转换就是隐式的,我们需要保证智能指针与原始指针之间的兼容性。
声明固然已经完成,但是实现却值得花一番心思。并非任意的U型智能指针都能转为T型智能指针(比如把base转为derived),所以我们必须在某方面对这一member template所创建的成员函数群进行筛选。


解决方案的实现

 
假设SmartPtr遵循shared_ptr的接口,存在一个get函数返回原始指针的副本,那我们则可以在构造模板中实现代码约束,具体如下:

1
2
3
4
5
6
7
8
9
10
template <typename T>
class SmartPtr{
public:
template <typename U>
SmartPtr(const SmartPtr<U> &other)
:helder(other.get()) {....}
T* get() const {return heldptr;}
private:
T* heldPtr;
}

现在,仅在“存在某个隐式转换将U*指针转为T*指针”时才能通过编译,而这正是我们想要的。


其它

 
member function template不仅仅单纯用于构造函数,它还常常在赋值操作时发挥作用。
在class中声明泛化copy构造函数并不会阻止编译器生存自己的copy构造函数(一个non-template),因此如果需要控制copy构造的方方面面(比如禁止拷贝之类),我们必须同时声明泛化copy构造函数与non-template构造函数,同样的规则也适用于赋值操作。


总结

  1. 可使用member function template生成“可接受所有兼容类型”的函数
  2. 如果你声明了member template用于泛化操作,你还是需要声明正常的copy构造函数与copy assignment运算符。

48.正确的include

Posted on 2018-04-21 | In Effective STL

STL头文件概要:

  • 所有的容器都在同名的头文件中
  • 几乎所有算法都在algorithm中
  • 上一句之所以用”几乎”是因为accumulate、inner_product、adjacent_difference、partial_sum,它们4个在numeric中
  • 特殊的迭代器在iterator中声明
  • 标准仿函数(less<T>等)和仿函数适配器(not1、bind2nd等)在functional中声明

45.将参数无关的代码抽离template

Posted on 2018-04-21 | In Effective C++

前言

 
Templates是避免代码重复与节约时间的最优解。但有时,template可能会造成代码膨胀:二进制码带着大量重复(或几乎重复)的代码或者数据。其源码可能看起来优雅整洁,但实际上生成的object code完全不是一回事,我们需要对此进行优化。


共性与变性分析

 
当我们在撰写函数时,往往倾向于提取出函数的共同部分,组成新的函数,然后令原来的函数调用该新函数。在OOP中,如果多个class存在相同部分,则会抽离classes的共有部分组成新的class,然后采用继承或者复合获取共性,而原本classes的互异部分则保持不变。
在编写templates也是同样的方式避免重复,但在细节处略有不同。在non-template程序中我们可以明确地看到重复的函数或重复的class成员,但在template编码中,源码只有一份,必须自己去感知当template具现化时可能发生的重复。

实例分析

为一个方阵编写template,其性质之一是支持求逆:

1
2
3
4
5
template<typename T,size_t n>
class SquareMatrix{
public:
void invert();//求逆矩阵
}

假设现有操作如下:
1
2
3
4
SquareMatrix<double,5> sm1;
sm1.invert();
SquareMatrix<double,10> sm2;
sm2.invert();

以上操作直接导致具现化了两个invert,虽然它们并非完全相同,但是相当类似,唯一的区别就是一个作用于5*5矩阵一个作用于10*10矩阵。

解决方案

第一版修改方案

当我们看到两个除了参数之外完全相同的函数,本能反应应当是建立一个带数值参数的可调用函数,而不是在class中重复代码。基于这种理念,第一版修改方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
class SquareMatrixBase{
protected:
void invert(size_t matrixSize);
}
template<typename T,size_t n>
class SquareMatrix:private SquareMatrixBase{
private:
using base::invert;//避免遮蔽名称
public:
void invert() {this->invert(n);}//inline调用
}

如你所见,所有矩阵元素类型相同的class共有一个bc,并且共享这唯一一个class内的invert.在dc中调用invert的额外成本应该为0,因为这是一个inline调用,this->的使用详见前一节内容(Effective C++ 44)。private继承说明了bc只是为了帮助dc实现,他们并不是is-a的关系。


第二版修改方案

现在的问题是:base内的invert如何知道操作什么数据?虽然它知道尺寸,但他如何知道某个特定矩阵的数据存在于内存的何处?这显然只有dc知道。dc如何联络bc做逆运算呢?​
一个可能的做法是向invert函数添加另一个参数,一个指向矩阵数据的指针。根据经验,invert应该不是唯一一个需要获取矩阵数据的函数,所以我们令base class贮存一个指针,指向矩阵数值指向的内存,既然它持有了数据,那必然也需要了解矩阵的大小。具体实现如下所示:

1
2
3
4
5
6
7
8
9
template<typename T>
class SquareMatrixBase{
protected:
SquareMatrixBase(size_t n,T *pMem):size(n),pData(pMem) {}
void setDataPtr(T* ptr) {pData = ptr;}//更改数据域
private:
size_t n;
T* pData;//矩阵具体内容
}

这种写法允许derived classes自主决定内存分配方式。

dc的内存分配方式

  1. 矩阵存储于对象内部
    1
    2
    3
    4
    5
    6
    7
    template<typename T,size_t n>
    class SquareMatrix:private SquareMatrixBase<T>{
    public:
    SquareMatrix():SquareMatrixBase<T>(n,data) {}
    private:
    T data[n*n];
    };
    这一类对象无需动态分配内存,但是对象自身可能非常大。
  2. 把对象数据放入heap
    1
    2
    3
    4
    5
    6
    7
    8
    template<typename T,size_t n>
    class SquareMatrix:private SquareMatrixBase<T>{
    public:
    SquareMatrix():SquareMatrixBase<T>(n,0),padta(new T[n*n])
    {this->setDataPtr(pData.get());}
    private:
    shared_ptr<T> pData;//RAII
    };

总结

  1. template生成多个class与多个函数,所以任何template代码都不应该与某个造成膨胀的template参数相互依存
  2. 因为non-type template parameters造成的代码膨胀往往可以消除,做法是以函数参数或者class成员变量替换template参数。
  3. 因type parameters造成的代码膨胀往往可以降低,做法是让带有相同二进制表述的具现类型共享实现码。

47.避免产出write-only code

Posted on 2018-04-21 | In Effective STL

问题实例

 
假设现有一个vector,我们需要删除vector中值小于x且出现在至少和y一样大的最后一个元素之后的所有元素,现有实现如下:

1
2
3
vector<int> v;
int x, y;
v.erase(remove_if(find_if(v.rbegin(), v.rend(),[](int i){return i>=y;}).base(),v.end(),[](int i){return i<x;}),v.end());

虽然该实现用一条语句就解决了问题,但看起来…emm…似乎不是很好维护。
那接着考虑如下实现:
1
2
3
ypedef vector<int>::iterator VecIntIter;
VecIntIter rangeBegin = find_if(v.rbegin(),v.rend(),[](int i){return i>=y;}).base();
v.erase(remove_if(rangeBegin, v.end(), [](int i){return i<x;}), v.end());

这一段代码的可读性无疑强了很多。


问题剖析

 
让我们回到问题本身:去除值小于x且出现在至少和y一样大的最后一个元素之后的所有元素。
那我们很自然的会想到下面的方案:

  1. 找到一个元素的最后一次出现需要用反向迭代器,并且需要使用find或者find_if
  2. 去除元素需要使用erase-remove惯用法

那么写出如下伪代码也顺理成章:

1
v.erase(remove_if(find_if(v.rbegin(), v.rend(), something).base(),v.end(),something)),v.end());

读者不难发现这种形式的程序写出来很容易,但如果逆向从代码去推导最初的需求则很难,这种风格被称为write-only,可读性极差。

代码的可读性十分重要,从某种意义上来说,它直接影响了软件开发的高效性。码农何苦为难码农,write-only类型的代码虽然写起来很舒服,但在工作中应当尽力避免。

46.以函数对象代替函数作为谓词

Posted on 2018-04-21 | In Effective STL

(本节内容已淘汰,笔者推荐配合lambda使用泛型算法)

44.处理模板化基类内的名称

Posted on 2018-04-21 | In Effective C++

问题实例

 
假定我们需要撰写一个程序,它需要传递信息至不同的公司。信息或者是明文,或者是密码。如果我们在编译期可以确定发送至哪家公司,就可以采用基于template的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CompanyA{
public:
...
void sendCleartext(const string& msg);
void sendEncrypted(const string& msg);
...
};
class CompanyB{
public:
...
void sendCleartext(const string& msg);
void sendEncrypted(const string& msg);
...
};
class MsgInfo {...};//保存信息
template<typenmae Company>
class MsgSender{
public:
...
void sendClear(const MsgInfo& info){
string msg;
//根据info产生信息
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info);
};

显然这种处理方案是合理的。
但如果我们希望在每次发送信息时完成记录,我们通过派生一个derived class来完成这种行为:
1
2
3
4
5
6
7
8
9
10
template<typenmae Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){//避免遮蔽名称
//log sth
sendClear(info);//试图调用base class函数,无法编译
//log sth
}
};

这段程序逻辑上无误,但编译器会拒绝编译,并给出sendClear不存在这样的错误信息。


问题剖析

 
报错原因在于,当编译器遭遇class template LoggingMsgSender定义式时,并不了解其基类,因为此时基类尚未被具现化。
再具体一些,我们假定有CompanyZ坚持使用加密通讯:

1
2
3
4
5
6
class CompanyZ{
public:
...//不提供sendClear
void sendEncrypted(const string& msg);
...
};

那一般化的MsgSender template对CompanyZ并不合适,因为该template提供了sendClear。因此我们针对CompanyZ做出了一个特化:
1
2
3
4
5
6
template<>//全特化
class MsgSender<CompanyZ>{
public:
...
void sendSecret(const MsgInfo& info);
};

在完成了全特化后,我们再次考虑derived class LoggingMsgSender:
1
2
3
4
5
6
7
8
9
10
template<typenmae Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){
//log sth
sendClear(info);//如果参数为CompanyZ,该函数不存在
//log sth
}
};

如今我们可以清楚地看出编译器拒绝调用的原因:base class template可能被特化,且特化版本可能不具备与一般性版本一致的接口。编译器拒绝在templated base classes内寻找继承而来的名称,面向对象的规则在泛型编程中依然不再适用。


解决方案

  1. 使用this->
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template<typenmae Company>
    class LoggingMsgSender:public MsgSender<Company>{
    public:
    ...
    void sendClearMsg(const MsgInfo& info){
    //log sth
    this->sendClear(info);//假设继承了sendClear,隐式接口
    //log sth
    }
    };
  2. 使用using
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<typenmae Company>
    class LoggingMsgSender:public MsgSender<Company>{
    public:
    using MsgSender<Company>::sendClear;//假设存在
    ...
    void sendClearMsg(const MsgInfo& info){
    //log sth
    sendClear(info);
    //log sth
    }
    };
    值得注意的是,这里使用using与Effective C++ 34中的作用并不相同,这里的情况并非发生了名称遮蔽,而是主动要求编译器前往base class内部查找对象函数。
  3. 使用作用域运算符明确指出该名称
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template<typenmae Company>
    class LoggingMsgSender:public MsgSender<Company>{
    public:
    ...
    void sendClearMsg(const MsgInfo& info){
    //log sth
    MsgSender<Company>::sendClear(info);
    //log sth
    }
    };
    第三种方法不建议使用,因为如果名称是一个virtual函数,这种做法关闭了virtual绑定行为。

解决方案剖析

 
上述的三种方法其实都是在做同一件事:对编译器承诺“base class template的任何特化版本都将支持其一般版本所提供的接口”。
面对“指涉base class mrmbers”的无效references,编译器的诊断可能在早期(当解析dc template的定义式时),也可能发生在晚期(当template被特定实参具现化时)。c++一般惯于较早诊断,这也就是为什么“当base classes从templates中被具现化时”,它假设它对base classes的内容一无所知的原因。

45.查找算法

Posted on 2018-04-21 | In Effective STL

前言

 
假设我们需要在一个区间内进行搜寻,并且拥有众多的搜索策略:count、count_if、find、find_if、binary_search、lower_bound、upper_bound和equal_range。


区间的有序性

 
在查找之前,首先要判断区间的性质,如果是有序区间,可以用binary_search、lower_bound、upper_bound和equal_range,它们消耗对数时间。
如果不是,那你只能用线性时间的算法count、count_if、find和find_if。

无序区间

 
如果你已经选择了find或者count,要明白它们针对的问题并不相同。

  • count回答的问题是“是否存在,如果有,则存在几份”
  • find则回答“是否存在,如果有,它在哪儿”

问题实例

如果你想知道是否有一个特定的Widget值w在list中。
使用count的代码大概如下所示:

1
2
3
list<Widget> lw; 
Widget w;//被搜索物
if (count(lw.begin(), lw.end(), w))//true则存在

如果使用find,则代码大致如下:
1
if (find(lw.begin(), lw.end(), w) != lw.end())//true则存在

count在这里的效率要低于find,因为find一旦找到就终止算法,而count则一直搜索到结尾。同时,find能够提供对象所在的位置,这也是count做不到的。


有序区间

对于有序区间,查找的效率得到了提高。但是因此导致了另一个问题,判同由相等变为了等价。别忘了,count和find算法都用相等来搜索,而binary_search、lower_bound、upper_bound和equal_range则基于等价。

binary_search只返回bool,也就是说,它只能告诉你某个值是否存在于区间内,如果你需要其具体的位置,那我们得使用equal_range,或者lower_bound。

lower_bound

lower_bound在寻找时返回一个迭代器,它往往指向该值的第一个copy或者可以插入该值的位置,具体使用方略如下:

1
2
3
4
5
6
7
auto i = lower_bound(vw.begin(), vw.end(), w);
if (i != vw.end() && *i == w) {//存在bug
...
}
else {
...
}

bug在于lower_bound使用等价判定,而非相等判定。尽管在大多数情况下他们确实是一致的,但正如Effective STL 19所指出的那样,总有例外。

equal_range

equal_range是一个非常好用的算法,它返回一对迭代器,第一个等于lower_bound返回的,第二个等于upper_bound返回的。因此,它返回了与你要搜索的值等价的区间。使用方略如下:

1
2
3
4
5
6
7
8
9
10
11
vector<Widget> vw;
sort(vw.begin(), vw.end());
typedef vector<Widget>::iterator VWIter;
typedef pair<VWIter, VWIter> VWIterPair;
VWIterPair p = equal_range(vw.begin(), vw.end(), w);
if (p.first != p.second) {
...//找到了,且p.first指向第一个
}
else {
...//不在区间内,不过找到了合适的插入位置
}

另外,对这两个迭代器作distance得到的就是count的功能。


寻找位置

 
之前一直假设任务为在区间中搜索某个特定值,但有时候我们更感兴趣于在区间中寻找一个位置。

问题实例

假设我们有一个TimeStamp类与一个存储它的vector,它的排序方式是老的在新的前面:

1
2
3
4
class Timestamp { ... };
bool operator<(const Timestamp& lhs,const Timestamp& rhs); //按照时间比较
vector<Timestamp> vt;
sort(vt.begin(), vt.end());

现在我们有一个特殊的TimeStamp对象——ageLimit,而且我们需要从vt中删除所有比ageLimit老的TimeStamp。

解决方案

在这种情况下,我们不需要在vt中搜索和ageLimit等价的Timestamp,因为可能不存在任何等价于这个精确值的元素。取而代之的是,我们需要在vt中找到一个位置:第一个不比ageLimit更老的元素。那无疑应当使用lower_bound.
那如果是删除所有至少和agelimit一样大的呢?那无疑是使用upper_bound.


总结

 
我们可以根据下表来判断何时使用何种查找算法:
image_1cbiseq7rd1919oe1s6a9pf1sjg9.png-114.4kB

<i class="fa fa-angle-left"></i>1…171819…27<i class="fa fa-angle-right"></i>

xander.liu

266 posts
11 categories
36 tags
RSS
GitHub E-Mail
© 2024 xander.liu
Powered by Hexo
|
Theme — NexT.Pisces v5.1.4