首页 > 其他分享 >go语言快速入门指北

go语言快速入门指北

时间:2023-07-02 10:44:06浏览次数:56  
标签:指北 入门 int fmt Vertex 接口 func go

0 前言

本文是个人自用的go语言指南学习笔记,主要是方便我个人复习。

通过上面那个指南,对于有编程基础的同学,可以在三天内速成go语言(我只花了两天

0.1 推荐学习资料

  1. 基于VSCODE的 go 环境搭建
  2. go语言指南--------适合快速入门
  3. topgoer-----我个人比较推荐这个指南
  4. go语言圣经--比较全面

1 包、函数、变量

1 包

  1. Go程序由包组成的,main程序对应一个package main
  2. Go中要导出的东西,以首字母大写命名,比如你要使用math包中的pi,那么这个东西要写成math.Pi

1.2 函数

1.2.1 函数

  1. 标准写法,支持多值返回

    func add_sub(x int, y int) (int,int) {
        return x + y,x-y
    }
    
  2. 命名返回值

    // 返回声明中定义了两个变量,可以直接在函数体内使用
    func split(sum int) (x, y int) {
    	x = sum * 4 / 9
    	y = sum - x
    	return
    }
    

1.2.2 变量

  1. 变量定义

      //1. 标准做法
      var a int = 0
      //2. 自动推导
      var a = 100
      //3. 同类型变量
      var a,b,c int
      //4. 海象运算符偷懒,省略一个var关键字,但是不能在函数外使用
      a := 100
      //5. 偷懒
      var (
      	a,b,c int = 1,2,3
          d bool = true
          e complex128 = cmplx.Sqrt(-5+12i) 
      )
    
  2. 常量 用const修饰

    const (
    	Big = 1 << 100
    	Small = Big >> 99
    )
    

2 流程控制语句

go控制流语句(if/for...)没有小括号,但是一定有大括号,而且大括号一定是不换行党直接杀死异教徒了属于是

2.1 循环

  1. go中只有for循环,写法类似c中的for,但是没有括号
  2. go中用for代替c中的while,同样是没有括号for i < 10
  3. 无限循环for{}

2.2 if-else

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {	// 允许在if条件前写一个表达式(作用域在if中)
		return v
	}
	return v
}

2.3 switch

  1. go的switch中,可以不用写break(自动给你加上了)
  2. switch的case无需为常数,也不用为整数
func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}
  1. 无条件switch,替代if-then-else
func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

2.4 defer

defer:推迟函数调用,推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

defer求值的顺序是不变的,但是返回次序会发生变化

//猜猜下面代码的返回值
func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

3 更多类型

3.1 指针

var p *int,p的用法和c一样,空指针的值为nil

3.2 结构体

type Vertex struct {
	X, Y int
}

// 初始化
var (
	v1 = Vertex{1, 2}  // 创建一个 Vertex 类型的结构体
	v2 = Vertex{X: 1}  // Y:0 被隐式地赋予
	v3 = Vertex{}      // X:0 Y:0
	p  = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)

3.3 数组

  1. 最基本的数组var a [10]int,其中长度也属于类型的一部分,也就是说[9]int,[10]int是两种不同类型

3.3.1 切片

切片是对数组的动态引用,类型为[]T

  1. 切片的长度与容量

    容量是指:从这个切片的第一个元素,到底层数组的最后一个元素的大小

    // 下面这个创建方法,会先创建一个数组,然后s引用这个数组
    // 类似先执行了 var tmp = [5]int{1,2,3,4,5}
    // 再执行了 s:=tmp[:]
    s := []int{1,2,3,4,5}		// len(s)=5 cap(s)=5
    
    s = s[:0] 		// len(s)=0 cap(s)=5		//并不会覆盖原来的s,新的s是一个引用
    s = s[:4]		// len(s)=4 cap(s)=5	扩展切片的大小,会覆盖那个引用
    s = s[2:]		// len(s)=2 cap(s)=3	舍弃前两个元素
    
  2. 修改切片的值,会同时改变底层数组的值,其他切片引用也会修改

  3. 切片的零值是 nil。nil 切片的长度和容量为 0 且没有底层数组。

3.3.2 动态数组的创建 ---make

 ```GO
 b := make([]int, 0, 5) // len(b)=0, cap(b)=5
 //make(type,len,cap),最后一个可以忽略,此时len=cap
 ```

3.3.3 append 切片扩容

发现一个很逆天的东西,解释在这里

func main() {    
    s := []int{1,2}    
    s = append(s, 3,4,5)    
    println(cap(s))
}

3.3.4 range

用range遍历的时候,实际上会返回两个值:元素的下标和对应的元素

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

3.4 映射(kv对)

  1. map类型声明:map[键的类型]值的类型
// 定义一个结构体
type Vertex struct {
	Lat, Long float64
}

// 初始化创建一个map
var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

//下面写法更加简洁 
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}
  1. 可以用make创建一个空map:var m = make(map[string]Vertex)
  2. 删除一个映射的元素:delete(m,key)
  3. 双赋值检测某个键是否存在elem,ok := m[key],若 keym 中,oktrue ;否则,okfalse

3.5 函数做参数or返回值

// 函数做参数
func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}
  1. compute函数的参数中,定义了一个func(float64,float64)float64类型的函数,参数中,将该函数命名为fn
  2. 主函数中hypot函数,首先创建了一个匿名函数,然后再赋值给这个hypot变量的

3.6 函数闭包

go的闭包

4 方法和接口

4.1 方法

  1. go没有类,但是可以为结构体定义方法,方法就是带有特殊接收者的函数
//下面是一个方法
type Vertex struct {
	X, Y float64
}

// 定义方式:func (方法接收者参数列表) 方法名(方法的参数列表) 返回类型{}
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
  1. 很神奇的是,你可以为非结构体类型声明方法,比如type float64 Myfloat,然后让这个Myfloat作为接收者

  2. 接收者的类型定义和方法声明必须在同一包内,所以如果直接使用float64为接收者,这是不允许的

    因为float64这种内建类型,不在你的包内定义,但是你可以用type重新命名一个

4.1.1 指针接收者

// 值接收者,不会修改接收者本身
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 指针接收者,会修改指针指向的接收者
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

上面的例子类似函数的指针传参or值传参的过程,但是又有不同,不同在于 方法有指针的重定向

4.1.2 方法与指针的重定向

对于函数,值传参和指针传参是严格区分的

var v Vertex
ScaleFunc(v, 5)  // 编译错误!
ScaleFunc(&v, 5) // OK

对于方法,以指针为接收者的方法被调用时,接收者既能为值又能为指针:

// Scale是一个指针接收者的函数
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK

对于语句 v.Scale(5),即便 v 是个值而非指针,带指针接收者的方法也能被直接调用。 也就是说,由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)

同样的,当方法接收者为 值 的时候,即使你传入一个指针接收者,会通过编译

// Abs()是值接收者的函数
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK,会被自动解释成(*p).Abs()

4.2 接口

go中的接口类似rust中的trait,但是go中将接口认为是一种类型,表示一组方法签名定义的集合。

接口类型的变量可以保存任何实现了这些方法的值。

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

type Abser interface {
	Abs() float64		// 方法名+返回类型
}

var a Abser
f := MyFloat(-math.Sqrt2)	// f是一个MyFloat变量
v := Vertex{3, 4}			// v是一个vertex变量

a = f  // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser

// 可以直接调用接口
a.Abs()

接口的隐式实现

一个类型通过实现一个接口的所有方法来实现该接口。这种方式就是隐式实现。

像rust中,要为某个类型实现某个trait时,要显式声明impl xxxtrait for xxx,go通过隐式实现就能完成某个接口,所以不需要有类似impl的关键字。

接口可作为值进行传递

从底层数据结构来看,接口实际上由(value,type)组成,value保存了一个具体type的具体值

type I interface{
    M()
}

// 为*T类型实现该接口
func (t *T) M() {
	fmt.Println(t.S)
}

func main(){
	var i I		//创建接口变量
    i = &T{"Hello"}		// 将i指向T类型的指针
    fmt.Printf("(%v,%T)\n",i,i)
}

输出结果为(&{Hello}, *main.T)

底层值为 nil 的接口值

// 下面的例子中,值为nil,但是type还是有的,整个接口变量也不为空
var i I
var t *T	// 定义一个*T类型,但是没有具体值
i=t
fmt.Printf("(%v,%T)",i,i)

输出结果:(, *main.T)

nil接口值

var i I
fmt.Printf("(%v, %T)\n", i, i)

此时的结果为(, )

空接口

指定了零个方法的接口值被称为 空接口

var i interface{}

// 空接口可保存任何类型的值,下面的赋值都没有问题
i = 52
i = "hello"

// 空接口被用来处理未知类型的值,比如下面这个函数,你传入任意数量的参数都行
func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

类型断言

  1. 是一种 访问接口底层值的方式

    当我们认为接口变量i存在某个具体类型T的值时,我们可以通过t:=i.(T)来取得 对应该类型 的具体值。

    如果这个接口i,不存在我们想要的类型T 的具体值,就会引发panic

    所以说,这个过程就叫 断言,(我觉得可以理解为猜测)

  2. 类型判断

    类型断言可返回两个值,通过双返回的方法,不会引发panic

    t, ok := i.(T)

    i真的有T这个类型的值,那么会返回具体值,同时ok=true

    i没有这个类型的值,那么t=该类型的空值ok= false

类型选择

是一种按顺序从几个类型断言中选择分支的结构。

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}

4.3 常用接口

4.3.1 Stringer

这是fmt包中常用的接口,Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。

// Stringer接口要包含一个String()方法
type Stringer interface {
    String() string
}

// 下面是一个例子
// 自定义了一个Person结构体
type Person struct {
	Name string
	Age  int
}

// 为自定义结构体定义 打印时如何输出
func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)		// 可以直接调用fmt的函数来打印我们自定义的结构体
}

4.3.2 错误 error

一个内建接口

// 该函数会返回一个error值,如果error值为nil,则无错误,如果不为nil,则发生了错误
type error interface {
    Error() string
}

4.3.3 Reader

io 包指定了 io.Reader 接口,它表示从数据流的末尾进行读取。

这个接口有很多用途,包括文件,网络连接等等

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。

4.3.4 图像

image 包定义了 Image 接口:

// 具体是什么,查文档吧
package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

5 并发

5.1 Go程(goroutine)

Go 程(goroutine)是由 Go 运行时管理的 轻量级线程

// 下面的实例,会启动一个新的go程并执行f(x,y,z)函数
go f(x, y, z)

f,x,y,z的求值是在当前go程中,f函数的执行则是在新的go程中

5.2 信道

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。(箭头表示数据流的方向)

// 创建信道
ch = make(chan int)

// 数据传输
ch <- v    // 将 v 发送至信道 ch。
v := <-ch  // 从 ch 接收值并赋予 v。

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

5.2.1 带缓冲的信道

ch := make(chan int, 100)

仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

5.2.2 range 和 close

发送者可通过 close 关闭一个信道来表示没有需要发送的值了。

只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。

接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完

v, ok := <-ch

之后 ok 会被设置为 false

循环 for i := range c 会不断从信道接收值,直到它被关闭。

一般来说信道不需要关闭,但是对于range循环,只能通过关闭来终止

5.2.3 select操作(多路复用)

select 语句使一个 Go 程可以等待多个通信操作。

通道(channel)实现了多个goroutine之前的同步或者通信,那么select则实现了多个通道(channel)的同步或者通信,并且select具有阻塞的特性。

select 随机执行一个可运行的 case(并发的)。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

// select后面的每个case都必须是一个channel通信操作
select {
    case <-ch1:
        // 如果从 ch1 信道成功接收数据,则执行该分支代码
    case ch2 <- 1:
        // 如果成功向 ch2 信道成功发送数据,则执行该分支代码
    default:
        // 如果上面都没有成功,则进入 default 分支处理流程
}

go中的select实际上是参考了unix中的select,多路复用。区别就是unix中是对文件句柄读写的监控,而go中的select是对信道读写的监控

  1. 如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。如果此段时间内出现了可操作的case,则直接执行这个case。一般用超时语句代替了default语句
  2. 对于空的select{},会引起死锁

5.2.4 sync.Mutex互斥锁类型

用于访问共享资源的时候进行互斥,有Lock和Unlock两个方法

我们可以用 defer 语句来保证互斥锁一定会被解锁。

标签:指北,入门,int,fmt,Vertex,接口,func,go
From: https://www.cnblogs.com/jye159X/p/17520466.html

相关文章

  • GO语言调用外部函数失败总结
    目录GO练习的项目结构Q1导入的是空路径Q2导入的路径不全Q3找不到路径A3Q4函数不可调用A4Q5报错UseasvalueA5GO练习的项目结构@:~/goProject/test.cn$tree.├──go.mod├──main.go└──model  └──mysql.go1directory,3filesQ1导入的是空路径......
  • Django:单表查询之神奇的双下划线
    一、单表查询中双下划线运用案例models.Tb1.objects.filter(id__lt=10,id__gt=1)、#获取id大于1且小于10的值models.Tb1.objects.filter(id__in=[11,22,33])#获取id等于11、22、33的数据models.Tb1.objects.exclude(id__in=[11,22,33])#notinmodels.Tb1.objec......
  • ImportError:无法从“django.utils.encoding”导入名称“force text”[Python错误已解
    在软件开发过程中遇到错误是很常见的,在使用Python和Django时,这样的错误之一就是ImportError:cannotimportname'forcetext'from'django.utils.encoding'.forcetext此特定错误表明从模块导入方法时出现问题django.utils.encoding。缺少的方法用于将输入数据转换为一致......
  • go src - sync.Map
    前言在并发编程中,我们经常会遇到多个goroutine同时操作一个map的情况。如果在这种情况下直接使用普通的map,那么就可能会引发竞态条件,造成数据不一致或者更严重的问题。sync.Map是Go语言中内置的一种并发安全的map,但是他的实现和用法与普通的map完全不同,这篇文章将详细介绍这些区......
  • go反射使用及proto协议字段随机动态赋值
    1.基本概念Go语言的反射是一种在运行时动态访问程序元数据的能力。反射可以让我们在运行时检查类型和变量,例如它的大小、方法和动态的值等。这种机制让我们可以编写更加通用的函数和类型,而不需要关心具体的类型。在Go语言中,反射的实现主要依赖于两种类型:Type和Value。这......
  • Golang实现图片与视频的缩略图生成
    图片与视频的缩略图是一个十分常见的需求,比如即时消息。这里摘取了Golang项目中的相关代码,分享图片与视频相关处理的开发经验。图片缩略图缩略图的尺寸分为两种规则:1)边长模式,生成正方形缩略图;2)宽高模式,又分三种:指定宽高、指定宽(高等比缩放)、指定高(宽等比缩放)。如果原图为png或gif,缩......
  • django优缺点 # ninja的优点可替代DRF
    摘抄:https://www.cnblogs.com/fnng/p/16084825.htmldjango优点通过脚手架创建项目/应用:不用考虑项目架构怎么设计。自带Admin后台:在没有前端的情况下,可以比较方便通过Admin对数据进行操作。自带常用模块:一个命令就能生成group、user、session...表,一般个系统都需要user表吧......
  • 嵌入式linux开发 | u-boot启动logo修改
    原文:https://zhuanlan.zhihu.com/p/582316377一、导读使用嵌入式linux作为设备的操作系统,当在设备上电启动后,希望显示开机logo。一般会经历以下几个流程:(1)运行芯片内部引导程序(2)运行引导加载程序(u-boot较为常用)(3)运行linux内核(4)运行用户根文件系统,在这个阶段,就会根据项目......
  • goorm php环境安装go 1.20
    1、下载golang最新版本1.20.5,并安装到/usr/local/go目录wgethttps://golang.google.cn/dl/go1.20.5.linux-amd64.tar.gztarzxfgo1.20.5.linux-amd64.tar.gztar-C/usr/local/-xzvfgo1.20.5.linux-amd64.tar.gz 2、创建GOPATH目录mkdir~/.go 3、设置环境变量G......
  • python开发入门
    python开发很简单,但环境问题很讨厌。需要搞定各种包scikit-learn,tensorflow,pytorch,pandas,numpy如果遇到超时错误,可通过指定镜像站的方式来搞定pip3installtensorflow-ihttps://pypi.tuna.tsinghua.edu.cn/simple还可以用python包管理软件,condapython开发还需......