Go语言适用场景
Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。
Go 语言同时也是一门可以用于实现一般目标的语言,例如对于文本的处理,前端展现,甚至像使用脚本一样使用它。
因为垃圾回收和自动内存分配的原因,Go 语言不适合用来开发对实时性要求很高的软件。
Go 语言可以在 Intel 或 ARM 处理器上运行,因此它也可以在安卓系统下运行。
Go语言特性
Go 语言没有类和继承的概念,但是它通过**接口(interface)**的概念来实现多态性。
Go 语言并不使用面向对象编程技术,有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。
函数是 Go 语言中的基本构件。
Go 语言使用静态类型,所以它是类型安全的一门语言,加上通过构建到本地代码,程序的执行速度也非常快。
作为强类型语言,隐式的类型转换是不被允许的,记住一条原则:让所有的东西都是显式的。
Go 语言其实也有一些动态语言的特性(通过关键字 var)。
- 简化问题,易于学习
- 内存管理,简洁语法,易于使用
- 快速编译,高效开发
- 高效执行
- 并发支持,轻松驾驭
- 静态类型
- 标准类库,规范统一
- 易于部署
- 文档全面
- 免费开源
Go语言特性缺失
许多能够在大多数面向对象语言中使用的特性 Go 语言都没有支持,但其中的一部分可能会在未来被支持。
- 为了简化设计,不支持函数重载和操作符重载
- 为了避免在 C/C++ 开发中的一些 Bug 和混乱,不支持隐式转换
- Go 语言通过另一种途径实现面向对象设计来放弃类和类型的继承
- 尽管在接口的使用方面可以实现类似变体类型的功能,但本身不支持变体类型
- 不支持动态加载代码
- 不支持动态链接库
- 不支持泛型
- 通过 recover 和 panic 来替代异常机制
- 不支持静态变量
Go与其他语言区别
-
默认值:它使用 nil 作为默认值(在 Objective-C 中是 nil,在 Java 中是 null,在 C 和 C++ 中是NULL或 0)
-
一个函数可以拥有多返回值:返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来,如:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
-
显式转换:Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
valueOfTypeB = typeB(valueOfTypeA)
类型 B 的值 = 类型 B(类型 A 的值)示例:
a := 5.0
b := int(a)
- 声明了一个变量,必须使用,否则编译错误;(全局变量除外)
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
尝试编译这段代码将得到错误 a declared and not used。
此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 fmt.Println(“hello, world”, a) 会移除错误。
-
Go语言拥有指针,但不允许指针操作,例如pointer+2,c=*p++不被允许,更类似于java中的引用,不允许空指针
-
Go 完全省略了 if、switch 和 for 结构中条件语句两侧的括号,例如for i:=1; i<5; i++{}
Go函数命名规则
只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;
否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。
左大括号 { 必须与方法的声明放在同一行,这是编译器的强制规定。
Go注释
每一个包应该有相关注释,在 package 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。
当开发人员需要了解包的一些情况时,自然会用 godoc 来显示包的文档说明,在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。
另外,在多段注释之间应以空行分隔加以区分。
// Package superman implements methods for saving the world.
//
// Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world.
package superman
几乎所有全局作用域的类型、常量、变量、函数和被导出的对象都应该有一个合理的注释。
如果这种注释(称为文档注释)出现在函数前面,例如函数 Abcd,则要以 “Abcd…” 作为开头。
// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() error {
...
}
Go语言代码顺序
- 在完成包的 import 之后,开始对常量、变量和类型的定义或声明。
- 如果存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。
- 如果当前包是 main 包,则定义 main 函数。
- 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。
Go语言执行顺序
Go 程序的执行(程序启动)顺序如下:
- 按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程:
- 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
- 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。
- 在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。
声明全局变量
因式分解关键字的写法一般用于声明全局变量
var (
a int
b bool
str string
)
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。
引用类型与值类型
值类型:变量直接指向存在内存中的值;
int、float、bool 和 string 这些基本类型
数组和结构这些复合类型也是值类型。
当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝。
引用类型:内存地址,实际的值;
指针, slices,maps和 channel属于引用类型。
被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。
使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
并行赋值
并行 或 同时 赋值。
如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a。
(在 Go 语言中,这样省去了使用交换函数的必要)
空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。
判断语句的写法
关键字 if 和 else 之后的左大括号 { 必须和关键字在同一行,如果你使用了 else-if 结构,则前段代码块的右大括号 } 必须和 else-if 关键字在同一行。这两条规则都是被编译器强制规定的。
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
if 可以包含一个初始化语句(如:给一个变量赋值)。这种写法具有固定的格式(在初始化语句后方必须加上分号):
if initialization; condition {
// do something
}
例如:
val := 10
if val > max {
// do something
}
你也可以这样写:
if val := 10; val > max {
// do something
}
异常执行
value, err := pack1.Function1(param1)
if err != nil {
fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
return err
}
// 未发生错误,继续执行:
Switch结构
switch var1 {
case val1:
...
case val2:
...
default:
...
}
变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。前花括号 { 必须和 switch 关键字在同一行。
您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。
每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个 switch 代码块,也就是说您不需要特别使用 break 语句来表示结束。
因此,程序也不会自动地去执行下一个分支的代码。如果在执行完每个分支的代码后,还希望继续执行后续分支的代码,可以使用 fallthrough 关键字来达到目的。
因此:
switch i {
case 0: // 空分支,只有当 i == 0 时才会进入分支
case 1:
f() // 当 i == 0 时函数不会被调用
}
并且:
switch i {
case 0: fallthrough
case 1:
f() // 当 i == 0 时函数也会被调用
}
在 case …: 语句之后,您不需要使用花括号将多行语句括起来,但您可以在分支中进行任意形式的编码。当代码块只有一行时,可以直接放置在 case 语句之后。
您同样可以使用 return 语句来提前结束代码块的执行。当您在 switch 语句块中使用 return 语句,并且您的函数是有返回值的,您还需要在 switch 之后添加相应的 return 语句以确保函数始终会返回。
可选的 default 分支可以出现在任何顺序,但最好将它放在最后。它的作用类似与 if-else 语句中的 else,表示不符合任何已给出条件时,执行相关语句。
switch 语句的第二种形式是不提供任何被判断的值(实际上默认为判断是否为 true),然后在每个 case 分支中进行测试不同的条件。当任一分支的测试结果为 true 时,该分支的代码会被执行。这看起来非常像链式的 if-else 语句,但是在测试条件非常多的情况下,提供了可读性更好的书写方式。
switch {
case condition1:
...
case condition2:
...
default:
...
}
例如:
switch {
case i < 0:
f1()
case i == 0:
f2()
case i > 0:
f3()
}
switch 语句的第三种形式是包含一个初始化语句:
switch initialization {
case val1:
...
case val2:
...
default:
...
}
这种形式可以非常优雅地进行条件判断:
switch result := calculate() {
case result < 0:
...
case result > 0:
...
default:
// 0
}
在下面这个代码片段中,变量 a 和 b 被平行初始化,然后作为判断条件:
switch a, b := x[i], y[j] {
case a < b: t = -1
case a == b: t = 0
case a > b: t = 1
}
循环结构
循环开始前,会执行且仅会执行一次初始化语句 i := 0;;这比在循环之前声明更为简短。紧接着的是条件语句 i < 5;,在每次循环开始前都会进行判断,一旦判断结果为 false,则退出循环体。最后一部分为修饰语句 i++,一般用于增加或减少计数器。
这三部分组成的循环的头部,它们之间使用分号 ; 相隔,但并不需要括号 () 将它们括起来。例如:for (i = 0; i < 10; i++) { },这是无效的代码!
同样的,左花括号 { 必须和 for 语句在同一行,计数器的生命周期在遇到右花括号 } 时便终止。一般习惯使用 i、j、z 或 ix 等较短的名称命名计数器。
特别注意,永远不要在循环体内修改计数器,这在任何语言中都是非常差的实践!
您还可以在循环中同时使用多个计数器:
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
无限循环
条件语句是可以被省略的,如 i:=0; ; i++ 或 for { } 或 for ;; { }(;; 会在使用 gofmt 时被移除):这些循环的本质就是无限循环。最后一个形式也可以被改写为 for true { },但一般情况下都会直接写 for { }。
如果 for 循环的头部没有条件语句,那么就会认为条件永远为 true,因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。
想要直接退出循环体,可以使用 break 语句(第 5.5 节)或 return 语句直接返回
无限循环的经典应用是服务器,用于不断等待和接受新的请求。
for-range 结构
这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合(包括数组和 map,详见第 7 和 8 章)。语法上很类似其它语言中 foreach 语句,但您依旧可以获得每次迭代所对应的索引。一般形式为:for ix, val := range coll { }。
要注意的是,val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(译者注:如果 val 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值)。一个字符串是 Unicode 编码的字符(或称之为 rune)集合,因此您也可以用它迭代字符串:
for pos, char := range str {
...
}
指针
指针变量(定义的时候)
1. 内部存储地址;
2. 如果要取值,使用*ptr;
3. 如果传入值,使用&data;