goland有基础速通(需要其它编程语言基础)

tip: 无论是变量、方法还是struct的访问权限控制都是通过命名控制的,命名的首字母是大写就相当于java中的public,小写的话就是private,(private只有本包可以访问)

1 go的变量声明

普通变量

特点: 变量类型后置

方式1var num int = 1
方式2:
num := 1					// := 会帮我们自动匹配数据类型(最常用)
方式3var num = 1

常量

还是通过const关键字声明(和c是一样的)

const (
	//声明常数列表
	ZHANGSAN = 1
	LISI = 2
	....
)

并且我们可以使用iota关键字进行简写
const (
	ZHANGSAN = iota + 1      //iota默认是0,每次被使用后都会+1
	LiSI					//等同于LISI = iota + 1, 但是公式没有发生变化所以可以省略
)

常量使用:
fmt.println("ZHANGSNA = ", ZHANGSAN)

2 go的函数/方法声明

特点: func (绑定struct名 类型) 函数名(形参名 形参类型, …) 返回类型 {函数体}

func print(s string) string {
	....
}

go当中的方法是可以有多个返回值的:
func play() (string, string) {
    return zhangsan, lisi
}

//多返回值接收
name1, name2 := play()

3.对象

声明

特点: go语言的对象是一种组合概念,由结构体+一系列方法构成

type关键字:定义自定义类型

type person struct {
	name string
	age int
}

func (p *person) setName(name string) {
	p.name = name
}

上述代码就等同于java中的如下代码:
private class person {
    private String name;
    private int age;
    
    private void setName(String name) {
        this.name = name;
    }
}

"(p *person)"代表这个方法绑定person这个struct

为什么要使用"*person"?
因为go中的对象(struct)是值类型(也就是java中的基本类型),因此不传入指针的话修改也不生效(需要一点c的指针基础)

类继承

type human struct {
	name string
	age int
}

type man struct {
	human // 就代表man继承了human对象
	sex int
}

//man的创建
man1 := man{human{"zhangsan", 18}, 1}

4 init方法

特点: 导包时执行的初始化方法

package p1

import "fmt"

func init() {
	fmt.println("p1, init")
}

如果我们引入这个包,那么就会执行p1的init方法

5 指针

go中的指针和c是一样的

6 defer关键字

特点: 在defer所在的代码块执行完后执行,类似java的finally或c++的析构函数

使用场景: 文件关闭、连接关闭、资源释放等

func print() {
	defer fmt.println("2..")
	fmt.println("1..")
}
输出:先12

//defer可以有多个,通过栈结构存储
func print() {
	defer fmt.println("2..")		//2进栈
	defer fmt.println("3..")		//3进栈
	fmt.println("1..")
}
输出:先132

tip:deferreturn的执行顺序,因为defer是在所在方法的生命周期结束后才会执行
func deferCall() {
    fmt.println("defer...")
}
func returnCall() {
    fmt.println("return...")
}
func deferAndReturn() {
    defer deferCall()
    return returnCall()
}
func main() {
    deferAndReturn()
}
输出: return...
	 defer...

7 数组与动态数组

声明

固定数组(常用)
arr := [3]int{1,2,3} 

动态数组(也叫切片slice)
arr := []int{1, 2, 3} 或   //虽然我们只添加了三个初始值,但是我们还可以往后继续添加
arr1 := make([]int, 3)    //开辟大小为3的数组,但是没有设置初始值
arr2 := make([]int, 3, 5) //开辟一个大小为3,容量为5的切片(arr[3]和arr[4]实际还不可以访问)
容量的概念就是我们声明了这个空间但是我没创建(也就是我告诉你这有但是现在还没有),大小就是现在实际有的

切片扩容:当容量不够时,会自动扩容为当前容量的2倍,没指定容量的话就是切片大小

对动态数组的操作与python中的切片操作类似。

tip: 
go当中的固定数组是一种直接类型,不像是java中的引用类型,所以[3]int 和 [4]int不是同一类型的,所以在使用固定数组作为方法形参的时候如果想要改变内容应该传入指针。
而动态数组是引用类型,type(arr)得到的是[]int.

遍历

arr := []int{1,2,3,4}

方式1for index, value := range arr {
	fmt.println("下标是:", index)
	fmt.println("内容是:", value)
}

方式2for i := 1, i < len(arr), i++ {
	fmt.println(arr[i])
}

8 map

声明

var myMap map[String]string				//[]内是key类型,外是value类型

//开辟空间
myMap := make(map[string]string, 10)

//有初始值创建
myMap := map[string]string{
	"one":	"zhangsan"
	"two":	"lisi"
}

使用

myMap := make(map[string]string)
//增
myMap["one"] = "zhangsan"
myMap["two"] = "lisi"

//删
delete(myMap, "one")

//改
myMap["two"] = "wangwu"

//查
value := myMap["two"]

// 遍历
for key, value := range myMap {
	
}

9 多态

特点: 实现接口

type animal interface {
	show()
	play()
}
// cat实现了animal接口, 是通过实现接口方法
type cat struct {
	name string
}
func (c *cat) show() {
	fmt.println("这是一只猫")
}
func (c *cat) play() {
	fmt.println("猫在玩耍")
}

10.interface{}

特点: 通用数据类型,几乎所有类型都实现了interface{}

func call(this interface{}) {
	fmt.println("this is ", this)
    // 类型断言
    value, res := this.(string) // 判断this是不是是tring类型,会返回两个数据一个是this本身一个是判断结果(bool)
}

type human struct {
	name string
}

func main() {
	human1 := human{"zhangsan"}
    //call 方法可以接收任何类型
	call(human1)
	call(1)
	call("lisi")
}

11 pair

任何变量都会有一个piar对变量描述,这个piar结构如下:<type, value>,type代表变量的类型,value代表变量存储的值。

type又分为static type(基本类型,如:int、char这些)和concrete type(引用类型,如:切片)

12 反射

go提供了reflect包实现反射功能,其中有两个核心方法TypeOf()和ValueOf(), 分别用来获取参数对象的类型与值。

反射基本使用

package main

import (
	"fmt"
	"reflect"
)

type User struct {
    Name string
    Age int
}

func (this User) Call() {   //这里写*User会识别不到方法,原因在后面
	fmt.Println("User is call...")
	fmt.Printf("%v\n", this)
}

func main() {
    user :=  User{"zhangsan", 18}
	DoFiledAndMethod(user)
}

func DoFiledAndMethod(input interface{}) {
    // 获取input信息
    t := reflect.TypeOf(input)
    fmt.Println("type:", t.Name())
	v := reflect.ValueOf(input)
	fmt.Println("value:", v)

    // 遍历结构体的所有字段
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	methodNum := t.NumMethod()
	fmt.Printf("the User has %d methods\n", methodNum)

	// 通过type调用方法
	for i := 0; i < t.NumMethod(); i++ {
		method := t.Method(i)
		fmt.Printf("%s: %v\n", method.Name, method.Type)
	}
}

Go 的方法集规则

Go 的方法集规则如下:

  1. 值类型(T 的方法集包含所有接收者为 T 的方法。
  2. 指针类型(*T 的方法集包含所有接收者为 T*T 的方法。

因此:

  • User 值类型的方法集:只有接收者为 User 的方法。
  • *User 指针类型的方法集:接收者为 User*User 的方法。

13 标签

在 Go 语言中,标签(Tags) 是结构体字段的元数据,通常用于编码/解码(如 JSON、XML)、ORM 映射(如 GORM)、验证(如 validator)等场景。标签以反引号 `` 包裹,格式为 key:“value”,多个标签用空格分隔。

package main

import (
	"fmt"
	"encoding/json"
)

type User struct {
    Name string		`json:"name"`
    Age int			`json:"age"`
}

func main() {
	user := User{"zhangsan", 18}
    // 结构体 -> json
	jsonData, _ := json.Marshal(user)
	fmt.Println(string(jsonData))
    //json -> 结构体
	myUser := User{}
	err := json.Unmarshal(jsonData, &myUser)
	if(err != nil) {
		fmt.Println("err: ", err)
		return
	}
	fmt.Printf("%v\n", myUser)
}

输出:
{"name":"zhangsan","age":18}
{zhangsan 18}

14 协程

协程(Goroutine) 是一种轻量级的并发执行单元,由 Go 运行时(runtime)管理,用于实现高并发编程。

go语言对于协程的设计是将原先线程中用户态部分分离出来了,这样不需要频繁的切换,而原线程中的内核态部分成为新的线程复杂管理协程。

协程结构:

在这里插入图片描述

GMP模型控制:

关键组件

  1. G (Goroutine)
    • 包含执行栈、程序计数器(PC)、状态(运行/阻塞)。
    • 通过 go 关键字创建,轻量且可动态扩缩。
  2. P (Processor)
    • 协程调度器,绑定到 M 上运行。
    • 维护一个本地 Goroutine 队列(优先调度本地队列)。
  3. M (Machine)
    • 操作系统线程,真正执行计算的单元。
    • 由 P 分配任务,空闲时会被回收或休眠。

调度流程:

1. G 创建 → 放入 P 的本地队列(或全局队列)。//只用本地队列满会放入全局队列
2. M 获取 G → 从绑定的 P 的队列中取 G 执行。
3. G 阻塞(如 I/O)→ 该阻塞协程从本地队列移除,M解绑P,由M负责该阻塞协程,解绑后的P绑定一个新的线程,P可以正常工作去调度其他G。
4. G 就绪 → 被放回队列,等待 M 执行。

在这里插入图片描述

Goroutine 解决了线程的哪些痛点?

(1) 高创建和切换成本
  • 线程问题:每个线程需要分配较大的栈(1MB+),创建和切换涉及内核态操作,开销大。
  • Goroutine 方案:初始栈仅 2KB,由 Go 运行时在用户态调度,切换成本极低。
(2) 并发数量受限
  • 线程问题:受限于内存和内核调度,通常只能创建几千个线程。
  • Goroutine 方案:轻量级设计,单机可轻松支持百万级并发,还有协程调度器的存在支持多对多的调度关系。
(3) 共享内存导致的竞态问题
  • 线程问题:多线程需通过锁(mutex)同步,易引发死锁、数据竞争。
  • Goroutine 方案:通过 Channel 通信,避免显式锁,更安全。
(4) 阻塞导致线程浪费
  • 线程问题:一个线程阻塞(如 I/O 操作)时,线程池中的线程被占用,影响整体吞吐量。
  • Goroutine 方案:阻塞时,Go 运行时会自动挂起该 Goroutine,复用线程执行其他任务。

协程的使用

package main

import (
	"fmt"
	"time"
)
func print() {
	i := 0
	for {
		i++
		fmt.Printf("new Goroutine: i = %d\n", i)
		time.Sleep(1 * time.Second)
		
	}
}

func main() {
	go print() // 创建协程执行print方法
	
	i := 0
	for {

		i++
		fmt.Printf("main Goroutine: i = %d\n", i)
		time.Sleep(1 * time.Second)
	}
}


// 也可以通过匿名调用的方式
func main() {
	go func ()  {
		i := 0
		for {
			i++
			fmt.Printf("new Goroutine: i = %d\n", i)
			time.Sleep(1 * time.Second)
			
		}
	}() // 括号中填入方法的参数,我这是无参就没填
	
	i := 0
	for {

		i++
		fmt.Printf("main Goroutine: i = %d\n", i)
		time.Sleep(1 * time.Second)
	}
}

//执行结果
main Goroutine: i = 1
new Goroutine: i = 1
new Goroutine: i = 2
main Goroutine: i = 2
main Goroutine: i = 3
new Goroutine: i = 3
new Goroutine: i = 4
main Goroutine: i = 4

15 channel

用于协程间通信的一种方式,分为无缓冲channel有缓冲channel

无缓冲channel:channel中同一时间只能存在一个数据

package main

import (
	"fmt"
	"time"
)

// 1
func main() { // 主协程
	c := make(chan int)
    
    fmt.Println("main Goroutine try to read from channel")
    num := <-c
	fmt.Println(num)
    
	go func (){  //子协程
		c <- 1
		fmt.Println("already write")
	}()
}

// 2
func main() {
	c := make(chan int)
	go func (){
		c <- 1
		fmt.Println("already write")
	}()
	
	fmt.Println("main Goroutine not read from channel")
	
	i := 0
	for {
		i++
		fmt.Println(i)
		time.Sleep(1 * time.Second)

	}
}

输出:
// 1
fmt.Println("main Goroutine try to read from channel")
1

// 2
main Goroutine not read from channel
1
2
3
4
5

子协程向channel中写入数据后会在此阻塞直到数据被取走,同样如果主协程想要从channel中读取数据但目前channel中没有数据主协程也会阻塞等待。

有缓冲channel:其与阻塞队列的功能非常相似,允许存在多个数据在channel中,如果从空channel中获取元素或向满channel中发送元素则会触发阻塞。

// 声明有缓冲channel
ch := make(chan int 3) //创建缓冲大小为3的有缓冲channel

虽然channel和阻塞队列功能类似但他俩在实现上还是有很大的不同,感兴趣的可以去了解一下。

关闭channel:

channel一旦关闭就无法向channel中发送数据,但如果缓冲区内还有数据则还可以读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值