The C++ Standard Library --- A Tutorial Reference 读书笔记

          .  需要注意的
          .  通常是指错误的
          .  通常是指正确的
          .  通常与下面的蓝色相对, 且需要注意的
          .  通常与上面的褐色相对, 且需要注意的

 

2.2 新的语言特性


Template constructor:

  1. 通常用于 "在对象复制时实现饮食类型转换".
  2. 如果类型完全吻合, copy constructor 会被调用, 而不是 template constructor.
class C
{
public:
C(){}

/*
* Copy constructor.
*/
C(const C &other)
{
cout <<"copy constructor" <<endl;
}

/*
* Used for implicit convertion.
*/
template <typename T>
C(const T &t)
{
cout <<"template constructor" <<endl;
}
};

int _tmain(int argc, char* argv[])
{
C c1;

C c2(c1); // Copy constructor.
C c3(1); // Template constructor.


return 0;
}

 

命名空间的作用是可以分布式的管理标识符. 这有两层含义:

  1. 可以将相同的标识符区分在不同的作用域下, 使之成为两个完全不同的标识符. 如 NA::x 和 NB::x.
  2. 可以利用一个 namespace定义一些组件, 而它们可分布于多个实质模块上.

在头文件中使用 using declaration 和 using directive 会污染直接或者间接包含此头文件的模块. 因此不要在头文件中使用任何形式的 using.

 


Explicit:

  通过 explicit 关键字能禁止 "单参数构造函数" 被用于自动类型转换. 注: 多参数无法实现自动类型转换, 你可能想到了使用 C c = (1, "a"); 的方法, 可惜语句中的 ,(逗号) 会被当作逗号表达式.

 

Stack s1(40) 等价于 Stack s1 = 40 么? 答案是: 不!

class Stack
{
public:
explicit Stack(const int n){} // Explicit convertion.
Stack(const Stack &other){}
Stack &operator=(const Stack &other){}
};

int _tmain(int argc, char* argv[])
{
Stack s1(40); // Ok
Stack s2 = 40; // Error

return 0;
}

这是因为

 

  X x;
  Y y(x);   // Explicit convertion.

  X x;
  Y y = x;   // Implicit convertion.

的区别.

结论: explicit 能够阻止 "以赋值语法进行初始化的操作".

 

 

C++ 类型转换操作符:

1. static_cast
  唯有当所要进行的类型转换有所定义, 整个转换才能成功.

class B;
class A
{
public:
A(){}
// A(const B &b){} // 从 B -> A 转换的函数, 可以被 static_cast<A>(B()) 使用.
};
class B
{
public:
B(){}
// operator A(){return A();} // 从 B -> A 转换的函数, 可以被 static_cast<A>(B()) 使用.
};

int _tmain(int argc, char* argv[])
{
static_cast<A>(B()); // 上述两函数都可以实现转换, 定义一者即可, 如果都定义, 则调用 A(const B &).

return 0;
}

 2. dynamic_cast

  1. 将多态类型(polymorphic type) 向下转换(downcast) 为其实际的静态类型(static type). 这是唯一在执行期进行检验的转换操作.
  2. 转换失败时, 抛出 bad_cast 异常.

3. const_cast

  1. 添加或去除 const 属性. 也可去除 volatile 属性.
  2. 只能转换指针或者引用. 

4. reinterpret_cast

  此操作行为由编译器决定. 因此不可移植. 

以上转换操作符都只接受一个参数. 因此以下例子是错误的:

static_cast<Fraction>(15, 100);           // Opps, creates Fraction(100)

因为 '(15, 100)' 被解释为逗号表达式

正确的做法是:

Fraction(15, 100);          // Fine, creates Fraction(15, 100)

 

常数静态成员:

  1. 可以在类中直接初始化. 也可以在定义时初始化.
  2. 必须在实现文件中定义一个空间.
// X.h
class X
{
static const int i = 0; // 只有静态成员才能在类中直接初始化.
};

// X.cpp
const int X::i; // 这里不能再次初始化, 否则报错.

 

main() 的定义:

  1. 在 C++ main() 的末尾定义了一个隐式的 return 0;. 这一点和 C 不同. 这意味着如果你不写 return 语句离开 main(), 实际上返回了 0.
  2. 只有两种行的是可移植的:
// 1.
int main()
{
...
}

// 2.
int main(int argc, char* argv[]) // argv 也可以是 char ** 类型.
{

...
}

 

Big-O 表示法:

Big-O 表示法隐藏了指数较小的因子, 它不关心算法到底耗用多长时间. 因此具有最佳(最低)复杂度的算法不一定就是最好(最快)的算法.

 

 

3.3 错误(Error) 异常(Exception) 处理 <exception>/<stdexcept>

标准异常

语言本身或标准库程序所抛出的所有异常, 都派生自基类 exception.
这些标准异常类型可分为三类: 

1. 语言本身支持的异常

1) 全局 new 操作失败会抛出 bad_alloc 异常.
2) 执行期间 dynamic_cast 转换失败抛出 bad_cast 异常.
3) 执行期间类型识别(RTTI) 过程中, 如果给 typeid 提交 NULL 指针, 会抛出 bad_typeid 异常.
4) 发生非预期异常(指在异常规格中未声明, 却被抛出的异常), bad_exception 异常会接手处理, 处理方法如下: 当函数抛出异常规格(exception specification) 以外的异常, bad_exception 就会调用 unexpected(), 通常后者会调用 terminateion() 终止程序. 然而, 如果异常规格中罗列了 bad_exception, 那么任何未罗列的异常在函数 unexpected() 中被代以 bad_exception.

2. C++ 标准程序库发出的异常: C++ 标准库异常总是派生自 logic_error. 用于在程序中避免逻辑错误, 所谓逻辑错误包括违背逻辑前提或者违背 class 的不变性. 例如对函数参数进行额外测试等等

1) 无效参数会抛出 invalid_argument 异常.
2) 超越了最大极限会抛出 length_error 异常.
3) 参数不在预期范围内会抛出 out_of_range 异常. 
4) 专业领域范畴内的错误会抛出 domain_error 异常. 

3. 程序作用域(scope of a program) 之外发出的异常: 用来指出 "不在程序范围内, 且不容易回避" 的事件. 

1) 内部计算错误会抛出 range_error 异常. 
2) 算术运算发生上溢位抛出 overflow_error 异常. 
3) 算术运算发生下溢位抛出 underflow_error 异常. 


只要声明有异常规范, 函数就绝不会抛出异常规格中未列出的异常.

4. 为在头文件:

exceptionbad_exception 异常位于头文件 <exception> 中
bad_alloc 异常位于头文件 <new> 中
bad_castbad_typeid 位于头文件 <typeinfo> 中
其它异常位于头文件 <stdexcept> 中.


5. 异常的使用方法:

1) 任何标准异常均可通过制定一个字符串构造.

2) 任何标准异常均可通过 what() 成员获得一个以 '\0' 结束的字符串表述信息. 此信息即构造时所指定的信息.

 

#include <iostream>
#include <exception>
using namespace std;

void foo() throw(exception)
{
throw exception("information");
}

int main(int argc, char* argv[])
{
try
{
foo();
}
catch (exception e)
{
cerr <<e.what() <<endl;
}

return 0;
}

 

4.1 Pairs 对组 <utility>

struct pair
{ // store a pair of values
typedef pair<_Ty1, _Ty2> _Myt;
typedef _Ty1 first_type;
typedef _Ty2 second_type;

pair()
: first(_Ty1()), second(_Ty2())
{ // construct from defaults
}

pair(const _Ty1& _Val1, const _Ty2& _Val2)
: first(_Val1), second(_Val2)
{ // construct from specified values
}

template<class _Other1,
class _Other2>
pair(const pair<_Other1, _Other2>& _Right)
: first(_Right.first), second(_Right.second)
{ // construct from compatible pair
}

void swap(_Myt& _Right)
{ // exchange contents with _Right
if (this != &_Right)
{ // different, worth swapping
_STD _Swap_adl(first, _Right.first);
_STD _Swap_adl(second, _Right.second);
}
}

_Ty1 first; // the first stored value
_Ty2 second; // the second stored value
};

template<class _Ty1,
class _Ty2> inline
bool operator<(const pair<_Ty1, _Ty2>& _Left,
const pair<_Ty1, _Ty2>& _Right)
{ // test if _Left < _Right for pairs
return (_Left.first < _Right.first ||
!(_Right.first < _Left.first) && _Left.second < _Right.second);
}

上述代码摘自 vs 2010 中 utility 头文件. 仅供学习参考.

  1. pair 被定义为 struct, 因此其所有的成员都是可见的.
  2. 使用 default constructor 定义一个对象时, pair<T1, T2> 会使用 T1, T2default constructor 初始化 first 和 second.
  3. operator< 中, first 比 second 具有更高的优先级.
  4. make_pair 的目的是简化 pair<T1, T2> 的使用. 因此函数模板可以自动推导参数类型.
  5. make_pair 中, 整数会被当作 int, 小数会被当作 double(有特殊说明符除外).

 

4.2 class auto_ptr <memory>

C++ 标准库提供的 auto_ptr 是一种智能指针(smart pointer), 帮助程序员防止 "被异常抛出是发生资源泄漏". 

智能指针应该保证, 无论何种情况下, 只要自己被摧毁, 就一定连带释放其所指资源.

try
{
A a;
throw exception("opps"); // 'a' will destruct before throw.
// Thus, none resource will leak.
}
catch (...)
{
// Exception handling...
}

如此便解决了由于异常导致的资源泄漏问题.

auto_ptr 要求一个对象只能有一个拥有者, 严禁一物二主.
  因为 auto_ptr析构时删除其所指向的对象, 所以这个对象绝对不能同时被其它对象拥有.
  每次赋值或拷贝构造 auto_ptr 对象时, 会发生 ownership 转移: 先前的拥有者将资源拥有权转移给新对象, 同时先前的拥有者失去了拥有权, 拥有者一旦失去了拥有权, 就只剩下一个 NULL 指针. 不能对失去拥有权的 auto_ptr 执行提取操作, 但这是程序员的责任.

auto_ptr<int> p1(new int(1)), p2(new int(2));

p1 = p2; // 1. delete object owned by 'p1'.
// 2. transfer ownership from 'p2' to 'p1'.



auto_ptr 不允许你使用一般指针惯用的赋值初始化方式. 

// constructor
explicit auto_ptr(T *ptr = 0) throw();
auto_ptr<int> p1(new int(1));        // Ok.
auto_ptr<int> p2 = new int(2); // Error.


 

起点和终点:

  当函数返回 auto_ptr 时, ownership 被返回, 拥有权转移给调用端. 这是资源的起点.
  如果 auto_ptrby value 的方式作为参数传递给函数. 函数的参数获得 ownership, 当函数退出时被释放. 因此是资源的终点.

传递 auto_ptr: 
  通过 pass by reference 传递 auto_ptr 会使 "拥有权" 的概念变得难以捉摸, 你无法确定 ownership 是否被转交. 所以 pass by reference 方式传递 auto_ptr非常糟糕的设计, 应该全力避免.
  传递一个 auto_ptr 对象, 应当使用 const auto_ptr 作为函数参数, 可以终结 ownership 转移. 这意味着 const auto_ptr 不能更改拥有权.

使用 auto_ptr 作为成员:
  只有当对象被完整的构造成功, 才有可能于将来调用其析构函数. 这造成了资源泄漏的隐患: 如果第一个 new 成功了, 第二个 new 失败了, 就会造成资源泄漏.

class A
{
public:
A() : p1(new int(1)), p2(new int(2)) // If 'p2' initialize fail,
// 'p1' will Not release!
{}
~A()
{
delete p1;
delete p2;
}

int *p1, *p2;
};

因此最好使用 auto_ptr 作为其成员:

class A
{
public:
A() : p1(new int(1)), p2(new int(2)) // If 'p2' initialize fail,
// 'p1' will be released!
{}
~A(){}

auto_ptr<int> p1, p2;
};


auto_ptr 错误运用:

  1. auto_ptr 之间不能共享拥有权. 否则当一个对象析构后另外一个对象将会指向一个非法的地址.
  2. 不存在array 设计的 auto_ptr. 因为 auto_ptr 是通过 delete 而非 delete [] 来释放其所拥有的资源. C++ 标准库并未提供针对 array 设计的 auto_ptr.
    auto_ptr<int> p(new int[10]);     // Error! Memory Leak!
  3. auto_ptr 并非引用计数(reference counting) 型指针. 因此, 并非当且仅当最后一个引用对象析构才会销毁资源.
  4. auto_ptr 不满足 STL 标准容器对元素的基本要求, 因为在拷贝和赋值动作后, 原本的 auto_ptr 和新产生的并不相等.
  5. auto_ptr 应该定义为对象的智能指针, 而非指针的智能指针. 即: auto_ptr<int>正确的, 而 auto_ptr<int *> 可能不是你想要的.

使用:

类型:

auto_ptr::element_type

  auto_ptr 所拥有对象的类型.

构造析构函数:

auto_ptr::auto_ptr() throw()

  不拥有任何对象, 其值初始化为 0.

explicit auto_ptr::auto_ptr(T *ptr) throw()

  拥有 ptr 所分配的对象.
  此后, *this 是 ptr 所指对象的唯一拥有者.
  ptr 如果不是 NULL 指针, 就必须是 new 的返回值. 因为 ~auto_ptr() 会使用 delete 对其进行删除.
  不能使用 new [] 生成的 array 作为初值. 当你需要 array 时, 请考虑 STL 容器.

auto_ptr::auto_ptr(auto_ptr &) throw()
template <class U> auto_ptr::auto_ptr(auto_ptr<U> &) throw()

  针对 non-const values 而设计的 copy constructor.
  生成一个 auto_ptr 并在入口处夺取 ap 所拥有对象的所有权.
  此操作完毕后, 参数不再拥有任何对象, 其值变为 NULL 指针. 此函数不同于一般的 copy constructor, 这个操作改变了原对象.

auto_ptr &auto_ptr::operator=(auto_ptr &) throw()
template <class U> auto_ptr &auto_ptr::operator=(auto_ptr<U> &) throw()

  如果自身拥有对象, 那么进入时先释放(delete) 自身对象, 然后获得 ap 所拥有的对象, 于是 ap 所拥有的对象拥有权就移交给了 *this.
  其它同 copy constructor.

auto_ptr::~auto_ptr() throw()

  如果 auto_ptr 拥有某个对象, 此处调用 delete 删除之.

数值存取:

T *auto_ptr::get() const throw()

  返回 auto_ptr 所拥有对象的地址.
  并不改变拥有权.

T &auto_ptr::operator*() const throw()

  返回 auto_ptr 所拥有的对象.
  如果 auto_ptr 未拥有任何对象, 此调用的行为未定义.

T *auto_ptr::operator->() const throw()

  返回 auto_ptr 所拥有对象的一个成员.
  如果 auto_ptr 未拥有任何对象, 此调用的行为未定义.

数值操作:

T *auto_ptr::release() throw()

  放弃 auto_ptr 原先对象的拥有权, 但并不释放对象.
  返回原先对象的地址.
  设置 ap 为 NULL.

void auto_ptr::reset(T *ptr = 0) throw()

  以 ptr 重新初始化 auto_ptr.
  如果 auto_ptr 之前拥有对象, 则此动作前先删除之.

 

4.3 数值极限(Numeric Limits) <limits>

一般来说, 数值类别(Numeric types) 的极值是一个与平台相关的特性.

C++ 标准程序库通过 template numeric_limits 提供这些极值, 取代传统 C 语言所采用的预处理常数(preprocessor constants).

  • radix 可以用于测试当前计算机的进制.

所有的数据成员, 如果不是 const 就是 static, 这么一来其值就可以在编译期间确定.

 

4.4 辅助函数 <algorithm>

算法程序库内含 3 个辅助函数.

  1. 两值中挑出较
     
    namespace std{
    template <typename T>
    inline const T &min(const T &a, const T &b)
    {
    return b < a ? b : a;
    }

    template <typename T, typename Compare>
    inline const T &min(const T &a, const T &b, Compare comp)
    {
    return comp(b, a) ? b : a;
    }
    }

    如果两值相等, 通常返回第一值. 不过最好不要依赖这一点.
  2. 两值中挑出较
     
    namespace std{
    template <typename T>
    inline const T &max(const T &a, const T &b)
    {
    return a < b ? b : a;
    }

    template <typename T, typename Compare>
    inline const T &max(const T &a, const T &b, Compare comp)
    {
    return comp(a, b) ? b : a;
    }
    }

    如果两值相等通常返回第一值. 不过最好不要依赖这一点.
  3. 交换两个值
    namespace std{
    template <typename T>
    inline void swap(T &a, T &b)
    {
    T tmp(a);
    a = b;
    b = tmp;
    }
    }
    swap 依赖 copy constructor 以及 operator= 函数.
 

4.5 辅助性 "比较操作符" (Comparision Operators) <utility>

比较操作符共有 6 种:

namespace std {
namespace rel_ops {
template <typename T>
inline bool operator!=(const T &x, const T &y) {
return !(x == y);
}

template <typename T>
inline bool operator>(const T &x, const T &y) {
return y < x;
}

template <typename T>
inline bool operator<=(const T &x, const T &y) {
return !(y > x);
}

template <typename T>
inline bool operator>=(const T &x, const T &y) {
return !(x < y);
}
}
}

 

因此, 只需定义 == 与 < 即可获得完整的比较操作符定义.
class A{};
bool operator==(const A &a, const A &b){ return true; }
bool operator<(const A &a, const A &b){ return true; }

void foo()
{
using namespace std::rel_ops;
A a, b;

if (a >= b)
{}
}

 

有些版本实现采用

namespace std {
template <typename T1, typename T2>
inline bool operator!=(const T1 &x, const T2 &y) {
return !(x == y);
}
}

但这并非 C++ 标准库所支持的做法, 因此不可移植.

4.6 头文件 <cstddef> 和 <cstdlib>

<cstddef> 定义了很多预定义值.

C 语言中的 NULL 通常被定义为 (void *)0. 在 C++ 中这并不正确, NULL 的类型必须是个整数类型, 否则你无法将 NULL 赋值给一个指针. 这是因为 C++ 并没有定义从 void * 到任何其它类型的自动转换操作.

 

<cstdlib> 

  • exit() 会销毁所有 static 对象, 将所有缓冲区(buffer) 清空(flushes), 关闭所有 I/O 通道(channels), 然后终止程序(之前会调用经由 atexit() 登录的函数). 如果 atexit() 登陆的函数抛出异常, 就会调用 terminate(). 
  • abort() 会立刻终止函数, 不作任何清理(clean up) 工作
  • 这两个函数都不会销毁局部对象(local objects), 因为堆栈辗转开展动作(stack unwinding) 不会被执行起来. 为确保所有局部对象的析构函数获得调用, 你应该运用异常(exceptions) 或正常返回机制, main() 离开.

 

5.1 STL 组件

每种容器都提供了自己的迭代器, 而这些迭代器了解该容器的内部结构, 所以能够知道如何正确进行. 

STL 的基本观念就是将数据和操作分离. 迭代器在两者之间充当粘合剂, 使任何算法都可以和任何容器交互运作

 

5.2 容器 ( Containers )

序列式容器: vector, deque, list 

  每个元素均有固定的位置 --- 取决于插入时机和地点, 和元素值无关. 如果你一追加方式对一个群集置入 6 个元素, 他们的排列次序将和置入次序一致.

vector

  random access.
  在 array 尾部附加元素或移除元素均非常迅速.

  在其他位置插入比较费时, 因为为了保持原本元素顺序, 其后所有元素都需要移动.

deque (发音类似 "check". 是 "double-eded queue" 的缩写. )

  random access.
  无论在尾部或头部安插匀速都非常迅速.  
  中间插入元素比较费时. 原因也是需要移动其后的元素.

list (doubly linked list)

  不提供随机存取.
  在任何位置上执行安插或删除动作都非常迅速.  

 

关联式容器:  set, multiset, map, multimap

  依据特定的排序准则算法, 自动为其元素排序, 省却情况下以 operator< 进行比较. 如果你将 6 个元素置入这样的群集中, 他们的位置取决于元素的值, 和插入次序无关
  实际产品中, 所有这些关联式容器通常由二叉树实现

set

  元素唯一.

multiset

  元素可以重复.

map

  以 key/value pairs 组成. key 唯一.
  默认以 key 进行排序.

multimap

  元素可以重复.

 

容器配接器

stack

  元素采用 LIFO 管理策略.

queue

  元素采用 FIFO 管理策略.

priority_queue

  下一个元素永远有最高的优先级.

 

5.3 迭代器 (iterator)

迭代器是个所谓的 "smart pointer", 具有遍历复杂数据结构的能力
迭代器用来指明容器中的一个位置.
容器的某些操作有副作用, 会导致其上的迭代器失效.

设计方法学:

  泛型程序的设计概念是: 所有操作行为都是用相同的接口, 虽然他们的类型不同.  

不必对空区间采取特殊处理手法, 空区间的 begin() == end().
container::iterator: 模式
container::const_iterator: 模式. 

 

双向迭代器:

  可以双向进行. list, set, multiset, map, multimap

随机存取迭代器:

  不但可以双向进行, 还可以随机读写. vector, deque, string

为了撰写尽可能与容器类型无关的泛型程序代码, 最好不要使用随机存迭代器的特有操作.

 

设计时的思考:

  为了写适用于任何容器的泛型程序代码, 你应该使用 operator!= 而非 operator<. 不过如此一来, 程序代码的安全性可能有损失, 因为如果 pos 的位置在 end() 的后面, 你未必便能发现. 究竟使用哪种方式, 取决于当时情况, 取决于个人经验, 取决于你.

 

5.4 算法 (algorithm) <algorithm>

设计方法学: 

  算法并非容器类型的成员函数, 而是一种搭配迭代器使用的全局函数. 这样把算法数据划分开来, 在透过特定的接口彼此互动, 就是泛型编程思维模式. 当然这需要付出代价: 首先, 用法有失直观, 其次, 某些数据结构和算法之间不能兼容

 

区间:

  所有算法处理的都是半开区间(half0open ranges) --- 含括起始元素位置但不含结尾元素位置. 
  半开区间的优点主要是单纯, 可避免对空群集做另外特殊处理.  

  调用者必须保证量参数定义的区间是有效的(valid). 所谓有效是指: 从起点出发, 逐一前进, 能够到达终点. 程序员必须自己确保两个迭代器隶属于同一容器. 否则引发未定义行为, 所谓未定义行为指: 任何 STL 实现, 均可自由选择合适的方式处理此类错误.

  如果某个算法用来处理多个区间, 那么当你调用它时, 务必确保第二区间所拥有的元素个数, 至少和第一个区间内的元素个数相同.

  关联式容器不可能被当作覆写型算法的操作目标.

 

5.5 迭代器配接器 (iterator adapter)

三种迭代器配接器:

  • insert iterator
  • stream iterator
  • reverse iterator

 

Insert Iterator

  使算法以安插而非覆写的形式运作.
  它会使目标区间的大小自动增长.
  "单步前进" 无任何动静, 是一个空操作(no-op).

  1. Back inserters: vector, deque, list
    copy(coll1.begin(), coll1.end(),             // Source.
    back_inserter(coll2)); // Destination.
    Back inserters 内部调用 push_back(). 当然, 只有提供 push_back() 成员函数的容器中, back_inserter 才能派上用场. 
     
  2. Front inserters: deque, list
    copy(coll1.begin(), coll1.end(),             // Source.
    front_inserter(coll3)); // Destination.
    Front inserters 内部调用 push_front(). 当然, 只有提供 push_front() 成员函数的容器中, front_inserter 才能派上用场. 
     
  3. General inserters
    copy(coll1, begin(), coll1.end(),            // Source.
    inserter(coll4, coll4.begin())); // Destination.
    内部调用成员函数 insert() .
    所有 STL 容器都提供了 insert() 成员函数. 因此, 这是唯一可用于关联数组的一种预先定义好的 inserter.


Stream Iterator
  一种用来读写 stream 的迭代器.
  1. istream_iterator<>
  2. ostream_iterator<>
    copy(istream_iterator<string>(cin), istream_iterator<string>(),
ostream_iterator<string>(cout, "\n"));

其中, istream_iterator<string>() 调用了 default constructor, 产生一个代表 "流结束符号"(end-of-stream) 的迭代器. 它代表的意义是: 你不能从中读取任何东西.

 

Reverse Iterator

  将 increment 运算符转换为 decrement 运算, 反之亦然.
  所有容器可以通过 rbegin() 和 rend() 产生 reverse iterator. 

 

5.6 更易型算法 (manipulating algorithm) <algorithm>

manipulating algorithm 指: 删除重排修改元素.

remove: 删除某区间中指定的值.

remove(coll1.begin, coll1.end(),
3);

实际上, remove() 并没有改变群集中元素的数量, 只是符合条件的元素被其后的元素覆盖了.

remove() 返回值正是 "被修改的群集" 经过元素 "移除" 操作后在逻辑上的新终点. 因此可以使用这个新终点作为真正删除元素的起点:

coll1.erase(remove(coll1.begin, coll1.end(), 3), 
coll1.end());

 

更易型算法(remove, resort, modify 等语义的算法) 无法作用于关联式容器, 原因很简单: 如果更易型算法用于关联容器上, 会改变某位置上的值, 进而破坏已序特性. 因此, 为了保证这个原则, 关联式容器的所有迭代器均被声明为指向常量


容器本身可能提供功能相似而性能更佳成员函数. 如果效率是你的最高目标, 你应该永远选择成员函数.
list::remove 会真的移除元素, 而不是移动并覆盖元素. 

 

5.7 自定义泛型函数

template <typename T>
inline void PrintAll(const T &container, const char *delimiter = " ")
{
for (typename T::const_iterator it = container.begin();
it != container.end(); ++it)
{
std::cout <<*it <<delimiter;
}
std::cout <<std::endl;
}


5.9 函数对象 <functional>

所谓函数行为, 就是指可以 "使用小括号传递参数, 介意调用某个东西".

函数对象的优势可以拥有成员函数和对象成员, 因此就有了状态.

 

算法调用配接器的时候, 配接器 bind2nd 将表达式作为第一个参数保存起来, 第二参数作为内部值也保存起来. 然后, bind2nd 将元素的值作为表达式的第一参数, 把先保存的值作为表达式第二参数, 调用表达式.

transform(coll1.begin(), coll1.end(),
coll2.begin(),
bind2nd(multiplies<int>(), 10));

 

5.10 容器内的元素

STL 容器, 迭代器, 算法都是 template 的, 因此可以操作任何类型.

STL 容器元素必须满足以下三个基本要求:

  1. 可透过 copy constructor 进行复制, 副本必须与原本相等.
  2. 可以通过 assignment operator 进行赋值.
  3. 可以通过 destuctor 进行销毁, 此外, C++ 惯例, destuctor 不能抛出异常, 否则会导致程序结束.

以下几个条件, 也应该满足.

  1. 对于序列式容器, 元素的 default constructor 必须可用. 因为我们需要在没有任何处置的情况下, 创建一个非空容器, 或增加容器的元素个数.
  2. 对于某些动作, 必须定义 operator == 以测试相等性. 如果你有搜索需要, 这一点特别重要.
  3. 在关联式容器中, 元素必须定义出排序准则, 缺省情况下是 operator<, 通过函数对象 less<> 被调用.

 

value 语义 vs. reference 语义

所有容器都会建立元素副本, 并返回该副本. 这意味容器内的元素与你放进去的对象相等(equal)同一(identical).

容器中不能使用 auto_ptr, 因为它不符合容器元素所需的基本要求. (拥有权问题)

 

错误处理

STL 的设计原则是效率优先, 安全次之. 原因有二:

错误检验会降低效率, 而速度始终是程序的总体目标.

如果你认为安全重于效率, 你还是可以如愿的, 但是反之则不行.

 

使用 STL 时, 必须满足以下要求:

迭代器务必合法而且有效. 列入当 vector 和 deque 发生元素的 insert 和 erase 或 resort 时, 迭代器可能失效.

一个迭代器如果指向 "逾尾(past-the-end)" 位置, 它并不指向任何对象, 适用于任何容器的 end() 和rend() 所返回的迭代器.

区间(range) 必须合法: 从第一个迭代器出发, 必须可以到达第二个迭代器所指位置.

如果涉及区间不止一个, 第二区间及后继区间必须又有和第一个区间一样多的元素.

覆盖动作中的目标区间必须拥有足够的元素, 否则就采用 insert iterator.

 

C++ 标准程序库为异常处理问题提供了以下基本保证: 

C++ 标准程序库在面对异常时, 保证不会发生资源泄露(resource leak), 也不会与容器的恒常特性(container invariant)发生抵触.

 

如果你需要一个 transaction-safe 的容器, 就用 list 吧.

 

6 STL 容器

共通能力

  1. 所有容器提供的都是 "value semantic".
  2. 都提供可返回迭代器的函数.
  3. 通常 STL 自己不会抛出异常. 如果 STL 容器所调用的使用者自定义操作抛出异常, 会导致各不相同的行为. 甚至可能不能正确的将堆栈辗转开展.

共通操作

  1. 都提供了 default constructor, copy constructor, destructor. 可以使用区间作为容器初值.
  2. size() 返回当前元素数量.
  3. empty() 可能是 size() == 0 的一种便捷方式. empty() 的实现可能比 size() == 0 效率更高.
  4. max_size() 返回容器所能容纳的最大元素数量. 其值随不同实现而不同.

比较(comparison) 操作

两端类型必须相同

两容器内所有元素依序相等, 那么两个容器相等.

采用字典序(lexicography) 顺序比较容器内元素原则判断.

赋值和交换(swap)

赋值时, 源容器所有元素被拷贝到目标容器内, 目标容器所有元素被移除. 所以赋值操作代价比较高.

swap 性能比赋值更优异. 事实上它只交换某些内部指针, 所以时间复杂度是 "常数", 而赋值操作复杂度是 "线型".

 

vector

采用动态数组管理元素, 提供随机存取.

namespace std
{
template <typename T,
typename Allocator = allocator<T> >
class vector;
}

vector 的元素必须是 assignable 和 copyable 的.

能力

常数时间内存取任何元素. 
每次移动调用 assignment 操作符.

大小(size) 和容量(capacity)

vector 性能优异的秘诀之一就是: 分配比其容纳元素更多的内存.

  • size() 是当前元素的数量.
  • max_size() 是系统能够支持最大元素数量.
  • capacity() 在不重新分配内存的情况下, 最大能容纳的元素数量. 只有 vector 和 string 提供了此函数.

一旦重新分配内存, 所有的 references, pointers, iterators 都会失效.

reserve() 会预先分配一定的内存, 并不初始化该内存. 而 vector<>(n) 则会分配内存并使用 default constructor 初始化新建的内存. 关于 reserve()resize() 的讨论: http://wenku.baidu.com/view/9b19b5a5f524ccbff1218400.html

reserve() 不能缩减容量. 这一点和 string 不同.

容量的涨幅可能比你我料想的都要大. 事实上为了防止内存碎片, 在许多实现中即使你不调用 reserve(), 当你第一次安插元素的时候也会一口气配置整块内存(例如 2K). 如果你有一大堆 vectors, 每个 vector 的实际元素却寥寥无几, 那么浪费的内存会相当可观.

删除元素不会缩减内存. 其 references, pointers, iterators 仍然有效. 然而安插操作会使之失效.

异常

at() 是唯一一个抛出异常的函数.

 

vector<bool> 

C++ 标准库专门针对 bool 的 vector 设计了一个特殊版本. 目的是获得一个优化的 vector. vector<bool> 的实现不同, 性能也有可能不同.

vector<bool> 可以动态添加或删除 bits. 如果你需要静态的 bitfield, 应当使用 bitset 而不是 vector<bool>.

其中 m[idx].flip() 中, m[idx] 并非返回 bool, 而是一个内建类型, 称作 proxy. 这个 proxy 有对 bool 自动转换的操作符. 因此, m[idx] 可以直接被当作 bool 类型.

 

deque

发音为 deck. 采用动态数组管理元素, 提供随机存取.

存取元素时, 内部结构会多出一个间接过程. 存取和迭代器速度稍慢一些.

deque 内存大小可缩减, 但是由实现版本而定.

删除元素导致所有 references, pointers, iterators 失效.

首尾插入元素时, pointers 和 references 有效, iterators 失效.

 

list

不支持随机存取.

安插和删除不会造成 references, pointers, iterators 失效.

未提供容量和空间重新分配/预分配函数.

list::remove真实删除节点元素. 而 ::remove 只是用其后面元素覆盖被删除元素.

 

异常

所有 STL 标准容器中, list 对于异常安全性提供了最佳支持. 几乎所有操作都是不成功便成仁.

list::remove(), list::remove_if(), list::unique() 提供保证是有前提的, 就是元素比较(operator==) 或判断式不抛出异常.

 

set/multiset

通常使用红黑树实现.

strict weak ordering

  1. 反对称(antisymmetric)
    如果 x < y 为真, 则 y < x 为假 
  2. 可传递(transitive)
    如果 x < y 为真且 y < z 为真, 则 x < z 为真. 
  3. 非自反(irreflexive)
    x < x 为假

 

能力


自动排序.

上述能力的一个限制就是不能直接改变元素的值, 因为会打乱原有顺序. 因此, 必须先删除原有元素, 再插入新元素.

不能对 set 调用任何 manipulating algorithms, 因为会破坏容器内部顺序. 如 remove, remove 实际上是以一个参数值覆盖被移除的元素.

缺省排序准则时, 两元素的相等性测试使用: if (! (elem1 < elem2 || elem2 < elem1)). (注: 要求 operator < 具有反对称性 antisymmetric)

两个 set 如果排序准则类型不同, 则无法进行比较赋值.

两个 set 排序准则类型相同, 但是算法可以不同:

#include <iostream>
#include <set>
#include <string>
#include <iterator>
#include <algorithm>
using namespace std;

class A
{
public:
A(bool less) : m_less(less){}

bool operator()(char a, char b)
{
return m_less ? a < b : b < a; // 类型相同, 算法不同
}

private:
bool m_less;
};


int main()
{
set<char, A> s1(true), s2(false);

s1.insert(1);
s1.insert(2);
s1.insert(3);
s1.insert(4);

s2.insert(1);
s2.insert(2);
s2.insert(3);
s2.insert(4);

copy(s1.begin(), s1.end(), ostream_iterator<int>(cout, "\n")); // 1 2 3 4
copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, "\n")); // 4 3 2 1
return 0;
}

所有关联式容器, assignment 操作符不仅赋值了元素, 也赋值了排序准则算法.

 

set::insert() 函数

set 提供如下接口:

pair<iterator, bool> insert(const value_type &elem);
iterator insert(iterator pos_hint,
const value_type &elem);
  1. pair 结构中的 first 返回新元素的位置, 或返回现存元素的位置.
  2. second 表示是否安插成功.

multiset 提供如下接口:

iterator insert(const value_type &elem);
iterator insert(iterator pos_hint,
const value_type &elem);

其中, pos_hint 仅作为插入位置的提示, 如果被安插元素的位置恰好紧邻提示位置之后. 那么时间复杂度就会从 "对数" 变为 "分期摊还常数".

 

set::erase() 函数

和 list 不同, erase() 并非取名为 remove(). erase() 返回删除元素的个数, 在 set 下, 返回值非 0 即 1.

特殊的搜索函数

#include <iostream>
#include <algorithm>
#include <iterator>
#include <set>
using namespace std;


int main()
{
set<int> s;

s.insert(1);
s.insert(2);
// s.insert(3);
s.insert(4);
s.insert(5);
s.insert(6);

cout <<"lower_bound(3): " <<*s.lower_bound(3) <<endl; // 4
cout <<"upper_bound(3): " <<*s.upper_bound(3) <<endl; // 4

cout <<endl;

cout <<"lower_bound(5): " <<*s.lower_bound(5) <<endl; // 5
cout <<"upper_bound(5): " <<*s.upper_bound(5) <<endl; // 6
cout <<"equal_range(5): " <<*s.equal_range(5).first <<" " // 5 6
<<*s.equal_range(5).second <<endl;

return 0;
}

 

异常

如果节点构造失败, 容器仍保持原样. 然而, 面对多重元素安插, 其中一个元素抛出异常后无法会无原样.

 

map/multimap

通常以平衡二叉树实现.

 

能力

不能直接改变元素的 key, 因为这回破坏正确的次序. 要修改 key, 必须先删除拥有该 key 的元素, 然后插入新的元素. 从迭代器观点看, key 是常数.

不能对 map 调用任何 manipulating algorithms. 如 remove, remove 实际上是以一个参数值覆盖被移除的元素.

"比较和赋值" 同 set.

map 提供了 operator[], 而 multimap 没有提供.

 

map::insert() 函数

  1. coll.insert(map<int, string>::value_type(1, "one"));
  2. coll.insert(pair<int, string>(1, "one"));
  3. coll.insert(make_pair(1, "one"));

 

map::erase() 函数

coll.erase(key);             // 返回所删除元素的个数.


lower_bound 和 upper_bound 演示

 

#include <iostream>
#include <map>
#include <string>
using namespace std;


int main()
{
multimap<string, string> mm;

mm.insert(make_pair("people", "Richard"));

mm.insert(make_pair("cat", "Tom"));
mm.insert(make_pair("cat", "Bob"));
mm.insert(make_pair("cat", "Jim"));

mm.insert(make_pair("dog", "Jone"));
mm.insert(make_pair("dog", "Scott"));
mm.insert(make_pair("dog", "Tina"));

mm.insert(make_pair("fish", "Silly"));

cout <<"cat: " <<endl;
for (multimap<string, string>::iterator pos = mm.lower_bound("cat");
pos != mm.upper_bound("cat"); ++pos) // lower_boundupper_bound 用法
{
cout <<"\t" <<pos->second <<endl;;
}

return 0;
}

 

序列式容器 vs. 关联式容器

erase() 函数

  • 序列式容器

    iterator erase(iterator pos);                           // 返回后继元素的迭代器, 或者逾尾迭代器.

    iterator erase(iterator beg, iterator end);

     
  • 关联式容器

    void erase(iterator pos);

    void erase(iterator beg, iterator end);

存在这种差别, 完全是为了性能. 在关联式容器中 "搜寻某元素并返回后继元素" 可能颇为耗时.

关联式容器erase() 操作不会导致其它元素的 references, pointers, iterators 失效.

 

只有序列式容器提供了 container::container(size_type num) 预分配内存并初始化元素的构造函数.

 

所有容器assign() 都相当于重新构造容器.

 

其他 STL 容器

框架原则: 开放性封闭(Open-Closeed), 即 "允许扩展, 谢绝修改".

泛化的核心思想是: 行为类似某物, 那么它就是某物!

 

使你的容器 "STL 化" 三种不同方法:

  1. The invasive approach(侵入性做法)
    直接提供 STL 容器的所需接口. 特别是诸如 begin() 和 end() 之类的常用函数. 这种做法需以某种特定方式编写容器, 所以是侵入性的. 
  2. The noninvasive approach(非侵入性做法)
    有你撰写或提供特殊迭代器, 作为算法和特殊容器间的界面. 此一做法是非侵入性的, 它所需的只是 "遍历容器所有元素" 的能力 --- 这是任何容器都能以某种形式展现的能力. 
  3. The wrapper approach(包装法)
    将上述两种方法结合, 我们可以写一个外套类型(wrapper class) 来包装任何数据结构, 并显示出与 STL 容器相似的接口

 

STL 总结

vector

其内部简单, 允许随机存取.

deque

  1. 经常在首/尾增减元素. 
  2. 减少元素时, 容器自动缩减.

list

  1. 经常在中间安插/移除元素.
  2. 以节点为基础, 插入操作如果不成功, 便无效用, 且不会对其他指向其元素的迭代器失效.

set

经常以某个准则搜索元素.

map

以 key/value 处理数据.

 

能力

 

container::swap

  具有常数复杂度, 因此, 如果不再需要容器中的老旧元素, 则应该使用本函数取代赋值动作.

 

迭代器

 

vector::capacity()

  只有 vector 有 capacity() 成员函数.

list::remove(const T &value)
list::remove_if(UnaryPredicate op) 

  只有 list 具有 remove() 和 remove_if() 成员函数.

 

container::resize(size_type num)
container::resize(size_type num, T value) 

  •   如果 size() == num, 则两者皆不生效.
  •   如果 size() < num, 则在容器尾端附加额外元素, 第一种形式通过 default constructor 构造元素; 第二种形式通过 copy constructor 构造元素.
  •   如果 size() > num, 则移除尾端元素. 每个被移除元素的析构函数都会被调用.

 

异常


7. 迭代器 <iterator>

所有容器定义其各自的迭代器类型, 所以你通常不需要专门引入头文件.

有几种特殊的迭代器, 如 reverse iterator 被定义在 <iterator> 中, 但容器本身已经包含了该头文件, 所以通常你不需要专门引入头文件.

 

input iterator

 

只能一次一个向前读取元素.

如果你复制 input iterator, 并使原始 iterator 和新产生的副本都向前读取, 可能会遍历到不同的值.

如果两个 input iterator 占用同一个位置, 则两者相等. 但并不意味着他们存取元素时能够传回相同的值.

 

output iterator


将元素一个个写入.

如果在相同位置上对着同一个 "黑洞" 进行第二次写入, 不能保证这次写入的值会覆盖前一个值.

output iterator 无比较操作. 你无法检验 iterator 是否有效写入动作是否成功.

 

forward iterator

是 input iterator 和 output iterator 的组合. 具有 input iterator全部功能以及 output iterator大部分功能. 原因是: output iterator 无比较操作, 所以无法得知是否到达尾端. 而 forward iterator 需要在提领操作前确保 iterator 有效. 这一点限制导致了 forward iterator 不能实现 output iterator 的全部语义.

 

bidirectional iterator

在 forward iterator 基础上增加了回头遍历的能力.

 

random acees iterator

在 bidirectional iterator 基础上增加了随机存取能力.

 

迭代器辅助函数

void advance(InputIterator &pos, Dist n) <iterator>

使 pos 前进后退 n 步. 

此函数不检查迭代器是否越界. 所以调用 advance 可能导致未定义行为.

advance 对于 random access iterator, 具有常数复杂度. 对于 bidirectional iterator 具有线性复杂度.

 

Dist distance(InputIterator pos1, InputIterator pos2) <iterator>

两个迭代器必须指向同一个容器.

返回值类型有迭代器决定: iterator_traits<InputIterator>::difference_type.

 

void iter_swap(ForwardIterator1 pos1, ForwardIterator2 pos2) <algorithm>

交换迭代器所指向元素的内容.

 

reverse iterator

是一种配接器, 重新定义 increment 和 decrement 运算符.

逆序迭代器实际所指的元素位置(阴影所指), 和逻辑上所指的元素位置(白色区框) 并不一致.

优点

在对 reverse iterator 进行输出的时候, 其实际区间和 iterator 相同:

#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
using namespace std;


int main()
{
vector<int> v;

for (int i = 0; i < 10; ++i)
{
v.push_back(i+1);
}

vector<int>::iterator beg = v.begin();
vector<int>::iterator end = v.begin();
advance(beg, 2);
advance(end, 6);

// Original.
copy(beg, end,
ostream_iterator<int>(cout, " "));

cout <<endl;

// Reverse iterator.
vector<int>::reverse_iterator rbeg(end);
vector<int>::reverse_iterator rend(beg);

copy(rbeg, rend,
ostream_iterator<int>(cout, " "));

cout <<endl;

return 0;
}

 


base() 函数

  将逆序迭代器转换为正常迭代器. 即将实际位置修正为逻辑位置.

#include <iostream>
#include <vector>
#include <iterator>
using namespace std;

int main()
{
vector<int> v;

for (int i = 0; i < 10; ++i)
{
v.push_back(i+1);
}

vector<int>::iterator it = ++v.begin();
vector<int>::reverse_iterator rit(it);
vector<int>::iterator rrit = rit.base(); // 实际位置 => 逻辑位置

// Common iterator.
cout <<"*it: " <<*it <<endl;

// Reverse iterator.
cout <<"*rit: " <<*rit <<endl;

// After ajust.
cout <<"*rrit: " <<*rrit <<endl;

return 0;
}


insert iterator

namespace std{
template <typename InputIterator, typename OutputIterator>
OutputIterator copy(InputIterator from_pos, InputIteartor from_end,
OutputIterator to_pos)
{
while (from_pos != from_end)
{
*to_pos = *from_pos; // inserter 在这里重载了自己的 operator=, 调用了容器不同的成员函数, 如 push_back, push_front 和 insert.
++from_pos;
++to_pos;
}
}
}

将 "赋值" 操作转换为 "安插" 操作.

因此, 我们甚至可以使用:

back_inserter(coll) = 100; 来对 coll 执行 push_back 调用.

 

operator= 实际调用

 

stream iterator

流迭代器是一种迭代器配接器.

ostream iterator 将赋值操作转化为 operator<<.

ostream iterator 没有 end of stream 的概念.

copy(coll.begin(), coll.end(), 
ostream_iterator<int>(cout, "\n"));

 

istream iterator 比 ostream iterator 要复杂一些, 因为算法需要知道何时到达终点. 这个特殊位置使用 end-of-stream 迭代器表示, 由 default constructor 产生.

istream iterator 将赋值操作符转化为 operator>>.

istream iterator  constructor将 stream 打开, 读取第一个元素(陷入等待输入状态). 所以请注意, 在确实需要用到一个 istream 迭代器之前, 别过早定义它.

满足一下条件, 我们便说两个 istream iterator 相等:

  • 两者都是 end-of-stream iterator.
  • 两者都可以进行读取, 并指向相同的容器.

 

 迭代器特性

iterator tag

iterator trait

该结构包含了迭代器相关的所有信息, 为迭代器应具备的所有类型定义提供了一致的接口.

 

编写迭代器泛型函数:

template <typename Forwarditerator>
void shift_left(Forwarditerator beg, Forwarditerator end)
{
typedef typename std::iterator_traits<Forwarditerator>::value_type value_type;

if (beg != end)
{
// Swap
value_type tmp(*beg);
// ....
}
}

 

通过迭代器特性进行特化

template <typename iterator>
void foo(iterator beg, iterator end)
{
foo(beg, end, std::iterator_traits<iterator>::iterator_category());
}

// foo() for bidirectional iterator
template <typename iterator>
void foo(iterator beg, iterator end, std::bidirectional_iterator_tag)
{
...
}

// foo() for random access iterator.
template <typename iterator>
void foo(iterator beg, iterator end, std::random_access_iterator_tag)
{
...
}

 

使用者自定义的迭代器:

C++ 标准库为用户提供了标准的特殊基础类: iterator<>

class MyIter : 
public std::iterator<
std::bidirectional_iterator_tag, // 迭代器类型.
type, // 元素类型.
std::ptrdiff_t, // 距离类型.
type *, // 指针类型.
type & // 引用类型.
>
{};

sample:

#include <iostream>
#include <iterator>
#include <set>
using namespace std;

template <typename Container>
class asso_insert_iterator
: public iterator<std::output_iterator_tag, void, void, void, void>
{
public:
explicit asso_insert_iterator(Container &c) : container(c){}

// Assignment operator
// - inserts a value into the container.
asso_insert_iterator<Container> &operator=(const typename Container::value_type &value)
{
container.insert(value);
return *this;
}

// Dereferencing is a no-op.
asso_insert_iterator<Container> &operator*()
{
return *this;
}

// Dereferencing is a no-op.
asso_insert_iterator<Container> &operator++()
{
return *this;
}

// Dereferencing is a no-op.
asso_insert_iterator<Container> &operator++(int)
{
return *this;
}

protected:
Container &container;
};

template <typename Container>
asso_insert_iterator<Container> asso_inserter(Container &c)
{
return asso_insert_iterator<Container>(c);
}

int main()
{
set<int> s;
asso_insert_iterator<set<int> > it(s);

it = 1; // insert(1)
asso_inserter(s) = 2; // insert(2)
it++; // no-op

return 0;
}

 

8. STL 仿函数(函数对象, function object)

STL 算法仿函数都是 pased by value, 因此不会改变函数对象. 算法当然可以改变仿函数的状态, 但是你无法存取改变其最终状态, 因为你所改变的只不过是仿函数的副本而已.

两个使算法修改仿函数最终状态的方法:

  1. 以 pass by reference 方式传递仿函数.
  2. 运用 for_each 算法的返回值. for_each 算法会将最终的仿函数副本返回.

优点:

  1. 仿函数可以拥有状态. 同一类型不同状态. 见中文版 p.191 sample.
  2. 每个仿函数都有其类型. 可以当作 template 参数传递, 从而指定某种特定行为.
  3. 执行速度比一般函数快.
#include <iostream>
using namespace std;

void foo()
{
cout <<"function foo()" <<endl;
}

class A
{
public:
void operator()()
{
cout <<"operator()" <<endl; // Called!
}
};

int main()
{
A foo; // function object
foo(); // operator foo()

return 0;
}

 

判断式(predicates)

判断式就是返回布尔值(可转换为 bool ) 的一个函数或者仿函数.

 

注意事项:

C++ 标准程序库并未限制算法 "对一个容器元素" 调用函数对象的次数, 因此可能导致同一个函数对象有若干副本被传递给元素. 这回惹来一些麻烦

class Nth
{
private:
int nth;
int count;
public:
Nth(int n) :
nth(n),
count(0) // 这里埋下了隐患!
{}

bool operator()(int)
{
return ++count == nth;
}
};

template <typename ForwIter, typename Predicate>
ForwIter std::remove_if(ForwIter beg, ForwIter end, Predicate op)
{
beg = find_if(beg, end, op); // 这里 op 被当作形参, 副本被改变. 结束后 op 并无改变.
if (beg == end)
{
return beg;
}
else
{
ForwIter next = beg;
return
remove_copy_if(++next, end, beg,
op); // 这里 op 还是形参, 再次被复制构造.
}
}

C++ 并未明确判定式是否可被算法复制一份副本. 因此, 为了获得 C++ 标准程序库的保证行为, 你不应该传递一个 "行为取决于被拷贝次数或被调用次数" 的函数对象. 你一定得保证不能因为函数调用而改变判断式的状态, 你应当将 operator() 声明为 const 成员函数

 

预定义仿函数 <functional>

 

9. STL 算法

C++ 标准程序库 <algorithm>

用于数值处理 <numeric>

常用的仿函数和配接器 <functional>

 

STL 算法采用覆盖(overwrite) 模式而非安插(insert) 模式. 所以调用者必须保证目标区间拥有足够的元素空间.

 

算法命名约定:

  • 尾词 _if:
      如果算法有两种形式, 参数都相同, 但第一种形式的参数要求传递一个值(const reference), 第二形式的参数要求传递一个函数或仿函数(pass by value). 那么第二种形式则算法名有尾词 _if. 如 find_if(). 
  • 尾词 _copy:
      元素不光被操作, 还会被复制到目标区间. 

 

算法分类:

  • 非变动性算法
  • 变动性算法
  • 移除性算法
  • 变序性算法
  • 排序算法
  • 已序区间算法
  • 数值算法

 

非变动性算法

非变动性算法即不改动元素次序, 也不改动元素.

它们通过 input iterator 和 forward iterator 完成工作. 因此可以作用于所有标准容器.

元素统计:

difference_type
count (InputIterator beg, InputIterator end, const T &value)

difference_type
count_if (InputIterator beg, InputIterator end, UnaryPredicate op) 

  • 关联式容器提供了等效的成员函数.
  • 复杂度: 线性.

 

最大/最小值:

InputIterator
min_element (InputIterator beg, InputIterator end) 

InputIterator
min_element (InputIterator beg, InputIterator end, CompFunc op)  

  • op 用来比较两个元素: 
       op(elem1, elem2)
  • 复杂度: 线性.

 

搜索元素:

InputIterator
find (InputIterator beg, InputIterator end, const T &value) 

InputIterator
find_if (InputIterator beg, InputIterator end, UnaryPredicate op) 

  • 如果没有找到匹配的元素, 两种形式都返回 end().
  • 复杂度: 线性.
 
InputIterator
search_n (InputIterator beg, InputIterator end, 
      Size count, const T &value) 
InputIterator
search_n (InputIterator beg, InputIterator end, 
      Size count, const T &value, BinaryPredicate op) 
  • 第一种形式返回第一组 "连续 count 个元素值全等于 value" 的位置.
  • 第二种形式返回第一组 "连续 count 个元素造成一下二元判断式结果为 true" 的元素位置: op(elem, value)
  • 如果没有找到合适的元素, 两种形式都返回 end().
  • 复杂度: 线性.
 
ForwardIterator
search (ForwardIterator1 beg, ForwardIterator1 end,
    ForwardIterator2 searchBeg, ForwardIterator2 searchEnd)
ForwardIterator
search (ForwardIterator1 beg, ForwardIterator1 end,
    ForwardIterator2 searchBeg, ForwardIterator2 searchEnd
    BinaryPredicate op)
  • 搜索第一个子区间. 两个区间类型可以不一致.
  • 如果没有符合条件区间, 两种形式都返回 end().
  • 复杂度: 线性.
 
ForwardIterator
find_end (ForwardIterator1 beg, ForwardIterator1 end,
      ForwardIterator2 searchBeg, ForwardIterator2 searchEnd)
ForwardIterator
find_end (ForwardIterator1 beg, ForwardIterator1 end,
      ForwardIterator2 searchBeg, ForwardIterator2 searchEnd
      BinaryPredicate op)
  • 搜索最后一个子区间. 两个区间类型可以不一致.
  • 如果没有符合条件的区间, 两种形式都返回 end().
  • 复杂度: 线性.
 
ForwardIterator
find_first_of (ForwardIterator1 beg, ForwardIterator1 end,
    ForwardIterator2 searchBeg, ForwardIterator2 searchEnd) 
ForwardIterator
find_first_of (ForwardIterator1 beg, ForwardIterator1 end,
    ForwardIterator2 searchBeg, ForwardIterator2 searchEnd
    BinaryPredicate op) 
  • 返回第一个 "即在第一个区间出现, 又在第二个区间出现" 的元素位置, 两区间类型可以不同.
  • 第二种形式返回 op(elem, searchElem) 结果为 true 的第一个元素位置.
  • 如果没有符合条件的区间, 两种形式都返回 end().
  • 复杂度: 线性.
 
 

变动性算法

直接改变元素的值, 或者在复制到另一区间过程中改变元素值.


UnaryProc

for_each(InputIterator beg, InputIterator end, UnaryProc op)

  • 返回 op 的一个副本, 这个副本是 op 在经过多次调用后的结果对象.
  • op 的任何返回值被忽略.
  • 复杂度: 线性.

 

移除性算法

移除算法只是在逻辑上移除元素, 方法是: 将不需要被移除的元素往前覆盖应被移除的元素. 因此它并不改变操作区间内的元素个数, 而是返回逻辑上的新终点位置.

 

变序性算法

通过元素的赋值交换, 改变元素的顺序.

 

排序算法

事实上, 排序算法的复杂度通常低于线性算法, 而且需要动用随机存取迭代器.

 

 

已序区间算法

 

数值算法

 

STL 算法共性

  • 除 for_each() 外, 所有传入算法的函数或对象都不应该改变原容器的值.
  • 所有 Predicates, 都不应该在运行的时候改变自身状态. 因此, 应声明为 const. 理由是 STL 没有规定算法对 Predicates 进行复制的次数限制, 详见 P.302 例子.

 

10. 特殊容器

标准容器配接器

  • stack
  • queue
  • priority queue
特殊容器
  • bitset

stack <stack>

之所以选择 deuqe, 是因为 deque 移除元素时会释放内存, 并且不必再重新分配时复制全部元素.

任何序列式容器都可支持 stack, 只要它们支持 back(), push_back(), pop_back() 等动作.

空容器调用 pop() 会导致未定义行为.

 

queue <queue>

之所以选择 deque, 很显然, deque 在头部移除元素时不会导致其后元素的复制或者其它迭代器失效.

任何序列式容器都可支持 stack, 只要它们支持 front(), back(), push_back(), pop_front() 等动作.

空容器调用 front() 和 back() 会导致未定义行为.

 

priority_queue <queue>

内部进行自动排序, 默认以 less 进行排序.

pop() 弹出 "优先级最高" 的元素. 如果以 less 排序, 那么就是 "数值最大" 的元素. 如果同时存在多个数值最大的元素, 无法确定究竟哪一个会选入.

内部使用 STL heap 算法. 

容器必须是序列式容器, 且支持随机存取.

 

bitset <bitset>

bitset 是一个容易抛出异常的特殊容器. 访问超过其内部容量的索引会抛出 out_of_range 异常; 返回值无法表示结果会抛出 overflow_error 异常.

bitset<bits>::bitset(unsigned long value);                              // bits 不能表示则忽略, 否则补 0.
explicit bitset<bits>::bitset(const string &str); // 如果 'str' 中还有既不是 '0' 也不是 '1' 的字符, 抛出 invalid_argument 异常
bitset<bits>::bitset(consts string &str, string::size_type str_idx); // 'str_idx' > 'str.size()' 则抛出 out_of_range 异常
bitset<bits>::bitset(consts string &str, string::size_type str_idx
string::size_type str_num);

size_t bitset<bits>::count() const

  统计 '1' 的个数.

bool bitset<bits>::any() const

  任意位为 '1' 则返回 true.

bool bitset<bits>::none() const

  所有位都为 '0' 则返回 true.

bool bitset<bits>::test(size_t idx) const

  返回 'idx' 上的值. 如果 'idx' >= size() 则抛出 out_of_range 异常.

bitset<bits>& bitset<bits>::set(size_t idx)

  设置 'idx' 位.

bitset<bits>& bitset<bits>::reset()

  置为 false.

bitset<bits>& bitset<bits>::flip()

  反转位.

bitset<bits>::reference bitset<bits>::operator[](size_t idx)

  返回 'idx' 位的一个 proxy. 该 proxy 可以自动转换为 bool.

bitset<bits> bitset<bits>::operator &= (const bitset<bits> &b)

bitset<bits> bitset<bits>::operator |= (const bitset<bits> &b)

bitset<bits> bitset<bits>::operator <<= (const bitset<bits> &b)

bitset<bits> bitset<bits>::operator >>= (const bitset<bits> &b)

  位操作. 无法表示的位忽略. 缺少的位补 0.

unsigned long bitset<bits>::to_long() const

  返回 unsigned long 形式的非负整数结果. 如果 unsigned long 不足以表示, 抛出 overflow_error 异常.

string bitset<bits>::to_string() const

  返回二进制形式的 string 字符串, 字符顺序按照 bitset 的索引由高至低排列.

ostream& operator<<(ostream &os, const bitset<bits> &b)

  以 to_string() 形式输出.

ostream& operator>>(ostream &os, const bitset<bits> &b)

  以 '0'/'1' 进行输入.

 

容器配接器共性

都可以通过其内部容器初始化.

#include <iostream>
#include <queue>
using namespace std;

void main()
{
deque<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i+1);
}

queue<int, deque<int>> q(v); // 'queue<int>' is ok.

while (!q.empty())
{
cout <<q.front() <<" ";
q.pop();
}
cout <<endl;
}

 

都具备 value_type, size_type, container_type 内置类型.

 

11. string <string>

字符串的一般接口并不依赖 STL 概念.

string 的容量概念类似于 vector. 如: max_size(), capacity(), size(), resize(). 但是 string::reserve 是一种非强制性缩减请求(nonbinding shrink request), 而 vector::reserve 决不会释放已分配的内存. C++ Standard 规定, 惟有在相应 reserve() 调用时, 容量才有可能缩减. 因此, "字符被删除或者改变" 都不会影响指向其元素的 references, pointers, iterators.

string 对象的字符串尾部并没有一个特殊字符 '\0'. '\0' 在 string 中不具备特殊含义.

如果搜寻失败, 返回值是 string::npos. 请特别注意, 凡是涉及索引的比较, 应该使用 string::size_type 类型与之比较或赋值. 避免这种错误的办法之一就是直接检验搜寻是否失败. if (string::npos == s.find(...))

涉及到索引作为参数的函数, 如果索引值 >= 实际字符数, 则抛出 out_of_range 异常.

涉及到增加 string 长度的操作, 如果字符太多, 超过 max_size, 则抛出 length_error 异常.

涉及到字符数量被用作参数, string::npos 相当于指名 "剩余所有字符".

STL  搜索算法的命名方式和 string 搜索算法的命名方式非常不同.

尽可能使用 swap 代替赋值操作, 因为前者具有常数复杂度.

 

size() 和实际占用内存无关.

 

c_str() 返回 C-strings 形式的字符串, 末尾添加 '\0'.

data() 以及 copy() 并未在末尾追加 '\0'.

 

c_str() 和 data() 返回值的有效期在下一次调用 non-const 成员函数时即告终止. 因此, 最好不要保存这两个函数的返回值.

c_str() 和 data() 的返回值不能被调用者释放.

 

 operator[] 的 const 版本, 最后一个字符的后面位置也是有效的, 其值是该类型的默认值. 对于 char, 该值是 '\0'.

 

push_back 的参数形式类似于 operator+=; 然而 append 类似于在末尾 assign.

 

string &string::insert(size_type idx, size_type num, char c)
void string::insert(iterator pos, size_type num, char c)

 

这两个函数构成重载的形式, 可能导致模棱两可. 如果你穿入 NULL 作为第一个参数, 既可以被当作 unsigned 调用第一种形式; 也可以被当作 char * 调用第二种形式. 因此, 你应该明确告知:

s.insert(0, 1, ' ');                                // Error.
s.insert(std::string::size_type(0), 1, ' '); // Ok.

 

 

getline(istream &is, string &str, char c) 函数读取所有字符, 包括开头的空白符, 直道遇到分行符或者 end-of-file.

 

12. valarray

valarray 的 constructor 第一个参数是初值, 第二个参数是数量. 而所有 STL 容器, 第一个参数是数量, 第二个是初值.

若干 valarray 进行运算的元素数量不同, 运算结果无定义.

所有涉及元素求值的成员函数, 如果容器为空, 结果无定义.

所有索引值, 由调用者保证有效性.

unary-opconst 成员函数, 而 binary-oplogic-op不是.

 

valarray 最重要的两个特性:

  • 联合运算.
               
  • 切片.

 

联合运算:

  将两个容器内相同位置的元素进行某种操作, 结果返回并保存在第三个容器中的相同位置.

    valarray<int> a(12), b(12), c(12);

a = b + c; // a[0] = b[0] + c[0]
// a[1] = b[1] + c[1]
// a[2] = b[2] + c[2]
// ......
// a[11] = b[11] + c[11]

切片:

  将一维数组看作一个多维数组, 或看作一个矩阵.

    valarray<int> a(12), b(12), c(12);

a[slice(0, 4, 3)] = valarray<int>(b[slice(1, 4, 3)]) * valarray<int>(c[slice(2, 4, 3)]);


成员函数:

valarray::valarray()

explicit valarray::valarray(size_t num)

valarray::valarray(const T &value, size_t num)

valarray::valarray(const T *array, size_t num)

valarray::valarray(const valarray &va)

  构造一个 valarray 对象.

 

valarray &valarray::operator= (const valarray &va)

  如果 va 大小和 *this 不同, 则导致未定义行为.

valarray &valarray::operator= (const T &value)

  将 value 赋值给 *this 所有元素.

 

size_t valarray::size() const

  返回当前元素数量.

void valarray::resize(size_t num)

void valarray::resize(size_t num, T value)

  重新设置元素数量, 多余的截取, 缺少的使用 T 的 default constructor 构造.

  使指向元素的所有 pointers, references 失效.

 

T valarray::min() const

T valarray::max() const

  返回最小/最大元素值.

  元素以 operator< operator> 比较, 因此元素必须支持这两个操作.

  容器为空, 结果无定义.

T valarray::sum() const

  求和.

  元素必须支持 operator+=.

  容器为空, 结果无定义.

 

valarray valarray::shift(int num) const

  位移. 

  0 < num 则左移动, 所有元素索引减小; 0 > num 则向右移动, 所有元素索引增大.

valarray valarray::cshift(int num) const

  循环移动.

 

valarray valarray::apply(T fun(T) ) const

valarray valarray::apply(T fun(const T &) ) const

  返回新的 valarray, 其内容是原 valarray 的所有元素以 fun() 处理后的结果.

 

T &valarray::operator[](size_t idx)

T valarray::operator[](size_t idx) const

  存取元素.

slice_array<T> valarray::operator[](slice s)

    int arr[] = {0,1,2,3,4,5,6,7,8,9};
valarray<int> va(arr, 10);

valarray<int> res = va[slice(0, 4, 3)]; // va[0], va[3], va[6], va[9]

 

gslice_array<T> valarray::operator[](gslice gs)

    int arr[] = {
0, 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,28,29,
};
size_t arr_x[] = {2, 4};
size_t arr_y[] = {10, 3};
valarray<int> va(arr, 100);
valarray<size_t> vx(arr_x, 2), vy(arr_y, 2); // 这里必须定义 size_t 类型的 valarray.

valarray<int> res = va[gslice(2, vx, vy)]; // [2] [5] [8] [11]
// [12] [15] [18] [21]

mask_array<T> valarray::operator[](const valarray<bool> &va)

    int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
valarray<int> va(arr, 10);

valarray<int> res = va[va > 7]; // [8] [9]

indirect_array<T> valarray::operator[](const valarray<size_t> &va)

    int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
valarray<int> va(arr, 10);

size_t arr_pos[] = {1, 5, 8, 2, 1}; // 无序, 可以重复.
valarray<size_t> pos(arr_pos, 5); // 这里必须定义 size_t 类型的 valarray.

valarray<int> res = va[pos]; // [1] [5] [8] [2] [1]

  创建切片.  

 

valarray valarray::unary-op() const

  • operator+
  • operator-
  • operator~
  • operator!

 

  每个元素会调用相应的 unary-op(), 处理后返回新的 valarray.

  其中, operator! 会返回 bool 类型的 valarray.

valarray valarray::binary-op(const valarray &va1, const valarray &va2)

valarray valarray::binary-op(const valarray &va, const T &value)

valarray valarray::binary-op(const T &value, const valarray &va)

  • operator+
  • operator-
  • operator*
  • operator/
  • operator%
  • operator^
  • operator&
  • operator|
  • operator<<
  • operator>>

  每一对元素调用 binary-op() 处理, 返回新的 valarray.

  两个 valarray 元素数量不同, 结果无定义.

valarray valarray::assign-op(const valarray &va1, const valarray &va2)

valarray valarray::assign-op(const valarray &va, const T &value)

  • operator +=
  • operator -=
  • operator *=
  • operator /=
  • operator %=
  • operator ^=
  • operator &=
  • operator |=
  • operator <<=
  • operator >>=
  结果直接保存在 *this 中.
  *this 和 va 元素数量不同, 结果无定义.

valarray<bool> valarray::logic-op(const valarray &va1, const valarray &va2)
valarray<bool> valarray::logic-op(const valarray &va, const T &value)
valarray<bool> valarray::logic-op(const T &value, const valarray &va)

  • operator ==
  • operator !=
  • operator <
  • operator <=
  • operator >
  • operator >=
  • operator &&
  • operator ||

  返回 valarray<bool> 作为结果.

 

13. I/O

IOStream 程序库严格按照 "职责分离" 的原则设计.

 

<iosfwd> 内含 stream classes 的前置声明. 如 ostream 等等.

<stream> 内含 basic_streambuf<> 的定义.

<istream> 内含 basic_istream<> 和 basic_iostream 的定义.

<ostream> 内含 basic_ostream<> 的定义.

<iostream> 内含全局性的 stream 对象, 如 cin(stdin), cout(stdout), cerr(stderr 无缓冲), clog(stderr 有缓冲).

 

ios_base 定义了 stream classes 所有 "与字符类型及其相应的字符特性(traits) 无关" 的属性. 主要包括状态格式标志等组件和函数.

basic_ios<> 定义 "与字符类型及其相应的字符特性(traits) 相关" 的 stream classes 共同属性. 

basic_istream<> 和 basic_ostream<> 分别定义出用于 读/写 的对象.

basic_iostream<> 用来定义既可读又可改写的对象.

basic_streambuf<> 时 IOSream 程序库的核心, 其具体表现和 basic_ios 一致, 负责实际的读写操作.

 

state

goodbit:  意味着其它位均被清为 0.

eofbit: 读取最后一个字符并未设立, 但再次试图读取, 就会导致 eofbit 和 failbit 同时设立.

failbit: 某项操作未完成, 但 stream 整体 OK. 如读入格式错误.

badbit: 原因不明或丢失数据的严重错误. 如指向 "某个文件起点" 的更前方.

 

如果某些标志被 clear() 或 setstate() 设立, 便可能抛出异常(由 ios_base::exceptions() 所设置).

C 语言可以在 "格式错误" 发生后仍然读入字符, 而 C++ 不同: 如果设置了 failbit, 除非显示予以清除, 否则无法进行下一个操作.

 

operator void* ()

operator ! ()

while (cin >>str);      // operator void* ()
if (! cin) // operator ! ()
{}

为了方便使用, 所以重载了转换操作符.

使用 "转换为 bool" 的方式, 会引起编程风格的争论, 通常使用诸如 fail() 成员函数可以使程序更加清晰可读.

std::cin >>x;
if (std::cin.fail()) // Best practice.
{...}

 

无格式 I/O 函数

如果发生异常, 无论源自某个被调用函数, 或因为某个状态标志被设立, badbit 标志均被设立.

 

istream &istream::ignore()
istream &istream::ignore(streamsize count)
istream &istream::ignore(streamsize count, int delim) 

  1.   第一中形式忽略一个字符
  2.   第二中形式忽略 count 个字符, 输入的回车也算一个字符.
  3.   第三种形式忽略 count 个字符, 或者在 [0, count] 遇到 delimiter 或 eof, 则忽略 delimiter 或 eof 以及其前面的字符.
cin.ignore((numeric_limits<streamsize>::max)(), '\n');    // 我们常用这样的方式忽略一行剩下的所有字符.
cin.ignore((numeric_limits<streamsize>::max)()); // 这样放弃 cin.

 

int istream::peek()

  返回 input stream 中下一个将被读取的字符, 但不真的把它读出.

  如果不能读取任何字符, 返回 EOF.

 

istream &istream::unget()

istream &istream::putback(char c)

  两者均把上一次读取的字符放回 input stream 中. 

  第二中形式会检查 c 是否是上一次读取的字符. 如果不是, 则设立 badbit 位.

  两次读取之间允许调用任一函数一次.

 

ostream &ostream::put(char c)

  将 c 写入 output stream.

 

ostream &ostream::write( const char *str, streamsize count )

  将 str 中 count 个字符写入.

 

ostream &ostream::flush()

  把所有缓冲数据强制写入设备.

manipulator <iomanip>

 通过 static overloading 实现.

 

ostream &ostream::operator<< ( ostream &(*op)(ostream &) )
{
return (*op)(*this);
}

 例如:

template <typename charT, typename traits>
inline std::basic_istream<charT, traits>
&ignoreLine (std::basic_istream<charT, traits> &strm)
{
// Skip until end-of-line.
strm.ignore( (std::numeric_limits<typename traits::pos_type>::max)(), '\n' );

// Return stream for concatenation.
return strm;
}

std::cin >>ignoreLine; // 忽略整行.


格式化

两个概念支配着 I/O 格式:

  1. 格式标志(format flags) .
  2. 调整格式. 

另外一片更注重实践的文章: http://www.cnblogs.com/walfud/articles/2047096.html

ios_base::fmtflags 用来存储格式标志.

其中, setf(flags, mask) 函数首先清除该掩码所标识的所有标志, 然后设置第一参数所代表的标志. 

 

用于存取格式的两个操纵器:

 

 布尔值格式操纵器:

如果 boolapha 被设置, bool 值便以文字表示, 读入的字符串必须是 true 或者 false.

 

字段宽度(width), 填充(fill):

执行了任何 I/O 操作后, 字段宽度自动恢复默认值( 0 ), 填充字符位置调整方式保持不变.

width() 对于 char * 而言, 会读取 width() -1 个字符(width( 0 ) 表示读取任意多个), 然而对 string 而言, 却会读取 width() 个字符:

char buf[80] = {'\0'};
cin >>setw(sizeof(buf)) >>buf; // Safe

cin.width() 对于 int 类型无效. 直到下一次执行类似 cin >>char * 或者 cin >>string 后, 才会重置 width().

    cout.fill('*');

cin.width(3);
cout.width(5);

string str;
int i;
cin >>i; // 这里 width() 不生效
cout <<i <<endl; // 这里重置 cout.width().

cin >>str; // 这里重置 cin.width().
cout <<str <<endl; // 这里 cout.width() == 0;

 

  

位置调整(adjust):

cout <<left;  相当于  cout.setf(left, adjustfield);

 

操纵器:

 

符号(sign)与大写(uppercase):

 

操纵器:

 

数值进制(base):

除非重置标志位, 否则会持续应用于后继的整数处理过程.

如果没有设置任何标志, 则由起始字符决定进制: 

  • 以 0x 或 0X 起始  =>  十六进制
  • 以 0 起始       =>  八进制
  • 其它        =>  十进制

IOStream 不支持二进制, 不过可借助 bitset 实现.

 

操纵器:

    cout.setf(ios_base::showbase   // 显示进制符号.
| ios_base::uppercase); // 进制符号大写.


cout <<hex <<127 <<endl; // 0X7F

 

浮点数(floating point):

持久有效.

余数采用四舍五入.

  • 如果 scientific 或 fixed 均为设置, 但至多只能输出 precision() 个有意义的数字. 所谓有意义的数字是只从左往右起第一位不为 0 的数字起, 以及其后的所有数字.
  • 无论是 scientific 还是 fixed, 如果设置 precision(num), 则小数点后一定有 num 位.

 操纵器:

 

其它格式:

操纵器:

 

13.9 文件 I/O

打开文件, 文件标志( file flags)

  • 如果想要复制文件, 则应该使用 binary 打开文件. 某些系统换行符被定义为 "\r\n"(CR LF), 而另外一些则定义为 '\n'.
  • 如果当做文本处理, 则不使用 binary 打开. 此时可以将不同系统之间的换行符转换为 '\n'.

其他的组合是非法的.

 

文件位置:

g 表示 get, p 表示 put.

如果某个位置处于文件开头之前, 或者末尾之后, 将导致未定义行为.

使用 longunsigned long 作为 stream 位置类型是错误的.

绝对位置类型

  • ios_base::pos_type
  • streampos

相对位置类型

  •  ios_base::seekdir 
    • beg  相对与文件开头
    • cur  相对与文件当前位置
    • end  相对与文件末尾
  • ios_base::off_type

 

读取文件结束后要使用 clear() 清除 eofbit 以及 failbit 状态, 否则无法进行任何进一步处理, 包括改变读写位置.

每次读取和改写操作之间, 一定要进行一次 seek 操作, 否则有可能导致一个被窜改的文件, 甚至更致命的错误.

 

13.11 string stream <sstream>

basic_istringstream 用于从 string 读取数据.

basic_ostringstream 用于向 string 写入数据.

basic_stringstream 用于对 string 读写数据.

    string fmtStr = "123 is a number\n\
456 is a number\n";
istringstream is(fmtStr);
int num1 = 0, num2 = 0;

is >>num1; // Get 123
is.ignore((numeric_limits<streamsize>::max)(), '\n');
is >>num2; // Get 456
is.ignore((numeric_limits<streamsize>::max)(), '\n');
#include <iostream>
#include <string>
#include <sstream>
using namespace std;


int main()
{
string fmtStr = "1234 5678";
stringstream io(fmtStr);
string num1, num2;

io.seekg(0);
io >>num1 >>num2; // 读取到 num2 时遇到 eof, failbit 被设立. 详见: http://topic.csdn.net/u/20120320/13/4447f1ab-867e-41bb-8b04-de214f4cda79.html?seed=40170768&r=77957992#r_77957992
io.clear(); // 清除 failbit 标志. 否则后续 io 操作都无法正常进行.

io.seekp(0);
io <<num2 <<" " <<num1;

cout <<io.str() <<endl;

return 0;
}

使用 istringstream 时, 在读取结束后要检查 flag 状态, 使用 clear() 重置. 否则导致后续操作无效

 

I/O 中的异常

为了保持向下兼容, stream 缺省情况并不抛出异常.

一旦指定的那个标志被设立起来, 立刻就会引发相应的异常.

调用 ios_base::exceptions() 时, 如果 stream 已设立相应的标记位, 那么此时也会抛出异常.

抛出的异常是 class ios_base::failure 对象:




其它

操纵器 endl 做两件事:

  1. 输出换行符. 
  2. 栓新 output 缓冲区, 对应 flush() 函数.

C++ stream(cin, cout 等) 在和标准 C stream(printf 等) 同步, 也就是说 "混合使用 C++ streams 和 C streams" 顺序有保障. 同步会占用一定时间, 使用 sync_with_stdio(false) 取消同步.

operator >> 和 operator << 都是从左向右进行 evaluation.

 

operator >>

  缺省情况下会逃过一开始的空格.

  对于 bool 值, 输入值非 0 非 1, 就被认为错误, 并设立 ios_base::failbit.

  

 

惯用法:

  向前声明 IOStream 只需包含 <iosfwd>, 只有相应的实现文件中才需含入完整的定义文件.

 

posted @ 2012-02-16 21:45  walfud  阅读(1599)  评论(0编辑  收藏  举报