golang学习笔记

安装与配置

安装go

下载网址:
https://golang.google.cn/dl/
Windows包括32位和64位版本,zip是压缩包,Installer需要安装;Linux中.tar.gz表示解压后就能使用64位,Mac系统只有一个下载。
推荐直接在C盘根目录下新建一个Go目录,如C:\Go,直接一路next即可。
在这里插入图片描述
等待安装完成后,在命令行中输入go version可以检查一下是否安装成功:
将刚才安装go的目录下的bin文件夹加入系统变量的path中:
在这里插入图片描述
在命令行中输入以下两行代码,修改设置:

go env -w GO111MODULE="on"
go env -w GOPROXY="http://goproxy.cn"

安装git,全使用默认的安装选项即可:
https://www.git-scm.com/download/win
git\bin目录添加到环境变量中(系统环境变量的path),如果没有自动添加的话就手动添加一下。

vscode中配置go

  1. 装GO插件(Go Team at Google)
  2. 创建第一个GO文件,右下角会又弹窗,建议下载Go全部依赖包(关键),点击“install all” 下载全部如果没有弹窗,可以手动下载依赖包:
    键盘输入:ctrl+shift+p
    弹窗选择:Go:install/Update Tool
    选择下载依赖包,建议全部选中下载

vscode运行方式(4种)

  1. 编译后执行
    a.初始化:
    执行:go mod init hello(项目名或者仓库名或其他)
    结果:生成go.mod文件,这文件是描述项目包信息,项目包通过hello可以被引用
    b.将hello.go编译成执行文件(3种方式):
    执行1:go build,默认生成hello.exe
    执行2:go build hello,生成hello.exe
    执行3:go build -o world.exe,生成world.exe
    c.运行:
    文件目录下执行:hello.exe

  2. 手动输入运行:
    hello.go文件目录下执行:go run hello.go

  3. 利用插件运行:
    安装插件:Code Runner
    点击右上角三角符Run Code 或者按F4

  4. 调试运行:
    a.在launch.json文件中添加配置:

{
   "name": "GO",
   "type": "go",
   "request": "launch",
   "mode": "debug",
   "debugAdapter": "legacy",
   "program": "${file}"
}

b.按下F5,在调试控制台会显示结果
c.如果报错:couldn’t start dlv dap: connection timeout
那就重装依赖包:dlv dap

go的一些基础操作

几个路径解释

  • GOROOT:GO的安装目录
  • GOPATH:GO的工作目录
  • bin:编译所形成的二进制文件
  • pkg:编译后包文件存放位置
  • src:源码位置,将自己clone下来的源代码放在此位置可以解决找不到包的情况

go常用命令

  • build: 编译包和依赖
  • env: 打印go的环境信息
  • fmt: 运行gofmt进行格式化
  • get: 下载并安装包和依赖
  • install: 编译并安装包和依赖
  • list: 列出包
  • mod:模块管理
  • run: 编译并运行go程序
  • test: 运行测试
  • version: 显示go的版本

go中的包

  • 包可以区分命令空间(一个文件夹中不能有两个同名文件),也可以更好地管理项目。go中创建一个包一般是创建一个文件夹,在该文件夹里面的go文件中,使用package关键字声明包名称。通常文件夹名称和包名称相同,并且同一个文件夹下面只有一个包。

  • 当设置为GO111MODULE=on的时候,我们使用go mod来管理包。

  • GO111MODULE=off,无模块支持,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或GOPATH模式来查找

  • GO111MODULE=on,模块支持,go命令行会使用modules,而一点也不会去GOPATH目录下查找

  • GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:
    (1)当前目录在GOPATH/src之外且该目录包含go.mod文件,开启模块支持。
    (2)当前文件在包含go.mod文件的目录下面

  • 如果多个文件夹下有重名的package,它们其实是彼此无关的package。

  • 如果一个go文件需要同时使用不同目录下的同名package,需要在import这些目录时为每个目录指定一个package的别名。

go mod tidy的作用

go mod tidy的作用

  1. 引用项目需要的依赖增加到go.mod文件。
  2. 去掉go.mod文件中项目不需要的依赖。

和包相关的报错

  1. no required module provides package github.com/gin-gonic/gin: go.mod file not found in current directory or any parent directory; see ‘go help modules’
    在运行go程序时遇到此问题,发现如果是在终端使用go run命令运行则没有报错,但在vscode中使用code-runner插件则会报错,解决方案:
    在settings.json文件中加入以下两行设置:
code-runner.runInTerminal": true,    //这一行可以注释,也能解决问题
code-runner.fileDirectoryAsCwd": true

如果是在调试go的时候出现这个问题,可能是因为调试的路径没有写对,修改一下launch.json

    {
        "name": "GoLaunch",
        "type": "go",
        "request": "launch",
        "mode": "auto",
        "debugAdapter": "legacy",
        "program": "${workspaceRoot}", //此处设置改为"${file}"即可解决问题
        "env": {},
        "args": []
    }
  1. could not import xxx(no required module xxx provides package)尝试在settings.json文件中加入设置:
"go.useLanguageServer": true,

使用go mod管理包

  1. 查看自己GOPATH的路径,比如我这里是C:\GoProject在这里插入图片描述
  2. cd到目标路径下,比如我要在C:\GoProject\test下建立一个go mod,那么就在C:\GoProject\test输入go mod init test

在这里插入图片描述
此时即完成建立:
在这里插入图片描述

下载go第三方包

可以在这个网址找到包的路径:
https://pkg.go.dev/
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

go基础语法

hello world实例

package main

import "fmt"

func main() {
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}
  1. 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
    下一行 import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
  2. 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
  3. 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
  4. 下一行是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
  5. 下一行 fmt.Println(…) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
    使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。
    Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
  6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

短变量声明

语法:变量名 := 数据
注意:只能在当前函数作用域中使用,不能作为全局变量

var global_variable string = "666"

func main() {
	short_variable := "777"
	fmt.Println(global_variable)
	fmt.Println(short_variable)
}

匿名变量

如果我们接收到多个变量,有一些变量使用不到,可以使用下划线_表示变量名称,这种变量叫做匿名变量:

func getNameAndAge() (string, int) {
	return "tom", 20
}
func main() {
	_, age := getNameAndAge()
	fmt.Println(age)
}

iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

func main() {
	const (
		a1 = iota
		a2 = iota
		a3 = iota
	)

	fmt.Printf("a1: %v\n", a1)
	fmt.Printf("a2: %v\n", a2)
	fmt.Printf("a3: %v\n", a3)
}

运行结果:

a1: 0
a2: 1
a3: 2

go中println和printf的用法区别

Println : 可以打印出字符串,和变量
Printf : 只可以打印出格式化的字符串,可以输出字符串类型的变量

package main

import "fmt"

func main() {
	a := 1
	b := 'a'
	c := 3.14
	d := "abc"
	fmt.Println("a =", a)
	fmt.Println("b = ", b)
	fmt.Println("c=", c)
	fmt.Println("d=", d)
	fmt.Printf("a=%d\nb=%v\nc=%v\nd=%s\n", a, b, c, d)

}

格式化操作:

动词功能
%v按值的本来值输出
%+v如果值是一个结构体,%+v 的格式化输出内容将包括结构体的字段名
%T打印值的类型
%b输出二进制表示形式
%d输出十进制表示形式
%x输出十六进制表示形式
%p十六进制表示,前缀0x的内存地址

go中的数据类型

  • intuint在32位操作系统上,它们均使用32位(4个字节),在64位操作系统上,它们均使用64位(8个字节)。
  • uintptr的长度被设定位足够存放一个指针即可。
  • int是计算最快的一种类型。
  • 整形的零值为0,浮点型的零值为0.0。
类型描述
uint8无符号 8 位整型 (0 到 255)
uint16无符号 16 位整型 (0 到 65535)
uint32无符号 32 位整型 (0 到 4294967295)
uint64无符号 64 位整型 (0 到 18446744073709551615)
int8有符号 8 位整型 (-128 到 127)
int16有符号 16 位整型 (-32768 到 32767)
int32有符号 32 位整型 (-2147483648 到 2147483647)
int64有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
package main

import (
	"fmt"
	"math"
	"unsafe"
)

func main() {
	var i8 int8
	var i16 int16
	var i32 int32
	var i64 int64
	var ui8 uint8
	var ui16 uint16
	var ui32 uint32
	var ui64 uint64

	fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(i8), math.MinInt8, math.MaxInt8)
	fmt.Printf("%T %dB %v~%v\n", i16, unsafe.Sizeof(i16), math.MinInt16, math.MaxInt16)
	fmt.Printf("%T %dB %v~%v\n", i32, unsafe.Sizeof(i32), math.MinInt32, math.MaxInt32)
	fmt.Printf("%T %dB %v~%v\n", i64, unsafe.Sizeof(i64), math.MinInt64, math.MaxInt64)
	fmt.Printf("%T %dB %v~%v\n", ui8, unsafe.Sizeof(ui8), 0, math.MaxUint8)
	fmt.Printf("%T %dB %v~%v\n", ui16, unsafe.Sizeof(ui16), 0, math.MaxUint16)
	fmt.Printf("%T %dB %v~%v\n", ui32, unsafe.Sizeof(ui32), 0, math.MaxUint32)
	fmt.Printf("%T %dB %v~%v\n", ui64, unsafe.Sizeof(ui64), 0, uint64(math.MaxUint64))
}

输出结果:

int8 1B -128~127
int16 2B -32768~32767
int32 4B -2147483648~2147483647
int64 8B -9223372036854775808~9223372036854775807
uint8 1B 0~255
uint16 2B 0~65535
uint32 4B 0~4294967295
uint64 8B 0~18446744073709551615

go中的if语句

  • 不能使用0非0表示真假
  • 不需要使用括号将条件包含起来
  • 大括号{}必须存在,即便只有一行语句
  • 左括号必须在ifelse的同一行
  • if之后,条件语句之前,可以添加变量初始化语句,使用;分割
func main() {
	// var a bool = true
	if age := 20; age > 6 {
		fmt.Println("666")
	}
}

for range循环

Go中可以使用for range遍历数组、切片、字符串、map及通道(channel),通过for range遍历的返回值有以下规律:

  1. 数组、切片、字符串返回索引和值
  2. map返回键和值
  3. 通道(channel)只返回通道内的值
func main() {
	var a = [5]int{1, 2, 3, 4, 5}
	for i, v := range a {
		fmt.Printf("i:%d , v:%v\n", i, v)
	}
	m1 := map[string]string{"name": "An", "age": "20"}
	for k, v := range m1 {
		fmt.Printf("%v: %v\n", k, v)
	}
}

continue 语句

Go 语言的 continue 语句 有点像 break 语句。但是 continue 不是跳出循环,而是跳过当前循环执行下一次循环语句。for 循环中,执行 continue 语句会触发 for 增量语句的执行。在多重循环中,可以用标号 label 标出想 continue 的循环。

func main() {
	for i := 0; i < 10; i++ {
	LABEL:
		for j := 0; j < 10; j++ {
			if i == 2 && j == 2 {
				continue LABEL
			}
			fmt.Printf("%v,%v\n", i, j)
		}
	}
}

输出结果:
在这里插入图片描述

数组

  • 数组是相同数据类型的一组数据的集合,数组一旦定义长度则不能修改,数组可以通过下标(索引)来访问元素。
  • 定义一个数组:var variable_name [SIZE] variable_type,如var a1[2]intvar a1 = [3]int{1, 2}var a2 = [3]string{"A", "B", "C"}
  • 数组长度可以省略,使用...代替,如var a3 = [...]int{1, 2, 3}

遍历一个数组:

func main() {

	var a1 = [3]int{1, 2, 3}

	//根据下标和长度遍历
	for i := 0; i < len(a1); i++ {
		fmt.Printf("a1[%v]: %v\n", i, a1[i])
	}

	//使用for range遍历
	for i, v := range a1 {
		fmt.Printf("a1[%v]: %v\n", i, v)
	}

}

切片

  • 可以把切片理解为可变长度的数组,它底层就是用数组实现的,增加了自动扩容功能,切片(Slice)是一个拥有相同类型元素的可变长度的序列。
  • 声明一个切片和声明一个数组类似,只要不添加长度就可以了:var identifier []type
  • 切片是引用类型,也可以使用make函数来创建切片:var slice1 []type = make([]type,len)slice1 := make([]type,len)
  • 还可以使用数组进行切片的初始化:arr := [...]int {1,2,3}; s1 := arr[:]
  • 切片的添加与删除,如下:
  1. 添加
func main() {
	var s1 = []int{}
	s1 = append(s1, 100)
	s1 = append(s1, 200)
	s1 = append(s1, s1[:2]...)
	s1 = append(s1, s1[2:]...)
	fmt.Printf("s1: %v\n", s1)
}
  1. 删除
func main() {
	// 要从切片a中删除索引为index的元素,操作方法是 a = append(a[:index], a[index+1:]...)
	var s1 = []int{1, 2, 3, 4, 5}
	s1 = append(s1[:2], s1[3:]...)  //删除索引为2的元素
	fmt.Printf("s1: %v\n", s1)
}
  1. 更新(修改)
func main() {
	var s1 = []int{1, 2, 3, 4, 5}
	s1[1] = 100
	fmt.Printf("s1: %v\n", s1)
}

map

Map是一种key:value键值对的数据结构容器。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

  1. 创建map:
// 1.声明变量
var map_variable map[key_data_type]value_data_type
// 2.使用make函数
map_variable := make(map[key_data_type]value_data_type)
func main() {
	m1 := make(map[string]string)
	m1["name"] = "tom"
	m1["age"] = "20"
	fmt.Printf("m1: %v\n", m1)

	m2 := map[string]string{
		"name": "xx",
		"age":  "24",
	}
	fmt.Printf("m2: %v\n", m2)
}

创建一个复杂的map:

package main

import "fmt"

type Person struct {
	age, height int
}

func main() {
	k := map[string]Person{
		"Peter": {23, 170},
		"Tom":   {24, 180},
		"Green": {age: 25},
		"Q":     {height: 50},
	}

	fmt.Printf("k: %v\n", k)   //输出结果   k: map[Green:{25 0} Peter:{23 170} Q:{0 50} Tom:{24 180}]
}
  1. go中的特殊用法,判断某个键是否存在
func main() {
	var m1 = map[string]string{"name": "xx", "age": "20"}
	var k1 = "name"
	var k2 = "email"
	v, ok := m1[k1]
	fmt.Printf("v: %v\n", v)
	fmt.Printf("ok: %v\n", ok)
	fmt.Println("---------------------")
	v, ok = m1[k2]
	fmt.Printf("v: %v\n", v)
	fmt.Printf("ok: %v\n", ok)
	fmt.Println("---------------------")
}

输出结果:

v: xx
ok: true
---------------------
v: 
ok: false
---------------------

函数

  1. go中函数的特性
  • go中有三种函数:普通函数、匿名函数和方法(定义在struct上的函数)
  • go中没有函数重载(overload)
  • go中的函数不能嵌套函数,但可以嵌套匿名函数
  • 函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数
  • 函数可以作为参数传递给另一个函数
  • 函数的返回值可以是一个函数
  • 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再讲副本传递给函数
  • 函数参数可以没有名称
  • 函数在使用之前必须先定义,可以调用函数来完成某个任务
  1. 定义函数的语法
func function_name( [parameter list] ) [return_types] {
   函数体
}
  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。
  1. 还可以使用type关键字来定义一个函数类型,语法格式如下:
type fun func(int,int) int
type fun func(int, int) int

func sum(a int, b int) int {
	return a + b
}

func main() {
	var f fun = sum
	s := f(1, 2)
	fmt.Printf("s: %v\n", s)
}
  1. 把函数作为参数或返回值:
func sum(a int, b int) int {
	return a + b
}

func sub(a int, b int) int {
	return a - b
}
func main() {
	ff := cal("+")
	f := ff(5, 2)
	fmt.Printf("f: %v\n", f)
}

func cal(operator string) func(int, int) int {
	switch operator {
	case "+":
		return sum
	case "-":
		return sub
	default:
		return nil
	}
}
  1. 匿名函数
  • go中函数不能嵌套,但在函数内部可以定义匿名函数实现简单的功能调用,定义匿名函数的语法格式:func (参数列表)(返回值)
func main() {
	max := func(a int, b int) int {
		if a > b {
			return a
		} else {
			return b
		}
	}
	i := max(5, 2)
	fmt.Printf("i: %v\n", i)

	//自己调用自己
	i2 := func(a int, b int) int {
		if a > b {
			return a
		} else {
			return b
		}
	}(5, 6)
	fmt.Printf("i2: %v\n", i2)
}

闭包

闭包可以理解为定义在一个函数内部的函数,在本质上闭包是将函数内部和函数外部连接起来的桥梁,或者说是函数和其引用环境的组合体:闭包=函数+引用环境

func add() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	var f = add()
	fmt.Println(f(10))
	fmt.Println(f(20))
	fmt.Println(f(30))
}
---------------------------------------------------
输出结果:
10
30
60
func makeSuffixFunc(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

func main() {
	jpgFunc := makeSuffixFunc(".jpg")
	txtFunc := makeSuffixFunc(".txt")
	fmt.Println(jpgFunc("test"))
	fmt.Println(txtFunc("test"))
}
---------------------------------------------------
输出结果:
test.jpg
test.txt

defer语句

defer语句会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句最先被执行。

func main() {
	fmt.Println("start")
	defer fmt.Println("step1")
	defer fmt.Println("step2")
	defer fmt.Println("step3")
	fmt.Println("end")
}

init函数

  • 先于main函数执行,实现包级别的一些初始化操作。
  • 主要特点:
  1. 先于main函数执行,不能被其他函数调用
  2. 没有输入参数与返回值
  3. 每个包可以有多个init函数
  4. 包的每个源文件也可以有多个init函数
  5. 同一个包的init执行顺序go中没有明确定义
  6. 不同包的init函数按照包导入的依赖关系决定执行顺序

指针

  • go中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量,传递数据使用指针,而无需拷贝数据。
  • 类型指针不能进行偏移和运算。
  • &取地址,*根据地址取值。
  • 指针声明的语法:var var_name *var_type
func main() {
	var ip *int
	fmt.Printf("ip: %v\n", ip) //<nil>
	fmt.Printf("ip: %T\n", ip) //*int

	var i int = 100
	ip = &i
	fmt.Printf("ip: %v\n", ip)   //0xc00000a0f0
	fmt.Printf("ip: %v\n", *ip)   //100
}
  • 指向数组的指针:var ptr [MAX]*int表示数组里面的元素的类型是指针类型
const MAX int = 3

func main() {
	a := []int{1, 3, 5}
	var ptr [MAX]*int
	fmt.Println(ptr) //输出结果  [<nil> <nil> <nil>]
	for i := 0; i < MAX; i++ {
		ptr[i] = &a[i] //整数地址赋值给指针数组
	}
	fmt.Printf("ptr: %v\n", ptr)   //输出结果  ptr: [0xc000014180 0xc000014188 0xc000014190]
	for i := 0; i < MAX; i++ {
		fmt.Printf("a[%d] = %d\n", i, *ptr[i]) //*ptr[i]就是打印出相关指针的值
	}
}
---------------------------------------------------
输出结果:
[<nil> <nil> <nil>]
ptr: [0xc000014180 0xc000014188 0xc000014190]
a[0] = 1
a[1] = 3
a[2] = 5

结构体

  • go中没有面向对象的概念,但是可以用结构体来实现面向对象的一些特性,例如继承、组合等特性。
  • 未初始化的结构体,成员都是零值,int对应0,float对应0.0,bool对应false,string对应""
  • 语法结构如下:
type struct_variable_type struct {
	member definition
}
//type 结构体定义关键字
//struct_variable_type 结构体类型名称
//struct 结构体定义关键字
//member definition 成员定义
func main() {
	var tom Person
	tom.id = 666
	tom.name = "tome"
	fmt.Printf("tom: %v\n", tom) //输出 tom: {666 0 tome}

	kite := Person{}
	fmt.Printf("kite: %v\n", kite)   //输出 kite: {0 0 }
}

type Person struct {
	id, age int
	name    string
}
  • 匿名结构体:结构体临时使用的情况下可以不起名字
func main() {
	var dog struct {
		id   int
		name string
	}
	dog.id = 1
	dog.name = "花花"
	fmt.Printf("dog: %v\n", dog)  //输出结果  dog: {1 花花}
}
  • 对结构体进行初始化
func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	var tom Person
	fmt.Printf("tom: %v\n", tom) //输出结果  tom: {0 0  }
	tom = Person{
		id:    101,
		name:  "tom",
		age:   20,
		email: "888@qq.com",
	}
	fmt.Printf("tom: %v\n", tom) //输出结果   tom: {101 20 tom 888@qq.com}

	//使用new关键字初始化
	var Jim = new(Person)
}
  • 结构体指针
func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	var tom Person = Person{
		id:    101,
		name:  "tom",
		age:   20,
		email: "888@qq.com",
	}

	var p_person *Person = &tom

	fmt.Printf("p_person: %v\n", *p_person) //输出结果   p_person: {101 20 tom 888@qq.com}
	fmt.Printf("p_person: %p\n", p_person)  //输出结果   p_person: 0xc0000783c0

}
  • 嵌套结构体
type Dog struct {
	name  string
	color string
	age   int
}

type Person struct {
	dog  Dog
	name string
	age  int
}

func main() {
	var tom Person
	tom.dog.name = "花花"
	tom.dog.color = "黄色"
	tom.dog.age = 5

	tom.name = "tom"
	tom.age = 30

	fmt.Printf("tom: %v\n", tom)  //输出结果  tom: {{花花 黄色 5} tom 30}
}

方法

  • Go中的方法是一种特殊的函数,定义于struct之上,与struct关联、绑定,被称为struct的接受者(receiver)。通俗讲,方法就是有接受者的函数。
  • 定义方法的语法如下:
type mytype struct{}

func (recv mytype) my_method(para) return_type{}
func (recv *mytype) my_method(para) return_type{}

// mytype 定义一个结构体
// recv 接受该方法的结构体
// my_method 方法名称
// para 参数列表
// return_type 返回值类型
type Person struct {
	name string
}

func (per Person) eat() {
	fmt.Printf("%v,eat...", per.name)
}

func main() {
	per := Person{
		name: "tom",
	}
	per.eat() //输出结果   tom,eat...
}
type Customer struct {
	name string
}

func (customer Customer) login(name string, pwd string) bool {
	fmt.Printf("customer.name:%v\n", customer.name)
	if name == "tom" && pwd == "123" {
		return true
	} else {
		return false
	}
}

func main() {
	cus := Customer{
		name: "tom",
	}

	b := cus.login("tom", "666")
	fmt.Printf("b: %v\n", b)
}
------------------------------------------
输出结果:
customer.name:tom
b: false
  • 方法的receiver type 并非一定要是struct类型
  • struct结合它的方法就等价于面向对象中的类,只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包
  • 方法有两种接受类型:(T Type)(T *Type),他们之间有区别
  • 如果receiver是一个指针类型,则会自动解除引用
  • 方法和type是分开的,意味着实例的行为和数据存储是分开的,但是它们通过receiver建立起关联关系
type Person struct {
	name string
}

func main() {
	p1 := Person{name: "tom"}
	fmt.Printf("p1: %T\n", p1)   //输出结果  p1: main.Person
	p2 := &Person{name: "tom"}   //输出结果  p2: *main.Person
	fmt.Printf("p2: %T\n", p2)
}
type Person struct {
	name string
}

func main() {
	p1 := Person{
		name: "tom",
	}

	p2 := &Person{
		name: "tom",
	}

	fmt.Printf("p1: %v\n", p1)  //输出结果 p1: {tom}
	showPerson1(p1)
	fmt.Printf("p1: %v\n", p1)  //输出结果 p1: {tom}

	fmt.Printf("p2: %v\n", p2)  //输出结果 p2: &{tom}
	showPerson2(p2)
	fmt.Printf("p2: %v\n", p2)  //输出结果 p2: &{Jake}
}

func showPerson1(per Person) {
	per.name = "Jake"

}

func showPerson2(per *Person) {
	per.name = "Jake"
}

从运行结果看,p1是值传递,拷贝了副本,地址发生了改变,而p2是指针类型,地址没有发生改变。

接口

  • 接口是一种新的类型定义,它把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}
func main() {
	c := Computer{
		name: "联想",
	}

	c.read()
	c.write()
}

type USB interface {
	read()
	write()
}

type Computer struct {
	name string
}

func (c Computer) read() {
	fmt.Printf("c.name: %v\n", c.name)
	fmt.Println("read...")
}

func (c Computer) write() {
	fmt.Printf("c.name: %v\n", c.name)
	fmt.Println("write...")
}
  • 一个类型可以实现多个接口
type Player interface {
	playMusic()
}

type Video interface {
	playVideo()
}

type Mobile struct {
}

func (m Mobile) playMusic() {
	fmt.Println("play music")
}

func (m Mobile) playVideo() {
	fmt.Println("play video")
}
func main() {
	m := Mobile{}
	m.playMusic()
	m.playVideo()
}
  • 多个类型实现同一个接口(类似于多态
type Pet interface {
	eat()
}

type Dog struct {
}

type Cat struct {
}

func (cat Cat) eat() {
	fmt.Println("cat eat...")
}

func (dog Dog) eat() {
	fmt.Println("dog eat...")
}

func main() {
	var p Pet
	p = Cat{}
	p.eat()
	p = Dog{}
	p.eat()
}
  • 接口的组合(嵌套)
type Flyer interface {
	fly()
}

type Swimmer interface {
	swim()
}

type FlyFish interface {
	Flyer
	Swimmer
}

type Fish struct {
}

func (fish Fish) fly() {
	fmt.Println("fly...")
}

func (fish Fish) swim() {
	fmt.Println("swim...")
}

func main() {
	var ff FlyFish = Fish{}
	ff.fly()
	ff.swim()
}

继承

go本质上没有继承的概念,但可以通过结构体嵌套来实现这个特性。

type Animal struct {
	name string
	age  int
}

func (a Animal) eat() {
	fmt.Println("eat...")
}

func (a Animal) sleep() {
	fmt.Println("sleep...")
}

type Dog struct {
	Animal
}

type Cat struct {
	Animal
}

func main() {
	dog := Dog{
		Animal{
			name: "dog",
			age:  2,
		},
	}

	cat := Cat{
		Animal{
			name: "cat",
			age:  3,
		},
	}

	dog.eat()
	dog.sleep()

	cat.eat()
	cat.sleep()
}

构造函数

go中没有构造函数的概念,但可以使用函数来模拟构造函数的功能

type Person struct {
	name string
	age  int
}

func NewPerson(name string, age int) (*Person, error) {
	if name == "" {
		return nil, fmt.Errorf("name 不能为空")
	}
	if age < 10 {
		return nil, fmt.Errorf("age 不能小于10")
	}
	return &Person{name: name, age: age}, nil
}

func main() {
	person, err := NewPerson("tom", 1)
	if err == nil {
		fmt.Printf("person:%v\n", *person)
	} else {
		fmt.Printf("err:%v\n", err)
	}
}

错误处理:panic、recover与defer

Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

package main

import "fmt"

func main(){
    fmt.Println("c")
     defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
        fmt.Println("d")
        if err:=recover();err!=nil{
            fmt.Println(err) // 这里的err其实就是panic传入的内容:"异常信息"
        }
        fmt.Println("e")
    }()

    f() //开始调用f
    fmt.Println("f") //这里开始下面代码不会再执行
}

func f(){
    fmt.Println("a")
    panic("异常信息")
    fmt.Println("b") //这里开始下面代码不会再执行
    fmt.Println("f")
}

-------------------------------------------------------------------
输出结果:
c
a
d
异常信息
e

go常用方法

读取文件

content, err := os.ReadFile("hh.json")
if err != nil {
	panic(err)
}

写入文件

var d1 = []byte("hello world")
err := ioutil.WriteFile("./output2.txt", d1, 0666) //写入文件(字节数组)
if err != nil {
	panic(err)
}

获取随机数

go并发编程

协程

go中的并发是函数相互独立运行的能力,goroutines是并发运行的函数,在go中创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字go task()

package main

import (
	"fmt"
	"time"
)

func show(msg string) {
	for i := 0; i < 5; i++ {
		fmt.Printf("msg: %v\n", msg)
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	go show("java")
	show("golang")
}

通道channel

  • Go中提供了被称为通道的机制,用于在goroutine之间共享数据
  • 根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换,缓冲通道没有这样的保证。
  • 通道由make函数创建,该函数指定chan关键字和通道的元素类型。
  • 无缓冲通道:在无缓冲通道中,在接收到任何值之前没有能力保存它。在这种类型的通道中,发送和接收goroutine在任何发送或接收操作完成之前的同一时刻都准备就绪。如果两个goroutine没有在同一时刻准备好,则通道会让执行其各自发送或接收操作的goroutine首先等待。同步是通道上发送和接收之间交互的基础。没有另一个就不可能发生。
  • 缓冲通道:在缓冲通道中,有能力在接收到一个或多个值之前保存它们。在这种类型的通道中,不要强制goroutine在同一时刻准备好执行发送和接收。当发送和接收阻塞时也有不同的条件。只有当通道中没有要接收的值时,接收才会阻塞。仅当没有可用缓冲区来放置正在发送的值时,发送才会阻塞。
    创建无缓冲和缓冲通道的代码块:
  • 通道的发送和接收特性:
  1. 对于同一个通道,发送操作之间是互斥的。接收操作之间也是互斥的。
  2. 发送操作和接收操作中对元素值的处理都是不可分割的。
  3. 发送操作在完全完成之前会被阻塞。接收操作也是如此。
unbuffered := make(chan int)  //整型无缓冲通道
buffered := make(chan int,10)  //整型有缓冲通道

将值发送到通道的代码块需要使用<-运算符,一个包含5个值的缓冲区的字符串类型的goroutine1通道:

goroutine1 := make(chan string ,5) //字符串缓冲通道
goroutine1 <- "hello"  //通过通道发送字符串

从通道接收值:

data := <- goroutine1   //从通道接收字符串

例子1:

package main 
 
import ( 
    "fmt" 
    "math/rand" 
    "time" 
) 
 
// 创建int类型通道,只能传入int类型值 
var values = make(chan int) 
 
func send() { 
    rand.Seed(time.Now().UnixNano()) 
    value := rand.Intn(10) 
    fmt.Printf("send: %v\n", value) 
    // time.Sleep(time.Second * 5) 
    values <- value 
} 
 
func main() { 
    // 从通道接收值 
    defer close(values) 
    go send() 
    fmt.Println("wait...") 
    value := <-values 
    fmt.Printf("receive: %v\n", value) 
    fmt.Println("end...") 
} 

例子2:

ch1 := make(chan int)                 //创建一个整型类型的通道
ch2 := make(chan interface{})         //创建一个空接口类型的通道, 可以存放任意格式
 
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)             //创建Equip指针类型的通道, 可以存放*Equip
package main

import "fmt"

type Product struct {
	full_name       string
	sale_start_date string
	is_sale         bool
}

func main() {
	structchan := make(chan *Product, 5)

	t1 := Product{
		full_name:       "a124",
		sale_start_date: "2031-12-11",
		is_sale:         true,
	}
	structchan <- &t1
	close(structchan)
	t2 := <-structchan
	fmt.Printf("t2: %v\n", t2)  //输出结果   t2: &{a124 2031-12-11 true}
}

参考

golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]
Go-常用命令go的使用(build、env、run、fmt等)
Go语言-Golang Gin Go Web Gorm Go-Micro微服务教程
Go panic 用法
Channel 通道详解
一看就懂系列之Golang的goroutine和通道

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值