• GoLang基础语法 + 面向对象


    Golang 的优势

    极简单的部署方式:可直接编译成机器码、不依赖其他库、直接运行即可部署。

    静态类型语言,编译的时候可以检查出大多数问题。

    语言层面的并发:天生的基因支持、充分的利用多核

    func goFunc(i int) {
    	fmt.Println("goroutine ", i, " ...")
    }
    
    func main() {
    	for i := 0; i < 1000; i++ {
    		go goFunc(i) 
    	}
    	time.Sleep(time.Second)
    }
    

    强大的标准库:runtime 系统调度机制、高效的 CG 垃圾回收、丰富的标准库

    “大厂” 领军:Google、facebook、Tencent、Baidu、七牛、字节 …

    不同语言的斐波那契数列算法 编译 + 运行 时间对比:

    Golang 的应用场景

    1、云计算基础设施领域

    代表项目:docker、kubernetes、etcd、consul、cloud flare CDN、七牛云存储 等。

    2、基础后端软件

    代表项目:tidb、influxdb、 cockroach 等。

    3、微服务

    代表项目:go-kit、 micro、 monzo bank 的 typhon、bilibili 等。

    4、互联网基础设施

    代表项目:以太坊、hyperledger 等。

    Golang 明星作品:DockerKubernetes

    Golang 的不足

    1、包管理,大部分包都托管在 Github 上。

    像我们熟悉的 maven、npm 等都有专门的包管理组织;

    托管在 Github 上的代码容易被作者个人操作影响到使用该项目的工程。

    2、无泛化类型。

    据说很快就会有了。

    3、所有 Exception 都用 Error 来处理(有争议)。

    4、对 C 的降级处理,并非无缝,没有 C 降级到 asm 那么完美。(序列化问题)

    main

    package main
    
    
    import (
    	"fmt"
    	"time"
    )
    
    
    func main() { 
    	fmt.Println("Hello Go!")
    	time.Sleep(1 * time.Second)
    }
    

    变量

    输出变量类型的方法

    var a int
    fmt.Printf("type of a = %T\n", a)
    
    

    局部变量的声明:

    var a int 
    
    
    var b int = 100
    
    
    var c = 100
    
    
    d := 100
    

    全局变量的声明:以上只有方法四不支持(编译会报错)

    多变量的声明:

    var xx, yy int = 100, 200
    var kk, ll = 100, "Aceld"
    
    
    var (
      vv int  = 100
      jj bool = true
    )
    

    常量与 iota

    使用 const 定义常量,常量是只读的,不允许修改。

    const a int = 10
    
    const (
    	a = 10
      b = 20
    )
    

    const 可以用来定义枚举:

    const {
      BEIJING = 0
      SHANGHAI = 1
      SHENZHEN = 3
    }
    

    const 可以和 iota 一起使用来定义有规则的枚举

    const (
    	
    	BEIJING = iota	
    	SHANGHAI 		  	
    	SHENZHEN      	
    )
    
    const (
    	a, b = iota+1, iota+2 
    	c, d				  
    	e, f				  
    
    	g, h = iota * 2, iota *3  
    	i, k					   
    )
    

    string

    对于字符串操作的 4 个包:bytes、strings、strconv、unicode

    • bytes 包操作 []byte。因为字符串是只读的,因此逐步构创建字符串会导致很多分配和复制,使用 bytes.Buffer 类型会更高。
    • strings 包提供 切割、索引、前缀、查找、替换 等功能。
    • strconv 包提供 布尔型、整型数、浮点数 和对应字符串的相互转换,还提供了双引号转义相关的转换。
    • unicode 包提供了 IsDigit、IsLetter、IsUpper、IsLower 等类似功能,用于给字符分类。

    如果 string 中包含汉字,要注意:

    • UTF-8 编码中,一个汉字需要 3 个字节,通过 len() 获取的是字符串占据的字节数
    str1 := "hello 世界"
    fmt.Println(len(str1)) 
    
    • 如果想要得到字符串本身的长度,可以将 string 转为 rune 数组再计算:
    str2 := "hello 世界"
    fmt.Println(len([]rune(str2))) 
    

    字符串遍历

    byte 是 uint8 的别名

    rune 是 int32 的别名,相当于 Go 里面的 char

    如果包含汉字,以下遍历方式会出现乱码:

    str := "你好世界!"
    
    for i := 0; i < len(str); i++ {
      fmt.Printf("%c", str[i])
    }
    
    
    • 解决方案 1:转成 rune 切片再遍历
    str := "你好世界!"
    newStr := []rune(str)
    for i := 0; i < len(newStr); i++ {
      fmt.Printf("%c", newStr[i])
    }
    
    
    • 解决方案 2:使用 range 来遍历

    range 按照字符遍历,前面的 for 按照字节遍历

    str := "你好世界123"
    for index, value := range str {
      fmt.Printf("index = %d value = %c\n", index, value)
    }
    
    index = 0 value = 你
    index = 3 value = 好
    index = 6 value = 世
    index = 9 value = 界
    index = 12 value = 1
    index = 13 value = 2
    index = 14 value = 3
    

    strings 包

    字符串比较:使用 strings.Compare 比较两个字符串的字典序

    strings.Compare("aaa", "bbb") 
    strings.Compare("baa", "abb") 
    strings.Compare("aaa", "aaa") 
    

    查找函数:使用 strings.Index 查找字符串中子串的位置(第 1 个),不存在返回 -1

    strings.Index("hello world", "o") 
    

    类似的,使用 strings.LastIndex 查找字符串子串出现的最后一个位置,不存在返回 -1

    strings.Index("hello world", "o") 
    

    Count、Repeat

    使用 strings.Count 统计子串在整体中出现的次数:

    strings.Count("abc abc abab abc", "abc") 
    

    使用 strings.Repeat 将字符串重复指定次数:

    strings.Repeat("abc", 3) 
    

    Replace、Split、Join

    strings.Replace 实现字符串替换

    str := "acaacccc"
    
    
    strings.Replace(str, "a", "b", 2)  
    strings.Replace(str, "a", "b", -1) 
    
    
    strings.ReplaceAll(str, "a", "b")  
    

    strings.Split 实现字符串切割

    str := "abc,bbc,bbd"
    
    slice := strings.Split(str, ",")
    fmt.Println(slice) 
    

    strings.Join 实现字符串拼接

    slice := []string{"aab", "aba", "baa"}
    
    str := strings.Join(slice, ",")
    fmt.Println(str 
    

    bytes 包

    Buffer 是 bytes 包中定义的 type Buffer struct {...},Bufer 是一个变长的可读可写的缓冲区。

    创建缓冲器bytes.NewBufferStringbytes.NewBuffer

    func main() {
    	buf1 := bytes.NewBufferString("hello")
    	buf2 := bytes.NewBuffer([]byte("hello"))
    	buf3 := bytes.NewBuffer([]byte{'h', 'e', 'l', 'l', 'o'})
    
    	fmt.Printf("%v,%v,%v\n", buf1, buf2, buf3)
    	fmt.Printf("%v,%v,%v\n", buf1.Bytes(), buf2.Bytes(), buf3.Bytes())
    
    	buf4 := bytes.NewBufferString("")
    	buf5 := bytes.NewBuffer([]byte{})
    	fmt.Println(buf4.Bytes(), buf5.Bytes())
    }
    
    hello,hello,hello
    [104 101 108 108 111],[104 101 108 108 111],[104 101 108 108 111]
    [] []
    

    写入缓冲器WriteWriteStringWriteByteWriteRuneWriteTo

    func main() {
    	buf := bytes.NewBufferString("a")
    	fmt.Printf("%v, %v\n", buf.String(), buf.Bytes())
    	
    
    	buf.Write([]byte("b")) 
    	buf.WriteString("c")   
    	buf.WriteByte('d')     
    	buf.WriteRune('e')     
    	fmt.Printf("%v, %v\n", buf.String(), buf.Bytes())
    	
    }
    

    缓冲区原理介绍:Go 字节缓冲区底层以字节切片做存储,切片存在长度 len 与容量 cap

    • 缓冲区从长度 len 的位置开始写,当 len > cap 时,会自动扩容
    • 缓冲区从内置标记 off 位置开始读(off 始终记录读的起始位置)
    • 当 off == len 时,表明缓冲区已读完,读完就重置缓冲区 len = off = 0
    func main() {
    	byteSlice := make([]byte, 20)
    	byteSlice[0] = 1                                  
    	byteBuffer := bytes.NewBuffer(byteSlice)          
    	c, _ := byteBuffer.ReadByte()                     
    	fmt.Printf("len:%d, c=%d\n", byteBuffer.Len(), c) 
    	byteBuffer.Reset()                                
    	fmt.Printf("len:%d\n", byteBuffer.Len())          
    	byteBuffer.Write([]byte("hello byte buffer"))     
    	fmt.Printf("len:%d\n", byteBuffer.Len())          
    	byteBuffer.Next(4)                                
    	c, _ = byteBuffer.ReadByte()                      
    	fmt.Printf("第5个字节:%d\n", c)                       
    	byteBuffer.Truncate(3)                            
    	fmt.Printf("len:%d\n", byteBuffer.Len())          
    	byteBuffer.WriteByte(96)                          
    	byteBuffer.Next(3)                                
    	c, _ = byteBuffer.ReadByte()                      
    	fmt.Printf("第9个字节:%d\n", c)                       
    }
    

    缓冲区:

    func main() {
    	buf := &bytes.Buffer{}
    	
    	buf.WriteString("abc?def")
    	
    	str, _ := buf.ReadString('?')
    
    	fmt.Println("str = ", str)
    	fmt.Println("buff = ", buf.String())
    }
    
    str =  abc?
    buff =  def
    

    缓冲区读数据ReadReadByteReadByesReadStringReadRuneReadFrom

    func main() {
    	log.SetFlags(log.Lshortfile)
    	buff := bytes.NewBufferString("123456789")
    	log.Println("buff = ", buff.String()) 
    
    	
    	s := make([]byte, 4)
    	n, _ := buff.Read(s)
    	log.Println("buff = ", buff.String()) 
    	log.Println("s = ", string(s))        
    	log.Println("n = ", n)                
    
    	
    	n, _ = buff.Read(s)
    	log.Println("buff = ", buff.String()) 
    	log.Println("s = ", string(s))        
    	log.Println("n = ", n)                
    
    	n, _ = buff.Read(s)
    	log.Println("buff = ", buff.String()) 
    	log.Println("s = ", string(s))        
    	log.Println("n = ", n)                
    
    	buff.Reset()
    	buff.WriteString("abcdefg")
    	log.Println("buff = ", buff.String()) 
    
    	b, _ := buff.ReadByte()
    	log.Println("b = ", string(b))        
    	log.Println("buff = ", buff.String()) 
    
    	b, _ = buff.ReadByte()
    	log.Println("b = ", string(b))        
    	log.Println("buff = ", buff.String()) 
    
    	bs, _ := buff.ReadBytes('e')
    	log.Println("bs = ", string(bs))      
    	log.Println("buff = ", buff.String()) 
    
    	buff.Reset()
    	buff.WriteString("编译输出GO")
    	r, l, _ := buff.ReadRune()
    	log.Println("r = ", r, ", l = ", l, ", string(r) = ", string(r))
    	
    
    	buff.Reset()
    	buff.WriteString("qwer")
    	str, _ := buff.ReadString('?')
    	log.Println("str = ", str)            
    	log.Println("buff = ", buff.String()) 
    
    	buff.WriteString("qwer")
    	str, _ = buff.ReadString('w')
    	log.Println("str = ", str)            
    	log.Println("buff = ", buff.String()) 
    
    	file, _ := os.Open("doc.go")
    	buff.Reset()
    	buff.ReadFrom(file)
    	log.Println("doc.go = ", buff.String()) 
    
    	buff.Reset()
    	buff.WriteString("中国人")
    	cbyte := buff.Bytes()
    	log.Println("cbyte = ", cbyte) 
    }
    

    strconv 包

    字符串转 []byte

    sum := []byte("hello")
    

    字符串 —> 整数:使用 strconv.Atoi 或 strconv.ParseInt

    i, _ := strconv.Atoi("33234")
    fmt.Printf("%T\n", i) 
    
    
    
    
    i2, _ := strconv.ParseInt("33234", 10, 0)
    fmt.Printf("%T\n", i2) 
    

    字符串 —> 浮点数:使用 strconv.ParseFloat

    val, _ := strconv.ParseFloat("33.33", 32)
    fmt.Printf("type: %T\n", val) 
    
    val2, _ := strconv.ParseFloat("33.33", 64)
    fmt.Printf("type: %T\n", val2) 
    

    整数 —> 字符串:使用 strconv.Iota 或 strconv.FormatInt

    num := 180
    
    
    f1 := strconv.Itoa(num)
    
    
    
    f2 := strconv.FormatInt(int64(num), 10)
    

    浮点数 —> 整数:使用 strconv.FormatFloat

    num := 23423134.323422
    fmt.Println(strconv.FormatFloat(float64(num), 'f', -1, 64)) 
    fmt.Println(strconv.FormatFloat(float64(num), 'b', -1, 64)) 
    fmt.Println(strconv.FormatFloat(float64(num), 'e', -1, 64)) 
    fmt.Println(strconv.FormatFloat(float64(num), 'E', -1, 64)) 
    fmt.Println(strconv.FormatFloat(float64(num), 'g', -1, 64)) 
    fmt.Println(strconv.FormatFloat(float64(num), 'G', -1, 64)) 
    
    23423134.323422
    6287599743057036p-28
    2.3423134323422e+07
    2.3423134323422E+07
    2.3423134323422e+07
    2.3423134323422E+07
    

    字符串 和 bool 类型转换

    flagBool, _ := strconv.ParseBool("true")
    
    
    
    
    flagStr := strconv.FormatBool(true)
    

    unicode 包

    /src/unicode/letter.go

    func IsUpper(r rune) bool
    
    
    func IsLower(r rune) bool
    
    
    
    
    
    func IsTitle(r rune) bool
    
    
    func ToUpper(r rune) rune
    
    
    func ToLower(r rune) rune
    
    
    
    
    func ToTitle(r rune) rune
    
    
    
    func To(_case int, r rune) rune
    

    /src/unicode/digit.go

    func IsDigit(r rune) bool
    

    /src/unicode/graphic.go

    func IsNumber(r rune) bool
    
    
    
    func IsLetter(r rune) bool
    
    
    
    
    
    func IsSpace(r rune) bool
    
    
    
    
    func IsControl(r rune) bool
    
    
    
    
    
    func IsGraphic(r rune) bool
    
    
    
    
    
    
    func IsPrint(r rune) bool
    
    
    func IsPunct(r rune) bool
    
    
    func IsSymbol(r rune) bool
    
    
    func IsMark(r rune) bool
    
    
    func IsOneOf(set []*RangeTable, r rune) bool
    

    循环语句

    go 语言中的 for 循环有 3 种形式:

    for init; condition; post { }
    for condition { }
    for { }
    
    func main() {
    	numbers := [6]int{1, 2, 3, 5}
    
    	for i := 0; i < len(numbers); i++ {
    		fmt.Println(numbers[i])
    	}
    
    	i := 0
    	for i < len(numbers) {
    		fmt.Println(numbers[i])
    		i++
    	}
    
    	for i, x := range numbers {
    		fmt.Printf("index: %d, value: %d\n", i, x)
    	}
    
      
    	for {
    		fmt.Println("endless...")
    	}
    }
    

    range

    func main() {
    	numbers := []int{1, 2, 3, 4, 5, 6}
    
    	
    	for i := range numbers {
    		fmt.Println(numbers[i])
    	}
    
    	
    	for _, n := range numbers {
    		fmt.Println(n)
    	}
    
    	
    	for range numbers {
    	}
    
    	m := map[string]int{"a": 1, "b": 2}
    	for k, v := range m {
    		fmt.Println(k, v)
    	}
    
    }
    

    注意:range 会复制对象

    func main() {
    	a := [3]int{0, 1, 2}
    	for i, v := range a { 
    		if i == 0 { 
    			a[1], a[2] = 999, 999
    			fmt.Println(a) 
    		}
    		a[i] = v + 100 
    	}
    	fmt.Println(a) 
    }
    

    函数

    多返回值

    单返回值的函数:

    func foo1(a string, b int) int {
    	return 100
    }
    

    多返回值的函数:

    func foo2(a string, b int) (int, int) {
    	return 666, 777
    }
    
    
    func foo3(a string, b int) (r1 int, r2 int) {
    	
    	
    	fmt.Println("r1 = ", r1) 
    	fmt.Println("r2 = ", r2) 
    
    	
    	r1 = 1000
    	r2 = 2000
    
    	return
    }
    
    func foo4(a string, b int) (r1, r2 int) {
    	
    	r1 = 1000
    	r2 = 2000
    
    	return
    }
    

    init 函数

    每个 go 程序都会在一开始执行 init() 函数,可以用来做一些初始化操作:

    package main
    
    import "fmt"
    
    func init() {
    	fmt.Println("init...")
    }
    
    func main() {
    	fmt.Println("hello world!")
    }
    
    init...
    hello world!
    

    如果一个程序依赖了多个包,它的执行流程如下图:

    制作包的时候,项目路径如下:

    $GOPATH/GolangStudy/5-init/ 
    ├── lib1/
    │ └── lib1.go
    ├── lib2/
    │ └── lib2.go 
    └── main.go
    
    lib1 .init() ...
    lib2 .init() ...
    lib1Test()
    lib2Test()
    

    闭包

    func a() func() int {
    	i := 0
    	b := func() int {
    		i++
    		fmt.Println(i)
    		return i
    	}
    	return b
    }
    
    func main() {
    	c := a()
    	c() 
    	c() 
    	c() 
    
    	a() 
    }
    

    import 导包

    • import _ "fmt"

      给 fmt 包一个匿名, ⽆法使用该包的⽅法,但是会执行该包内部的 init() 方法

    • import aa "fmt"

      给 fmt 包起一个别名 aa,可以用别名直接调用:aa.Println()

    • import . "fmt"

      将 fmt 包中的全部方法,导入到当前包的作用域中,全部方法可以直接调用,无需 fmt.API 的形式

    匿名函数

    匿名函数的使用:

    func main() {
    	res := func(n1 int, n2 int) int {
    		return n1 * n2
    	}(10, 20)
    
    	fmt.Printf("res: %v\n", res)
    }
    

    将匿名函数赋值给变量,通过变量调用:

    func main() {
    	ret := func(n1 int, n2 int) int {
    		return n1 + n2
    	}
    	
    	sum := ret(100, 20)
    	fmt.Printf("sum: %v\n", sum)
    	
    	sum2 := ret(1000, 30)
    	fmt.Printf("sum2: %v\n", sum2)
    }
    

    指针

    经典:在函数中交换两数的值

    func swap(pa *int, pb *int) {
    	var temp int
    	temp = *pa
    	*pa = *pb
    	*pb = temp
    }
    
    func main() {
    	var a, b int = 10, 20
    
    	swap(&a, &b) 
    
    	fmt.Println("a = ", a, " b = ", b)
    } 
    

    defer

    defer 声明的语句会在当前函数执行完之后调用:

    func main() {
    	defer fmt.Println("main end")
    	fmt.Println("main::hello go ")
    }
    
    main::hello go 
    main end
    

    如果有多个 defer,依次入栈,函数返回后依次出栈执行:

    上图执行顺序:func3() -> func2() -> func1()

    关于 defer 和 return 谁先谁后:

    func deferFunc() int {
    	fmt.Println("defer func called...")
    	return 0
    }
    
    func returnFunc() int {
    	fmt.Println("return func called...")
    	return 0
    }
    
    func returnAndDefer() int {
    	defer deferFunc()
    	return returnFunc()
    }
    
    func main() {
    	returnAndDefer()
    }
    
    return func called...
    defer func called...
    

    结论:return 之后的语句先执⾏,defer 后的语句后执⾏

    切片 slice

    Golang 默认都是采用值传递,有些值天生就是指针:slice、map、channel。

    注意:定长数组是值传递,slice 是指针传递

    数组

    声明数组的方式:(固定长度的数组)

    var array1 [10]int
    array2 := [10]int{1,2,3,4}
    array3 := [4]int{1,2,3,4}
    

    数组的长度是固定的,并且在传参的时候,严格匹配数组类型

    func printArray(myArray [4]int) {
    	fmt.Println(myArray) 
    	myArray[0] = 666     
    }
    
    func main() {
    	myArray := [4]int{1, 2, 3, 4}
    	printArray(myArray)
    	fmt.Println(myArray) 
    }
    

    myArray := [...]int{1, 2, 3, 4} 是自动计算数组长度,但并不是引用传递。

    声明动态数组和声明数组一样,只是不用写长度。

    func printArray(myArray []int) {
    	fmt.Println(myArray) 
    	myArray[0] = 10      
    }
    
    func main() {
    	myArray := []int{1, 2, 3, 4}
    	printArray(myArray)
    	fmt.Println(myArray) 
    }
    

    slice

    slice 的声明方式:通过 make 关键字

    slice1 := []int{1, 2, 3} 
    
    
    var slice2 []int 
    
    slice2 = make([]int, 3) 
    
    
    var slice3 []int = make([]int, 3) 
    
    
    slice4 := make([]int, 3) 
    

    len() 和 cap() 函数:

    • len:长度,表示左指针⾄右指针之间的距离。
    • cap:容量,表示指针至底层数组末尾的距离。

    切⽚的扩容机制,append 的时候,如果长度增加后超过容量,则将容量增加 2 倍。

    var numbers = make([]int, 3, 5)
    fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
    
    
    numbers = append(numbers, 1)
    fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
    
    
    numbers = append(numbers, 2)
    fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
    
    
    numbers = append(numbers, 3)
    fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
    
    len = 3, cap = 5, slice = [0 0 0]
    len = 4, cap = 5, slice = [0 0 0 1]
    len = 5, cap = 5, slice = [0 0 0 1 2]
    len = 6, cap = 10, slice = [0 0 0 1 2 3]
    

    slice 操作

    slice 截取是浅拷贝,若想深拷贝需要使用 copy

    可以通过设置下限以及上限设置截取切片 [lower-bound: upper-bound],实例:

    func main() {
    	
    	numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
    	fmt.Println(numbers)
    
    	
    	fmt.Println("number ==", numbers)
    
    	
    	fmt.Println("numbers[1:4] ==", numbers[1:4])
    
    	
    	fmt.Println("numbers[:3] ==", numbers[:3])
    
    	
    	fmt.Println("numbers[4:] ==", numbers[4:])
    
    	numbers1 := make([]int, 0, 5)
    	fmt.Println(numbers1)
    
    	
    	numbers2 := numbers[:2]
    	fmt.Println(numbers2)
    
    	
    	numbers3 := numbers[2:5]
    	fmt.Println(numbers3)
    }
    
    [0 1 2 3 4 5 6 7 8]
    number == [0 1 2 3 4 5 6 7 8]
    numbers[1:4] == [1 2 3]
    numbers[:3] == [0 1 2]
    numbers[4:] == [4 5 6 7 8]
    []
    [0 1]
    [2 3 4]
    

    利用 copy 函数拷贝切片,是深拷贝。

    slice1 := []int{1, 2, 3}
    slice2 := make([]int, 3)
    copy(slice2, slice1)
    slice2[0] = 10
    fmt.Println(slice1) 
    

    直接赋值切片,是浅拷贝。

    slice1 := []int{1, 2, 3}
    slice2 := slice1
    slice2[0] = 10
    fmt.Println(slice1) 
    

    ... 是 Go 的一种语法糖。

    • 用法 1:函数可以用来接受多个不确定数量的参数。
    • 用法 2:slice 可以被打散进行传递。
    func test(args ...string) {
    	for _, v := range args {
    		fmt.Println(v)
    	}
    }
    
    func main() {
    	var ss = []string{
    		"abc",
    		"efg",
    		"hij",
    		"123",
    	}
    	test(ss...)
    }
    

    map

    slice、map、channel 都是引用类型,声明后还需要初始化分配内存,即 make

    map 的声明

    map 的第一种声明方式:

    var myMap1 map[string]string
    fmt.Println(myMap1 == nil) 
    
    myMap1 = make(map[string]string, 10)
    
    myMap1["one"] = "java"
    myMap1["two"] = "c++"
    myMap1["three"] = "python"
    
    fmt.Println(myMap1)
    
    

    map 的第二种声明方式:

    myMap2 := make(map[int]string)
    myMap2[1] = "java"
    myMap2[2] = "c++"
    myMap2[3] = "python"
    
    fmt.Println(myMap2)
    
    

    map 的第三种声明方式:

    myMap3 := map[string]string {
      "one":   "php",
      "two":   "c++",
      "three": "python",
    }
    
    fmt.Println(myMap3)
    
    

    map 的使用

    func printMap(cityMap map[string]string) {
    	for key, value := range cityMap {
    		fmt.Println("key = ", key+", value = ", value)
    	}
    }
    
    func AddValue(cityMap map[string]string) {
    	
    	cityMap["England"] = "London"
    }
    
    func main() {
    	cityMap := make(map[string]string)
    	
    	cityMap["China"] = "Beijing"
    	cityMap["Japan"] = "Tokyo"
    	cityMap["USA"] = "NewYork"
    	
    	delete(cityMap, "China")
    	
    	printMap(cityMap)
    	fmt.Println("-------")
    
    	
    	cityMap["USA"] = "DC"
    	
    	AddValue(cityMap)
    	
    	printMap(cityMap)
    }
    
    key =  Japan, value =  Tokyo
    key =  USA, value =  NewYork
    -------
    key =  England, value =  London
    key =  Japan, value =  Tokyo
    key =  USA, value =  DC
    

    判断 map 中 key 值是否存在:直接取值,返回有两个返回值,通过第 2 个返回值判断。

    m := make(map[string]interface{})
    m["a"] = "AAA"
    if _, ok := m["ba"]; ok {
      fmt.Println("存在")
    } else {
      fmt.Println("不存在")
    }
    

    error

    捕获系统抛出异常:

    func main() {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("捕获:", err)
    		}
    	}()
    
    	nums := []int{1, 2, 3}
    	fmt.Println(nums[4]) 
      
    }
    

    手动抛出异常并捕获:

    func main() {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("捕获:", err)
    		}
    	}()
    	panic("出现异常!") 
      
    }
    

    返回异常:

    func getCircleArea(radius float32) (area float32, err error) {
    	if radius < 0 {
    		
    		err = errors.New("半径不能为负")
    		return
    	}
    	area = 3.14 * radius * radius
    	return
    }
    
    func main() {
    	area, err := getCircleArea(-5)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(area)
    	}
    }
    

    自定义异常:

    type PathError struct {
    	path       string
    	op         string
    	createTime string
    	message    string
    }
    
    func (p *PathError) Error() string {
    	return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s",
    		p.path, p.op, p.createTime, p.message)
    }
    
    func Open(filename string) error {
    
    	file, err := os.Open(filename)
    	if err != nil {
    		return &PathError{
    			path:       filename,
    			op:         "read",
    			message:    err.Error(),
    			createTime: fmt.Sprintf("%v", time.Now()),
    		}
    	}
    
    	defer file.Close()
    	return nil
    }
    
    func main() {
    	err := Open("test.txt")
    	switch v := err.(type) {
    	case *PathError:
    		fmt.Println("get path error,", v)
    	default:
    	}
    }
    

    type

    利用 type 可以声明某个类型的别名(理解为声明一种新的数据类型)

    type myint int
    
    func main() {
      	var a myint = 10
    		fmt.Println("a = ", a)
    		fmt.Printf("type of a = %T\n", a)
    }
    
    a =  10
    type of a = main.myint
    

    方法

    方法:包含了接受者的函数,接受者可以是命名类型或结构体类型的值或者指针。

    方法和普通函数的区别

    • 对于普通函数,参数为值类型时,不能将指针类型的数据直接传递,反之亦然。

    • 对于方法,接收者为值类型时,可以直接用指针类型的变量调用方法(反过来也可以)。

    func valueIntTest(a int) int {
    	return a + 10
    }
    
    
    func pointerIntTest(a *int) int {
    	return *a + 10
    }
    
    func structTestValue() {
    	a := 2
    	fmt.Println("valueIntTest:", valueIntTest(a))
    	
    	
    	
    
    	b := 5
    	fmt.Println("pointerIntTest:", pointerIntTest(&b))
    	
    	
    	
    }
    
    type PersonD struct {
    	id   int
    	name string
    }
    
    
    func (p PersonD) valueShowName() {
    	fmt.Println(p.name)
    }
    
    
    func (p *PersonD) pointShowName() {
    	fmt.Println(p.name)
    }
    
    func structTestFunc() {
    	
    
    	
    	personValue := PersonD{101, "hello world"}
    	personValue.valueShowName()
    	personValue.pointShowName()
    
    	
    	personPointer := &PersonD{102, "hello golang"}
    	personPointer.valueShowName()
    	personPointer.pointShowName()
    }
    

    struct

    type Book struct {
    	title string
    	price string
    }
    
    func changeBook(book Book) {
    	
    	book.price = "666"
    }
    
    func changeBook2(book *Book) {
    	
    	book.price = "777"
    }
    
    func main() {
    	var book Book
    	book.title = "Golang"
    	book.price = "111"
    	fmt.Printf("%v\n", book) 
    
    	changeBook(book)
    	fmt.Printf("%v\n", book) 
    
    	changeBook2(&book)
    	fmt.Printf("%v\n", book) 
    }
    

    一道 struct 与指针面试题:

    type student struct {
    	name string
    	age  int
    }
    
    func main() {
    	m := make(map[string]*student)
    	stus := []student{
    		{name: "aaa", age: 18},
    		{name: "bbb", age: 23},
    		{name: "ccc", age: 28},
    	}
    	for _, stu := range stus {
    		m[stu.name] = &stu
    	}
    	for k, v := range m {
    		fmt.Println(k, "=>", v.name)
    	}
    }
    
    aaa => ccc
    bbb => ccc
    ccc => ccc
    

    解决方法 1:

    for _, stu := range stus {
      
      temp := stu
      m[stu.name] = &temp
    }
    

    解决方法 2:

    for i, stu := range stus {
      
      m[stu.name] = &stus[i]
    }
    

    封装

    Golang 中,类名、属性名、⽅法名 首字⺟大写 表示对外(其他包)可以访问,否则只能够在本包内访问。

    type Hero struct {
    	
    	Name  string
    	Ad    int
    	level int 
    }
    
    func (h *Hero) Show() {
    	fmt.Println("Name = ", h.Name)
    	fmt.Println("Ad = ", h.Ad)
    	fmt.Println("Level = ", h.level)
    	fmt.Println("---------")
    }
    
    func (h *Hero) GetName() string {
    	return h.Name
    }
    
    
    func (h *Hero) SetName(newName string) {
    	h.Name = newName
    }
    
    func main() {
    	hero := Hero{Name: "zhang3", Ad: 100}
    	hero.Show()
    
    	hero.SetName("li4")
    	hero.Show()
    }
    
    Name =  zhang3
    Ad =  100
    Level =  0
    ---------
    Name =  li4
    Ad =  100
    Level =  0
    ---------
    

    继承

    Golang 通过匿名字段实现继承的效果:

    type Human struct {
    	name string
    	sex  string
    }
    
    func (h *Human) Eat() {
    	fmt.Println("Human.Eat()...")
    }
    
    func (h *Human) Walk() {
    	fmt.Println("Human.Walk()...")
    }
    
    
    type SuperMan struct {
    	Human 
    	level int
    }
    
    
    func (s *SuperMan) Eat() {
    	fmt.Println("SuperMan.Eat()...")
    }
    
    
    func (s *SuperMan) Fly() {
    	fmt.Println("SuperMan.Fly()...")
    }
    
    func main() {
      
      
      var s SuperMan
      s.name = "li4"
      s.sex = "male"
      s.level = 88
    
      s.Walk() 
      s.Eat()  
      s.Fly()  
    }
    
    Human.Walk()...
    SuperMan.Eat()...
    SuperMan.Fly()...
    

    多态

    Go 中接口相关文章:理解 Duck Typing(鸭子模型)

    Golang 中多态的基本要素:

    • 有一个父类(有接口)
    type AnimalIF interface {
    	Sleep()
    	GetColor() string 
    	GetType() string  
    }
    
    • 有子类(实现了父类的全部接口)
    type Cat struct {
    	color string 
    }
    
    func (c *Cat) Sleep() {
    	fmt.Println("Cat is Sleep")
    }
    
    func (c *Cat) GetColor() string {
    	return c.color
    }
    
    func (c *Cat) GetType() string {
    	return "Cat"
    }
    
    • 父类类型的变量(指针)指向(引用)子类的具体数据变量
    var animal AnimalIF
    animal = &Cat{"Green"}
    animal.Sleep() 
    

    不同接收者实现接口

    type Mover interface {
    	move()
    }
    
    type dog struct {
    	name string
    }
    

    值类型接收者实现接口:可以同时接收 值类型 和 指针类型。

    Go 语言中有对指针类型变量求值的语法糖,dog 指针 dog2 内部会自动求值 *dog2

    func (d dog) move() {
    	fmt.Println(d.name, "is moving")
    }
    
    func main() {
    	var m Mover
    
    	var dog1 = dog{"dog1"}
    	m = dog1 
    	m.move()
    
    	var dog2 = &dog{"dog2"}
    	m = dog2 
    	m.move()
    }
    

    指针类型接收者实现接口:只能接收指针类型。

    func (d *dog) move() {
    	fmt.Println(d.name, "is moving")
    }
    
    func main() {
    	var m Mover
    
    	
    	
    	
    	
    
    	var dog2 = &dog{"dog2"}
    	m = dog2
    	m.move()
    }
    

    一道面试题:以下代码能否通过编译?

    type People interface {
    	Speak(string) string
    }
    
    type Student struct{}
    
    func (stu *Student) Speak(think string) (talk string) {
    	if think == "sb" {
    		talk = "你是个大帅比"
    	} else {
    		talk = "您好"
    	}
    	return
    }
    
    func main() {
    	var peo People = Student{}
    	think := "bitch"
    	fmt.Println(peo.Speak(think))
    }
    

    不能。修改 var peo People = Student{} 为 var peo People = &Student{} 即可。

    通用万能类型

    interface{} 表示空接口,可以用它引用任意类型的数据类型。

    func myFunc(arg interface{}) {
    	fmt.Println(arg)
    }
    
    type Book struct {
    	auth string
    }
    
    func main() {
    	book := Book{"Golang"}
    
    	myFunc(book)
    	myFunc(100)
    	myFunc("abc")
    	myFunc(3.14)
    }
    

    Golang 给 interface{} 提供类型断言机制,用来区分此时引用的类型:

    注意断言这个操作会有两个返回值

    func myFunc(arg interface{}) {
      
      value, ok := arg.(string)
      if !ok {
        fmt.Println("arg is not string type")
      } else {
        fmt.Println("arg is string type, value = ", value)
        fmt.Printf("value type is %T\n", value)
      }
    }
    

    一个接口的值(简称接口值)是由一个 具体类型 和 具体类型的值 两部分组成的。

    这两部分分别称为接口的动态类型和动态值。

    var w io.Writer
    w = os.Stdout
    w = new(bytes.Buffer)
    w = nil
    

    switch 判断多个断言:

    func justifyType(x interface{}) {
        switch v := x.(type) {
        case string:
            fmt.Printf("x is a string,value is %v\n", v)
        case int:
            fmt.Printf("x is a int is %v\n", v)
        case bool:
            fmt.Printf("x is a bool is %v\n", v)
        default:
            fmt.Println("unsupport type!")
        }
    }
  • 相关阅读:
    mysql5.7版本centos8环境修改my.cnf配置文件
    mysql5.7使用r2dbc持久层框架性能飙升,一小时插入623万数据,平均每秒插入1723条
    mysql5.7决定SQL中IN条件是否走索引的成本计算,mysql中的index dive是在两个区间之间计算有多少条记录的方式
    mysql5.7的SQL执行成本计算,IO成本和CPU成本,单表查询成本,多表连接查询成本,执行成本决定mysql是否走索引,.OPTIMIZER_TRACE,cost_info
    mysql5.7基于块的嵌套循环连接(Block Nested-Loop Join)
    时间复杂度计算法
    mysql5.7索引合并:交集、并集,EXPLAIN例子
    mysql5.7分区表
    mysql5.7的随机IO和顺序IO
    MySQL5.7二级索引+回表的MRR优化技术
  • 原文地址:https://www.cnblogs.com/Gaimo/p/16110144.html
Copyright © 2020-2023  润新知