loading...
Go学习初探
Published in:2024-03-11 | category: Go

0x00前言

近期也是对区块链愈发关注,从金融方面开始过渡到了技术方面,随着发现的深入,了解到了区块链比较主流的开发语言是Go语言(以太坊网络就是基于Go语言编写),本身也对Go语言有点兴趣,所以借此文章记录下学习Go的过程中的知识点。

0x01简介

Go语言全称是Golang,是由谷歌开发的,刚好用PHP做完一个项目,借此机会对比下。两者同样是可以做Web等后台应用

GO VS PHP

PHP是一种动态类型语言,也就是在声明变量的时候无需带上属性,如$data,就是直接定义了一个data变量,在赋值的时候就识别变量类型

Go是一种静态类型语言,与JAVA相似,声明变量的时候需要带上变量属性,也就是如var data string

Go的并发性能很好,强于PHP几倍,常用于构建微服务、网络IO密集型服务,能同时处理多任务,且有丰富的库

PHP还是主要用于Web服务,性能较差,扩展性相对较弱

Go以安全为著称,其包括内存安全、类型安全、并发安全、错误处理等等

PHP存在许多安全问题,比如内存占用,错误不明显等

Go可以编译运行成二进制可执行文件,且Go程序生成的二进制可执行文件常常拥有以下优点:

  • 内存消耗少
  • 执行速度快
  • 启动快
  • 跨平台,一次编译多平台使用

PHP不能生成二进制文件,但是可以热重载,也就是修改代码后刷新页面即可,不用重新编译运行

总结

Go具有以下特点:

  • 内置并发编程支持:
    • 使用协程(goroutine)做为基本的计算单元。轻松地创建协程
    • 使用通道(channel)来实现协程间的同步和通信
  • 内置了映射(map)和切片(slice)类型
  • 支持多态(polymorphism)
  • 使用接口(interface)来实现裝盒(value boxing)和反射(reflection)
  • 支持指针
  • 支持函数闭包(closure)
  • 支持方法
  • 支持延迟函数调用(defer)
  • 支持类型内嵌(type embedding)
  • 支持类型推断(type deduction or type inference)
  • 内存安全
  • 自动垃圾回收
  • 良好的代码跨平台性
  • 自定义泛型(从Go 1.18开始)

尽管Go语言有很多特点优势,但缺点也有:

  • Go语言的历史较为短暂
  • 1.18版本之前不支持泛型
  • 错误处理基于返回值,需在所要调试的函数中加入显式返回,即err变量

0x02正文

开始学习ing…

一、组成结构

一个Go基本文件由6个内容组成,包声明、引入包、注释、函数、变量、表达式/语句

1
2
3
4
5
6
7
8
9
10
package main			//包声明
import { //引入包
"fmt"
}
/*注释*/ //注释 多行注释/**/,单行注释//
func main(){ //函数
var a int //变量
fmt.Println("hello") //表达式/语句
}

1.每个Go程序都有一个声明main包的页面

2.一个页面中有init()和main(),会优先运行init(),每个包可含有多个init(),init方法不能被其他函数调用,而是在main()之前就生效

3.函数、方法、循环、条件语句等的大括号必须与相应的声明或语句放在同一行,不能单独一行

二、数据类型

1.布尔类型

bool

2.数字类型

uint8/int8

uint16/int16

uint32/int32

uint64/int64

byte 类似于uint8

rune 类似于uint32

uint 32或64

int 与uint一样大小

uintptr 用于存放指针

3.字符串类型

float32

float64

complex64 复数类型

complex128

4.派生类型

指针类型 *a int

数组类型 []a int

结构化类型 type a struct{}

Channel类型 chan string

函数类型 func a() int {}

切片类型 []int{}

接口类型 type a interface{}

Map类型 map[string] int{}

三、变量

一般声明变量用var

如var data string = “test”

当用var声明变量未赋值时,默认为0值

如var data string,字符串的0值为空,数值类型0值为0(其中浮点数类型的0值为0.0),布尔类型为false

以下变量类型0值为nil

*int(指针类型)、[]int(数组类型)、map[string] int(键为字符串,值为整形)、chan int(通道类型)、func(string) int(函数类型)、error(接口类型)

其中在声明变量中也可以用 := 表达,即data := “test”

那var和 := 有什么不同,var初始化可以没有赋值,而 := 没有赋值就会报错;并且如果用var声明过变量,再用 := 也会编译报错,总之,var和 := 不能同时存在,:= 是简短声明

即 var data string = “test” => data := “test”

可以使用&取变量的地址,

声明多变量

var data1 , data2 string = “test”,”abc”,声明变量的类型必须统一,如果不同需要换行单独一行声明变量

此外声明变量赋值还不够,必须在程序中使用到变量,否则也会报错,除非是全局变量

tips:Go语言有一个空白标识符,使用它可以避免创建无效的变量,即:

_, err := “1”,”2” 只会返回err变量值为2,1默认被丢弃

四、常量

const关键字

const data string =”test”,多个常量声明可参考上面的变量声明法

特殊常量

iota是一个特殊常量

iota在const每声明一次变量都会计数一次,类似给const的索引值,每当有一个新的const声明时,iota会置为0,一般可以用作枚举值,当const的值被赋值后,在下一个const被赋值之前,变量的值跟随首个变量值,iota作为独立值仍继续计数

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
// 0 1 2 ha ha 100 100 7 8
}

五、运算符

基本运算符跟其他语言相似

运算符优先级

5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

六、条件语句

Go没有三目运算符,不支持 ? : 判断

Go的if 条件中不用加括号

select语句(并发编程)

除了有if else switch语句,还多了个select语句

select 是 Go 中的一个控制结构,类似于 switch 语句

select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收

select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块

1
2
3
4
5
6
7
8
9
10
select {
case <-ch1:
// 从 ch1 接收数据
case data := <-ch2:
// 从 ch2 接收数据,并将数据赋值给 data
case ch3 <- data:
// 将数据发送到 ch3
default:
// 如果没有任何通道操作可用,则执行 default 分支
}

如果没有default分支,select会进入阻塞,直到至少有一个通道操作可用

  1. 处理多个通道的并发操作,可以避免因某个通道阻塞而导致整个程序的阻塞。
  2. 实现超时机制,可以通过selecttime.After结合使用,在一定时间内等待某个操作的完成,超时后执行其他操作。
  3. 实现非阻塞的通道操作,可以通过select在通道操作之前加上default分支,如果通道操作不能立即进行,就可以执行default分支中的逻辑。

七、循环语句

goto、continue、break,同其他语言

八、函数

常用格式

1
2
3
func function_name( [parameter list] ) [return_types] {
函数体
}

1
2
3
func test (num1 , num2 int) int{
}
// 两个int参数,返回int类型

或者

1
2
3
4
5
6
func test() (int, int) {
a := 1
b := 2
return a, b
}
//返回多少个参数,返回的类型也多少个,一一对应

常用内置函数

append 用于追加数组、slice中,返回的修改数组、slice

close 主要关闭channel

delete 从map中删除key对应的value

panic 停止常规goroutine

revocer 允许程序定义goroutine的panic操作

imag 返回complex实部

real 返回complex虚部

make 用来分配内存,返回Type本身(用于slice、map、channe)

new 同样分配内存,返回指向Type的指针,主要是分配值类型用于(int、struct)

cap 返回某个类型最大容量(用于slice、map)

copy 用于复制和连接slice,返回复制的数目

len 求长度(slice、map、array、string、channel)

printprintln 打印

九、变量作用域

同名的全局变量和在函数中的局部变量,当调用该函数的时候,优先考虑的是局部变量

十、数组

声明数组

1
var array [size] type => var test [10]int => test := [10]int

可以表示为var test = [5] int{1,2,3,4,5} => test := [5]int{1,2,3,4,5}

声明数组未赋值,则自动初始化,默认所有值为0

长度不确定时可以用…代替

var test = […] int{1,2,3}

二维数组

1
2
var test [3][3] int => test := [3][3]int{{1,2,3},{1,2,3},{1,2,3}}
//定义了一个三行三列的数组

十一、指针

1
2
var a int = 20
var b *int

&取地址符,*取指针地址所存的的值

空指针

nil

十二、结构体

学过C语言都了解结构体,其使用方法也差不多类似

1
2
3
4
5
6
7
type test struct{

attr1 string

attr2 int

}

可以使用key:value形式创建,也可以常规直接添加数据创建新的数据类型

test{“aaa”, 1} => test{attr1:”aaa”, attr2:123},没有填写的字段默认为空或者0值,取决于数据类型

还可以单独赋值,并且读取结构体对象属性也是对象+ . 比如

test.attr1 = “aaa”

test.attr2 = 123

结构体能代入函数作为参数,在传到函数中如果不是指针对象,则为值传递,无法改变原有结构体的属性,除非是以指针对象的形式作为参数也就是*object,则为引用传递,直接修改原有地址的值

结构体指针

1
2
var testptr *test
testptr = &test1

用结构体指针访问变量也是用”.”

结构体属性大小写问题

  • 首字母大写相当于 public。
  • 首字母小写相当于 private。

实际上就是在本包内可用,对外private不可用

当要将结构体对象转换为 JSON 时,对象中的属性首字母必须是大写,才能正常转换为 JSON。

十三、切片

切片与之前的数组有点相似,但又不完全相似

数组长度是不可改变的,而切片属于动态数组,可以动态改变长度,切片无需声明长度,用make则需要

1
2
3
4
5
var slice1 []type
// 使用make创建切片
var slice1 []type = make([]type, len) => slice := make([]type, len, capacity)//len是长度,capacity是容量

var slice1 []int = make([]int,3) => slice1 := make([]int,3)

容量和长度的概念

容量是这个切片最大能存放多少个元素,长度是当前切片存放元素的个数,初始化时长度不能大于容量,否则编译报错。

如果长度大于容量,则会扩展,Go会创建更大的底层数组

切片初始化

slice1 := [] int{1,2,3}

未初始化的切片默认为nil,长度为0

假设有个arr数组

slice1 := arr[:],也就是把数组所有值初始化到切片slice1上

slice1 := arr[startindex:],把arr数组从开头到结束索引值-1的元素创建为切片

slice1 := arr[:endindex],一直到最后一个元素创建切片

**len()**方法可以提供给切片索引

**cap()**方法可以检测切片最大容量

若动态追加切片容量其实也就是要创建一个更大的切片,把原有元素copy过来

slice = append(slice,1,2,3) 向slice追加1,2,3,三个元素

也可以是用**copy()**方法

copy(新切片,原有切片)

十四、范围

**range()**,与python比较类似

也是通过迭代遍历元素和对象,Go是返回迭代数组(array)、切片(slice)、通道(channel)或集合(map)

以key:value形式

1
2
3
for key, value := range oldMap {
newMap[key] = value
}

用空白标识符代替key或value可以不返回特定变量

十五、Map集合

Map是无序的键值对集合

返回时顺序是随机的,若不存在值则返回零值。可以通过key:value形式索引元素

1
maptest := make(map[string]int, initialCapacity)	// initialCapacity用于指定容量

跟切片一样,Map集合可以动态扩容

删除键值对用**delete()**方法,修改元素可以maptest[‘name’] = “hahaha”

十六、接口

1
type test interface{}

隐式实现,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。

通俗易懂的例子

先用PHP举例

PHP有一个类是Class A{},在里面定义了一个方法叫test(),如果在本文件中使用的话就是$this->test(),在其他模块引用的时候一般是$a = new A(),引用方法则是$a->test()

而在Go中则是定义结构体类比成一个类,创建一个带有结构体类型变量的方法,通过接口引用,可以实现类似PHP引用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Dog struct{}				// 类比成PHP的 Class object(){}
type Test interface{ // 抽象到面向对象的桥梁
move()
}
func (dog Dog) move() { // object.function()
fmt.Println("move dog")
}

func main(){
var test Test
var dog Dog = Dog{}
test = dog
test.move()
}

十七、错误处理

**error()**是一个接口类型

1
2
3
type error interface {
Error() string
}

其次还有panic()revocer()

panic用于主动抛出错误,revocer用于捕获panic抛出的错误

类似tyr catch

1、如果没有revocer,panic在系统遇到错误会中止执行,只有recover捕捉到了才会正常

2、panic在发生错误后不会往后执行,会逆序执行defer,就类似于finally

3、利用revocer捕捉的panic时,defer需要在panic之前声明

十八、并发

进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位

线程是进程的一个执行实体,是CPU调度和分配的基本单位,它是比进程更小的能独立运行的基本单位

一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行

并发与并行

多线程程序在同一核CPU上运行,就是并发,并发主要由切换时间片实现同时运行

多线程程序在多个核CPU上运行,就是并行,并行主要利用多核实现多线程运行

协程和线程

协程:独立的栈空间,共享堆空间,调度由用户控制,本质上有点类似用户级线程

线程:一个线程上可以跑多个协程,协程是轻量级线程

goroutine

1
go f(x, y, z) //go 函数名( 参数列表 )

允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

goroutine期望是通过通信来共享内存,而不是共享内存来通信

通道(channel)

ch <- v 把v发送到ch通道

v := <-ch 从ch通道接收数据

通道声明

1
ch := make(chan int)
Prev:
solidity入门教程
Next:
股权架构设计
catalog
catalog