Golang 学习笔记

  因为工作的关系要转而使用Go,对我来说这是一门新语言,只好临时抱一下佛脚。绝大部分内容来源于菜鸟教程

一、环境安装

1. Go语言安装

  下载地址:https://golang.org/dl/,包括Windows、Mac、Linux三种操作系统,默认安装位置是在C盘,如果自定义了,需要修改系统环境变量中的GOPATH以及Path中的相关值,让它保持跟自定义的路径一致。

2. 编译器

  常用的Go编译器有三种:JetBrains系列的Goland(付费)、轻量级开源的LiteIDE以及Eclipse插件goclipse,当然,也可以自行用文本编辑器编写.go后缀的文件,然后用命令行:go run xxx.go来运行,或者给sublime、vscode之类的安装相应的插件。我自己图方便就用了LiteIDE。

二、基础语法

1. Hello World

// test project main.go
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World!")
}

  第一行声明代码所在的包,包名和文件名、文件夹名没有必然联系,但同一文件夹内的包名必须一致。
  第二行导包,fmt是Go的内置包,实现了一些IO操作。
  剩余的就是声明了一个main函数,众所周知一般来讲,main函数是程序运行的入口,这里的代码也很简单,就是在控制台输出“Hello World!”的字符串。
  第零行(不是)是典型的单行注释,多行注释使用/* */夹击。
   Go中不允许"{"为单独一行

2. 数据类型

  Go语言的数据类型分得比较细,数量也比较多,总体来讲分成了四个大类:

  • 布尔型:bool,取值只有truefalse
  • 字符串:string,由单个字节连接起来,使用 UTF-8 编码标识 Unicode 文本,可用+号连接:"HelloWorld" = "Hello" + "World"
  • 派生类型:指针(ptsd要犯了)、数组、结构、Channel、函数、切片、接口、Map,有两三个之前没接触过的概念
  • 数字:这个类型最多了,选择哪个完全看内存需要
    整型:uint8、uint16、uint32、uint64、int8、int16、int32、int64
    浮点型:float32、float64、complex64、complex128(后两个分别指32位和64位的实数+虚数)
    其他:byte(类似uint8)、rune(类似int32)、uint(32或64位)、int(=uint)、uintptr(用来放指针)

3. 变量与常量

3.1 变量声明

  一般会使用var关键字。

指定类型
var name type
var name type = value

  变量声明时可以不指定初始值,此时将被设置为零值。其中数字类型的零值为0,布尔型为false,字符串为"",其他的大致上都是nil

不指定类型
var d = true // 自动判断为布尔型
省略var关键字

  这种情况下必须声明新的变量,而不是仅对已有变量赋值。有多个变量的情况下,其中至少要有一个变量是新声明的。这种形式只能使用在函数体中,不可用于全局变量。

name := value // 必须是新变量
name1, name2 := value1, value2 // 其中之一是新变量即可
多变量声明
var name1, name2 type
name1, name2 = value1, value2

var name1, name2 = value1, value2

// 只能在函数体中出现
name1, name2 := value1, value2

// 常用于全局变量
var(
    name1 type1
    name2 type2
)
3.2 值类型和引用类型
  • 值类型:int、float、bool、string,通过&i获取变量i的内存地址
  • 复杂数据用引用类型,变量只存储内存地址(即指针)或地址中第一个字所在的位置
3.3 变量的其他说明
  • 局部变量不允许只声明不使用(仅赋值也不行),也不允许未声明就使用
  • 全局变量允许只声明不使用
  • 同一类型变量可在同一行声明,不同类型变量可在同一行赋值
  • 交换变量值
a, b = b, a
  • 抛弃一个值
_, b = 5, 7
3.4 常量

  Go语言的常量只能是布尔、数字、字符串之一,程序运行中不能修改,声明方法和变量类似,但是要把var改为const

const name type = value
const name1, name2 = value1, value2
// 枚举
const (
    name1 = value1
    name2 = value2
    name3 = value3
)

  常量可以使用len()、cap()、unsafe.Sizeof()等内置函数。
  使用最后一种方式时,如果某常量只声明未赋值,其赋值表达式将和它的上一个常量一样。但如果这是第一个常量,编译出错。

3.5 itoa

  itoa是一个特殊常量,在const出现时重置为0,const中每新增一行常量声明使itoa加1。

const (
    a = iota   // a = 0
    b          // b = 1
    c          // c = 2
)

const (
    a = iota   // a = 0
    b          // b = 1
    c = "ha"   // c = "ha"
    d          // d = "ha"
    e = 100    // e = 100
    f          // f = 100
    g = iota   // g = 6
    h          // h = 7
)

const (
    a = 1 << iota  // a = 1
    b = 3 << iota  // b = 6
    c              // c = 12
    d              // d = 24
)

4. 运算符

  二元操作符需要二者类型相同。

4.1 算术运算符

  +、-、*、/、%、++、--,其中++、--略有限制,不能用在赋值语句中。

var a = 10
a++              // a = 11
++a              // 编译出错
var b = a++      // 编译出错
fmt.Print(a++)   // 编译出错
4.2 关系运算符

  ==、!=、>、<、>=、<=。

4.3 逻辑运算符

  &&、||、!。

4.4 位运算符

  &、|、^、<<、>>,没有无符号位移<<<和>>>。
  &^:按位置零,z = x &^ y,y = 0则z = x;y != 0则z = 0。
  ^也可作单目运算符,表示按位取反。

3 &^ 0 = 3
3 &^ 1 = 2
3 &^ 2 = 1

var a int8 = 3
^a = -4
var a uint8 = 3
^a = 252

a &^ b = a & (^b)
4.5 赋值运算符

  =、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=。

4.6 其他运算符
  • &:取地址,&a为返回变量a的实际地址
  • *:指针变量
var a int = 4
var ptr *int
ptr = &a
fmt.Printf(a)     // a = 4
fmt.Printf(*ptr)  // *ptr = 4
fmt.Println(ptr)  // ptr = a的物理地址 
4.7 运算符优先级

  没有括号的情况下,优先级由高到低:

  • *、/、%、<<、>>、&、&^
  • +、-、|、^
  • ==、!=、<、<=、>、>=
  • &&
  • ||

5. 条件语句

  Go中没有三目运算符。

if 布尔表达式 {
}

if 布尔表达式 {
} else {
}

if 布尔表达式 {
} else if 布尔表达式 {
} ... else {
} 

switch var1 {         // case后默认自带break语句
    case value1:                     
    case value2, value3: 
          xxxx
          fallthrough  // 强制执行下一条case语句
    ...
    default:
}

switch x.(type) {
    case type1:                     
    case type2, type3:
    ...
    default:
}

  select语句类似于switch,每个case必须是一个通信操作(发送或接受)。select随机执行一个可运行的case,若无则将阻塞。

select {
    case communication clause1  :
       statement(s);      
    case communication clause2  :
       statement(s);
    ...
    default : 
       statement(s);
}

6. 循环语句

for i := 0; i <= 10; i++ {
}
for [true] {   // 无限循环
}
strings := []string{"google", "runoob"}
for i, s := range strings {   // 对数组的foreach循环
}

  循环控制语句包括break、continue和goto(跳至某个标签),前二者除了普通用法外,还可使用标签。

re:
        for i := 1; i <= 3; i++ {
            fmt.Printf("i: %d\n", i)
            for i2 := 11; i2 <= 13; i2++ {
                fmt.Printf("i2: %d\n", i2)
                break re
            }
        }
// 输出:
// i: 1
// i2: 11

re:
        for i := 1; i <= 3; i++ {
            fmt.Printf("i: %d\n", i)
                for i2 := 11; i2 <= 13; i2++ {
                    fmt.Printf("i2: %d\n", i2)
                    continue re
                }
        }
// 输出
// i: 1
// i2: 11
// i: 2
// i2: 11
// i: 3
// i2: 11

三、代码块

1. 函数

  函数定义,函数可以返回多个值。

func functionName( [parameter list] ) [returnType] {
}
func functionName( [parameter list] ) (returnType list) {
}

  函数可以作为另外一个函数的实参,将函数声明为函数变量。

/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
   return math.Sqrt(x)
}

/* 使用函数 */
fmt.Println(getSquareRoot(9))

  函数闭包,匿名函数,可以直接使用外函数内的变量。

func getSequence() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}
func main() {
    /* nextNumber 为一个函数,函数 i 为 0 */
    nextNumber := getSequence()
    /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
    fmt.Println(nextNumber())
    fmt.Println(nextNumber())
    fmt.Println(nextNumber())
}
// 输出结果
// 1
// 2
// 3

// 带参数的闭包
func add(x1, x2 int) func(x3 int,x4 int)(int,int,int)  {
    i := 0
    return func(x3 int,x4 int) (int,int,int){ 
       i++
       return i,x1+x2,x3+x4
    }
}
func main() {
    add_func := add(1,2)
    fmt.Println(add_func(1,1))
    fmt.Println(add_func(0,0))
    fmt.Println(add_func(2,2))
} 
// 输出结果
// 1 3 2
// 2 3 0
// 3 3 4

2. 方法

  类似于函数,但会绑定一个接受者。接受者可以使命名类型或结构体类型,相当于Java中的实例方法。但若要改变接受者中某部分的内容,需要传递指针。

// 声明格式
func (name type) functionName([parameter list]) [returnType] {
}

/* 定义结构体 */
type Circle struct {
  radius float64
}
// 该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  return 3.14 * c.radius * c.radius
}
// 方法的调用
func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

3. 变量作用域

  • 局部变量:函数体内声明,作用域也在函数体内。包括参数和返回值。
  • 形式参数:作为函数的局部变量
  • 全局变量:函数体外声明,可在整个包内或导出后在包外被使用。局部变量和全局变量名称相同时,局部变量会被优先考虑。

四、数据结构

1. 数组

var arrName [size] type             // 仅声明
var arr = [5]int32{1, 2, 3, 4, 5}   // 初始化,长度等于初始元素
var arr = [6]int32{1, 2, 3, 4, 5}   // 初始化,长度大于初始元素,剩余位置置为0;长度不能小于初始元素
var arr = [...]int32{1, 2, 3, 4, 5} // 初始化,长度不指定,而是根据初始化值计算

  多维数组

var arrName [size1][size2]...[sizeN] type
var arr = [2][3] int{
   {1,2,3},
   {4,5,6},
}
var arr = [2][3] int{
   {1,2,3},
   {4,5,6}}

  向函数传递数组,必须制定数组长度。此时传递的是数组的拷贝,而不是指针

func functionName(param [10]int) int { // 指定长度,此时传入的数组不能是其他长度
}

2.指针

// 获取变量内存地址
var a int = 1
fmt.Println(&a)
// 声明指针
var p *int
p = &a
// 用指针访问值
fmt.Println(*p)

  指针定义后未分配变量,为空指针,值为nil。
  指针也可以作为数组元素。

var arr []int = {1,2,3}
var pArr [3]*int
var i int
for i = 0; i < 3; i++ {
    pArr[i] = &arr[i]
}

  指针的指针(可以套娃到至少3层)

var a int = 1
var p *int = &a
var pp **int = &p
fmt.Println(a)
fmt.Println(*p)
fmt.Println(**pp)

  指针作为函数参数,改变其所指向的变量的值

func swap(x *int, y *int) {
   *x, *y = *y, *x
}

3.结构体

type Person struct {
    name string
    age int
}
var structVar1 Person = Person{"zhangsan", 20}
var structVar2 Person = Person{name: "zhangsan", age: 20}
var structVar3 Person = Person{name: "zhangsan"}
structVar1.age = 21
fmt.Println(structVar.age)
var sp *Person = &structVar1
sp.age = 22
fmt.Println(sp.age)

  结构体作为函数参数时是值传递,若要改变成员的值,需要传递指针。

4.切片

  数组的部分引用,长度可变。作为参数传递时是引用传递。
  切片的初始化,既可以是数组,也可以是切片

// a为已存在数组,s为已存在切片
var s1 []int = a[startIndex : endIndex] // s[startIndex : endIndex]
var s1 []int = make([]int, len, cap)

  其中startIndex,endIndex,cap均可省略。
  len()和cap()方法可用于计算切片的长度和容量。切片只定义未初始化时为空切片,值为nil,长度为0。可以用append()追加切片内容,可同时追加多个元素,若长度超过容量,将自动扩容。可以用copy()拷贝切片内容,只拷贝和俩切片较小长度相同数量的内容。
  如果切片来自于数组,对切片进行修改时,相应的数组元素也会被修改。

5.范围(Range关键字)

  用于迭代,可用在数组、切片、集合、字符串等类型上,返回索引和对应值,或kv。

// 求和
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
    sum += num
}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
    fmt.Printf("%s -> %s\n", k, v)
}
for i, c := range "go" {
    fmt.Println(i, c)    // 输出unicode值而不是字符
}
// 遍历命令行参数
for i, s := os.Args {
}

6.集合(Map)

var mapName map[keyType]valueType = make(map[keyType]valueType)
// 插入键值对
mapName["key1"] = "value1"
// 根据key读取value
value, exist := mapName["key"]  // exist为布尔变量
// 删除一组kv
delete(mapName, "key1")

五、实用

1.数据类型转换

var sum int = 17
var count int = 5
var mean float32
mean = float32(sum) / float32(count)

2.接口

type Animal interface {
    call(nums int) bool
}

type Cat struct {
    name string
    voice string
}

// 接口方法的实现函数有特殊写法
func (cat Cat) call(nums int) bool {
    fmt.Print(cat.name + "叫:")
    for i := 0; i < nums; i++ {
        fmt.Print(cat.voice)
        if i < nums - 1 {
            fmt.Print(", ")
        }
    }
    fmt.Println()
    return true
}

var cat Animal = Cat{"Hua", "Miao"}
ok := cat.call(2)
fmt.Println(ok)
cat2 := new(Cat)
cat2.name = "Shabi"
cat2.voice = "Aba"
cat2.call(3)
3.错误处理

  利用内置的error接口。

// 返回错误信息
func sqrt(f float) (float, error) {
    if f < 0:
        return -1, errors.New("Input a negative number")
    ...
}
// 
panic、defer和recover

  内置函数,用于异常处理。panic用于抛出错误,会终止其后面代码的运行。若panic函数中存在defer列表,会逆序执行。recover可用来获取panic传出的错误信息,执行一些应对措施。以下代码来源于这里

func test() {
    fmt.Println("c")
    defer func() { // 必须要先声明defer,否则不能捕获到panic异常
        fmt.Println("d")
        if err := recover(); err != nil {
            fmt.Println(err) // 这里的err其实就是panic传入的内容
        }
        fmt.Println("e")
    }()
    f() //开始调用f
    fmt.Println("f") //这里开始下面代码不会再执行
}

func f() {
    fmt.Println("a")
    panic("异常信息")
    fmt.Println("b") //这里开始下面代码不会再执行
}

func f1() {
    defer func(){
        if err := recover() ; err != nil {
            fmt.Println(err)
        }
    }()
    defer func(){
        panic("three")
    }()
    defer func(){
        panic("two")
    }()
    panic("one")
}

  执行 test,先打印“c”,调用f,打印“a”,随后引发panic,进入defer,打印“d”和携带的错误信息“异常信息”,最后打印“e”,结束test函数。
  执行f1,panic逆序触发另两个panic,但recover只捕获最后一次panic,因此只打印“three”。
  函数结束时,即便未触发panic,也会逆序处理函数内声明的defer。

4.并发

  使用go关键字开启协程(轻量级线程),主协程结束后,所有协程都会结束。

go f(x, y, z) 

  使用管道(Channel)在协程之间通信,管道相当于线程安全的FIFO队列。

ch := make(chan int32, 5)  // 也可以make(chan int32)表示不带缓冲区
ch <- 1
ch <- 2
num := <-ch
fmt.Println(num) // 1
num = <-ch
fmt.Println(num) // 2
fmt.Println(len(ch)) // 0
close(ch)

  无缓冲区的管道,发送方会阻塞直到接收方获取值,同步。若有缓冲区,接收方会阻塞直到管道中有值,异步。
  管道关闭后数据不会丢失,但使用range遍历时将不会阻塞等待新数据进入,同时写入也会报错。

// 权限控制
go func(c chan int) { //读写均可的channel c } (a)
go func(c <- chan int) { //只读的Channel } (a)
go func(c chan <- int) {  //只写的Channel } (a)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342