Go语言基础

使用Go编写第一行代码

新建golangFile文件夹,在文件夹中新建main.go,在里面写入如下代码

package main

import "fmt"

func main() {
    fmt.Println("hellow world")
}

编译

go build main.go

或者直接编译当前包含go代码的文件夹

go build

会看到编译后文件夹内多出

main.exe或者golangFile.exe

直接在终端执行就会看到“hello world”了

.\main.exe

如果想查看编译过程

go build -x main.go

如果想指定一个编译后的二进制文件名称

go build -x -o hello.exe main.go

如果省去手动编译过程,一步编译+执行

go run main.go 这样直接在一个其它临时的目录生成.exe并执行出结果


定义变量

*匿名变量 _ :是一个go已定义的变量,本身这种变量不会进行空间分配,也不会占用一个变量的名字。
相当于一个黑洞,对它的任何赋值都会被吃掉不保存,通常来用作占位符

package main

import "fmt"

//定义包级别的变量
var (
    name string = "yxp"
    age  int    = 29
)

//定义变量可以省略类型,编译器根据字面量去猜这个变量的类型
var (
    name2 = "zxm"
    age2  = 28
)

//不推荐这种在一行里定义多个变量
var name3, age3 = "hhh", 29

func main() {
    //定义函数内部变量
    var test string
    //变量的初始化
    test = "asd"
    fmt.Println(test)
    fmt.Println(name)
    //这是一个简短声明,简短声明只能在函数内部
    yyds := true
    //简短声明多个变量,其中的变量至少有一个是未定义的
    _, num2 := 1, 2
    fmt.Println(yyds)
    //赋值
    test, name = "this is test", "this is name"
    fmt.Println(test, name)
    //小tips,交换test和name的值
    test, name = name, test
    fmt.Println(test, name)
}

常量

package main

import "fmt"

func main() {
    const NAME string = "yxp"
    //可以省略类型
    const AGE = 18
    //定义多个常量
    const (
        AGE1  int    = 19
        NAME1 string = "yyy"
    )
    //可以省略类型
    const (
        AGE2  = "yyds"
        NAME2 = 20
    )
    //也可以这样
    const AGE3, NAME3 = 21, "jkl"
    //同类型的常量第一个赋值之后,后面的常量值和第一个值相等
    const (
        AGE4 int = 1
        AGE5
        AGE6
    )
    fmt.Println(AGE4, AGE5, AGE6)
    //也可以这样写
    const (
        A1 int = 1
        A2
        A3
        A4 float64 = 0.1
        A5
        A6
    )
    fmt.Println(A1, A2, A3, A4, A5, A6)
    //枚举,const_iota
    const (
        B1 int = iota //0
        B2            //1
        B3            //2
    )
    fmt.Println(B1, B2, B3)
    const (
        C1 int = (iota + 1) * 100 //100
        C2                        //200
        C3                        //300
    )
    fmt.Println(C1, C2, C3)
}


作用域、块

package main

import "fmt"

func main() {
    //作用域、块。由{}来划分
    outer := "outer"
    outDeclare := "outDeclare"
    {
        inner := "inner"
        //里面可以访问外面的
        fmt.Println(outer)
        fmt.Println(inner)
        //在里面改变外面的
        outer = "ch_outer"
        fmt.Println(outer)
        //块内重新定义块外的变量
        outDeclare := "bloack_outDeclare"
        fmt.Println(outDeclare)
    }
    fmt.Println(outer)
    fmt.Println(outDeclare)
    //外面不能访问里面的
    //fmt.Println(inner)
}


布尔类型变量

package main

import "fmt"

func main() {
    //不初始化默认是false
    var zero bool
    //布尔类型只有两个值true、false。不会像别的语言可以是0和1
    //布尔类型占用空间1字节
    isBoy := true
    isGirl := false
    fmt.Println(zero, isBoy, isGirl)
}

int类型变量

package main

import "fmt"

func main() {

    var age int = 31
    fmt.Printf("%T %d\n", age, age)
    //打印查看八进制和十六进制
    fmt.Println(0777, 0x10)
    //位运算
    //十进制转换二进制 辗转相除法(请百度)
    //十进制7转换成二进制
    //7/2=3...1, 3/2=1...1, 1/2=0...1
    //0111
    //十进制2转二进制
    //2/2=1...0
    //10
    //7 & 2 => 0111 & 0010 => 0010 就是十进制的2
    //7 | 2 => 0111 | 0010 => 0111 就是十进制的7
    //7 ^ 2 => 0111 ^ 0010 => 0101 就是十进制的5
    //2 << 1 => 0010 << 1  => 0100 就是十进制的4
    //2 >> 1 => 0010 >> 1  => 0001 就是十进制的1
    //7 &^ 2 => 0111 &^ 0010 => 0101就是十进制的5;
    //&^ 按位清空,就是两个数字按位一一对应,第二个数字为1的位置,第一个数字就要为0,结果取第一个数字改变后的样子
    fmt.Println(7 & 2)
    fmt.Println(7 | 2)
    fmt.Println(7 ^ 2)
    fmt.Println(2 << 1)
    fmt.Println(2 >> 1)
    fmt.Println(7 &^ 2)

    //int/uint/byte/rune/int*
    var intA int = 10
    var uintB uint = 3
    fmt.Println(intA + int(uintB)) //两种类型的数需要强制转换成一种才能计算
    fmt.Println(uint(intA) + uintB)

    //强制类型转换要注意会溢出丢失,从大往小转
    var intC int = 0xfffff
    fmt.Println(intC, uint8(intC))

    //byte,rune。实际上也是int(百度搜索这两种类型,理解的不太清楚)
    
    var a byte = 'A'
    var w rune = '中'
    fmt.Println(a, w)
    fmt.Printf("%T %d %b %o %x\n", age, age, age, age, age)
    fmt.Printf("%T %c\n", a, a)
    fmt.Printf("%T %q %U %c\n", w, w, w, w)
    age = 1234
    fmt.Printf("%5d\n", age)  //一共占5位,不够在前面补空格,超出不处理
    fmt.Printf("%05d\n", age) //一共占5位,不够在前面补0
    fmt.Printf("%-5d\n", age) //左对齐,在右边补位。默认是右对齐,在左边补位
}

byte和rune详解

他们都属于别名类型,byte是uint8(无符号8位整数)的别名类型,rune是int32(有符号32位整数)的别名类型

package main

import ( 
    "fmt" 
)

func main() {
    //rune类型可以表示一个Unicode字符,rune类型的值需要用''包裹
    //直接使用Unicode支持的字符赋值,这种表示方法最通用,其他的的了解就好
    var char rune = '赞' 
    //一个Unicode代码点通常由“U+”和一个以十六进制表示法表示的整数表示。例如,英文字母“A”的Unicode代码点为“U+0041”。
    fmt.Printf("字符 '%c' 的Unicode代码点是 %s。\n", char1, ("U+8D5E"))
    
    var ch1 rune = '\101'//表示字母A,"\"加三位八进制数,只能表示值在[1,255]内的字符
    var ch2 rune = '\x41'//表示字母A,"\x"加两个十六进制数,只能表示ASCLL支持的字符
    var ch3 rune = '\u90DD'//表示“郝”,'\u'加四位十六进制数,只能表示编码值在[0,65535]内的字符
    var ch4 rune = '\U000090DD'//表示“郝”,'\U'加四位十六进制数,可表示任何Unicode字符
    fmt.Println(char,ch1,ch2,ch3,ch4)
    fmt.Printf("char:%T %c\n", char, char);
    fmt.Printf("ch1:%T %c\n", ch1, ch1);
    fmt.Printf("ch2:%T %c\n", ch2, ch2);
    fmt.Printf("ch3:%T %c\n", ch3, ch3);
    fmt.Printf("ch4:%T %c\n", ch4, ch4);
    //rune类型值的表示中支持转义字符
    var ch5 rune = '\''//单引号,Unicode代码点U+0027
    var ch6 rune = '"'//双引号,Unicode代码点U+0022
    var ch7 rune = '\\'//反斜杠,Unicode代码点U+005c
    var ch8 rune = '\r'//回车,Unicode代码点U+000D
    var ch9 rune = '\t'//水平制表符,Unicode代码点U+0009
    var ch10 rune = '\n'//换行符,Unicode代码点U+000A
    var ch11 rune = '\b'//退格符,Unicode代码点U+0008
    var ch12 rune = '\a'//告警铃声或蜂鸣声,Unicode代码点U+0007
    fmt.Printf("ch5:%T %c\n", ch5, ch5);
    fmt.Printf("ch6:%T %c\n", ch6, ch6);
    fmt.Printf("ch7:%T %c\n", ch7, ch7);
    fmt.Printf("ch8:%T %c\n", ch8, ch8);
    fmt.Printf("ch9:%T %c\n", ch9, ch9);
    fmt.Printf("ch10:%T %c\n", ch10, ch10);
    fmt.Printf("ch11:%T %c\n", ch11, ch11);
    fmt.Printf("ch12:%T %c\n", ch12, ch12);

}



浮点类型变量

package main

import "fmt"

func main() {
    //float32,float64只有这两种类型
    var high float64
    fmt.Printf("%T %f\n", high, high)
    fmt.Println(1.1 + 1.2)
    fmt.Println(1.1 - 1.2)
    fmt.Println(1.1 * 1.2)
    fmt.Println(1.1 / 1.2) //0.9166666666666666
    high = 1.64
    high++
    //浮点数计算是有精度损耗的,不准确。结果是2.6399999999999997
    fmt.Println(high)
    fmt.Printf("%T %5.2f\n", high, high) //保留5位2个小数点
    //关系运算
    //浮点数一般不计算== !=,因为精度损耗结果可能不准确
    //一般计算 > >= < <=

    //如果要计算== != 请百度搜搜

}


字符串类型变量(含切片和索引)

package main

import "fmt"

func main() {
    //特殊字符 \n \f \t \r...在""里可以被转义,在``里不会被转义
    var name string = "as\tdasa" //可解释字符串
    var desc string = `aaa\taa`  //原生字符串
    fmt.Println(name, desc)
    //字符串链接
    fmt.Println("我叫" + "hhh")
    //fmt.Println("我叫" + 'Q')//报错 字符串和字符不能连接
    //字符串之间的比较
    fmt.Println("a" == "b")
    fmt.Println("a" > "b")
    fmt.Println("a" >= "b")
    fmt.Println("a" < "b")
    fmt.Println("a" <= "b")
    fmt.Println("a" != "b")
    //使用索引或者切片操作,字符串定义的内容只能为ASCII
    desc = "abcdefg"
    //索引为从0~(n-1)
    fmt.Printf("%T %c\n", desc[0], desc[0]) //a
    //切片[strat:end],取出从[star ~ (end-1)]之内的字符
    fmt.Printf("%T %s\n", desc[0:3], desc[0:3]) //abc
    //如果不是ASCII
    desc = "哈哈哈"
    fmt.Printf("%T %c\n", desc[0], desc[0])     //乱码
    fmt.Printf("%T %s\n", desc[0:2], desc[0:2]) //乱码
    //得到字符串长度,也要是ASCII字符串,实际上len()得到的是字符串所占字节
    desc = "abc"
    fmt.Println(len(desc)) //3
    desc = "几十块"
    fmt.Println(len(desc)) //9
}


指针类型变量

package main

import "fmt"

func main() {
    a := 2
    var b int
    fmt.Println(a, b) //2 0
    b = 3
    fmt.Println(a, b) //2 3
    //指针
    var c *int
    c = &a
    //c := &a
    fmt.Printf("%T %d\n", c, c)
    fmt.Printf("%T %d\n", *c, *c)
    //通过指针修改变量a的值
    *c = 4
    fmt.Println(a, *c)
}


scan方法,用户输入方法

package main

import "fmt"

//用户输入
func main() {
    var name string
    fmt.Println("请输入名字")
    fmt.Scan(&name)
    fmt.Println("您输入的内容是:" + name)

    //输入内容要保证是int,不是int会输出0
    var age int
    fmt.Println("请输入年龄")
    fmt.Scan(&age)
    fmt.Println("年龄是:", age)

    //如果上一个输入的类型错误,那么这里也会使用上一个错的数据类型自动输入
    var height string
    fmt.Println("请输入身高")
    fmt.Scan(&height)
    fmt.Println("身高是:", height)
}


if 语句

语法

if 布尔表达式 {
    /* 在布尔表达式为 true 时执行 */
}
func main() {
    x := 0

    if n := "abc"; x > 0 {//定义了局部变量n,只能在块内访问
        fmt.Printf("%T : %c", n[2], n[2])
    } else if x < 0 { // 注意 else if 和 else 左大括号位置。
        fmt.Printf("%T : %c", n[1], n[1])
    } else {
        fmt.Printf("%T : %c", n[0], n[0])
    }
}

不支持三元操作符(三目运算符) "a > b ? a : b"。


switch语句

Golang switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。 您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

var grade string = "B"
    var marks int = 90

    switch marks {
    case 90:
        grade = "A"
    case 80:
        grade = "B"
    case 50, 60, 70:
        grade = "C"
    default:
        grade = "D"
    }
    //省略条件表达式,可当 if...else if...else
    switch {
    case grade >= "A":
        fmt.Printf("优秀\n")
    case grade >= "B":
        fmt.Printf("良好\n")
    case grade >= "D":
        fmt.Printf("及格\n")
    case grade <= "D":
        fmt.Printf("不及格\n")
    }

    //fallthrough
    var k = 0
    switch k {
    case 0:
        println("fallthrough")
        fallthrough
        /*
           Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;
           而如果switch没有表达式,它会匹配true。
           Go里面switch默认相当于每个case最后带有break,
           匹配成功后不会自动向下执行其他case,而是跳出整个switch,
           但是可以使用fallthrough强制执行后面的case代码。
        */
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }

Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

格式:

switch x.(type){
    case type:
       statement(s)      
    case type:
       statement(s)
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s)
}

实例:

var x interface{}

switch i := x.(type) { // 带初始化语句
    case nil:
        fmt.Printf(" x 的类型 :%T\r\n", i)
    case int:
        fmt.Printf("x 是 int 型")
    case float64:
        fmt.Printf("x 是 float64 型")
    case func(int) float64:
        fmt.Printf("x 是 func(int) 型")
    case bool, string:
        fmt.Printf("x 是 bool 或 string 型")
    default:
        fmt.Printf("未知型")
}

for循环结构


# for循环结构
```go
package main

func main() {
    s := "abc"

    // 常见的 for 循环,支持初始化语句。
    for i, n := 0, len(s); i < n; i++ { 
    println(s[i])
    }

    // 替代 while (n > 0) {}
    n := len(s)
    for n > 0 { 
    println(s[n-1]) // 替代 for (; n > 0;) {}
    n--
    }
    // 替代 while (true) {}
    // 替代 for (;;) {}
    for { 
    println(1) 
    }
}


for range

s := "abc"
//忽略 value,支持 string/array/slice/map。
for i := range s {
    println(s[i])
}
//忽略 key。
for _, c := range s {
    println(c)
}
// 忽略全部返回值,仅迭代。
for range s {

}

m := map[string]int{"a": 1, "b": 2}
// 返回 (key, value)。
for k, v := range m {
    println(k, v)
}

9*9乘法表

package main

import "fmt"

func main() {
    var a int = 0
    var b int = 0
    for a = 1; a < 10; a++ {
        for b = 1; b < 10; b++ {
            var res int
            if b <= a {
                res = a * b
                fmt.Printf("%d*%d=%d ", b, a, res)
            }

        }
        fmt.Printf("\n")
    }
}
1*1=1 
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

数组

package main

import "fmt"

func main() {
    //定义数组
    var num [10]int
    var boolType [10]bool
    var strType [10]string
    //查看类型和每个数组单元默认值是什么
    fmt.Printf("%T   %d\n", num, num)
    fmt.Printf("%T   %t\n", boolType, boolType)
    fmt.Printf("%T   %s\n", strType, strType)
    /*数组字面量的三种表示方法*/
    //只定义前几个数组元素
    num = [10]int{1, 2, 3}
    fmt.Println(num)
    //指定数组某个单元的值
    num = [10]int{1: 10, 8: 80, 9: 90}
    fmt.Println(num)
    //不声明数组的长度,数组字面量的元素个数要和数组变量num能容纳的元素个数一致
    num = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(num)

    /*数组的计算*/
    arr1 := [...]int{1, 2, 3}
    arr2 := [...]int{1, 2, 2}
    fmt.Println(arr1 == arr2)
    //len()获得数组的长度
    arr3 := [...]string{"asd", "aaa"}
    fmt.Println(len(arr3))
    //索引
    arr3[1] = "hhahah"
    fmt.Println(arr3[0])

    //字符串切片
    var s string = "qwertyuiop"
    fmt.Printf("%T %v\n", s[0:4], s[0:4])
    //数组切片
    var arr4 [10]int
    arr4 = [10]int{1, 2, 3, 4}
    fmt.Printf("%T %v\n", arr4[0:4], arr4[0:4])
    //多维数组
    var arr5 [3][2]int
    fmt.Printf("%T %v\n", arr5, arr5)
    arr5 = [3][2]int{{1}, {2, 2}}
    fmt.Printf("%T %v\n", arr5, arr5)
}


切片slice

需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。

------------有时间详细了解切片到底是不是指针

nil 简单来说:

nil是一个预先声明的标识符,表示指针、通道、函数、接口、映射或切片类型。
nil可以代表很多类型的零值

------------有时间详细总结下nil

//定义一个切片
var num []int
fmt.Printf("%T len:%d cap:%d\n", num, len(num), cap(num))
if num == nil {
    fmt.Println("未初始化的切片num 等于 nul,其实切片底层就是一个指针,指针的默认值是空,所以这里切片默认值就是空")
}
//切片字面量,和初始化
num = []int{1, 2, 3}
fmt.Printf("初始化切片:%#v len:%d cap:%d\n", num, len(num), cap(num))
//切片可长度可变
num = []int{1, 2, 3, 4}
fmt.Printf("改变了切片的长度:%#v len:%d cap:%d\n", num, len(num), cap(num))
//数组长度不可变,以下代码会报错
//var arr = [3]int{1, 2, 3}
//arr = [2]int{1, 2}

//数组切片赋值给切片
var arr1 [10]int = [10]int{1, 2, 3, 4, 5}
num = arr1[0:8]
fmt.Printf("通过数组得到的切片:%#v len:%d cap:%d\n", num, len(num), cap(num))
//make函数
num = make([]int, 3)
fmt.Printf("通过make函数只声明len的切片:%#v len:%d cap:%d \n", num, len(num), cap(num))
num = make([]int, 3, 5)
fmt.Printf("通过make函数声明了len和cap的切片:%#v len:%d cap:%d \n", num, len(num), cap(num))
/*
    切片为什么要有容量的概念?为什么不直接使用长度len
    因为切片是可变长的,每次变长都会重新生在内存开辟一片更大的空间,然后把这个空间的地址重新赋值给变量
    那么如果每增加一个元素就新开辟空间再赋值有点浪费计算机资源
    所以可以生成冗余的空间,我们当前用到几个单元就是len,如果需要增加元素就从cap中再给一个len就ok
*/

/*切片的操作,增、删、改、查*/
//查
fmt.Printf("根据索引查看切片元素:%d\n", num[0])
//fmt.Println(num[4]) //无法查到4,index out of range [4] with length 3
//改
num[0] = 10
fmt.Printf("查看通过索引改变了的切片元素:%d\n", num[0])
//增
num = append(num, 333)
fmt.Printf("查看append之后的切片:%#v len:%d cap:%d \n", num, len(num), cap(num))
//超出切片容量之后,会自动扩展容量。底层其实是重新申请一个更大的内存空间,并把空间的地址赋值给切片变量
num = append(num, 444, 555)
fmt.Printf("查看append了超出原有容量的切片:%#v len:%d cap:%d \n", num, len(num), cap(num))

//切片的切片
newNum := num[1:3]
//new_cap = 10-1
fmt.Printf("查看切片的切片:%#v new_len:%d new_cap:%d \n", newNum, len(newNum), cap(newNum))
newNum = num[1:3:6]
//new_cap = 6-1
fmt.Printf("查看切片的切片:%#v new_len:%d new_cap:%d \n", newNum, len(newNum), cap(newNum))
//切片的切片容量不能设置的比原来的大
//n := num[1:3:100]//这是会报错的

//遍历切片
for k, v := range num {
    fmt.Println(k, v)
}

/*
    切片的副作用
    切片其实是使用源的地址。而不是把源的值复制一份使用
*/
//切片的切片被改
num = make([]int, 3, 5)
num01 := num[1:3]
fmt.Println(num, num01)
num01[0] = 1
fmt.Println(num, num01)
//增加num01的元素
num01 = append(num01, 111)
fmt.Println(num, num01)
//增加num的元素
num = append(num, 222)
fmt.Println(num, num01)
//直到num或num1之中的哪一个先append超出了cap容量,那么就会重新分配空间,两者才会分开
num01 = append(num01, 333, 444)
fmt.Println(num, num01)
//数组的切片被改
var arr2 = [5]int{0, 1, 2, 3, 4}
var num02 = arr2[:] //只写一个[:]代表生成一个和原数组一样的切片
fmt.Println(arr2, num02)
num02[0] = 111
fmt.Println(arr2, num02)

//删-copy。在go中常用copy来实现删除切片元素
sArr := []int{1, 2, 3}
sArr1 := []int{11, 22, 33, 44}
copy(sArr, sArr1) //把sArr1 copy到sArr上
fmt.Printf("使用copy的效果:%v\n", sArr)
//删除切片第一个元素和最后一个元素
sArr2 := []int{1, 2, 3, 4, 5}
fmt.Printf("删除切片第一个元素:%v\n", sArr2[1:])
fmt.Printf("删除切片最后一个元素:%v\n", sArr2[0:len(sArr2)-1])
//删除切片的中间元素
copy(sArr2[2:], sArr2[3:])
sArr2 = sArr2[0 : len(sArr2)-1]
fmt.Printf("删除sArr2[2]元素:%v\n", sArr2)

/*利用append和删除首尾元素的方法实现,堆栈和队列*/
//队列-先进先出
queue := []int{}
fmt.Println(queue)
queue = append(queue, 1)
fmt.Println(queue)
queue = append(queue, 2)
fmt.Println(queue)
queue = append(queue, 3)
fmt.Println(queue)
queue = queue[1:]
fmt.Println(queue)
queue = queue[1:]
fmt.Println(queue)
queue = queue[1:]
fmt.Println(queue)
//堆栈-先进后出
stack := []int{}
fmt.Println(stack)
stack = append(stack, 1)
fmt.Println(stack)
stack = append(stack, 2)
fmt.Println(stack)
stack = append(stack, 3)
fmt.Println(stack)
stack = stack[0 : len(stack)-1]
fmt.Println(stack)
stack = stack[0 : len(stack)-1]
fmt.Println(stack)
stack = stack[0 : len(stack)-1]
fmt.Println(stack)

总结:

1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。

2. 切片的长度可以改变,因此,切片是一个可变的数组。

3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。

4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。

5. 切片的定义:var 变量名 []类型,比如 var str []string var arr []int。

6. 如果 slice == nil,那么 len、cap 结果都等于 0。

多维切片

//第一种声明方式
point := [][]int{}
fmt.Printf("%T\n", point)
point = [][]int{{1, 2}, {2, 23}, {1, 2, 3, 4, 5}}
fmt.Println(point)
point[1][1] = 222
fmt.Println(point)
//第二种声明方式
point2 := make([][]int, 2, 4)
fmt.Printf("%T %d %d\n", point2, len(point2), cap(point2))
point2 = append(point2, []int{1, 1, 1, 1, 1})
fmt.Println(point2)
[][]int
[[1 2] [2 23] [1 2 3 4 5]]
[[1 2] [2 222] [1 2 3 4 5]]
[][]int 2 4
[[] [] [1 1 1 1 1]]

切片和数组的传值类型对比

//改变slice02会影响到slice01,因为slice01赋值给slice02其实是把地址给了slice02,他俩指向同一片内存空间
var slice01 []int = []int{1, 2, 3, 4}
var slice02 []int = slice01
fmt.Println(slice01, slice02)
slice02[0] = 10000
fmt.Println(slice01, slice02)
slice02 = []int{1, 1, 1}      //这里改变slice02,相当于把slice02重新指向了一个新地址
fmt.Println(slice01, slice02) //所以slice01不会跟着变了

//改变arr02就不会影响到arr01,arr01和arr02指向不同的内存空间
var arr01 [5]int = [5]int{1, 1, 1, 1, 1}
var arr02 = arr01
arr02[0] = 222
fmt.Println(arr01, arr02)
[1 2 3 4] [1 2 3 4]
[10000 2 3 4] [10000 2 3 4]
[10000 2 3 4] [1 1 1]
[1 1 1 1 1] [222 1 1 1 1]

sort模块的排序函数

sort模块可以对切片进行排序,请查官网手册的sort模块

num := []int{2, 4, 3, 6, 678, 31}
sort.Ints(num)
fmt.Println(num)

str := []string{"a1a", "asd", "34", "hhh"}
sort.Strings(str)
fmt.Println(str)

fnum := []float64{-1, 2.2, 391.01, 0.0, 0}
sort.Float64s(fnum)
fmt.Println(fnum)
[2 3 4 6 31 678]
[34 a1a asd hhh]
[-1 0 0 2.2 391.01]

映射

映射,相当于其它语言的hash-map。是key/value对

定义

//只定义不初始化,那么它是一个值为nil的映射
var score map[string]int
fmt.Printf("%T %#v\n", score, score)
if score == nil {
    fmt.Println("score == nil")
}
//结果
map[string]int map[string]int(nil)
score == nil

字面量初始化

//使用字面量的形式初始化得到的是空映射
var score2 = map[string]int{}
if score2 != nil {
    fmt.Println("score2 != nil")
}
score2 = map[string]int{"小李": 10, "小王": 20, "小张": 30}
fmt.Println(score2)
//使用make方法初始化映射
var score3 = make(map[string]int) //也是个空映射
fmt.Println(score3)
if score3 != nil {
    fmt.Println("score3 != nil")
}
//结果
score2 != nil
map[小张:30 小李:10 小王:20]
map[]
score3 != nil

操作-增、删、改、查

var price = map[string]int{"大米": 20, "白面": 30, "豆油": 40}
p := price["大米"]
fmt.Printf("%T %#v %v\n", p, p, p)
//访问不存在的key值
value := price["粉笔"]
fmt.Printf("%T %v \n", value, value)
//如果price中本来就有一个key/value,的值等于0.就会混淆
//所以
v, ok := price["粉笔"]
fmt.Printf("v:%T %v ; ok:%T %v\n", v, v, ok, ok)
//通过ok的值来判断key=粉笔是否存在
if ok {
    fmt.Println(v)
}
//也可以把v,ok变成局部变量
if v, ok := price["粉笔"]; ok {
    fmt.Println(v)
} else {
    fmt.Println("粉笔不存在")
}
//结果
int 20 20
int 0 
v:int 0 ; ok:bool false
粉笔不存在

var price = map[string]int{"大米": 20, "白面": 30, "豆油": 40}
price["大米"] = 100
fmt.Println(price)
//结果
map[大米:100 白面:30 豆油:40]

映射是无序的,先添加的也不一定就是在前面

var price = map[string]int{"大米": 20, "白面": 30, "豆油": 40}
price["粉笔"] = 10
fmt.Println(price) 
//结果
map[大米:20 白面:30 粉笔:10 豆油:40]

var price = map[string]int{"大米": 20, "白面": 30, "豆油": 40}
delete(price, "白面")
fmt.Println(price)
//结果
map[大米:20 豆油:40]

获取映射的长度用 len()

var price = map[string]int{"大米": 20, "白面": 30, "豆油": 40}
fmt.Println(len(price))

遍历

映射是无序的,遍历出来的顺序和添加时的顺序不一样

var price = map[string]int{"大米": 20, "白面": 30, "豆油": 40}
for k, v := range price {
    fmt.Printf("%v=%v \n", k, v)
}
//结果
白面=30 
豆油=40 
大米=20

映射是无序的

go的映射是使用hashtable实现的,key和value建立关系是通过计算key,把key转化成一个int索引来和value 一 一映射,但是这个由key计算出的int值是不确定的,以至于映射是无序的

关于key和value的类型

key的类型至少可以进行 == != 的运算

key的类型可以是:bool int string array 不能是slice不能是map

value可以是任意类型,包括slice map

//定义
var user map[string]map[string]string
//初始化
user = map[string]map[string]string{"小胖": {"班级": "3年1班", "年龄": "10岁"}, "吴亦凡": {"班级": "大一", "年龄": "18岁"}}
fmt.Println(user)
//元素的操作-增加
user["美美"] = map[string]string{"班级": "6年一班", "年龄": "13岁"}
fmt.Println(user)
//元素的操作-改
user["美美"]["班级"] = "4年5班"
fmt.Println(user)
//元素的操作-删除
delete(user["美美"], "年龄")
fmt.Println(user)
//结果
map[吴亦凡:map[年龄:18岁 班级:大一] 小胖:map[年龄:10岁 班级:3年1班]]
map[吴亦凡:map[年龄:18岁 班级:大一] 小胖:map[年龄:10岁 班级:3年1班] 美美:map[年龄:13岁 班级:6年一班]]
map[吴亦凡:map[年龄:18岁 班级:大一] 小胖:map[年龄:10岁 班级:3年1班] 美美:map[年龄:13岁 班级:4年5班]]
map[吴亦凡:map[年龄:18岁 班级:大一] 小胖:map[年龄:10岁 班级:3年1班] 美美:map[班级:4年5班]]

映射的练习

计数

var stack = []string{"钢笔", "钢笔", "铅笔", "钢笔", "毛笔", "毛笔", "钢笔", "毛笔", "毛笔"}
var count = map[string]int{}
for _, v := range stack {
    // if _, ok := count[v]; !ok {
    //  count[v] = 1
    // } else {
    //  count[v] += 1
    // }
    //优化后
    count[v] += 1
}
fmt.Println(count)
//结果
map[毛笔:4 钢笔:4 铅笔:1]

统计每个英文字母出现的次数

var char = `I say to you today, my friends.
And so even though we face the difficulties of today and tomorrow, I still have a dream. It is a dream deeply rooted in the American dream.
    I have a dream today!`
var arr = map[rune]int{}
for _, v := range char {
    if v >= 'A' && v <= 'Z' || v >= 'a' && v <= 'z' {
        arr[v] += 1
    }
}
fmt.Printf("%#v\n", arr)
fmt.Println("------------------------------------------------")
for key, cnt := range arr {
    fmt.Printf("%c : %v\n", key, cnt)
}
//结果
map[int32]int{65:2, 73:4, 97:16, 99:3, 100:13, 101:18, 102:5, 103:1, 104:6, 105:8, 108:4, 109:7, 110:6, 111:13, 112:1, 114:9, 115:6, 116:12, 117:3, 118:3, 119:2, 121:7}
------------------------------------------------
A : 2
v : 3
l : 4
I : 4
a : 16
t : 12
d : 13
m : 7
f : 5
r : 9
n : 6
g : 1
s : 6
y : 7
o : 13
u : 3
i : 8
c : 3
e : 18
h : 6
w : 2
p : 1

你可能感兴趣的:(Go语言基础)