思考: 如何学习一门语言?Java、C++、Go的横向对比

Posted by attack204 on 2022-07-08

语言的需求

注意一下仅讨论Java、C++和Go

语言的出现是为了解决什么问题?个人见解,是为了解决程序的问题。

程序的三大功能是通信、存储、计算,而语言亦是如此。

先放开通信不谈,我们只考虑存储与计算

在语言中,我所认为的存储应当是属性, 而计算对应的是函数/方法

而相对应的,产生了两套编程模式,第一套叫做oo,第二套叫做op

在op模式中,承载数据的结构叫做结构体(structure),而处理数据的叫做函数(function) (当然像pascal语言中也有过程一说)

在oo模式中,语言想办法把op中的结构体和函数放到一起处理,定义为一个对象,对象拥有属性(attributes)和方法(methods)

但实际上,不论哪种方法,都是为了解决数据的“存储”与“计算”问题

并发编程(TODO)

想写一写我所理解的并发编程背后的设计哲学

首先C++和Java都是用锁、原子变量和信号量的方式来进行通信的,即使用共享内存来进行通信。而Go是使用通信来共享内存,所以这里分两块来说。

对于Java和C++的并发编程问题,其实就是提供一系列的API以控制好临界区与线程之间的竞争关系

对于临界区是一个变量的情况,可以提供原子变量的设计供开发者使用。

而对于临界区是一个代码片段的情况,则提供互斥锁mutex来避免竞态条件。

实际上有了mutex,单纯的竞态条件就已经不存在了

但是这里还有许多问题

  1. 对于mutex,可能会出现n个线程阻塞在同一个临界区的情况,因此提供condition条件变量来让开发者可以自行决策当unlock时究竟唤醒哪一个线程
  2. mutex需要手动lock和unlock,所以提供了lock_gurad来自动lock和unlock。(所以lock_gaurd是不是可以类比为智能锁233)
  3. condition的notify和wait接口通信,本质上是在传输一个布尔变量。但如果我们想传递一个对象或者一个字符串呢?虽然可以通过开全局变量的方式来解决,但是C++引入了promise和future来更直接的完成这种操作
  4. 常见的thread.join并不能获取线程的返回结果,因此提供了async API来使得开发者能够获取线程返回结果

OO与OP

OO/OP的实现方式

  1. Java: interface + abstract class + class

  2. Go: interface + struct。Go语言不允许在struct中写方法,强行把属性和方法分开了。但是C++可以在.h文件中定义方法且做一部分实现。我猜Go不允许写方法的原因是,当C++中出现方法的删除时,需要删除.h中的定义以及.cpp中的实现,做了重复的工作。而Java则规定方法的实现不能在class外部。所以Go和Java算是两个极端了吧hh,C++夹在它们中间。

  3. C++: interface(使用纯虚函数实现,纯虚函数不能有实现) + abstract class(使用虚函数,虚函数可以有实现)+ class(可以重写其父类的虚函数和非虚函数)。具体在实现时,一般用.h文件来定义class的声明信息,在.cpp中写具体的实现

我的理解是,不论interface还是abstract class,其本质还是对class进行分类

有的class需要大量的被继承,且每个方法对于不同的类实现都不同,因此可以被抽象为interface

有的class需要被继承,但是含有一些确定的公共方法,因此可以被抽象为abstract class

语法格式

异常的实现方式(TODO)

Java、C++: try...catch

Go: nil

func设计

为什么go的return类型要放在后面?

我猜是因为go可以返回多个变量,如果放在前面且类型为void则不会写任何东西,设计的不够优美

对于class/struct的考量(TODO)

对于基础数据类型的考量(TODO)

命名规范

对于Java语言,基本上遵循着驼峰式命名法(对于类名首字母大写,变量名首字母小写)

对于C++语言,基本上采用下划线分割的方法(当然openGauss里驼峰式和下划线式混用就多少有点奇葩)。像cmu15-445的bustub,对于成员变量都在最后加了下划线,非常友好

而对于Go语言,变量的命名方法与是否是private变量有关,因此一般采用驼峰式命名法,而是否大小写根据成员的具体情况来定.

另外在这里记录一下我所想到的关于变量命名的一些tips

结合上面所说,一个函数中的中间变量本质上还是对那三种数据的Read、Update。

总的来说,都是临时变量,所以在声明时可以全部以tmp开头,同时在结尾加入类型标识符,例如ptr, num来表示其所表达的数据类型

打包方式

  1. C++提供了CMake来生成MakeFile,并使用make来进行编译生成可执行文件

C++并没有大一统的模块管理工具,习惯使用CMake + GitModules的方式进行管理

例如 https://github.com/ClickHouse/ClickHouse/blob/master/.gitmodules

  1. Java提供了maven package命令来生成jar包,最终将jar包放到不同的平台上,使用java运行jar包即可。(实际上,Java所谓的“一次编译, 到处运行”只是将对操作系统的判断逻辑托管给了不同平台下的JRE而已。)

  2. Go提供了go build命令来进行打包