go---基础使用(代码片段)

whkzm whkzm     2023-04-08     489

关键词:

目录

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]int1    # 如果值不满,使用默认值替换
fmt.Println(b)      # [1 0 0]

10.3 数据类型

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

# 数组的长度
var b [9]int = [9]int1,2,3,4,5,6,7,8,9
fmt.Println(len(b))

# 循环数组
第一种:
var a = [10]int1,2,3,9:111
for i:=0;i<len(a);i++
	fmt.Println(a[i])

    
第二种:
var b = [9]int1,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]int1,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]int1,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]int1,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]int1,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=[][]int1,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 =[]int1,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]string1:"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]string1:"haha",2:"heiheihei"

t,v := a[1]
fmt.Println(t,v)    # haha true

11.5 删除map中的元素

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

11.6 获取map的长度

var a  map[int]string = map[int]string1:"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]int89, 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]int89, 90, 91
    modify(a[:])
    fmt.Println(a)


12.6 Go不支持指针运算

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

package main

func main()   
    b := [...]int109, 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]int1,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=EmployeefirstName:"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 := namefirstName:"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:=Personname:"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,
        width:  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,
        width:  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]int1,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的东西")
	()


go基础(代码片段)

基础包、变量和函数流程控制语句:for、if、else和switch复杂类型:struct、slice和map方法和接口方法接口并发基础包每个Go程序都是由包组成的。程序运行的入口是包main。这个程序使用并导入了包"fmt"和"math/rand"。packagemainimport("fmt""... 查看详情

go基础系列:流程控制结构(代码片段)

条件判断结构:ifelse分支选择结构:switchcase循环结构:forbreak:退出for或switch结构(以及select)continue:进入下一次for迭代虽然Go是类C的语言,但Go在这些流程控制语句中的条件表达式部分不使用括号。甚至有些时候使用括号会报错... 查看详情

go编程基础之四(代码片段)

 1.数组Array:---定义数组的格式:var <varName> [n] <type> ,n>=0---数组长度也是类型的一部分,因此具有不同长度的数组为不同类型---注意区分指向数组的指针和指针数组---数组在Go中为值类型---数组之间... 查看详情

go基础(代码片段)

GOPATH在不同平台上的路径?GO项目结构在进行Go语言开发的时候,我们的代码总是会保存在\\(GOPATH/src目录下。在工程经过gobuild、goinstall或goget等指令后,会将下载的第三方包源代码文件放在\\)GOPATH/src目录下,产生的二进制可执行... 查看详情

[go语言]基础介绍(代码片段)

Go语言编程环境Windows下直接下载安装包,这里在https://studygolang.com/dl下载到的go1.17.windows-amd64.msi。安装完成之后可以通过goversion查看:PSF:\\Gitee\\go\\Code\\Basic>goversiongoversiongo1.17windows/amd64还可以查看go的基本使用方 查看详情

go语言学习笔记—基础—go工具:编译后运行——gorun(代码片段)

python语言可以在不输出二进制可执行文件情况下,使用虚拟机直接执行源文件。go语言没有虚拟机,但使用gorun可以达到同样效果。gorun会编译go源码,直接执行go源码中的main()函数,不在当前目录下输出任何可执行... 查看详情

go语言基础切片,map(代码片段)

目录切片(Slice)切片使用切片内存申请切片赋值拷贝切片遍历切片扩容动态扩容切片扩切片切片copy删除切片map简单使用判断键是否在map中遍历map删除键值对按照某个固定顺序遍历map元素为map类型的切片元素为map类型的切片循环初... 查看详情

go语言基础包,接口(代码片段)

目录包接口接口简单使用体会空接口接口底层原理类型断言简单使用断言状态判断断言配合switch包包的话我感觉没啥可说的,需要注意的是比如有两个包,一个main一个func包,如果func包的函数或变量名开头小写,... 查看详情

go基础定义常量(代码片段)

定义常量定义常量定义常量相关资料定义常量常量的值不可修改定义常量使用const关键字示例:import"fmt"consta=12345funcmain() constb="咿呀咔咔" fmt.Println("a=",a)//输出:a=12345 fmt.Println("b&# 查看详情

go基础1(代码片段)

目录变量声明变量有名变量匿名变量常量常量的声明及基本使用iota关于iota的面试题及使基本数据类型整形特殊整形数字字面量语法浮点数布尔型 字符串字符串转义定义多行字符串byte和rune类型修改字符串类型转换fmt.Printf中的... 查看详情

云原生时代崛起的编程语言go基础实战(代码片段)

...语言。本篇主要介绍Go目前发力的使用场景、开发环境、基础命令、命名规范,进一步了解其原生标准库的强大,最后通过多个Go代码示例演示了基础语法的使用,从而具备初级编程能力。@目录概述定义使用场景Go安全使用须知... 查看详情

go语言基础:map|函数(代码片段)

文章目录mapmap的定义map使用判断某个键是否存在map的遍历使用delete函数删除键值对特定的顺序遍历map元素是map类型的切片元素类型是切片的map练习函数函数定义函数的调用参数类型的简写可变参数返回值多返回值返回值命名返回... 查看详情

go语言基础:map|函数(代码片段)

文章目录mapmap的定义map使用判断某个键是否存在map的遍历使用delete函数删除键值对特定的顺序遍历map元素是map类型的切片元素类型是切片的map练习函数函数定义函数的调用参数类型的简写可变参数返回值多返回值返回值命名返回... 查看详情

使用go语言交叉编译开发嵌入式linux应用程序(代码片段)

文章目录目的基础说明编译生成嵌入式Linux应用程序二进制文件瘦身与C语言混合进行开发总结目的Go语言非常适合用来开发存储空间不紧张的嵌入式Linux设备应用程序,可以在性能需求和开发速度上找到一个不错的平衡点。这... 查看详情

go语言基础(代码片段)

go语言由package构成,go语言的实现使用传统的编译链接模式产生可执行文件。go语言的特色:支持环境采纳模式,例子就是不需声明变量类型,编译器会自动根据赋值的类型而采用相应的类型。编译时间短内置并行... 查看详情

go基础语法-常量与枚举(代码片段)

常量与枚举1.常量定义用const关键字修饰常量名并赋值,常量命名不同于java等语言,golang中一般用小写,因为在golang中首字母大写表示public权限consta=32.常量使用使用数值常量进行运算时不需要进行强制类型转换,编译器会自动识... 查看详情

go基础定义枚举(代码片段)

定义枚举定义枚举定义枚举相关资料定义枚举定义枚举使用const关键字+()当前面某个变量使用了iota后,该变量后面的变量都回给默认加上一个iota,不论你是否显式的写了出来iota的取值从0开始iota的累加机制是在=作用于... 查看详情

go语言基础入门(代码片段)

文章目录前言安装代理设置开发环境程序编写与编译包和模块包(package)模块(module)模块编写与使用多模块工作区总结前言Go是由谷歌支持的开源编程语言,属于编译型语言,对并发编程有较好的支持。... 查看详情