X

曜彤.手记

随记,关于互联网技术、产品与创业

吉 ICP 备10004938号

《现代 C++ 教程:高速上手 C++11/14/17/20》读书笔记


又一本知乎上推荐的 C++ 书籍,本身只有不到 100 页,因此可以用来花点时间快速查缺补漏一下。

一、迈向现代 C++

  1. (Page:8)自 C++11 以后被弃用的特性
  1. (Page:9)在不得不使用 C 时,应该注意使用 extern "C" 这种特性,将 C 语言的代码与 C++ 代码进行分离编译,再统一链接这种做法。
// foo.h
#ifdef __cplusplus
extern "C" {
#endif
int add(int x, int y);
#ifdef __cplusplus
}
#endif
// foo.c
int add(int x, int y) {
  return x + y;
}
// main.cc
#include "foo.h"
#include <iostream> 
#include <functional>
int main(int argc, char **argv) {
  // out 为一个自动推导的 const 引用.
  [out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){
    out.get() << ".\n";
  }();
  return 0;
}
  1. (Page:10)C 和 C++ 互相兼容情况

二、语言可用性的强化

  1. (Page:10)语言可用性:指那些发生在运行时之前的语言行为。
  2. (Page:12)传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。而C++ 不允许直接将 void* 隐式转换到其他类型
  3. (Page:12)nullptr 的类型为 nullptr_t,能够隐式转换为任何指针或成员指针的类型,也能和他们进行相等性上的比较
  4. (Page:15)从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句:
constexpr int fib(const int n) {
  if (n == 1) return 1;
  if (n == 2) return 1;
  return fib(n - 1) + fib(n - 2);
}
  1. (Page:16)C++17 消除了对 if/switch 变量声明位置(可以在结构内部声明)的限制:
std::vector<int> vec = {1, 2, 3, 4};
if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) {
  *itr = 4;
}
  1. (Page:18)“列表初始化”与“结构化绑定”:
#include <iostream>
#include <tuple>

std::tuple<int, double, std::string> foo() {
  return std::make_tuple(1, 2.3, "456");
}
int main(int argc, char** argv) {
  auto [x, y, z] = foo();
  std::cout << x << ", " << y << ", " << z << std::endl;
  return 0;
}
  1. (Page:18)自动类型推导:
auto arr = new auto(10);  // arr -> int*.
template<typename T, typename U>
auto add(T x, U y) {  // auto add(T x, U y) -> decltype(x + y) {.
  return x + y;
}
std::string& foo();
decltype(auto) bar() {
  return foo();
}
  1. (Page:22)C++17 允许在代码中声明常量表达式(constexpr)判断条件
template<typename T>
auto foo(const T& t) {
  if constexpr (std::is_integral<T>::value) {
    return t + 1;
  } else {
    return t + 0.001;
  }
}
  1. (Page:24)传统 C++ 中,模板只有在使用时才会被编译器实例化。换句话说,只要在每个“编译单元”中,编译的代码中遇到了被完整引用的模板,都会进行实例化,而重复实例化便会导致编译时间的增加。对于这个问题可以通过“外部模板”来解决(缩短编译时间减小中间 “.o” 文件的大小)。
extern template class std::vector<double>;
  1. (Page:25)模板是用来产生类型的typedef 仅能用来定义类型,而无法定义模板别名。这里使用 using 便可以做到“模板别名”:
template<typename T>
using Foo = Bar<std::vector<T>, std::string>
  1. (Page:26)默认模板参数
template<typename T = int, typename U = int>
auto add(T x, U y) {
  return x + y;
}
  1. (Page:26)变长模板参数
template<typename T>
void foo(T arg) {
  std::cout << arg << std::endl;
}
template<typename T, typename... Ts>
void foo(T arg, Ts... args) {
  std::cout << arg << std::endl;
  foo(args...);
}
template<typename T, typename... Ts>
void foo(T arg, Ts... args) {
  std::cout << arg << std::endl;
  if constexpr (sizeof...(args) > 0) foo(args...);
}
template<typename T, typename... Ts>
void foo(T arg, Ts... args) {
  std::cout << arg << std::endl;
  (void) std::initializer_list<T>{
    ([&args]{
      std::cout << args << std::endl;
    }(), arg)...
  };
}
template<typename... Ts>
void foo(Ts... args) {
  auto _t = [](auto arg) {
    std::cout << arg << std::endl;
  };
  (_t(args), ...);  // comma expression.
}
  1. (Page:28)非类型模板参数
template<auto size>
void foo() {
  int arr[size] = {};
  std::cout << (sizeof(arr) / sizeof(int)) << std::endl;
}
int main(int argc, char** argv) {
  foo<10>();
  return 0;
}
  1. (Page:29)委托构造:
class A {
 public:
  int v;
  A() = default;
  A(int x) : A() {  // 委托另一个构造函数;
    v = x;
  }
};
  1. (Page:30)继承构造:
class A {
 public:
  int v;
  A() = default;
  A(int x) : A() { v = x; }
};
class B : A {
 public:
  using A::A;  // 继承基类的构造函数;
};
  1. (Page:31)fianl 关键字:防止类被继续继承以及终止虚函数继续重载override 关键字:显式告知编译器进行重载,防止错误重载的发生。
class A final {
  virtual void foo() final {
    std::cout << "A" << std::endl;
  }
};
  1. (Page:32)=default=delete
struct A {
  A() = default;
  A& operator=(const A&) = delete;
};

三、语言运行期的强化

  1. (Page:34)Lambda 表达式:唯一需要注意的是,Lambda 表达式的“值拷贝”是在定义时拷贝的。C++14 允许捕获的成员用任意的表达式进行初始化,被声明的捕获变量类型会根据表达式进行判断。
int main(int argc, char** argv) {
  auto x = std::make_unique<int>(1);
  auto y = [v = std::move(x)]() {  // “v” 使用表达式进行初始化;
    return *v + 10;
  };
  std::cout << y() << std::endl;
}
auto add = [](auto x, auto y) {
  return x + y;
}
  1. (Page:36)当 Lambda 表达式的捕获列表为空时,闭包对象还能够转换为函数指针进行传递。C++11 中所有“可调用对象”都可以通过 std::function 进行表示,并且可以同 std::bind 一起使用来绑定部分参数。
  2. (Page:40)“常量左值引用”为万能引用类型,可以引用任何类型的右值。而常量左值引用允许绑定到右值则源于 Fortran 的需要
  3. (Page:43)引用折叠:对一个引用类型继续进行引用。无论模板参数是什么类型的引用,当且仅当实参类型为右引用时,模板参数才能被推导为右引用类型

int main(int argc, char** argv) {
  std::vector<int> v{1, 2, 3};
  for (auto&& i : v) {  // int&.
    std::cout << i << std::endl;
  }
}
// std::forward 的简单实现;
template<typename T>                         
T&& forward(std::remove_reference_t<T>& param) {  
  return static_cast<T&&>(param);
}
// std::move 的简单实现;
template<typename T>
std::remove_reference_t<T>&& move(T&& t) {
  return static_cast<std::remove_reference_t<T>&&>(t);
}

四、容器

  1. (Page:47)同传统数组一样,std::array 的数组大小参数也必须是常量表达式:
constexpr int len = 4;
std::array<int, len> = {1, 2, 3, 4};
  1. (Page:48)C++ 中的有序容器 std::mapstd::set,这些元素内部通过红黑树进行实现,元素插入和搜索的平均复杂度均为 “O(log(size))”。而对应的无序容器则使用“Hash 表”实现,对应的操作复杂度为常数级。
  2. (Page:51)C++17 运行时遍历 std::tuple
int main() {
  std::tuple t{42, 'a', 4.2}; 
  std::apply(
    [](auto&&... args) {  // 参数为该 Tuple 的所有元素;
      ((std::cout << args << '\n'), ...);
    }, t);
}

五、智能指针与内存管理

  1. (Page:54)C++11 为什么没有提供 std::make_unique?Herb Sutter 答:“被他们忘记了”。

六、正则表达式

(略)

七、并行与并发

  1. (Page:62)std::thread 用于创建一个执行的线程实例:
#include <thread>
int main() {
  std::thread t([](){
    std::cout << "Hello, world!" << std::endl;
  });
  t.join();
  return 0;
}

(其他略,暂时不需要这方面知识)



这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)