go语言语法入门

1.源文件

  • 源码采用UTF-8编码,对Unicode支持良好
  • 在文件头部用package声明所属包的名称
  • 左大括号{必须和函数声明或者控制结构放在同一行
  • go语句最后的分号可省,出现分号的典型位置是for循环或类似之处

2.变量

声明单个变量

  • var name type是声明单个变量的语法
  • 如果变量未被赋值,Go 会自动地将其初始化,赋值该变量类型的零值(Zero Value)
  • 这是对C语言的优化
  • 如C语言
    int* a,b; a是指针,b不是,如果两者都是指针,必须要在b前重复一个*
  • 而go语言
    var a,b *int a,b都是指针

声明变量并初始化

  • var name type = initialvalue 的语法用于声明变量并初始化。

支持类型推断

  • var name = initialvalue根据初始化值推断类型

声明多个变量

  • var name1, name2 type = initialvalue1, initialvalue2
  • 不同类型的变量
var (  
        name1 = initialvalue1
        name2 = initialvalue2
        name3 type
      )

简短声明

  • name:=initialvalue
  • 要求1::=操作符左边的所有变量都有初始值
  • name,age:="kyl"就会报错,因为age没有赋值
  • 要求2::=操作符左边至少有一个变量是未声明的

强类型

  • 不允许某一类型的变量赋值为其他类型的值

定义变量后要使用,否则会报错

var a int//声明单个变量
    var b int=10//声明变量并初始化
    var c="hello"//类型推断
    d:=1.5//简短声明

3.if语句

  • go 中的条件表达式不需要像C语言括起来
  • if 和else 后面的大括号不可省略,即使只有一条语句
  • else 不可单独成行,必须要跟在if的右括号的后面,else if同理
  • if 和条件间支持定义一个变量,但是变量只在条件块中有效
if a > b {
     c = a
    } else {
        c = b
    }
    if d := 50; d > 60 {
        fmt.Println("pass")
    } else {
        fmt.Println("fail")
    }

4.输入语句

  • 使用fmt.Scanln()获取输入/使用fmt.Scanf()获取输入
var name string
    var age int
    var num int
    //使用fmt.Scanln()
    fmt.Println("input your name")
    fmt.Scanln(&name) //莫忘取地址符号
    fmt.Println("input your age")
    fmt.Scanln(&age)
    //使用fmt.Scanf()
    fmt.Println("input your num")
    fmt.Scanf("%d", &num)
    fmt.Printf("名字是%v,年龄是%v,序号是%d\n", name, age, num)

5.switch分支结构

  • case 后面可以带多个表达式,用分号隔开
  • case 语句块不需要写break , 因为默认会有,即在默认情况下,当程序执行完case 语句块后,就直接退出该switch 控制结构。
  • case/switch 后是一个表达式( 即:常量值、变量、一个有返回值的函数等都可以)
  • case 后的各个表达式的值的数据类型,必须和switch 的表达式数据类型一致
  • case 后面的表达式如果是常量值(字面量),则要求不能重复
  • switch 后也可以不带表达式,类似if --else 分支来使用
  • 如果在case 语句块后增加fallthrough, 则会继续执行下一个case,而不去判断下一个case的表达式是否为true,也叫switch 穿透
var month int
    fmt.Println("input month")
    fmt.Scanln(&month)
    switch month {
    case 3, 4, 5:
        fmt.Println("spring")
    case 6, 7, 8:
        fmt.Println("summer")
    case 9, 10, 11:
        fmt.Println("autumn")
    case 12, 1, 2:
        fmt.Println("winter")
    default:
        fmt.Println("input error!")
    }

input month
12
winter

input month
13
input error!

input month
5
spring

//switch后可以不带表达式,类似if-else来使用
    var age int
    fmt.Println("input age")
    fmt.Scanln(&age)
    switch {
    case age == 10:
        fmt.Println("age==10")
    case age == 20:
        fmt.Println("age==20")
    default:
        fmt.Println("no match")
    }

    //对范围的选择
    var score int
    fmt.Println("input score")
    fmt.Scanln(&score)
    switch {
    case score >= 90://case后也可以对范围进行判断
        fmt.Println("great")
    case score >= 80:
        fmt.Println("good")
    case score >= 60:
        fmt.Println("ok")
    default:
        fmt.Println("bad")
    }

    switch time.Now().Weekday() {//需要使用time包
    case time.Saturday, time.Sunday:
        fmt.Println("it's the weekend")
    default:
        fmt.Println("it's a weekday")
    }

    switch t := time.Now();true{//tru如果省略,默认为true;但‘;’不能少
    case t.Hour() < 12:
        fmt.Println("it's before noon")
    default:
        fmt.Println("it's after noon")
    }

    //fallthrough
    var num int
    switch num {
    case 1:
        fmt.Println("ok1")
        fallthrough //默认只能穿透一层
    case 2:
        fmt.Println("ok2")
        fallthrough
    case 3:
        fmt.Println("ok3")
    default:
        fmt.Println("no match")
    }

6.循环

  • 方式一:(与c相同)
  • for循环变量初始化; 循环条件; 循环变量迭代{
    循环操作(语句)
    }
  • 方式二:(相当于c中的while
  • for 循环判断条件{
    //循环执行语句
    }
    将变量初始化和变量迭代写到其它位置
  • 方式三:(需要配合break使用)
  • for {
    //循环执行语句
    }
package main

    import(
        "fmt"
        "time"
        "math/rand"
    )
    func main(){
        count:=0
        for{
            rand.Seed(time.Now().UnixNano())
            n:=rand.Intn(100)+1
            //fmt.Println("n=",n)
            count++
            if(n==50){//莫忘大括号
                break
            }
        }
        fmt.Println("生成50需要的次数:",count)
    }
package main

    import (
        "fmt"
    )
    //打印九九乘法表
    func main(){
        var num  int=9
        for i:=1;i<=num;i++{
            for j:=1;j<=i;j++{
                fmt.Printf("%v * %v =%v \t",j,i,j*i)
            }
            fmt.Println()
        }	
    }
package main

    import (
        "fmt"
    )

    func main(){
        str:="a song 雪花飘飘,北风啸啸"
        for i,v:=range str{
            fmt.Printf("%d %c\n",i,v)
            //可以看出utf-8中汉字占3个字节
        }
    }

7.函数

多返回值

  • func 函数名(形参列表)(返回值列表){
    执行语句…
    return 返回值列表
    }
package main

    import (
        "fmt"
    )

    func z(x,y int)(int,int){
        s1,s2:=x,y
        for t:=x%y;t!=0;t=x%y{
            x=y
            y=t
        }
        return y,s1*s2/y
    }

    func main(){
        fmt.Println(z(16,24))
    }

8 48

  • 给每个返回值命名
  • 就像形参一样,将每个返回值具体到变量上,这样就可以对返回的变量赋值。
package main

    import (
        "fmt"
    )

    //返回两个数的和与差
    func ans(a, b int) (int, int) {
        return a + b, a - b
    }
    //给返回值命名
    func ans1(a,b int)(mul,div ,rem int){
        mul=a*b
        div=a/b
        rem=a%b
        return
    }
    func main() {
        a, b := 5, 8
        fmt.Println(ans(a, b))
        fmt.Println(ans1(a,b))
    }

13 -3
40 0 5

  • 多返回值模型促进了”comma-ok”模式
package main

    import (
        "fmt"
    )

    func main(){
        arr:=[3]int{5,6,7}
        if v,ok:=get(2,arr);ok{
            fmt.Println(v)
        }
    }
    func get(i int,arr [3]int)(int,bool){
        if i<0||i>len(arr){
            return 0,false
        }
        return arr[i],true
    }

7

匿名函数

  • 匿名函数不能作为顶级函数,而只能放在其他函数的函数体中,也就是说它必须有一个外层函数
  • 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
  • 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
package main

    import (
        "fmt"
    )

    func main(){
        fmt.Println(func(a,b int) int{
            return a+b
        }(15,16))//声明匿名函数时调用

        f1:=func(a,b int) int{
            return a+b
        }//声明一个匿名函数,并直接赋给函数类型变量f
        fmt.Println(f1(1,6))

        f:=func(a,b int) int{
            return a+b
        }(1,16)//将匿名函数返回值赋给变量
        fmt.Println(f)
    }

31
7
17

闭包

  • 匿名函数被称为闭包,原因在于匿名函数通过某种方式使其可见范围超出了其定义的范围。
  • go为声明匿名函数提供了简单的语法,像许多动态语言一样,这些匿名函数在它们被定义的范围内创建了词法闭包
  • 函数返回一个匿名函数,而匿名函数使用了自身的外部变量,匿名函数与这个变量共同形成闭包
实例一
package main

    import (
        "fmt"
        "strings"
    )
    //传入文件名,无后缀名的添加后缀名
    func main() {
        f2 := makeSuffix(".jpg")
        fmt.Println("处理后=", f2("winter"))
        fmt.Println("处理后=", f2("summer.jpg"))

    }
    func makeSuffix(suffix string) func(string) string {
        return func(name string) string {
            if !strings.HasSuffix(name, suffix) {
                return name + suffix
            }
            return name
        }
    }
    //与传统方式的不同在于,传统方法需要每次都传入后缀名
    //而使用闭包返回的函数保留着后缀名,可以反复使用

处理后= winter.jpg
处理后= summer.jpg

实例二
package main

    import (
        "fmt"
    )

    func main() {
        f := squares()
        fmt.Println(f()) // "1"
        fmt.Println(f()) // "4"
        fmt.Println(f()) // "9"
        fmt.Println(f()) // "16"
    }

    // squares返回一个匿名函数。
    // 该匿名函数每次被调用时都会返回下一个数的平方。
    func squares() func() int {
        var x int
        return func() int {
            x++
            return x * x
        }
    }

1
4
9
16

实例三
package main

    import "fmt"

    func main() {
        add5 := makeAdder(5)//5+y
        add36 := makeAdder(36)//36+y
        fmt.Println(add5(add36(1)))
        sub5 := makeSuber(5)//5-y
        sub36 := makeSuber(36)//36-y
        fmt.Println(sub5(sub36(1)))
    }
    func makeAdder(x int) func(int) int {
        return func(y int) int { return x + y }
        //引用了外部变量X
    }
    func makeSuber(x int) func(int) int {
        return func(y int) int { return x - y }
        //引用了外部变量x,此x与makeAdder的x不同
    }

42
-30

回调函数

  • 函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调
实例
package main

    import (
        "fmt"
    )

    func main(){
        callback(1,add)
        callback(1,sub)
        callback(5,mul)
        callback(5,div)
    }
    func add(a, b int) {
        fmt.Println(a, "+", b, "=", a+b)
    }
    func sub(a, b int) {
        fmt.Println(a, "-", b, "=", a-b)
    }
    func mul(a, b int) {
        fmt.Println(a, "*", b, "=", a*b)
    }
    func div(a, b int) {
        fmt.Println(a, "/", b, "=", a/b, "...", a%b)
    }
    func callback(y int, f func(int, int)) {
        f(y, 2)
    }

1 + 2 = 3
1 - 2 = -1
5 * 2 = 10
5 / 2 = 2 … 1

defer语句

  • 延迟调用语句,无论函数执行是否出错,都确保结束前被调用
  • 用于数据清理工作,保证代码结构清晰,避免遗忘
srcFile,err:=os.Open(srcName)
defer srcFile.Close()//关闭文件句柄,结构清晰,避免忘记关闭
实例
package main

    import (
        "fmt"
    )

    func main(){
        defer fmt.Println("雪花飘飘")
        defer fmt.Println("北风啸啸")
        fmt.Println("天地一片苍茫")
    }

天地一片苍茫
北风啸啸
雪花飘飘

8.异常处理机制

  • 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
  • 内置函数panic,中断原有控制流,抛出一个异常流
  • 内置函数recover,捕获到panic的异常值并恢复
  • recover仅在延迟函数中有效,即在defer语句中调用

实例

package main

    import (
        "fmt"
    )

    func main(){
        test01()
        fmt.Println("test01")
        test02()
        fmt.Println("test02")//没有输出
    }
    func test01(){
        defer func(){
             if err:=recover(); err !=nil{
                fmt.Println("err=",err)
            }
        }()//声明并调用
        num1:=10
        num2:=0
        res:=num1/num2
        fmt.Println("res=",res)
    }
    func test02(){
        num1:=10
        num2:=0
        res:=num1/num2
        fmt.Println("res=",res)//没有输出
    }

err= runtime error: integer divide by zero
test01
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.test02()
e:/file/golang/goproject/src/异常处理机制/main.go:27 +0x11
main.main()
e:/file/golang/goproject/src/异常处理机制/main.go:10 +0x8a
exit status 2

9.数组

定义、初始化

package main

    import (
        "fmt"
    )

    func main() {
        //数组
        var a [4]int                        //元素自动初始化为零
        var aa [4]int = [4]int{1, 2, 3, 4}  //定义并初始化数组aa
        var aaa [4]int = [4]int{1, 2, 3, 4} //定义并初始化数组aa
        b := [4]int{2, 5}                   //未提供初始值的元素自动初始化为零
        c := [4]int{5, 3: 10}               //可指定索引位置初始化
        d := [...]int{1, 2, 3}              //编译器按初始化值数量确定数组长度
        e := [...]int{10, 3: 100}           //支持索引初始化,但数组长度与此有关
        f := [...]int{
            5: 200,
            9: 10, 
            //这里需要汪意:如果分成多进行声明+初始化,
            //那么你需要在最后一行的最后加上','
        }
        fmt.Println(a) //可直接输出素组各个元素,比C方便
        fmt.Println(aa, aaa, b, c, d, e, f)
        //…表示不指定长度,长度由初始化列表决定,这种情况…不能省略,否则就成了切片
        //
    }

结构体数组

数组元素为结构体

实例
package main

import (
	"fmt"
)
type user struct{
	id   int
	name string
	age byte
}
func main(){
	brother :=[...]user{
		{1,"刘备",20},
		{2,"关羽",19},
		{3,"张飞",18},
	}
	fmt.Println(brother)
	fmt.Printf("%v\n%+v\n%#v",brother,brother,brother)
}

[{1 刘备 20} {2 关羽 19} {3 张飞 18}]
[{1 刘备 20} {2 关羽 19} {3 张飞 18}]
[{id:1 name:刘备 age:20} {id:2 name:关羽 age:19} {id:3 name:张飞 age:18}]
[3]main.user{main.user{id:1, name:“刘备”, age:0x14}, main.user{id:2, name:“关羽”, age:0x13}, main.user{id:3, name:“张飞”, age:0x12}}

注:%v%+v%#v区别

1.%v 只输出所有的值

2.%+v 先输出字段类型,再输出该字段的值

3.%#v 先输出结构体名字值,再输出结构体(字段类型+字段的值)

多维数组

  • …仅用在第一维
  • 内置函数len和cap都可以返回第一维的长度
package main

    import "fmt"

    func main() {
        a := [2][2]int{{1, 2}, {3, 4}}
        b := [...][2]int{//...仅用在第一维
            {10, 20},
            3: {30, 40},
        }
        c := [...][2][2]int{
            {
                {1, 2},
                {3, 4},
            },
            {
                {10, 20},
                {30, 40},
            },//莫忘逗号
        }
        fmt.Printf("%v\n%v\n%v", a, b, c)
        //内置函数len和cap都可以返回第一维的长度
    }

[[1 2] [3 4]]
[[10 20] [0 0] [0 0] [30 40]]
[[[1 2] [3 4]] [[10 20] [30 40]]]

数组和指针

  • 数组名字不代表数组的首地址
package main

    import (
        "fmt"
    )

    func main() {
        x, y := 10, 20
        a := [...]*int{&x, &y}
        q := a
        p := &a
        fmt.Println(a, p, q)
        fmt.Printf("%T,%v\n", a, a)
        fmt.Printf("%T,%v\n", q, q)
        fmt.Printf("%p %p %p\n", &a, &a[0], &q[0])
        fmt.Printf("%v %v %v\n", &a, &a[0], &q[0])
        fmt.Printf("%T,%p,%v,%v,%p\n", p, p, p, *p, &p)
    }

[0xc0000100a0 0xc0000100a8] &[0xc0000100a0 0xc0000100a8] [0xc0000100a0 0xc0000100a8]

[[2]*int,[0xc0000100a0 0xc0000100a8]

[[2]*int,[0xc0000100a0 0xc0000100a8]

0xc00003a1f0 0xc00003a1f0 0xc00003a200

&[0xc0000100a0 0xc0000100a8] 0xc00003a1f0 0xc00003a200

*[2]*int,0xc00003a1f0,&[0xc0000100a0 0xc0000100a8],[0xc0000100a0 0xc0000100a8],0xc000006028

数组和函数

package main

    import (
        "fmt"
    )

    func test(x [2]int){
        fmt.Printf("x:%p,%v\n",&x,x)
    }
    func main(){
        a:=[2]int {10,20}
        var b [2]int
        b=a//数组整体复制
        fmt.Printf("a:%p,%v\n",&a,a)
        fmt.Printf("b:%p,%v\n",&b,b)
        test(a)
        //&x!=&a,说明数组名不代表首地址,
        //赋值 和 传参 只复制全部元素
        //形参变化不影响实参
    }

a:0xc0000100a0,[10 20]
b:0xc0000100b0,[10 20]
x:0xc000010110,[10 20]

数组、指针和函数

package main

    import (
        "fmt"
    )
    func test(x *[2]int){
        x[1]+=100
        fmt.Printf("x:%p,%v\n",x,*x)
    }
    func main(){
        a:=[2]int{10,20}
        test(&a)
        fmt.Printf("a:%p,%v\n",&a,a)
    }

x:0xc0000100a0,[10 120]
a:0xc0000100a0,[10 120]

  • 区分指针数组与数组指针
  • 数组指针:var x *[2]int,x是指向数组的指针,即指向数组的首地址
  • 指针数组:var y [2]*int,y是指针数组,即数组中每个元素都是指针

10.切片

切片基础

  • [ ]表示切片
  • 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  • 切片是一个动态数组
  • make函数用来创建指定类型的对象,var 切片名[]type = make([]type, len, [cap])
  • make的方式实际上也会创建一个数组,但是不可见
  • cap()获取切片空间的大小
  • len()获得有效元素的个数
  • append()在切片有效元素末尾添加元素;如果超出容量限制,容量翻倍;这时,要把新地址赋给原切片
  • append的原型为:append([]interface{}, …interface{})
  • 功能:表示可以为任何类型([]interface{})的切片追加0-n个元素(…interface{}),
  • 如m.musics[index+1:]…含义就是把切片m.musics[index+1:]打散成一个一个的可追加的参数
package main

    import (
        "fmt"
    )

    func main(){
        x:=make([]int,0,5)//创建长度为0,容量为5的切片
        for i:=0;i<8;i++{
            x=append(x,i)//在切片的尾部添加数据,莫忘赋给x
        }	
        fmt.Println(x,len(x),cap(x))
        //如若超出容量限制时,容量自动翻倍
    }

[0 1 2 3 4 5 6 7] 8 10

//数组中元素 有效长度 容量

切片和数组

  • 定义一个切片,然后让切片去引用一个已经创建好的数组,容量为数组的长度减去引用的元素的起始下标
  • 切片初始化时var slice = arr[startIndex:endIndex] 说明:从arr 数组下标为startIndex,取到下标为endIndex 的元素(不含arr[endIndex])。
  • var slice = arr[0:end] 可以简写var slice = arr[:end]
    var slice = arr[start:len(arr)] 可以简写: var slice = arr[start:]
    var slice = arr[0:len(arr)] 可以简写: var slice = arr[:]
package main

    import (
        "fmt"
    )

    func main(){
        x:=[...]int{0,1,2,3,4,5,6,7,8,9}
        s1:=x[2:5]//s1包含2、3、4(不包含5)
        fmt.Println(s1)
        s2:=x[:6]//即x[0:6]
        fmt.Println(s2)
        s3:=x[4:]//即x[4:len(x)]
        fmt.Println(s3)
        s4:=x[:]//即x[0:len(x)]
        fmt.Println(s4)
        //fmt.Println(s1[5])
        for i,v:=range s1{//遍历
            fmt.Printf("%v:%v\n",i,v)
        }
        fmt.Println("len",len(s1),"cap",cap(s1))

        //利用切片可以修改在数组中对应的那部分元素的值
        s1[0]=5
        //但是切片只是指向数组的子集,修改不能超出切片范围
        //如s1[5]就不合法

        s1=append(s1,15,16)
        //但是可以利用通过给切片增加元素的方式更改值
        fmt.Println(s1)

    }

[2 3 4]
[0 1 2 3 4 5]
[4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
0:2
1:3
2:4
len 3 cap 8
[5 3 4 15 16]

切片拷贝

  • 切片可以进行值的拷贝
  • 切片之间可以传递
package main

    import (
        "fmt"
    )

    func main(){
        //切片是引用类型,可传递
        arr:=[...]int{1,2,3,4,5}
        slice0:=arr[:]
        slice1:=slice0
        slice0[0]=10
        fmt.Println("arr",arr)//arr [10 2 3 4 5]
        fmt.Println("slice0",slice0)//slice [10 2 3 4 5]
        fmt.Println("slice1",slice1)//slice1 [10 2 3 4 5]
        //切片和数组的值均改变

        //对切片拷贝
        var slice2[]int=[]int{1,2,3,4,5}
        var slice3=make([]int,10)//省略容量
        copy(slice3,slice2)
        slice3[0]=5
        fmt.Println("slice2",slice2)//slice2 [1 2 3 4 5]
        fmt.Println("slice3",slice3)//slice3 [5 2 3 4 5 0 0 0 0 0]
        //可见,两个数据空间是独立的,相互不影响

        var slice4[]int=[]int{1,2,3,4,5}
        var slice5[]int=make([]int,1)
        fmt.Println("slice5",slice5)//[0]
        copy(slice5,slice4)
        fmt.Println("slice5",slice5)//[1]

        a:=[...]int{5,6,7,8,9}
        var slice6[]int=a[2:]
        var slice7=make([]int,5)
        copy(slice7,slice6)
        slice7[1]=9
        fmt.Println("slice6",slice6)
        fmt.Println("slcie7",slice7)
    }

arr [10 2 3 4 5]
slice0 [10 2 3 4 5]
slice1 [10 2 3 4 5]
slice2 [1 2 3 4 5]
slice3 [5 2 3 4 5 0 0 0 0 0]
slice5 [0]
slice5 [1]
slice6 [7 8 9]
slcie7 [7 9 9 0 0]

11.map

  • var map 变量名 map[keytype]valuetype
  • 声明不会分配内存,初始化需要make,分配内存后才能赋值和使用
  • 可以声明的时候用{}初始化
  • map在使用前一定要make
  • make的key-value是无序的,所以每次遍历结果先后关系可能不一样
  • make的value可以相同
  • make的key不能相同,如果重复了,则以最后的key-value为准
  • map的key可以是很多中类型,通常为int,string但是slice,map,functio不可以,因为这几个不能用==来判断
  • map的value通常为数字,string,map,struct
  • map是无序的,如果要排序,可以对key进行排序,再根据key的值遍历输出
package main

    import (
        "fmt"
    )

    //字典类型
    func main() {
        gf := make(map[string]map[int]string)
        //如果key还没有,就是增加,如果key存在就是修改
        //可以先make再初始化,如gf
        //也可以直接初始化,如gf["萧峰"]
        gf["萧峰"] = map[int]string{0: "降龙十八掌", 1: "太祖长拳"}
        gf["段誉"] = map[int]string{0: "六脉神剑", 1: "凌波微步"}
        gf["虚竹"] = map[int]string{0: "小无相功", 1: "北冥神功"}

        //map遍历
        for name, kongfu := range gf { //字典是无序存储,每次遍历先后关系不同
            fmt.Printf("%s:", name)
            for _, kf := range kongfu {
                fmt.Printf("%s\t", kf)
            }
            fmt.Println()
        }

        //查找
        if p, ok := gf["萧峰"]; ok {
            fmt.Println(p)
        }

        //删除
        delete(gf, "段誉")
        fmt.Println(gf)

        //一次性的删除所有key
        //1.遍历所有的key,逐一删除
        //2.直接make一个新的空间
        gf=make(map[string]map[int]string)
        fmt.Println(gf)
    }

虚竹:小无相功 北冥神功
萧峰:降龙十八掌 太祖长拳
段誉:六脉神剑 凌波微步
map[0:降龙十八掌 1:太祖长拳]
map[萧峰:map[0:降龙十八掌 1:太祖长拳] 虚竹:map[0:小无相功 1:北冥神功]]
map[]

12.对象与方法

  • go支持类似c++面对对象的风格
  • go提供了方法定义和调用他们的方式,因此语法并不笨拙
  • 可以建立对象的同时初始化
  • 通过内置函数new来建立对象,new返回一个指针
  • 成员或方法首字母大写为public,小写为private
  • func和方法名之间指定了方法的接收者,即所属的对象,也是方法的第一个参数
  • 方法如果想要更改对象中的值,那么第一个参数应设为指针类型,不然只是对对象的拷贝
package main

    import (
        "fmt"
        "math"
    )

    //Point 点类
    type Point struct {
        x, y float64
    }

    //Distance 计算距离
    func (p Point) Distance() float64 {
        return math.Sqrt(p.x*p.x + p.y*p.y)
    }
    //Change 修改
    func (p *Point) Change(){
        p.x=5.5
    }
    func main() {
        var p1 Point = Point{3, 4}
        var p2 *Point = new(Point) //通过内置函数new来建立对象
        p3 := new(Point)
        p3.Change()
        fmt.Println(p1.Distance(), p2, p3)
    }

5 &{0 0} &{5.5 0}

13.接口

  • 如果几个对象间存在公共行为,而开发者想要抽象这种行为,那么就可以将这类行为定义为接口,并使用它。
  • 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
  • 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  • 空接口interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量
    赋给空接口。
package main

    import (
        "fmt"
    )
    //Printer 定义接口
    type Printer interface{
        Print()//公共方法
        //无需显示给出实现类型
        //add other methods
        //没有数据字段
    }
    type camera struct{
        name string
        age byte
    }
    //实现接口,谁使用谁实现
    func (u camera) Print(){
        fmt.Println(u)
    }

    type goods struct{
        name string
        num,price byte
    }

    //实现接口,谁使用谁实现
    func(g goods)Print(){
        fmt.Println(g)
    }

    type computer struct{

    }

    func (c computer)working ( p Printer){//接受接口类型变量
        p.Print()
    }
    func main(){
        //usb插槽就是现实中的接口
        u:=camera{"Tom",18}
        u.Print()//像使用普通方法一样使用接口,接口的热插拔
        g:=goods{"bed",10,200}
        var ip Printer
        //定义接口变量ip 
        //只要对象实现了接口的全部方法,
        //就表示实现了该接口,
        //就可以赋值给接口变量
        ip=u
        ip.Print()
        ip=g
        ip.Print()
        g.name="sofa"
        g.Print()
        ip.Print()
        //当对象赋值给接口时,会发生拷贝。
        //接口内部存储的是指向这个复制品的指针,
        //无法修改其状态,也无法获取指针;
        com:=computer{}
        com.working(u)
        com.working(ip)
        com.working(g)
    }

14.继承

  • 继承可以解决代码复用
  • 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
  • 其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 匿名结构体即可
  • 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问
    匿名结构体的字段和方法,可以通过匿名结构体名来区别
package main

    import (
        "fmt"
    )
    type user struct{
        name string
        age byte
    }
    //Output 定义方法
    func (u user)Output()string{
        return fmt.Sprintf("%+v\n",u)
    }

    type leader struct{
        user//匿名成员,可实现继承
        title string
    }
    func main(){
        var wo leader
        wo.user.name="kyl"//匿名成员有着和类型一样的名字
        wo.user.age=18
        //可以简化写法
        wo.name="fcl"
        wo.title="manager"
        var us=leader{user{"lzc",18},"dom"}//莫忘user
        //var us=user{"  ",91}
        fmt.Println(us)
    }

15.并发

  • 进程就是程序在操作系统的一次执行过程,是系统进行资源分配和调度的基本单位
  • 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
  • 一个程序至少有一个进程,一个进程至少有一个线程
  • 并行与并发
  • 并发:多个任务作用在一个cpu,在一个时间点上,只有一个任务在执行
  • 并行:多个任务作用在多个cpu,在一个时间点上,多个任务在同时执行
package main

    import (
        "fmt"
        "time"
    )

    func main(){
        //非并发
        /*task1()
        task2()*/
        go task1()
        go task2()
        fmt.Println("hhhhhhhhhh")
        time.Sleep(time.Second*6)//如果没有这句话,没有输出,因为main执行太快
    }
    func task1(){
        for i:=0;i<5;i++{
            fmt.Println("hello")
            time.Sleep(time.Second)
        }
    }
    func task2(){
        for i:=0;i<5;i++{
            fmt.Println("world")
            time.Sleep(time.Second)
        }
    }
package main

    import (
        "fmt"
        "time"
    )

    func main() {
        go spinner(100*time.Millisecond)//计算的同时显示动画
        const n = 45
        fibN := fib(n) // slow
        fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
    }

    func spinner(delay time.Duration) {
        for {
            for _, r := range `-\|/` {
                fmt.Printf("\r%c", r)
                time.Sleep(delay)
            }
        }
    }

    func fib(x int) int {
        if x < 2 {
            return x
        }
        return fib(x-1) + fib(x-2)
    }

16.通道

  • 主线程在等待所有goroutine 全部完成的时间很难确定
  • 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine 处于工作
    状态,这时也会随主线程的退出而销毁
  • channle 本质就是一个数据结构-队列
  • 数据是先进先出【FIFO : first in first out】
  • var 变量名 chan 数据类型
  • ch:=make(chan int)声明并初始化
  • ch:=make(chan int,10)带缓冲区的通道,容量是10
  • channel 是引用类型
  • channel必须初始化才能写入数据,即make后才能使用
  • 管道是有类型的,只能放置特定类型的数据
  • channel的数据满了之后,不能再放入,除非取出数据,才可以继续放入
  • 写入数据 : channel变量名 <- 数据;向channel写入数据通常会导致程序堵塞,直到有其他goroutine来从这个通道中读取数据
  • 读取数据 : value := <- channel变量名;如果通道之前没有写入数据,那么从通道中读取数据也会导致程序堵塞,直到channel中被写入数据
  • 关闭: close(channel变量名)‘关闭通道后不能再写入数据,但是仍然可以读取数据
  • 无论怎样都不应该在接收端关闭通道,因为那样无法判断发送端是否还会向该通道发送值。
  • 我们在发送端调用close关闭通道却不会对接收端接收该通道中已有的元素值产生任何影响。
  • 遍历:支持for-range的方式进行遍历,但遍历前,channel需要关闭,如若不然,会出现deadlock报错
package main

    import (
        "fmt"
    )

    func consumer(data chan int, done chan bool) {
        for x := range data {//遍历前channel需要关闭
            fmt.Println("recv:", x)
        }
        done <- true//消费结束,向done通道写入true
    }

    //生产者
    func producer(data chan int) {
        for i := 0; i < 5; i++ {
            fmt.Println("send message", i)
            data <- i
            //写入数据
        }
        close(data)//莫忘关闭
    }
    func main() {
        //各个并发/行体之间如何通信
        //布尔通道
        done:=make(chan bool)//channel make后才能使用
        //整形通道
        data:=make(chan int)
        //启动生产者
        go producer(data)

        go consumer(data,done)

        <-done
        //阻塞,直到接受消费者发出的结束信号
    }

17.数据类型

  • 布尔:bool
  • 长度:1字节
  • 取值范围:true,false
  • 注意事项:不可以用数字代表true或false
  • 整型:int/uint
  • 根据运行平台可能为32位或64位
  • 8位整型:int8/uint8
  • 长度:1字节
  • 取值范围:-128127/0255
  • 字节型:byte(uint别名)
  • 16位整型:int16/uint16
  • 长度:2字节
  • 取值范围:-3276832767/065535
  • 32位整型:int32(rune)/uint32
  • 长度:4字节
  • 取值范围:-232/2~232/2-1/0~2^32-1
  • 64位整型:int64/uint64
  • 长度:8字节
  • 取值范围:-264/2~264/2-1/0~2^64-1
  • 浮点型:float32/float64
  • 长度:4/8字节
  • 小数位:精确到7/15小数位
  • 复数:complex64/complex128
  • 长度:8/16字节
  • 能够保存指针的 32 位或 64 位整数型:uintptr
  • 其它值类型:
  • array、struct、string
  • 引用类型:
  • slice、map、chan
  • 接口类型:inteface
  • 函数类型:func
  • 零值类型
  • 零值并不等于空值,而是当变量被声明为某种类型后的默认值
  • 通常情况下值类型的默认值为0,bool为false,string为空字符串“”
  • 类型转换
  • Go中不存在隐式转换,所有类型转换必须显式声明
  • 转换只能发生在两种相互兼容的类型之间
  • 类型转换的格式
  • <ValueA> [:]= <TypeOfValueA>(<ValueB>)
  • rune 类型示例
package main

    import (
        "fmt"
    )

    func main(){
        var ch1 rune
        ch1='考'//int32
        ch2:='试'//int32
        str1:=string(ch1)+string(ch2)
        str2:=ch1+ch2
        fmt.Println(ch1,ch2)
        fmt.Printf("%T %T %c %c",ch1,ch2,ch1,ch2)
        fmt.Println(str1,str2)

        str3:="成功" 
        fmt.Printf("%T %T\n",str3[0],str3)
        for i,v:=range str3{
            fmt.Printf("%T %d %c\n",v,i,v)
        }
        fmt.Println("-----")
        for i:=0;i<len(str3);i++{
            fmt.Printf("%d %v %T\n",i,str3[i],str3[i])
        }
        fmt.Println("str3.len=",len(str3))
    }

32771 35797
int32 int32 考 试考试 68568
uint8 string
int32 0 成
int32 3 功
-----
0 230 uint8
1 136 uint8
2 144 uint8
3 229 uint8
4 138 uint8
5 159 uint8
str3.len= 6

  • complex示例
package main

    import (
        "fmt"
        "math/cmplx"
    )

    func main() {
        //var comp complex128
        comp := 2 + 4i
        fmt.Printf("%T", comp) //默认128位
        r := real(comp)        //实部
        im := imag(comp)       //虚部
        fmt.Println("comp=", r, "+", im, "*i")
        c := cmplx.Sqrt(comp) //用于128位复数开方
        fmt.Println(c)
    }

complex128comp= 2 + 4 *i
(1.7989074399478673+1.1117859405028423i)

18.关键字

25个

  • var和const :变量和常量的声明
  • var varName type 或者 varName : = value
  • package and import: 导入
  • func: 用于定义函数和方法
  • return :用于从函数返回
  • defer someCode :在函数退出之前执行
  • go : 用于并行
  • select 用于选择不同类型的通讯
  • interface 用于定义接口
  • struct 用于定义抽象数据类型
  • break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
  • chan用于channel通讯
  • type用于声明自定义类型
  • map用于声明map类型数据
  • range用于读取slice、map、channel数据

19.运算符

  • go中运算符均是从左向右结合
  • 优先级
  • 优先级 运算符
    7 ^ !
    6 * / % << >> & &^
    5 + - | ^
    4 == != < <= >= >
    3 <-
    2 &&
    1 ||
  • &^为清除标志位运算,从a上清除b 的标志位;
    1、如果右侧是0,则左侧数保持不变
    2、如果右侧是1,则左侧数一定清零
  • ^作为一元运算符为按位取反 ,作为二元运算符为异或
package main

    import (
        "fmt"
    )

    func main(){
        var a uint8=5
        var b uint8=3
        fmt.Println(a&^b)//从a上清除b上的标准位
        fmt.Println(a^b)//按位异或
        fmt.Println(^a)//按位取反
        
		fmt.Println(0 &^ 0)
		fmt.Println(0 &^ 1)
		fmt.Println(1 &^ 0)
		fmt.Println(1 &^ 1)
    }

4
6
250
0
0
1
0

20.指针、++、–

  • Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不支持指针运算(如p++)以及”->”运算符,而直接采用”.”选择符来操作指针目标对象的成员
  • 操作符”&”取变量地址,使用”*”通过指针间接访问目标对象
  • 默认值为 nil 而非 NULL
  • 递增递减语句
  • 在Go当中,++ 与 – 是作为语句而并不是作为表达式

21.常量

  • 常量的值在编译时就已经确定
  • 常量的定义格式与变量基本相同
  • 等号右侧必须是常量或者常量表达式
  • 常量表达式中的函数必须是内置函数
  • 当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。
  • 一个常量的声明也可以包含一个类型和一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。
const pi=3.14159//声明单个常量
const (
const (
    e  = 2.71828182845904523536028747135266249775724709369995957496696763
    pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
  • 如果不提供初值,则表示将使用上行的表达式
const (
    a = 1
    b
    c = 2
    d
)

fmt.Println(a, b, c, d) // "1 1 2 2"
  • itoa:常量计数器
  • 常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。
type Weekday int

const (
    Sunday Weekday = iota//默认为0
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)
const(
 today =iota//重置为0
)
  • iota与<<结合实现计算机存储单位的枚举
const (
		_ = iota//0
		KB ByteSize = 1 << (10*iota)//iota为1
		MB
		GB
		TB
		PB
		EB
		ZB
		YB
	)