为什么一提到 C++ 大家都会直接想到性能、速度、底层这些标签

0 阅读7分钟

开场白

很多同学在纠结于选择哪门语言的时候,如果你去网上搜索 C++,通常搜索结果就是清一色的什么更贴近底层、内存优化、性能优势、做游戏、搞金融之类的。这篇文章的主要目的就是带你对 “C++ 为什么和这些标签高度绑定” 这件事有更清楚的认识,而不是以后别人问你 C++ 怎么样,你只会打标签但说不出个所以然来。

我们先从一个最常见的例子说起:
大家如果有刷一些算法题,会发现通常如果编程语言选的 C++ 的话,题目的时间限制一般是 1s,而如果是其他语言(比如 Python、Java 之类)通常是 2s 甚至更多。

这其实间接说明了 C++ 跑同样算法就是更快。比如排个序,C++ 0.3 秒完事,Python 可能要 1.5 秒。

那问题就来了:C++ 到底凭什么?


一、先看个最简单的:循环累加 1 亿次

C++ 写起来:

int sum = 0;
for (int i = 0; i < 100000000; ++i) sum += i;

编译后,sumi 直接放在 CPU 寄存器里,循环展开、流水线优化,在 -O2-O3 优化下大概 30~100 毫秒跑完。

Python 写同样的:

s = 0
for i in range(100000000):
    s += i

Python 的整数是对象,每次 + 都要新建对象、检查类型、经过虚拟机。跑完起码 5 秒以上。

说白了:C++ 的代码编译完直接跑在 CPU 上,Python 的代码要交给解释器翻译成自己的指令再跑。多一层就慢一层。
也就是说 C++ 写出来就是机器码,Python 写出来要过一道虚拟机


二、高级语法会不会拖慢速度?

有人担心:我用 vector 这种高级容器,会不会有额外开销?

这里就要提到 C++ 很出名的那句话了:

“你不用的东西,不需要为它付出代价”You don‘t pay for what you don’t use

vector<int>,你写 v.push_back(42),编译后就是几行内存操作指令,没有对象头、没有锁、没有 GC 屏障(垃圾回收)。

再看 Java 的 ArrayList<Integer>,每个 Integer 都是一个堆对象,读一个元素要跳好几次指针,还有 GC 随时可能卡你一下。


三、你的数据在内存里怎么摆,你说了算

很多语言你管不了数据在内存里是连续还是分散。C++ 可以。

举个例子,三维向量:

struct Vec3 { float x, y, z; };
vector<Vec3> points(1000000);

这 100 万个点在内存里是 连续 的一块:x,y,z,x,y,z...。CPU 预取、SIMD 一次性处理多个浮点数,快得很。

Java 里,ArrayList<Vec3> 每个 Vec3 是独立对象,散落在堆里。遍历的时候缓存根本用不上,性能差好几倍。


四、你能直接接触内存

很多语言里,你没法直接碰内存。比如 Java 的变量是引用,你改不了它的地址;Python 更是全自动。

C++ 让你用指针。指针就是一个数字,代表内存地址。你可以把它往前往后移,直接读写那块内存。

看个最简单的例子:

int arr[5] = {1,2,3,4,5};
int* p = arr;        // p 指向数组第一个元素
p[2] = 100;          // 直接改第三个元素的值为 100

这在很多语言里做不到(或者要用别扭的“不安全”模式)。

再比如,你要和一个硬件设备通信——设备把数据放在某个固定的内存地址上。C++ 可以这样写:

int* sensor = (int*)0x40000000;
int value = *sensor;

别的语言没有这种“直接给地址”的写法。

顺带一提:这段代码只能在嵌入式裸机或驱动开发中才能合法,在普通操作系统中会触发段错误,因为你无权访问物理内存。

总结一下:C++ 让你自己动手操作内存地址,而不是交给虚拟机托管。


五、能帮编译器省活儿,编译期就算完了

C++ 把很多计算挪到编译期,运行时几乎不留痕迹。

比如编译期算阶乘:

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
int arr[factorial(5)];  // 编译期就算出 120,数组大小固定

如果没有 constexpr,类似的“编译期常量”在 C 语言里只能用宏(不安全)或者硬编码(不灵活)。在 Java 或 C# 中,常量表达式有部分编译期折叠能力,但像函数调用这种复杂的编译期计算,直到近年才逐步支持(Java 的常量表达式有限,C# 的 const 只支持字面量,不能调用函数)。而 C++ 的 constexpr 函数可以像普通函数一样写逻辑,完全在编译期执行,既安全又不占运行时 CPU。

还有模板:vector<int>vector<float> 会生成两套独立代码,各优化各的。Java 的泛型是擦除式的,运行时还要转换。

小 lambda 也经常被内联,函数调用都没了。


六、落实到现实需求

金融交易

大致流程:收行情 → 算价格 → 发单子。要求:追求零 GC、极少的锁和系统调用(除了网卡)。
C++ 可以做到:

  • 内存池,全程不 new
  • 线程绑核,不让操作系统乱调度
  • 直接用 rdtsc 指令读 CPU 时钟

高频交易领域通常倾向避免 GC, 因为任何不可预测延迟都可能带来风险。

游戏行业

  • 引擎底层(Unreal、自研):渲染、物理、音频、网络。主流商业游戏引擎底层长期以 C++ 为主,因为每帧只有 16 毫秒(60 帧),要直接操作 GPU 和内存。
  • 游戏逻辑(技能、AI、UI):用 C# (Unity) 或 Lua。改技能不用重编译引擎,开发快。

底层基础设施

  • 数据库:MySQL、RocksDB 的核心是 C++,要直接操作磁盘(O_DIRECT)、手写内存池。
  • 浏览器:Chrome 的 Blink、V8 都是 C++,解析 HTML 跑 JS 要飞快。
  • 消息队列:ZeroMQ 也是 C++,处理 TCP 和并发。

嵌入式

路由器、汽车仪表、医疗设备。直接写寄存器点灯,不经过操作系统。

*((uint32_t*)0x40021000) |= (1 << 5);  // 亮灯

Python 干不了这活,因为它得跑在操作系统上。


七、C++ 的困境

这里我想多提一嘴,简单说一下 C++ 被人一直诟病的地方。其实如果你刚开始学 C++,遇到最大的困难应该其实是各种各样的语法,一堆莫名其妙的初始化方式,你完全不理解为什么要这样设计。但其实这只是 C++ 很小的一部分问题。

1. C++ 一直被吐槽的那些点(只是很少的一部分)

  • 头文件:改一个头文件,整个项目重编译半小时。
  • 模板报错:写错一点,编译器喷你 200 行错误,根本看不懂。
  • 没有标准包管理:C++ 搞个依赖比 Java 麻烦十倍。

这些痛苦都是为了 性能和控制 付出的代价。Java 没这些坑,但也摸不到内存和 CPU。

2. Rust 会不会取代 C++?

Rust 解决了 C++ 的一个大痛点:内存安全
C++ 里迭代器很容易失效(你 push_back 后之前的迭代器就不能用了),Rust 编译器直接不允许:

let mut v = vec![1,2,3];
let it = v.iter();
v.push(4);   // 编译错误!不能同时存在可变和不可变借用

这就防止了一大类崩溃。而且 Rust 的工具链(cargo)比 C++ 好太多了。

但众所周知,一个语言最重要的是找到属于它自己的生态位,而 C++ 已经在众多行业领域深耕多年,目前来看,Rust 在国内的发展路还很长。