为什么C++程序员不想改用GO语言?看十年C++架构师怎样说

作者 : 开心源码 本文共7132个字,预计阅读时间需要18分钟 发布时间: 2022-05-12 共130人阅读

我正在读由Brian Kernighan和Alan Donovan编写的《The Go Programming Language》这本书。 这是一本在语义、编排和例程选取方面都勘称完美的语言类书籍。 没有华而不实,而是精简设计,摒除长篇阔论。

这是小编准备的C ++学习资料,加群825414254,就可获取整套!

QQ截图20190309205647.jpg

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

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

下面是少量本人关于简单功能的肤浅见地,其中大部分看起来都是对C的简单改进。在

第2部分中,我将提到更高级的功能,我希望可以做一个关于并发的第3部分。 我强烈建议您阅读本书以便正确了解这些问题。

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

行尾没有分号

我们从简单的入手。 与C,C ++或者Java不同,Go在代码行的末尾不需要分号。 所以出现下面的情形:

QQ截图20190310194142.jpg

这对于将GO作为第一门编程语言来学的人来说可能更好。 对于分号问题可能需要一段时间来适应。

if 和 for 语句没有括号

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

例如,在Go中,我们可以这样写:QQ截图20190310194315.jpg

用C语言是这样:

QQ截图20190310194422.jpg
类型推断

Go有类型推断,从文本值或者函数返回值,所以你不需要公告编译器能识别的类型。 这有点像C++的auto关键字(从C ++ 11开始)。 例如:

QQ截图20190310194519.jpg

还有一个 := 语法, 避免了 var 的需要, 尽管我不认为在语言中都需要:

QQ截图20190310194600.jpg

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

名称后的类型

GO 有变量/参数/函数名称后的类型, 感觉相当随便,虽然我猜想是有起因的,我个人可以适应。所以,在 C中可以这样:

QQ截图20190310194646.jpg

但是GO语言却这样写:

QQ截图20190310194720.jpg
保持一个更相似于 C 的语法将会使 C 开发人员轻松地入门。这些人往往不会接受语言的细微变化。

没有隐式转换

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

因而,这不会被编译:

QQ截图20190310194755.jpg

C编译器警告可以捕获其中的少量,但是 a)人们通常不会打开所有这些警告,并且它们不会将警告作为错误,b)警告不是严格的。

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

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

没有type公告的隐式转换

Go甚至不允许类型之间进行隐式转换,在C中,只能是typedef。 所以,这不会编译:

QQ截图20190310194833.jpg

在使用 typedef 时, 我想在 c 和 c++ 编译器中看到这是一个警告。

但是,允许隐式地将文本 (非类型化) 值赋给类型变量, 但不能从基础类型的实际类型变量中分配:

QQ截图20190310194915.jpg

没有枚举(enum)

GO语言没有枚举,应该使用带 iota关键字的常量代替,尽管C ++代码可能有这样的:

QQ截图20190310195005.jpg

在GO语言中,应该这样:

QQ截图20190310195109.jpg

请注意, 与 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值。 所以你可以打开字符串,例如:

QQ截图20190310195142.jpg

这很方便,我想它依然被编译为高效的机器代码,当它使用编译时值。 C ++似乎已经抵制了这种方便,由于它不能总是像标准的 switch/case一样有效,但是我认为当人们希望更加意识到映射,不必要地将switch/case语法与C的原始含义联络起来。

指针,但没有间接引用运算符(->),没有指针运算

Go具备普通类型和指针类型,并使用C和C ++中的*和&。 例如:

QQ截图20190310195227.jpg

与C ++一样,new关键字返回一个指向新实例的指针:

QQ截图20190310195254.jpg
这相似于C ++,但不同于Java,其中任何非基本类型(例如,不是int或者booleans)都可以通过引用(它只是看起来像一个值)被有效地使用,刚开始可能会使使用者混淆,通过允许这种疏忽的共享机制。

与C ++不同,您可以使用相同的点运算符调用值或者指针上的方法:

QQ截图20190310195334.jpg

我喜欢这个。毕竟,编译器知道这个类型是一个指针还是一个值,所以为什么抱怨一下 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类型。 所以,例如这个函数可以改变其输入的滑动参数,即便参数没有被公告为一个指针,调用者也可以看到这个改变:

QQ截图20190310195418.jpg

我不知道这能否是语言的基本特征,或者者只是关于这些类型的实现方式。 对于某些类型的行为来说, 这似乎有些混乱,我发现这个解释有点凌乱。 方便诚然很好,但一致性更加重要。

常量(const)

Go的const关键字不像C(很少有用)或者C ++中的const,它表示在初始化后不应该更改变量的值。 它更像C ++的constexpr关键字(自C ++ 11),它在编译时定义了值。 所以这有点像在C中通过#define定义的宏,而且是类型安全。 例如:

QQ截图20190310195502.jpg

请注意,我们不为const值指定类型,因而该值可以根据值的语法使用各种类型,有点像C宏#define。 但是我们可以通过指定一个类型来限制它:

QQ截图20190310195537.jpg

与C ++中的constexpr不同,没有可以在编译时评估的constexpr函数或者类型的概念,所以你不能这样做:

QQ截图20190310195615.jpg
你不能这样做
QQ截图20190310195658.jpg

尽管你可以使用一个简单的类型,它的底层类型可以是const:

QQ截图20190310195732.jpg

只有for循环

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

例如:

QQ截图20190310195815.jpg

或者者像C中的while循环一样:

QQ截图20190310195850.jpg

而for循环对于诸如字符串,切片或者map之类的容器有一个基于范围的语法,我稍后会提到:

QQ截图20190310195932.jpg

C ++有一个基于范围的for循环,C ++ 11以后版本,但我喜欢Go可以(可选)给你索引和值。 (它为您提供索引,或者索引和值,让您忽略带 _ variable 名称的索引。)

本机(Unicode)字符串类型

Go具备内置的字符串类型,并且内置比较运算符,如==,!=和<(和Java一样)。 像Java一样,字符串是不可变的,所以一旦创立便不能更改,不过可以通过将其余字符串与内置运算符 + 中的其余串连接起来来创立新字符串。 例如:

QQ截图20190310200014.jpg

GO语言源代码总是UTF-8编码,字符串文本可能包含非ASCII utf-8代码点。 GO调用Unicode代码点“runes(符文)”。

尽管内置的len()函数返回字节数,而字符串的内置运算符[]运行在字节上,但是有一个utf8包用于解决字符串作为符号(Unicode代码点)。 例如:

QQ截图20190310200056.jpg

而基于范围的for循环在runes中解决,而不是字节:

QQ截图20190310200132.jpg
c++ 仍没有标准等效项。
QQ截图20190310200203.jpg

GO语言的Slices(切片)与 c 中动态分配的数组相似, 虽然它们实际上是底层数组的视图, 而两个切片可以是同一底层数组的不同部分的视图。他他们感觉有点像C ++ 17或者GSL :: span中的std :: string_view,但它们可以轻松调整大小,如C ++ 17中的std :: vector或者Java中的ArrayList。

我们可以公告一个像这样的范围, 并追加到它:

QQ截图20190310200239.jpg

数组(大小固定,不像切片)具备非常类似的语法:

QQ截图20190310200310.jpg
通过指针将数组传递给函数使用时必需注意,否则就会导致按值赋值。

与C ++中的std :: array或者std :: vector不同,切片不是(深度)可比较或者可复制的,这感觉相当不方便。

假如内置的append()函数需要比现有容量多(可能超过当前长度),则可以分配更大的底层数组。 所以你应该始终如此分配append()的结果:

QQ截图20190310200339.jpg

我认为你不能将指针指向切片中的元素。 假如可以的话,垃圾收集系统需要保留以前的底层数组,直到你中止使用该指针。

与C或者C ++数组不同,不同于使用std :: vector的operator [],尝试访问切片的无效索引将导致紧急(有效地崩溃),而不仅仅是未定义的行为。 我更喜欢这个,尽管我想像是边界检查有少量小的性能成本。

Maps(映射)

Go有一个内置的 map 类型。这大致相当于C ++的std :: map(平衡二叉树)或者std :: unordered_map(哈希表)。GO的maps显然是哈希表,但是我不知道它们是单独链接的哈希表(如std::unordered_map)还是开放寻址哈希表(不幸的是,标准C ++中没有什么)。

显然,哈希表中的 keys 必需是hashable 和 comparable。这本书提到了可比性,但是很少有事情是可比的,他们都很容易hashable。只有基本类型(int,float64,string等,但不是slice)或者数据结构是可比较的,所以可以用它们作为一个关键。可以通过使用(或者制作)您的值的哈希值的基本类型(如int或者字符串)来处理此问题。我喜欢C ++需要一个std :: hash <>专业化,虽然我希望写一个更容易。

与C ++不同,您不能保留指向地图中的元素的指针,因而更改值的一部分意味着将整个值复制回地图,大概用另一个查找。显然,当地图必需增长时,完全避免无效指针的问题。 C ++可以让您承担风险,指定何时可能无效。

Go Maps显然是一个比C更大的优势,否则您必需使用少量第三方数据结构或者编写自己的数据,通常只有很少的类型安全。

看起来像下面这样:

QQ截图20190310200408.jpg

Multiple return values(多个返回值)

Go中的函数可以有多个返回类型,更显著的是输出参数。 例如:

QQ截图20190310200433.jpg

这有点像在现代C ++中返回元组,特别是在C ++ 17中的结构化绑定:

QQ截图20190310200459.jpg
Garbage Collection(垃圾回收)

与 Java 一样, GO 具备自动内存管理, 因而可以信任在使用完这些实例之前不会释放它们, 也不需要显式释放它们。因而,可以放心地完成此操作,,而不必担心以后释放该实例:

QQ截图20190310200531.jpg

你甚至可以这样做,不用关心和理解实例是在堆栈还是堆上创立的:

QQ截图20190310200600.jpg

我不知道Go如何避免循环引用或者不需要的“泄漏”引用,由于Java或者C ++将使用弱引用。

我不知道如何,或者者假如,Go避免了Java因为垃圾收集而间歇性放缓的问题。 Go似乎是针对系统级代码,所以我想它肯定要做得更好。

然而,也像Java一样,并且可能像所有垃圾收集一样,这仅对于管理内存而非一般资源是有用的。 程序员通常很高兴在代码完成使用后一段时间内释放内存,而不肯定立即。 但其余资源,如文件形容符和数据库连接,需要立即释放。 少量事情,如互斥锁,通常需要在显著的范围结束时释放。 破坏者使之成为可能。 例如,在C ++中:

QQ截图20190310200628.jpg

Go不能这样做,所以它有defer(),而是让你指定一个事情发生在一个功能结束。 这是一个烦人的推迟与功能相关联,而不是一般范围。

QQ截图20190310200659.jpg

这感觉像是一个尴尬的黑客,就像Java的试用资源一样。

我更愿意看到一种语言,以简明的语法给我所有的范围资源管理(包括析构函数),引用计数(如std :: shared_ptr <>)和垃圾回收,所以我可以有可预测的,显著的, 但可靠,必要时释放资源,垃圾收集时我不在乎。

当然,我不是伪装内存管理在C ++中很容易。 当它很困难时,这可能非常困难。 所以我明白垃圾收集的选择。 我只是期望系统级语言提供更多。

不喜欢GO语言的地方

除了上面提到的较小的语法烦恼以及缺乏简单的通用资源(而不仅仅是内存)管理之外,还有其它少量缺陷。

No generics(没有泛型)

Go专注于类型安全,特别是对于数字类型,使得缺乏泛型令人惊讶。我可以记得在泛型之前使用Java的感觉是多么令人沮丧,而这感觉差不多是尴尬的。没有泛型,我很快发现自己不得不选择缺乏类型安全或者反复重新实现每种类型的代码,感觉就像这个语言作斗争。

我知道泛型是难以实现的,必需做出选择,觉得GO语言能到达什么程度(可能超过Java,但不如C ++),我知道Go将远远超过一个更好的C.但我认为泛型是不可避免的一次,像Go,你追求静态型安全。

不知何故,切片和maps容器是通用的,可能是由于它们是内置类型。

Lack of standard containers(缺少标准容器)

Go在其标准库中没有队列或者堆栈。在C ++中,我定期使用std :: queue和std :: stack。我认为这些将需要仿制药。人们可以使用go的切片(动态分配的数组)实现相同的功能,并且可以将其包装在自己的类型中,但是类型只能包含特定类型,因而将为每种类型重新实现。或者者您的容器可以容纳{}类型的接口(显然有点像Java对象或者C ++ void *),放弃(静态)类型安全。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 为什么C++程序员不想改用GO语言?看十年C++架构师怎样说

发表回复