共计 9662 个字符,预计需要花费 25 分钟才能阅读完成。
写在后面:本系列为 java 程序员转 go 的疾速学习笔记,波及根底语法、进阶常识、简略实战三大部分
根底语法
本节次要解说 go 语言的 数据类型、变量的申明和赋值、条件语句、循环语句。
一、数据类型
和大多数后端语言一样,go 语言数据类型也可分为根本数据类型和援用数据类型
go 语言根本数据类型如下:
布尔型:go 用 bool 示意
整型:Go 外面有 int8 int16 int32 uint64,别离对应 Java 中的 byte short int long,同时 Go 外面还有专门 示意无符号数的 uint8 uint 16 uint32 uint64。
浮点型:float32 float64
字符串类型:string
很多写其余后端语言如 Java 的同学刚开始接触 go 可能会对 uint8 和 rune 不太分明,其实 []uint8 即 []byte,rune 通常示意 int32,也就是 4 个字节有符号的整数,对应 Java 中 int
byte // uint8 的别名
rune // int32 的别名
除此之外,go 语言数据类型一些派生类型,包含:
指针类型(Pointer)、数组类型、结构化类型(struct)、Channel 类型、函数类型、切片类型、接口类型(interface)、Map 类型 等。
二、变量
变量申明赋值
Go 语言变量申明赋值有三种形式
a. 先申明、后赋值
例如:
var name string
name = "lubanproj"
Go 语言中用 var 申明变量,且行末没有“;”。C++ 和 Java 语言中是以 分号“;”进行语句宰割,所以行末会有分号。
和大多数语言约定不同,Go 中申明变量,变量放在类型前,例如上文 var name string,如果从 C++ 和 Java 等语言刚切换过去可能会很不习惯,这里肯定要留神
b. 申明并且赋值
var name = "lubanproj"
// 编译器会依据值自行断定变量类型
c. 应用 ” := ” 申明并赋值
name := "lubanproj"
// 这里省略 var 关键字,成果和第二种是一样的
三、条件语句
Go 语言中 if 语句的语法如下:
if 布尔表达式 {/* 在布尔表达式为 true 时执行 */}
须要留神的是,Go 中条件语句,if 没有括号 , 例如:
if a == 10 {
/* 如果条件为 true 则执行以下语句 */
fmt.Println("a == 10")
}
此外,if 还有另外一种模式,它蕴含一个 statement
可选语句局部,该组件在条件判断之前运行。它的语法是
if statement; condition {}
例如:
if a := 1 ; a < 10 {
/* 如果条件为 true 则执行以下语句 */
fmt.Println("a < 10")
}
四、循环语句
与其余后盾语言不同的是,Go 语言只提供了 for 循环,没有 while 循环。
go 尽管没有提供 while 循环,然而能够应用 for 循环实现 其余语言 while 循环的成果。
Go 语言的 for 循环有 3 种模式,只有其中的一种应用分号。
第一种:和其余语言的 for 语法相似,语法格局如下:
for init; condition; post { }
init:个别为赋值表达式,给控制变量赋初值;condition:关系表达式或逻辑表达式,循环管制条件;post:个别为赋值表达式,给控制变量增量或减量。例如:for i :=1 ; i < 10 ; i ++ {fmt.Println("i =", i)
}
这里是不是很相熟?跟其余语言惟一不一样的中央在于 Go 中 for 循环没有小括号。
第二种:和其余语言 while 循环 相似,语法格局如下:
for condition { }
例如:for 0 == 0 {}
for true {}
第三种:死循环,相似 while (true) {},语法格局如下:
for { }
例如:for {server.Listen(8080) // 服务器监听 8080 端口
}
数据结构和函数的应用
一、构造体
构造体是由一系列具备雷同类型或不同类型的数据形成的数据汇合。(相当于 Java 中的类 class )
go 语言中 构造体定义如下:
type identifier struct {
field1 type1
field2 type2
...
}
用构造体能够示意一个具备某一特色的对象。field1, field2 … 是对象的属性,type1, type2 是属性的数据类型。
依照面向对象的思维,“人”、“书本”、“学校”、“电脑”、“空气”等自然界的万事万物都能够用对象来示意。拿“人”这个对象来说,能够如下示意:
type Person struct {
name string // 姓名
age uint32 // 年龄
birth string // 出生日期 用 yyyy/mm/dd 格局的字符串示意
height float32 // 身高
weight float32 // 体重
...
}
name, age, birth, height, weight 是“人”这个对象的姓名、年龄、出生日期、身高、体重等属性
string, uint32, float32 是 这些属性的数据类型
对构造体的赋值有两种形式:
第一种:
t := new(T):变量 t 是一个指向 T 的指针,此时构造体字段的值是它们所属类型的零值,例如:
person := new(Person)
第二种:
t := &T{}
&T{} 这种语法叫做 混合字面量语法(composite literal syntax),相似 &struct1{a, b, c} 这种混合字面量语法是一种简写,底层依然会调用 new (),这里值的程序必须依照字段程序来写。
person := &Person {name : "lubanproj", age : 12}
如果要拜访构造体成员,须要应用点号 . 操作符,格局为:
构造体. 成员名 "
例如:
person := &Person {name : "lubanproj", age : 12}
fmt.Printf("name : %s, age : %d", person.name, person.age)
二、数组
数组是有序的元素序列。
go 中数组语法如下:
var variable_name [SIZE] variable_type
例如:
// 定义一个 10 个长度 string 类型的 数组 array
var array [10] string
// 数组赋值
array[0] = "go"
array[1] = "java"
array[2] = "c++"
数组赋值
下面代码能够看出。间接通过拜访数组下标即可对数组元素进行赋值。
数组初始化
申明时初始化,语法如下:
var variable_name = [SIZE]variable_type {value1, value2 ...}
例如:
var array = [5]string{"go", "java", "c++"}
数组遍历
一般 for 循环遍历,例如:
for i:=0; i < 5; i++ {fmt.Printf("array[%d] = %s n" , i, array[i])
}
for range 遍历,这是 go 外面一种特有的遍历形式,例如:
// 这段代码能够实现跟下面代码雷同成果
for index, value := range array {fmt.Printf("array[%d] = %s n", index, value)
}
下面代码中,index 即为数组元素下标,value 即为数组元素的值,如果不须要应用到 index 或者 value,能够用 _ 进行代替。
例如:
// 这段代码能够实现跟下面代码雷同成果
for _, value := range array {fmt.Printf("value = %s n", value)
}
三、切片
go 提供了一种相似“动静数组”构造的数据类型,这种类型就是切片 slice。
slice 的实质是一个数据结构,实现了对数组操作的封装。
切片 slice 的申明语法如下:
var identifier []type
例如:
// 申明一个为 int64 类型的切片
var array []int64
你会发现 slice 和数组的申明语法是不同的。go 语言中申明数组时,是须要指定长度的,例如:
// 申明一个为 int64 类型、长度为 10 的数组
var array [10] int64
初始化操作也不一样,对 slice 的初始化是应用 make 初始化
// 初始化一个 int64 类型的切片
array = make ([]int64 , 10)
而对数组的初始化是:
array = [10] int64 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
切片 slice 的罕用办法
append : 实现对 slice 元素的增加 例如 :
array := make ([]int64 , 10 )
// 往数组中增加两个元素
array = append (array, 1)
array = append (array, 2)
截取 : 能够通过设置上限及下限来设置截取切片 [lower-bound:upper-bound],例如:
package main
import "fmt"
func main() {
/* 创立切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
/* 打印子切片从索引 1(蕴含) 到索引 4(不蕴含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* 默认上限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])
/* 默认下限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])
}
func printSlice(x []int){fmt.Printf("len=%d slice=%v n" , len(x) , x)
}
执行以上代码输入后果为:numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
获取切片长度 : 通过 len 办法,能够获取 slice 长度
len (slice)
遍历 : 和数组一样,slice 能够通过 range 关键字进行遍历,例如:
package main
import "fmt"
func main() {
// 应用 range 去求一个 slice 的和
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
sum := 0
for _, num := range nums {sum += num}
fmt.Println("sum:", sum)
for i, num := range nums {
if num == 3 {fmt.Println("index:", i)
}
}
}
四、map
map 是一种无序的键值对的汇合
go 语言中 语法如下:
/* 申明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
例如:
var dataMap map[string]string
map 初始化
map 能够应用 make 进行初始化
/* 应用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
例如:
dataMap := make(map[string]string)
如果不初始化 map,那么就会创立一个 nil map。nil map 不能用来寄存键值对
例如上面代码,会执行失败:
package main
func main() {var dataMap map[string]string
dataMap["lubanproj"] = "hello lubanproj"
}
输入:
panic: assignment to entry in nil map
所以 对于 map 类型,肯定要进行初始化再赋值
和数组不同,map 能够依据新增的 key-value 对动静的伸缩,因而它不存在固定长度或者最大限度。然而你也能够抉择表明 map 的初始容量 capacity,语法如下:
make(map[keytype]valuetype, capacity)
例如:
dataMap := make(map[string]string, 20)
map 遍历
和 slice 类似,map 的遍历同样能够应用 for range 进行遍历,例如:
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {fmt.Printf("%s -> %sn", k, v)
}
map 基本操作
假如有一个 map m,上面演示下对 m 的操作
设置 / 批改 元素值
m[key] = elem
获取 元素:
elem = m[key]
删除 元素:
delete(m, key)
检测某个键 是否存在:
elem, ok = m[key]
如果 key 在 m 中,ok
为 true。否则,ok 为 false
,并且 elem 是 map 的元素类型的零值。
同样的,当从 map 中读取某个不存在的键时,后果是 map 的元素类型的零值。
上面代码演示了对 map 的基本操作 :
package main
import "fmt"
func main() {dataMap := make(map[string]string, 10)
dataMap["hunan"] = "changsha"
dataMap["hubei"] = "wuhan"
dataMap["guangdong"] = "guangzhou"
dataMap["guangxi"] = "guilin"
dataMap["anhui"] = "hefei"
// 遍历输入省和省会城市
for province, city := range dataMap {fmt.Printf("%s 的省会是 %s n", province, city)
}
// 获取湖南省的省会
hunanCity := dataMap["hunan"]
beijingCity, ok := dataMap["heibei"]
fmt.Printf("hunan 省的省会城市是 : %s n",hunanCity)
if ok {fmt.Printf("hebei 省的省会城市是 : %s n",beijingCity)
} else {fmt.Printf("hebei 省的省会城市不存在 n")
}
delete(dataMap, "hunan")
hunanCity = dataMap["hunan"]
fmt.Printf("hunan 省的省会城市是 : %s n",hunanCity)
}
输入后果为 :
guangxi 的省会是 guilin
hubei 的省会是 wuhan
guangdong 的省会是 guangzhou
anhui 的省会是 hefei
hunan 的省会是 changsha
hunan 省的省会城市是 : changsha
hebei 省的省会城市不存在
hunan 省的省会城市是 :
五、函数
置信学过编程的同学都晓得,函数是最根本的代码块
go 语言函数定义格局如下:
func function_name([parameter list] ) [return_types] {函数体}
例如 :
// 此函数实现了取两个数最大值的性能
func max(a int32, b int32) int32 {
if a > b {return a} else {return b}
}
与许多后端语言不同的是,go 语言中的函数是反对多返回值的,比方咱们将上述函数批改一下,实现取两个数最大值和最小值的性能。
// 此函数实现了取两个数最大值和最小值的性能
func getMaxAndMin(a int32, b int32) (int32, int32) {
if a > b {return a, b} else {return b, a}
}
办法
与其余语言不同的是,go 语言还有一种非凡的函数,叫做办法 。 一个办法就是一个蕴含了接受者的函数,接受者能够是命名类型或者构造体类型的一个值或者是一个指针。所有给定类型的办法属于该类型的办法集。语法格局如下:
func (variable_name variable_data_type) function_name() [return_type]{/* 函数体 */}
例如 :
// 咱们用 面向对象的思维实现一个封装的构造体
type Person struct{
name string // 姓名
age uint32 // 年龄
}
// 获取姓名
func (p *Person) GetName() string{return p.name}
// 获取年龄
func (p *Person) GetAge() uint32{return p.age}
// 设置姓名
func (p *Person) SetName(name string) {p.name = name}
// 设置年龄
func (p *Person) SetAge(age uint32) {p.age = age}
异样解决机制和面向对象
身为一门强壮、高效的语言,go 提供了异样解决机制来实现容错性,以及提供了相似 java 的面向对象的能力来实现高内聚、低耦合。
这个章节咱们别离介绍下 异样解决机制和面向对象 是如何实现的。
一、异样解决
家喻户晓,很多语言(例如:java、c++ 等)都有 exception 的 try — catch — finally 异样捕捉机制,go 语言中个别不采纳这种异样捕捉机制,而是通过上面几种形式进行异样解决。
error 接口
go 语言通过内置的谬误接口提供了非常简单的错误处理机制。
error 类型是一个接口类型,这是它的定义:
type error interface {Error() string
}
应用 errors.New () 能够返回一个 error 信息 例如:
func checkParam(username string,password string)error {
if username == ""|| password =="" {return errors.New("params error")
}
}
一般来说,error 是 go 语言中最常见的处理错误的形式,通过返回 error,解决 error,产生相似 exception 的成果。
defer 语句
在 go 语言中,能够应用关键字 defer 向函数注册退出调用,即 主调函数退出时,defer 后的函数才会被调用。
defer 语句的作用是不论程序是否出现异常,均在函数退出时主动执行相干代码。(相当于 finally 代码块)
例如:
func main() {
for i := 0; i < 5; i++ {defer fmt.Println(i)
}
}
其执行后果为 :
4
3
2
1
0
所以,咱们在进行数据库连贯、文件、锁 操作时,个别都会应用 defer 语句进行数据库连贯的开释,开释文件句柄和锁的开释。
panic-recover 机制
咱们在上文说到能够在办法中抛出 error 谬误来实现异样的捕捉,然而 error 只能针对预期内的谬误,因为你是预判这段程序可能出现异常逻辑,才会去被动调用 errors.New () 生成一个 error。然而对于一个办法来说,咱们不可能预判到所有的异常情况,那如果某一个暗藏 bug 导致程序解体了怎么办呢?这里就须要引入 panic-recover 机制了。
如果代码运行时异样解体了,此时 go 会主动 panic,go 的每次 panic 都是十分耗费性能的,且 go 是单线程,所以,咱们应该尽量去防止应用 panic。
panic () 是一个内建函数,能够中断原有的管制流程,进入一个令人 panic (恐慌,即 Java 中的异样)的流程中。当函数 F 调用 panic,函数 F 的执行被中断,然而 F 中的提早函数 (必须是在 panic 之前的已加载的 defer) 会失常执行,而后 F 返回到调用它的中央。在调用的中央,F 的行为就像调用了 panic。这一过程持续向上,直到产生 panic 的 goroutine 中所有调用的函数返回,此时程序退出。异样能够间接调用 panic 产生。也能够由运行时谬误产生,例如拜访越界的数组。
recover () 是一个内建的函数,能够让进入令人恐慌的流程中的 goroutine 恢复过来。recover 仅在提早函数中无效。在失常的执行过程中,调用 recover 会返回 nil,并且没有其它任何成果。如果以后 goroutine 陷入 panic,调用 recover 能够捕捉到 panic 的输出值,并且恢复正常的执行。
个别状况下,recover () 应该在一个应用 defer 关键字的函数中执行以无效截取错误处理流程。如果没有在产生异样的 goroutine 中明确调用复原过程(应用 recover 关键字),会导致该 goroutine 所属的过程打印异样信息后间接退出。
这里联合自定义的 error 类型给出一个应用 panic 和 recover 的残缺例子:
package main
import ("fmt")
// 定义除法运算函数
func Devide(num1, num2 int) int {
if num2 == 0 {panic("num cannot be 0")
} else {return num1 / num2}
}
func main() {
var a, b int
fmt.Scanf("%d %d", &a, &b)
defer func() {if r := recover(); r != nil {fmt.Printf("panic 的内容 %vn", r)
}
}()
rs := Devide(a, b)
fmt.Println("后果是:", rs)
}
二、面向对象
面向对象是程序设计的一种根本思维。面向对象思维次要体现在封装、继承、多态等个性的设计与使用。那么在 go 中是如何去进行实现的呢?
封装
封装次要是通过拜访权限管制实现的。以 Java 为例,共有 public、protected、default、private 四种权限,每个关键字的权限范畴如下:
在 go 语言中,并没有 public,private 这些权限控制符。那么 go 是如何实现 构造体的封装的呢?
在 go 语言中,是通过约定来实现权限管制的。变量和办法都恪守驼峰式命名。变量和办法的首字母大写,相当于 public,变量和办法的首字母小写,相当于 private。同一个包中拜访,相当于 default,因为 go 语言没有继承,所以也没有 protected 权限。
继承
下面刚说到,go 语言是没有继承的。然而 go 语言能够通过构造体之间的组合来实现相似继承的成果。
如果把 go 中 struct 看做 Java 或者 c++ 中的类 class,在 struct 中能够蕴含其余的 struct,继承外部 struct 的办法和变量,同时能够重写,代码如下:
package main
import "fmt"
type oo struct {
inner
ss1 string
ss2 int
ss3 bool
}
type inner struct {ss4 string}
func (i *inner) testMethod () {fmt.Println("testMethod is called!!!")
}
func main() {oo1 := new(oo)
fmt.Println("ss4 无值:"+oo1.ss4)
oo1.ss4 = "abc"
fmt.Println("ss4 已赋值"+oo1.ss4)
oo1.testMethod()// 继承调用
oo1.inner.testMethod()// 继承调用 这里也能够重写}
多态
Java 中的多态是通过 extends class 或者 implements interface 实现的,在 go 中既没有 extends,也没有 implements,那么 go 中是如何实现多态的呢?
咱们来看以下代码,Girl 和 Boy 都实现了 Person。在 go 语言中,只有某个 struct 实现了某个 interface 的所有办法,那么咱们就认为这个 struct 实现了这个类(相当于 Java 中的 implements)。
package main
import ("fmt")
type Person interface {Sing ()
}
type Girl struct {Name string}
type Boy struct {Name string}
func (this *Girl) Sing () {fmt.Println("Hi, I am" + this.Name)
}
func (this *Boy) Sing () {fmt.Println("Hi, I am" + this.Name)
}
func main() {g := &Girl{"Lucy"}
b := &Boy{"Dave"}
p := map[int]Person{}
p[0] = g
p[1] = b
for _, v := range p {v.Sing()
}
}