• Go --- 基础使用


    目录

    Go语言基础

    在写Go代码之前,先建立Go的工作区,可以通过 go env 命令来查看Go当前的工作区,默认的工作区为 GOPATH=C:Userusernamego ,Go的工作区目录结构如下

    -C:
        -User:
            -username:
                -go:
                    -src:写的所有的go代码全放在这个文件夹下
                    -bin:编译后的可执行文件位置
                    -pkg:编译时生成的对象文件
    

    1.hello word

    package main   # 每一个go文件都应该在开头尽心该声明,
    
    import "fmt"
    
    func main() {  
        fmt.Println("Hello World")
    }
    
    // 1.写go代码,必须把代码放在main包下的main函数中(go程序的入口,是main包下的main函数)
    // 2.fmt.Println()  看到的a...其实是goland给你提示的,函数的形参是a...
    // 3.go语言中,包导入必须使用,否则报错,注释掉包会自动删除(goland做的,其他编辑器没有)
    

    1.1执行方法:

    1.使用golang直接运行

    2.在命令行敲:go run g1.go

    2.变量

    2.1声明变量的三种方式

    2.1.1.全定义

    var关键字 变量名 变量类型 = 变量值
    var a int = 10
    

    2.1.2 类型推导(自动推导出变量类型)

    var关键字 变量名 = 变量值
    var a = 10
    
    fmt.Pringf("%T",a)  # 查看a的类型
    

    2.1.3 简略声明

    a : = 10
    fmt.Pringln(a)
    

    附加1:只定义变量,不赋值

    var a int
    a = 10
    fmt.Println(a)
    

    附加2:声明多个变量

    var a,b,c int=10,11,'xxx'
    

    附加3:变量不能重复定义

    //var a int
    ////var a=90   //重复定义
    //a :=90      //重复定义
    //a=90        //重复定义
    

    变量:总结

    1.变量定义了必须使用,否则报错(只有go要求这样)
    2.查看变量没写
    3.变量要先定义在使用
    4.变量类型是固定的,不允许中途改变的(静态语言)
    5.如果只是定义了变量,必须指定类型,只能用第一种定义方式
    6.变量不允许重复定义
    强调:
        以后所有类型的变量定义,就参照变量定义的三种方式
    
    

    3.变量类型

    3.1 数字类型

    //类型: 数字,字符串,布尔
    /*
    数字:
    	-int:整数类型(包括负数)
    		-int,int8,int16,int32,int64
    		-int8:表示能存储的长度 8个比特位,8个小格,一个格只能放0和1,
    				能表示的范围是:正的2的七次方-1到,负的2的7次方-1
    		-int16,32...类推
    		-int:在32位机器上,表示int32,在64位机器上表示int64
    		-表示人的年龄:300岁撑死了,int64 是不是有点浪费,可以定义成int8
    		-python中int类型,远远比go的int64大,大很多,python中一切皆对象,int int对象
    
    	-uint:正整数
    		-uint,uint8,uint16,uint32,uint64
    		-uint8:范围:正2的8次方减一,有个0
    	-float:小数
    		-float32,float64
    	-complex:复数(高等数学学得,实部和虚部,了解)
    		-complex32,complex64
    	-byte:uint8的别名,byte:翻译过来是字节,一个字节是多少,8个比特位,uint8就占8个比特位
    	-rune:int32的别名,4个字节,表示一个字符
    
    
    

    3.2 string类型

    string:用双引号表示的(单引号?表示的不是字符串,三引号?)
    		双引号和三引号
    
    

    3.3 bool类型

    bool类型标识一个布尔值,值为true或flase

    package main
    
    import "fmt"
    
    func main() {  
        a := true
        b := false
        fmt.Println("a:", a, "b:", b)
        c := a && b
        fmt.Println("c:", c)
        d := a || b
        fmt.Println("d:", d)
    }
    
    

    在上面的程序中,a赋值为true,b赋值为false

    c赋值为 a&&b ,仅当a和b都为 true 时,操作符 && 才会返回 true,

    当 a 或者 b 为 true 时,操作符 || 返回 true

    a: true b: false  
    c: false  
    d: true
    
    

    4.常量

    常量表示固定的值,不能再重新赋值为其他的值,不可以使用简略声明的方式创建常量

    const关键字 变量名 变量类型 =值
    
    const name  string=  "lqz"
    //类型推导
    const name  =  "lqz"
    
    //简略声明 不可以
    
    

    5.函数

    5.1 定义函数

    # 1.函数定义格式
    func 关键字 函数名(参数1 类型,参数2 类型){ 需要执行的代码 }
    
    # 2.函数的基本定义
    func test(){}
    
    # 3.带参数的函数
    func test(a int,b int){ fmt.Println(a,b) }
    
    # 4.带参数的函数,两个参数类型相同可以省略
    func test(a,b int){ fmt.Println(a,b) }
    
    # 5.带返回值(需要指定返回值类型是什么)
    格式:func 关键字 函数名(参数1 类型,参数2 类型)返回值类型 {}
    func test(a int,b int)int {
    	fmt.Println(a,b)
    	return a + b
    }
    
    # 6.多返回值(python中可以),返回两个int类型,需要指定每一个返回值的类型
    func test(a int,b int)(int,int) {
    	fmt.Println(a,b)
    	return a , b
    }
    
    # 7.可变长,接受任意长度的参数,最后一个参数...T,表示可以接受多个T类型的参数,
    func test2(s...string){
    	fmt.Println(s)
    }
    
    # 8.go中只有位置参数,没有关键字参数,没有默认参数
    func test(a int,b string)  {
    	fmt.Println(a)
    	fmt.Println(b)
    }
    
    # 9.匿名函数(没有名字的函数),一定要定义在函数内部
    例1:定义匿名函数的时候就直接调用
    func (n1 int, n2 int) int {
            return n1 + n2
        }(10, 30)  //括号里的10,30 就相当于参数列表,分别对应n1和n2
    
    例2: 将匿名函数赋值给一个变量,再通过该变量来调用匿名函数
    test_fun := func (n1 int, n2 int) int {
            return n1 - n2
        }
    
    res2 := test_fun(10, 30)
    res3 := test_fun(50, 30)
    fmt.Println("res2=", res2)
    fmt.Println("res3=", res3)
    fmt.Printf("%T", test_fun)
    
    # 10.函数这个类型,他的参数,返回值,都是类型的一部分
    //var a func()
    //var b func(a,b int)
    //var c func(a,b int)int
    //var d func(a,b int)(int,string)
    
    # 11.闭包 ---》 1.定义在函数内部   2.对外部作用域有引用
    例1:没有参数
    func test4() func(){
    	a:= func() {
    		fmt.Println("我是内层函数")
    	}
    	return a
    }
    
    例2:内层函数有参数
    func test4()func(x,y int){
    	a:= func(x,y int)int {
    		fmt.Println("我是内层函数")
    		return x+y
    	}
    	return a
    }
    
    # 12.go实现装饰器 --go中欸有装饰器语法糖
    func test5(x,y int)func(){
    	a:= func() {
    		fmt.Println(x+y)
    	}
    	return a
    }
    
    return 没有加返回值表示没有返回值,函数执行到这里就结束了
    
    

    6.包

    同一个文件夹下只能由一个包,也就是package后面的名字都要一致,默认跟文件夹名字一致,

    总结

    1.新建一个文件夹(包),包下新建任意多个go文件,但是包名都必须一致(建议就用文件夹名)
    2.在包内定义的函数,大写字母开头,表示外部包可以使用,小写字母开头,表示只能内部使用,直接使用即可
    3.在其他包中使用,要先导入(goland有自动提示)
    4.公有和私有  就是大小写区分的
    5.所有的包必须在gopath的src路径下,否则找不到
    
    """
    package main
    
    import "awesomeProject/test1"
    
    func main() {
    	test1.Test()
    }
    """
    
    

    7.if判断

    package main
    
    import "fmt"
    
    func main() {
    	//1 语法
    	/*
    	if 条件 {
    		//符合上面条件的执行
    	}
    	else if 条件{
    		//符合上面条件的执行
    	}else {
    		// 不符合上面的条件执行这个
    	}
    	*/
    
    # 例1:定义全局变量
    package main
    
    import "fmt"
    
    func main() {
    	var a int = 11
    	if a > 10{
    		fmt.Println("a>10")
    	} else if a ==10{
    		fmt.Println("a=10")
    	}else{
    		fmt.Println("a<10")
    	}
    }
        
    # 例2:定义局部变量  
    //作用域范围不一样
    func main() {
    	if a:=10;a<9{
    		fmt.Println("小于9")
    	}else if a==10{
    		fmt.Println("10")
    	}else {
    		fmt.Println("都不符合")
    	}
    }
    }
    
    

    8.for循环

    package main
    
    //循环:go中,没有while循环,只有一个for循环,for可以替代掉while
    
    func main() {
    	# 1 语法,三部分都可以省略
    	/*
    	for关键字 初始化;条件;自增自减{
    		循环体的内容
    	}
    	 */
    	# 2 示例 从0 打印到9
    	//for i:=0;i<10;i++{
    	//	fmt.Println(i)
    	//}
    	# 3 省略第一部分  从0 打印到9,把i的定义放在for外面
    	//i:=0
    	//for ;i<10;i++{
    	//	fmt.Println(i)
    	//}
    
    	# 4  省略第三部分
    	//i:=0
    	//for ;i<10;{
    	//	fmt.Println(i)
    	//	i++
    	//}
    
    	# 5 第二部分省略,条件没了,死循环
    	//i:=0
    	//for ;;{
    	//	fmt.Println(i)
    	//	i++
    	//}
    
    	# 6 全省略的简略写法,死循环
    	//for {
    	//	fmt.Println("xxxx")
    	//}
    
    	# 7 第一部分和第三部分都省略的变形
    	//i:=0
    	//for i<10 {
    	//	fmt.Println(i)
    	//	i++
    	//}
    	//for 条件{  就是while循环
    	//
    	//}
    
    	# 8 break continue:任何语言都一样
    }
    
    

    9.switch

    go中有的,python中没有,作用是多条件匹配,用于替换多个 if else

    package main
    
    import "fmt"
    # 1.基本使用
    func main() {
    	var a = 10
    	switch a {
    	case 4: 
    		fmt.Println("4")        # 满足条件后输出
    	case 11:
    		fmt.Println("11")
    	default:
    		fmt.Println("都不符合")    # 都不符合情况下的默认
    	}
    }
    
    # 2.多表达式判断
    func main() {
    	var a = 10
    	switch a {
    	case 4,5,6,7:        # 满足条件中的一个
    		fmt.Println("4,5,6,7")
    	case 10,11,12,13:
    		fmt.Println("10,11,12,13")
    	default:
    		fmt.Println("都不符合")
    	}
    }
    
    # 3.无条件执行下一个
    func main() {
    	var a = 10
    	switch a {
    	case 4,5,6,7:        # 满足条件中的一个
    		fmt.Println("4,5,6,7")
    	case 10,11,12,13:
    		fmt.Println("10,11,12,13")
            Fallthrough   # 不管条件是否符合,都无条件执行下一个
    	default:
    		fmt.Println("都不符合")
    	}
    }
    
    

    10.数组

    10.1 数组声明

    数组是同一类型元素的集合,类似于python中的列表(列表可以放任意元素)

    定义一个长度为3的int类型,返回的是默认值,int类型的0
    var a [3]int
    
    int 默认值:0
    string 默认值:""
    bool 默认值:false
    切片 默认值:nil
    数组默认值:跟他的数据是有关系的,数组存放类型的空值
    
    

    10.2 数组定义

    # 定义数组
    var a [3]int
    
    # 初始化数组
    var a [3]string = [3]string{"haha","hehih","ajsdlkfjas"}
    fmt.Println(a)         # [haha hehih ajsdlkfjas]
    
    var b [3]int = [3]int{1}    # 如果值不满,使用默认值替换
    fmt.Println(b)      # [1 0 0]
    
    

    10.3 数据类型

    数据的类型有值类型和引用类型,go中的数组是值类型,当做函数传参,传到函数内部,修改数组,不会影响原来的,go中函数传参,都是copy传递

    # 数组的长度
    var b [9]int = [9]int{1,2,3,4,5,6,7,8,9}
    fmt.Println(len(b))
    
    # 循环数组
    第一种:
    var a = [10]int{1,2,3,9:111}
    for i:=0;i<len(a);i++{
    	fmt.Println(a[i])
    }
        
    第二种:
    var b = [9]int{1,2,3,4,5,6,7,8,9}
    for i,v := range b{
    	fmt.Println(i,v)
    }
    # i是索引,v是具体的值,如果不想接收,使用_
    
    # 多维数组:数组套数据,但是多维数组中的数据类型必须是一致的(一般最多二维)
    var c [3][3]int = [3][3]int{{1,2,3},{4,5,6}}
    fmt.Println(c)    # [[1 2 3] [4 5 6] [0 0 0]]
    
    

    10.4 切片

    10.4.1 切片简介

    切片是由数组建立的一种方便,灵活且功能强大的包装,切片本身不拥有任何数据,他们只是对现有数组的引用。

    基本使用

    var b = [9]int{1,2,3,4,5,6,7,8,9}
    var d = b[:]   # 全切
    var d = b[5:]   # 从索引为5开始且
    fmt.Println(d)
    
    

    10.4.2 切片的修改

    var b = [9]int{1,2,3,4,5,6,7,8,9}
    var d = b[5:]
    d[0]=111
    fmt.Println(d)    # [111 7 8 9]
    fmt.Println(b)    # [1 2 3 4 5 111 7 8 9]
    
    # 切片的修改会影响原数组,数组的更改也可以改变切片
    
    

    10.4.3 切片的长度和容量

    len():长度,现在有多少值
    cap():容量,总共能放多少值
    
    # 注:容量是从切片开始的位置往后算
    
    

    10.4.4 使用make创建一个切片

    # make内置函数,第一个参数写类型,第二个参数是切片的长度,第三个参数是切片的容量
    var e []int = make([]int,3,4)
    fmt.Println(e)   # [0 0 0]
    
    e[1]=100  # 给创建的切片赋值,赋值不能超出切片的长度范围,新添可以
    
    

    10.4.5 追加切片元素

    var b = [9]int{1,2,3,4,5,6,7,8,9}
    var d []int= b[5:]
    	
    d = append(d, 111,222,333)
    fmt.Println(b)    # [1 2 3 4 5 100 7 8 9]
    fmt.Println(d)    # [100 7 8 9 111 222 333]
    # 当最佳的值超过容量,会重新创建一个数组,让切片指向新的数组,新数组大小是原来切片容量的两倍
    
    

    10.4.6 切片的函数传递(引用类型,传递,修改原来的值)

    将切片当作函数形参一样传递

    var a []int=make([]int,4,5)
    fmt.Println(a)
    test2(a)    //copy传递
    
    

    10.4.7 多维切片

    var a [][]int=[][]int{{1,2,3},{2,3},{4,5,5,6,7,8,9}}
    fmt.Println(a)    # [[1 2 3] [2 3] [4 5 5 6 7 8 9]]
    
    

    10.4.6 切片copy

    var a []int=make([]int,6,7)
    var b []int =[]int{1,2,3,4,5}
    fmt.Println(a)    # [0 0 0 0 0 0]
    fmt.Println(b)    # [1 2 3 4 5]
    
    //把b的数据copy到a上
    copy(a,b)
    fmt.Println(a)    # [1 2 3 4 5 0]
    fmt.Println(b)    # [1 2 3 4 5]
    
    

    11.maps

    相当于python中的字典,

    11.1 如何创建map

    map的类型:map[key的类型]value的类型
    
    var a map[int]string
    
    map的空值类型;nil (引用类型的控制都是nil)
    
    

    11.2 定义并初始化(make完成)

    定义map

    # 没有经过初始化的map是空值类型,默认是nil
    # 检测
    var a map[int]string
    	if a==nil{
    		fmt.Println("我是空值")
    	}
    
    

    初始化map

    如果map不进行初始化,默认值为nil,不能进行操作

    # 第一种:定义的时候直接传入值进行初始化
    var a map[int]string= map[int]string{1:"lqz",2:"egon",3:"jason"}
    fmt.Println(a)    # map[1:lqz 2:egon 3:jason]
    
    # 第二种:使用make进行初始化
    var a  = make(map[int]string)
    
    

    11.3 给map添加元素

    给map添加元素,有就更新,没有就新增,但是前提是map已经初始化过了

    定义的map变量[key值] = value值    # 注意:key和value必须遵照规定的数据类型
    a[1] = "no1"
    
    

    11.4 获取map中的元素

    a[1]   # 如果a中有key为1的键值对,就获取,没有就返回默认值(空值)
    
    从字典中取值,其实会返回两个值,一个是true和flase,一个是真正的值
    如果用一个变量来接,就是真正的值,如果两个变量来接,第二个是true或flase
    var a  map[int]string = map[int]string{1:"haha",2:"heiheihei"}
    
    t,v := a[1]
    fmt.Println(t,v)    # haha true
    
    

    11.5 删除map中的元素

    var a  map[int]string = map[int]string{1:"haha",2:"heiheihei"}
    delete(a,1)
    fmt.Println(a)    # map[2:heiheihei]
    
    

    11.6 获取map的长度

    var a  map[int]string = map[int]string{1:"haha",2:"heiheihei"}
    fmt.Println(len(a))    # 2
    
    

    11.7 map是引用类型

    11.8 map的value可以是任意类型

    var a map[int]map[string]string=make(map[int]map[string]string)
    
    

    11.9 map循环

    # 只能使用range
    var a =make(map[int]string)
    a[1]="xx"
    a[2]="uu"
    a[3]="99"
    a[4]="66"
    a[6]="99"
    fmt.Println(a)
    //map是无序的  python3.6之前也是无序的,3.6以后有序了,如何做到的
    for k,v:=range a{
    	fmt.Println(k,v)
    }
    
    

    12.指针

    12.1 什么是指针

    指针是一种存储变量内存地址的变量

    变量 b 的值为 156,而 b 的内存地址为 0x1040a124,变量 a 存储了 b 的地址,我们就称 a 指向了 b

    12.2 指针使用

    指针变量的类型为 *T ,该指针指向一个 T 类型的变量,指针的零值是 nil

    package main
    
    import "fmt"
    
    func main() {
    	var b = 222
    	//a := &b
    	var a *int=&b
    	fmt.Println(*a)    # 222
    	}
    
    # 总结:
    把b的内存地址赋值给a这个指针,直接打印a返回的是b的内存地址,
    1.取一个变量的地址:&  取地址符号
    2.如果在类型前面加 * ,表示指向这个类型的指针,例如:把 b 的地址赋值给 *int 类型的 a
    3.在指针变量前加*,表示解引用(反解),可以获取对应指针的值,例如 *a
    
    

    12.3 指针的解引用

    指针的解引用可以获取指针所指向的变量值,将a解引用的语法是 *a

    package main
    
    import "fmt"
    
    func main() {
    	var b = 222
    	a := &b
    	fmt.Println(&b)    # 打印b的内存地址:0xc00000a0c8
        
    	*a += 10
    	fmt.Println(b)     # 打印反解更改后b的值:232
    	fmt.Println(a)     # 此时a的内存地址为:0xc00000a0c8
    	}
    # 总结:
    反解后的值的更改会影响原来值
    
    

    12.4 向函数传递指针参数

    package main
    
    import "fmt"
    
    func main() {
    	a  :=100
    	b := &a
    	test(b)
    	}
    
    func test(val *int){     # int 必须加上 * 
    	fmt.Println(val)
    }
    
    

    12.5 不要向函数传递数组的指针,而应该使用切片

    假如我们想在函数内修改一个数组,并希望调用函数的地方也能的带修改后的数组,一种解决方案是把一个指向数组的指针传递给这个函数

    package main
    
    import (  
        "fmt"
    )
    
    func modify(arr *[3]int) {  
        (*arr)[0] = 90   # 或者使用 arr[0] = 90 也可以
    }
    
    func main() {  
        a := [3]int{89, 90, 91}
        modify(&a)
        fmt.Println(a)    # [90 90 91]
    }
    
    

    这种方法虽然好,但是在go中最好是使用切片来处理

    package main
    
    import (  
        "fmt"
    )
    
    func modify(sls []int) {  
        sls[0] = 90
    }
    
    func main() {  
        a := [3]int{89, 90, 91}
        modify(a[:])
        fmt.Println(a)
    }
    
    

    12.6 Go不支持指针运算

    Go不支持其他语言中的指针运算

    package main
    
    func main() {  
        b := [...]int{109, 110, 111}
        p := &b
        p++
    }
    
    

    12.7 指针的指针

    指针可以指向指针的指针
    
    var b int = 156
    var a *int = &b
    var c **int     c就是指向指针的指针,类型用**int表示,多一个*,如果多个,就用多个*
    c = &a
    
    

    12.8 数组指针和指针数组

    # 数组指针:指向数组的指针
    var a [3]int=[30]int{1,2,3}
    var b *[3]int &a
    
    # 指针数组:数组里面放指针
    var x,y,z = 12,13,14
    var b [3]*int = [3]*int{&x,&y,&c}
    
    

    13.结构体

    13.1 什么是结构体

    结构体是一系列属性的集合,go语言中的结构体,类似于面向对象语言中的类,只不过只有属性,没有方法。

    13.2 结构体声明

    13.2.1 命名结构体

    # 结构体定义:
    type 结构体名字 struct {
        属性 类型
        属性 类型
    }
    
    # 举例:
    type Employee struct {
        firstName string
        lastName  string
        age       int
    }
    
    或者
    
    type Employee struct {
        firstName, lastName string
        age, salary         int
    }
    
    

    13.2.2 匿名结构体

    声明结构体时不用声明一个新类型

    var employee struct {
        firstName, lastName string
        age int
    }
    
    

    13.2.3 结构体初始化

    # 对结构体进行实例化:
    方法一:e:=Employee{}
    方法二:var e Employee=Employee{}
    
    # 传入属性:
    1.按位置传(顺序不变,有几个值就要传几个值)
    var e Employee=Employee{"1","2",3}
    
    2.按关键字传(位置可以乱,可以少传)
    var e Employee=Employee{firstName:"1",lastName:"2",age:3}
    
    

    13.3 创建命名结构体

    package main
    
    import (  
        "fmt"
    )
    
    type Employee struct {  
        firstName, lastName string
        age, salary         int
    }
    
    func main() {
    
        //creating structure using field names
        emp1 := Employee{
            firstName: "Sam",
            age:       25,
            salary:    500,
            lastName:  "Anderson",
        }
    
        //creating structure without using field names
        emp2 := Employee{"Thomas", "Paul", 29, 800}
    
        fmt.Println("Employee 1", emp1)   # Employee 1 {Sam Anderson 25 500}
        fmt.Println("Employee 2", emp2)   # Employee 2 {Thomas Paul 29 800}
    }
    
    

    13.4 创建匿名结构体

    匿名结构体,没有名字,不需要写type,定义在函数内部,只能使用一次,把一大堆属性方到一个变量中

    package main
    
    import ("fmt")
    
    func main() {
        emp3 := struct {
            firstName, lastName string
            age, salary         int
        }{
            firstName: "Andreah",
            lastName:  "Nikola",
            age:       31,
            salary:    5000,
        }
    
        fmt.Println("Employee 3", emp3)   # Employee 3 {Andreah Nikola 31 5000}
    }
    
    

    13.5 结构体的零值

    定义好的结构体并没有被显式地初始化,该结构的字段默认赋值为字段的默认值

    package main
    
    import (  
        "fmt"
    )
    
    type Employee struct {  
        firstName, lastName string
        age, salary         int
    }
    
    func main() {  
        var emp4 Employee //zero valued structure
        fmt.Println("Employee 4", emp4)
    }
    
    Employee 4 { 0 0}
    
    

    13.6 访问结构体的字段

    点号操作符 . 用于访问结构体的字段

    package main
    
    import (  
        "fmt"
    )
    
    type Employee struct {  
        firstName, lastName string
        age, salary         int
    }
    
    func main() {  
        emp6 := Employee{"Sam", "Anderson", 55, 6000}
        fmt.Println("First Name:", emp6.firstName)
        fmt.Println("Last Name:", emp6.lastName)
        fmt.Println("Age:", emp6.age)
        fmt.Printf("Salary: $%d", emp6.salary)
    }
    
    First Name: Sam  
    Last Name: Anderson  
    Age: 55  
    Salary: $6000
    
    

    还可以创建零值的start,然后再给各个字段赋值

    package main
    
    import (
        "fmt"
    )
    
    type Employee struct {  
        firstName, lastName string
        age, salary         int
    }
    
    func main() {  
        var emp7 Employee
        emp7.firstName = "Jack"
        emp7.lastName = "Adams"
        fmt.Println("Employee 7:", emp7)      # Employee 7: {Jack Adams 0 0}
    }
    
    

    13.7 结构体指针

    创建结构体的指针,可以通过 . 的方式来访问结构体内的字段值,

    如果需要改原来的值,就传入指针,如果不改变原来的,就传入实例对象过去

    package main
    
    import (  
        "fmt"
    )
    # 创建结构体
    type Employee struct {  
        firstName, lastName string
        age, salary         int
    }
    
    func main() {  
        emp8 := &Employee{"Sam", "Anderson", 55, 6000}    # 获取结构体的内存地址
        fmt.Println("First Name:", (*emp8).firstName)
        fmt.Println("Age:", (*emp8).age)
    }
    
    注:(*emp8).firstName 和 emp8.firstName 的作用是一样的,都可以被Go认同
    
    

    13.8 匿名字段

    当我们创建结构体时,字段可以只有类型,而没有字段名,这样的字段称为匿名字段

    package main
    
    import (  
        "fmt"
    )
    
    type Person struct {  
        string
        int
    }
    
    func main() {  
        var p1 Person
        p1.string = "naveen"
        p1.int = 50
        fmt.Println(p1)    # {naveen 50}
    }
    
    

    虽然匿名字段没有名称,但其实匿名字段的名称就默认为他的类型,比如上面的 Person结构体,虽说字段是匿名的,但Go默认这些字段名是他们各自的类型,所以 Person 结构体有两个名为 stringint 的字段

    13.9 嵌套结构体

    结构体的字段有可能也是一个结构体,这样的结构体称为嵌套结构体,结构体的类型就是定义的结构体的名

    package main
    
    import (  
        "fmt"
    )
    
    type Address struct {  
        city, state string
    }
    type Person struct {  
        name string
        age int
        address Address
    }
    
    func main() {  
        var p Person
        p.name = "Naveen"
        p.age = 50
        p.address = Address {
            city: "Chicago",
            state: "Illinois",
        }
        fmt.Println("Name:", p.name)
        fmt.Println("Age:",p.age)
        fmt.Println("City:",p.address.city)
        fmt.Println("State:",p.address.state)
    }
    
    # 输出
    Name: Naveen  
    Age: 50  
    City: Chicago  
    State: Illinois
    
    

    13.10 提升字段

    将结构体中的 嵌套的结构体中的字段 提升到可以直接在结构体中进行访问

    package main
    
    import (
        "fmt"
    )
    
    type Address struct {
        city, state string
    }
    type Person struct {
        name string
        age  int
        Address
    }
    
    func main() {  
        var p Person
        p.name = "Naveen"
        p.age = 50
        p.Address = Address{
            city:  "Chicago",
            state: "Illinois",
        }
        fmt.Println("Name:", p.name)
        fmt.Println("Age:", p.age)
        fmt.Println("City:", p.city)    //city is promoted field
        fmt.Println("State:", p.state)    //state is promoted field
    }
    
    # 输出
    Name: Naveen  
    Age: 50  
    City: Chicago  
    State: Illinois
    
    

    13.11 导出结构体和字段

    如果结构体名称以大写字母开头,则他是其他包可以访问的导出类型,同样,如果结构体里的字段首字母大写,他也可能被其他包访问到

    定义一个包

    package computer
    
    type Spec struct { //exported struct  
        Maker string //exported field
        model string //unexported field
        Price int //exported field
    }
    
    

    在另一个包中进行访问

    package main
    
    import "structs/computer"  
    import "fmt"
    
    func main() {  
        var spec computer.Spec
        spec.Maker = "apple"
        spec.Price = 50000
        fmt.Println("Spec:", spec)
    }
    
    

    13.12 结构体相等性

    结构体是值类型,如果他的每一个字段都是可比较的,则该结构体也是可比较的,如果两个结构体变量的对应字段相等,则这两个变量也是相等,如果两个结构体包含不可比较的字段,则结构体变量也不可比较

    package main
    
    import (  
        "fmt"
    )
    
    type name struct {  
        firstName string
        lastName string
    }
    
    
    func main() {  
        name1 := name{"Steve", "Jobs"}
        name2 := name{"Steve", "Jobs"}
        if name1 == name2 {
            fmt.Println("name1 and name2 are equal")
        } else {
            fmt.Println("name1 and name2 are not equal")
        }
    
        name3 := name{firstName:"Steve", lastName:"Jobs"}
        name4 := name{}
        name4.firstName = "Steve"
        if name3 == name4 {
            fmt.Println("name3 and name4 are equal")
        } else {
            fmt.Println("name3 and name4 are not equal")
        }
    }
    
    

    14.方法

    14.1 什么是方法

    方法其实就是一个函数,在 func 这个关键字和方法名中间加入一个特殊的接收器类型,接收器可以是结构体类型或者是非结构体类型,接受器是可以在方法的内部访问的

    结构体 + 方法 = 面向对象中的类

    # 创建方法的语法
    func (t Type) methodName(parameter list) {}
    
    

    14.2 方法的使用

    package main
    
    import "fmt"
    // 1.定义结构体
    type Person struct {
    	name string
    	age int
    	sex int
    }
    // 2.给结构体绑定方法
    func (p Person)printName(){
    	fmt.Println(p.name)
    }
    
    func main(){
    	// 3.使用方法
    	p:=Person{name:"wang",age:12,sex:1}
    	// 4.自动传值
    	p.printName()
    }
    
    

    14.3 值接受器和指针接受器

    值接受器不改变原来的,指针接收器改变原来的

    package main
    
    import (
        "fmt"
    )
    
    type Employee struct {
        name string
        age  int
    }
    
    /*
    使用值接收器的方法。
    */
    func (e Employee) changeName(newName string) {
        e.name = newName
    }
    
    /*
    使用指针接收器的方法。
    */
    func (e *Employee) changeAge(newAge int) {
        e.age = newAge
    }
    
    func main() {
        e := Employee{
            name: "Mark Andrew",
            age:  50,
        }
        fmt.Printf("Employee name before change: %s", e.name)
        e.changeName("Michael Andrew")
        fmt.Printf("
    Employee name after change: %s", e.name)
    
        fmt.Printf("
    
    Employee age before change: %d", e.age)
        (&e).changeAge(51)
        fmt.Printf("
    Employee age after change: %d", e.age)
    }
    
    # 输出:
    Employee name before change: Mark Andrew
    Employee name after change: Mark Andrew
    
    Employee age before change: 50
    Employee age after change: 51
    
    

    14.3.1 什么时候使用指针接收器和值接受器

    指针接收器:对方法内部的接收器所做的改变应该对调查者可见时,因为指针接收器会改变原来的值,当拷贝一个结构体的待解过于昂贵的时候,使用指针接收器,结构体不会被使用,只会传递一个指针到方法内部使用

    一般情况下推荐使用值接受器

    14.4 匿名字段的方法

    匿名字段:在一个结构体中调用添加另一个结构体,绑定方法的时候可以在这个结构体中访问到零一个结构体中的属性

    type Hobby1 struct {
    	id int
    	hobbyName string
    }
    type Person5 struct {
    	name string
    	age int
    	sex int
    	Hobby1   //匿名字段
    }
    
    //Hobby1的绑定方法
    func (h Hobby1)printName()  {
    	fmt.Println(h.hobbyName)
    
    }
    //Person5的绑定方法
    func (p Person5)printName()  {
    	fmt.Println(p.name)
    }
    
    

    14.5 在方法中使用值接受器与在函数中使用值参数

    当一个函数有一个值参数,他只能接受一个值参数

    当一个方法有一个值接受器,它可以接受值值接受器和指针接收器

    package main
    
    import (
        "fmt"
    )
    
    type rectangle struct {
        length int
        width  int
    }
    # 定义一个值参数
    func area(r rectangle) {
        fmt.Printf("Area Function result: %d
    ", (r.length * r.width))
    }
    # 定义一个值接受器
    func (r rectangle) area() {
        fmt.Printf("Area Method result: %d
    ", (r.length * r.width))
    }
    
    func main() {
        r := rectangle{
            length: 10,
              5,
        }
        area(r)  // 调用函数
        r.area()  // 调用方法
    
        p := &r
        /*
           compilation error, cannot use p (type *rectangle) as type rectangle
           in argument to area
        */
        //area(p)
    
        p.area()  //通过指针调用值接收器
    }
    
    # 输出
    Area Function result: 50
    Area Method result: 50
    Area Method result: 50
    
    

    14.6 在方法中使用指针接收器与在函数中使用指针参数

    和值参数类似,函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接受器和指针接收器

    package main
    
    import (
        "fmt"
    )
    
    type rectangle struct {
        length int
        width  int
    }
    # 定义一个函数,传入指针参数
    func perimeter(r *rectangle) {
        fmt.Println("perimeter function output:", 2*(r.length+r.width))
    }
    # 定义一个指针接收器,传入指针
    func (r *rectangle) perimeter() {
        fmt.Println("perimeter method output:", 2*(r.length+r.width))
    }
    
    func main() {
        r := rectangle{
            length: 10,
              5,
        }
        p := &r //pointer to r
        perimeter(p)
        p.perimeter()
    
        /*
            cannot use r (type rectangle) as type *rectangle in argument to perimeter
        */
        //perimeter(r)
    
        r.perimeter()//使用值来调用指针接收器
    }
    
    

    14.7 在非结构体上的方法

    package main
    
    import "fmt"
    
    type myInt int   // 给传统的数据类型重命名,然后绑定方法
    
    func (a myInt) add(b myInt)myInt{
    	return a + b
    }
    
    func main() {
    	num1 := myInt(5)
    	num2 := myInt(10)
    
    	sum := num1.add(num2)
    	fmt.Println(sum)
    }
    
    

    15.接口

    15.1 什么是接口

    接口:接口定义一个对象的行为,一系列方法的集合

    在Go语言中,接口就是方法签名的集合,当一个类型定义了接口中的所有方法,我们称他实现了该接口

    15.2 接口的声明和实现

    package main
    
    import "fmt"
    
    //1 定义一个鸭子接口(run方法   speak方法)
    type DuckInterface interface {
    run()
    speak()
    }
    
    //1.1 写一个唐老鸭结构体,实现该接口
    type TDuck struct {
    name string
    age int
    wife string
    }
    //1.2 实现接口(只要结构体绑定了接口中的所有方法,就叫做:结构体实现了该接口)
    func (t TDuck)run()  {
    fmt.Println("我是唐老鸭,我的名字叫",t.name,"我会人走路")
    }
    func (t TDuck)speak()  {
    fmt.Println("我是唐老鸭,我的名字叫",t.name,"我说人话")
    }
    
    //2.1 写一个肉鸭结构体,实现该接口
    type RDuck struct {
    name string
    age int
    }
    //2.2 实现接口(只要结构体绑定了接口中的所有方法,就叫做:结构体实现了该接口)
    
    func (t RDuck)run()  {
    fmt.Println("我是肉鸭,我的名字叫",t.name,"我会人走路")
    }
    func (t RDuck)speak()  {
    fmt.Println("我是肉鸭,我的名字叫",t.name,"我说人话")
    }
    
    func main() {
    //var t TDuck=TDuck{"嘤嘤怪",18,"刘亦菲"}
    //var r RDuck=RDuck{"建哥",18}
    ////唐老鸭的run方法
    //t.run()
    ////肉鸭的run方法
    //r.run()
    
    //定义一个鸭子接口类型(因为肉鸭和唐老鸭都实现了鸭子接口,所有可以把这俩对象赋值给鸭子接口)
    var i1 DuckInterface
    i1=TDuck{"嘤嘤怪",18,"刘亦菲"}
    var i2 DuckInterface
    i2=RDuck{"建哥",18}
    //唐老鸭的run方法
    i1.run()
    //肉鸭的run方法
    i2.run()
    }
    
    # 输出
    我是唐老鸭,我的名字叫 嘤嘤怪 我会人走路
    我是肉鸭,我的名字叫 建哥 我会人走路
    
    

    15.3 空接口

    空接口因为一个接口都没有,所有类型都给了空接口

    type Empty interface { }
    
    var a Empty =[3]int{1,2,3}
    fmt.Println(a)
    
    

    15.4 匿名空接口

    没有名字的空接口,不需要type关键字,只是用一次

    var a interface{}="xxx"
    fmt.Println(a)
    
    

    15.5 类型选择

    根据类型判断走那一条路,通过赋值给一个便令的方式,来对对应的接口进行取值。

    # 4 类型选择
    func test(i interface{})  {  //可以接收任意类型的参数
    	switch a:=i.(type) {
    	case int:
    		fmt.Println("我是int")
    	case string:
    		fmt.Println("我是字符串")
    	case TDuck:
    		fmt.Println("我是TDuck类型")
    		//打印出wife
    		fmt.Println(a.wife)
    	case RDuck:
    		fmt.Println("我是RDuck类型")
    		fmt.Println(a.name)
    	default:
    		fmt.Println("未知类型")
    	}
    }
    
    

    15.6 实现多接口

    //type SalaryCalculator interface {
    //	DisplaySalary()
    //}
    //
    //type LeaveCalculator interface {
    //	CalculateLeavesLeft() int
    //}
    //
    //type Employee struct {
    //	firstName string
    //	lastName string
    //}
    //
    //func (e Employee)DisplaySalary()  {
    //	fmt.Println("xxxxx")
    //}
    //
    //func (e Employee)CalculateLeavesLeft() int {
    //	return 1
    //}
    
    

    15.7 接口嵌套

    type SalaryCalculator interface {
    	DisplaySalary()
    	run()
    }
    
    //type LeaveCalculator interface {
    //	SalaryCalculator
    //	//DisplaySalary()
    //	//run()
    //	CalculateLeavesLeft()
    //}
    
    type Employee struct {
    	firstName string
    	lastName string
    }
    
    func (e Employee)DisplaySalary()  {
    }
    func (e Employee)run()  {
    }
    func (e Employee)CalculateLeavesLeft()  {
    }
    
    func main() {
    	//4 实现多个接口,就可以赋值给不通的接口类型
    	//var a SalaryCalculator
    	//var b LeaveCalculator
    	//var c Employee=Employee{}
    	//a=c
    	//b=c
    
    	//4 接口嵌套
    	//var c Employee=Employee{}
    	//var b LeaveCalculator
    	//b=c
    
    	//5 接口零值:是nil类型,接口类型是引用类型
    	//var a LeaveCalculator
    	//fmt.Println(a)
    
    
    

    15.6 侵入式接口和非侵入式接口

    go,python:非侵入式接口 修改,删除,对子类没有影响

    16.Go协程

    并发:交替运行,看起来像同时运行

    并行:多核cpu下才行

    直接使用 go 关键字
    
    goroutine:go的协程(并不是真正的协程,只是一个统称,有线程也有协程
    
    

    16.1 启动一个协程

    如果直接在main中使用 go func() 创建一个协程,呢么这个协程中的代码不会执行,因为在调用go协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值,所以即使开了协程,也不会执行,可以使用时间延迟,等协程运行完,将值返回之后再运行下一行代码

    package main
    
    import (  
        "fmt"
        "time"
    )
    
    func hello() {  
        fmt.Println("Hello world goroutine")
    }
    func main() {  
        go hello()
        time.Sleep(1 * time.Second)
        fmt.Println("main function")
    }
    
    

    17.信道

    信道:Go中不同协程之间通信的通道,如同管道中的水,从一端流向另一端,信道就是一个变量

    17.1 信道声明

    信道的零值为 nil
    
    所有的信道都关联了一个类型,信道只能运输这种类型的数据,而运输其他类型的数据都是非法的,
    chan T 代表 T 类型的信道
    
    # 信道操作
    data := <- a // 读取信道 a  
    a <- data // 写入信道 a
    
    // 声明一个信道
    package main
    
    import (
    	"fmt"
    	"time"
    )
    // 定义一个信道
    func ttt(a chan int){
    	fmt.Println("ttt")
    	time.Sleep(time.Second*2)
    	a <- 1    # 将值放入信道中
    	fmt.Println("xxx")
    }
    
    func main() {
    	var a chan int = make(chan int )    # 对信道进行实例化
    	go ttt(a)
    	c := <-a   # 将值取出
    	fmt.Println("我是c",c)
    }
    
    

    17.2 信道接收与发送

    发送和接收默认是阻塞的,当把数据发送到信道时,程序控制会在发送数据的语句出发生阻塞,直到有其他Go协程从信道读取到数据,才会接触阻塞,当读取信道的数据实,如果没有其他的协程把数据写入到这个信道,呢么读取过程就会一致阻塞

    17.3 死锁

    使用信道需要考虑的一个重点就是死锁,当Go协程给一个信道发送数据时,如果没有其他协程来接收数据,就会在运行时触发panic,形成死锁

    同理,当有Go协程等着从一个信道接收数据时,如果没有其他协程向该信道写入数据,就会触发panic

    17.4 单向信道

    单向信道的作用只能发送或者接收数据

    package main
    
    import "fmt"
    // 把chal转换称一个唯送信道
    func sendData(sendch chan<- int) {
    	sendch <- 10
    }
    
    func main() {
    	// 创建一个双向信道
    	cha1 := make(chan int)
    	go sendData(cha1)
    	fmt.Println(<-cha1)
    }
    
    

    17.5 关闭信道和使用for range遍历信道

    数据发送方可以关闭信道,通知接收方这个信道不再有数据发送过来

    当从新到接收数据时,接收方可以多用一个变量来检查信道是否已经关闭

    v, ok := <- ch    # 第一个是真实的值,第二个是true或者false
        
    // 创建一个遍历信道
    package main
    
    import (  
        "fmt"
    )
    
    func producer(chnl chan int) {  
        for i := 0; i < 10; i++ {
            chnl <- i
        }
        close(chnl)
    }
    func main() {  
        ch := make(chan int)
        go producer(ch)
        for {
            v, ok := <-ch
            if ok == false {
                break
            }
            fmt.Println("Received ", v, ok)
        }
    }
    
    

    18.有缓冲信道

    有缓冲信道:管子可以放多个值,相当于一个队列,如果超出定义队列的长度,就会报错,提示是死锁。在缓冲为空的时候,才会阻塞从缓冲信道接收数据

    18.1 有缓冲信道声明

    # 通过向make函数再传递一个表示容量的参数(指定缓冲的大小),可以创建缓冲信道
    ch := make(chan type, capacity)    # capacity 即为缓冲信道容量
        
    # 使用信道创建生产者消费者模型
    package main
    
    import (  
        "fmt"
        "time"
    )
    
    func write(ch chan int) {  
        for i := 0; i < 5; i++ {
            ch <- i
            fmt.Println("successfully wrote", i, "to ch")
        }
        close(ch)
    }
    func main() {  
        ch := make(chan int, 2)
        go write(ch)
        time.Sleep(2 * time.Second)
        for v := range ch {
            fmt.Println("read value", v,"from ch")
            time.Sleep(2 * time.Second)
        }
    }
    
    

    18.2 死锁

    当我们向容量为2的缓冲信道写入3个字符串,程序控制到达第3次写入时,由于他超出了信道的容量,会发生阻塞,这个时候我们就必须要有其他携程来读取这个信道的数据,如果没有,就会发生死锁

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        ch := make(chan string, 2)
        ch <- "naveen"
        ch <- "paul"
        ch <- "steve"
        fmt.Println(<-ch)
        fmt.Println(<-ch)
    }
    
    # 输出提示
    fatal error: all goroutines are asleep - deadlock!
    
    goroutine 1 [chan send]:  
    main.main()  
        /tmp/sandbox274756028/main.go:11 +0x100
    
    

    18.3 长度 vs 容量

    缓冲信道的容量是指信道可以存储的值的数量,我们在使用make函数创建缓冲信道的时候会指定容量带线啊哦

    缓冲信道的长度是指信道中当前排队的元素个数

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        ch := make(chan string, 3)
        ch <- "naveen"
        ch <- "paul"
        fmt.Println("capacity is", cap(ch))
        fmt.Println("length is", len(ch))
        fmt.Println("read value", <-ch)
        fmt.Println("new length is", len(ch))
    }
    
    # 输出
    capacity is 3  
    length is 2  
    read value naveen  
    new length is 1
    
    

    19.异常处理

    go中没有try,发生异常的时候需要使用别的方法来捕获异常

    go中捕获异常使用的是:
    defer:延迟执行,即使出现严重错误也会执行
    panic:主动抛出异常,终止执行
    recover:恢复程序,继续执行
    
    

    19.1 defer

    defer是go语言中的关键字,延迟指定函数的执行。通常在资源释放、连接关闭、函数结束时调用。多个defer为堆栈结构,先进后出,也就是先进的后执行。defer可用于异常抛出后的处理。

    19.2 panic

    panic是go语言中的内置函数,抛出异常(类似java中的throw)。其函数定义为:

    func panic(v interface{})
    
    

    19.3 recover

    recover() 是go语言中的内置函数,获取异常(类似java中的catch),多次调用时,只有第一次能获取值。其函数定义为:

    func recover() interface{}
    
    

    19.4 异常处理

    // 终极总结异常处理,以后要捕获哪的异常,就在哪写
    func f2() {
    	//defer fmt.Println("我会执行")
    	//在这捕获异常,把异常解决掉
    	# 只要哪里发生异常,就从使用下边的异常方法处理
    	defer func() {
    		if err := recover(); err != nil { //recover执行,如果等于nil 表示没有异常,如果有值,表示出错,err就是错误信息
    			fmt.Println(err)
    		}
    		fmt.Println("我是finally的东西")
    	}()
    }
    
    
  • 相关阅读:
    Hibernate 多表查询结果集的处理
    is not mapped [from错误
    input输入框内,焦点后文字消失;placeholder 与 value 区别
    滚动文字JS
    安装mysql和xampp遇到问题
    python数据结构总结
    翻译二--创建一个Web测试计划
    jmeter元件执行顺序及简介
    testlink for windows 安装
    postman使用
  • 原文地址:https://www.cnblogs.com/whkzm/p/12586574.html
Copyright © 2020-2023  润新知