Golang 中更好的错误处理:理论和实践技巧

Python06

Golang 中更好的错误处理:理论和实践技巧,第1张

云和安全管理服务专家新钛云服 张春翻译

这种方法有几个缺点。首先,它可以对程序员隐藏错误处理路径,特别是在捕获异常不是强制性的情况下,例如在 Python 中。即使在具有必须处理的 Java 风格的检查异常的语言中,如果在与原始调用不同的级别上处理错误,也并不总是很明显错误是从哪里引发的。

我们都见过长长的代码块包装在一个 try-catch 块中。在这种情况下,catch 块实际上充当 goto 语句,这通常被认为是有害的(奇怪的是,C 中的关键字被认为可以接受的少数用例之一是错误后清理,因为该语言没有 Golang- 样式延迟语句)。

如果你确实从源头捕获异常,你会得到一个不太优雅的 Go 错误模式版本。这可能会解决混淆代码的问题,但会遇到另一个问题:性能。在诸如 Java 之类的语言中,抛出异常可能比函数的常规返回慢数百倍。

Java 中最大的性能成本是由打印异常的堆栈跟踪造成的,这是昂贵的,因为运行的程序必须检查编译它的源代码 。仅仅进入一个 try 块也不是空闲的,因为需要保存 CPU 内存寄存器的先前状态,因为它们可能需要在抛出异常的情况下恢复。

如果您将异常视为通常不会发生的异常情况,那么异常的缺点并不重要。这可能是传统的单体应用程序的情况,其中大部分代码库不必进行网络调用——一个操作格式良好的数据的函数不太可能遇到错误(除了错误的情况)。一旦您在代码中添加 I/O,无错误代码的梦想就会破灭:您可以忽略错误,但不能假装它们不存在!

try {

doSometing()

} catch (IOException e) {

// ignore it

}

与大多数其他编程语言不同,Golang 接受错误是不可避免的。 如果在单体架构时代还不是这样,那么在今天的模块化后端服务中,服务通常和外部 API 调用、数据库读取和写入以及与其他服务通信

以上所有方法都可能失败,解析或验证从它们接收到的数据(通常在无模式 JSON 中)也可能失败。Golang 使可以从这些调用返回的错误显式化,与普通返回值的等级相同。从函数调用返回多个值的能力支持这一点,这在大多数语言中通常是不可能的。Golang 的错误处理系统不仅仅是一种语言怪癖,它是一种将错误视为替代返回值的完全不同的方式!

重复 if err != nil

对 Go 错误处理的一个常见批评是被迫重复以下代码块:

res, err := doSomething()

if err != nil {

// Handle error

}

对于新用户来说,这可能会觉得没用而且浪费行数:在其他语言中需要 3 行的函数很可能会增长到 12 行

这么多行代码!这么低效!如果您认为上述内容不优雅或浪费代码,您可能忽略了我们检查代码中的错误的全部原因:我们需要能够以不同的方式处理它们!对 API 或数据库的调用可能会被重试。

有时事件的顺序很重要:调用外部 API 之前发生的错误可能不是什么大问题(因为数据从未通过发送),而 API 调用和写入本地数据库之间的错误可能需要立即注意,因为 这可能意味着系统最终处于不一致的状态。即使我们只想将错误传播给调用者,我们也可能希望用失败的解释来包装它们,或者为每个错误返回一个自定义错误类型

并非所有错误都是相同的,并且向调用者返回适当的错误是 API 设计的重要部分,无论是对于内部包还是 REST API

不必担心在你的代码中重复 if err != nil ——这就是 Go 中的代码应该看起来的样子。

自定义错误类型和错误包装

从导出的方法返回错误时,请考虑指定自定义错误类型,而不是单独使用错误字符串。字符串在意外代码中是可以的,但在导出的函数中,它们成为函数公共 API 的一部分。更改错误字符串将是一项重大更改——如果没有明确的错误类型,需要检查返回错误类型的单元测试将不得不依赖原始字符串值!事实上,基于字符串的错误也使得在私有方法中测试不同的错误案例变得困难,因此您也应该考虑在包中使用它们。回到错误与异常的争论,返回错误也使代码比抛出异常更容易测试,因为错误只是要检查的返回值。不需要测试框架或在测试中捕获异常

可以在 database/sql 包中找到简单自定义错误类型的一个很好的示例。它定义了一个导出常量列表,表示包可以返回的错误类型,最著名的是 sql.ErrNoRows。虽然从 API 设计的角度来看,这种特定的错误类型有点问题(您可能会争辩说 API 应该返回一个空结构而不是错误),但任何需要检查空行的应用程序都可以导入该常量并在代码中使用它不必担心错误消息本身会改变和破坏代码。

对于更复杂的错误处理,您可以通过实现返回错误字符串的 Error() 方法来定义自定义错误类型。自定义错误可以包括元数据,例如错误代码或原始请求参数。如果您想表示错误类别,它们很有用。DigitalOcean 的本教程展示了如何使用自定义错误类型来表示可以重试的一类临时错误。

通常,错误会通过将低级错误与更高级别的解释包装起来,从而在程序的调用堆栈中传播。例如,数据库错误可能会以下列格式记录在 API 调用处理程序中:调用 CreateUser 端点时出错:查询数据库时出错:pq:检测到死锁。这很有用,因为它可以帮助我们跟踪错误在系统中传播的过程,向我们展示根本原因(数据库事务引擎中的死锁)以及它对更广泛系统的影响(调用者无法创建新用户)。

自 Go 1.13 以来,此模式具有特殊的语言支持,并带有错误包装。通过在创建字符串错误时使用 %w 动词,可以使用 Unwrap() 方法访问底层错误。除了比较错误相等性的函数 errors.Is() 和 errors.As() 外,程序还可以获取包装错误的原始类型或标识。这在某些情况下可能很有用,尽管我认为在确定如何处理所述错误时最好使用顶级错误的类型。

Panics

不要 panic()!长时间运行的应用程序应该优雅地处理错误而不是panic。即使在无法恢复的情况下(例如在启动时验证配置),最好记录一个错误并优雅地退出。panic比错误消息更难诊断,并且可能会跳过被推迟的重要关闭代码。

Logging

我还想简要介绍一下日志记录,因为它是处理错误的关键部分。通常你能做的最好的事情就是记录收到的错误并继续下一个请求。

除非您正在构建简单的命令行工具或个人项目,否则您的应用程序应该使用结构化的日志库,该库可以为日志添加时间戳,并提供对日志级别的控制。最后一部分特别重要,因为它将允许您突出显示应用程序记录的所有错误和警告。通过帮助将它们与信息级日志分开,这将为您节省无数时间。

微服务架构还应该在日志行中包含服务的名称以及机器实例的名称。默认情况下记录这些时,程序代码不必担心包含它们。您也可以在日志的结构化部分中记录其他字段,例如收到的错误(如果您不想将其嵌入日志消息本身)或有问题的请求或响应。只需确保您的日志没有泄露任何敏感数据,例如密码、API 密钥或用户的个人数据!

对于日志库,我过去使用过 logrus 和 zerolog,但您也可以选择其他结构化日志库。如果您想了解更多信息,互联网上有许多关于如何使用这些的指南。如果您将应用程序部署到云中,您可能需要日志库上的适配器来根据您的云平台的日志 API 格式化日志 - 没有它,云平台可能无法检测到日志级别等某些功能。

如果您在应用程序中使用调试级别日志(默认情况下通常不记录),请确保您的应用程序可以轻松更改日志级别,而无需更改代码。更改日志级别还可以暂时使信息级别甚至警告级别的日志静音,以防它们突然变得过于嘈杂并开始淹没错误。您可以使用在启动时检查以设置日志级别的环境变量来实现这一点。

原文:https://levelup.gitconnected.com/better-error-handling-in-golang-theory-and-practical-tips-758b90d3f6b4

1、 string的定义

Golang中的string的定义在reflect包下的value.go中,定义如下:

StringHeader 是字符串的运行时表示,其中包含了两个字段,分别是指向数据数组的指针和数组的长度。

// StringHeader is the runtime representation of a string.

// It cannot be used safely or portably and its representation may

// change in a later release.

// Moreover, the Data field is not sufficient to guarantee the data

// it references will not be garbage collected, so programs must keep

// a separate, correctly typed pointer to the underlying data.

type StringHeader struct {

Data uintptr

Len int

}

1

2

3

4

5

6

7

8

9

10

1

2

3

4

5

6

7

8

9

10

2、string不可变

Golang中的字符串是不可变的,不能通过索引下标的方式修改字符串中的数据:

在这里插入图片描述

运行代码,可以看到编译器报错,string是不可变的

在这里插入图片描述

但是能不能进行一些骚操作来改变元素的值呢?

package main

import (

"fmt"

"reflect"

"unsafe"

)

func main() {

a := "hello,world"

b := a[6:]

bptr := (*reflect.StringHeader) (unsafe.Pointer(&b))

fmt.Println(a)

fmt.Println(b)

*(*byte)(unsafe.Pointer(bptr.Data)) = '.'

fmt.Println(a)

fmt.Println(b)

}

// 运行结果

hello,world

world

unexpected fault address 0x49d7e3

fatal error: fault

[signal 0xc0000005 code=0x1 addr=0x49d7e3 pc=0x4779fa]

goroutine 1 [running]:

runtime.throw(0x49c948, 0x5)

C:/Program Files/Go/src/runtime/panic.go:1117 +0x79 fp=0xc0000dbe90 sp=0xc0000dbe60 pc=0x405fd9

runtime.sigpanic()

C:/Program Files/Go/src/runtime/signal_windows.go:245 +0x2d6 fp=0xc0000dbee8 sp=0xc0000dbe90 pc=0x4189f6

main.main()

F:/go_workspace/src/code/string_test/main.go:20 +0x13a fp=0xc0000dbf88 sp=0xc0000dbee8 pc=0x4779fa

runtime.main()

C:/Program Files/Go/src/runtime/proc.go:225 +0x256 fp=0xc0000dbfe0 sp=0xc0000dbf88 pc=0x4087f6

runtime.goexit()

C:/Program Files/Go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc0000dbfe8 sp=0xc0000dbfe0 pc=0x435da1

Process finished with the exit code 2

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

在上面的代码中,因为在go语言中不能进行指针的加减运算,因此取切片,让b的Data指针指向’,'所在的位置。然后把"hello,world"中的逗号改为点,但是发现还是不行,程序直接崩溃了。看来go语言中的指针得到了大大的限制,设计者并不想让程序员过度使用指针来写出一些不安全的代码。

3、使用string给另一个string赋值

Golang中的字符串的赋值并不是拷贝底层的字符串数组,而是数组指针和长度字段的拷贝。例如:当我们定义了一个字符串 a := “hello,world” 然后定义了 b := a 底层所做的操作只是创建了两个StringHeader的结构体,它们的Data字段都指向同一段数据,如下图:

在这里插入图片描述

我们可以利用代码来证实这一点:

package main

import (

"fmt"

"reflect"

"unsafe"

)

func main() {

a := "hello,world"

b := a

fmt.Println(a)

fmt.Println(b)

aptr := (*reflect.StringHeader) (unsafe.Pointer(&a))

bptr := (*reflect.StringHeader) (unsafe.Pointer(&b))

fmt.Println("a ptr:", unsafe.Pointer(aptr.Data))

fmt.Println("b ptr:", unsafe.Pointer(bptr.Data))

}

// 运行结果

hello, world

hello, world

a ptr: 0x6bdb76

b ptr: 0x6bdb76

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

在上面的代码中,将a和b转换为StringHeader类型的指针,然后分别打印出,a和b的Data指针的值,发现是相同的

那么如果对a做切片赋值给b呢?

func main() {

a := "hello,world"

b := a[6:]

fmt.Println(a)

fmt.Println(b)

aptr := (*reflect.StringHeader) (unsafe.Pointer(&a))

bptr := (*reflect.StringHeader) (unsafe.Pointer(&b))

fmt.Println("a ptr:", unsafe.Pointer(aptr.Data))

fmt.Println("b ptr:", unsafe.Pointer(bptr.Data))

}

// 运行结果

hello,world

world

a ptr: 0xd4d849

b ptr: 0xd4d84f

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

0xd4d849 - 0xd4d84f = 0x000006

显然,也没有分配新的数组并拷贝数据,而是将原字符数组的指针的偏移赋给了b的StringHeader的Data

4、string重新赋值

如果对一个已经赋值的字符串重新赋值,也不会修改原内存空间,而是申请了新的内存空间,对其赋值,并指向新的内存空间。如下图:

在这里插入图片描述

也可以使用代码来证实一下:

package main

import (

"fmt"

"reflect"

"unsafe"

)

func main() {

a := "hello,world"

aptr := (*reflect.StringHeader) (unsafe.Pointer(&a))

fmt.Println("a ptr:", unsafe.Pointer(aptr.Data))

fmt.Println("a len", aptr.Len)

a = "hello,golang"

newAPtr := (*reflect.StringHeader) (unsafe.Pointer(&a))

fmt.Println("b ptr:", unsafe.Pointer(newAPtr.Data))

fmt.Println("b len:", newAPtr.Len)

}

// 运行结果

a ptr: 0x3ed7f4

a len 11

b ptr: 0x3edb2c

b len: 12

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

文章知识点与官方知识档案匹配

Go技能树九阴真经字符串

2043 人正在系统学习中

点击阅读全文

打开CSDN APP,看更多技术内容

Golang底层原理剖析之string类型与字符编码_cheems~的博客_g...

string类型 string结构 go语言中默认使用的是UTF-8编码 string由两部分组成,一部分是指向字符串起始地址的指针,另一部分是字节个数len注意不是字符个数,是字节个数!这个数据类型占用16B空间,指向字符串起始地址的指针和存在字节个数的整...

继续访问

...底层系列二(基础)_GoGo在努力的博客_golang string底层

1、 string的定义 Golang中的string的定义在reflect包下的value.go中,定义如下: StringHeader 是字符串的运行时表示,其中包含了两个字段,分别是指向数据数组的指针和数组的长度。 // StringHeader is the runtime representation of a...

继续访问

最新发布 Golang: []string 和 ...string

… 参数语法形成了可变参数的参数。它将接受零个或多个string参数,并将它们作为切片引用

继续访问

常用的Golang字符串(string)操作

文章目录Golang中常用的字符串操作一、标准库相关的Package二、常用字符串操作判断是否为空字符串 正文 Golang中常用的字符串操作 一、标准库相关的Package 二、常用字符串操作 判断是否为空字符串 思路:直接判断是否等于""空字符串,由于golang中字符串不能为nil,且为值类型,所以直接与空字符串比较即可 举例: str := "" if str == ""{...

继续访问

golang中的string_xiaodongdonga的博客_golang ...string

golang中的string 在golang中,string其实是个结构体,如图 ,在这个结构体中有两个值,一个是指向字符数组的指针str,另一个是这个字符串的长度len。 另外在golang中用的是UFT8边长编码,里面字母是用一个字节,西方的文字使用两个字节,...

继续访问

Golang中对字符串string的相关操作_Codex_97的博客_go 字符...

golang中拥有string的内置类型,可以使用与普通slice类型相似的性质。 1.简介 strings包提供了很多操作字符串的简单函数,通常一般的字符串操作都可以在这个包中找到。 strconv 包提供了基本数据类型和字符串之间的转换。在Go 中,没有隐式...

继续访问

go-string-set:一种在GoLang中创建字符串集的方法

概述 通过字符串设置方法。 用法 go get github.com/wojnosystems/go-string-set package main import ( "github.com/wojnosystems/go-string-set/string_set" "strings" ) func main () { myPeople := string_set . New () myPeople . Add ( "Bob" ) myPeople . Add ( "Jane" ) myPeople . Add ( "Gary" ) myPeople . Add ( "Bob" ) if myPeople . Exists ( "Bob" ) { // Bob exists in myPeople, so this code block execute

Golang:strings包和字符串操作

strings包 string类型值是不可变的,如果想要获得一个不一样的字符串,就只能基于原字符串进行剪裁,拼接等操作,然后将得到的新字符串存放到一块联系内存中。 string值包含了指向底层字节数组头部的指针值,以及该字节数组的长度。在string值上做切片,就相当于在其底层字节数组做切片。 而字符串拼接时(使用+),会把所有被拼接的字符串依次拷贝到一个崭新且足够大的连续内存空间中,并把持有新...

继续访问

golang中的strings.SplitN_盼盼编程的博客_strings.splitn

golang中的字符串操作strings.SplitN package main import ( "fmt" "strings" ) //golang字符串操作 func main(){ s := "hello world hello world" //str := "wo" //以str为分隔符,将s切分成多个子串,结果中**不包含*...

继续访问

Golang_String常用操作_itzhuzhu.的博客_golang strings

func main() { var x string = "ITzhuzhu" var y string = "itzhuzhu" fmt.Println(strings.EqualFold(x, y)) //true fmt.Println(x == y) //false } 1 2 3 4 5 6 返回子串在字符串第一次出现的索引,如果没有则...

继续访问

Golang类型的String()方法

作用 用于定制fmt.Println(x)、fmt.Printf("%v", x)、fmt.Print(x)时输出的内容。 示例 package main import "fmt" type Couple struct { Husband string Wife string } func (self Couple) String() string { return "(husband:" + self.Husband + ", wife:" + self.Wife +

继续访问

golang的string

golang中的string是不可变的字节序列,零值是空字符串,默认是UTF-8编码。 golang中使用字符串最需要注意的是,golang中的字符串是字节序列,string == []byte,由于一个字符占用的字节数不确定,所以无法通过下标[i]的方式稳定地获取对应位置的字符。 也就意味着字符串的第i个字节,不一定就是第i个字符,原因请参考 unicode与UTF-8 文字符号在golang中被称为rune,发音为/ru:n/,意思是符号。由于utf-8最多使用4个字节来编码,所以rune类型是int3

继续访问

golang中的strings.Trim_盼盼编程的博客_strings.trim

golang中的字符串操作strings.Trim package main import ( "fmt" "strings" ) //golang字符串操作 func main(){ s := "Hello world hello world" str := "world" //var s = []string{"11","22","33"} //删除s首尾...

继续访问

Golang 字符串

Go语言的代码是由Unicode字符组成的,它们都必须由Unicode编码规范中的UTF-8编码格式进行编码并存储,Unicode编码规范中的编码格式定义的是字符与字节序列之间的转换方式。其中的UTF-8是一种可变宽的编码方案,它会用一个或多个字节的二进制数来表示某个字符,最多使用四个字节。Go语言中的一个string类型值会由若干个Unicode字符组成,每个Unicode字符都可以由一个rune类型的值来承载。stringforrange。...

继续访问

golang字符串[]string(slice)去重

1、现实代码 // []string 去重 func RemoveDuplicate(list []string) []string { // 这个排序很关键 sort.Strings(list) i := 0 var newlist = []string{""} for j := 0j <len(list)j++ { if strings.Compare(newlist[i], list[j]) == -1 { newlist = append(newlist, lis

继续访问

深入理解golang string

golang string string的定义 // string is the set of all strings of 8-bit bytes, conventionally but not // necessarily representing UTF-8-encoded text. A string may be empty, but // not nil. Values of string type are immutable. type string string string里存储的是字符

继续访问

golang中string包

对于基本类型来说,字符串所需要执行的操作会比较复杂,所以一般语言都会额外封装一些方法用于处理字符串,go语言标准库中也存在这样一个名为strings的库 包含判断,判断一个字符中是否有相应的某个子字符串是经常遇到的一种字符串操作,再go语言中可以使用strings包中的两个方法判断 ...

继续访问

golang中的strings.TrimLeft

golang中的字符串操作strings.TrimLeft package main import ( "fmt" "strings" ) //golang字符串操作 func main(){ s := "Hello world hello world" str := "Hello" //var s = []string{"11","22","33"} //删除s头部连续的包含在str中的字符串 .

继续访问

golang入门time与string转换, time加减时间, 两个时间差

package mainimport ( "fmt" "time")var timeLayoutStr = "2006-01-02 15:04:05" //go中的时间格式化必须是这个时间//var timeLayoutStr = 2006/01/02 03:04:05 //合法, 格式可以改变//var timeLayoutStr = 2019/01/02 15:04:05 /...

继续访问

Golang——string

1.string结构体底层 由两部分组成:指向底层[ ]byte数组的指针;长度(类似切片结构体) 2.相同的string常量不会重复存储 //由s1、s2的指针指向一块相同的只读内存,这段内存以utf-8编码存放hello数组 s1 := "hello" s2 := "hello" 3.string常量会在编译期分配到只读段,对应数据地址不可写入,故string不支持修改。 要修改必须转[]byte,string和[]byte转换,会将这段只读内存的数据复制到堆/栈上。 //wrong s := "he

继续访问

热门推荐 golang -----------字符串(rune,string,type)

一、内存布局 字符串在Go语言内存模型中用一个2字长的数据结构表示。它包含一个指向字符串存储数据的指针和一个长度数据。因为string类型是不可变的,对于多字符串共享同一个存储数据是安全的。切分操作str[i:j]会得到一个新的2字长结构,一个可能不同的但仍指向同一个字节序列(即上文说的存储数据)的指针和长度数据。这意味着字符串切分可以在不涉及内存分配或复制操作。这使得字符串切分的效率等同于...

继续访问

Golang string 常用方法

strings.Compare(p1, p2) 判断p1是否大于p2,大于:1,小于:-1,等于:0。2.strings.LastIndex(p1,p2) p2在p1中最后一次出现的位置,未出现返回-1。1.strings.Replace(p1,p2,p3,n) 将p1中的p2替换成p3,最多替换n个。1.strings.Index(p1,p2) p2在p1中第一次出现的位置,未出现返回-1。2.strings.ReplaceAll(p1,p2,p3) 将p1中的所有p2替换成p3。

继续访问

golang中的字符串

在go中rune是一个unicode编码点。 我们都知道UTF-8将字符编码为1-4个字节,比如我们常用的汉字,UTF-8编码为3个字节。所以rune也是int32的别名。

继续访问

golang之字符串

Go语言的字符有两种类型: 一种是byte型,或者叫uint8类型,代表了ASCII码的一个字符。 一种是rune类型,或者叫int32类型,代表一个UTF-8字符,当需要处理中文、日文等unicode字符时,则需要用到rune类型。 ...

继续访问

Golang的 string 类型

一点睛 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 二 string 使用注意事项和细节 1 Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本,这样 Golang 统一使用 UTF-8 编码,中文乱码问题不会再困扰程序员。 2 字符串一旦赋值了,字符串就不能修改了,在 Go 中字符串是不可变的。 3 字符串的两种表示形式。 a 双引号——会识别转义字符 .

继续访问

Golang Strings 官方包常用方法,学会这些够用了

1.判断是否以某字符串打头/结尾 2.字符串分割 3.返回子串索引 4.字符串连接 5.字符串转化为大小写 6.统计某个字符在字符串出现的次数 7.判断字符串的包含关系 8.字符串替换

1、简单易学。

Go语言的作者本身就很懂C语言,所以同样Go语言也会有C语言的基因,所以对于程序员来说,Go语言天生就会让人很熟悉,容易上手。

2、并发性好。

Go语言天生支持并发,可以充分利用多核,轻松地使用并发。 这是Go语言最大的特点。

描述

Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo,但它也具有Pi运算的特征,比如通道传输。

在1.8版本中开放插件(Plugin)的支持,这意味着现在能从Go中动态加载部分函数。

与C++相比,Go并不包括如枚举、异常处理、继承、泛型、断言、虚函数等功能,但增加了 切片(Slice) 型、并发、管道、垃圾回收、接口(Interface)等特性的语言级支持。