Java 转 C++ 那些事

前提纪要

虽说编程语言只是承载思想的一种媒介,但是每种编程语言有自己的设计哲学,所以在实现自己思想的时候,也需要遵循该门语言的理念才行。截止 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);
  }
}

不兴 get/set 函数

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);

你可能感兴趣的:(C++,java,c++,开发语言)