首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >前端开发者快速掌握 C++:从 JS/Kotlin 到 C++ 的思维跨越

前端开发者快速掌握 C++:从 JS/Kotlin 到 C++ 的思维跨越

原创
作者头像
骑猪耍太极
发布2026-05-14 21:13:06
发布2026-05-14 21:13:06
130
举报
文章被收录于专栏:AI编程之旅AI编程之旅

AI 编程时代,开发者的技术边界正在被重新定义。作为一个前端开发者,我最近在跨平台框架项目中需要参与 C++ 模块的 Code Review 和协同开发。这篇文章记录了我从 JavaScript/Kotlin 背景切入 C++ 的学习路径——不追求系统性,只追求快速建立可工作的心智模型

一、最大的认知转变:没有垃圾回收

前端开发者学 C++ 最大的障碍不是语法,而是思维模式的切换

在 JS/Kotlin 的世界里,你创建一个对象后就不用管它了:

代码语言:javascript
复制
// JS — 创建完就忘,GC 帮你收拾
function createUser() {
    const user = { name: "Tom", age: 25 };
    return user;  // 用完了 GC 自动回收
}

但在 C++ 中,每一个对象都需要有明确的"主人"——谁创建了它,谁负责销毁它:

代码语言:cpp
复制
// C++ — 你必须为每个对象安排归宿
User* user = new User("Tom", 25);   // 你创建了它
// ... 使用 user ...
delete user;                          // 你必须销毁它,否则内存泄漏

好消息是,现代 C++ 已经有了"半自动"方案——智能指针。这是你学 C++ 最该先掌握的东西。


二、智能指针 = C++ 的"引用管理器"

shared_ptr ≈ 普通变量引用

代码语言:cpp
复制
// C++
#include <memory>
auto node = std::make_shared<TreeNode>(1, "Button");
auto alias = node;  // 引用计数 +1,两个变量指向同一个对象
// 当 node 和 alias 都离开作用域时,对象才被销毁
代码语言:javascript
复制
// JS 等价 — 你从来不需要想这些,GC 自动处理
const node = new TreeNode(1, "Button");
const alias = node;
代码语言:kotlin
复制
// Kotlin 等价
val node = TreeNode(1, "Button")
val alias = node

要点: shared_ptr 内部维护一个引用计数器。每次拷贝 +1,每次销毁 -1,归零时自动 delete

unique_ptr ≈ "独占所有权"

代码语言:cpp
复制
// C++
auto cache = std::make_unique<CacheData>();
// auto copy = cache;           // ❌ 编译错误!unique_ptr 不能拷贝
auto moved = std::move(cache);  // ✅ 所有权转移,cache 变成 nullptr

类比理解:就像你把一个快递单号给了别人,你手里的单号就作废了。

weak_ptr ≈ WeakReference

代码语言:cpp
复制
// C++
std::weak_ptr<RenderView> view_ref;  // 弱引用,不阻止对象销毁
if (auto view = view_ref.lock()) {    // 尝试获取强引用
    view->Render();                    // 成功:对象还活着
}
// 失败:对象已被销毁,lock() 返回 nullptr
代码语言:kotlin
复制
// Kotlin
val viewRef = WeakReference(renderView)
viewRef.get()?.render()  // 对象被回收了就返回 null
代码语言:javascript
复制
// JS — WeakRef (ES2021)
const viewRef = new WeakRef(renderView);
viewRef.deref()?.render();

什么时候用哪个?速记口诀

智能指针

口诀

场景

shared_ptr

"大家一起用"

节点树、被多处引用的对象

unique_ptr

"只有我能用"

缓存数据、工厂创建的对象

weak_ptr

"我看看你还在不在"

回调中引用父对象、避免循环引用


三、可空类型

C++ 的 std::optional 对应 Kotlin 的 ? 和 JS 的 undefined

代码语言:cpp
复制
// C++
std::optional<int> parentId;           // 没有值
parentId = 42;                          // 有值
if (parentId.has_value()) {
    int id = parentId.value();          // 取值
}
parentId = std::nullopt;                // 置空
代码语言:kotlin
复制
// Kotlin
var parentId: Int? = null
parentId = 42
parentId?.let { id -> /* 使用 id */ }
parentId = null
代码语言:javascript
复制
// JS
let parentId = undefined;
parentId = 42;
if (parentId !== undefined) { /* 使用 parentId */ }
parentId = undefined;

四、一个类 = 两个文件

C++ 把"声明"和"实现"分开,这对前端开发者最反直觉:

代码语言:cpp
复制
// TreeNode.h — 头文件(声明):"我长什么样"
class TreeNode {
public:
    int tag;
    std::string name;
    void AddChild(std::shared_ptr<TreeNode> child, int index);
private:
    std::shared_ptr<Prop> FindProp(const std::string& key) const;
};

// TreeNode.cpp — 源文件(实现):"我怎么工作"
void TreeNode::AddChild(std::shared_ptr<TreeNode> child, int index) {
    children.insert(children.begin() + index, child);
    child->parentTag = tag;
}
代码语言:kotlin
复制
// Kotlin — 一个文件搞定
class TreeNode(var tag: Int, var name: String) {
    fun addChild(child: TreeNode, index: Int) {
        children.add(index, child)
        child.parentTag = tag
    }
    private fun findProp(key: String): Prop? { ... }
}

为什么要分两个文件? 因为 C++ 的编译模型:编译器处理每个 .cpp 文件时需要知道其他类"长什么样"(靠 #include 头文件),但不需要知道"怎么工作"。这样可以加速编译——修改实现时只重新编译一个 .cpp,不影响其他文件。

关键语法:

  • public: / private: = Kotlin 的 public / private
  • TreeNode::AddChild 中的 :: 表示"属于",相当于"TreeNode 类的 AddChild 方法"
  • const 出现在方法末尾(如 FindProp(...) const)= 承诺此方法不修改成员变量

五、参数传递:& 的三种用法

这是 C++ 代码中最密集的符号,前端开发者容易懵:

代码语言:cpp
复制
void Process(const std::string& name);    // const 引用
void Modify(std::string& name);           // 可变引用
void Consume(std::string&& name);         // 右值引用(移动语义)

用餐厅类比:

写法

类比

含义

std::string name

打包一份外卖带走

完整拷贝,互不影响

const std::string& name

在餐厅里看菜单拍照

借来看看,不拷贝不修改

std::string& name

去后厨帮厨

直接在原数据上修改

std::string&& name

把整个餐车推走

转移所有权,原来的变空

代码语言:javascript
复制
// JS 开发者理解:
// JS 中对象天然是引用传递,基本类型是值传递
// C++ 默认全是值传递(拷贝),加 & 才是引用传递

实战中最常见的是 const &,约 80% 的函数参数都用它——既避免拷贝开销,又保证不被修改。


六、Lambda 表达式

代码语言:cpp
复制
// C++
auto callback = [this, count](const std::string& msg) {
    std::cout << name << ": " << msg << " (" << count << ")" << std::endl;
};
代码语言:javascript
复制
// JS — 自动捕获,不需要声明
const callback = (msg) => {
    console.log(`${this.name}: ${msg} (${count})`);
};
代码语言:kotlin
复制
// Kotlin — 也是自动捕获
val callback = { msg: String ->
    println("${name}: $msg ($count)")
}

关键差异:C++ lambda 必须用 [] 显式声明要捕获哪些变量。

捕获方式

含义

[this]

捕获当前对象指针

[count]

按值拷贝 count

[&count]

按引用捕获 count(危险!原变量销毁后变悬空)

[=]

按值拷贝所有外部变量

[&]

按引用捕获所有外部变量(最危险)


七、容器对照

C++

JS

Kotlin

说明

std::vector<T>

Array

MutableList<T>

动态数组

std::unordered_map<K,V>

Map / Object

HashMap<K,V>

哈希表

std::unordered_set<T>

Set

HashSet<T>

哈希集合

std::string

string

String

字符串

std::pair<A,B>

[a, b] 解构

Pair<A,B>

二元组

常用操作速查:

代码语言:cpp
复制
// C++ vector
std::vector<int> nums;
nums.push_back(42);                        // JS: nums.push(42)
nums.insert(nums.begin() + 2, 99);         // JS: nums.splice(2, 0, 99)
nums.erase(nums.begin() + 2);              // JS: nums.splice(2, 1)
nums.size();                               // JS: nums.length
nums.empty();                              // JS: nums.length === 0
nums.reserve(100);                         // JS: 无对应,引擎自动管理

// C++ unordered_map
std::unordered_map<std::string, int> ages;
ages["Tom"] = 25;                          // JS: ages["Tom"] = 25 或 ages.set("Tom", 25)
auto it = ages.find("Tom");                // JS: ages.has("Tom")
if (it != ages.end()) {
    int age = it->second;                  // JS: ages.get("Tom")
}
ages.erase("Tom");                         // JS: ages.delete("Tom") 或 delete ages["Tom"]

八、auto = 让编译器推导类型

代码语言:cpp
复制
// C++ — auto 推导
auto node = std::make_shared<TreeNode>(1, "Button");   // 类型: shared_ptr<TreeNode>
auto it = nodeMap.find(tag);                            // 类型: unordered_map<...>::iterator
auto [updated, hasWatch] = DiffTrees(oldTree, newTree); // C++17 解构绑定
代码语言:javascript
复制
// JS — 天生弱类型
const node = new TreeNode(1, "Button");
const entry = nodeMap.get(tag);
const [updated, hasWatch] = diffTrees(oldTree, newTree);
代码语言:kotlin
复制
// Kotlin — 类型推导
val node = TreeNode(1, "Button")
val entry = nodeMap[tag]
val (updated, hasWatch) = diffTrees(oldTree, newTree)

九、接口与继承

代码语言:cpp
复制
// C++ 接口(纯虚类)
class IRenderLayer {
public:
    virtual void CreateView(int tag, const std::string& name) = 0;  // = 0 纯虚
    virtual void Destroy() = 0;
    virtual ~IRenderLayer() = default;  // 虚析构函数(接口必备)
};

// C++ 实现
class ProxyRenderLayer : public IRenderLayer {
    void CreateView(int tag, const std::string& name) override { ... }
    void Destroy() override { ... }
};
代码语言:kotlin
复制
// Kotlin
interface IRenderLayer {
    fun createView(tag: Int, name: String)
    fun destroy()
}

class ProxyRenderLayer : IRenderLayer {
    override fun createView(tag: Int, name: String) { ... }
    override fun destroy() { ... }
}
代码语言:javascript
复制
// JS — 没有接口,靠鸭子类型
class ProxyRenderLayer {
    createView(tag, name) { ... }
    destroy() { ... }
}

关键词对照:

C++

Kotlin

JS

virtual ... = 0

abstract fun

override

override

: public Base

: Base()

extends Base

dynamic_pointer_cast<T>(p)

p as? T

无(靠 instanceof


十、异常处理

代码语言:cpp
复制
// C++
try {
    auto node = Deserialize(data, offset);
} catch (const std::exception& e) {
    // e.what() 获取错误信息
    return nullptr;
} catch (...) {
    // 捕获所有异常(包括非标准异常)
    return nullptr;
}
代码语言:kotlin
复制
// Kotlin
try {
    val node = deserialize(data, offset)
} catch (e: Exception) {
    return null
}
代码语言:javascript
复制
// JS
try {
    const node = deserialize(data, offset);
} catch (e) {
    return null;
}

catch (...) 是 C++ 特有的"兜底捕获",对应 Kotlin 的 catch (e: Throwable) 或 JS 的 catch (e)


十一、线程同步

代码语言:cpp
复制
// C++ — RAII 锁(作用域结束自动解锁)
std::mutex mutex;
void WriteFile(const std::vector<uint8_t>& data) {
    std::lock_guard<std::mutex> lock(mutex);
    // ... 线程安全操作 ...
}  // ← lock 离开作用域,自动解锁
代码语言:kotlin
复制
// Kotlin
private val mutex = Any()
fun writeFile(data: ByteArray) {
    synchronized(mutex) {
        // ... 线程安全操作 ...
    }
}
代码语言:javascript
复制
// JS — 单线程模型,通常不需要锁
// Web Worker 间通过 postMessage 通信,无共享内存

十二、static = 类级别的"共享资源"

代码语言:cpp
复制
// C++
class CacheManager {
    static std::mutex fileMutex;  // 所有实例共享一把锁
    static std::string BuildCacheKey(const std::string& page, const std::string& key);
};

// .cpp 中定义(static 成员必须在类外定义一次)
std::mutex CacheManager::fileMutex;
代码语言:kotlin
复制
// Kotlin
class CacheManager {
    companion object {
        private val fileMutex = Mutex()
        fun buildCacheKey(page: String, key: String): String { ... }
    }
}
代码语言:javascript
复制
// JS
class CacheManager {
    static fileMutex = new Mutex(); // 提案 stage 3
    static buildCacheKey(page, key) { ... }
}

十三、构造函数初始化列表

代码语言:cpp
复制
// C++ — 冒号后面的部分是"初始化列表"
ProxyRenderLayer::ProxyRenderLayer()
    : realLayer(std::make_shared<RenderLayer>()),
      rootNode(std::make_shared<TreeNode>(-1, "Root")) {
    // 构造函数体(可选的额外逻辑)
}
代码语言:kotlin
复制
// Kotlin — 直接在属性声明时初始化
class ProxyRenderLayer {
    private val realLayer = RenderLayer()
    private val rootNode = TreeNode(-1, "Root")
}

初始化列表的优势:在对象创建的第一时间就赋值,比在构造函数体内赋值更高效(避免先默认构造再赋值)。


十四、variant ≈ sealed class / 联合类型

代码语言:cpp
复制
// C++17 variant — 类型安全的联合体
std::variant<
    std::monostate,                     // 空状态
    std::shared_ptr<Value>,             // 普通值
    std::function<void(Result)>,        // 回调函数
    std::shared_ptr<ShadowNode>         // 布局节点
> propValue;

// 类型判断
if (std::holds_alternative<std::shared_ptr<Value>>(propValue)) {
    auto val = std::get<std::shared_ptr<Value>>(propValue);
}
代码语言:kotlin
复制
// Kotlin sealed class
sealed class PropValue {
    object Empty : PropValue()
    data class AnyValue(val value: Value) : PropValue()
    data class Callback(val fn: (Result) -> Unit) : PropValue()
    data class Shadow(val node: ShadowNode) : PropValue()
}

when (propValue) {
    is PropValue.AnyValue -> { val v = propValue.value }
    ...
}
代码语言:typescript
复制
// TypeScript 联合类型
type PropValue =
    | { type: "empty" }
    | { type: "value"; value: Value }
    | { type: "callback"; fn: (result: Result) => void }
    | { type: "shadow"; node: ShadowNode };

十五、高频模式速查表

在真实项目中你会反复遇到这些代码模式,贴在手边备查:

C++ 代码

含义

JS/Kotlin 心智模型

auto& x = vec[i]

引用取元素,不拷贝

const x = list[i]

if (!ptr)

指针/智能指针为空

if (ptr == null)

ptr->method()

通过指针调方法

ptr.method()

*shared_bool

解引用取值

atomicBool.get()

static_cast<int>(x)

显式类型转换

x.toInt() / x as Int

std::move(x)

转移所有权

无对应(GC 管)

constexpr

编译时常量

const val

std::function<void()>

函数类型

() -> Unit / () => void

#include "xxx.h"

导入头文件

import

namespace X { }

命名空间

package / 模块


写在最后

以上并不是一份完整的 C++ 教程,而是我作为前端开发者在实际工作中最高频使用的知识子集。掌握这些内容后,你就能看懂大部分现代 C++ 项目代码,足以参与 Code Review 和协同开发。

AI 编程时代,AI 可以帮你写出 C++ 代码,但你至少要能读懂它、审查它。希望这篇文章能帮你迈出这一步。

下一篇文章我们聊聊更实战的话题:C++ 中那些前端开发者难以发现的 Bug。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、最大的认知转变:没有垃圾回收
  • 二、智能指针 = C++ 的"引用管理器"
    • shared_ptr ≈ 普通变量引用
    • unique_ptr ≈ "独占所有权"
    • weak_ptr ≈ WeakReference
    • 什么时候用哪个?速记口诀
  • 三、可空类型
  • 四、一个类 = 两个文件
  • 五、参数传递:& 的三种用法
  • 六、Lambda 表达式
  • 七、容器对照
  • 八、auto = 让编译器推导类型
  • 九、接口与继承
  • 十、异常处理
  • 十一、线程同步
  • 十二、static = 类级别的"共享资源"
  • 十三、构造函数初始化列表
  • 十四、variant ≈ sealed class / 联合类型
  • 十五、高频模式速查表
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档