Cycoe@Home

C++ Playground

1. 返回局部变量的引用

#include <iostream>

int &foo() {
  int i = 0;
  return i;    // warning: reference to local variable ‘i’ returned
}

int bar() {
  int i = 0;
  return i;
}

int main() {
  // 运行出现 =segmentation fault (core dumped)=
  // 在 foo 返回时局部变量 i 就会被释放,因此返回的引用是指向已释放内存
  int i = foo();

  // 函数 bar 返回了一个为右值的 int 型临时变量,因此只能使用常量引用
  int &ir = bar();         // 错误
  int const &icr = bar();  // 正确
}

2. 引用传递

这样看来,引用与变量在作为右值时是没有区别的

#include <iostream>

int main() {
  int i = 10;
  int &ir = i;
  int &irr = ir;
  std::cout << irr << std::endl;

  i = 20;
  std::cout << ir << std::endl;
  std::cout << irr << std::endl;
}
10
20
20

这也就解释了在重载 << 运算符时传入 ostream 对象的引用再传出是可行的

#include <iostream>

typedef struct Coor {
  float x;
  float y;
  float z;
} Coor;

std::ostream &operator<<(std::ostream &os, Coor &coor) {
  std::cout << "(" << coor.x << ", " << coor.y << ", " << coor.z << ")";
  return os;
}

int main() {
  Coor coor = {0.0, 1.0, 2.0};
  std::cout << coor << std::endl;
}
(0, 1, 2)
#include <iostream>

int &foo(int &i) {
  return ++i;
}

int main() {
  int i = 10;
  // foo 返回了 i 的引用,并将 i 的值赋值给 j
  // i 与 j 是两个不同的变量
  int j = foo(i);
  std::cout << "i is " << i << std::endl;
  std::cout << "j is " << j << std::endl;
}
i is 11
j is 11

3. 泛型数组模板

#include <cassert>
#include <cstddef>
#include <iostream>

template<typename T>
class Array {
  // 将 << 运算符的重载函数声明为友元,此处注意一定要声明为模板
  // 否则编译时会提示重载函数不是一个函数模板
  template<typename Ty>
  friend std::ostream &operator<<(std::ostream &os, Array<Ty> const &rhs);

public:
  // 构造与析构
  Array(size_t const size);           // 已知长度
  Array(Array<T> const &rhs);         // 从另一 Array 拷贝
  Array(T const *rhs, size_t size);   // 从数组构造
  Array(T const *beg, T const *end);  // 传入数组的头尾指针
  ~Array() { delete[] arr; }
  // 接口
  size_t size() const;
  Array<T> &extend(Array<T> const &rhs);
  // 运算符重载
  T &operator[](size_t const index);             // 下标运算符
  Array<T> &operator=(Array<T> const &rhs);      // 赋值运算符
  Array<T> operator+(Array<T> const &rhs) const; // 两 Array 相加
  bool operator==(Array<T> const &rhs) const;    // 判断相等

private:
  size_t sz = 0;
  T *arr = nullptr;
};

template<typename T>
Array<T>::Array(size_t const size) {
  sz = size;
  arr = new T[sz];
}

template<typename T>
Array<T>::Array(Array<T> const &rhs) {
  sz = rhs.sz;
  arr = new T[sz];
  for (size_t i = 0; i < sz; ++i) {
    arr[i] = rhs.arr[i];
  }
}

template<typename T>
Array<T>::Array(T const *rhs, size_t size) {
  sz = size;
  arr = new T[sz];
  for (size_t i = 0; i < sz; ++i) {
    arr[i] = rhs[i];
  }
}

template<typename T>
Array<T>::Array(T const *beg, T const *end) {
  sz = end - beg;
  arr = new T[sz];
  for (size_t i = 0; i < sz; ++i) {
    arr[i] = beg[i];
  }
}

template<typename T>
inline size_t Array<T>::size() const {
  return sz;
}

template<typename T>
inline Array<T> &Array<T>::extend(Array<T> const &rhs) {
  size_t new_size = sz + rhs.sz;
  T *new_arr = new T[new_size];
  for (size_t i = 0; i < sz; ++i) {
    new_arr[i] = arr[i];
  }
  for (size_t i = sz; i < new_size; ++i) {
    new_arr[i] = rhs.arr[i - sz];
  }
  delete[] arr;
  arr = new_arr;
  sz = new_size;
  return *this;
}

template<typename T>
T &Array<T>::operator[](size_t const index) {
  assert(index >= 0 && index < sz);
  return arr[index];
}

template<typename T>
std::ostream &operator<<(std::ostream &os, Array<T> const &rhs) {
  for (size_t i = 0; i < rhs.sz; ++i) {
    std::cout << rhs.arr[i] << ", ";
  }
  return os;
}

template<typename T>
Array<T> &Array<T>::operator=(Array<T> const &rhs) {
  assert(sz == rhs.sz);
  for (size_t i = 0; i < sz; ++i) {
    arr[i] = rhs.arr[i];
  }
}

template<typename T>
Array<T> Array<T>::operator+(Array<T> const &rhs) const {
  size_t size = sz + rhs.sz;
  Array<T> tmp(size);
  for (size_t i = 0; i < sz; ++i) {
    tmp.arr[i] = arr[i];
  }
  for (size_t i = sz; i < size; ++i) {
    tmp.arr[i] = rhs.arr[i - sz];
  }
  return tmp;
}

template<typename T>
bool Array<T>::operator==(Array<T> const &rhs) const {
  if (sz != rhs.sz)
    return false;
  size_t i;
  for (i = 0; arr[i] == rhs.arr[i] && i < sz; ++i);
  return i == sz;
}

int main() {
  Array<std::string> arrstr1(5);
  Array<std::string> arrstr2(5);
  arrstr1[0] = "Hello";
  arrstr1[1] = "world";
  arrstr1[2] = "hello";
  arrstr1[3] = "C++";
  arrstr1[4] = "!";
  arrstr2 = arrstr1;    // 赋值操作
  std::cout << arrstr2 << std::endl;

  int ints[5] = {1, 2, 3, 4, 5};
  Array<int> arrint1(ints, 5);
  Array<int> arrint2(ints, ints + 3);
  std::cout << arrint1 << std::endl;
  std::cout << arrint2 << std::endl;
  std::cout << "arrint1 and arrint2 is "
            << ((arrint1 == arrint2)? "": "not ")    // 判断 arrint1 和 arrint2 是否相等
            << "equal." << std::endl;
  Array<int> arrintsum = arrint1 + arrint2;          // 两人 Array 相加
  std::cout << arrintsum << std::endl;
  std::cout << arrint1.extend(arrint1) << std::endl; // 用 arrint1 扩展自身
}
Hello, world, hello, C++, !, 
1, 2, 3, 4, 5, 
1, 2, 3, 
arrint1 and arrint2 is not equal.
1, 2, 3, 4, 5, 1, 2, 3, 
1, 2, 3, 4, 5, 1, 2, 3, 4, 5,

4. 可变参数模板

#include <iostream>

void print() { }

template<typename T, typename... Types>
void print(T firstArg, Types... args) {
  std::cout << firstArg << std::endl;
  print(args...);
}

int main() {
  std::string s("world");
  print(7.5, "hello", s);
}
7.5
hello
world

5. TODO 几种初始化方法的区别

#include <string>

class C {
public:
  C(std::string const& s) {
    this->s = s;
  }
private:
  std::string s;
};

int main() {
  C c1 = "hello";    // 拷贝构造,调用 C(C const&),等号后面需要的是类型 C
  C c2("hello");     // 根据传入参数调用对应构造函数,此处调用 C(std::string const &)
  C c3{"hello"};     // 使用参数列初始化,调用 C(): s("hello")
  C c4 = {"hello"};  // 还没搞明白
}

6. 实现 for_each

#include <iostream>
#include <type_traits>
#include <vector>
#include <cmath>

// 实现 Python 中的 map 函数
// Container: 容器类
// Callable: 可调用对象(函数或仿函数)
template<typename Container, typename Callable>
Container& foreach(Container& c, Callable op) {
  typename Container::iterator pos;
  typename Container::iterator end = c.end();
  for (pos = c.begin(); pos != end; ++pos) {
    op(*pos);
  }
  return c;
}

// 这种写法无法自动推断,只能手动指定
template<typename Container, typename Callable>
void foreach(typename Container::iterator pos,
             typename Container::iterator end, Callable op) {
  while (pos != end) {
    op(*pos++);
  }
}

template<typename Iter, typename Callable>
void foreach(Iter pos, Iter end, Callable op) {
  while (pos != end) {
    op(*pos++);
  }
}

// 实现 Python 中的 reduce 函数
// Container: 容器类
// Callable: 可调用对象(函数或仿函数)
// 返回值是值传递
template<typename Container, typename Callable>
auto reduce(Container& c, Callable op) -> std::decay_t<decltype(c[0])> {
  using ElemType = std::decay_t<decltype(c[0])>;

  // 如果容器为空,返回与容器第一个元素类型相同的零初始化对象,注意此处需要退化
  if (c.empty()) {
    return ElemType{};
  }
  // 容器长度为 1 时直接返回第一个元素
  if (c.size() == 1) {
    return c[0];
  }

  ElemType ret(c[0]);
  foreach(c.begin() + 1, c.end(), [&ret, &op](ElemType const& elem) {
    ret = op(ret, elem);
  });
  return ret;
}

template<typename Iter, typename Callable>
auto reduce(Iter pos, Iter end, Callable op) -> std::decay_t<decltype(*pos)> {
  using ElemType = std::decay_t<decltype(*pos)>;

  if (pos == end ) {
    return ElemType{};
  }
  if (end - pos == 1) {
    return *pos;
  }

  ElemType ret(*pos);
  foreach(pos + 1, end, [&ret, &op](ElemType const & elem) {
    ret = op(ret, elem);
  });
  return ret;
}

template<typename Container, typename ElemType>
void linspace(Container& c, ElemType const& start,
              ElemType const& end, ElemType const& sep) {
  if (!std::is_same<std::decay_t<decltype(c[0])>, ElemType>::value) {
    std::cerr << "Element in container should be same type with range!\n";
  }
  for (ElemType current = start; current < end; current += sep) {
    c.push_back(current);
  }
}

int main() {
  // 初始化一个等差数列
  std::vector<double> dvec;
  linspace(dvec, 1.0, 5.0, 0.001);

  // 计算对应的 log 函数值,并乘上步长
  foreach(dvec.begin(), dvec.end(), [](double& elem) {
    elem = log(elem) * 0.001;
  });

  // 求 ln(x) 在 [1, 5] 范围内的积分值
  double integral = reduce(dvec.begin(), dvec.end(), [](double const& a, double const& b) {
    return a + b;
  });
  std::cout << "Integral of ln(x) in [1, 5] is " << integral << std::endl;
}
Integral of ln(x) in [1, 5] is 4.04638

7. std::vector 内存分配策略

#include <iostream>
#include <vector>

int main() {
  std::vector<int> ivec;

  // 在添加元素之前的容量
  std::cout << "Capacity is " << ivec.capacity() << std::endl;
  // 添加 24 个元素
  for (std::vector<int>::size_type pos = 0; pos < 24; ++pos) {
    ivec.push_back(pos);
  }
  // ivec 在不触发重新内存分配之前可保存多少个元素
  std::cout << "Capacity is " << ivec.capacity() << std::endl;
  // 设定保留 24 个可用空间
  ivec.reserve(24);
  // 当指定的元素个数小于实际的个数时,不会回收内存
  std::cout << "Capacity is " << ivec.capacity() << std::endl;
  // 指定保留 48 个可用空间
  ivec.reserve(48);
  // 当指定的元素个数大于实际的个数时,会扩展内存
  std::cout << "Capacity is " << ivec.capacity() << std::endl;
  // 指定回收多余内存,具体是否回收决定于编译器实现
  ivec.shrink_to_fit();
  std::cout << "Capacity is " << ivec.capacity() << std::endl;

  // ivec 的大小是 24 个字节,保存了三个指针,分别是向量头、向量尾后、容量尾后
  std::cout << sizeof(ivec) << std::endl;
}
Capacity is 0
Capacity is 32
Capacity is 32
Capacity is 48
Capacity is 24
24

8. std::string 的一些操作

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void find_all(string const& s, string const& pattern) {
  string::size_type pos = 0;
  while ((pos = s.find_first_of(pattern, pos)) != string::npos) {
    cout << "Found index: " << pos
         << ", element is: " << s[pos] << endl;
    ++pos;
  }
}

template<typename RET, typename Con, typename Callable>
RET sum(Con const& container, Callable convert) {
  RET ret{};
  typename Con::const_iterator pos;
  typename Con::const_iterator end = container.end();
  for (pos = container.begin(); pos != end; ++pos) {
    ret += convert(*pos);
  }
  return ret;
}

int main() {
  string s1("Hello, world!");
  string s2("world");
  string punct(",.!");

  find_all(s1, punct);

  string::size_type pos = 0;
  if ((pos = s1.find(s2)) != string::npos) {
    cout << "Find " << s2 << " at index " << pos << endl;
  }

  string s3  = to_string(123);
  cout << s3 << endl;
  int ival = stoi(s3);
  cout << ival << endl;
  string s4("pi = 3.14159");
  double pi = stod(s4.substr(s4.find_first_of("+-.1234567890")));
  cout << "Pi is " << pi << endl;

  vector<string> string_with_digitals{
    "3.14159",
    "2.71828",
    "1.41421"
  };
  cout << "Sum of int is " << sum<int>(string_with_digitals, [](string const& s) {
    return stoi(s);
  }) << endl;
  cout << "Sum of double is " << sum<double>(string_with_digitals, [](string const& s) {
    return stod(s);
  }) << endl;
}
Found index: 5, element is: ,
Found index: 12, element is: !
Find world at index 7
123
123
Pi is 3.14159
Sum of int is 6
Sum of double is 7.27408

9. 泛型算法

#include <iostream>
#include <algorithm>
#include <vector>
#include <list>

using namespace std;

int main() {
  vector<int> ivec{1, 2, 3, 4, 5};
  list<int> ilist{1, 2, 3, 4, 5};
  cout << "ivec and ilist is"
       << (equal(ivec.cbegin(), ivec.cend(), ilist.cbegin()) ? " " : " not ")
       << "same." << endl;
}
ivec is same.
#include <iostream>
#include <vector>
#include <numeric>

int main() {
  std::vector<double> ivec{1.2, 2.9, 3.4, 4.5, 5.5};
  // 注意对于泛型算法第三个参数非常重要,决定了返回值类型
  std::cout << "Sum is " << std::accumulate(ivec.cbegin(), ivec.cend(), 0) << std::endl;
  std::cout << "Sum is " << std::accumulate(ivec.cbegin(), ivec.cend(), .0) << std::endl;
}
Sum is 15
Sum is 17.5
#include <iostream>
#include <vector>
#include <algorithm>

template<typename Iter>
void print_items(Iter __pos, Iter const __end) {
  if (__pos == __end) {
    return;
  }

  std::cout << "[";
  // 此处采用 __pos + 1 != __end 而不是 __pos != __end - 1 是因为 list 的迭代器
  // 没有重载减操作
  for (; __pos + 1 != __end; ++__pos) {
    std::cout << *__pos << ", ";
  }
  std::cout << *__pos << "]" << std::endl;
}

int main() {
  std::vector<int> ivec{0, 1, 2, 3, 0, 1, 2};
  std::vector<int> ivec_new;

  // 将 ivec 中的 0 替换为 42 并写入 ivec_new,注意此处的 back_inserter 生成了一
  // 个插入迭代器
  std::replace_copy(ivec.begin(), ivec.end(), std::back_inserter(ivec_new), 0, 42);
  print_items(ivec_new.cbegin(), ivec_new.cend());

  // 将大于 1 的值替换为 1,注意此处因为 ivec_new 中本来已经有相同数量的值,直接覆盖
  std::replace_copy_if(ivec.begin(), ivec.end(), ivec_new.begin(),
                       [](int const& elem) { return elem > 1; }, 1);
  print_items(ivec_new.cbegin(), ivec_new.cend());

  // 直接在 ivec 上修改
  std::replace_if(ivec.begin(), ivec.end(),
                  [](int const& elem) { return elem > 1; }, 1);
  print_items(ivec.cbegin(), ivec.cend());

  std::fill(ivec.begin(), ivec.end(), 0);
  print_items(ivec.cbegin(), ivec.cend());

  for (std::size_t count = 0; count < 5; ++count) {
    *std::inserter(ivec, ivec.begin() + 2) = count + 1;
  }
  print_items(ivec.cbegin(), ivec.cend());

  auto back_insertor = std::back_inserter(ivec_new);
  for (std::size_t count = 0; count < 5; ++count) {
    *back_insertor = count + 1;
  }
  print_items(ivec_new.cbegin(), ivec_new.cend());
}
[42, 1, 2, 3, 42, 1, 2]
[0, 1, 1, 1, 0, 1, 1]
[0, 1, 1, 1, 0, 1, 1]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 0, 1, 1, 1, 2, 3, 4, 5]

消除重复的单词

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>>

using namespace std;

template<typename Iter>
void print_items(Iter __pos, Iter const __end) {
  if (__pos == __end) {
    return;
  }

  std::cout << "[";
  for (; __pos + 1 != __end; ++__pos) {
    std::cout << *__pos << ", ";
  }
  std::cout << *__pos << "]" << std::endl;
}

template<typename Con>
void elim_dups(Con& container) {
  // 排序
  sort(container.begin(), container.end());
  // 将无重复的元素放在开头,并返回最后一个不重复元素之后位置的迭代器
  auto end_unique = unique(container.begin(), container.end());
  // 消除重复的元素
  container.erase(end_unique, container.end());
}

int main() {
  vector<int> ivec{1, 0, 2, 3, 1, 2, 0};
  deque<string> sdeque{
    "hello",
    "world",
    "C++",
    "hello",
    "C++"
  };
  elim_dups(ivec);
  print_items(ivec.cbegin(), ivec.cend());
  elim_dups(sdeque);
  print_items(sdeque.cbegin(), sdeque.cend());
}
[0, 1, 2, 3]
[C++, hello, world]

谓词

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

template<typename Iter>
void print_items(Iter __pos, Iter const __end) {
  if (__pos == __end) {
    return;
  }

  std::cout << "[";
  for (; __pos + 1 != __end; ++__pos) {
    std::cout << *__pos << ", ";
  }
  std::cout << *__pos << "]" << std::endl;
}

// 交换迭代器元素的模板实现
template<typename Iter>
void swap(Iter const __iter_a, Iter const __iter_b) {
  typename iterator_traits<Iter>::value_type tmp;
  tmp = *__iter_a;
  *__iter_a = *__iter_b;
  *__iter_b  = tmp;
}

// 冒泡排序的模板实现,此处的 Predicate 称为二元谓词
template<typename Iter, typename Predicate>
void sort(Iter const __beg, Iter __end, Predicate __predicate) {
  Iter pos = __beg;
  for (; __beg + 1 != __end; --__end) {
    for (pos = __beg; pos + 1 != __end; ++pos) {
      if (!__predicate(*pos, *(pos + 1))) {
        swap(pos, pos + 1);
      }
    }
  }
}

int main() {
  vector<string> svec{
    "hello",
    "world",
    "evil",
    "C++",
    "elegant",
    "Python"
  };
  // 自定义排序条件,此处使用了 lambda 模板,C++20 才加入的特性
  ::sort(svec.begin(), svec.end(), []<typename T>(T const& a, T const& b) {
      return a < b;
    });
  print_items(svec.cbegin(), svec.cend());

  ::sort(svec.begin(), svec.end(), []<typename T>(T const& a, T const& b) {
      return a.size() < b.size();
    });
  print_items(svec.cbegin(), svec.cend());

  // 将 svec 先按字典序重排,再使用 stable_sort 保持相同长度的 string 的顺序
  std::sort(svec.begin(), svec.end());
  std::stable_sort(svec.begin(), svec.end(), []<typename T>(T const& a, T const& b) {
      return a.size() < b.size();
    });
  print_items(svec.cbegin(), svec.cend());

  // 此处需要显式指明迭代器类型,若使用 auto 会返回 normal_iterator 类型,需要使
  // 用 static_cast 进行类型转换
  vector<string>::const_iterator iter_sep;
  // 打印长度大于 4 的元素,partition 将元素分为两部分,前面为满足一元谓词的元素,
  // 返回值为不满足条件的最后一个元素的后一个迭代器
  iter_sep = std::partition(svec.begin(), svec.end(), []<typename T>(T const& a) {
      return a.size() > 4;
    });
  print_items(svec.cbegin(), iter_sep);
}
[C++, Python, elegant, evil, hello, world]
[C++, evil, world, hello, Python, elegant]
[C++, evil, hello, world, Python, elegant]
[elegant, Python, hello, world]

lambda 表达式,闭包

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <functional>

using namespace std;

template<typename Iter>
void print_items(Iter __pos, Iter const __end) {
  if (__pos == __end) {
    return;
  }

  std::cout << "[";
  for (; __pos + 1 != __end; ++__pos) {
    std::cout << *__pos << ", ";
  }
  std::cout << *__pos << "]" << std::endl;
}

// 一个闭包模板
template<typename T>
auto longer_than(T const& __pivot) {
  // 返回一个一元谓词(模板谓词),lambda 表达式拷贝捕获了 __pivot 局部变量作为
  // 成员,之后每次调用谓词都可使用 __pivot 的值。若采用引用捕获则需注意生命周期
  // 和变量变化
  return [__pivot]<typename U> (U const& elem) { return elem.size() > __pivot; };
}

template<typename U, typename T>
bool longer_than(U const& elem, T const& __pivot) {
  return elem.size() > __pivot;
}

int main() {
  vector<string> svec{
    "hello",
    "world",
    "evil",
    "C++",
    "elegant",
    "Python"
  };

  vector<string>::const_iterator iter_sep;
  // 打印长度大于 4 的元素
  iter_sep = partition(svec.begin(), svec.end(), longer_than(4));
  print_items(svec.cbegin(), iter_sep);
  // 打印长度大于 5 的元素
  iter_sep = partition(svec.begin(), svec.end(), longer_than(5));
  print_items(svec.cbegin(), iter_sep);
  // 使用 std::bind 绑定参数,在 functional 头文件中
  iter_sep = partition(svec.begin(), svec.end(), bind(longer_than<string, size_t>, placeholders::_1, 4));
  print_items(svec.cbegin(), iter_sep);
}
[hello, world, Python, elegant]
[elegant, Python]
[elegant, Python, world, hello]

std::bind 的用法

#include <iostream>
#include <functional>

using namespace std;
using namespace std::placeholders;

template<typename Arg>
void print(Arg const& arg) {
  cout << arg << endl;
}

template<typename Arg, typename... Args>
void print(Arg arg, Args... args) {
  cout << arg << ", ";
  print(args...);
}

int main() {
  print("evil", "C++", "elegant", "Python");
  auto f = bind(print<string, string, string, string>, "evil", _1, "elegant", _2);
  f("C++", "Python");
}
evil, C++, elegant, Python
evil, C++, elegant, Python

使用 std::bind 重排参数顺序

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>

using namespace std;
using namespace std::placeholders;

template<typename Iter>
void print_items(Iter __pos, Iter const __end) {
  if (__pos == __end) {
    return;
  }

  std::cout << "[";
  for (; __pos + 1 != __end; ++__pos) {
    std::cout << *__pos << ", ";
  }
  std::cout << *__pos << "]" << std::endl;
}

template<typename T1, typename T2>
bool shorter(T1 const& a, T2 const& b) {
  return a.size() > b.size();
}

int main() {
  vector<string> svec{
    "devil",
    "C++",
    "elegant",
    "Python"
  };

  sort(svec.begin(), svec.end(), shorter<string, string>);
  print_items(svec.cbegin(), svec.cend());

  sort(svec.begin(), svec.end(), bind(shorter<string, string>, _2, _1));
  print_items(svec.cbegin(), svec.cend());
}
[elegant, Python, devil, C++]
[C++, devil, Python, elegant]

10. 迭代器的一些高级用法

从字符串流读取数据

#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <numeric>>

using namespace std;

template<typename Iter>
void print_items(Iter __pos, Iter const __end) {
  if (__pos == __end) {
    return;
  }

  // 定义一个 pre 变量缓存迭代器的返回值,对于流迭代器无法预测何时到达流的尾部
  typename Iter::value_type pre;
  std::cout << "[";
  for (pre = *__pos++; __pos != __end; pre = *__pos++) {
    std::cout << pre << ", ";
  }
  std::cout << pre << "]" << std::endl;
}

// 从流中读取数据到插入迭代器
template<typename IStream, typename Insert_Iter>
void data_from_stream(IStream& __is, Insert_Iter __insert_iter) {
  // TRICK 萃取迭代器所指元素的类型,但该类型无法直接通过
  // Insert_Iter::value_type 获取,直接获取为 void,参照
  // https://stackoverflow.com/questions/16165635/why-the-value-type-difference-type-pointer-reference-of-back-insert-iterator-fro
  using Type = typename Insert_Iter::container_type::value_type;
  // 初始化 __is 流的一个 Type 型迭代器
  std::istream_iterator<Type> is_iter(__is);
  // 默认初始化为尾后迭代器
  std::istream_iterator<Type> eof;

  while (is_iter != eof) {
    *__insert_iter = *is_iter++;
  }
}

int main() {
  // 初始化一个字符串流
  stringstream ss{"123 456 789"};
  vector<int> ivec;

  // 使用自定义的方式将流中数据写入输出流
  data_from_stream(ss, std::back_inserter(ivec));
  print_items(ivec.cbegin(), ivec.cend());

  // 标准库提供了更为简便的方式,从输入流拷贝到 vector
  stringstream ssd{"3.14 1.41 2.56"};
  vector<double> dvec;
  std::copy(std::istream_iterator<double>(ssd),
            std::istream_iterator<double>(),
            std::back_inserter(dvec));
  print_items(dvec.cbegin(), dvec.cend());

  // 甚至还可以更简单,直接从输入流拷贝到输出流
  stringstream ssd2{"3.14 1.41 2.56"};
  std::copy(std::istream_iterator<double>(ssd2),
            std::istream_iterator<double>(),
            std::ostream_iterator<double>(std::cout, ", "));
  std::cout << std::endl;

  // 或者利用泛型的 print_items 函数模板,从输入流自定义输出
  stringstream ssd3{"3.14 1.41 2.56"};
  print_items(std::istream_iterator<double>(ssd3),
              std::istream_iterator<double>());
}
[123, 456, 789]
[3.14, 1.41, 2.56]
3.14, 1.41, 2.56, 
[3.14, 1.41, 2.56]

将数据写到输出流迭代器

#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>

int main() {
  // 从流中读取数据,排序后输出到标准输出流
  std::stringstream ss{"1 3 2 5 4"};
  std::istream_iterator<int> is_iter(ss), eof;
  std::vector<int> ivec;
  // 输入流迭代器不支持随机访问,因此不能直接排序
  std::copy(is_iter, eof, std::back_inserter(ivec));
  std::sort(ivec.begin(), ivec.end());
  std::ostream_iterator<int> os_iter(std::cout, " ");
  // 从此处可以看出流迭代器与插入迭代器类似
  std::copy(ivec.begin(), ivec.end(), os_iter);
}
1 2 3 4 5

11. 关联容器

统计单词数量

#include <iostream>
#include <string>
#include <sstream>
#include <iterator>
#include <algorithm>
#include <map>
#include <fmt/core.h>

using namespace std;

void word_count(string const __text, ostream_iterator<string> __os_iter) {
  stringstream ss(__text);
  istream_iterator<string> is_iter(ss), eof;
  map<string, size_t> counter;

  while (is_iter != eof) {
    string buf = *is_iter++;
    transform(buf.begin(), buf.end(),
              buf.begin(), static_cast<int(*)(int)>(tolower));
    ++counter[buf];
  }

  for (auto const& w : counter) {
    *__os_iter++ = fmt::format("{0} occurs {1} times.", w.first, w.second);
  }
}

int main() {
  string text("Hello world hello Evil C++ hello c++ world");
  word_count(text, ostream_iterator<string>(cout, "\n"));
}
c++ occurs 2 times.
evil occurs 1 times.
hello occurs 3 times.
world occurs 2 times.

set 中自定义比较函数

#include <iostream>
#include <set>

struct Point {
public:
  Point(double __x = .0, double __y = .0) : x(__x), y(__y) { }
  friend std::ostream& operator<< (std::ostream& os, Point const& p) {
    os << "(" << p.x << ", " << p.y << ")" << std::endl;
  }
  double x, y;
};

bool compare_point(Point const& a, Point const& b) {
  if (a.x == 0 && b.x == 0) {
    return a.y < b.y;
  }
  if (a.x == 0) {
    return false;
  }
  if (b.x == 0) {
    return true;
  }

  return a.y / a.x < b.y / b.x;
}

bool strict_compare_point(Point const& a, Point const& b) {
  return a.x < b.x || a.y < b.y;
}

int main() {
  std::set<Point, decltype(compare_point)*> points(compare_point);
  points.emplace(1, 2);
  points.emplace(1, 3);
  points.emplace(2, 4);
  for (Point const& point : points) {
    std::cout << point;
  }

  std::set<Point, decltype(strict_compare_point)*> spoints(strict_compare_point);
  spoints.emplace(1, 2);
  spoints.emplace(1, 3);
  spoints.emplace(2, 4);
  for (Point const& point : spoints) {
    std::cout << point;
  }
}
(1, 2)
(1, 3)
(1, 2)
(1, 3)
(2, 4)
#include <map>
#include <iostream>

int main()
{
  // m 中进行了值初始化,因此 m 中有个 std::pair<int, int>(0, 0)
  std::map<int, int> m;
  m[0] = 1;
  std::cout << m.begin()->first << ", " << m.begin()->second << std::endl;
  if(m.find(0) != m.end()) {
    auto& item = *m.find(0);
    std::cout << item.first << ", " << item.second << std::endl;
  }
}
0, 1
0, 1

multimap 中找出所有与 key 绑定的值

#include <iostream>
#include <map>

int main()
{
  std::multimap<std::string, std::string> mm = {
    {"duck", "run"},
    {"dog", "run"},
    {"duck", "fly"}
  };

  // 第一种方法是手动遍历 map
  std::cout << "The first method to find duck." << std::endl;
  auto pos = mm.begin();
  while (pos != mm.end()) {
    if (pos->first == "duck") {
      std::cout << "duck can " << pos->second << std::endl;
    }
    ++pos;
  }

  // 第二种方法是利用 find 和 count
  std::cout << "The second method to find duck." << std::endl;
  auto count = mm.count("duck");
  auto entries = mm.find("duck");
  while (count) {
    std::cout << "duck can " << entries->second << std::endl;
    ++entries;
    --count;
  }

  // 第三种方法是利用 lower_bound 和 upper_bound
  std::cout << "The third method to find duck." << std::endl;
  for (auto beg = mm.lower_bound("duck"), end = mm.upper_bound("duck");
       beg != end; ++beg) {
    std::cout << "duck can " << beg->second << std::endl;
  }

  // 第四种方法是利用 equal_range
  std::cout << "The forth method to find duck." << std::endl;
  for (auto pos = mm.equal_range("duck");
       pos.first != pos.second; ++pos.first) {
    std::cout << "duck can " << pos.first->second << std::endl;
  }
}
The first method to find duck.
duck can run
duck can fly
The second method to find duck.
duck can run
duck can fly
The third method to find duck.
duck can run
duck can fly
The forth method to find duck.
duck can run
duck can fly

将自定义的类型作为 key 时需要定义类型的哈希方法和 == 操作符

#include <iostream>
#include <string>
#include <unordered_map>
#include <hash_map>

struct Person {
  Person(std::string const& __name, std::size_t const __age) :
    name(__name), age(__age) { }
  std::string name;
  std::size_t age;
};

// 哈希函数
std::size_t hasher(Person const& __person) {
  return std::hash<std::string>()(__person.name);
}

bool equal_person(Person const& lhs, Person const& rhs) {
  return lhs.name == rhs.name;
}

int main() {
  // 定义类型别名,比一般的 map 多两个类型
  using person_multimap = std::unordered_map<Person,
                                             std::string,
                                             decltype(hasher)*,
                                             decltype(equal_person)*>;
  // 定义电话薄,24 是桶的大小
  person_multimap book(24, hasher, equal_person);
  book.insert(std::make_pair(Person("Alisa", 25), "123-456-789"));
  book.insert(std::make_pair(Person("Bob", 26), "324-783-192"));
  book.insert(std::make_pair(Person("Tim", 23), "583-125-372"));
  std::cout << "Alisa's telephone number is "
            << book.find(Person("Alisa", 25))->second << std::endl;
  std::cout << "Bucket size of key Alisa is "
            << book.bucket_size(book.bucket(Person("Alisa", 25))) << std::endl;
}
Alisa's telephone number is 123-456-789
Bucket size of key Alisa is 1

12. std::format

需要等待编译器支持,目前仍需使用 fmt 第三方库

#include <iostream>
// 需要 C++20 标准支持
// #include <format>
// fmt 第三方库
#include <fmt/core.h>

int main() {
  std::cout << fmt::format("{1}, {0}.", "world", "hello");
}
hello, world.

13. rangeview 视图

ranges 功能目前编译器还未实现,需要自己安装 ranges v3 头文件。惰性求值的特性不错, 就是编译太慢了

#include <vector>
#include <iostream>
#include <range/v3/action.hpp>
#include <range/v3/view.hpp>
#include <range/v3/numeric.hpp>
#include <range/v3/algorithm.hpp>
#include <range/v3/iterator.hpp>

using namespace ranges;

int main() {
  int total = ranges::accumulate(
                               view::ints(1) |
                               view::transform([](int i) {return i * i;}) |
                               view::take(100),
                               0);
  std::cout << total << std::endl;

  // std::vector<int> ivec{0, 2, 2, 1, 4, 6, 4, 3, 1};
  // std::vector<int> result = ivec | actions::sort | actions::unique;
  // ranges::copy(result, ranges::ostream_iterator<int>(std::cout, " "));
}

一个老派的方式为

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

int main() {
  std::vector<int> ivec;
  for (int i = 1; i < 101; ++i) {
    ivec.push_back(i);
  }

  // 前 10 个平方数的和
  std::transform(ivec.begin(), ivec.end(),
                 ivec.begin(), [](int i) { return i * i; });
  std::cout << std::accumulate(ivec.cbegin(), ivec.cend(), 0) << std::endl;
}
338350

g++C++20 标准有了部分支持,需要指定 -std=c++20

#include <iostream>
#include <ranges>

using namespace std;

int main()
{
  for (auto i : views::iota(0, 10)
         | views::filter([](int i) { return i % 2; })
         | views::transform([](int i) { return i * i; }))
    cout << i << " ";
}
1 9 25 49 81
#include <ranges>
#include <memory>
#include <iostream>
#include <string>
#include <vector>

namespace std {
namespace ranges {

template <input_range _Vp>
class cycle_view : public view_interface<cycle_view<_Vp>>
{
private:
  struct _Sentinel;

  struct _Iterator
  {
  private:
    friend _Sentinel;
    using _Vp_iter = iterator_t<_Vp>;

    _Vp_iter _M_current = _Vp_iter();
    cycle_view* _M_parent = nullptr;
  public:
    using iterator_category = typename iterator_traits<_Vp_iter>::iterator_category;
    using value_type = range_value_t<_Vp>;
    using difference_type = range_difference_t<_Vp>;

    _Iterator() = default;

    constexpr _Iterator(cycle_view& __parent, _Vp_iter __current)
      : _M_current(std::move(__current)),
        _M_parent(std::__addressof(__parent)) {}

    constexpr range_reference_t<_Vp> operator*() const
    { return *_M_current; }

    constexpr _Vp_iter operator->() const
      requires __detail::__has_arrow<_Vp_iter> && copyable<_Vp_iter>
    { return _M_current; }

    constexpr _Iterator& operator++()
    {
      ++_M_current;
      if(_M_current == ranges::end(_M_parent->_M_base))
        _M_current = ranges::begin(_M_parent->_M_base);

      return *this;
    }

    constexpr void operator++(int)
    {
      ++*this;
    }

    constexpr _Iterator operator++(int) requires forward_range<_Vp>
    {
      auto __tmp = *this;
      ++*this;
      return __tmp;
    }

    friend constexpr bool operator==(const _Iterator& __x, const _Iterator& __y)
      requires equality_comparable<_Vp_iter>
    { return __x._M_current == __y._M_current; }
  };

  struct _Sentinel
  {
  private:
    sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();

    constexpr bool __equal(const _Iterator& __i) const
    {
      return __i._M_current == _M_end;
    }
  public:
    _Sentinel() = default;

    constexpr explicit _Sentinel(cycle_view& __parent) :
      _M_end(ranges::end(__parent._M_base)) {}

    friend constexpr bool operator==(const _Iterator& __x, const _Sentinel& __y)
    { return __y.__equal(__x); }
  };

  _Vp _M_base = _Vp();
public:
    cycle_view() = default;

    constexpr cycle_view(_Vp __base): _M_base(std::move(__base)) {}

    constexpr _Iterator begin()
    {
        return {*this, ranges::begin(_M_base)};
    }

    constexpr auto end()
    {
      if constexpr (common_range<_Vp>)
        return _Iterator{*this, ranges::end(_M_base)};
      else
        return _Sentinel{*this};
    }
};

template <input_range _Range>
cycle_view(_Range &&) -> cycle_view<views::all_t<_Range>>;

namespace views {
  inline constexpr __adaptor::_RangeAdaptorClosure cycle
  = [] <viewable_range _Range> (_Range&& __r)
  {
    return cycle_view { std::forward<_Range>(__r) };
  };
} // namespace views

template <input_range _Vp1, input_range _Vp2>
class zip_view : public view_interface<zip_view<_Vp1, _Vp2>>
{
private:
  struct _Sentinel;

  struct _Iterator
  {
  private:
    friend zip_view;
    friend _Sentinel;
    using _Vp1_iter = iterator_t<_Vp1>;
    using _Vp2_iter = iterator_t<_Vp2>;

    _Vp1_iter _M_current1 = _Vp1_iter();
    _Vp2_iter _M_current2 = _Vp2_iter();
    zip_view* _M_parent = nullptr;
  public:
    using iterator_category = typename iterator_traits<_Vp1_iter>::iterator_category;
    using value_type = std::pair<range_value_t<_Vp1>, range_value_t<_Vp2>>;
    using difference_type = range_difference_t<_Vp1>;

    _Iterator() = default;

    constexpr _Iterator(zip_view& __parent,
      _Vp1_iter __current1, _Vp2_iter __current2) :
      _M_current1(std::move(__current1)),
      _M_current2(std::move(__current2)),
      _M_parent(std::__addressof(__parent)) {}

    constexpr std::pair<range_reference_t<_Vp1>, range_reference_t<_Vp2>>
    operator*() const
    { return std::make_pair(std::ref(*_M_current1), std::ref(*_M_current2)); }

    constexpr std::pair<_Vp1_iter, _Vp2_iter> operator->() const
      requires __detail::__has_arrow<_Vp1_iter> &&
          __detail::__has_arrow<_Vp2_iter>
    { return std::make_pair(_M_current1, _M_current2); }

    constexpr _Iterator& operator++()
    {
      ++_M_current1;
      ++_M_current2;
      //if(_M_current1 == ranges::end(_M_parent->_M_base1)
      //        || _M_current2 == ranges::end(_M_parent->_M_base2))
      return *this;
    }

    constexpr void operator++(int)
    { ++*this; }

    constexpr _Iterator operator++(int) requires forward_range<_Vp1> && forward_range<_Vp2>
    {
      auto __tmp = *this;
      ++*this;
      return __tmp;
    }

    friend constexpr bool operator==(const _Iterator& __x, const _Iterator& __y)
      requires equality_comparable<_Vp1_iter> && equality_comparable<_Vp2_iter>
    { return __x._M_current1 == __y._M_current1 && __x._M_current2 == __y._M_current2; }
  };

  struct _Sentinel
  {
  private:
    std::pair<sentinel_t<_Vp1>, sentinel_t<_Vp2>> _M_end =
      std::make_pair(sentinel_t<_Vp1>(), sentinel_t<_Vp2>());

    constexpr bool __equal(const _Iterator& __i) const
    {
      return __i._M_current1 == _M_end.first || __i._M_current2 == _M_end.second;
    }
  public:
    _Sentinel() = default;

    constexpr explicit _Sentinel(zip_view& __parent): _M_end(std::make_pair(
      ranges::end(__parent._M_base1), ranges::end(__parent._M_base2))) {}

    friend constexpr bool operator==(const _Iterator& __x, const _Sentinel& __y)
    { return __y.__equal(__x); }
  };

  _Vp1 _M_base1 = _Vp1();
  _Vp2 _M_base2 = _Vp2();
public:
  zip_view() = default;

  constexpr zip_view(_Vp1 __base1, _Vp2 __base2): _M_base1(std::move(__base1)),
    _M_base2(std::move(__base2)) {}

  constexpr _Iterator begin()
  { return {*this, ranges::begin(_M_base1), ranges::begin(_M_base2)}; }

  constexpr auto end()
  {
    //if constexpr (common_range<_Vp1> && common_range<_Vp2>)
    //  return _Iterator{*this, ranges::end(_M_base1), ranges::end(_M_base2)};
    //else
      return _Sentinel{*this};
  }
};


template <input_range _Vp1, input_range _Vp2>
inline constexpr bool enable_borrowed_range<zip_view<_Vp1, _Vp2>> = true;

template <input_range _Range1, input_range _Range2>
zip_view(_Range1&&, _Range2&&) -> zip_view<views::all_t<_Range1>, views::all_t<_Range2>>;

namespace views {
  inline constexpr __adaptor::_RangeAdaptor zip
  = [] <viewable_range _Range1, viewable_range _Range2> (_Range1&& __r1, _Range2&& __r2)
  {
    return zip_view { std::forward<_Range1>(__r1), std::forward<_Range2>(__r2) };
  };
} // namespace views

} // namespace ranges
} // mamespace std

using namespace std;

namespace vs = std::ranges::views;
namespace rs = std::ranges;

int main() {
  vector v1 {"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
  vector v2 {"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};

  auto a = v1 | vs::cycle;
  auto b = v2 | vs::cycle;
  auto c = vs::zip(a, b) | vs::take(60);

  for(auto&& [x, y]: c)
    cout<<x<<y<<" ";
  cout<<"\n";

  return 0;
}
甲子 乙丑 丙寅 丁卯 戊辰 己巳 庚午 辛未 壬申 癸酉 甲戌 乙亥 丙子 丁丑 戊寅 己卯 庚辰 辛巳 壬午 癸未 甲申 乙酉 丙戌 丁亥 戊子 己丑 庚寅 辛卯 壬辰 癸巳 甲午 乙未 丙申 丁酉 戊戌 己亥 庚子 辛丑 壬寅 癸卯 甲辰 乙巳 丙午 丁未 戊申 己酉 庚戌 辛亥 壬子 癸丑 甲寅 乙卯 丙辰 丁巳 戊午 己未 庚申 辛酉 壬戌 癸亥

14. 动态内存

#include <iostream>
#include <memory>
#include <fmt/core.h>

struct Point {
  Point(double __x, double __y) : x(__x), y(__y) {
    std::cout << fmt::format("Pointer({0}, {1}) is constructed.\n", x, y);
  }
  ~Point() {
    std::cout << fmt::format("Pointer({0}, {1}) is deconstructed.\n", x, y);
  }
  friend std::ostream& operator<<(std::ostream& os, Point const& p) {
    return std::cout << fmt::format("Pointer({0}, {1})", p.x, p.y);
  }
  double x = 0.0l;
  double y = 0.0l;
};

int main() {
  std::cout << "Make shared pointer p." << std::endl;
  std::shared_ptr<Point> p = std::make_shared<Point>(1.0, 2.0);

  std::cout << "Copy shared pointer q from p." << std::endl;
  std::shared_ptr<Point> q(p);
  std::cout << *q << fmt::format(" has {0} references.", q.use_count())
            << std::endl;

  std::cout << "Let p point to a new Point." << std::endl;
  p.reset(new Point(3.0, 4.0));

  std::cout << "Construct a point with no name assigned." << std::endl;
  std::make_shared<Point>(5.0, 6.0);

  std::cout << "Now all Points will be deconstructed." << std::endl;
}
Make shared pointer p.
Pointer(1.0, 2.0) is constructed.
Copy shared pointer q from p.
Pointer(1.0, 2.0) has 2 references.
Let p point to a new Point.
Pointer(3.0, 4.0) is constructed.
Construct a point with no name assigned.
Pointer(5.0, 6.0) is constructed.
Pointer(5.0, 6.0) is deconstructed.
Now all Points will be deconstructed.
Pointer(1.0, 2.0) is deconstructed.
Pointer(3.0, 4.0) is deconstructed.

利用 shared_ptr 实现对象共享底层数据

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// StrBlob 的代理指针类
class StrBlobProxy;
class ConstStrBlobProxy;

class StrBlob {
public:
  // 友元
  friend std::ostream& operator<< (std::ostream& os, StrBlob const& rhs);
  friend StrBlobProxy;
  friend ConstStrBlobProxy;

  // 类型别名
  using size_type = std::vector<std::string>::size_type;
  using iterator = StrBlobProxy;
  using const_iterator = ConstStrBlobProxy;

  // 构造函数
  StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }
  StrBlob(std::initializer_list<std::string> il) :
    data(std::make_shared<std::vector<std::string>>(il)) { }

  size_type size() const { return data->size(); }
  bool empty() const { return data->empty(); }
  void push_back(std::string const& s) { data->push_back(s); }
  void pop_back();
  std::string& front();
  std::string& back();
  std::string const& front() const;
  std::string const& back() const;
  iterator begin();
  iterator end();
  const_iterator begin() const;
  const_iterator end() const;
private:
  std::shared_ptr<std::vector<std::string>> data;
  void check(size_type i, std::string const& msg) const;
};

// 代理指针类
class StrBlobProxy {
public:
  // 构造函数
  StrBlobProxy() : curr(0) { }
  StrBlobProxy(StrBlobProxy const& rhs) :
    wptr(rhs.wptr.lock()), curr(rhs.curr) { }
  StrBlobProxy(StrBlob& __str_blob, std::size_t __index = 0) :
    wptr(__str_blob.data), curr(__index) { }
  StrBlobProxy(StrBlob const& __str_blob, std::size_t __index = 0) :
    wptr(__str_blob.data), curr(__index) { }

  // 运算符重载
  StrBlobProxy& operator++() {
    ++curr;
    return *this;
  }
  StrBlobProxy operator++(int) {
    StrBlobProxy tmp(*this);
    ++curr;
    return tmp;
  }
  StrBlobProxy& operator--() {
    --curr;
    return *this;
  }
  StrBlobProxy operator--(int) {
    StrBlobProxy tmp(*this);
    --curr;
    return tmp;
  }
  StrBlobProxy operator+(std::size_t offset) {
    StrBlobProxy tmp(*this);
    tmp.curr += offset;
    return tmp;
  }
  std::string& operator*() {
    if (std::shared_ptr<std::vector<std::string>> sptr; (sptr = wptr.lock())) {
      return (*sptr)[curr];
    } else {
      throw  std::runtime_error("Unbound StrBlob pointer");
    }
  }
  bool operator==(StrBlobProxy const& rhs) {
    return curr == rhs.curr;
  }
  bool operator!=(StrBlobProxy const& rhs) {
    return curr != rhs.curr;
  }
protected:
  std::shared_ptr<std::vector<std::string>>
  check(std::size_t, std::string const &) const;
  std::weak_ptr<std::vector<std::string>> wptr;
  std::size_t curr;     // 在数组中的当前位置
};

/// 从 StrBlobProxy 派生出常量指针类
class ConstStrBlobProxy : public StrBlobProxy {
public:
  using StrBlobProxy::StrBlobProxy;
  std::string const& operator*() {
    if (std::shared_ptr<std::vector<std::string> const> sptr; (sptr = wptr.lock())) {
      return (*sptr)[curr];
    } else {
      throw  std::runtime_error("Unbound StrBlob pointer");
    }
  }
private:
  std::weak_ptr<std::vector<std::string> const> wptr;
};

void StrBlob::check(size_type i, std::string const& msg) const
{
  if (i >= data->size())
    throw std::out_of_range(msg);
}

std::string& StrBlob::front()
{
  check(0, "front on empty StrBlob.");
  return data->front();
}

std::string& StrBlob::back()
{
  check(0, "back on empty StrBlob.");
  return data->back();
}

std::string const& StrBlob::front() const
{
  check(0, "front on empty StrBlob.");
  return data->front();
}

std::string const& StrBlob::back() const
{
  check(0, "back on empty StrBlob.");
  return data->back();
}

void StrBlob::pop_back()
{
  check(0, "pop_back on empty StrBlob.");
  data->pop_back();
}

StrBlob::iterator StrBlob::begin() {
  return StrBlobProxy(*this, 0);
}

StrBlob::iterator StrBlob::end() {
  return StrBlobProxy(*this, data->size());
}

StrBlob::const_iterator StrBlob::begin() const {
  return ConstStrBlobProxy(*this, 0);
}

StrBlob::const_iterator StrBlob::end() const {
  return ConstStrBlobProxy(*this, data->size());
}

std::ostream& operator<< (std::ostream& os, StrBlob const& rhs)
{
  if (rhs.data->empty())
    return os;

  StrBlob::iterator __pos = rhs.begin();
  StrBlob::iterator const __end = rhs.end();

  os << "[";
  for (; __pos + 1 != __end; ++__pos) {
    os << *__pos << ", ";
  }
  return os << *__pos << "]" << std::endl;
}

int main()
{
  // b1 和 b2 共享同样的底层数据
  StrBlob b1 = {"hello", "world", "evil"};
  std::cout << b1;
  StrBlob b2 = b1;
  b2.push_back("C++");
  std::cout << b1;
}

shared_ptr 中使用自定义的 delete 操作

#include <iostream>
#include <memory>
#include <string>
#include <fmt/core.h>

struct Connection {
  Connection(int* const __descriptor) {
    std::cout << fmt::format("Connected to descriptor {0}.", *__descriptor) << std::endl;
    descriptor = *__descriptor;
  }
  int descriptor;
};

Connection connect(int* const __descriptor) {
  return Connection(__descriptor);
}

void disconnect(Connection* con) {
  std::cout << fmt::format("Disconnected from descriptor {0}.", con->descriptor) << std::endl;
}

int main()
{
  int descriptor = 1;
  // 建立一个连接,con 分配在栈内存上
  Connection con = connect(&descriptor);
  // 将连接绑定到共享指针上,虽然此处 con 不在共享内存上,指定了自定义的
  // disconnect 函数保证在 p 的生命同期结束时能够自动 disconnect
  std::shared_ptr<Connection> p(&con, disconnect);
}
Connected to descriptor 1.
Disconnected from descriptor 1.

shared_ptrweak_ptr 的常量性

#include <iostream>
#include <memory>

int main() {
  // 声明指针常量
  std::shared_ptr<int> const ip = std::make_shared<int>(1);
  *ip = 2;                  // 可以改变指针指向的值
  // ip.reset(new int(3));  // 无法改变指针本身
  std::cout << *ip << std::endl;

  // 声明指向常量的指针
  std::shared_ptr<const int> cip = std::make_shared<const int>(1);
  // *cip = 2;             // 无法改变指针指向的值
  cip.reset(new int(3));   // 可以将指针指向新值
  std::cout << *cip << std::endl;
}
2
3

15. allocator

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
  std::vector<int> ivec{1, 2, 3, 4, 5};
  std::allocator<int> alloc;
  // 分配 10 个 int 元素的内存
  auto p = alloc.allocate(ivec.size() * 2);
  // 拷贝 ivec 中的元素来构造从 p 开始的元素
  auto q = std::uninitialized_copy(ivec.begin(), ivec.end(), p);
  // 将剩余的元素初始化为 24
  std::uninitialized_fill_n(q, ivec.size(), 24);
  // 打印输出
  std::copy(p, p + ivec.size() * 2, std::ostream_iterator<int>(std::cout, ", "));
}
1, 2, 3, 4, 5, 24, 24, 24, 24, 24,

16. 单例模式

#include <iostream>
#include <thread>

// 通过局部静态变量的特性保证了线程安全;
// 不需要使用共享指针,代码简洁;
// 注意在使用的时候需要声明单例的引用 Single& 才能获取对象。
class Singleton {
public:
  ~Singleton() {
    std::cout << "Destructor called!" << std::endl;
  }
  Singleton(Singleton const&) = delete;
  Singleton& operator=(Singleton const&) = delete;
  static Singleton& get_instance() {
    // If control enters the declaration concurrently while the variable is
    // being initialized, the concurrent execution shall wait for completion of
    // the initialization.
    std::cout << "Get instance.\n" << std::flush;
    static Singleton __instance;
    return __instance;
  }
private:
  Singleton() {
    std::cout << "Constructor called!" << std::endl;
  }
};

int main() {
  std::thread t1(&Singleton::get_instance);
  std::thread t2(&Singleton::get_instance);
  std::thread t3(&Singleton::get_instance);
  t1.join();
  t2.join();
  t3.join();
}
Get instance.
Constructor called!
Get instance.
Get instance.
Destructor called!

17. 容器的常量性

#include <iostream>
#include <vector>
#include <string>

int main() {
  std::vector<std::string> const svec{"hello"};
  // *svec.back() = std::string("world");
}

18. 设计一个文本单词查询程序

#include <iostream>
#include <memory>
#include <iterator>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <fmt/core.h>

// 定义一个概念,类型 T 能够隐式转换为 std::size_t 类型(C++20 required)
template<typename T>
concept can_convert_to_size = std::is_convertible<T, std::size_t>::value;

// 根据 size 的值返回单数或复数形式
template<can_convert_to_size T>
std::string const& make_plural(T size, std::string const& __one, std::string const& __more)
{
  return (size > 1? __more : __one);
}

// 查询结果类
class QueryResult;

// 查询器类
class TextQuery {
public:
  using line_no = std::vector<std::string>::size_type;  // 行号数据类型
  TextQuery(std::ifstream&);
  QueryResult query(std::string const&) const;
private:
  std::shared_ptr<std::vector<std::string>> text;      // 保存读取的文本
  // 保存单词行号的集合的哈希表
  std::map<std::string, std::shared_ptr<std::set<line_no>>> word_map;
};

// 查询结果类
class QueryResult {
  friend std::ostream& operator<<(std::ostream& os, QueryResult const& rhs);
public:
  QueryResult(std::string __sought,
              std::shared_ptr<std::set<TextQuery::line_no>> __lines,
              std::shared_ptr<std::vector<std::string>> __text) :
    sought(__sought), lines(__lines), text(__text) { }
private:
  std::string sought;                                  // 查询的单词
  std::shared_ptr<std::set<TextQuery::line_no>> lines; // 出现的行号集合的指针
  std::shared_ptr<std::vector<std::string>> text;      // 输入文本的指针
};

TextQuery::TextQuery(std::ifstream& ifs) : text(new std::vector<std::string>)
{
  std::string buf, word;             // 行缓冲,单词缓冲
  line_no number;                    // 行号

  while (std::getline(ifs, buf)) {
    text->push_back(buf);            // 保存当前行到 text
    number = text->size() - 1;       // 当前行号

    std::istringstream line(buf);    // 从当前行生成字符串流
    while (line >> word) {
      auto& lines = word_map[word];  // 指向 word 对应 set 的共享指针
      if (!lines) {                  // 指针为空则分配一个新的 set
        lines.reset(new std::set<line_no>);
      }
      lines->insert(number);
    }
  }
}

QueryResult TextQuery::query(std::string const& sought) const
{
  // 如果没有找到 sought, 返回一个空 set 的指针
  static std::shared_ptr<std::set<TextQuery::line_no>>
    null(new std::set<TextQuery::line_no>);
  // 使用 find 查找单词,防止将单词加入到 map 中
  auto locate = word_map.find(sought);
  if (locate == word_map.end())
    return QueryResult(sought, null, text);  // 如果没找到 sought 返回空 set 指针
  else
    return QueryResult(sought, locate->second, text);
}

std::ostream& operator<<(std::ostream& os, QueryResult const& rhs)
{
  os << fmt::format("[{0}] occurs in {1} {2}.", rhs.sought, rhs.lines->size(),
                    make_plural(rhs.lines->size(), "line", "lines")) << std::endl;
  for (auto num : *rhs.lines) {
    os << fmt::format("\t(line {0}) {1}", num + 1, *(rhs.text->begin() + num))
       << std::endl;
  }
  return os;
}

// 运行查询操作
// TextQuery&: 传入一个查询器的引用
// std::istream_iterator<std::string>: 传入一个输入流迭代器,好处是可以兼容从标
// 准输入或从文件流或字符串流输入
void runQueries(TextQuery& __text_query, std::istream_iterator<std::string> __is_iter)
{
  std::string word;
  while (true) {
    std::cout << "Please input the word you want to query: " << std::flush;
    std::cout << std::endl;
    word = *__is_iter++;

    // 读到 q 的时候退出
    if (word == "q") {
      std::cout << "Quit!" << std::endl;
      break;
    }

    std::cout << __text_query.query(word);
  }
}

int main()
{
  std::ifstream ifs;
  ifs.open("/home/cycoe/input.txt");
  TextQuery tq(ifs);

  // 用字符串流模拟标准输入
  std::string command("it a q");
  std::istringstream iss(command);
  runQueries(tq, std::istream_iterator<std::string>(iss));
}
Please input the word you want to query: 
[it] occurs in 4 lines.
	(line 4) even far inland, it must be said that professional seamen were especially
	(line 17) vitality with which it seemed to be gifted. If it was a cetacean, it exceeded in
	(line 25) exaggerated views that saw it as a mile wide and three long--you could still
	(line 27) then known to ichthyologists, if it existed at all.
Please input the word you want to query: 
[a] occurs in 7 lines.
	(line 1) THE YEAR 1866 was marked by a bizarre development, an unexplained and downright
	(line 10) In essence, over a period of time several ships had encountered "an enormous
	(line 11) thing" at sea, a long spindle-shaped object, sometimes giving off a
	(line 17) vitality with which it seemed to be gifted. If it was a cetacean, it exceeded in
	(line 20) accepted the existence of such a monster sight unseen-- specifically, unseen by
	(line 24) timid estimates that gave the object a length of 200 feet, and ignoring those
	(line 25) exaggerated views that saw it as a mile wide and three long--you could still
Please input the word you want to query: 
Quit!

19. 树的遍历

#include <iostream>
#include <vector>
#include <memory>
#include <iterator>
#include <algorithm>

// 二叉树节点类
template<typename T>
struct Node {
  // 叶子节点的构造方法
  Node(T __value, Node* __left = nullptr, Node* __right = nullptr) :
    value(__value), left(__left), right(__right) { }
  // 拷贝节点(浅拷贝)
  Node(Node* rhs) :
    value(rhs->value), left(rhs->left), right(rhs->right) {
    // 将 rhs 的左右子树置为 nullptr,防止 rhs 析构时将子树析构
    // 神奇的是使用引用的话会提示引用的对象是一个右值
    rhs->left = nullptr;
    rhs->right = nullptr;
  }
  // 递归构造
  Node(T __value, Node __left, Node __right) : value(__value) {
    // 使用动态内存替换栈内存
    left = new Node(&__left);
    right = new Node(&__right);
  }
  ~Node() {
    std::cout << "Delete Node(" << value << ")." << std::endl;
    delete left;
    delete right;
  }
  T value;
  Node* left = nullptr;
  Node* right = nullptr;
};

template<typename T, typename Iter>
void preorder_traverse(Node<T>* tree, Iter iter)
{
  if (!tree)
    return;
  *iter++ = tree->value;
  preorder_traverse(tree->left, iter);
  preorder_traverse(tree->right, iter);
}

template<typename T, typename Iter>
void midorder_traverse(Node<T>* tree, Iter iter)
{
  if (!tree)
    return;
  midorder_traverse(tree->left, iter);
  *iter++ = tree->value;
  midorder_traverse(tree->right, iter);
}

template<typename T, typename Iter>
void postorder_traverse(Node<T>* tree, Iter iter)
{
  if (!tree)
    return;
  postorder_traverse(tree->left, iter);
  *iter++ = tree->value;
  postorder_traverse(tree->right, iter);
}

int main(int argc, char* argv[])
{
  Node<int>* itree = new Node<int>({0, {1, {2}, {3}}, {4, {5}, {6}}});

  std::cout << "Preorder traverse: ";
  preorder_traverse(itree, std::ostream_iterator<int>(std::cout, ", "));
  std::cout << std::endl;

  std::cout << "Midorder traverse: ";
  midorder_traverse(itree, std::ostream_iterator<int>(std::cout, ", "));
  std::cout << std::endl;

  std::cout << "Postorder traverse: ";
  postorder_traverse(itree, std::ostream_iterator<int>(std::cout, ", "));
  std::cout << std::endl;

  delete itree;

  return 0;
}
Delete Node(4).
Delete Node(6).
Delete Node(5).
Delete Node(1).
Delete Node(3).
Delete Node(2).
Preorder traverse: 0, 1, 2, 3, 4, 5, 6, 
Midorder traverse: 2, 1, 3, 0, 5, 4, 6, 
Postorder traverse: 2, 1, 3, 0, 5, 4, 6, 
Delete Node(0).
Delete Node(1).
Delete Node(2).
Delete Node(3).
Delete Node(4).
Delete Node(5).
Delete Node(6).

20. std::pairstd::tuple

std::pairstd::tupleC++ 中的通用工具类型, std::pair 表示会同时出现的变量 对,常用在 map 中。 std::tuple 表示异质元素列,可视为 std::pair 的扩展长度类型, 在 C++98 中通过对不同元素长度的 tuple 进行逐个定义实现, C++11 引入变长参数模板 后就可通过更加简单的方式定义。

20.1. std::pair

#include <iostream>
#include <utility>
#include <tuple>

int main()
{
  // 有两种方式构造一个 pair
  // 1. 使用 make_pair 方式进行构造
  auto p1 = std::make_pair(24, "Hello, world!");
  // 2. 使用 pair 的构造函数
  std::pair<int, std::string> p2(24, "Hello, C++!");

  std::string s1, s2;
  // 有三种方式获取 pair 中的元素
  // 1. 使用 first 或 second
  s1 = p1.second;
  // 2. 使用 tuple 的 get 元素方法,从 p1 中取 1 号元素,注意 tuple 不是寻常的容
  // 器因此不允许迭代,也因此 get 是个编译期操作。
  s1 = std::get<1>(p1);
  // 3. 使用 tie 方法构造一个接收器,并用 p2 给其赋值
  // 等价于 std::make_pair<std::ref(i2), std::ref(s2)> = p2;
  std::tie(std::ignore, s2) = p2;
  std::cout << "s1 is " << s1 << std::endl;
  std::cout << "s2 is " << s2 << std::endl;
}
s1 is Hello, world!
s2 is Hello, C++!

std::get<tuple> 头文件定义的一个方法,用于从 tuple 中获取元素。 get 方法是一 个编译期的方法,tuple 也无法像普通容器一样进行迭代,因为 C++ 是强类型静态语言, 而 tuple 又是一个异质列表,编译期必须在编译期确定要取出的元素类型

20.2. std::tuple

#include <iostream>
#include <utility>
#include <tuple>

int main()
{
  // tuple 的构造与 pair 类似
  auto t1 = std::make_tuple(24, "Hello, world!");
  std::tuple<int, std::string> t2(24, "Hello, C++!");

  // tuple 在比较时使用字典序依次对元素进行比较,但注意与 pair 不同的是 tuple 会
  // 进行类型转换,p1 会由std::tuple<int, char const*> 类型转换为 std::tuple<int,
  // std::string>
  if (t1 < t2)
    std::cout << "t1 is smaller than t2." << std::endl;
  else
    std::cout << "t2 is smaller than t1." << std::endl;
}
t2 is smaller than t1.

tuple 包含一些辅助函数

#include <iostream>
#include <tuple>
#include <utility>
#include <typeinfo>

int main()
{
  int n{};

  auto tt = std::tuple_cat(std::make_tuple(42, 3,14, "Hello"),
                           std::make_pair(24, "world"),
                           std::tie(n));
  std::cout << "tt has " << std::tuple_size<decltype(tt)>::value << " elements." << std::endl;
  std::cout << "Type of n is " << typeid(std::tuple_element<5, decltype(tt)>::type).name() << std::endl;
}
tt has 7 elements.
Type of n is PKc

利用模板元编程实现打印任意长度元组

#include <tuple>
#include <iostream>

// 打印 tuple 中 IDX 号元素的助手类模板(递归方法)
template<int IDX, int MAX, typename... Args>
struct PRINT_TUPLE
{
  static void print(std::ostream& os, const std::tuple<Args...>& t)
  {
    os << std::get<IDX>(t) << (IDX + 1 == MAX ? "" : ", ");
    PRINT_TUPLE<IDX + 1, MAX, Args...>::print(os, t);
  }
};

// PRINT_TUPLE 的偏特化模板(递归出口)
template<int MAX, typename... Args>
struct PRINT_TUPLE<MAX, MAX, Args...>
{
  static void print(std::ostream& os, const std::tuple<Args...>& t) { }
};

// 重载 << 运算符
template<typename... Args>
std::ostream& operator<<(std::ostream& os, const std::tuple<Args...> t)
{
  os << "[";
  PRINT_TUPLE<0, sizeof...(Args), Args...>::print(os, t);
  return os << "]";
}

int main()
{
  auto t = std::make_tuple(77, 3.14, "Hello, world!");
  std::cout << t << std::endl;
}
[77, 3.14, Hello, world!]

21. 智能指针与自定义 deleter

在使用智能指针时,我们可以自己指定使用的 deleter 删除器来指定删除行为

#include <iostream>
#include <vector>
#include <string>
#include <memory>

int main()
{
  // 创建一个指向 string 的智能指针,同时指定 deleter
  std::shared_ptr<std::string> nico(new std::string("Nico"),
                                    [] (std::string* p) {
                                      std::cout << "delete " << *p << std::endl;
                                    });
  // 创建一个名字顺序列表
  std::vector<std::shared_ptr<std::string>> names;
  // 将名字指针加入列表
  names.push_back(nico);
  names.push_back(nico);

  nico = nullptr;
}
delete Nico

另一种情况是在处理 Array 时, shared_ptr 默认的删除器是 delete 而不是 delete[] ,当处理 Array 需要传入自定义的删除器

// 能通过编译但实际会导致不完全释放
// std::shared_ptr<int> p(new int[10]);
// 构造 shared_ptr 的同时指定删除器
std::shared_ptr<int> p(new int[10], [] (int* p) { delete[] p; });
// 或者使用为 =unique_ptr= 而提供的辅助函数
std::shared_ptr<int> q(new int[10], std::default_delete<int[]>());

// 对于 unique_ptr 我们可以只提供对应元素的类型甚至不用指明长度
std::unique_ptr<int[]> p(new int[10]);
// 但是对于 shared_ptr 就无法通过编译
// std::shared_ptr<int[]> p(new int[10]);

// 另外,对于 unique_ptr 在指明删除器时需要指明第一个模板参数
std::unique_ptr<int, void(*)(int*)> p(new int[10], [] (int* p) { delete[] p; });

22. Type Trait

#include <iostream>
#include <type_traits>

// 标准库中所有的类型判别式的产出类型都特化自一个叫 =std::integral_constant= 的模板
template<typename T, T val>
struct integral_constant
{
  static constexpr T value = val;
  typedef T value_type;
  typedef integral_constant<T, value> type;
  constexpr operator value_type() { return value; }
};

typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;

// 定义一个 Person 类
class Person { std::string name; };

// 自定义一个 type predicate
template<typename T>
struct is_Person
{
  static constexpr bool value = std::is_same<Person, T>::value;
  typedef is_Person<T> type;
};

int main()
{
  std::cout << is_Person<Person>::value << std::endl;
}
1

23. 辅助函数

23.1. std::max

#include <algorithm>
#include <iostream>

// 在 std::max(const T&, const T&) 外面加了打印包装
template<typename T>
const T& max(const T& a, const T& b)
{
  std::cout << "Use std::max(const T&, const T&);" << std::endl;
  return std::max(a, b);
}

// 在 std::max(std::initializer_list<T>) 外面加了打印包装
template<typename T>
T max(std::initializer_list<T> initList)
{
  std::cout << "Use std::max(std::initializer_list<T>);" << std::endl;
  return std::max(initList);
}

template<typename T1, typename T2>
auto max(const T1& a, const T2& b) -> std::common_type_t<T1, T2>
{
  std::cout << "Use max(const T1& a, const T2& b);" << std::endl;
  return a > b ? a : b;
}

// 实现一个变参的 max 函数模板,注意与初始化参数列的区别
template<typename Arg, typename... Args>
auto max(Arg arg, Args... args)
{
  std::cout << "Use auto max(Arg arg, Args... args);" << std::endl;
  return arg > max(args...) ? arg : max(args...);
}

int main()
{
  std::cout << max<int>(1, 2) << std::endl;
  std::cout << max<int>({1, 2, 3}) << std::endl;
  std::cout << max<int, float, double>(1, 2.2, 3.14) << std::endl;
}
Use std::max(const T&, const T&);
2
Use std::max(std::initializer_list<T>);
3
Use auto max(Arg arg, Args... args);
Use max(const T1& a, const T2& b);
Use max(const T1& a, const T2& b);
3.14

23.2. std::swap

标准库内有不同的 std::swap 重载版本,你也可以重载自己的版本

#include <utility>
#include <iostream>

// 在 std::swap(T&, T&b) 外面加了打印包装
template<typename T>
inline void swap(T& a, T& b)
{
  std::cout << "Use std::swap(T&, T&b);" << std::endl;
  std::swap(a, b);
}

// 在 std::swap(T (&a)[N], T (&b)[N]) 外面加了打印包装
template<typename T, size_t N>
void swap(T (&a)[N], T (&b)[N])
{
  std::cout << "Use std::swap(T (&a)[N], T (&b)[N]);" << std::endl;
  std::swap(a, b);
}

int main()
{
  int ia = 1;
  int ib = 2;
  int iarra[3] = {1, 2, 3};
  int iarrb[3] = {4, 5, 6};

  swap(ia, ib);
  swap(iarra, iarrb);
}
Use std::swap(T&, T&b);
Use std::swap(T (&a)[N], T (&b)[N]);

23.3. ratio<> 编译期分数运算

std::ratio 可以实现编译期约分

#include <ratio>
#include <iostream>

int main()
{
  // 定义一个分数类型
  using FiveThirds = std::ratio<5, 3>;
  std::cout << FiveThirds::num << "/" << FiveThirds::den << std::endl;

  // 分数会在编译期约分
  using Two = std::ratio<30, 15>;
  std::cout << Two::num << "/" << Two::den << std::endl;

  // 两分数相加
  using ElevenThirds = std::ratio_add<FiveThirds, Two>;
  std::cout << ElevenThirds::num << "/" << ElevenThirds::den << std::endl;

  // 使用预定义的 ratio 单位
  std::cout << "1 TB = " << std::tera::num << " Bytes." << std::endl;
}
5/3
2/1
11/3
1 TB = 1000000000000 Bytes.

23.4. chrono

chrono 库中常用的 duration 表示

#include <chrono>

int main()
{
  // chrono 使用一个数值和一个 tick 大小表示一个 duration
  std::chrono::duration<int> twentySeconds(20);
  std::chrono::duration<double, std::ratio<60>> halfMinute(0.5);
  std::chrono::duration<long, std::ratio<1, 1000>> oneMillisecond(1);

  // 或者使用 chrono 库中定义的常用时间单位
  std::chrono::seconds tenSeconds(10);
  std::chrono::hours aDay(24);
}

23.4.1. Duration 的算数运算

此处时间段的算数计算与隐式转换规则

  1. 时间段可进行加减乘除与取模等算术运算
  2. duration 可隐式转换为更精确的单位(tick 更小),但反之不行。这也就是为什么要 将 remain 变量定义为 seconds 类型的原因
#include <chrono>
#include <iostream>

using namespace std::chrono;

int main()
{
  // 计算一天有多少秒
  seconds secondsInADay(hours(24));
  std::cout << secondsInADay.count() << " seconds in a day." << std::endl;

  // 计算一天过去 1 小时 15 分钟 35 秒后还剩余多少时间
  hours aDay(24);
  seconds remain(aDay);
  remain = remain - hours(1) - minutes(15) - seconds(35);
  std::cout << remain.count() << " seconds remains." << std::endl;
}
86400 seconds in a day.
81865 seconds remains.

23.4.2. Duration 的其它操作

上面代码块中的 std::chrono::duration::count 正是 Duration 的一个操作,用于返回一 个时间段对应 tick 单位的计数,可利用它实现打印一个 Duration

#include <chrono>
#include <iostream>

using namespace std::chrono;

// 重载 std::chrono::duration 的 << 运算符
template<typename C, typename R>
std::ostream& operator<<(std::ostream& os, duration<C, R> d)
{
  return os << "[" << d.count() << " of " << R::num << "/" << R::den << "]";
}

int main()
{
  milliseconds d(42);
  std::cout << d << std::endl;
  hours day(24);
  std::cout << day << std::endl;
}
[42 of 1/1000]
[24 of 3600/1]
Author: Cycoe (cycoejoo@163.com)
Date: <2020-06-10 Wed 10:34>
Generator: Emacs 29.1 (Org mode 9.6.6)
Built: <2024-01-27 Sat 21:20>