一、 go基础
1、Go 语言最主要的特性
- 自动垃圾回收
- 更丰富的内置类型
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性
2、$GOPATH目录约定有三个子目录
- src存放源代码(比如:.go .c .h .s等) 按照golang默认约定,go run,go install等命令的当前工作路径(即在此路径下执行上述命令)。
- pkg编译时生成的中间文件(比如:.a) golang编译包时
- bin编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,如果有多个gopath,那么使用${GOPATH//😕/bin:}/bin添加所有的bin目录)
3、$GOROOT GO安装目录,为了直接使用go命令,设置环境变量 $GOROOT/bin
4、程序入口
- main 标识符是随处可见的,每一个 Go 程序都是从一个叫 main 的包中的 main 函数开始的,当 main 函数返回时,程序执行结束。
fun main() {
//执行语句
}
⚠️ init 函数
- 1、init函数用于包的初始化,如初始化包中的变量,这个初始化在package xxx的时候完成,也就是在main之前完成,如果只是调用 init() 函数逻辑,通过 import _ "model";
- 2、每个包可以拥有多个init函数, 每个包的源文件也可以拥有多个init函数;
- 3、同一个包中多个init函数的执行顺序是没有明确定义的,但是不同包的init函数是根据包导入的依赖关系决定的。
- 4、init函数不能被其他函数调用,其实在main函数之前自动执行的。
main() init(),都没有参数和返回值
5、import
- 相对路径
import "./model"
- 绝对路径
impoert "a/b/model"
- 点操作,这个点操作的作用就是这个包导入后,当你想要调用这个包的函数的时候,你就能够省略它的包名了,即fmt.Println()可以省略成Println()
import (
. "fmt"
)
- __操作,其作用就是引入该包,而不直接使用包里面的函数或变量,会先调用包中的init函数,这种使用方式仅让导入的包做初始化,而不使用包中其他功能
import(
_ "github.com/ziutek/mymysal/godrv"
)
6、在go中++和--等操作符只会用于语句而非表达式中,可能只可以做成后缀操作符而非前缀操作符。即go中不会出现 f(i++) 或 A[i]=b[++i] 这样的表达式
7、在go语言中只讲类型和值,而非类、对象或实例
- 相同结构的自定义类型等价,可以相互替换,但是不能有任何方法,
- 任何命名的自定义类型都可以有方法,并且和这些方法一起构成该类型的接口。命名的自定义类型即时结构完全相同,也不能相互替换
接口也是一种类型,可以通过指定一组方法的方式定义。接口是抽象的,因此不可实例化。如果某个具体类型实现了某个接口所有的方法,那么这个类型就被认为实现了该接口。一个类型可以实现多个接口。 - 空接口(即没有定义方法的接口),用interface{}来表示。由于空接口没有做任何要求(因为它不需要任何方法),它可以用来表示任意值(效果相当于一个指向任意类型值的指针),无论这个值是一个内置类型的值还是一个自定义类型的值
8、变量声明
- 全局申明
varname type =
var varname type =
var (
varname type
varname type
)
-函数体内部局部声明:varname :=
func m() {
a = "O" //全局修改变量的值
a := "p" //函数体内部的简短声明,为局部变量
print(a)
}
9、数据类型
1)整型
- 按长度:int8 int32 ....
- 无符号: uint8
2) float
3) bool
4)字符串 string "双引号书写",“ ` 反引号支持多行”
- 底层字符串是字节的定长数组。
- len() 来获取字符串所占的字节长度,例如:len(str),一个汉字是三个字节(三个字符)。
- 字符串、数组、切片底层原始数据有相同的内存结构,但是切片是指针引用类型
- 字符串无法转整型,但是可以转数组[ ]byte,[ ]rune(中文处理转 [ ]rune)
- 字符串处理函数,strings包
Contains(s,substr string) bool
5)字符 单引号
- byte uint8别名,代表ASCII码的一个字符 var a byte = 'a'
- rune int32, 代表utf8的一个字符,一般涉及中文,var b rune = '二'
10、常量
const name type = ' s'
隐式声明 const name = "adAD"
存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数complex)和字符串型。
Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,
var a int
var b int32
a = 15
b = a + a // 编译错误
b = b + 5 // 因为 5 是常量,所以可以通过编译
声明多个常量,未赋值则从上面获取,iota ,常量计数器,从0开始,可用作枚举
const (
a = 10
b
c
)
11、条件控制
if num%2 == 0 {
} else if {
}
//特殊写法
if varname,ok := mapdata[key]; ok {
}
switch var1{
case val1:
statement
case val2:
statement
default:
statement
}
//select 类似switch,随机执行一个分支
select {
case <- c:
fmt.Printf("Unblocked %v later.\n", time.Since(start))
case <- ch1:
fmt.Printf("ch1 case...")
case <- ch2:
fmt.Printf("ch1 case...")
default:
fmt.Printf("default go...")
}
12、循环
for 循环:
for 初始语句int;条件语句;post语句变量增值变动 {
statement
}
//post语句是每次循环的最后执行;
for i:= 0;i<=10;i++{
}
形式一、这三条语句可以省略,但是分号;不可省略
形式二、只有条件语句
var i int
for i <= 10 {
}
形式三、相当于python中的while True:
for {
}
形式四:for key,value := range oldmap{
statement
}
break continue goto 可以控制循环
labelname: statement
goto labelname
13、函数
func funcname(paramname type1,paramname type2) (output1 type1, ...) {
statement
return output1,... //可返回多个值
}
不定数量的参数(可变长参数)
func funcname( arg ...int) (output1 type1,) {
} //arg 本质是 int型的slice,相当于 [ ]int,若传入slice时,需展开([ ]int...)
变量声明的地方:
- 函数内的局部变量
- 函数外的全局变量
- 函数中的形式参数
14、指针(一个变量的内存地址)
var name *int //指针声明
var a int = 100
name = &a //获取地址值
*name //获取指针指向的值
- 值传递:完全复制一份,开辟新的内存 (包含简单数据类型,数组,struct) , new()方法创建,该方法也用于创建自定义类型( 如:type newtype int ),返回指针
- 引用传递: 引用原来的内存,若操作修改,将修改原来的值,有利于性能的提高 make()创建,返回指针
15、GO语言内置容器
1)数组
var varname [ 数组长度 数据类型
var nums = [5]int{1,2,3,5,6}
var nums = [...]int{1,2,3,5,6}
//只能放统一类型的数据,数组一旦定义后,大小不能改变
2)切片
- 本质是数组,展示一部分,但是切片大小可变,若容量不够时,可再申请空间
var varname []type
var slice []type = make([]type,len,capacity)
//也可以截取数组来初始化切片
arr := [5]int{1,2,35,5,6}
slice := arr[start:end]
- len() 返回切片长度
- cap() 返回切片起始位置到数组最后位置的长度
- 若是截取切片,形成的切片,对外是截取的数据,其本质是从截取的开始位置,到数组的最后位置
append(slice,addnumber)函数,注意:
- 可以追加一个或多个元素,或一个切片
- append 会改变slice所引用的数组内容,对于其他引用该数组的切片(类似于python的浅拷贝),也会受到影响
- 当容量不够时,GO 会创建一个新的内存地址来存储元素,摈弃原来的地址
copy(var1,var2)
将var2 的内容拷贝到 var1, 完全复制过去,操作时互不影响,
通过切片、append 实现slice删除元素
3)map
- map 是一种无序键值对的集合,map通过key来快速检索数据,类似于索引;
- 长度不定,可以像slice一样扩展,可用len(), 不可用cap();
切片,函数等引用类型数据不能做键值
声明
var mapname map[string]string //该初始是一个 nil 空指针,无法存储值,除非声明的同时初始化数据
var country = map[string]string{
"china":"beijing",
}
name := make(map[key类型]value类型) //提前申请了空间
//遍历
for k,v := range map{
}
value,ok := map[key] //获取value, 如果 ok is true,则存在,否则返回空字符串“”;
delete(map,key) //删除一个键值对
//清空 map 可以创建一个空的map,将原值覆盖;
4)list
- list是一种非连续链式存储的容器,双向链表;最大特点:可以存储任意类型的数据
- list 包下主要两种类型,Eelement\List ,以下核心方法
//声明
name := list.New() 建议使用; 引用类型
var name list.List 值类型
//Element
func ( e *Element) Next() *Element
func ( e *Element) Prev() *Element
//List
func New() *List //List包创建list
func ( l *List) Init() *List
func ( l *List) Len() *List
func ( l *List) Front() *Element //获取首个元素
func ( l *List) Back() *Element //获取最后一个元素
func ( l *List) PushFront( v interface{}) *Element //头部添加一个元素
func ( l *List) PushFrontList( v *LIst) *List
func ( l *List) InsertBefore( v insterface{},mark *Element) * Element
func ( l *List) MoveToFront(mark *Element) //将一个元素移到头部
func ( l *List) MoveBefore(v isnterface{}, mark *Element).
func ( l *List) Remove(e *Element) interface{}
16、面向对象
- 通过结构体、接口实现,结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
type
- type 用于给类型起别名 type newname_type = oldtype
- type 用于类型定义 type newname_type oldtype
定义一个结构体
type Teaher struct {
name string
age int8
sex byte
}
5种声明方式
1. var 声明
var p Teacher
p.name = ''david"
p.age = 30
p.sex = 1
2. 简短声明
p := Teacher{}
p.name = ''david"
p.age = 30
p.sex = 1
3. 直接声明并赋值
p = Teacher{ name:‘’sdff“,age: 25,sex:1}
4.不写属性名,但是需按照顺序
p = Teacher{ "sdhn",30,0}
5.创建指针类型结构体 new( )
p := new( Teacher)
*p.name = "fjgns"
*p.age = 31
p.sex = 0 语法糖简写
匿名结构体和匿名字段
//没有名字的结构体:
a := struct {字段的定义}{赋值}
//匿名字段,可以理解为字段名和字段类型一致
type A struct{
string
int
}
b := A{"字符串", 20}
b.string, b.int
type A struct{
}
//匿名字段可以模拟继承关系
type B struct{
A //匿名字段的继承关系,一个类作为另一个类的子类,定义一个匿名字段struct.file, 继承属性和方法
}
// 聚合关系,一个类作为另一个类的属性,定义一个字段其类型是一个结构体,访问相关属性,struct.filestruct.file
type B struct{
filedname A
}
方法(包含接收者的函数)
方法可以同名,只要接受者不同就可以
func (接受者变量 接受者类型) 方法名( 参数列表) (返回值列表){
//方法体
}
接口,无法直接实例化
- 面向对象中,接口用于定义对象的行为,接口只指定对象应该做什么,实现这种行为的方法是由对象来决定
- 接口定义一组方法,如果某个对象实现了该接口的所有方法,则此对象就是实现了该接口;
- 接口刚好体现了多态,该接口可以被实现这个接口的任意对象赋值,同一接口,不同对象,同一方法的不同表现形态
1.定义接口
type 接口名字 interface {
方法1(参数列表)返回值列表
方法2(参数列表)返回值列表
}
2.定义结构体
type 结构体名 struct {
//属性
}
3.实现接口的方法
func (变量名 结构体类型) 方法1(参数列表) 返回值
func main( ) {
var port 定义的接口类型
port = new( 定义的实现该接口的结构体)
}
4、接口断言
nstance,ok := 接口对象 . (实际类型) //一般配合switch\case 使用
注意:接口类型对象,不能访问其实现类中的属性字段
定义方法时,修改属性字段,由于struct传参时,是值类型,所以需要传入指针。
定义一个接口变量,那么实际上可以赋值实现这个接口的任意对象(一般利用一个空接口)
如定义一个接口类型容器(数组或切片),实际上该容器中可以存储实现这个接口的任意对象
//要改变对象的值必须用指针
func (b *User) SetName(name string) {
b.Name = name
}
17、错误处理
- error 本质是一个接口类型,其中只含有一个Error() 方法
创建error对象的几种方式
1) errors包下的New()函数返回error对象
errors.New() //创建一个新的错误
package errors
//返回一个新定义的错误结构体
func New( text string) error {
return &errorString{ text}
}
2) fmt包下的Errorf()函数返回error对象,其本质还是调用 errors.New()
func Errorf (formmat string,a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
3)自定义错误
定义一个结构体,表示自定义的错误
type errorString struct {
s string
}
func ( e *errorString) Error() string {
return e.s
}
18.defer 延迟
- defer 语句在该函数内最后执行,即使报错语句也会执行,当有多个defer语句时,采用先进后出模式
- 注意:延迟参数,延迟函数的参数在执行延迟语句时被执行,相当于参数已传入,但是函数内的逻辑被延后执行
defer 应用
- panic和recover机制,以此来实现极特殊的异常处理
- panic()让程序进入恐慌,中断程序的执行,但是延迟函数会正常执行
- recover()让程序恢复,必须在defer函数中执行,即仅在延.迟函数中有效。 正常的程序执行过程中,调用recover会返回 nil ,并且没有其他任何效果。如果当前程序进入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行
下面这个函数检查作为其参数的函数执行时是否会 panic
func throwsPanic( f func() ) b bool {
defer func( ) {
if x: = recover( );x != nil {
b = true
}() //声明一个匿名函数并调用
f( ) // 执行函数f,如果出现 panic ,程序中止,执行延迟语句,恢复回来
return
}
19、闭包 环境变量 + 函数,具有数据的行为,返回函数
func getSequence() func( ) int {
i := 0
return func() int {
i += 1
return i
}
}
func main( ) {
nextNumber := getSequence( )
// 调用nextNumber 函数, i 变量自增 1 并返回
fmt.Println( nextNumber( ) )
fmt.Println( nextNumber( ) )
nextNumber1 := getSequence( )
// 新创建nextNumber1函数, i 变量从0开始,自增 1 并返回
fmt.Println( nextNumber1( ) )
fmt.Println( nextNumber1( ) )
输出结果
>>> 1
>>> 2
>>> 1
>>> 2
20、go的并发
- 通过go关键字开启goroutine,goroutine是轻量级的线程
- go 函数名( 参数列表)
21、通道channel
- channel 是用来传递数据的一个数据结构,channel可以用于两个goroutine之间 传递一个指定类型的值来同步运行和通讯。
操作符 <- 用于指定通道方向,表示发送或接收,若未指定方向,则为双向通道
ch <- v //把 v 发送到通道ch
v := <- ch // 从ch 接收数据 并赋值给v
ch : = make( chan int ) 声明一个带缓存通道,默认通道是不带缓冲区的,
- 带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
- 不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
- 如果缓冲区已满,发送方阻塞;缓冲区无数据,接收方阻塞
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭close(),那么 range 函数就不
// 会结束,将一直处于阻塞状态。
for i := range c {
fmt.Println(i)
}
}