C++ Tips
收集一下奇奇怪怪的玩意(包括冷门用法)。。。。
虚函数的默认参数
#include <iostream>
using namespace std;
class Base {
public:
virtual void show(int x = 10) = 0;
virtual ~Base() { cout << "~Base\n"; }
};
class Derive : public Base {
public:
void show(int x = 20) override {
cout << "x = " << x << endl;
}
~Derive() override { cout << "~Derive\n"; }
};
int main(int argc, char* argv[]) {
Base* bb = new Derive();
bb->show();
delete(bb);
return 0;
}c++ alignas 和 alignof
alignas 是 C++11 引入的对齐说明符,用于指定变量或类型的对齐要求。
基本用法
#include <iostream>
using namespace std;
// 指定结构体16字节对齐
struct alignas(16) AlignedStruct {
char data[8];
};
int main() {
AlignedStruct s;
cout << "Alignment: " << alignof(AlignedStruct) << endl;
cout << "Size: " << sizeof(AlignedStruct) << endl;
return 0;
}变量对齐
#include <iostream>
using namespace std;
int main() {
// 指定变量对齐
alignas(32) int array[10];
alignas(64) char buffer[128];
cout << "Array alignment: " << alignof(decltype(array)) << endl;
cout << "Buffer alignment: " << alignof(decltype(buffer)) << endl;
return 0;
}类成员对齐
#include <iostream>
using namespace std;
class MyClass {
public:
char c;
alignas(8) int i; // 这个成员8字节对齐
double d;
};
int main() {
MyClass obj;
cout << "Class size: " << sizeof(MyClass) << endl;
cout << "Class alignment: " << alignof(MyClass) << endl;
return 0;
}cout 的缓冲区
std::cout 默认是有缓冲的。如果你在写高性能代码,不理解缓冲就是在浪费 CPU 周期。
1. 缓冲模式
- 全缓冲 (Fully Buffered):缓冲区满了才输出。通常在
cout被重定向到文件时发生。 - 行缓冲 (Line Buffered):遇到换行符
\n就输出。这是cout连接到终端时的默认行为。 - 无缓冲 (Unbuffered):数据直接输出。
std::cerr默认就是这种模式,因为错误信息需要立即显示。
2. endl vs \n
这是新手最容易犯错的地方:
std::endl:输出\n并强制调用flush()。在循环里用endl会导致频繁的系统调用,性能烂得像坨屎。\n:只输出换行符。让缓冲区自己决定什么时候该刷新。
如果你不需要立即看到输出(比如在跑一个几百万次的循环),永远用 \n。
3. 性能优化
如果你觉得 C++ 的 I/O 慢,通常是因为它在同步老旧的 C stdio。
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);这两行代码能让你的 I/O 速度提升一个量级,但你需要明白它们在干什么:
sync_with_stdio(false):默认情况下,C++ 的cout要和 C 的printf同步,共享同一个缓冲区。这就像是两个人共用一个笔记本,每次写字都要互相打招呼,慢得要死。关掉它,cout就能拥有自己的独立缓冲区,跑得飞快。代价:你不能再在同一个程序里混用cout和printf,否则输出顺序会乱套。cin.tie(nullptr):默认情况下cin和cout是绑定的。这意味着每次你从cin读数据前,cout都会被强制刷新(flush)。这是为了保证你在输入前能看到提示语(比如 “请输入:”)。但在处理大量数据时,这种频繁的自动刷新就是性能杀手。解绑后,cout只有在缓冲区满或者你手动刷新时才会输出。
4. std::cout vs C++23 std::print
C++23 引入了 <print>,这才是现代 C++ 该有的样子。
- 类型安全与格式化:
cout靠一堆<<拼接,代码又臭又长。std::print使用类似std::format的语法,类型安全且可读性极高。 - 性能:
std::print通常比std::cout快。它直接将格式化后的字符串交给底层系统调用,避免了ostream那套复杂的内部状态维护和多次缓冲区拷贝。 - 原子性:在多线程环境下,多个线程同时用
cout输出会导致字符交织(除非你加锁)。std::print的单次调用通常是原子的,输出不会被打碎。 - Unicode 支持:
std::print原生支持 UTF-8 输出到终端,而cout在 Windows 等平台上处理 Unicode 就是个噩梦。
结论:如果你的编译器支持 C++23,赶紧把那堆垃圾 cout 换成 std::print。
operator"" 用户自定义字面量
C++11 引入了用户自定义字面量(User-defined literals),通过 operator"" 让你定义自己的字面量后缀。这玩意儿能让代码读起来像自然语言,但用不好就是灾难。
基本语法
#include <iostream>
using namespace std;
// 整型字面量后缀
constexpr long long operator"" _KB(unsigned long long value) {
return value * 1024;
}
// 浮点型字面量后缀
constexpr long double operator"" _deg(long double degrees) {
return degrees * 3.14159265358979323846L / 180.0;
}
// 字符串字面量后缀
string operator"" _str(const char* str, size_t len) {
return string(str, len);
}
int main() {
auto size = 4_KB; // 4096
auto angle = 90.0_deg; // 1.5708...
auto text = "hello"_str; // std::string
cout << "Size: " << size << endl;
cout << "Angle: " << angle << endl;
cout << "Text: " << text << endl;
return 0;
}参数类型规则
operator"" 的参数类型由字面量类型决定,这是编译器强制的:
| 字面量类型 | 参数类型 | 示例 |
|---|---|---|
| 整型 | unsigned long long | 123_suffix |
| 浮点型 | long double | 3.14_suffix |
| 字符串 | (const char*, size_t) | "abc"_suffix |
| 字符 | char | 'a'_suffix |
#include <iostream>
using namespace std;
// 字符字面量后缀
char operator"" _upper(char c) {
return (c >= 'a' && c <= 'z') ? c - 32 : c;
}
int main() {
cout << 'a'_upper << endl; // 'A'
cout << 'Z'_upper << endl; // 'Z'
return 0;
}实际应用场景
1. 类型安全的单位系统
#include <iostream>
using namespace std;
struct Distance {
long double meters;
constexpr Distance(long double m) : meters(m) {}
};
constexpr Distance operator"" _m(long double value) {
return Distance(value);
}
constexpr Distance operator"" _km(long double value) {
return Distance(value * 1000);
}
constexpr Distance operator"" _cm(long double value) {
return Distance(value / 100);
}
int main() {
auto d1 = 1.5_m;
auto d2 = 2.0_km;
auto d3 = 150.0_cm;
cout << "d1: " << d1.meters << " m" << endl;
cout << "d2: " << d2.meters << " m" << endl;
cout << "d3: " << d3.meters << " m" << endl;
return 0;
}2. 编译期字符串哈希
#include <iostream>
using namespace std;
// FNV-1a 哈希算法
constexpr unsigned long long operator"" _hash(const char* str, size_t len) {
unsigned long long hash = 14695981039346656037ULL;
for (size_t i = 0; i < len; ++i) {
hash ^= static_cast<unsigned long long>(str[i]);
hash *= 1099511628211ULL;
}
return hash;
}
int main() {
constexpr auto h1 = "hello"_hash;
constexpr auto h2 = "world"_hash;
cout << "hash(\"hello\"): " << h1 << endl;
cout << "hash(\"world\"): " << h2 << endl;
// 编译期计算,零运行时开销
static_assert("test"_hash == 13582463669883289057ULL, "Hash mismatch");
return 0;
}3. 时间字面量
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
constexpr seconds operator"" _s(unsigned long long sec) {
return seconds(sec);
}
constexpr milliseconds operator"" _ms(unsigned long long ms) {
return milliseconds(ms);
}
constexpr microseconds operator"" _us(unsigned long long us) {
return microseconds(us);
}
int main() {
auto timeout = 5_s;
auto delay = 100_ms;
auto interval = 500_us;
cout << "Timeout: " << timeout.count() << " seconds" << endl;
cout << "Delay: " << delay.count() << " milliseconds" << endl;
cout << "Interval: " << interval.count() << " microseconds" << endl;
return 0;
}命名空间限制
用户自定义字面量必须在命名空间中定义,不能在全局作用域(除非是标准库的)。这是为了避免污染全局命名空间。
#include <iostream>
using namespace std;
namespace my_literals {
constexpr int operator"" _bin(const char* str, size_t len) {
int value = 0;
for (size_t i = 0; i < len; ++i) {
value = value * 2 + (str[i] - '0');
}
return value;
}
}
int main() {
using namespace my_literals;
cout << "1010"_bin << endl; // 10
return 0;
}注意事项
- 后缀必须以下划线开头:以下划线开头的后缀保留给用户使用,不以
_开头的保留给标准库。 - 编译期计算:如果标记为
constexpr,字面量可以在编译期计算,零运行时开销。 - 类型安全:相比宏,用户自定义字面量是类型安全的,编译器会进行类型检查。
- 不要滥用:这玩意儿用得好是神器,用不好就是灾难。只在你真的需要的时候用,别为了炫技而用。










