这篇文章用于澄清大家对一些 c++ 语法/标准库问题的误区,以正视听。
  1. 有符号整形算术溢出 UB。
  • 但是,无符号整数整形溢出 不是 UB。
  • 左/右移负数或大于等于数据类型宽度的值 也是 UB。(如:uint64_t 左移 $64$ 位)c++20 标准规定了负数进行左移右移所得结果。此前,负数左移未定义,负数右移由实现定义。详见 cppreference
  • 整数转换中,如果目标类型有符号,那么当源整数能以目标类型表示时不会更改它的值。否则结果

    • 由实现定义 (C++20 前)
    • 等于源值模 $2^n$ 的唯一目标类型值,其中 $n$ 用于表示目标类型的位数 (C++20 起)

    这与未定义的有符号算术溢出不同,并意味着 int32_t(int64_t(INT64_MAX)) 一定会得到 -1

  1. 带文件操作的程序正常运行结束,不需要 fclose
  • 手动 fclose 很有可能带来更多问题,尤其是当 cin 关闭同步流的时候。
  • c++ 流在执行析构函数的时候会刷新缓冲区并自动关闭文件。
  • c 输出函数会在 main 结束后完成对缓冲区的刷新。只要程序正常终止,缓冲区内的所有内容都会被写入文件,无需手动 fclose
  1. 关同步流最简单的方式是 cin.tie(0)->sync_with_stdio(0)
  • cout.tie(0) 是完全没有必要的,因为 cout 默认就没有 tie 任何东西。
  • 没有任何证据表明在 freopen 前关闭流同步会造成任何问题。
  1. 请慎用 __int128
  • STL 对 __int128 相关特化(如 std::abs<__int128>, std::hash<__int128>, std::is_integral_v<__int128>)均在 gnu++ 标准而非 c++ 标准中,故考试编译选项中无法使用。
  • 数组下标访问使用 __int128 可能造成编译器 bug(于 14.1 修复,影响 gcc9.3),请务必留意。一份复现样例在 这里(这是缩减后的代码,gcc 12 中不会出问题,但事实上原始代码在 12 也会爆)。
  1. 需要慎用 size_t。由于其是无符号整数类型,将 size_t(0) 减去 $1$ 会得到 UINT64_MAX,而且 -1 < size_t(0) 会得到 $0$。由于其是 STL 容器 size() 函数的返回值类型,请务必注意这一点。
  2. 结构化绑定在 c++17 才进入标准,但是在正式比赛中可以使用(只会有 warning)。此外,std::array 也是可以通过结构化绑定拆开的,这使得其成为 std::tuple 在元素类型相同时的上位替代。
std::array<int, 4> qwq = {1, 2, 3, 4};
auto [a, b, c, d] = qwq;
std::cout << a << ' ' << b << ' ' << c << ' ' << d << '\n'; // 输出:1 2 3 4
  1. 如果你写的一个成员函数要区分在常量/非常量语境下的返回值,可以这样写:
struct QwQ {
  int a[5][5];
  const int *operator[](int _id) const & { return a[_id]; } // (1)
  int *operator[](int _id) & { return a[_id]; } // (2)
  int operator*(const QwQ &rhs) const { return (*this)[0][0] * rhs[1][1]; } // 调用 (2)
};
  1. std::unordered_map 的清空复杂度关于桶数而非元素数线性,请务必注意。
  • 例如,你应该通过 decltype(x){}.swap(x) 或者 x.erase(x.begin(), x.end()) 清空 x,而非 x.clear()
  • 值得注意的是,__gnu_pbds::gp_hash_table 在效率方面优于 std::unordered_map,可以考虑自己写一个不容易被卡的哈希函数(例如:__builtin_bswap64(x*C),$C$ 是大奇数)并使用 gp_hash_table
  1. 在考场上,如何判断是你写挂了还是踩到编译器 bug 了?
  • -Wall -Wextra -Wshadow -Wconversion -fsanitize=address,undefined。如果报错,大概率是你写挂了。
  • 检查是否在关同步流后仍然使用了 cstdio 的函数,或者不当使用了 fclose
  • 关闭 -O2。如果通过了上一条测试,并且关闭 -O2 后得到了正确的输出,那么有 一定概率 是真的遇到编译器 bug 了。
  • 目前,笔者只听说有选手由于 __int128 下标访问触发过编译器 bug,其余问题在 OI 中触发概率极低,一般可忽略。

标签: none

添加新评论