golang(4):函数 & 数组 & 切片 & map & 锁

内置函数

// 1. close:主要用来关闭channel
// 2. len:用来求长度,比如string、array、slice、map、channel
// 3. new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
// 4. make:用来分配内存,主要用来分配引用类型,比如chan、map、slice
// 5. append:用来追加元素到数组、slice中

示例如下:

// new 示例:
package main

import "fmt"

func main(){
    var i int
    fmt.Println(i)

    j := new(int)
    fmt.Println(j)
    
    *j = 100
    fmt.Println(*j)
}

// 运行结果如下: 
[root@NEO example01_new]# go run main/main.go 
0
0xc0000160b0
100
[root@NEO example01_new]#

// append 示例:
package main

import "fmt"

func main(){
    var a []int    // [] 中不写数字表示是一个切片 slice
    a = append(a,1,2)    // append 中也是可变参数
    fmt.Println(a)

    a = append(a,a...)    // 添加一个切片;a...表示 切片a 中的所有元素
    fmt.Println(a)
}

// 运行结果如下:
[root@NEO example01_append]# go run main/main.go
[1 2]
[1 2 1 2]
[root@NEO example01_append]# 

// recover 示例:
package main

import "fmt"

func test(){
    defer func(){   // func(){} 是匿名函数; func(){...}()  执行匿名函数
    if err := recover(); err != nil{    // recover()  是捕获异常
        fmt.Println(err)
    }
    }()

    a := 0
    b := 1/a
    fmt.Println(b)
    return
}

func main(){
    test()
    fmt.Println("hello world")
}

// 运行结果如下:
[root@NEO example01_recover]# go run main/main.go
runtime error: integer divide by zero
hello world
[root@NEO example01_recover]# 
递归函数

一个函数调用自己,就叫做递归。示例代码如下:

// 示例代码1:
package main

import (
    "fmt"
)

func calc(n int) int {
    if n == 1 {
        return 1
    }

    return calc(n-1) * n
}

func main() {
    n := calc(5)
    fmt.Println(n)
}


// 示例2:斐波那契数
package main

import "fmt"

func fab(n int) int {
    if n <= 1 {
    return 1
    }
    return fab(n-1)+fab(n-2)
}

func main(){
    n := fab(10)
    fmt.Println(n)
}

递归的设计原则

1)一个大的问题能够分解成相似的小问题
2)定义好出口条件
闭包

闭包:一个函数和与其相关的引用环境组合而成的实体。示例如下:

// 示例代码:
package main

import "fmt"

func Adder() func(int) int {    // 函数 Adder 的返回值是也一个函数
    var x int
    return func(d int) int {    // 该处的 return 要和上面定义的返回值类型的 return 的函数一样
    x += d
    return x
    }
}

func main() {
    f := Adder()   // f 是一个函数
    fmt.Println("f(1)",f(1))         // 此时 x 的值为默认的 0
    fmt.Println("f(10)",f(10))    // 此时 x 的值变成了 1
    fmt.Println("f(100)",f(100))    // 此时 x 的值变成了 10
}

// 运行结果如下:
[root@NEO example03_closure]# go run main/main.go
f(1) 1
f(10) 11
f(100) 111
[root@NEO example03_closure]#

数组和切片

// 1. 数组:是同一种数据类型的固定长度的序列。
// 2. 数组定义:var a [len]int,比如:var a[5]int,一旦定义,长度不能变;元素的值默认为0
// 3. 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型
// 4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
       for i := 0; i < len(a); i++ {
       }
       
       for index, v := range a {
       }

// 5. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
// 6. 数组是值类型,因此改变副本的值,不会改变本身的值
    arr2 := arr1
    arr2[2] = 100

数组的初始化

1. var age0 [5]int = [5]int{1,2,3}        // 定义的时候初始化
2. var age1 = [5]int{1,2,3,4,5}
3. var age2 = […]int{1,2,3,4,5,6}        // ... 的作用:编译器会自动帮你数里面有几个元素,然后就是长度为多少的数组
4. var str = [5]string{3:”hello world”, 4:”tom”}    // 定义时指定下标对应的元素

多维数组

1. var age [5][3]int        // 二维数组:可理解成5行3列
2. var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}        // ... 表示 编译器自己去数有多少行

多维数组遍历

// 示例代码:
package main

import "fmt"

func test(){
    var arr [2][5]int = [...][5]int{{1,2,3,4,5},{6,7,8,9,10}}

    for row,v_row := range arr{
    for col,v_col := range v_row {
        fmt.Printf("(%d,%d)=%d ",row,col,v_col)
    }
    fmt.Println()
    }
}

func main(){
    test()
}


// 运行结果如下:
[root@NEO example04_multi_dim_arr]# go run main/main.go
(0,0)=1 (0,1)=2 (0,2)=3 (0,3)=4 (0,4)=5 
(1,0)=6 (1,1)=7 (1,2)=8 (1,3)=9 (1,4)=10 
[root@NEO example04_multi_dim_arr]#

切片

// 1. 切片:切片是数组的一个引用,因此切片是引用类型;把一个切片传入函数中修改切片中元素的值,那么该切片在函数外的值也会改变,数组则不会
// 2. 切片的长度可以改变,因此,切片是一个可变的数组
// 3. 切片遍历方式和数组一样,并且可以用len()求长度
// 4. cap可以求出slice最大的容量,0 <= len(slice) <= cap(array),其中array是slice引用的数组
// 5. 切片的定义:var 变量名 []类型,比如 var str []string 和 var arr []int (这种方式只是定义了切片,但并没有对切片初始化,所以此时不能直接 arr[0]=1 这样赋值)

示例如下:

// 切片是引用类型的示例1:
package main

import "fmt"

func modifySlice(a []int){    // 该函数的需要传入的参数是切片类型;slice map 和 chan 类型的参数传入的就是地址
    a[1] = 100        // 修改 切片 a 的元素的值后,函数外 a 的值也会改变
}

func testSlice(){
    var s []int = []int{1,2,3}
    modifySlice(s)    // 切片是引用类型,是数组的一个引用(可理解成切片是一个地址);此函数传了一个引用(引用中包含指针);可理解成给该函数传了一个地址
    fmt.Println(s)
}

func main(){
    testSlice()
}


// 运行结果如下:
[root@NEO example05_slice_ref_type]# go run main/main.go
[1 100 3]        // 函数外切片的值也发生了改变
[root@NEO example05_slice_ref_type]#

// 切片是引用类型的示例2:
package main

import "fmt"

func testSliceRefArr(){
    var a = [10]int{1,2,3}    // 声明并初始化一个数组
    b := a[1:4]        // 声明并初始化一个切片

    fmt.Printf("%p\n",b)    // %p 表示十六进制表示,可用于打印一个地址
    fmt.Printf("%p\n",&a[1])    // 数组 a 第一个元素的地址
}

func main(){
    testSliceRefArr()
}

// 运行结果如下:
[root@NEO example05_slice_ref_arr]# go run main/main.go
0xc000064008
0xc000064008    // 切片 b  的地址和 数组a第一个元素的地址是一样的;所以 切片是一个地址
[root@NEO example05_slice_ref_arr]# 

// 示例代码:
package main

import "fmt"

func testSlice(){
    var slice []int    // 声明一个数组;只是定义了数组,但并没有对切片初始化,所以此时不能直接 arr[0]=1 这样赋值
    var arr [5]int = [...]int{1,2,3,4,5}    // 声明并初始化一个数组

    slice = arr[2:5]    // 对切片初始化; 顾首不顾尾;可简写成 arr[2:]
    fmt.Println(slice)
    fmt.Println("len:",len(slice))
    fmt.Println("cap:",cap(slice))

    slice = slice[0:1]
    fmt.Println("len:",len(slice))
    fmt.Println("cap:",cap(slice))
}

func main(){
    testSlice()
}
// slice、map、channel 都可以用 make 来初始化

// 运行结果如下:
[root@NEO example05_slice01]# go run main/main.go
[3 4 5]
len: 3
cap: 3
len: 1
cap: 3
[root@NEO example05_slice01]#

切片的创建方式1:通过 数组 创建

// 1. 切片初始化:var slice []int = arr[start:end] ;包含start到end之间的元素,但不包含end 
// 2. var slice []int = arr[0:end]可以简写为 var slice []int=arr[:end]
// 3. var slice []int = arr[start:len(arr)] 可以简写为 var slice[]int = arr[start:]
// 4. Var slice []int = arr[0, len(arr)] 可以简写为 var slice[]int = arr[:]
// 5. 如果要切片最后一个元素去掉,可以这么写: 
       Slice = slice[:len(slice)-1]

切片的创建方式2: 通过make来创建切片

var slice []type = make([]type, len)
slice  := make([]type, len)
slice  := make([]type, len, cap)

用append内置函数操作切片

// 示例:
slice = append(slice, 10)
var a = []int{1,2,3}
var b = []int{4,5,6}
a = append(a, b…)    // b... 表示 切片 b 中的所有元素


// append 的示例:
package main

import "fmt"

func testSlice(){
    var a [5]int = [...]int{1,2,3,4,5}
    b := a[1:]

    fmt.Printf("b=%p a[1]=%p\n",b,&a[1])   // 此时切片 b 的地址和 数组a第一个元素的地址是一样的

    b[1] = 100
    fmt.Println("before a:",a)    // 此时 改变切片 b 的值 数组a 也会改变

    b = append(b,10)
    b = append(b,10)
    b = append(b,10)
    b = append(b,10)
    b = append(b,10)
    b = append(b,10)    // 添加了5个元素后,已经超过了切片 b 的容量

    fmt.Println(b)
    fmt.Printf("b=%p a[1]=%p\n",b,&a[1])    // 此时切片 b 的地址和 数组a第一个元素的地址就不再一样;因为在声明并定义切片b时没有超过b的容量,此时b指向原来的数组,当超过b的容量后,系统会重新开辟一块内存,并把切片b原有的值放到新开辟的内存中,然再把追加的内容放进去
    
    b[1] = 1000
    fmt.Println("after a:",a)    // 此时改变 b 值, a的值将不会改变,因为 切片 b 指向的内存地址已经不是 a 了
}

func main(){
    testSlice()       // 切片是可变的,就是因为内部会根据容量的大小重新分配内存
}

// 运行结果如下:
[root@NEO example05_slice_beyond_cap]# go run main/main.go
b=0xc000014158 a[1]=0xc000014158
before a: [1 2 100 4 5]                // a 中对应的值也发生了改变
[2 100 4 5 10 10 10 10 10 10]
b=0xc000062080 a[1]=0xc000014158
after a: [1 2 100 4 5]                // 此时 a 中的值并没有改变
[root@NEO example05_slice_beyond_cap]# 

切片resize

var a = []int {1,3,4,5}
b := a[1:2]
b = b[0:3]

切片拷贝:copy()

s1 := []int{1,2,3,4,5}
s2 := make([]int, 10)
copy(s2, s1)    // 把 切片 s1 拷贝到 切片 s2 中;拷贝不会扩容

// 示例代码:
package main

import "fmt"

func testCopy(){
    var a []int = []int{1,2,3,4,5}
    b := make([]int,10)
    c := make([]int,1)

    copy(b,a)    // 把a拷贝到b中
    copy(c,a)    // 把a拷贝到c中

    fmt.Println(b)
    fmt.Println(c)    // 拷贝不会扩容
}

func main(){
    testCopy()
}

// 运行结果如下:
[root@NEO example05_slice_copy]# go run main/main.go
[1 2 3 4 5 0 0 0 0 0]
[1]
[root@NEO example05_slice_copy]# 

切片示例:使用非递归的方式实现斐波那契数列,打印前10个数。

// 示例代码如下:
package main

import "fmt"

func fab(n int){
    var a []int           // 声明一个切片;[]中没有数字,所以是切片
    a = make([]int,n)    // 为切片a 分配内存

    a[0] = 1
    a[1] = 1

    for i := 2; i < len(a); i++ {
    a[i] = a[i-1] + a[i-2]
    }
    
    for _,v := range a{
    fmt.Println(v)
    }
}

func main() {
    fab(10)
}

// 运行结果如下:
[root@NEO example04_fab_slice]# go run main/main.go
1
1
2
3
5
8
13
21
34
55
[root@NEO example04_fab_slice]#
string与slice
string底层就是一个byte的数组,因此,也可以进行切片操作
str := “hello world”
s1 := str[0:5]
fmt.Println(s1)

s2 := str[5:]
fmt.Println(s2)

string本身是不可变的,因此要改变string中字符,需要如下操作:先把字符串转换成数组

str := “hello world”
s := []byte(str)
s[0] = ‘o’
str = string(s)

示例代码:

// 示例代码如下:
package main

import "fmt"

func strModifyByte(){
    str := "hello world"
    s := []byte(str)    // 把字符串转换为 byte 数组
    fmt.Println(s)

    s[0] = 'o'
    str = string(s)    // 再把 byte 数组转换为字符串
    fmt.Println(str)
}

func strChangeRune(){
    str := "hello 中国"
    s := []rune(str)    // 把字符串转换为 字符 数组 (可以转换中文)
    fmt.Println(s)

    s[5] = ''        // 字符类型 一定要用 单引号
    str = string(s)    // 把 字符数组 转换成字符串
    fmt.Println(str)
}

func main(){
    strModifyByte()
    strChangeRune()
}


// 运行结果如下:
[root@NEO example05_string_change]# go run main/main.go
[104 101 108 108 111 32 119 111 114 108 100]
oello world
[104 101 108 108 111 32 20013 22269]
hello大中国
[root@NEO example05_string_change]# 

排序和查找操作

// 排序操作主要都在 sort包中,导入就可以使用了
    import(“sort”)
// sort.Ints对整数进行排序, sort.Strings对字符串进行排序, sort.Float64s对浮点数进行排序.

// 查找
sort.SearchInts(a []int, b int) 从数组a中查找b,前提是a必须有序
sort.SearchFloats(a []float64, b float64) 从数组a中查找b,前提是a必须有序
sort.SearchStrings(a []string, b string) 从数组a中查找b,前提是a必须有序

示例代码:

// 示例代码:
package main

import (
    "fmt"
    "sort"
)

func testSortInt(){
    var a = [...]int{1, 8, 38, 2, 348, 484}
    sort.Ints(a[:])    // 数组是值类型,所以对数组排序时需要传入其切片
    fmt.Println(a)
}

func testSortStrings(){
    var a = [...]string{"abc", "efg", "b", "A", "eeee"}
    sort.Strings(a[:])
    fmt.Println(a)
}

func testSortFloat(){
    var a = [...]float64{2.3, 0.8, 28.2, 392342.2, 0.6}
    sort.Float64s(a[:])
    fmt.Println(a)
}

func testSearchInts(){
    var a = [...]int{1, 8, 38, 2, 348, 484}
    sort.Ints(a[:])    // 搜索前需要先将数组排序;如果没有先排序,搜索到的索引值也是排序后的索引值
    index := sort.SearchInts(a[:],348)        // 传入切片
    fmt.Println(index)
}


func main(){
    testSortInt()
    testSortStrings()
    testSortFloat()
    testSearchInts()
}

// 运行结果如下:
[root@NEO example05_sort_search]# go run main/main.go
[1 2 8 38 348 484]
[A abc b eeee efg]
[0.6 0.8 2.3 28.2 392342.2]
4
[root@NEO example05_sort_search]# 

 

map数据结构

map简介:key-value的数据结构,又叫字典或关联数组
map 声明:
var map1 map[keytype]valuetype
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string        // 声明是不会分配内存的,初始化需要make


map相关操作
var a map[string]string = map[string]string{“hello”: “world”}
a = make(map[string]string, 10)
a[“hello”] = "world"    // 插入和更新
val, ok := a[“hello”]    // 查找; val 和 ok 都是变量名
for k, v := range a {    // 遍历
fmt.Println(k,v)
}
delete(a, “hello”)        // 删除 "hello" 这个key 对应的 k-v;删除map所有元素可以用  for 循环
len(a)        // 长度

示例代码:

// 示例代码:
package main

import "fmt"

func testMap(){
    var a map[string]string    // 声明、定义一个map;声明是不会分配内存的,初始化需要make(初始化会分配内存)
    a = make(map[string]string,10)    // 初始化
    a["a"] = "NEO"    // 未初始化的 map 不能直接使用(如这一步的赋值操作)
    fmt.Println(a)

    var b map[string]string = map[string]string{    // 声明并初始化
    "b":"neo",
    }
    fmt.Println(b)

    c := make(map[string]string,3)    // 声明并初始化
    fmt.Println(c)

    e := map[string]string{
    "key":"val",
    }
    fmt.Println(e)
    
    d := make(map[string]map[string]string,100)     // map 中嵌套 map;此处的map声明并初始化只是对最外层的 map 进行了 初始化,里面的 map 只进行了声明,但并未初始化
    d["key1"] = make(map[string]string,100)    // 在对里面那个 map 进行操作之前,需要先对里面的 map 初始化
    d["key1"]["key2"] = "abc"    // 对里面的map赋值
    d["key1"]["key3"] = "de"
    fmt.Println(d)

}

func testMapSearch(a map[string]map[string]string){
    val,is_existed := a["zhangsan"]    // 在 map a 中查找 "zhangsan" 这个key 是否存在;存在的话把"zhangsan"对应的值返回val,is_existed 为 true,不存在的话, is_existed 为 false
    fmt.Println(val,is_existed)

    if is_existed {
    val["nickname"] = "胖哥"
      val["passwd"] = "123456"
    } else {
    a["zhangsan"] = make(map[string]string)       // map 中不存在 "zhangsan" 这个 key,则在对其操作前要先对其初始化
    a["zhangsan"]["nickname"] = "胖哥"
        a["zhangsan"]["passwd"] = "123456"
    }
}

func main(){
    testMap()
    
    a := make(map[string]map[string]string,100)
    testMapSearch(a)
    fmt.Println("testMapSearch:",a)
}
// 值类型的数据定义(声明)之后就能直接使用,但 map 这种引用类型的数据定义之后一定要初始化之后才能使用

// 运行结果如下:
[root@NEO example06_map_var]# go run main/main.go
map[a:NEO]
map[b:neo]
map[]
map[key:val]
map[key1:map[key2:abc key3:de]]
map[] false
testMapSearch: map[zhangsan:map[nickname:胖哥 passwd:123456]]
[root@NEO example06_map_var]# 

map是引用类型

func modify(a map[string]int) {
    a[“one”] = 134        // 函数外 a 的值也会改变
}

slice of map

items := make([]map[int][int], 5)
For i := 0; i < 5; i++ {
    items[i] = make(map[int][int])        // 全部初始化;也可以用到哪个的时候再对哪个初始化
}

// map 和 slice 等是否为空用 nil 来判断

map排序

a. 先获取所有key,把key进行排序 (// 每次遍历时,map 的 key的顺序都是不一样的 (key是无序的))
b. 按照排序好的key,进行遍历

示例代码:

// 示例代码:
package main

import (
    "fmt"
    "sort"
)

func testMapSort(){
    var a map[int]string
    a = make(map[int]string,100)

    a[2] = "hello"
    a[1] = "中国"
    a[25] = "aaa"
    a[8] = "abc"
    a[72] = "world"

    // 先获取所有key,把key进行排序
    var keys []int
    for k,_ := range a{
    keys = append(keys,k)
    }
    sort.Ints(keys)

    // 按照排序好的key,进行遍历
    for _,v := range keys{
    fmt.Println(v,a[v])
    }
}

func main(){
    testMapSort()
}

// 运行结果如下:
[root@NEO example06_map_sort]# go run main/main.go
1 中国
2 hello
8 abc
25 aaa
72 world
[root@NEO example06_map_sort]# 

Map反转

// 初始化另外一个map,把key、value互换即可

 

a. import(“sync”)
b. 互斥锁, var mu sync.Mutex
c. 读写锁, var mu sync.RWMutex

// 互斥锁:同一时间只有一个 goroutine 能进去 (不论读写);适用于 写操作多的场景
// 读写锁:多个 goroutine 可同时进行读操作(一个协程进去加了个读锁,后面的读协程也可以进去,但写协程进不去),但写协程还是互斥的;适用于 读多写少的场景

示例代码:

// 示例代码:
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "sync/atomic"
    "time"
)

var lock sync.Mutex        // 互斥锁
var rwLock sync.RWMutex        // 读写锁

func testMap() {
    var a map[int]int
    a = make(map[int]int, 5)

    a[8] = 10
    a[3] = 10
    a[2] = 10
    a[1] = 10
    a[18] = 10

    for i := 0; i < 2; i++ {
        go func(b map[int]int) {    // 匿名函数
            lock.Lock()    
            b[8] = rand.Intn(100)    // 写操作;中间的代码加锁
            lock.Unlock()
        }(a)
    }

    lock.Lock()
    fmt.Println(a)        // 读操作;中间的代码加锁
    lock.Unlock()

    time.Sleep(time.Second)
}

func testRWLock() {
    var a map[int]int
    a = make(map[int]int, 5)
    var count int32
    a[8] = 10
    a[3] = 10
    a[2] = 10
    a[1] = 10
    a[18] = 10

    for i := 0; i < 2; i++ {
        go func(b map[int]int) {
            rwLock.Lock()
            b[8] = rand.Intn(100)
            time.Sleep(10 * time.Millisecond)
            rwLock.Unlock()
        }(a)
    }

    for i := 0; i < 100; i++ {
        go func(b map[int]int) {
            for {
                rwLock.RLock()
                time.Sleep(time.Millisecond)
                //fmt.Println(a)
                rwLock.RUnlock()
                atomic.AddInt32(&count, 1)        // 修改公共数据 count : 进行原子性 写操作
            }
        }(a)
    }
    time.Sleep(time.Second * 3)
    fmt.Println(atomic.LoadInt32(&count))        // 读取进行原子性的数据
}

func main() {
    //testMap()
    testRWLock()
}

 

转载于:https://www.cnblogs.com/neozheng/p/11235338.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值