Golang 基础学习

go run main.go
go build && ./main
go语言类似面向对象语言又有区别,没有继承概念,通过函数组合来简化代码开发和复用。

使用vscode开发配置国内源

go env -w GOPROXY=https://goproxy.cn,direct

goland附加进程调试

1、设置断点
2、安装:go get -u github.com/google/gops
3、打包
go build -gcflags=“all=-N -l” -o demo1

goland远程调试

go remote
服务器执行:dlv debug --headless --listen=:2345 --api-version=2 --accept-multiclient
本地配置

dep包管理器

# 安装
brew install dep

mkdir $GOPATH/src/test
cd  $GOPATH/src/test

# 初始化后会生成下列3个文件/目录,vendor里的依赖优先加载。
dep init
├── Gopkg.lock # 自动生成的文件
├── Gopkg.toml # 定义直接依赖项
└── vendor  # 这个目录的依赖代码是优先加载的,类似 node 的 node_module 目录。

# 依赖管理帮助
dep help ensure
# 添加一条依赖
dep ensure -add github.com/bitly/go-simplejson
# 这里 @= 参数指定的是 某个 tag
dep ensure -add github.com/bitly/go-simplejson@=0.4.3
# 添加后一定记住执行 确保 同步
dep ensure
# 建议使用
dep ensure -v
#  删除没有用到的 package
dep prune -v

变量

变量定义
var varname type = value
var varname1, varname2 type = v1, v2
varname := value (只允许在func函数内定义)

特殊:_(下划线) 任何赋予它的值都会被丢弃,
例:变量varname赋值20成功,10被丢弃

_, varname := 10, 20

常量

所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。
例:

v2 := 10
const i = 1000
i = v2 // 当对常量赋值时,会发现编辑器提示报错

基本数据类型

判断数据类型

fmt.Printf("%T", 1.121) // 打印数据类型

基本数据类型转换

	//1、string到int
	int, _ := strconv.Atoi(string2)
	fmt.Printf("%T %d", int, int)

	//2、string到int64
	int64, _ := strconv.ParseInt(string2, 10, 64)
	fmt.Printf("\n%T", int64)
	//
	//3、int到string
	string := strconv.Itoa(int)
	fmt.Printf("\n%T", string)
	//
	//4、int64到string
	stringA := strconv.FormatInt(int64, 10)
	fmt.Printf("\n%T", stringA)
	//
	//5、字符串到float64
	var string4 = "haha"
	float64, _ := strconv.ParseFloat(string4, 32)
	fmt.Printf("\n%T", float64)
	
	//6、int64转int
	//intb := int(int64)
	//fmt.Printf("%T", intb)
	//
	//7、int转int64
	//int64:=int64(int)

整型

uint:正整数
int:所有整数

	var num1 uint
	var num2 int
	num1 = 255
	num2 = -1
	fmt.Println(num1, num2)

浮点型

	var num3 float64
	num3 = 3.141592654

布尔类型

	var bool1 bool
	bool1 = false

字符串(两种)

  1. uint8类型( byte 型),代表了ASCII码的一个字符。
  2. rune类型,代表一个 UTF-8字符(处理中文)。

1.字符串默认不可变,若想变,需要转为为[]byte类型或rune类型

	s := "hello"
	c := []byte(s)  // 英文字符串 s 转换为 []byte 类型(
	c[0] = 'c'
	s2 := string(c)  // 再转换回 string 类型
	fmt.Printf("%s\n", s2)

	s3 := "12哈哈"
	rune2 := []rune(s3) // 中文使用[]rune)
	rune2[0] = '哈'
	fmt.Println(string(rune2))

2.使用+操作符来连接两个字符串

3.切片操作–同python

4.多行字符串使用 ``
res := 111 222 333

使用strings库处理字符串:https://studygolang.com/articles/5769
例:

	s := "11 22"
	fmt.Println(strings.Contains(s, "11"))

5.强制类型转换
int()
float64()
string()

array数组与切片

1.必须指定元素的类型
2.[…] 根据初始值自动推断数组长度是多少

数组定义及常用方法

len(arr):获取长度
cap(arr):获取容量

// 数组定义
func main() {
	// 必须指定元素的类型和容量
	// 长度是数组类型的一部分,a1和a2
	var a1 [3]bool // [true false ture]
	var a2 [4]bool //
	// %T元素类型
	fmt.Printf("%T %T\n", a1, a2)

	// 数组初始化,默认是零值(布尔:false,整形和浮点型:0,字符串:"")
	fmt.Println(a1, a2)
	// 初始化1
	a1 = [3]bool{true, true, false}
	fmt.Println(a1)
	// 初始化2: ... 根据初始值自动推断数组长度是多少
	a100 := [...]int{1, 1, 3}
	fmt.Println(a100)
	// 初始化3: 根据索引初始化
	a3 := [5]int{0: 1, 4: 2}
	fmt.Println(a3)
	// 初始化4: new
	var d = new([10]int)
	d[3] = 100


	// 数组是值类型
	b1 := [3]int{1, 2, 3}
	b2 := b1
	b2[0] = 100
	fmt.Println(b1, b2, b1)
}

数组操作(转切片)

数组没有append方法,需要先将数组转化为切片,对切片进行操作

func main() {
	// 数组没有append方法,需要先将数组转化为切片,对切片进行操作
	a := [3]int{0, 1, 2}
	cl := a[:]
	fmt.Println(cl)
	cl = append(cl, 3, 4)
	// 当容量(cap)不足时,切片会在上一个数组长度(len)基础上成倍扩充容量
	fmt.Println(len(cl), cap(cl)) // 当有5个元素时,此时cap是6,
	cl = append(cl, 5, 6)
	fmt.Println(cl)
	fmt.Println(len(cl), cap(cl)) // 当有7个元素时,超过原来的cap,则此时cap是12
	/*
		[0 1 2]
		5 6
		[0 1 2 3 4 5 6]
		7 12
	*/
}

初始化切片

func main() {
	a := [3]int{0, 1, 2}
	cl := a[:]
	cl1 := a[2:]
	fmt.Println(cl1)
	cl = append(cl, 10)
	fmt.Println(cl)
	// copy(dest, src) 将源切片中的元素复制到目标切片中(从index 0 开始覆盖)
	copy(cl, cl1)
	fmt.Println(cl)

	// 1、新建一个空切片
	var aa []int
	// 2、使用make新建一个切片,必须指定长度
	aaa := make([]int, 5)
	fmt.Println(aa, aaa)
	/*
		[2]
		[0 1 2 10]
		[2 1 2 10] 可以看到从index 0 开始覆盖
		[] [0 0 0 0 0]
	*/
}

数组遍历

// 数组遍历
	citys := [...]string{"beijing", "shanghai", "shenzhen"}
	// 1.根据索引遍历
	for i := 0; i < len(citys); i++ {
		fmt.Println(citys[i])
	}
	// 2.for range 遍历
	for i, v := range citys {
		fmt.Println(i, v)
	}
	// 2维数组定义[[1,2] [3,4] [5,6]],
	// 最外层可以写...,即a11 = [...][2]int{}
	var a11 [3][2]int
	a11 = [3][2]int{
		[2]int{1, 2},
		[2]int{3, 4},
		[2]int{5, 6},
	}
	fmt.Println(a11)
	for _, v := range a11 {
		fmt.Println(v)
		for _, v2 := range v {
			fmt.Println(v2)
		}
	}

map字典

map定义及常用方法

func main() {
	// map是引用类型,必须初始化,默认是nil。map[key类型]value类型
	// map[key的数据类型]value的数据类型
	// 初始化1
	m := make(map[int]string)
	m[1] = "dog"
	fmt.Println(m)

	// 初始化2
	m1 := map[string]string{
		"1": "true",
		"2": "false",
	}

	fmt.Println(m1)
	// 初始化3(较为麻烦,需要两步)
	var m2 map[string]string
	m2 = map[string]string{}
	m2["name"] = "lisi"
	m2["age"] = "18"
	fmt.Println(m2)

	// 初始化4 (终极大招,使用interface可以接收任意value类型)
	m3 := map[int]interface{}{}
	m3[1] = 1
	m3[2] = false
	m3[3] = "str"
	fmt.Println(m3)

	// 删除map元素
	delete(m3, 1)

	// 查看元素在map中是否存在, map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
	captial, ok := m3[3]
	if ok {
		fmt.Println("exist is:", captial)
	} else {
		fmt.Println("not exist")
	}
	/*
		map[1:dog]
		map[1:true 2:false]
		map[age:18 name:lisi]
		map[1:1 2:false 3:str]
		exist is: str
	*/
}

map遍历

	// 使用range遍历,如果是集合,返回 key 和 value
	for key := range testMap {
		fmt.Println(key, ":", testMap[key]) //打印 k,v
	}
	for k, v := range testMap {
		fmt.Println(k, v)
	}

指针

&符号取地址,*符号根据地址取值

func main() {
	//指针--获取变量内存地址
	a := "aaa"
	fmt.Println("a 的内存地址: ", &a, "; a的原值: ", a)
	//指针--通过内存找到原值
	b := &a
	//对内存地址赋值
	*b = "bbb"
	fmt.Println("b 的内存地址: ", b, "; b的原值: ", *b, "; a的新值: ", a)

	//指针--空指针判断
	var p *int
	if p != nil {
		fmt.Println("p is null")
	} else if p == nil {
		fmt.Println("p =", p)
	}
}
func showMemAddr(i *int) {
	fmt.Println(i)  // 输出内存地址
	fmt.Println(*i) // 输出原值
	return
}
func main() {
	i := 1
	showMemAddr(&i)
	fmt.Println(&i) // 输出内存地址

}

流程控制

运算符

比较运算符(操作的数据类型必须相同)
==
!= 
>
<
<=
>=

算数运算符(加减乘除余)
+-*/%

逻辑运算符
&& 与:两个条件是否都为true
|| 或:两个条件至少一个为true
! 非:条件为false

if

	if x := aaa; x > 10 {
		fmt.Println(">10")
	} else if x == 10 {
		fmt.Println("=10")
	} else {
		fmt.Println("<10")
	}

switch case

// 常规案例
i := 10
switch i {
case 1:
    fmt.Println("i is equal to 1")
case 10, 11, 22:
    fmt.Println("i is equal to 10 or 11 or 22")
default:
    fmt.Println("All I know is that i is an integer")
}
// 使用运算符
age := 20
	switch {
	case age < 25:
		fmt.Println("<25")
	case age > 25:
		fmt.Println(">25")
	default:
		fmt.Println("nono")
	}

defer延时执行

上图比较直观的看出defer的执行时机

1.在defer后指定的函数会在函数退出前调用,常用:资源清理、文件关闭、解锁、记录时间等

	func ReadWrite() bool {
	    file.Open("file")
	    defer file.Close() // 只需调用一次,省事
	    if failureX {
	        return false
	    }
	    if failureY {
	        return false
	    }
	    return true
	}

2.defer采用后进先出模式

	for i:=0; i<5; i++ {
		defer fmt.Printf("%d",i) // 43210
	}

for循环

	// 1. 常规for循环,有限制条件
	count := 10
	for i := 0; i < count; i++ {
		fmt.Println(i)
	}
	// 2. 无限循环,等价于while True
	for {
		fmt.Println(1)
	}
	// 3. 对arr和map遍历使用for ... range
	for i, n := range numbers {
		fmt.Printf("index %d\n", i) 
		fmt.Printf("valuse %d\n", n)
	}

goto跳转语句

func main() {
	a := 0
A:
	for a < 10 {
		a++
		fmt.Println(a)
		if a == 10 {
			break A
			goto B
		}
	}
B:
	fmt.Println("我是B")
}
/*
1
2
3
4
5
6
7
8
9
10
我是B
*/

函数

内置函数

append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close           -- 主要用来关闭channel
delete          -- 从map中删除key对应的value
panic           -- 停止常规的goroutine  (panic和recover:用来做错误处理)
recover         -- 允许程序定义goroutine的panic动作
imag            -- 返回complex的实部   (complex、real imag:用于创建和操作复数)
real            -- 返回complex的虚部
make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new             -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
cap             -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy            -- 用于复制和连接slice,返回复制的数目
len             -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println   -- 底层打印函数,在部署环境中建议使用 fmt 包

函数定义

func 函数名(参数)(返回值){
    函数体
}

函数几种定义方法

package main

import "fmt"

// 1.有参数有返回值
func f0(x int, y int) (res int) {
	// 法1
	return x + y
	// 法2
	// res = x + y
	// return  // 可以省略返回值,当然也可以写上
}

// 2.无参数和返回值
func f1() {
	fmt.Println("f1")
}

// 3.无返回值
func f2(x int, y int) {
	fmt.Println(x + y)
}

// 4.无参数有返回值
func f3() int {
	// 法1
	// return 3
	// 法2
	res := 3
	return res
}

// 5.1.多个返回值,用时变量数量需要与返回值数量一致,不需要可以用_占位,如:s41, s42, _ := f4()
func f4() (int, string, int) {
	return 1, "dsd", 3
}

// 5.2.多个返回值
func f7(x int, y int) (sum int, sub int) {
	sum = x + y
	sub = x * y
	// fmt.Println(sum, sub)
	return
}

// 6.参数类型可简写
func f5(x, y, z int, m, n string, i, j bool) int {
	return x + y
}

// 7.可变长参数... ,注意只能放到最后
func f6(x string, y ...int) {
	fmt.Println(x)
	fmt.Println(y) // y的类型是切片 [] int
}

// 8.函数作为参数
func add(x, y int) int {
	return x + y
}
func sub(x, y int) int {
	return x - y
}
func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}

func main() {
	s := f0(3, 5)
	fmt.Println(s)
	f1()
	f2(1, 3)
	s3 := f3()
	fmt.Println(s3)
	s41, s42, s43 := f4()
	fmt.Println(s41, s42, s43)
	s5 := f5(2, 2, 2, "m", "n", false, false)
	fmt.Println(s5)
	f6("呵呵呵", 1, 2, 3)
	fff, ggg := f7(3, 3)
	fmt.Println(fff, ggg)

	ret2 := calc(10, 20, add)
	fmt.Println(ret2)
}

init函数

程序启动自动调用,与python类似

匿名函数和闭包

匿名函数:常用于回调函数和闭包

// 9.闭包=函数+引用环境
func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	// 匿名函数1.保存到变量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(3, 4)
	// 匿名函数2.()直接执行
	func(x, y int) {
		fmt.Println(x, y)
	}(20, 10)
	// 闭包:变量bibao是一个函数并且它引用了其外部作用域中的x变量,此时在bibao的生命周期内,*变量x一直有效*。
	var bibao = adder()
	fmt.Println(bibao(10)) // 10
	fmt.Println(bibao(20)) // 30
	fmt.Println(bibao(30)) // 60
}

回调函数(函数作为值)

type typeName func(input1 inputType1 , input2 inputType2 [, …]) (result1 resultType1 [, …])

type Callback func(x,y int) int

// 说白了就是提供一个接口,通过外部函数实现具体方法
func test(x, y int, callback Callback) int {
	return callback(x,y)
}
func add(x, y int) int {
	return x + y
}
func del(x, y int) int {
	return x - y
}

func main() {
	x, y := 1,2
	fmt.Println(test(x,y,add))
	fmt.Println(test(x,y,del))
}

递归函数(函数自己调用自己)

// 递归函数,函数自己调用自己,超过一定的设定值return
func addMe(a, b int) int {
	a = a + b
	if a >= 10 {
		fmt.Printf("over %v", a)
		return a
	}
	fmt.Println("go on")
	return addMe(a, b)
}
func main() {
	addMe(1, 2)
}

struct 结构体类型

struct是一种数据类型,自己打造的用做一组 属性/字段 的容器。

方法与函数的区别:函数不属于任何类型,方法属于特定的结构体类型

func (接收者变量 接收者类型) 方法名(参数列表)(返回参数){
	函数体
}
下面有写例子
//常用:定义struct
type person struct {
	name  string
	age   int
	hobby []string
}
//继承/结构体嵌套
type student struct {
	person
	weight string
}

//自定义默认值
func Newperson(time string) person {
	a := person{
		Rating: 1, // fmt.Printf("%+v\n", Newperson()) 初始化不传参数输出零值,可以通过此方法设置默认值
	}
	return a
}


func main() {
	// 初始化1
	var p person
	p.name = "zs"
	p.age = 10
	// 初始化2,若没有显示指定key,则需要把所有value都写完全
	p1 := person{
		"ls",
		20,
		[]string{"吃饭", "睡觉"},
	}
	// 初始化3
	p2 := person{
		name: "ww",
		age:  30,
	}
	fmt.Println(p, p1, p2) // {zs 10 []} {ls 20 [吃饭 睡觉]} {ww 30 []}
	
	// 初始化嵌套结构体
	m := student{person{name:"zzd",age:18}, "80kg"}
	// 访问字段
	fmt.Println(m.name, m.age, m.weight)
	// 修改字段
	m.weight = "99kg"
	fmt.Println(m.weight)
}
// 人结构体
type person struct {
	name string
	age  int
}

// 地址结构体
type address struct {
	province string
	city     string
	person   person // 结构体嵌套
}

/* 结构体嵌套使用
addr := address{
	province: "bj",
	city:     "bj",
	person: person{
		name: "zzd",
		age:  20,
	},
}
fmt.Printf("%#v\n", addr) // main.address{province:"bj", city:"bj", person:main.person{name:"zzd", age:20}}
*/

// 构造函数(构造结构体变量的函数):用于处理结构体返回值(在多处使用时建议使用构造函数)
func newPerson(name string, age int) *person {
	return &person{
		name: name,
		age:  age,
	}
}

/*
方法与函数的区别:函数不属于任何类型,方法属于特定的结构体类型
func (接收者变量 接收者类型) 方法名(参数列表)(返回参数){
	函数体
}
*/
// person的方法Dream只有person可以调用
func (p person) Dream() {
	fmt.Printf("%s有梦想\n", p.name)
}
// 指针接收者
// 1. 需要修改结构体变量值时使用
// 2. 结构体本身比较大,拷贝开销大时使用
// 3. 保持一致性:如果一个方法使用了指针接收者,其它方法也统一使用
func (p *person) SetAge(newAge int) {
	p.age = newAge
}

// josn类型结构体,字段首字母需大写,可通过`json:name`打tag转小写,注意``内不能有空格
type people struct {
	Name string `json:"name"`
	Age  int    `json:"age`
}

// animal 结构体继承
type animal struct {
	name string
}

// animal 实现了move函数
func (a *animal) move() {
	fmt.Printf("%s会动\n", a.name)
}

// dog 结构体继承 animal
type dog struct {
	feet    int8
	*animal // 通过嵌套匿名结构体实现继承
}

// dog 实现了 wang 函数
func (d *dog) wang() {
	fmt.Printf("%s 汪汪汪\n", d.name)
}

func main() {
	// 结构体初始化方法1:使用key value
	p1 := person{
		name: "huaz",
		age:  18,
	}
	fmt.Println("p1:", p1) // p1: {huaz 18}
	// 初始化方法2:赋值
	var p2 person
	p2.name = "zzd"
	p2.age = 19
	fmt.Println("p2:", p2) // p2: {zzd 19}
	// 初始化方法3:使用值列表,顺序需一致
	var p3 = person{
		"dd",
		29,
	}
	fmt.Println("p3:", p3) // p3: {dd 29}
	// 指针kv初始化
	p4 := &person{
		name: "xiaozi",
		age:  10,
	}
	fmt.Printf("p4: %#v\n", p4) // p4: &main.person{name:"xiaozi", age:10}
	// 字段无初始值默认为零值
	p5 := &person{
		name: "haha",
	}
	fmt.Printf("p5: %#v\n", p5) // p5: &main.person{name:"haha", age:0}
	// 空结构体不占空间
	var v struct{}
	fmt.Println(unsafe.Sizeof(v)) // 0

	// 1.序列化:结构体变量 ---> json格式的字符串
	// 2.反序列化:json格式 ---> 结构体变量
	o1 := people{
		Name: "dalin",
		Age:  3,
	}
	// 序列化json
	b, err := json.Marshal(o1)
	if err != nil {
		fmt.Printf("err: %v", err)
		return
	}
	fmt.Printf("json: %v\n", string(b)) // json: {"name":"dalin","Age":3}
	// 反序列化json
	str := `{"name": "zz", "age": 10}`
	var o2 people
	json.Unmarshal([]byte(str), &o2)
	fmt.Printf("ajson: %#v\n", o2) // ajson: main.people{Name:"zz", Age:10}

	// 调用构造函数
	p9 := newPerson("zs", 20)
	fmt.Printf("%#v\n", p9) // &main.person{name:"zs", age:20}
	// 调用方法
	p9.Dream() // zs有梦想
	p9.SetAge(30)
	fmt.Println(p9.age)
	// 初始化继承自animal的dog
	d1 := &dog{
		feet: 4,
		animal: &animal{
			name: "huahua",
		},
	}
	fmt.Println(d1.name, d1.feet) // huahua 4
	d1.move()                     // huahua会动
	d1.wang()                     // huahua 汪汪汪
}

interface 接口类型

接口是方法的集合,是一种特殊的类型,它规定了变量有哪些方法。只要实现该接口的方法,都可以用这个接口接收。可以理解为定义了一种规范?必须实现接口定义的方法,才能使用。go提倡面向接口编程。
场景:不关心一个变量是什么类型,只关心能调用它什么方法(注:参数规则须一致)。如:多种数据库引擎的增删改查。

接口定义

type 名字 interface{
    func1(arg1, arg2 ...)(return1, return2 ...)
    func2(arg1, arg2 ...)(return1, return2 ...)
    ...
}

接口案例

package main

import "fmt"
// 接口嵌套示例
// type animal interface {
// 	mover
// 	eater
// }

// type mover interface {
// 	move()
// }
// type eater interface {
// 	eat(s string)
// }

// 定义一个car接口类型,只要实现了run()和color()都能被car调用,color()需要传一个参数
type car interface {
	run()
	color(s string)
}

// drive方法调用run()
func drive(c car) {
	c.run()
}

// bmw 和 aodi都实现了run和color,就可以被car接口直接调用
type bmw struct {
	name string
	feet int8
}

// 使用值接收者实现接口和指针接收者实现接口的区别:
// 1.使用值接收者实现接口所有方法,结构体类型和结构体指针类型变量都能存
func (b bmw) run() {
	fmt.Printf("%s速度70m\n", b.name)
}

func (b bmw) color(s string) {
	fmt.Printf("%s色\n", s)
}

// 2.使用指针接收者实现接口所有方法,只能存结构体指针类型变量
// func (b *bmw) run() {
// 	fmt.Printf("%s速度70m\n", b.name)
// }

// func (b *bmw) color(s string) {
// 	fmt.Printf("%s色\n", s)
// }
type aodi struct {
	name string
}

func (a aodi) run() {
	fmt.Printf("%s速度80m\n", a.name)
}
func (a aodi) color(s string) {
	fmt.Printf("%s色\n", s)
}

func main() {
	var c1 car
	fmt.Println(c1) // <nil>
	// 使用值接收者调用
	var b = bmw{
		name: "宝马",
		feet: 4,
	}
	// 使用指针接收者调用
	// var b = &bmw{
	// 	name: "宝马",
	// 	feet: 4,
	// }
	var a = aodi{
		name: "奥迪",
	}
	b.color("红") //红色
	a.color("黄") //黄色
	c1 = a
	fmt.Println(c1) // {奥迪}
	c1 = b
	fmt.Println(c1) // {宝马}
	drive(a)        //奥迪速度80m
	drive(b)        //宝马速度70m
}

package包

1.首字母大写外部可见,小写只能在当前包内用,如

package pkg2

import "fmt"

// 包变量可见性

var a = 111 // 首字母小写,外部包不可见,只能在当前包内使用

// 首字母大写外部包可见,可在其他包中使用
const Mode = 1

type person struct { // 首字母小写,外部包不可见,只能在当前包内使用
	name string
}

type Student struct {
	Name  string //可在包外访问的方法
	class string //仅限包内访问的字段
}

type Payer interface {
	init() //仅限包内访问的方法
	Pay()  //可在包外访问的方法
}

// 首字母大写,外部包可见,可在其他包中使用
func Add(x, y int) int {
	return x + y
}

func age() { // 首字母小写,外部包不可见,只能在当前包内使用
	var Age = 18 // 函数局部变量,外部包不可见,只能在当前函数内使用
	fmt.Println(Age)
}

// init方法只要调用包就会调用,无论是否引用
func init() {
	fmt.Println("this is init...")
}

2.包import导入的几种类型及ini()自动初始化调用

package main

import (
	"fmt"     // 标准倒入包
	. "fmt"   // 重命名包名写法1,可直接调用Println("haha"),不需要写fmt了,即将该文件下内容都导入进来
	_ "os"    // 导入匿名包,只导入包,包括init()方法,不导入包内普通方法。例如数据库连接包等
	p2 "pkg2" // 重命名包名写法2
)

func main() {
	a := p2.Mode // 可调用大写字母开头的变量
	fmt.Println(a)
	b := p2.Add(1, 4) // 可调用大写字母开头的方法
	fmt.Println(b)    // 标准包输出
	Println("haha")   // . "fmt" 包输出
}
=======输出=======
this is init...
1
5
haha

Goroutine并发编程

goroutine与线程

  • 一个操作系统线程对应用户态多个goroutine。
  • go程序可以同时使用多个操作系统线程。
  • goroutine和OS线程是多对多的关系,即m:n。

可增长的栈内存

  • OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈可以按需增大和缩小。

goroutine调度

  • GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。
  • G就是goroutine,里面除了存放本goroutine信息外还有与所在P的绑定等信息。
  • P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行。

通过sync.WaitGroup主动监听goroutine并发执行状态

// 通过sync.WaitGroup主动监听goroutine执行结束
var wg sync.WaitGroup

func wgTest(i int) {
	time.Sleep(time.Second * time.Duration(rand.Intn(1)))
	fmt.Println(i)
	defer wg.Done() // wg执行完
}

func a() {
	defer wg.Done() // wg执行完毕
	for i := 1; i < 10; i++ {
		fmt.Println("A:", i)
	}
}

func b() {
	defer wg.Done() // wg执行完毕
	for i := 1; i < 10; i++ {
		fmt.Println("B:", i)
	}
}

func main() {
	runtime.GOMAXPROCS(12) // 指定并发占用的cpu核数
	for i := 0; i < 10; i++ {
		wg.Add(1) // 启动一个goroutine wg计数器+1
		go wgTest(i)
	}
	wg.Add(2) // wg计数器+2
	go a()
	go b()
	wg.Wait() // wg等待上面流程执行
}
/*
并发结果输出通常都是乱序的
*/

通过channel实现Goroutine之间通信完成一组任务

  • 如果说Goroutine是一种支持并发编程的方式,那么通道(chan)就是一种与Goroutine通信的方式。通道(chan)让数据能够进入和离开Goroutine,可方便Goroutine之间进行通信。channel是先进先出的。
    不要通过共享内存来通信,而通过通信来共享内存
创建通道:c := make(chan (string)) 
向通道发送消息:c <- "sleep finish."
msg变量接收消息:msg := <-c 
package main

import (
	"errors"
	"fmt"
	"time"
)

// 3.slowfunc将通道作为参数
func slowfunc(c chan string) {
	time.Sleep(time.Second * 2)
	c <- "sleep finish." // 4.函数执行完毕向通道c发送一条消息
}

// 消息接收者遍历消息
func reciever(c chan string) {
	for msg := range c {
		fmt.Println(msg)
	}
}

func pinger(c chan string) {
	t := time.NewTicker(1 * time.Second) // 新建NewTicker定时器
	// 向通道循环输入 ping
	for {
		c <- "ping"
		<-t.C
	}
}

// 指定通道访问权限
// 指定通道只读
func channelReader(messages <-chan string) {
	msg := <-messages
	fmt.Println(msg)
}

// 指定通道只写
func channelWriter(messages chan<- string) {
	messages <- "Hello world"
}

// 指定通道读写
func channelReaderAndWriter(messages chan string) {
	msg := <-messages
	fmt.Println(msg)
}

// select验证通道case
func ping1(c chan string) {
	time.Sleep(time.Second * 4)
	c <- "ping1 finish"
}
func ping2(c chan string) {
	time.Sleep(time.Second * 3)
	c <- "ping2 finish"
}

// select验证无限阻塞
func sender(c chan string) {
	t := time.NewTicker(1 * time.Second) // 定时器
	for {
		c <- "i am send a message" // 向通道c内发送消息
		<-t.C
	}
}
func main() {
	// errors包创建错误
	err := errors.New("some wrong")
	if err != nil {
		fmt.Println(err)
	}
	// fmt包格式化输出错误
	name, role := "zhidao", "zhang"
	errs := fmt.Errorf("the %v  %v quit", role, name)
	if err != nil {
		fmt.Println(errs)
	}
	// 如果说Goroutine是一种支持并发编程的方式,那么通道就是一种与Goroutine通信的方式。通道让数据能够进入和离开Goroutine,可方便Goroutine之间进行通信。
	// 不要通过共享内存来通信,而通过通信来共享内存
	// 一、通道实例
	c := make(chan (string)) // 1.创建通道
	go slowfunc(c)           // 2.go执行slowfunc函数,并将通道c传入
	msg := <-c               // 5.接收来自通道c的消息。阻塞直到收到消息为止,避免进程过早退出
	fmt.Println(msg)         // 6.接收成功打印消息

	// 二、缓冲通道:没有接收者的情况下使用
	message := make(chan (string), 2) // 1.创建长度为2的通道限制调度数量
	message <- "hello"
	message <- "world"
	close(message) // 2.发送完消息关闭通道
	fmt.Println("push two message onto chan")
	time.Sleep(time.Second * 1)
	reciever(message) // 3. 消息接收者

	// 三、通道和流程控制
	c1 := make(chan string) // 创建通道c1
	go pinger(c1)

	for i := 0; i < 2; i++ {
		msg1 := <-c1 // 接收m1通道消息
		fmt.Println(msg1)
	}
	// 四、使用select。select用于通道chan。跟switch用法很像,即:执行最先满足的条件
	chan1 := make(chan string)
	chan2 := make(chan string)

	go ping1(chan1)
	go ping2(chan2)

	select {
	case mm1 := <-chan1:
		fmt.Println("recieved ", mm1)
	case mm2 := <-chan2:
		fmt.Println("recieved", mm2)
	case <-time.After(500 * time.Millisecond): // 超时条件
		fmt.Println("time out")
	}
	// 五、使用select无限阻塞,随时能够返回消息,最终设置超时退出通道判断
	chan3 := make(chan string)
	stop := make(chan bool) // bool的零值为false
	go sender(chan3)
	go func() {
		time.Sleep(time.Second * 2)
		fmt.Println("time is up!")
		stop <- true // 向stop通道内发送true
	}()
	for {
		select {
		case <-stop: // 接收stop通道内的条件,为ture则执行此处逻辑
			return
		case msg := <-chan3: // 接收chan3通道的条件
			fmt.Println(msg)
		}
	}
	// ===========输出==============
	// some wrong
	// the zhang  zhidao quit
	// sleep finish.
	// push two message onto chan
	// hello
	// world
	// 例三:
	// ping
	// ping
	// 例四:
	// time out
	// 例五:
	// i am send a message
	// i am send a message
	// i am send a message
	// time is up!
}

小技巧

处理错误errors和fmt.Errorf

func main() {
	// errors包创建错误
	err := errors.New("some wrong")
	if err != nil {
		fmt.Println(err)
	}
	// fmt包格式化输出错误
	name, role := "zhidao", "zhang"
	errs := fmt.Errorf("the %v  %v quit", role, name)
	if err != nil {
		fmt.Println(errs)
	}
}

比较两个字符串相等

fmt.Println("sa" == "sa")	//true

比较两个slice相等(map、array类型)

dict := map[string]string{"A":"a","B":"b"}
dict1:=dict
fmt.Println(reflect.DeepEqual(dict,dict1))  //true

判断变量类型reflect.TypeOf(变量名)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于上面的引用内容,golang基础语法学习可以参考以下几个步骤: 1. 首先,我们需要安装golang的开发环境。你可以直接进入官网(https://go.dev/)下载并安装golang的解释器。 2. 接下来,我们可以开始学习golang的语法。从上面的引用内容可以看出,golang的语法和Java的结构很相似,所以如果你之前有Java编程的经验,可以借鉴一些类比来学习。但是即使你没有Java的经验,也不用担心,golang的语法相对简单并且易于学习。 3. 另外,golang被称为“云计算时代的开发利器”,在使用过程中给人一种优雅、亲切、舒适的感觉。因此,在学习golang的过程中,你可能会发现它的语法设计和使用方式非常人性化。 综上所述,学习golang基础语法可以通过安装开发环境、参考Java的结构以及体验其优雅、亲切、舒适的特点来进行。希望这些信息能够帮助你开始学习golang基础语法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [一文看完golang基础语法](https://blog.csdn.net/qq_35889508/article/details/128125279)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值