Go --- 基础使用

目录

Go语言基础

在写Go代码之前,先建立Go的工作区,可以通过 go env 命令来查看Go当前的工作区,默认的工作区为 GOPATH=C:\User\username\go ,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("\nEmployee name after change: %s", e.name)

    fmt.Printf("\n\nEmployee age before change: %d", e.age)
    (&e).changeAge(51)
    fmt.Printf("\nEmployee 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\n", (r.length * r.width))
}
# 定义一个值接受器
func (r rectangle) area() {
    fmt.Printf("Area Method result: %d\n", (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]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的东西")
	}()
}

posted @ 2020-03-28 11:55  余人。  阅读(183)  评论(0编辑  收藏  举报