Go介绍
特点:
Go环境变量
- GOEXE=.exe // 形成可执行文件的后缀
- GOPATH // 工作目录
GOPATH下约定俗成的目录:
常用命令
GO程序结构
基础知识
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()
的方式进行简写
全局的变量的声明不可以省略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表示容量,可省略
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)
- }