1.5. 从其他语言迁移到 C++
本节帮助有其他语言背景的开发者快速适应 C++。
1.5.1. 从 Python 迁移
1.5.1.1. 类型系统差异
// Python: 动态类型
// x = 1
// x = "hello" # OK
// C++: 静态类型
int x = 1;
// x = "hello"; // 编译错误!
// 使用 auto 可以简化类型声明
auto x = 1; // int
auto y = "hello"; // const char*
auto z = std::string("hello"); // std::string
1.5.1.2. 内存管理差异
# Python: 引用计数 + GC
class Node:
def __init__(self):
self.next = None
a = Node()
b = Node()
a.next = b # 引用计数管理
// C++: 需要显式管理
// 方法1:智能指针(推荐)
struct Node {
std::shared_ptr<Node> next;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
// 方法2:值语义
struct Node {
std::unique_ptr<Node> next;
};
1.5.1.3. 字符串处理
# Python
s = "hello"
s += " world"
parts = s.split(" ")
// C++
std::string s = "hello";
s += " world";
// 分割字符串需要手动实现或使用库
std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> result;
std::istringstream iss(s);
std::string token;
while (std::getline(iss, token, delimiter)) {
result.push_back(token);
}
return result;
}
// C++20 ranges 版本
auto parts = s | std::views::split(' ');
1.5.1.4. 容器操作
# Python
numbers = [1, 2, 3, 4, 5]
doubled = [x * 2 for x in numbers]
filtered = [x for x in numbers if x > 2]
// C++ 传统写法
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::vector<int> doubled;
std::transform(numbers.begin(), numbers.end(),
std::back_inserter(doubled),
[](int x) { return x * 2; });
// C++20 Ranges
auto doubled = numbers | std::views::transform([](int x) { return x * 2; });
auto filtered = numbers | std::views::filter([](int x) { return x > 2; });
1.5.2. 从 Java 迁移
1.5.2.1. 对象创建和销毁
// Java
Widget widget = new Widget(); // 总是在堆上
// 自动 GC
Widget w1 = new Widget();
Widget w2 = w1; // w1 和 w2 指向同一对象
// C++: 多种方式
Widget w1; // 栈上创建,自动销毁(推荐)
auto w2 = std::make_unique<Widget>(); // 堆上,unique_ptr 管理
auto w3 = std::make_shared<Widget>(); // 堆上,shared_ptr 管理
// 值语义 vs 引用语义
Widget a;
Widget b = a; // 拷贝!b 是 a 的副本
Widget& ref = a; // ref 是 a 的引用
1.5.2.2. 继承与多态
// Java
class Base {
void method() { } // 默认可重写
}
class Derived extends Base {
@Override
void method() { }
}
// C++
class Base {
public:
virtual void method() { } // 必须显式声明 virtual
virtual ~Base() = default; // 虚析构函数很重要!
};
class Derived : public Base {
public:
void method() override { } // override 是可选的但推荐
};
// 使用多态
void process(Base& b) { // 必须用引用或指针
b.method(); // 动态绑定
}
1.5.2.3. 接口与抽象类
// Java
interface Drawable {
void draw();
}
class Circle implements Drawable {
public void draw() { }
}
// C++: 使用纯虚函数
class Drawable {
public:
virtual void draw() = 0; // 纯虚函数
virtual ~Drawable() = default;
};
class Circle : public Drawable {
public:
void draw() override { }
};
// C++20: 使用 Concepts 作为"接口"
template<typename T>
concept Drawable = requires(T t) {
{ t.draw() } -> std::same_as<void>;
};
template<Drawable T>
void render(T& obj) {
obj.draw();
}
1.5.2.4. 异常处理
// Java: 检查型异常
public void readFile() throws IOException {
// 必须声明或处理
}
// C++: 非检查型异常
void readFile() { // 可能抛任何异常
throw std::runtime_error("error");
}
// C++11: noexcept 说明符
void safeFunction() noexcept { // 承诺不抛异常
// 如果抛异常,程序终止
}
// 异常规范已废弃,不要使用
void oldStyle() throw(std::exception); // 已废弃!
1.5.3. 从 Go 迁移
1.5.3.1. 并发模型差异
// Go: Goroutine + Channel
func worker(ch chan int) {
for n := range ch {
fmt.Println(n)
}
}
func main() {
ch := make(chan int)
go worker(ch)
ch <- 42
close(ch)
}
// C++: std::thread + std::mutex/atomic/condition_variable
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
class Channel {
std::queue<int> queue_;
std::mutex mutex_;
std::condition_variable cv_;
bool closed_ = false;
public:
void send(int value) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(value);
cv_.notify_one();
}
std::optional<int> receive() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty() || closed_; });
if (queue_.empty()) return std::nullopt;
int value = queue_.front();
queue_.pop();
return value;
}
void close() {
std::lock_guard<std::mutex> lock(mutex_);
closed_ = true;
cv_.notify_all();
}
};
1.5.3.2. 错误处理差异
// Go: 多返回值
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
result, err := divide(10, 0)
if err != nil {
// 处理错误
}
// C++17: std::optional 或 std::variant
std::optional<int> divide(int a, int b) {
if (b == 0) return std::nullopt;
return a / b;
}
if (auto result = divide(10, 2)) {
std::cout << *result;
}
// C++23: std::expected
std::expected<int, std::string> divide(int a, int b) {
if (b == 0) return std::unexpected("division by zero");
return a / b;
}
auto result = divide(10, 0);
if (result) {
std::cout << *result;
} else {
std::cerr << result.error();
}
1.5.3.3. 结构体与方法
// Go
type Point struct {
X, Y int
}
func (p Point) Distance() float64 {
return math.Sqrt(float64(p.X*p.X + p.Y*p.Y))
}
// C++
struct Point {
int x, y;
double distance() const {
return std::sqrt(x * x + y * y);
}
};
// 或使用自由函数
double distance(const Point& p) {
return std::sqrt(p.x * p.x + p.y * p.y);
}
1.5.4. 常见迁移问题
1.5.4.1. 1. 忘记初始化
int x; // 未初始化!值不确定
int y = 0; // OK
int z{}; // OK,值初始化为 0
// 类成员也需要初始化
class Widget {
int value = 0; // C++11 默认成员初始化
std::string name; // OK,string 默认为空
};
1.5.4.2. 2. 混淆指针和引用
void modify_ptr(int* p) {
*p = 42; // 需要解引用
}
void modify_ref(int& r) {
r = 42; // 直接使用
}
int x = 0;
modify_ptr(&x); // 传地址
modify_ref(x); // 传引用
int* ptr = nullptr; // 指针可以为空
// int& ref = nullptr; // 引用不能为空
1.5.4.3. 3. 值传递 vs 引用传递
// 小对象:值传递
void process(int x) { }
// 大对象只读:const 引用
void process(const std::string& s) { }
// 需要修改:非 const 引用
void process(std::string& s) { }
// 转移所有权:移动
void process(std::unique_ptr<Widget> w) { }
1.5.4.4. 4. 理解作用域和生命周期
std::string* dangerous() {
std::string s = "hello";
return &s; // 危险!s 将被销毁
}
std::string safe() {
std::string s = "hello";
return s; // OK,返回拷贝或移动
}
// RAII 作用域
{
std::lock_guard<std::mutex> lock(mtx);
// 临界区
} // lock 析构,自动释放锁
备注
从其他语言迁移到 C++ 最重要的是理解:
值语义 vs 引用语义
手动内存管理(通过 RAII 和智能指针)
编译期 vs 运行期
模板与泛型的区别