
概要
GO语言是由Google在2012年正式发布的编程语言,在语法和特性上,GO和C++有很多相似之处,但也有很多不同点。本文就GO语言在语法上和C++的一些主要的差异点进行简单的描述。
Go 是一门简单、紧凑且通用的语言。而 C++ 是一门快速且复杂的通用编程语言。Go 和 C++ 都是静态类型语言且都有强大的社区。C++ 广泛用于各种应用,而 Go 主要用于 Web 后端。
Go 是专为现代多核处理器而设计的。Go 语言支持并发编程,这意味着它可以使用不同的线程同时运行多个处理过程,而不是同一时刻只运行一个任务。它还具有延迟垃圾回收功能,可以进行内存管理以防止内存泄漏。
- 编译
都可以静态编译,直接编译成二进制文件。目前,许多语言(比如Java,C#)都是基于运行时,能静态编译语言的不多,Go算一个。同时,都可以跨平台。
- 内存管理
在C++中,需要自己使用new和delete管理内存,尽管C++已经有了智能指针。但是有一些坑,不是那么好用。
Go虽是静态语言,但是自己管理内存,减轻了程序员的心智负担。这一点,非常重要。因为C++程序的崩溃,大多数时候都是内存问题,比如内存泄漏,非常难以解决。
- 标准库
一门语言开发效率高不高,它的标准库起着关键的作用。Go的标准库十分强大,常用的库几乎都有,开箱即用,十分顺手。C++的标准库里面的工具并不多,很多时候只能下载第三方库使用。不过,boost是一个著名的C++库,包含了大量的常用库。
- 性能
Go 相当快。其编译、静态类型以及高效的垃圾回收使其变得极其快。Go 也擅长内存管理;它有指针但没有引用。在速度方面,和 C++ 非常接近(以及 C 语言)。所有的时间都花在编码和编译上。因为 C++ 难编码,中级语言,更接近于机器码:当编译时,他更适合于机器码的嵌套。
C++ 也缺少那些让编码更容易但会给生成的程序增加阻力的特性。当提及运行时,C++ 轻量,精简且快速。
Go 配备了所有的能让你在编码过程中使生活更容易的零件和部件,因此,在运行时会慢一些。其中最大的一块是他的虽然很好但很慢的垃圾收集器。
- 并发编程
并发编程是Go语言的一大特色,可以轻松实现高并发,在语言层面就支持。C++只能调动系统API开启线程实现并发,语言层面并不支持。
- 开发和调试难度
C++的内存问题,很难排查和调试,比如内存越界,从程序崩溃的栈信息上可能就看不出来是什么问题,定位问题很难。Go进程中会启动Go自己的运行时,抛出的异常会指明错误信息,很容易能排查出问题。
代码格式
GO语言要求代码的花括号{}
必须采用统一的风格,即左花括号{
必须放在行尾而不能另起一行。
GO不需要在语句的最后增加分号;
以表示语句结束。
1 | if a == b { //花括号{必须放在上一行的最后面 |
常量和变量
常量&变量声明与赋值
GO在进行变量声明时,需要在变量前加var
关键字;但GO中的变量可以不定义直接赋值,此时GO会自己根据:=
右侧的表达式计算出变量的类型。
同时,GO还支持多重赋值。
1 | var i int |
在定义常量常量时,和C++一样只需要在前面增加const
关键字,并同时给常量赋以编译期可确定的值。
1 | const i int = 2 << 5 //=号右侧的表达式必须可以在编译期确定 |
GO语言中有三个预定义的常量:true
、false
、itoa
。true
/false
很好理解,itoa
是一个很特殊和常量,它在一个const
作用范围中,第一次出现时为0,后续每次出现都比上次的值大1,在GO中通常用于“枚举”(GO不支持枚举)定义。
1 | const { |
可见性
GO语言中没有private
/protected
/public
/friend
关键字,可见性是通过成员的首字母是否大写决定的,并且仅支持包间的可见性定义,不支持类的可见性。
包中所有首字母大写开头的成员(对象、函数),表示包间可见,可以被其它包中的代码访问。但对于首字母小写开头的成员,则仅能被本包中的成员所访问(这相当于包内的所有成员间都加了friend声明)
条件、选择和循环
条件语句
GO语言的条件语句中,条件不需要加括号()
,并且支持初始化语句:
1 | if a := b; a == 1 { |
选择语句
GO的选择语句中,不支持break
关键字,因为GO的选择语句中,遇到符合条件的分支会自动跳出;如果想要继续向下执行,需要在case
分支中增加fallthrough
关键字。
1 | switch i { |
在GO的switch后面可以不带表达式,此时在case中需要增加条件:
1 | switch { |
循环语句
GO不支持while
和do...while
形式的循环,仅支持for
循环。在GO的for
循环中可以通过多重赋值方式为多个变量赋值。对于多重循环,可以在最外层定义循环标签,并在内层循环中通过break
关键字直接指定标签名,直接跳出外层循环。
1 | OutLoop: //定义循环标签 |
函数
在Golang语言中不支持函数的重载
Golang不允许同一个文件里函数名不同
在Go中,函数本身也是一种数据类型,可以赋值给一个变量,该变量就是函数类型的变量,通过该变量实现对函数类型的调用
函数定义
Go 语言函数定义格式如下:
1 | func function_name( [parameter list] ) [return_types] { |
- func:函数由 func 开始声明
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。可以有返回值,也可以没有
- 函数体:函数定义的代码集合。
return语句
- Go 函数可以返回多个值,和python相似
- 如果返回多个值时,在接受时,希望忽略某个返回值,则使用
_
表示忽略 - 如果返回值只有一个,返回值类型列表可以不写
()
1 | package main |
普通函数
在定义GO函数时,需要在前面增加func
关键字,func
后面紧随函数名,然后定义参数列表;GO语言是支持多返回值的,因此最后需要定义返回值列表。下面是两个int类型的除法函数示例:
1 | //参数或返回值列表中,多个相同类型的可以通过逗号合并声明 |
可变参数
GO和C++一样也支持可变参数,同样需要将可变参数放在所有参数的最后。GO的可变参数可以指定类型,也可以通过interface{}
支持任意类型。
1 | func varFunc1(argv ...int){ |
说明:
-
args是slice切片,通过args[index]可以访问到各个值
-
切片是一个动态的数组
-
如果一个函数的形参列表中有可变参数,那么把可变参数放在最后
1 | package main |
匿名函数
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
GO语言支持匿名函数,这一点是C++所不具备的。
1 | package main |
接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。把所有方法全部实现了,叫做实现了接口
接口类型可以定义一组方法,但是这些不需要实现,而且Interface不能包含任何的变量
1 | /* 定义接口 */ |
案例:
1 | package main |
错误处理
GO语言通过error类型以及defer
、panic
、recover
三个关键字,相对C++提供了更方便的错误处理。
- error
GO的error实际是一个定义了func Error() string
函数的接口。所有的error类型都必须实现Error()
函数。
1 | type error interface { |
因为GO支持多个返回值,因此可以通过在返回值中增加error类型,更方便开发人员判断调用是否成功,比如前面定义的Devide函数的调用可以按如下方式写:
1 | ret, err := Devide(a, b) |
而在C++中要实现同样的功能,则需要函数抛出一个DevideZero异常,并在调用处捕获异常再处理;或是定义一个返回值并通过输出参数返回结果。而通过error进行处理则简单很多。
- defer
使用C++的开发人员对C++中的内存、文件句柄等资源的释放的处理一定有很深的映像。在使用C++开发过程中,我们必须保证在函数退出或对象析构时进行正确的资源释放动作,而这在复杂的代码逻辑中往往是非常容易出错的地方。
GO语言通过defer关键字解决了开发人员的这个难题。开发人员只需要在申请了资源后立即通过defer关键字编写资源释放的代码,GO会保证在正确的时机释放资源(有点类似C++的finally)。比如下面的例子中,GO会保证在TestResource函数在退出前,调用到defer后面的代码以保证文件被关闭:
1 | func TestResource() { |
注意:如果一个作用域内有多个defer,则在退出时会按先进后出的方式进行调用。因此在使用时应该尽量避免一个作用域内多个defer中的代码存在顺序要求,以避免出错。
- panic&recover
panic和recover的函数原型如下:
1 | func panic(interface{}) |
有点类似于C++中的抛出异常,在函数的执行过程中,如果panic被调用到则当前函数执行中止,此时将调用函数中走到过的defer代码,然后再依次向上级函数返回并调用这些函数的panic函数,直至当前goroutine中所有函数中止。
而recover函数通常用于在defer中用于判断函数是否是panic导致的退出:
1 | defer func() { |
更丰富的内建类型
C++仅支持布尔、字符、数值内建类型,如果需要使用字符串、列表、字典,必须引入stl或其它库。
GO则将字符串、切片(slice)、字典、复数作为了原子类型支持。下面我们通过示例看一下GO对各种类型的支持:
golang的数组可以像python一样使用range
遍历
1 | for index,value := range array{ |
- index : 数组下标
- value 下标对应位置
- array:数组名
- 都是仅在for循环内部可见的局部变量
- 遍历数组时,如果不想使用index,可以使用
_
代替
字符串(string)
1 | var s string |
切片(slice)
GO语言中的切片类似于stl中的vector,它实际包括了一个指向数组的指针,数组元素的个数,以及对应的内存存储空间。
-
切片是数组的一个引用,那么切片是一个引用类型,这和数组是不一样的,函数中改变的会改变其值
-
切片的长度是可以变化的
-
切片的使用类似于数组,遍历和访问都是和数组一样的
-
切片的定义基本语法:
1
2
3
4
5var slicename [] type
/*
slicename:切片名
type :类型
*/
下面的示例展示了如何通过一个数组创建slice。
1 | myArray := [5]int{1, 2, 3, 4, 5} |
1 | package main |
也可以通过make
函数创建指定类型、长度和容量的slice:
1 | //创建一个int类型的slice,初始长度为3,但容量为5 |
注意:
- slice 是切片名称
- intArr[1:3] 表示slice引用数组第二个元素到下标
- 引用intArr数组的起始下标为1,终止下标为3,但是不包含3
- 切片的容量cap是可变的,这样可以节约空间
- 此时改变数组的值,slice 的值也会发生变化(引用)
切片在内存中形式
在内存里,可以理解为slic是由三个部分组成的
- 第一个位置记录的是数组的地址,是引用类型
- 第二个记录了slic本身的长度
- 第三个记录的是slic容量的大小
可以理解为slic是一个引用类型(本身也是有个地址)slic从底层来说其实就是一个数据结构,是struct结构体
1 | type slice struct{ |
append() 和 copy() 函数
- append动态追加
1 | var slice []int = []int{100, 200, 300} |
- 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
- 使用append时Go底层创建一个新的数组newArr安装扩容后大小
- 将slice原来包含的元素拷贝到新的数组,newArr是在底层维护的,程序员不可见
- copy内置函数拷贝
1 | var slice4 []int = []int{1, 2, 3, 4, 5} |
- 如果修改slice5的值,slice4不变,他们之间的数据空间是独立的
- 默认情况下,使用make后,多余的空间默认为0
当使用拷贝的时候,如果当前切片容量不够怎么办,会报错吗?
1 | package main |
字典(map)
在Go中定义字典类型以map[KeyType]ValueType
的形式定义,示例如下:
1 | var dict map[string]int |
复数(complex64/complex128)
除了以上常见的数据类型,Go还支持复数类型。
1 | var z complex64 |
除了以上类型,GO还支持管道channel、unicode字符rune、错误类型error、指针pointer。
类型系统
除了内建类型之后,GO也支持用户自定义类型,比如struct、interface,但在语法上有一些差异,比如下面的示例是GO的struct和interface的定义:
1 | type Rect struct { |
但GO的struct和interface与C++存在几点显著的不同:
- 不支持继承,只能通过组合实现继承
- 可以给任意类型增加方法
- 类不需要显式指定实现interface
- 对象可以在多个具有相同接口的interface间转换
通过以下的示例可以更好的理解以上几点差异:
1 | //将内建类型int定义Integer |
Go并发
Go 语言支持并发,我们只需要通过 go
关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
1 | go 函数名( 参数列表 ) |
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
案例
1 | package main |
执行以上代码,你会看到输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行。
通道
概念
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
1 | ch <- v // 把 v 发送到通道 ch |
声明一个通道很简单,我们使用chan
关键字即可,通道在使用前必须先创建:
1 | ch := make(chan int) |
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
1 | package main |
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
1 | ch := make(chan int, 100) |
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
1 | package main |
Go 遍历通道与关闭通道
Go 通过 range
关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
1 | v, ok := <-ch |
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close()
函数来关闭。
1 | package main |
要点
-
goroutine 是 golang 中在语言级别实现的轻量级线程,仅仅利用 go 就能立刻起一个新线程。多线程会引入线程之间的同步问题,在 golang 中可以使用 channel 作为同步的工具。
通过 channel 可以实现两个 goroutine 之间的通信。
向 channel 传入数据,
CHAN <- DATA
, CHAN 指的是目的 channel 即收集数据的一方, DATA 则是要传的数据。从 channel 读取数据,
DATA := <-CHAN
,和向 channel 传入数据相反,在数据输送箭头的右侧的是 channel,形象地展现了数据从隧道流出到变量里。 -
Channel 是可以控制读写权限的 具体如下:
1
2
3
4
5
6
7
8//定义只读的channel
read_only := make (<-chan int)
//定义只写的channel
write_only := make (chan<- int)
//可同时读写
read_write := make (chan int)定义只读和只写的channel意义不大,一般用于在参数传递中
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
27package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
go send(c)
go recv(c)
time.Sleep(3 * time.Second)
}
//只能向chan里写数据
func send(c chan<- int) {
for i := 0; i < 10; i++ {
c <- i
}
}
//只能取channel中的数据
func recv(c <-chan int) {
for i := range c {
fmt.Println(i)
}
}