虽说编程语言只是承载思想的一种媒介,但是每种编程语言有自己的设计哲学,所以在实现自己思想的时候,也需要遵循该门语言的理念才行。截止 2022 年 07 月 12 日本人最大的体验是,Java 这门语言存在大量的过度封装(所以能封装成类就封装成类),C++ 这门语言有不少奇技淫巧(所以各种奇怪的茴香豆五种写法),所以编程体验差异很大。本文主要描述了 Java 程序员转 C++ 程序员写代码时那些不习惯的开发方式。
结果是对的,但它可能是不对的。
Java 中的包管理工具有 Maven, Gradle 等,但是 C++ 就一个 CMake(还有 Blade),对于引入的包的版本管理就是个空白,想要更新依赖的包,那就必须重新下载源码,重新编译。
C++ 变量的生命周期非常短,出了{} 往往就意味着生命周期结束了(除非是使用 new 在堆上定义一个对象)。最危险的莫过于返回了局部对象的引用或者指针。
int64_t* test1() {
int64_t arr[] = {1, 3, 5};
return arr;
}
int64_t* p = test1();
这样的写法,在 Java 中是没有问题的,但是 C++ 中就会出现结果不符合预期的情况(不会报错),原因就是 arr 是定义在函数内的局部变量,一旦 return 之后就释放掉了。
但是呢,返回一个值是对的(这个值即是个对象也行,未使用 new)
int test1() {
int64_t p = 1;
return p;
}
int num = test1();
这倒不是因为变量 p 的生命周期延长了,而是将临时变量 p 的结果通过拷贝的形式赋值给了 num。所以这里就引入了一个非常细节的问题,如果返回的是个对象,那么这个对象对应的类应该实现了拷贝构造函数来支持深拷贝,不然结果最后在函数外使用该临时变量的结果可能会不符合预期。对于 C++ 的基本类型,值拷贝一般也不会出什么问题。这里需要注意 Return Value Optimization(RVO),它可能让中间的表现形式看起来不是自己想的那样(比如不会调用拷贝构造函数之类的)。
函数返回值类型越简单越好
C++ 的返回值一般不会使用对象返回(除非该对象非常简单,函数逻辑同样非常简单)。
uint64_t Encrypt(Request& request, Response& response) {
...
return 0;
}
C++ 一般会写成上述风格,但是 Java 一般会写成下述风格。
public Response Encrypt(Request request) {
Response resp = new Response();
...
return resp;
}
C++ 的参数一般使用指针或者引用传递参数,很少使用值传递的方式来传参,除非确定值传参的性能会更高。不过指针和引用都能避免传递参数的拷贝,有时抉择使用哪个还挺难办的,目前个人更倾向入参使用引用,出参使用指针。当然 Java 中的对象传值都是引用的方式,所以这种形式也天然支持,不过就个人历史经验而言,很少见到这种形式的写法。
uint64_t Encrypt(Request& request, Response* response) {
...
return 0;
}
Java 项目中,尤其 Web 项目中,一般会以定义贫血模型为主(部分项目也会定义充血模型,尤其社区项目),C++ 中几乎很难看到贫血模型的定义方式。
虽然说 C++/Java 都是一种面向对象的语言,如果写过 Java Web 的话,会习惯性的各种封装以贴合 MVC 模式,C++ 就不是非常的在意这一套了,能使用基本参数的,几乎都会以基本参数优先。(如果参数多于 6 个再考虑将这些参数封装成类进行传递)。
void query(uint64_t stu_id, std::string name) {
// do something
}
C++ 大概会写成上述这样,但是 Java 更侧重下述写法。
public class StuModel {
int id;
String name;
}
public void query(StuModel model) {
// do something
}
异常可没那么流行
C++ 提供了异常机制,不过大家并不怎么会使用它。C++ 中一般返回错误码的形式来处理考虑不周到的情况,
Errorcode test() {
// some function
if (error) {
return Errorcode::FAILED_MSG;
}
return Errorcode::OK;
}
C++ 一般习惯上述写法,但是对于 Java 一般习惯下述写法。
public void test() {
try {
// some functions
} catch (RunTimeException e) {
throw new ByteDanceCustomException("error cause", e);
}
}
Java 中对于类的属性一般会定义为私有,然后调用 get/set 函数来对其访问,但是 C++ 一般会直接将该属性定义为公有。
class Stu {
public:
uint64_t id;
std::string name;
}
Stu stu;
stu.id = 1;
stu.name = "zhang"
std::cout << stu.id << stu.name << std::endl;
C++ 会写成上述形式,但是 Java 更侧重写成下述形式。
public class Stu {
public getId() {
return id;
}
public setId(int id) {
this.id = id;
}
...
private int id;
private String name;
}
Stu stu = new Stu();
stu.setId(1);
System.out.println(stu.getId() + stu.getName());
Java 的构造函数中一般会添加不少的初始化逻辑,在开源的项目中经常能看到这样的写法。C++ 当然也可以这么写,但是大家平时不会这么写。
class Encrypto {
public:
Encrypto() = default;
Init();
}
// Encrypto encrypto;
// encrypto.Init();
C++ 大概会按照上述方式写,Java 则会按照下述方式写。
public class Encrypto {
public Encrypto() {
Init();
}
public void Init() {
// do something
}
}
// Encrypto encrypto = new Encrypto();
C++ 并不喜欢链式编程,所以下述的写法是对的,但是大家很少这么写。
class Stu {
public:
Stu id(uint64_t id) {
this->id_ = id;
return *this;
}
Stu name(std::string name) {
this->name_ = name;
return *this;
}
Stu age(uint32_t age) {
this->age_ = age;
return *this;
}
private:
uint64_t id_;
std::string name_;
uing32_t age_;
}
// Stu stu;
// stu.id(1).name("bytedance").age(12);
Java 则比较喜欢下述写法,lombok 爱好者更甚。
public class Stu {
public Stu() {}
public Stu id(int id) {
this.id = id;
return this;
}
public Stu name(String name) {
this.name = name;
return this;
}
public Stu age(int age) {
this.age = age;
return this;
}
private int id;
private String name;
private int age;
}
// new Stu().id(1).name("bytedance").age(12);