C++ Primer 学习笔记 0x00

C++ Primer 学习笔记 0x00

RayAlto OP

1. std::clog

除了 std::cin, std::cout, std::cerr 还有一个与 C 输出流 stderr 关联但不同于 std::cerrstd::clog ,它不自动冲入这些流,且不自动与 std::cout tie()

1
std::clog << "hello world\n";

这些里面 c 表示 char

2. 字符字面值

1
2
std::cout << '\x82'  // chr(0x82),  'R'
<< '\202'; // chr(0o202), 'R'

3. 初始化

3.1. 精度损失

1
2
3
4
int i = 0;
int i = {0};
int i {0};
int i(0);

这些都是正确的初始化语句,但:

1
2
double d = 1.14514;
int i {d};

会使编译器产生 warning ,因为从 doubleint 会损失精度,而:

1
2
double d = 1.14514;
int i (d);

不会使编译器产生 warning 。

3.2. 类成员初始化顺序

1
2
3
4
5
6
7
class Foo {
int i;
int j;

public:
explicit Foo(int v) : j(v), i(j) {}
};

类成员初始化的顺序和代码中的顺序一致,也就是说 Foo 的成员的初始化顺序为 i -> j ,所以构造函数中初始化列表就是有问题的,看起来好像先用 v 初始化了 j ,然后用 j 初始化了 i 。实际上会先用 j 初始化 i ,而这时 j 是未初始化的,所以是未定义行为。

3.3. 委托构造

1
2
3
4
5
6
7
8
9
class Foo {
int i;
int j;

public:
Foo() : Foo(0, 0) {}
Foo(int x) : Foo(x, 0) {}
Foo(int x, int y) : i(x), j(y) {}
};

这种叫委托构造,委托构造后面不能再接初始化列表了,比如:

1
2
3
4
5
6
7
8
9
10
class Foo {
int i;
int j;
int k;

public:
Foo() : Foo(0, 0), k(0) {} // Error
Foo(int x) : Foo(x, 0), k(0) {} // Error
Foo(int x, int y) : i(x), j(y), k(0) {}
};

3.4. 使用 static_cast 显式使用构造函数

众所周知构造函数前面加上 explicit 就禁止了隐式转换:

1
2
3
4
5
6
class Foo {
int i;

public:
explicit Foo(int x) : i(x) {}
};

比如有这样的函数:

1
void foo(const Foo& f) {}

像这样用 static_cast 也可以实现转换:

1
foo(static_cast<Foo>(1));

4. decltype

decltype 用多个 ( ) 包围时推断出来的类型为引用类型

1
2
int i = 0;
decltype((i)) ii; // 错误,引用类型必须被初始化

因为 i 表示变量,而 (i) 是一个表达式,它的结果是一个整数

但多个 ( ) 包围一个返回值时不会被推断成引用类型:

1
2
int foo();
decltype(( foo() )) ii; // OK

5. sizeof

1
2
3
4
5
6
int i = 114514;

sizeof(i);
sizeof i
sizeof(int);
sizeof int;

四种 sizeof 用法都是正确的,但习惯上求类型的大小用 sizeof(type) ,求某个变量的大小用 sizeof var

6. 隐式类型转换

在一下情况下编译器会自动地转换运算对象的类型:

  1. 在大多数表达式中,比 int 类型小的整数类型值首先提升为较大的整数类型
  2. 在条件中,非 bool 值会被转换成 bool
  3. 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型
  4. 算术运算或关系运算中如果有多种类型的运算对象,则会转换成同一种类型
  5. 。。。

7. stdexcept

exception 头文件只定义 std::exception 类作为其他异常的基类, stdexcept 头文件定义了供用户/库使用的很多异常类

标准异常:

  • logic_error :程序内部错误的逻辑导致的错误,比如违背逻辑前提条件或打破了不可变条件,可能被避免
    • invalid_argument :数值未被接受,比如 std::stoi 传入非整数字符串
    • domain_error :定义域错误,数学表达式不合法或不能以数学方式表示,比如,标准库组件不会抛出此异常
    • length_error :超出方法实现的长度限制导致的错误,比如 std::string 的长度超过了 std::string::max_size()
    • out_of_range :试图访问合法范围外的元素导致的错误
    • future_error :异步和共享状态等机制失败时导致的错误
  • runtime_error :源于程序作用域外,且不可被轻易预测到的错误
    • range_error :值域错误(计算结果超过了有意义的值域范围)
    • overflow_error :计算上溢(计算结果对于目标类型过大)
    • underflow_error :计算下溢(计算结果是无意义浮点值)

8. 字面值常量类

满足以下条件的类为字面值常量类:

  • 数据成员都必须是字面值类型
  • 至少含有一个 constexpr 构造函数
  • 类内初始化必须使用常量表达式或 constexpr 函数
  • 使用默认析构函数

9. while (std::cin >> var) 的原理

约定俗成这么用,但没仔细思考过,众所周知这个 >> 的原型差不多是:

1
std::cin& operator>>(std::cin& in, T& var);

既然返回的是 std::cin& ,为什么能作为 while 的条件呢,首先想到的是有 operator bool() ,查了一下还真是:

我这里是 ArchLinux 的 core/gcc 13.2.1-3

1
2
3
/* -- /usr/include/c++/13.2.1/bits/basic_ios.h:117 -- */
explicit operator bool() const
{ return !this->fail(); }

10. 关联输入和输出流

把一个输入流与输出流关联起来后,在使用这个输入流进行输入前,关联的输出流会先被刷新,也就是说比如:

1
2
3
4
std::string str;
// 下面的输出没有换行或 std::flush ,所以不会发生刷新
std::cout << "Input: ";
std::cin >> str;

在进行输入前, “Input: “ 会被先打印出来,所以交互式系统关联输入流和输出流可以帮开发者完成刷新操作。绑定操作通过 tie 完成,输入新绑定的流的指针,返回之前绑定的流的指针,传入 nullptr 表示解绑:

1
2
std::ostream* out = std::cin.tie(&std::cout);
std::ostream* out = std::cin.tie(nullptr);

11. istringstream

可以像这样更精细地处理用户输入:

1
2
3
4
5
6
7
8
std::string line;
int i;
while (std::getline(std::cin, line)) {
std::istringstream iss(line);
while (iss >> i) {
std::cout << i << '\n';
}
}