Go介绍
特点:
Go环境变量
GOEXE=.exe // 形成可执行文件的后缀 GOPATH // 工作目录
GOPATH下约定俗成的目录:
bin // 存放编译后生成的可执行文件 pkg // 存放编译后生成的包文件 src // 存放项目源码
常用命令
go get // 获取远程包 (git或hg(如果是谷歌code上托管)) go run // 直接运行程序 go build // 编译,检查是否有编译错误 go fmt // 格式化源码 go install // 编译包文件并且编译整个程序 go test // 运行测试文件 go doc // 查看文档 godoc -http=:8080 // 查看文档
GO程序结构
mpathapp // 可执行文件存放位置 math.a // 包文件
基础知识
hello.go
package main import ( "fmt" ) func main () { fmt.Println("hello world") }
.go
文件的一般结构
Go程序是通过package
来组织的
只有package
名称为main
的包可以包含main
函数
一个可执行程序有且仅有一个main
包
通过import
关键字来导入其它非main
包
通过const
关键字来定义常量
通过在函数体外部使用var
关键字来进行全局变量的声明和赋值
通过type
关键字来进行结构struct
或接口interface
的声明
通过func
关键字来进行函数的声明
// 当前程序的包名 package main // 导入其它的包 // import . "fmt" // 省略调用 import ( "fmt" "io" "os" "time" "strings" ) // 常量的定义 const PI = 3.141592653 // 全局变量的声明与赋值 var name = "zf" // 一般类型声明 type newType int // 结构的声明 type struc struct{} // 接口关键字和大括号不能有空格 // 接口的声明 type inter interface{} // 接口关键字和大括号不能有空格 // 由 main 函数作为程序入口点启动 func main () { fmt.Println("hello world!") }
package 别名
当使用第三方包时,包名可能会非常接近或者相同,此时就可以使用别名来进行区别和调用
import std "fmt" // 别名 std.Println("hello world")
注释
// 单行注释 /* 多行注释 */
可见性规则
Go语言中,使用大小写来决定该常量,变量,类型,接口,结构,或函数是否可以被外部包所调用:根据约定,函数名首字母小写即为private
;函数名首字母大写即为public
类型与变量
基本类型
布尔型:bool
长度:1字节
取值范围:true,false
注意:不可以用数字代表true或false
整型:int/uint
根据运行平台可能为32或64位
-
8位整型:int8/uint8
长度:1字节 取值范围:-128~127/0~255
-
16位整型:int16/uint16
长度:2字节 取值范围:-32768~32767/0~65535
-
32位整型:int32(rune)/uint32
长度:4字节 取值范围:-2^32/2~2^32/2-1/0~2^32-1
-
64位整型:int64/uint64
长度:8字节 取值范围:-2^64/2~2^64/2-1/0~2^64-1
- 字节型:byte(uint8别名)
从严格意义上讲type newint int
这里的newint
ing不能说是int
的别名,而是底层数据结构相同,在这里自定义类型,在进行类型转换时扔旧需要显示转换,但byte
和rune
确实为uint8
和int32
的别名,可以相互进行转换。
浮点型:float32/float64
长度:4/8字节
小数位:精确到7/15小数位
复数:complex64/complex128
长度:8/16字节
足够保存指针的 32 位或 64 位整数型:uintptr
其它值类型
array、struct、string
引用类型
slice、map、chan
接口类型
interface
函数类型
func
类型零值
零值并不等于空值,而是当变量被声明为某种类型后的默认值。
通常情况下:
- 值类型的默认值是为0
- bool为false
- string为空字符串
变量
单个变量的声明与赋值
变量的声明格式:var <变量名称> <变量类型>
变量的赋值格式: <变量名称> = <表达式>
声明的同时赋值:var <变量名称> [变量类型] = <表达式>
写法:
var a int // 变量声明 a = 10 // 变量赋值 var b int = 20 // 变量声明的同时赋值 var b = 1 // 变量声明与赋值,由系统推荐是那种类型 b := 10 // 函数中的变量声明与赋值的最简写法 var 是全局的变量 := 只能在函数中使用,局部变量
多个变量的声明与赋值
全局变量的声明可使用var()
的方式进行简写
全局的变量的声明不可以省略var,但可使用并行方式
所有变量都可以使用类型推断
局部变量不可以使用var()
的方式简写,只能使用并行方式
var ( // 使用常规方式 aaa = "hello" // 使用并行方式以及类型推断 a,b = 1,2 // cc := 2 // 不可以省略 var ) func main () { // 多个变量的声明 var a,b,c,d int // 多个变量的赋值 a,d = 1,2,3,4 // 多个变量声明的同时赋值 var e,f,g,h int = 5,6,7,8 // 省略变量类型,由系统推断 var i,j,k,l = 9,10,11,12 // 多个变量声明与赋值的最简写法 i,m,n,o := 13,14,15,16 _,dd = 10,20 // 空白符号,省略该表达式赋值(应用函数返回值) }
类型转换
- Go中不存在隐式转换,都是显示声明(Go的类型安全)
- 转换只能发生在两种相互兼容的类型之间
-
类型转换的格式:
<ValueA> [:]= <TypeOfValueA>(<ValueB>)
// 在相互兼容的两种类型之间转换 var a float32 = 1.1 b := int(a) // 表达式无法通过编译 var c bool = true d := int(c)
package main import ( "fmt" ) func main () { var a float32 = 100.01 fmt.Println(a) // 100.01 b := int(a) fmt.Println(b) // 100 }
整型无法和布尔型兼容float
类型无法和字符串类型兼容
int
和string
互转
var c int = 3 // d := string(c) d := strconv.Itoa(c) // 字符串 3 c,_ = strconv.Atoi(d) // int 3
现象:
var a int = 65 string(a) fmt.Println(a) // A
string()
表示将数据转换成文本格式,因为计算机中存储的任何东西本质上都是数字,因此此函数自然的认为需要的是用数字65表示文本A
常量
常量的初始化规则与枚举
- 在定义常量组时,如果不提供初始值,则表示将使用上行的表达式
- 使用相同的表达式不代表具有相同的值
-
iota
是常量的计数器,从0开始,组中每定义1个常量自动递增1 - 通过初始化规则与
iota
可以达到枚举的效果 - 每遇到一个const关键字,
iota
就会重置为0
const ( _A = "A" _B _C = iota _D ) func main () { fmt.Println(_A,_B,_C,_D) // A A 2 3 }
const ( a = "123" b = len(a) c ) func main () { fmt.Println(a,c) // 123,3 }
编译不通过:
var ss = "123" const ( a = len(ss) b c ) func main () { fmt.Println(a,c) }
错误信息:
# command-line-arguments .\const.go:39: const initializer len(ss) is not a constant
const ( a,"xixi" c ) func main () { fmt.Println(a,c) }
错误信息:
# command-line-arguments .\const.go:40: extra expression in const declaration
运算符
Go中的运算符从左至右结合
优先级(从高到低)
-
^
!
(一元运算符) -
*
/
%
<<
>>
&
&^
(二元运算符) -
+
-
|
^
(二元运算符) -
==
!=
<
<=
>=
>
(二元运算符) -
<-
(专门用于channel) &&
||
fmt.Println(1 ^ 2) // 二元运算符 fmt.Println(^2) // 一元运算符 /* 6: 0110 11: 1101 ------------ & 0010 // 2 | 1111 // 15 ^ 1101 // 13 &^ 0100 // 4 6 -> 110 5 -> 101 4 -> 100 13 / 2 = 1 // 6 1101 */
控制语句
指针
Go虽然保留了指针,但与其他编程语言不同的是,在Go当中不支持指针运算以及->
运算符,而是直接采用.
选择符来操作指针目标对象的成员
- 操作符
&
取变量地址,使用*
通过指针间接访问目标对象 - 默认值为
nil
而非NULL
package main import ( "fmt" ) func main () { a := 1 var p *int = &a fmt.Println(p) // 0xc0420361d0 fmt.Println(*p) // 1 }
递增递减语句
在Go当中,++
与--
是作为语句而并不是作为表达式
判断if
func main () { a := 10 if a := 0; a > 0 { fmt.Println(a) } else if a == 0 { fmt.Println(0111) // 73 } fmt.Println(a) // 10 }
循环for
- Go只有for一个循环语句关键字,但支持3中形式
- 初始化和step表达式可以是多个值
- 条件语句每次循环都会重新检查,因此不建议在条件语句中使用函数,尽量提前计算好条件并以变量或常量代替
- 左大括号必须和条件语句在同一行
// 第一种形式 func main () { a := 1 for { a++ if a > 3 { break } fmt.Println(a) // 2,3 } fmt.Println(a) // 4 }
// 第二种形式 func main () { a := 1 for a <= 3 { a++ fmt.Println(a) // 2,4 } fmt.Println(a) // 4 }
// 第三种形式 func main () { a := 1 for i := 0; i < 3; i++ { a++ fmt.Println(a) // 2,4 } fmt.Println(a) // 4 }
swtich
- 可以使用任何类型或表达式作为条件语句
- 不需要break,一旦条件符合自动终止
- 如希望继续执行下一个case,需使用fallthrough语句
- 支持下一个初始化表达式(可以是并行方式),右侧需跟分号
- 做大括号必须和条件语句在同一行
func main () { a := 1 switch a { case 0: fmt.Println("a=0") case 1: fmt.Println("a=1") } fmt.Println(a) }
func main () { a := 1 switch { case a >= 0: fmt.Println("a>=0") fallthrough case a >= 1: fmt.Println("a>=1") } fmt.Println(a) }
func main () { switch a := 1; { case a >= 0: fmt.Println("a>=0") fallthrough case a >= 1: fmt.Println("a>=1") default: fmt.Println("none") } fmt.Println(a) // undefined: a //for,if,switch都具有块级作用域 }
跳转语句goto,break,continue
func main () { LABEL: for { for i := 0; i < 10; i++ { if i > 2 { break LABEL } else { fmt.Println(i) } } } }
func main () { LABEL: for i := 0; i < 10; i++ { for { fmt.Println(i) continue LABEL } } }
数组Array
- 定义数组的格式:var <varName> [n]<type>,n>=0
- 数组的长度也是类型的一部分,因此具有不同长度的数组为不同类型
- 数组在Go中为值类型
- 数组之间可以使用
==
或!=
进行比较,但不可以使用<
或>``(相同类型之间,才可以使用相等或不能判断。也就是数组长度也要相同,长度也是数组类型的一部分) - 可以使用new来创建数组,此方法返回一个指向数组的指针
- Go支持多维数组
创建数组
func main () { var a [2]string var b [1]int c := [2]int{11,12} d := [20]int{19: 1} e := [...]int{1,4,5} f := [...]int{0: 11,1: 22,2: 33} b[0] = 10 a[1] = "100" arr := [...]string{0: "xixi",1: "hhh"} fmt.Println(a,d,e,f) }
p := new([10]int) p[1] = 2 fmt.Println(&p) // 取地址 fmt.Println(*p) // 取值
// 多维数组 a := [2][3]int{ {1,1,1},{2,2},} fmt.Println(a)
冒泡排序:
func main () { a := [...]int{3,234,5} fmt.Println(a) num := len(a) for i := 0; i< num; i++ { for j := i+1; j < num; j++ { if a[i] < a[j] { temp := a[i] a[i] = a[j] a[j] = temp } } } fmt.Println(a) }
切片Slice
- 其本身并不是数组,它指向底层的数组
- 作为变长数组的代替方案,可以关联底层数组的局部或全部
- 为引用类型
- 可以直接创建或从底层数组获取生成
- 使用
len()
获取元素个数,cap()
获取容量 - 一般使用
make()
创建 - 如果多个
slice
指向相同底层数组,其中一个的值改变会影响全部 -
make([]T,len,cap)
其中`cap`可以省略,则和`len`的值相同 `len`表示存数的元素个数,`cap`表示容量
声明:
// 声明方法: var s1 []int // 中括号中没有数字或`...` fmt.Println(s1) // []
a := [...]int{1,5,8,9} fmt.Println(a) s1 := a[5: len(a)] // 包含起始索引,不包含终止索引 // a[5 6 7 8 9] s2 := a[5: ] // 包含起始索引,不包含终止索引 // a[5 6 7 8 9] fmt.Println(s1,s2)
make方法 (一般使用make创建)
s1 := make([]int,10) // 10小块连续的内存,如果slice超过10,内存卡会继续申请,重新生成内存地址 s2 := make([]int,10) // cap不给定,是slice的最大长度 fmt.Println(len(s1),cap(s1),s1) // 3 10 [0 0 0] fmt.Println(len(s2),cap(s2),s2) // 10 10 [0 0 0 0 0 0 0 0 0 0]
Reslice
-
reslice
时索引以被slice
的切片为准 - 索引不可以超过被
slice
的切片的容量cap()
值 - 索引越界不会导致底层数组的重新分配而引发错误
Append
- 可以在
slice
尾部追加元素 - 可以将一个
slice
追加在另一个slice
尾部 - 如果最终长度为超过追加到
slice
的容量则返回元素slice
- 如果超过追加到的
slice
的容量则将重新分配数组并拷贝原始数据
s1 := make([]int,6) fmt.Println("%p\n",s1) s1 = append(s1,4) fmt.Println("%v %p\n",s1) // [0 0 0 1 2 3 4]
Copy
nt{1,7} s2 := []int{8,9} copy(s1,s2) // copy(s1,s2[1: 2]) fmt.Println(s1,s2) // [8 9 3 4 5 6 7] [8 9]
Map
- 类似其它语言中的哈希表或者字典,以key-value形式存储的数据
- key必须是支持
==
或!=
比较运算符的类型,不可以是函数,map或slice - Map查找比线性搜索快很多,但比使用索引访问数据的类型慢100倍
- Map使用
make()
创建,支持:=
简写方式 - 键值对不存在时自动添加,使用
delete()
删除某键值对 - 使用
for range
对map
和slice
进行迭代操作 -
make([keyType]valueType,cap),cap表示容量,可省略
超过容量时会自动扩容,但尽量提供一个合理的初始值 使用`len()`获取元素个数
Map初始化
var m map[int]string // m = map[int]string{} m = make(map[int]string) var m1 map[int]string = make(map[int]string) m2 := make(map[int]string) fmt.Println(m,m1) // map[]
m2 := make(map[int]string) m2[1] = "OK" delete(m2,1) a := m2[1] fmt.Println(a)
迭代:
for i,v := range slice { // slice[i] } for k,v := range map { // map[k] }
取Map中的key
m := map[int]string{1: "A",2: "B",3: "C",4: "D"} s := make([]int,len(m)) i := 0 for k,_ := range m { s[i] = k i++ } sort.Ints(s) fmt.Println(s)
Map中的 key-value互换:
m1 := map[int]string{1: "A",3: "C"} m2 := make(map[string]int) // m2 := map[string]int{"A": 1,"B": 2,"C": 3} for k,v := range m1 { m2[v] = k } fmt.Println(m1) fmt.Println(m2)
Function
函数function
- 无需声明原型
- 不定长度变参
- 多返回值
- 命名返回值参数
- 匿名函数
- 闭包
定义函数使用关键字 func,且左大括号不能另起一行
函数也可以作为一种类型使用
闭包:
func closure (x int) func (int) int { return func (y int) int { return x + y } }
defer
-
defer
的执行方式类似其它语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行 - 即使函数发生严重错误也会执行
- 支持匿名函数的调用
- 常用于资源清理、文件关闭、解锁以及记录时间等操作
- 通过与匿名函数配合可在return之后修改函数计算结果
- 如果函数体内某个变量作为
defer
时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址 - Go 没有异常机制,但有
panic/recover
模式来处理错误 -
panic
可以在任何地方引发,但recover
只有在defer调用的函数中有效
defer使用:
func main () { fmt.Println("a") defer fmt.Println("b") defer fmt.Println("c") // a,b for i := 0; i < 3; i++ { defer fmt.Println(i) // 2 1 0 } for i := 0; i < 3; i++ { defer func () { fmt.Println(i) // 3 3 3 }() } }
defer
要放在panic()
之前:
func main () { A() B() C() } func A () { fmt.Println("func A") } func B () { defer func () { if err := recover(); err != nil { fmt.Println("Recover") } }() panic("Panic in B") } func C () { fmt.Println("func C") }
结构struct
- Go 中的
struct
与C中的struct
非常相似,并且Go没有class
- 使用
type <Name> struct{}
定义结构,名称遵循可见性规则 - 支持指向自身的指针类型成员
- 支持匿名结构,可用作成员或定义成员变量
- 匿名结构也可以用于
map
的值 - 可以使用字面值对结构进行初始化
- 允许直接通过指针来读写结构成员
- 相同类型的成员可进行直接拷贝赋值
- 支持
==
与!=
比较运算符,但不支持>
或<
- 支持匿名字段,本质上是定义了以某个类型名为名称的字段
- 嵌入结构作为匿名字段看起来像继承,但不是继承
- 可以使用匿名字段指针
type person struct { name string age int } func main () { a := person{} a.name = "zf" a.age = 23 fmt.Println(a) // { 0} }
struct
也是值类型
对初始化结构struct
使用地址符
type person struct { name string age int } func main () { a := &person{ // 调用结构使用地址符 // 字面值初始化 name: "zf",age: 24,} a.name = "pink" // a.name = "zf" // a.age = 23 fmt.Println(a) // { 0} // A(&a) A(a) B(a) fmt.Println(a) } func A (per *person) { per.age = 18 fmt.Println("A",per) } func B (per *person) { per.age = 20 fmt.Println("B",per) }
匿名结构:
func main () { a := &struct { name string age int } { name: "tan",age: 19,} fmt.Println(a) }
外层结构:
type person struct { name string age int contact struct { phone,city string } } func main () { b := person { name: "yellow",age: 18,} b.contact.phone = "123123" b.contact.city = "xiamen" fmt.Println(b) }
匿名字段:
type p1 struct { string int } func main () { c := p1{"cyan",20} // 字段的类型严格按照结构声明的字段 fmt.Println(a,c) }
匿名函数和匿名字段在函数中使用的次数非常少,没有必要声明,才会使用到。
嵌入(继承)结构:
type human struct { Sex int } type teacher struct { human name string age int } type student struct { human name string age int } func main () { // a := teacher{name: "cyan",age: 20,human{sex: 0}} a := teacher{name: "cyan",human: human{Sex: 0}} b := student{name: "pink",age: 22,human: human{Sex: 1}} a.name = "xixi" a.age = 23 // a.Sex = 100 a.human.Sex = 200 fmt.Println(a,b) }
方法method
- Go 中虽没有
class
,但依旧有method
- 通过显示说明
receiver
来实现与某个类型的组合 - 只能为同一个包中的类型定义方法
-
Receiver
可以是类型的值或者指针 - 不存在方法重载
- 可以使用值或指针来调用方法,编译器会自动完成转换
- 从某种意义上来说,方法是函数的语法糖,因为receiver其实就是
- 方法所接收的第1个参数(Method Value vs. Method Expression)
- 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
- 类型别名不会拥有底层类型所附带的方法
- 方法可以调用结构中的非公开字段
type Test struct { name string } type Person struct { name string } func main () { t := Test{} t.Print() fmt.Println(t.name) p := Person{} p.Print() fmt.Println(p.name) } func (t *Test) Print() { t.name = "red" fmt.Println("Test") } func (p Person) Print() { fmt.Println("Person") }
// 类型别名不会拥有底层类型所附带的方法 type TZ int func main () { var a TZ a.Print() (*TZ).Print(&a) } func (a *TZ) Print() { fmt.Println("TZ") }
type A struct { name string } func main () { a := A{} a.Print() // (*TZ).Print(&a) } func (a *A) Print() { a.name = "123" fmt.Println(a.name) // fmt.Println("TZ") }
// 属性的访问范围是在`package`中的可以访问的,如果需要在外部包中访问,需要大写字母 type A struct { name string } func main () { a := A{} a.Print() fmt.Println(a.name) } func (a *A) Print() { a.name = "123" fmt.Println(a.name) }