以 C++23(libc++ 实现)为基准回顾 1983→2023 · 全示例在 Termux / aarch64 实测通过
本文包含两部分:第一部分按时间线讲解 C++ 从"带类的 C"到 C++23 的每一次进化(每代配一个完整示例, 每一行代码都有解释,全部真实编译运行);第二部分讲 C++ 如何与 C、操作系统、Python、 汇编及"旧标准代码"混合使用——这是现实工程绕不开的课题。
clang++ 21.1.8,标准库 libc++。
1983 年 Bjarne Stroustrup 在贝尔实验室给 C 加上"类"(class),语言起初叫"C with Classes"。 1985 年首次发行,1998 年发布第一个国际标准 C++98,随后 2003 年发布小修订 C++03 (本文合称"经典 C++")。这一代确立了 C++ 的三大支柱:
经典 C++ 的局限(以今天的眼光):没有 auto 类型推导、没有 lambda、
空指针用宏 NULL、内存靠手写 new/delete、迭代器类型名要写全。
下面的示例完全用 C++98 语法,注意它"能编译但处处别扭"——这正是后来的版本要解决的痛点。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 历史01:C++98 时代(-std=c++98) | |
| 2 | // 特点:STL 已经可用,但没有 auto、lambda、nullptr | |
| 3 | #include <iostream> | 头文件:老式 IO 流 |
| 4 | #include <vector> | STL 容器:动态数组 |
| 5 | #include <string> | STL 字符串 |
| 6 | #include <algorithm> | STL 算法:排序 |
| 7 | #include <memory> | STL 智能指针(auto_ptr,C++11 起被废弃) |
| 8 | ||
| 9 | // 一个 1998 风格的小类:构造函数 + 成员函数 | |
| 10 | class Person { | |
| 11 | public: | |
| 12 | Person(const std::string& n, int a) : name(n), age(a) {} | 初始化列表 |
| 13 | void greet() const { | const 成员函数 |
| 14 | std::cout << "你好,我是 " << name << ",今年 " << age << " 岁。" << std::endl; | |
| 15 | } | |
| 16 | private: | |
| 17 | std::string name; | 私有成员:封装 |
| 18 | int age; | |
| 19 | }; | |
| 20 | ||
| 21 | int main() { | |
| 22 | // 98 风格:变量声明在需要时(C89 要求在函数开头) | |
| 23 | std::vector<int> nums; | |
| 24 | for (int i = 0; i < 10; ++i) nums.push_back(i * i); | 手写循环 |
| 25 | std::sort(nums.begin(), nums.end()); | 迭代器区间排序 |
| 26 | ||
| 27 | // 没有范围 for,只能用手写迭代器 | |
| 28 | int total = 0; | |
| 29 | for (std::vector<int>::iterator it = nums.begin(); | 迭代器类型要写全 |
| 30 | it != nums.end(); ++it) total += *it; | |
| 31 | std::cout << "总和 = " << total << std::endl; | |
| 32 | ||
| 33 | // NULL 空指针(C++11 起推荐 nullptr) | |
| 34 | int* p = NULL; | |
| 35 | if (p == NULL) std::cout << "p 是空指针(用 NULL,C++11 后改 nulltpr)" << std::endl; | |
| 36 | ||
| 37 | // 98 的智能指针 auto_ptr:拷贝会转移所有权(有坑,C++11 被 unique_ptr 取代) | |
| 38 | std::auto_ptr<Person> ap(new Person("小明", 18)); | |
| 39 | ap->greet(); | |
| 40 | ||
| 41 | return 0; | |
| 42 | } |
std::vector<int>::iterator 类型名冗长(C++11 的 auto 就是为了消灭它);② NULL 是整数 0 的宏,可被错误地当整数用(C++11 用 nullptr 修复);③ auto_ptr 拷贝会悄悄转移所有权(C++11 起被 unique_ptr 取代,本文不展开其争议,只记结论:如今用智能指针,且 unique_ptr 禁止拷贝)。clang++ -std=c++98 cpp98.cpp -o a98 && ./a98
总和 = 285 p 是空指针(用 NULL,C++11 后改 nulltpr) 你好,我是 小明,今年 18 岁。
C++11 是史上最大的一次标准更新,直接把语言"重装"了一遍。 如今写"现代 C++"默认就是指 C++11 之后的写法。核心成果:
std::move + 右值引用,大对象传递从"拷贝 O(n)"变"偷资源 O(1)"unique_ptr/shared_ptr/weak_ptr,配合 RAII 消灭手写 delete哲学转变:从"信任程序员"转向"编译器帮你检查"——类型推导、所有权转移、只读语义都由类型系统表达。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 历史02:C++11 时代(-std=c++11)—— 现代 C++ 的起点 | |
| 2 | #include <iostream> | |
| 3 | #include <vector> | |
| 4 | #include <string> | |
| 5 | #include <memory> | make_unique 需要 C++14,11 用 make_shared 或 new |
| 6 | ||
| 7 | int main() { | |
| 8 | // 1) auto:类型自动推断(编译器猜) | |
| 9 | auto x = 42; | x 是 int |
| 10 | auto name = std::string("小明"); | name 是 string |
| 11 | ||
| 12 | // 2) 范围 for:遍历容器不用写迭代器 | |
| 13 | std::vector<int> v{1, 2, 3, 4}; | 花括号初始化列表(C++11 新) |
| 14 | int sum = 0; | |
| 15 | for (int n : v) sum += n; | 遍历每一个 |
| 16 | std::cout << "和 = " << sum << std::endl; | |
| 17 | ||
| 18 | // 3) lambda:就地写匿名函数 | |
| 19 | auto twice = [](int n) { return n * 2; }; | [捕获](参数){体} |
| 20 | std::cout << "twice(21) = " << twice(21) << std::endl; | |
| 21 | ||
| 22 | // 4) nullptr:类型安全的空指针(取代 NULL) | |
| 23 | int* p = nullptr; | |
| 24 | if (p == nullptr) std::cout << "nullptr 空指针" << std::endl; | |
| 25 | ||
| 26 | // 5) 移动语义:std::move 把资源搬走(O(1) 而非拷贝) | |
| 27 | std::string s1 = "hello"; | |
| 28 | std::string s2 = std::move(s1); | s1 的资源给了 s2 |
| 29 | std::cout << "s2 = " << s2 << std::endl; | |
| 30 | ||
| 31 | // 6) constexpr:编译期常量与函数 | |
| 32 | constexpr int SIZE = 100; | 编译期常量 |
| 33 | std::cout << "SIZE = " << SIZE << std::endl; | |
| 34 | ||
| 35 | // 7) 智能指针:shared_ptr 共享所有权(引用计数) | |
| 36 | std::shared_ptr<int> sp1 = std::make_shared<int>(7); | |
| 37 | { | |
| 38 | std::shared_ptr<int> sp2 = sp1; | 计数 +1 |
| 39 | std::cout << "引用计数 = " << sp1.use_count() << std::endl; | |
| 40 | } | sp2 销毁,计数 -1 |
| 41 | std::cout << "引用计数 = " << sp1.use_count() << std::endl; | |
| 42 | ||
| 43 | return 0; | |
| 44 | } |
clang++ -std=c++11 cpp11.cpp -o a11 && ./a11
和 = 10 twice(21) = 42 nullptr 空指针 s2 = hello SIZE = 100 引用计数 = 2 引用计数 = 1
C++14 是小型修订,主题是"补齐 C++11 留下的边角":
auto,让编译器推导(C++11 必须写死类型)auto,由 return 语句推断0b1010'1100,可读性提升没有惊天动地的新思想,但每一项都在消灭日常的"手写样板"。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 历史03:C++14 时代(-std=c++14)—— 让 C++11 更顺手 | |
| 2 | #include <iostream> | |
| 3 | #include <string> | |
| 4 | ||
| 5 | // 1) 泛型 lambda:参数写 auto,类型由编译器推断 | |
| 6 | auto add_generic = [](auto a, auto b) { return a + b; }; | |
| 7 | ||
| 8 | // 2) 返回类型推导:auto 作为函数返回类型(不用写尾置返回) | |
| 9 | template <typename T> | |
| 10 | auto square(T n) { return n * n; } | 参数用模板,返回类型自动推导 |
| 11 | ||
| 12 | // 3) 初始化捕获:捕获时能算值 | |
| 13 | int main() { | |
| 14 | // 泛型 lambda:同一段代码服务 int、double、string | |
| 15 | std::cout << add_generic(1, 2) << std::endl; | 3 |
| 16 | std::cout << add_generic(1.5, 2.5) << std::endl; | 4 |
| 17 | std::cout << add_generic(std::string("a"), std::string("b")) << std::endl; | ab |
| 18 | ||
| 19 | // 返回类型推导 | |
| 20 | std::cout << square(5) << std::endl; | 25 |
| 21 | std::cout << square(2.5) << std::endl; | 6.25(类型自动变 double) |
| 22 | ||
| 23 | // 初始化捕获:捕获时就算好值 | |
| 24 | int factor = 3; | |
| 25 | auto scaled = [y = factor * 2](int x) { return x * y; }; | |
| 26 | std::cout << scaled(10) << std::endl; | 10 * (3*2) = 60 |
| 27 | ||
| 28 | // 4) 二进制字面量 + 数字分隔符(可读性) | |
| 29 | int flags = 0b1010'1100; | 0b 二进制,' 分隔 |
| 30 | std::cout << flags << std::endl; | 172 |
| 31 | ||
| 32 | // 5) std::make_unique(C++14 才补上,C++11 只有 make_shared) | |
| 33 | auto u = std::make_unique<int>(99); | 独占所有权 |
| 34 | std::cout << *u << std::endl; | |
| 35 | ||
| 36 | return 0; | |
| 37 | } |
clang++ -std=c++14 cpp14.cpp -o a14 && ./a14
3 4 ab 25 6.25 60 172 99
C++17 是"让日常代码变短"的版本,几乎每个特性都在消灭样板代码:
auto [a, b] = pair 一次拆包——遍历 map 从此不再写 .first/.secondif (auto it = m.find(k); it != end) 变量作用域收紧从这一代起,教科书式的现代 C++ 风格基本定型。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 历史04:C++17 时代(-std=c++17)—— 语法糖大丰收 | |
| 2 | #include <iostream> | |
| 3 | #include <string> | |
| 4 | #include <optional> | 可能没有值的包装 |
| 5 | #include <variant> | 多类型之一 |
| 6 | #include <map> | |
| 7 | #include <filesystem> | 文件系统库(新!) |
| 8 | #include <type_traits> | |
| 9 | ||
| 10 | namespace fs = std::filesystem; | |
| 11 | ||
| 12 | // 1) 结构化绑定:一次拆开多个返回值 | |
| 13 | std::pair<int, int> divide(int a, int b) { | |
| 14 | return {a / b, a % b}; | 商和余数一起返回 |
| 15 | } | |
| 16 | ||
| 17 | // 2) if constexpr:编译期分支(false 分支不生成代码) | |
| 18 | template <typename T> | |
| 19 | void describe(const T& v) { | |
| 20 | if constexpr (std::is_pointer_v<T>) { | |
| 21 | std::cout << "指针,指向 " << *v << '\n'; | |
| 22 | } else { | |
| 23 | std::cout << "普通值 " << v << '\n'; | |
| 24 | } | |
| 25 | } | |
| 26 | ||
| 27 | // 3) 变参模板 + 折叠表达式:任意数量参数求和 | |
| 28 | template <typename... Ts> | |
| 29 | auto sum_all(Ts... args) { return (args + ...); } | |
| 30 | ||
| 31 | int main() { | |
| 32 | // 结构化绑定 | |
| 33 | auto [q, r] = divide(17, 5); | q=3, r=2 |
| 34 | std::cout << "商 " << q << " 余 " << r << '\n'; | |
| 35 | ||
| 36 | // map 遍历拆包(经典用法) | |
| 37 | std::map<std::string, int> scores{{"张三", 90}, {"李四", 85}}; | |
| 38 | for (auto [name, score] : scores) | |
| 39 | std::cout << name << ": " << score << '\n'; | |
| 40 | ||
| 41 | // optional:安全地表达"可能没有结果" | |
| 42 | std::optional<int> maybe = 42; | 有值 |
| 43 | if (maybe) std::cout << "有值: " << *maybe << '\n'; | |
| 44 | std::optional<int> none = std::nullopt; | 无值 |
| 45 | std::cout << "无值时给默认: " << none.value_or(-1) << '\n'; | |
| 46 | ||
| 47 | // variant:一个变量轮流存多种类型 | |
| 48 | std::variant<int, std::string> v = "hello"; | 当前是 string |
| 49 | if (auto p = std::get_if<std::string>(&v)) | |
| 50 | std::cout << "variant 是字符串: " << *p << '\n'; | |
| 51 | ||
| 52 | // if 初始化:变量只在 if 内有效 | |
| 53 | if (int n = 7; n % 2 == 0) std::cout << n << " 是偶数\n"; | |
| 54 | else std::cout << n << " 是奇数\n"; | |
| 55 | ||
| 56 | // if constexpr | |
| 57 | int iv = 5; | |
| 58 | describe(iv); | |
| 59 | describe(&iv); | |
| 60 | ||
| 61 | // 折叠表达式 | |
| 62 | std::cout << sum_all(1, 2, 3, 4) << '\n'; | 10 |
| 63 | std::cout << sum_all(1.5, 2.5) << '\n'; | 4.0 |
| 64 | ||
| 65 | // filesystem:现代文件操作 | |
| 66 | fs::path p = "/tmp/demo/.."; | 路径对象 |
| 67 | std::cout << "文件名: " << p.filename() << '\n'; | |
| 68 | std::cout << "规范化: " << fs::weakly_canonical(p) << '\n'; | |
| 69 | ||
| 70 | return 0; | |
| 71 | } |
clang++ -std=c++17 cpp17.cpp -o a17 && ./a17
商 3 余 2 张三: 90 李四: 85 有值: 42 无值时给默认: -1 variant 是字符串: hello 7 是奇数 普通值 5 指针,指向 5 10 4 文件名: ".." 规范化: "/tmp/"
C++20 是自 C++11 以来最大的一次升级,被称作"现代 C++ 的成人礼":
| filter | transform,函数式风格降临 C++= default 让结构体排序零成本concepts 尤其值得强调:它让"模板报错难读"这个 C++ 最大的黑点得到根治。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 历史05:C++20 时代(-std=c++20)—— 成人礼:concepts/ranges/三路比较 | |
| 2 | #include <iostream> | |
| 3 | #include <ranges> | 范围库(容器直接操作) |
| 4 | #include <concepts> | 约束模板参数 |
| 5 | #include <compare> | 三路比较运算符 |
| 6 | #include <span> | 数组视图 |
| 7 | #include <vector> | |
| 8 | #include <algorithm> | |
| 9 | #include <string> | |
| 10 | ||
| 11 | // 1) concepts:给模板加"要求"(报错从迷宫变人话) | |
| 12 | template <std::integral T> | T 必须是整数类型 |
| 13 | T double_it(T x) { return x * 2; } | |
| 14 | ||
| 15 | // 2) 自定义 concept:要求类型有 area() 且返回 double | |
| 16 | template <typename T> | |
| 17 | concept HasArea = requires(const T& t) { | |
| 18 | { t.area() } -> std::same_as<double>; | |
| 19 | }; | |
| 20 | ||
| 21 | // 3) 三路比较:一个运算符生成全部比较 | |
| 22 | struct Point { | |
| 23 | int x, y; | |
| 24 | auto operator<=>(const Point&) const = default; | 一键生成 < <= > >= |
| 25 | bool operator==(const Point&) const = default; | 还要 == |
| 26 | }; | |
| 27 | ||
| 28 | int main() { | |
| 29 | // concepts 用法 | |
| 30 | std::cout << double_it(21) << '\n'; | 42 |
| 31 | ||
| 32 | // ranges:算法直接吃容器(不再写 begin/end) | |
| 33 | std::vector<int> v{5, 1, 4, 2, 3}; | |
| 34 | std::ranges::sort(v); | |
| 35 | for (int x : v) std::cout << x << ' '; | 1 2 3 4 5 |
| 36 | std::cout << '\n'; | |
| 37 | ||
| 38 | // 视图管道:过滤 + 变换(惰性流水线) | |
| 39 | auto evens = std::views::iota(1, 11) | |
| 40 | | std::views::filter([](int n) { return n % 2 == 0; }) | |
| 41 | | std::views::transform([](int n) { return n * n; }); | |
| 42 | for (int x : evens) std::cout << x << ' '; | 4 16 36 64 100 |
| 43 | std::cout << '\n'; | |
| 44 | ||
| 45 | // 三路比较 | |
| 46 | Point a{1, 2}, b{1, 3}; | |
| 47 | std::cout << (a < b ? "a < b" : "a >= b") << '\n'; | |
| 48 | ||
| 49 | // span:安全的数组视图(不拷贝) | |
| 50 | int raw[] = {10, 20, 30}; | |
| 51 | std::span<const int> sp(raw); | 只看不改 |
| 52 | std::cout << "span 大小 " << sp.size() << ", 首个 " << sp[0] << '\n'; | |
| 53 | ||
| 54 | // 指定初始化:按成员名赋值 | |
| 55 | struct Config { int width = 800; int height = 600; }; | |
| 56 | Config cfg{.width = 1024}; | |
| 57 | std::cout << cfg.width << 'x' << cfg.height << '\n'; | |
| 58 | ||
| 59 | return 0; | |
| 60 | } |
clang++ -std=c++20 cpp20.cpp -o a20 && ./a20
42 1 2 3 4 5 4 16 36 64 100 a < b span 大小 3, 首个 10 1024x600
C++23 没有颠覆性新思想,主题是"把体验补齐到日常的最后一公里":
printf 和 cout 的终结者)以 C++23 为基准看今天的推荐写法:输出用 println,错误用 expected,
容器 + 算法 + lambda + ranges,所有权靠智能指针。这套组合在 C++23 时代彻底成熟。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 历史06:C++23 时代(-std=c++23)—— 体验补齐 | |
| 2 | #include <print> | 新:类型安全输出(C++23) |
| 3 | #include <expected> | 新:要么有值,要么有错误 |
| 4 | #include <string> | |
| 5 | #include <vector> | |
| 6 | #include <flat_map> | 新:内存连续的字典 |
| 7 | #include <ranges> | |
| 8 | #include <cassert> | |
| 9 | ||
| 10 | // 1) std::println:类型安全格式化输出 | |
| 11 | // 2) std::expected:显式错误处理(不抛异常) | |
| 12 | enum class ParseErr { Empty, BadChar }; | |
| 13 | std::expected<int, ParseErr> parse_int(std::string_view s) { | |
| 14 | if (s.empty()) return std::unexpected(ParseErr::Empty); | 失败路径 |
| 15 | int v = 0; | |
| 16 | for (char c : s) { | |
| 17 | if (c < '0' || c > '9') return std::unexpected(ParseErr::BadChar); | |
| 18 | v = v * 10 + (c - '0'); | |
| 19 | } | |
| 20 | return v; | 成功路径 |
| 21 | } | |
| 22 | ||
| 23 | int main() { | |
| 24 | // println 全家桶:{} 占位符 + 格式控制 | |
| 25 | std::println("值 = {}", 42); | |
| 26 | std::println("十六进制 = {:#x}", 255); | 0xff |
| 27 | std::println("对齐 {:>8}", "右对齐"); | |
| 28 | std::println("精度 {:.2f}", 3.14159); | |
| 29 | ||
| 30 | // expected 使用 | |
| 31 | auto r1 = parse_int("123"); | |
| 32 | if (r1) std::println("解析成功: {}", *r1); | 123 |
| 33 | auto r2 = parse_int(""); | |
| 34 | if (!r2) std::println("解析失败: Empty"); | 走失败分支 |
| 35 | ||
| 36 | // string::contains(新方法) | |
| 37 | std::string s = "hello world"; | |
| 38 | std::println("包含 world? {}", s.contains("world")); | |
| 39 | ||
| 40 | // flat_map:底层是连续内存(比 map 缓存友好) | |
| 41 | std::flat_map<std::string, int> fm; | |
| 42 | fm["apple"] = 1; | |
| 43 | fm["banana"] = 2; | |
| 44 | for (const auto& [k, v] : fm) std::println("{} -> {}", k, v); | |
| 45 | ||
| 46 | // std::ranges::to(新版库支持时可用;本机 libc++ 较旧, | |
| 47 | // 这里用等价的显式构造) | |
| 48 | std::vector<std::string> words{"a", "bb", "ccc", "dddd"}; | |
| 49 | std::vector<std::string> long_words; | |
| 50 | for (const auto& w : words | std::views::filter([](const std::string& s) { | |
| 51 | return s.size() >= 3; | |
| 52 | })) | |
| 53 | long_words.push_back(w); | |
| 54 | std::println("长单词: {}", long_words.size()); | |
| 55 | ||
| 56 | return 0; | |
| 57 | } |
clang++ -std=c++23 cpp23.cpp -o a23 && ./a23
值 = 42 十六进制 = 0xff 对齐 右对齐 精度 3.14 解析成功: 123 解析失败: Empty 包含 world? true apple -> 1 banana -> 2 长单词: 2
extern "C" 把函数符号"去修饰",其他语言(C、Python、Rust、汇编、系统调用)
就能像调用 C 函数一样调用它。反之,C++ 调用 C 库只需普通声明。
ABI 的三条铁律:① 类型大小要对齐(int=4 字节等);② 返回值/参数约定要一致;
③ 谁分配的内存谁释放。C++ 支持重载(同名函数多个版本),所以编译器把函数名"修饰"成带参数类型的长符号名:
int add(int,int) 在 C++ 里符号是 _Z3addii。而 C 语言没有重载,
符号就是 add。两者对不上,链接必失败。
extern "C" 的作用:告诉 C++ 编译器"这个函数按 C 的符号规则导出/导入",
从而与 C(以及其他语言)互通。这是整个混合编程的枢纽。
# 观察符号差异 clang++ -std=c++23 -c mixB_cpp_side.cpp -o cpp_side.o nm cpp_side.o | grep cpp_sum # C++ 侧用 extern "C",符号是干净的 cpp_sum_squares # 反之不写 extern "C" 时:_Z14cpp_sum_squaresPKti(带类型修饰)
场景:调用操作系统 C 库(strlen/open…)或公司遗留 C 模块。
C 函数在 C++ 里直接声明即可调用;唯一陷阱是忘写 extern "C"——C 侧符号没修饰,C++ 侧却按修饰符号找,链接报错。下方示例先展示了这个错误,再用正确写法修复。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // C 语言函数文件(用 clang 按 C 编译) | |
| 2 | // 这是"纯 C 代码",没有 C++ 特性,ABI 与 C++ 兼容 | |
| 3 | ||
| 4 | #include <string.h> | C 头文件:strlen 声明 |
| 5 | ||
| 6 | // C 函数 1:计算字符串长度(等价于 strlen,手写演示) | |
| 7 | int c_strlen(const char* s) { | |
| 8 | int len = 0; | |
| 9 | while (s[len] != '\0') ++len; | 数到结束符 \0 |
| 10 | return len; | |
| 11 | } | |
| 12 | ||
| 13 | // C 函数 2:反转数组(就地) | |
| 14 | void c_reverse(int* arr, int n) { | |
| 15 | for (int i = 0, j = n - 1; i < j; ++i, --j) { | |
| 16 | int t = arr[i]; | 交换三连 |
| 17 | arr[i] = arr[j]; | |
| 18 | arr[j] = t; | |
| 19 | } | |
| 20 | } | |
| 21 | ||
| 22 | // C 函数 3:全局状态(C 风格全局变量 + 函数) | |
| 23 | static int counter = 0; | static = 文件私有 |
| 24 | int c_next_id(void) { | 自增序号 |
| 25 | return ++counter; | |
| 26 | } |
c_side.c 是纯 C 代码,用 clang -c 编译(C 编译器)。注意它的全局变量 static int counter 是文件私有的,但 c_next_id() 函数会修改它——跨语言调用时这个状态保持有效。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例A:C++ 调用 C 代码(c_side.c 里的函数) | |
| 2 | // 要点:C 函数只要"声明了"就能调用,无需任何包装 | |
| 3 | #include <print> | C++23 输出 |
| 4 | #include <cstring> | C 头文件的标准 C++ 版本 |
| 5 | ||
| 6 | // 声明 C 侧函数(与 c_side.c 里签名一致) | |
| 7 | // 关键:extern "C" 告诉 C++ 编译器"这些函数按 C 的符号规则编译" | |
| 8 | // —— 不套 extern "C" 的话,C++ 会做名字修饰(name mangling), | |
| 9 | // 链接器就找不到 c_side.o 里的 c_strlen 符号(上面演示过这个错误) | |
| 10 | extern "C" { | |
| 11 | int c_strlen(const char* s); | |
| 12 | void c_reverse(int* arr, int n); | |
| 13 | int c_next_id(void); | |
| 14 | } | |
| 15 | ||
| 16 | int main() { | |
| 17 | // 调用 1:C 函数处理字符串 | |
| 18 | const char* msg = "hello"; | C 风格字符串(以 \0 结尾) |
| 19 | std::println("c_strlen({}) = {}", msg, c_strlen(msg)); | |
| 20 | ||
| 21 | // 调用 2:C 函数反转 int 数组 | |
| 22 | int arr[] = {1, 2, 3, 4, 5}; | C 数组(退化为指针传给 C) |
| 23 | c_reverse(arr, 5); | |
| 24 | for (int i = 0; i < 5; ++i) std::print("{} ", arr[i]); | |
| 25 | std::println(""); | |
| 26 | ||
| 27 | // 调用 3:C 全局状态(counter 在 C 文件里) | |
| 28 | std::println("id = {}", c_next_id()); | |
| 29 | std::println("id = {}", c_next_id()); | |
| 30 | ||
| 31 | // 调用 4:直接用 libc(C 标准库)函数 | |
| 32 | std::println("strlen = {}", std::strlen("world")); | |
| 33 | return 0; | |
| 34 | } |
clang -c c_side.c -o c_side.o clang++ -std=c++23 mixA_cpp_call_c.cpp c_side.o -o mixA && ./mixA
c_strlen(hello) = 5 5 4 3 2 1 id = 1 id = 2 strlen = 5
extern "C",链接报
undefined symbol: c_strlen(char const*),且链接器贴心提示
did you mean: extern "C" c_strlen——照着改就对了。反向操作:C++ 函数用 extern "C" 导出,C 主程序直接调用。
要点:① 导出函数内部可以尽情用 C++ 特性;② 跨语言边界只传 C 兼容类型
(指针、int、double…);③ 返回 malloc 分配的内存由 C 侧释放(谁分配谁释放)。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例B:C 代码调用 C++(反向调用) | |
| 2 | // C++ 侧导出"按 C 符号规则"的函数,C 文件直接调用 | |
| 3 | // 这是 Python/其他语言 FFI 的基础机制 | |
| 4 | ||
| 5 | #include <print> | C++ 输出 |
| 6 | #include <string> | |
| 7 | #include <vector> | |
| 8 | #include <algorithm> | |
| 9 | #include <cstring> | |
| 10 | ||
| 11 | // C++ 内部函数:排序并拼接(纯 C++ 特性) | |
| 12 | static std::string join_sorted(std::vector<std::string> words) { | |
| 13 | std::ranges::sort(words); | 字典序排序 |
| 14 | std::string out; | |
| 15 | for (const auto& w : words) { | |
| 16 | if (!out.empty()) out += ", "; | 逗号分隔 |
| 17 | out += w; | |
| 18 | } | |
| 19 | return out; | RVO:零拷贝返回 |
| 20 | } | |
| 21 | ||
| 22 | // 导出给 C 的函数 1:计算数组平方和(C 能传指针和长度) | |
| 23 | // extern "C":符号按 C 规则(不修饰),C 才能找到 | |
| 24 | extern "C" long long cpp_sum_squares(const int* arr, int n) { | |
| 25 | long long total = 0; | |
| 26 | for (int i = 0; i < n; ++i) total += (long long)arr[i] * arr[i]; | |
| 27 | return total; | |
| 28 | } | |
| 29 | ||
| 30 | // 导出给 C 的函数 2:拼接字符串数组(C 传 char* 数组) | |
| 31 | // 返回值用 malloc 分配(C 侧负责 free)——C 与 C++ 的约定 | |
| 32 | extern "C" char* cpp_join_words(const char* const* words, int count) { | |
| 33 | std::vector<std::string> ws; | |
| 34 | for (int i = 0; i < count; ++i) ws.emplace_back(words[i]); | |
| 35 | std::string joined = join_sorted(std::move(ws)); | |
| 36 | ||
| 37 | char* out = static_cast<char*>(std::malloc(joined.size() + 1)); | |
| 38 | if (!out) return nullptr; | |
| 39 | std::memcpy(out, joined.c_str(), joined.size() + 1); | |
| 40 | return out; | C 侧 free(out) |
| 41 | } | |
| 42 | ||
| 43 | // 导出给 C 的函数 3:全局计数器(演示跨语言共享状态) | |
| 44 | static int g_calls = 0; | |
| 45 | extern "C" int cpp_call_count() { return ++g_calls; } | |
| 46 | ||
| 47 | // C++ 侧自测入口注释:实际编译时不带 main, | |
| 48 | // 由 C 主程序(mixB_c_main.c)作为程序入口。 | |
| 49 | // 单独自测方法: | |
| 50 | // clang++ -std=c++23 -DCPP_SELF_TEST mixB_cpp_side.cpp -o mixB_selftest | |
| 51 | #ifdef CPP_SELF_TEST | |
| 52 | int main() { | |
| 53 | int arr[] = {3, 4}; | |
| 54 | std::println("C++ 自测 sum_squares = {}", cpp_sum_squares(arr, 2)); | |
| 55 | const char* w[] = {"b", "a"}; | |
| 56 | char* r = cpp_join_words(w, 2); | |
| 57 | std::println("C++ 自测 join = {}", r); | |
| 58 | std::free(r); | |
| 59 | return 0; | |
| 60 | } | |
| 61 | #endif |
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | /* 混合示例B:C 主程序调用 C++ 导出的函数(C99 编译) */ | |
| 2 | #include <stdio.h> /* C 标准 IO */ | |
| 3 | #include <stdlib.h> /* free */ | |
| 4 | ||
| 5 | /* 声明 C++ 侧函数(按 C 符号,所以声明普通即可) */ | |
| 6 | long long cpp_sum_squares(const int* arr, int n); | |
| 7 | char* cpp_join_words(const char* const* words, int count); | |
| 8 | int cpp_call_count(void); | |
| 9 | ||
| 10 | int main(void) { | |
| 11 | /* 调用 1:C++ 算平方和 */ | |
| 12 | int data[] = {3, 4, 5}; | |
| 13 | printf("C 调用 C++: sum_squares = %lld\n", cpp_sum_squares(data, 3)); | |
| 14 | ||
| 15 | /* 调用 2:C 传字符串数组,C++ 排序拼接,返回 malloc 内存 */ | |
| 16 | const char* words[] = {"cherry", "apple", "banana"}; | |
| 17 | char* joined = cpp_join_words(words, 3); | |
| 18 | if (joined) { | |
| 19 | printf("C 调用 C++: join = %s\n", joined); | |
| 20 | free(joined); /* C++ 侧 malloc 的,C 侧 free */ | |
| 21 | } | |
| 22 | ||
| 23 | /* 调用 3:共享计数器(连续调用验证状态保持) */ | |
| 24 | printf("C 调用 C++: count = %d\n", cpp_call_count()); | |
| 25 | printf("C 调用 C++: count = %d\n", cpp_call_count()); | |
| 26 | return 0; | |
| 27 | } |
clang++ -std=c++23 -c mixB_cpp_side.cpp -o cpp_side.o clang -std=c99 mixB_c_main.c cpp_side.o -lstdc++ -o mixB && ./mixB
C 调用 C++: sum_squares = 50 C 调用 C++: join = apple, banana, cherry C 调用 C++: count = 1 C 调用 C++: count = 2
-lstdc++:C++ 代码依赖 C++ 标准库运行库
(异常展开、operator new 等),C 主程序不自动带上,要显式链接 libstdc++(libc++ 环境则 -lc++)。用 clang++ 做链接命令则自动处理。C++ 与"内核"的混合:syscall()、open/read/write/close、
errno 都是 libc 暴露的系统接口。这是 C++ 写底层工具(文件、进程、设备)的方式。
重点:C 风格错误处理靠 errno 全局错误号 + strerror 转文字;
现代 C++ 建议把这类调用包一层 RAII/expected(第 15、23 章),不让裸 errno 漏到业务层。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例C:系统调用 + errno(C++ 直接与操作系统对话) | |
| 2 | // 这是 C++ 与"内核"的混合:syscall() 是 libc 暴露的系统调用入口 | |
| 3 | #include <print> | |
| 4 | #include <string_view> | |
| 5 | #include <unistd.h> | syscall/read/write/getpid 等 POSIX 声明 |
| 6 | #include <sys/syscall.h> | SYS_getpid 等系统调用号 |
| 7 | #include <fcntl.h> | open 标志 |
| 8 | #include <cerrno> | errno:错误号(C 风格错误处理) |
| 9 | #include <cstring> | strerror |
| 10 | ||
| 11 | int main() { | |
| 12 | // 方式1:标准库包装(推荐日常用) | |
| 13 | std::println("getpid() = {}", ::getpid()); | |
| 14 | ||
| 15 | // 方式2:直接发系统调用(绕过包装,展示底层) | |
| 16 | // syscall(SYS_getpid) 直接进内核,不经过 libc 包装函数 | |
| 17 | long pid = ::syscall(SYS_getpid); | |
| 18 | std::println("syscall(SYS_getpid) = {}", pid); | |
| 19 | ||
| 20 | // 方式3:write 系统调用直接写文件描述符 | |
| 21 | // 注意:write 绕过 iostream 的缓冲,与 println 混用时输出顺序 | |
| 22 | // 可能被打乱(本例把它放在最后避免混乱) | |
| 23 | const char* msg = "write() 直接输出(不走 iostream 缓冲)\n"; | |
| 24 | ::write(1, msg, std::strlen(msg)); | 长度用 strlen 算(中文 UTF-8 3字节) |
| 25 | ||
| 26 | // 方式4:errno 错误处理(C 风格的错误检查) | |
| 27 | // 故意打开一个不存在的文件 | |
| 28 | int fd = ::open("/no/such/file.txt", O_RDONLY); | |
| 29 | if (fd < 0) { | |
| 30 | // errno 被内核/libc 设置为错误号,strerror 翻译成文字 | |
| 31 | std::println("打开失败,errno = {}:{}", errno, ::strerror(errno)); | |
| 32 | } | |
| 33 | ||
| 34 | // 方式5:系统调用读取文件内容(读 /proc 文件系统) | |
| 35 | int pfd = ::open("/proc/self/status", O_RDONLY); | |
| 36 | if (pfd >= 0) { | |
| 37 | char buf[256] = {}; | |
| 38 | ssize_t n = ::read(pfd, buf, sizeof(buf) - 1); | 读文件 |
| 39 | if (n > 0) { | |
| 40 | std::string_view sv(buf, n); | 视图不拷贝 |
| 41 | // 只打印第一行(进程状态) | |
| 42 | auto first_line = sv.substr(0, sv.find('\n')); | |
| 43 | std::println("状态文件首行: {}", first_line); | |
| 44 | } | |
| 45 | ::close(pfd); | |
| 46 | } | |
| 47 | return 0; | |
| 48 | } |
clang++ -std=c++23 mixC_syscall.cpp -o mixC && ./mixC
write() 直接输出(不走 iostream 缓冲) getpid() = 20018 syscall(SYS_getpid) = 20018 打开失败,errno = 2:No such file or directory 状态文件首行: Name: mixC
C++ 里直接嵌汇编指令(asm volatile),用于:SIMD 优化、读硬件寄存器、
写系统软件。语法:asm(指令模板 : 输出约束 : 输入约束 : 副作用列表)。
下方示例用 ARM64 指令 add(32/64 位加法)和 mrs(读系统寄存器)演示。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例D:内联汇编(C++ 与 CPU 指令的直接对话) | |
| 2 | // asm volatile:把一段汇编嵌进 C++ 代码 | |
| 3 | // aarch64 平台(你的手机)使用 ARM64 指令集 | |
| 4 | #include <print> | |
| 5 | ||
| 6 | // 加法函数:用内联汇编实现 a + b | |
| 7 | // "add %w0, %w1, %w2" 是 ARM64 的 32 位加法指令 | |
| 8 | // =r: 输出寄存器;r: 输入寄存器 | |
| 9 | static long asm_add(long a, long b) { | |
| 10 | long result; | |
| 11 | asm volatile ( | |
| 12 | "add %0, %1, %2\n" | %0 是输出,%1/%2 是输入 |
| 13 | : "=r"(result) | 输出约束:result 由寄存器写 |
| 14 | : "r"(a), "r"(b) | 输入约束:a、b 放寄存器 |
| 15 | : | 无副作用寄存器(clobber 列表) |
| 16 | ); | |
| 17 | return result; | |
| 18 | } | |
| 19 | ||
| 20 | // 读取 CPU 周期计数器(ARM64 的 CNTVCT_EL0,虚拟计数器) | |
| 21 | // 常用于微基准测试,比 chrono 精度高 | |
| 22 | static unsigned long long read_cycles() { | |
| 23 | unsigned long long t; | |
| 24 | asm volatile ("mrs %0, cntvct_el0\n" : "=r"(t)); | |
| 25 | return t; | |
| 26 | } | |
| 27 | ||
| 28 | int main() { | |
| 29 | long r = asm_add(20, 22); | |
| 30 | std::println("asm 加法 20 + 22 = {}", r); | |
| 31 | ||
| 32 | auto t1 = read_cycles(); | |
| 33 | auto t2 = read_cycles(); | |
| 34 | std::println("两次读数间隔约 {} 周期", t2 - t1); | |
| 35 | ||
| 36 | return 0; | |
| 37 | } |
clang++ -std=c++23 mixD_asm.cpp -o mixD && ./mixD
asm 加法 20 + 22 = 42 两次读数间隔约 1 周期
"=r"(result) 表示"输出到任意通用寄存器,写回 result";
"r"(a) 表示"输入,编译器挑个寄存器放 a"。模板里的 %0/%1/%2 按顺序对应。
汇编片段是体系结构相关的——换 CPU 架构(x86/ARM/riscv)要重写。最轻量的 C++↔Python 方案:把 C++ 编成 .so 共享库,Python 用 ctypes
按 C ABI 加载调用——无需修改 Python 解释器,无需编译 Python 扩展。适合:把 CPU 密集算法
(数值、加密、解析)下沉到 C++。更完整的方案(pybind11)原理相同,只是自动生成转换代码。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例E:把 C++ 编译成共享库,给 Python 调用(ctypes) | |
| 2 | // 这是"C++ 与 Python 混合"最轻量的路径:不改 Python 代码, | |
| 3 | // 通过 ctypes 按 C ABI 调用。 | |
| 4 | // | |
| 5 | // 编译命令: | |
| 6 | // clang++ -std=c++23 -fPIC -shared mixE_lib.cpp -o libmixE.so | |
| 7 | // Python 侧:import ctypes; lib = ctypes.CDLL("./libmixE.so") | |
| 8 | // | |
| 9 | // 更强大的方案(pybind11/Boost.Python)会在 C++ 侧生成 | |
| 10 | // Python 模块,支持类/异常/STL 自动转换——原理仍是 extern "C" | |
| 11 | // 暴露接口 + Python C API。 | |
| 12 | ||
| 13 | #include <string> | |
| 14 | #include <vector> | |
| 15 | #include <algorithm> | |
| 16 | #include <cstdlib> | |
| 17 | #include <cstring> | |
| 18 | #include <cmath> | |
| 19 | ||
| 20 | // 全局状态:Python 侧连续调用能看到计数变化 | |
| 21 | static int g_calls = 0; | |
| 22 | ||
| 23 | // 函数1:两个整数相乘(最简单:纯标量,ctypes 零转换成本) | |
| 24 | extern "C" long long py_mul(long long a, long long b) { | |
| 25 | ++g_calls; | |
| 26 | return a * b; | |
| 27 | } | |
| 28 | ||
| 29 | // 函数2:把 C 风格字符串转大写 | |
| 30 | // 输入 char*,输出 char*(malloc,Python 侧要释放) | |
| 31 | extern "C" char* py_uppercase(const char* s) { | |
| 32 | ++g_calls; | |
| 33 | std::string out(s); | |
| 34 | std::transform(out.begin(), out.end(), out.begin(), | |
| 35 | [](unsigned char c) { return static_cast<char>(std::toupper(c)); }); | |
| 36 | char* r = static_cast<char*>(std::malloc(out.size() + 1)); | |
| 37 | std::memcpy(r, out.c_str(), out.size() + 1); | |
| 38 | return r; | |
| 39 | } | |
| 40 | ||
| 41 | // 函数3:计算数组平方和(Python 传 list → ctypes 数组) | |
| 42 | extern "C" double py_sum_squares(const double* arr, int n) { | |
| 43 | ++g_calls; | |
| 44 | double total = 0; | |
| 45 | for (int i = 0; i < n; ++i) total += arr[i] * arr[i]; | |
| 46 | return total; | |
| 47 | } | |
| 48 | ||
| 49 | // 函数4:返回第 n 个素数(演示 CPU 密集任务下沉到 C++) | |
| 50 | extern "C" int py_nth_prime(int n) { | |
| 51 | ++g_calls; | |
| 52 | int found = 0; | |
| 53 | for (int candidate = 2;; ++candidate) { | |
| 54 | bool prime = true; | |
| 55 | for (int d = 2; d * d <= candidate; ++d) | |
| 56 | if (candidate % d == 0) { prime = false; break; } | |
| 57 | if (prime && ++found == n) return candidate; | |
| 58 | } | |
| 59 | } | |
| 60 | ||
| 61 | // 函数5:查询调用次数(验证全局状态共享) | |
| 62 | extern "C" int py_call_count() { return g_calls; } |
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | # 混合示例E:Python 用 ctypes 调用 C++ 共享库 | |
| 2 | # 运行:python3 mixE_python.py (先编译 libmixE.so) | |
| 3 | import ctypes | |
| 4 | ||
| 5 | # 加载共享库(ctypes 只认 C ABI,所以 C++ 侧必须 extern "C") | |
| 6 | lib = ctypes.CDLL("./libmixE.so") | |
| 7 | ||
| 8 | # ---- 配置函数签名(ctypes 默认假设 int 参数,必须显式声明)---- | |
| 9 | ||
| 10 | # 函数1:mul(long long, long long) -> long long | |
| 11 | lib.py_mul.restype = ctypes.c_longlong | |
| 12 | lib.py_mul.argtypes = [ctypes.c_longlong, ctypes.c_longlong] | |
| 13 | print("py_mul(7, 6) =", lib.py_mul(7, 6)) | |
| 14 | ||
| 15 | # 函数2:uppercase(const char*) -> char*(返回 malloc 内存,用完释放) | |
| 16 | lib.py_uppercase.restype = ctypes.c_char_p # 自动转 bytes | |
| 17 | lib.py_uppercase.argtypes = [ctypes.c_char_p] | |
| 18 | s = lib.py_uppercase(b"hello c++ from python") | |
| 19 | print("py_uppercase =", s.decode()) | |
| 20 | ||
| 21 | # 函数3:sum_squares(const double*, int) -> double | |
| 22 | # Python list → ctypes 数组(需要 (c_double * n) 类型) | |
| 23 | lib.py_sum_squares.restype = ctypes.c_double | |
| 24 | lib.py_sum_squares.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.c_int] | |
| 25 | data = [3.0, 4.0, 12.0] | |
| 26 | arr = (ctypes.c_double * len(data))(*data) # 构造 ctypes 数组 | |
| 27 | print("py_sum_squares([3,4,12]) =", lib.py_sum_squares(arr, len(data))) | |
| 28 | ||
| 29 | # 函数4:nth_prime(int) -> int(CPU 密集任务) | |
| 30 | lib.py_nth_prime.restype = ctypes.c_int | |
| 31 | lib.py_nth_prime.argtypes = [ctypes.c_int] | |
| 32 | print("第 1000 个素数 =", lib.py_nth_prime(1000)) | |
| 33 | ||
| 34 | # 函数5:全局状态在两次调用之间保持 | |
| 35 | lib.py_call_count.restype = ctypes.c_int | |
| 36 | print("累计调用次数 =", lib.py_call_count()) | |
| 37 | ||
| 38 | # 性能对比:同样的循环,Python 与 C++ 的速度差 | |
| 39 | import time | |
| 40 | ||
| 41 | def py_sum_squares_pure(n): | |
| 42 | total = 0.0 | |
| 43 | for i in range(1, n + 1): | |
| 44 | total += i * i | |
| 45 | return total | |
| 46 | ||
| 47 | N = 5_000_000 | |
| 48 | big = (ctypes.c_double * N)(*range(1, N + 1)) | |
| 49 | ||
| 50 | t0 = time.perf_counter() | |
| 51 | r_cpp = lib.py_sum_squares(big, N) | |
| 52 | t1 = time.perf_counter() | |
| 53 | r_py = py_sum_squares_pure(N) | |
| 54 | t2 = time.perf_counter() | |
| 55 | ||
| 56 | print(f"C++ 耗时 {t1-t0:.3f}s,结果 {r_cpp:.0f}") | |
| 57 | print(f"Python 耗时 {t2-t1:.3f}s,结果 {r_py:.0f}") | |
| 58 | print(f"C++ 快 {(t2-t1)/(t1-t0):.1f} 倍") |
clang++ -std=c++23 -fPIC -shared mixE_lib.cpp -o libmixE.so python3 mixE_python.py
py_mul(7, 6) = 42 py_uppercase = HELLO C++ FROM PYTHON py_sum_squares([3,4,12]) = 169.0 第 1000 个素数 = 7919 累计调用次数 = 4 C++ 耗时 0.022s,结果 41666679166732894208 Python 耗时 0.674s,结果 41666679166732894208 C++ 快 30.4 倍
restype 声明返回类型(默认当作 int,double/指针必改);
② argtypes 声明参数类型(默认当作 int,传 double/指针会出错);
③ Python 的 bytes 与 C 的 char* 自动互转,但 C++ 侧返回的 malloc 内存要 Python 侧 libc.free 释放。现实场景:公司 2014 年的旧模块只能按 C++11 编译,新代码想用 C++23。可行吗?可行——
因为决定"两个 .o 能否链接"的是 C++ ABI(类型布局、符号修饰、异常模型),它由同一编译器 + 同一标准库保证,与各自用的语言标准版本无关。唯一约束:extern 共享的对象/函数两侧声明一致。
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例F:多标准混编(两个翻译单元用不同 -std 编译后链接) | |
| 2 | // 老库 legacy.cpp:只用 C++11 特性,按 -std=c++11 编译 | |
| 3 | // 新代码 modern.cpp:用 C++23 特性,按 -std=c++23 编译 | |
| 4 | // | |
| 5 | // 编译命令: | |
| 6 | // clang++ -std=c++11 -c legacy.cpp -o legacy.o | |
| 7 | // clang++ -std=c++23 -c modern.cpp -o modern.o | |
| 8 | // clang++ legacy.o modern.o -o mixF && ./mixF | |
| 9 | ||
| 10 | #include <string> | |
| 11 | #include <map> | |
| 12 | ||
| 13 | // ===== 老库实现(按 C++11 编译)===== | |
| 14 | class LegacyStats { | |
| 15 | public: | |
| 16 | void record(const std::string& key, int value) { data_[key] = value; } | |
| 17 | int total() const { | |
| 18 | int sum = 0; | |
| 19 | // C++11 风格:手写迭代器,无范围 for 也可用(但11其实有) | |
| 20 | for (std::map<std::string, int>::const_iterator it = data_.begin(); | |
| 21 | it != data_.end(); ++it) | |
| 22 | sum += it->second; | |
| 23 | return sum; | |
| 24 | } | |
| 25 | private: | |
| 26 | std::map<std::string, int> data_; | |
| 27 | }; | |
| 28 | ||
| 29 | // 导出:跨翻译单元共享的全局对象(声明在 modern.cpp) | |
| 30 | static LegacyStats g_stats; | |
| 31 | int legacy_total(); | |
| 32 | void legacy_record(const char* key, int value); | |
| 33 | ||
| 34 | void legacy_record(const char* key, int value) { | |
| 35 | g_stats.record(key, value); | |
| 36 | } | |
| 37 | int legacy_total() { | |
| 38 | return g_stats.total(); | |
| 39 | } |
| 行 | 代码 | 逐行解释 |
|---|---|---|
| 1 | // 混合示例F-现代:新代码按 C++23 编译,调用老库 | |
| 2 | // 说明:不同 -std 编译的 .o 可以链接——因为 C++ ABI | |
| 3 | // (类布局、函数符号修饰、异常处理)由"编译器+标准库"决定, | |
| 4 | // 只要都用 clang+libc++,混编 C++11 和 C++23 代码是安全的。 | |
| 5 | #include <print> | |
| 6 | ||
| 7 | // 老库接口声明(legacy.cpp 提供) | |
| 8 | void legacy_record(const char* key, int value); | |
| 9 | int legacy_total(); | |
| 10 | ||
| 11 | int main() { | |
| 12 | // 用 C++23 语法调用 C++11 时代的库 | |
| 13 | legacy_record("苹果", 3); | |
| 14 | legacy_record("香蕉", 5); | |
| 15 | legacy_record("橙子", 2); | |
| 16 | ||
| 17 | std::println("总计 = {}", legacy_total()); | 10 |
| 18 | ||
| 19 | // 演示编译期标准检测(__cplusplus 宏,实际值见下方输出) | |
| 20 | std::println("本文件标准: C++{}", | |
| 21 | __cplusplus == 202302L ? 23 : | |
| 22 | __cplusplus == 202002L ? 20 : | |
| 23 | __cplusplus == 201703L ? 17 : | |
| 24 | __cplusplus == 201402L ? 14 : | |
| 25 | __cplusplus == 201103L ? 11 : 98); | |
| 26 | return 0; | |
| 27 | } |
clang++ -std=c++11 -c legacy.cpp -o legacy.o # 老代码按 C++11 clang++ -std=c++23 -c modern.cpp -o modern.o # 新代码按 C++23 clang++ legacy.o modern.o -o mixF && ./mixF # 验证两个 .o 的标准:clang++ -std=c++11 -E -dM legacy.cpp | grep __cplusplus # → #define __cplusplus 201103L # → #define __cplusplus 202302L
总计 = 10 本文件标准: C++23
| 目标 | 方式 | 本质 |
|---|---|---|
| C | extern "C" + 直接链接 | 同一 ABI,最亲密的混合(示例 2.1/2.2) |
| Python | ctypes / pybind11 / Boost.Python | ctypes 按 C ABI 动态加载 .so;pybind11 生成扩展模块(示例 2.5) |
| Rust | extern "C" / FFI 声明 | Rust 侧用 unsafe extern "C" fn 声明 C++ 导出,同样走 C ABI |
| 汇编 | asm volatile / 独立 .s 文件 | 直接操作寄存器与指令(示例 2.4) |
| 操作系统 | libc / syscall / errno | POSIX 接口就是 C 函数,规则同 C(示例 2.3) |
| JavaScript/WASM | emscripten 编译 | 编译器把 C++ 翻译成 WASM,导出函数给 JS 调用 |
std::string/vector(布局因实现而异);② 谁分配谁释放;③ C++ 侧导出的异常绝不能穿过边界(C/Python 不会展开 C++ 异常 → 崩溃),务必在导出函数内 catch 干净;④ 返回的 char* 约定好是 malloc 还是 new[],两侧一致。