C++程序员是如何评价GO的

论坛 期权论坛 期权     
CSDN   2019-6-9 21:24   1104   0



作者丨Murray
翻译丨王江平

此文主要对GO语言的简单语法做了详细描述,并与C、C++、Java作了比较,以下为译文。
我正在读由Brian Kernighan和Alan Donovan编写的《The Go Programming Language》这本书。 这是一本在语义、编排和例程选取方面都勘称完美的语言类书籍。 没有华而不实,而是精简设计,摒除长篇阔论。

作为一个C ++和Java的狂热开发者,并不是衷情于所有语言。这似乎是对C的一个改进版本,所以我宁愿使用GO而不是C,但我仍然向往C ++的强大表达力。 我甚至怀疑,由于安全功能,Go无法实现C或C ++的原始性能,尽管这可能取决于编译器优化。 但是,明智地选择性能安全是非常有效的,特别是如果想获得比Java更多的安全性和更高的性能。

我将选择使用GO而不是C ++来实现一个使用并发和网络的概念程序的简单证明。我会在以后的帖子中提及 Goroutines和 channels,一种方便的抽象,Go有HTTP请求的标准API。 并发性很强,在编写网络代码时,很容易选择安全性。

下面是一些本人关于简单功能的肤浅见解,其中大部分看起来都是对C的简单改进。在
第2部分中,我将提到更高级的功能,我希望可以做一个关于并发的第3部分。 我强烈建议您阅读本书以便正确理解这些问题。

欢迎友善的纠正和澄清。 免不了有几个错误,希望没有重大失误。

行尾没有分号

我们从简单的入手。 与C,C ++或Java不同,Go在代码行的末尾不需要分号。 所以出现下面的情形:
a = b  
c = d  
这对于将GO作为第一门编程语言来学的人来说可能更好。 对于分号问题可能需要一段时间来适应。
if 和 for 语句没有括号

这是另一个区别。 与 C 或 Java 不同,Go不将其条件放在括号内。 这是一个小小的变化,感觉很随意,可能会使C程序员感觉不舒服。

例如,在Go中,我们可以这样写:
for i := 0; i < 100; i++ {  
  ...  
}  

if a == 2 {  
  ...  
}  
用C语言是这样:

for (int i = 0; i < 100; i++) {  
  ...  
}  

if (a == 2) {  
  ...  
}  
类型推断

Go有类型推断,从文本值或函数返回值,所以你不需要声明编译器能识别的类型。 这有点像C++的auto关键字(从C ++ 11开始)。 例如:
var a = 1 // An int.  

var  b = 1.0 // A float64.

var  c = getThing()  
还有一个 := 语法, 避免了 var 的需要, 虽然我不认为在语言中都需要:
a := 1 // An int.  

b := 1.0 // A float64  

d := getThing()  
我喜欢使用C ++中的auto关键字进行类型推理,感觉使用没有这个语法的语言真的很痛苦。 相比之下, java显得有点繁琐, 但也许 java 会实现这种用法。 我不明白为什么C不能这样做。 毕竟,它们最终允许在函数开始时声明变量,所以改变是可能的。

名称后的类型

GO 有变量/参数/函数名称后的类型, 感觉相当随意,尽管我猜想是有原因的,我个人可以适应。所以,在 C中可以这样:
Foo foo = 2;   
但是GO语言却这样写:
var foo Foo = 2  
保持一个更类似于 C 的语法将会使 C 开发人员轻松地入门。这些人往往不会接受语言的细微变化。

没有隐式转换

Go不存在类型之间的隐式转换,例如int和uint,或者float和int。 == 和 != 也是如此。

因此,这不会被编译:
var a int = -2  
var b uint = a  
var c int = b  
var d float64 = 1.345  
var e int = c   
C编译器警告可以捕获其中的一些,但是 a)人们通常不会打开所有这些警告,并且它们不会将警告作为错误,b)警告不是严格的。

请注意,Go的类型是在变量 (或参数或函数) 名称之后, 而不是之前。

注意一点,与Java不同,Go仍然具有无符号整数。 与C ++的标准库不同,Go使用带符号整数的大小和长度。真心希望C ++也能做到这一点。

没有type声明的隐式转换

Go甚至不允许类型之间进行隐式转换,在C中,只能是typedef。 所以,这不会编译:
type Meters int  
type Feet int  
var a Meters = 100  
var b Feet = a  
在使用 typedef 时, 我想在 c 和 c++ 编译器中看到这是一个警告。

但是,允许隐式地将文本 (非类型化) 值赋给类型变量, 但不能从基础类型的实际类型变量中分配:
type Meters int  
var a Meters = 100 // No problem.  
var i int = 100  
var b Meters = i // Will not compile.  
没有枚举(enum)

GO语言没有枚举,应该使用带 iota关键字的常量代替,虽然C ++代码可能有这样的:
enum class Continent {  
  NORTH_AMERICA,  
  SOUTH_AMERICA,  
  EUROPE,  
  AFRICA,  
  ...  
};  
Continent c = Continent::EUROPE;  
Continent d = 2; // Will not compile
在GO语言中,应该这样:
  type continent int  

    const (  
     CONTINENT_NORTH_AMERICA continent = iota  
    CONTINENT_SOUTH_AMERICA // Also a continent, with the next value via iota.  
    CONTINENT_EUROPE // Also a continent, with the next value via iota.  
    CONTINENT_AFRICA // Also a continent, with the next value via iota.  
    )  

    var c continent = CONTINENT_EUROPE  
    var d continent = 2 // But this works too.  
请注意, 与 c++ 枚举 (尤其是 C++11) 相比, 每个值的名称必须有一个显式前缀,并且编译器不会阻止您将枚举值分配给枚举类型的变量。如果在switch/case模块中漏写,编译器不会警告你,因为GO编译器不会将这些值视为一组关联的数值。

Switch/Case: 默认没有fallthrough

译者注:go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch,但是可以使用fallthrough强制执行后面的case代码,fallthrough不会判断下一条case的expr结果是否为true。

在C和C ++中,您几乎需要每个case之后有break语句。 否则,下面case块中的代码也将运行。 这可能是有用的,特别是当您希望相同的代码响应多个值运行时,但这不是常见的情况。 在Go中,您必须添加一个明确的fallthrough关键字来获取此行为,因此代码在一般情况下更为简洁。

Switch/Case: 不仅仅是基本类型

与C和C ++不同,在Go中,您可以切换任何可比较的值,而不仅仅是编译时已知的值,如int,enums或其他constexpr值。 所以你可以打开字符串,例如:
switch str {  
case "foo":   
doFoo()  
case "bar":  
  doBar()  
}   
这很方便,我想它仍然被编译为高效的机器代码,当它使用编译时值。 C ++似乎已经抵制了这种方便,因为它不能总是像标准的 switch/case一样有效,但是我认为当人们希望更加意识到映射,不必要地将switch/case语法与C的原始含义联系起来。
指针,但没有间接引用运算符(->),没有指针运算

Go具有普通类型和指针类型,并使用C和C ++中的*和&。 例如:
var a thing = getThing();  
var p *thing = &a;  
var b thing = *p; // Copy a by value, via the p pointer
与C ++一样,new关键字返回一个指向新实例的指针:
var a *thing = new(thing)  
var a thing = new(thing) // Compilation error  
这类似于C ++,但不同于Java,其中任何非基本类型(例如,不是int或booleans)都可以通过引用(它只是看起来像一个值)被有效地使用,刚开始可能会使使用者混淆,通过允许这种疏忽的共享机制。

与C ++不同,您可以使用相同的点运算符调用值或指针上的方法:
var a *Thing = new(Thing) // You wouldn't normally specify the type.  
    var b Thing = *a  
    a.foo();  
    b.foo();   
我喜欢这个。毕竟,编译器知道这个类型是一个指针还是一个值,所以为什么抱怨一下 a.。哪里应该有 a-> 反之亦然?然而,随着类型推断,这可能会轻而易举地掩盖您的代码是否处理指针(可能与其他代码共享值)或值。我想在C ++中看到这一点,尽管智能指针会很尴尬。

Go中不能做指针运算。例如,如果您有一个数组,则不能通过自加到指针值并取消引用该数组。你必须通过索引来访问数组元素,我认为这涉及边界检查。这避免了C和C ++代码中可能发生的一些错误,当您的代码访问应用程序内存的意外部分时,会导致安全漏洞。

Go函数可以通过值或指针获取参数。这就像C ++,但不同于Java,它总是使用非基本类型(非const)引用,尽管它可以看起来像初学者程序员一样被值复制。我宁愿使用代码显示通过函数签名发生的事情的两个选项,如C ++或Go。

像Java一样,Go没有const指针或const引用的概念。因此,如果您的函数将参数作为指针,为了提高效率,您的编译器无法阻止您更改其指向的值。在Java中,这通常是通过创建不可变类型来完成的,并且许多Java类型(例如String)是不可变的,所以即使你愿意也不能改变它们。但是我更喜欢语言支持,如C ++中的常量,指针/引用参数以及在运行时初始化的值。

References(引用), sometimes

Go似乎有引用(类似于值的指针),但仅适用于内置的slice,map和channel类型。 所以,例如这个函数可以改变其输入的滑动参数,即使参数没有被声明为一个指针,调用者也可以看到这个改变:
func doThing(someSlice []int) {  
      someSlice[2] = 3;  
    }  

在C ++中,这将是一个明显的引用:

void doThing(Thing& someSlice) {  
     someSlice[2] = 3;  
    }
我不知道这是否是语言的基本特征,或者只是关于这些类型的实现方式。 对于某些类型的行为来说, 这似乎有些混乱,我发现这个解释有点凌乱。 方便固然很好,但一致性更加重要。

常量(const)

Go的const关键字不像C(很少有用)或C ++中的const,它表示在初始化后不应该更改变量的值。 它更像C ++的constexpr关键字(自C ++ 11),它在编译时定义了值。 所以这有点像在C中通过#define定义的宏,而且是类型安全。 例如:
const pi = 3.14
请注意,我们不为const值指定类型,因此该值可以根据值的语法使用各种类型,有点像C宏#define。 但是我们可以通过指定一个类型来限制它:
const pi float64 = 3.14
与C ++中的constexpr不同,没有可以在编译时评估的constexpr函数或类型的概念,所以你不能这样做:
const pi = calculate_pi()
你不能这样做
type Point struct {  
   X int  
   Y int  
}  

const point = Point {1,2}
虽然你可以使用一个简单的类型,它的底层类型可以是const:
type Yards int  

const length Yards = 100  
只有for循环

Go语言中的只有for循环 - 没有while或do-while循环。 与 C,C ++或Java语言相比,GO语言在这方面做了简化,尽管现在有多种形式的for循环。

例如:
for i:= 0; i < 100; i ++ {  
  ...  
}  
或者像C中的while循环一样:
for keepGoing {  
  ...  
}  
而for循环对于诸如字符串,切片或map之类的容器有一个基于范围的语法,我稍后会提到:
for i, c := range things {   
  ...   
}   
C ++有一个基于范围的for循环,C ++ 11以后版本,但我喜欢Go可以(可选)给你索引和值。 (它为您提供索引,或索引和值,让您忽略带 _ variable 名称的索引。)

本机(Unicode)字符串类型

Go具有内置的字符串类型,并且内置比较运算符,如==,!=和
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:65
帖子:240
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP