golang语法学习那些事儿

1、变量声明关键字:var

var a, b int

//带初始值变量声明
var a, b int = 1, 2

//如果带上初始值,变量类型可省略
var c, python, java = 1, true, "no!"

//多个不同类型变量声明,可以用括号括起来
var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<8 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

注:

(1)只有在函数中声明变量才能省略掉var关键词,如使用 k := 5声明一个整形变量,相当于一个语法trick;

(2)变量如果声明时没有显式给出初始值,则会被赋予默认的零值:

数字类型被赋予0, 布尔类型被赋予false,字符串类型被赋予"",interface和引用类型(slice, point er, map, channel, function)被赋予nil;

(3)package级别变量在main函数执行前初始化,而局部变量在函数执行到是才初始化

2、基本的数据类型:

bool, string, int, int8, int32, uint32, float32,float64

3、类型转换:

在不同类型变量直接的赋值需要进行显式类型转换:

var i int = 1
var j float64 = float64(i)

4、类型推断:

当声明一个变量时,如果没有显式指明数据类型,则编译器会从初始化值推断其类型:

func main() {
    v := 2 + 3i
    j := v
    fmt.Println("v is of type %T\n", v)
    fmt.Println("j is of type %T\n", j)
}

5、作用域(scope):

(1)如果一个entity在函数内声明,则该entity的作用域仅为该函数;如果在函数外声明,则对于该package下的所有文件可见;

(2)entity的首字母大小写决定了其对于其他package的可见性,如果首字母大写,则为被导,即其他模块可以导入和引用该entity;

(3)package名称全部为小写字母

场景a:test1.go里面定义了func demo(), 而同一个package下(同一个文件下)的test2.go中想使用demo函数

解决方法:test1.go和test2.go的package要相同,且test1.go中不能包含func main() {},然后编译时要执行go build test2.go test1.go,如果go build指令不带上test1.go(即执行go build test2.go),则会报undefined: demo错误

场景b:在/User/hehe/go/src/mygopl/lissajous/下的main.go中定义了Demo(), 而/User/hehe/go/src/mygopl/server2/下的main.go想使用Demo函数

解决方法:server2/main.go中import "mygopl/lissajous", 同时调用时:lissajous.Demo()

6、函数中短变量声明:

(1)在一个短变量声明中,如果左边包含多个变量,则可以包含已声明过的变量a,此时短变量声明对于a相当于赋值操作,例如:

in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)

(2)一个短变量声明至少包含一个新的未声明的变量,否则编译报错,如:

f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables

f, err = os.Create(outfile) //correct

7、变量的生命周期:

package变量(全局变量?)的生命周期是整个程序的执行时间;而局部变量的生命周期在其不可达(unreachable)时结束

8、如果一个package被导入但是没有被使用,或者一个变量声明了但是没被使用,则编译时会报错:"imported and not used"

9、package初始化:

待补充。

10、字符串

定义:不可变字节序列,用双引号包住

(1)在Go中,raw字符串(等价于python的r''字符串)所有的符号都不会被转义,用`str_conten`表示;注意,raw字符串中的'\r'(return carriage)会被删除,从而使得字符串值在所有平台都一样;raw字符串常用于正则表达式、html模版和json;

(2)当使用range遍历字符串时,Go会隐式对字符串进行UTF-8解码;

(3)当UTF-8解码器遇到意料外的输入字节,会产生一个特殊的代替符'\uFFFD',即

(4)处理字符串常用的4个标准库:bytes, strings, strconv, unicode

 

11、Go源代码总是使用UTF-8编码,Go的字符串也为UTF-8编码

12、Go中双引号和单引号的使用区别:

双引号用于修饰字符串,而单引号用于修饰字符,跟C语言一致

13、Go的rune就是unicode字符;UTF-8更常用作信息交换格式;而程序中使用rune更方便,因为字节大小一致

14、数组:

(1) [3]int和[4]int是不同的数据类型,例如:

q := [3]int{1,2,3}
q = [4]int{1,2,3,4} // compile error: cannot assign [4]int to [3]int

(2)由于数组大小是固定的,因此数组很少作为函数的参数,而用切片(slice)作为函数的参数

15、切片(Slice)

定义:同数据类型的可变长序列,由三部分组成:指针,长度和容量;指针指向通过切片访问到的切片底层数组的第一个元素;长度为切片包含的元素数;容量为切片开始的元素到底层数组的最后一个元素的元素数

生成切片:s[i:j], 0  <= i <= j <= cap(s), 其中i的缺省值为0, j的缺省值为len(s),而s[:]表示引用整个数组s

(1)不同的切片可以共享同一个底层数组

(2)对一个切片summer := s[6:9]进行再切片, 当切片范围上限大于cap(s) = 7,如summer[:20], 会产生运行时错误(panic);但切片范围上限大于len(s) = 3, 则可以扩大切片,参考代码:

package main
  
import "fmt"

func main() {
          months := [...]string{1: "January", 2: "February", 3: "March", 4: "Apr  il", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October  ", 11: "November", 12: "December"} 
          Q2 := months[4:7]
          summer := months[6:9]
          fmt.Println(Q2)
          fmt.Println(summer)
          //fmt.Println(summer[:20]) // panic: out of range
          endlessSummer := summer[:5]
          fmt.Println(endlessSummer)

(3)切片间不可比较,即不能用==来比较两个切片是否包含相同的元素

相关参考:深度解密Go语言之Slice

16、fmt.Printf的用法:

%x用于以16进制打印数组或者slice of bytes的所有元素;%t用于打印boolean;%T用于打印value的类型

17、Go的函数调用使用值传递,也即函数的参数接收到的值是传入值的copy

18、Map

(1)key的类型必须为可比较的,即可以用==进行比较;

(2)创建map:

ages := make(map[string]int) // mapping from strings to ints

// another way to create map
ages := map[string]int{
    "alice": 31,
    "charlie": 34
}

(3)移除map的元素:delete(ages, "alice")

(4)map的元素不是变量,因此不能取其元素地址,因为随着map的增长,可能会对已存在的元素进行rehash,导致原有的元素地址改变:

_ = &ages["bob"] // compile error: cannot take address of map element

(5)判断某个key是否在map中:

if age, ok := ages["bob"]; !ok {/* ... */}

19、结构体(struct)

(1)若两个strct的字段顺序不一样,则为两个不同的struct类型;

(2)若结构体某字段的首字母为大写,则该字段可被导出;反之则不可导出;

(3)一个结构体的字段类型不能为该结构体类型,但可以为该结构体类型变量指针(如链表或者树);

(4)空的结构体不包含任何字段,记为strcut{};

(5)函数传参时应使用结构体指针来提高性能;

(6)当且仅当结构体的所有字段都是可比较的,两个该结构体类型的实例才能用==比较,此时会对其每个字段进行比较;

(7)Go的结构体允许使用匿名字段(即一个字段只有类型没有名字),但类型只能为named type或者指向named type的指针,如:

type Point struct {
    X, Y int 
}
  
type Circle struct {
    Point
    Radius int 
}

type Wheel struct {
    Circle
    Spokes int 
}

var w Wheel

w.X = 8 // equivalent to w.Circle.Point.X = 8

同时,有嵌套结构的struct初始化的时候必须要遵循结构体定义,否则会编译报错:

w = Wheel{8,8,5,20} // compile error: unknown fields
w = Wheel{X: 8, Y: 8, Radius: 5, Spoke: 20} // compile error: unknown fields

并且,由于匿名字段有隐式名字,因此在一个结构体中,不能含有两个同类型的匿名字段;而由于匿名字段的名字由其类型隐式决定,因此该匿名字段的可见性也由其类型首字母大小写决定

20、JSON

(1)Go将数据转换为json:使用encoding/json下的json.Marshal(pending_data);Marshal函数使用Go的struct字段作为JSON对象的字段,只有可导出的字段才会被marshaled;

(2)json反序列化为Go数据结构:json.Unmarshal

21、函数

基本定义:

func name(parameter-list) (result-list) {
    body
}

注意:参数和返回结果列表均可为空;如果函数体为空,则该函数使用Go之外的其他语言实现

(1)调用多值返回函数的结果是一个元组,调用方必须将该函数每个返回结果显式赋予变量,如果想忽略某个返回结果,则需将其赋予给Go的blank identifier(空标志符) --- '_', 即单下划线

(2)bare return: 当一个函数的返回结果均有名字时,return后面可以不带任何东西,如

func CountWordsAndImages(url string) (words, images int, err error) {
    resp, err := http.Get(url)
    if err != nil {
        // this is so called bare return.
        return
    }
}

注: 由于该特性会降低代码可读性,实践中尽量避免使用

(2)错误处理:编写Go代码,习惯先处理错误情况,再到正常处理流程;

(3)函数是Go的第一类值(first-class values),函数名可以赋值给变量;函数类型的零值(zero value)是nil,调用此函数会引起panic:

var f func(int) int
f(3) //panic: call of nil function

(4)匿名函数

strings.Map(func(r rune) rune {return r + 1}, "hehe")

注意:当在for循环中使用匿名函数时,需要注意捕捉迭代变量

// 以下为错误demo
var rmdirs []func()
for _, dir := range tempDirs() {
    os.MkdirAll(dir, 0755)
    rmdirs = append(rmdirs, func() {
        os.RemoveAll(dir) // NOTE: incorrect!!!
    }
}

//以下为正确demo
var rmdirs []func()
for _, d := range tempDirs() {
    dir := d // NOTE: necessary!!!
    os.MkdirAll(dir, 0755)
    rmdirs = append(rmdirs, func() {
        os.RemoveAll(dir) 
    }
}

(5)可变参数函数:

定义:最后一个参数类型前面加上"..."

func sum(vals ...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

调用方式:

values := []int{1,2,3,4}
fmt.Println(sum(values...))

(6)defer语句

定义: defer + 函数/method调用;当程序执行到defer语句时,其后的函数/method完成传参过程,但是该函数/method则是在defer语句所在的函数块执行完后才被调用(可以类比python的with语句相关的__exit__方法);如果一个函数块中有多个defer语句,则最后的defer语句最先执行(跟栈一样,先进后出)

应用场景:go使用Defer的几个场景

(7)panic(有点类似于python的exception,但panic后程序最终会变为为crash态,即退出,而不是像python那样捕捉异常后还可以继续执行后续其他处理逻辑)

定义:Go程序在运行时发生错误,或者程序中直接调用内置panic函数时的状态;当处于panic时,正常的执行被终止,该goroutine里面的defer函数调用被执行,然后程序到达crash态,并给出相应的log消息

(8)recover

定义:有时我们不希望因为无法处理错误panic而导致整个进程挂掉,因此需要像python一样能够handle panic(异常处理机制);

golang在这种情况下可以在panic的当前goroutine的defer中使用recover来捕捉panic;注意recover只在defer的函数中有效,如果不是在refer上下文中调用,recover会直接返回nil

相关参考:Golang: 深入理解panic and recover

22、method

定义:类似于函数声明,只是在func关键字和函数名直接多了一个该method的receiver,例如:

type Point struct{X, Y float64}

// define a method of the Point type.
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

p := Point{1, 2}
q := Point{4, 6}
fmt.Println(p.Distance(q)) // method call

注:其中p叫做method的receiver;表达式p.Distance叫做selector

(1)由于方法和字段在同一个命名空间,因此如果对于Point类型再定义一个名称为X的method,则编译时会报错,因为Point已经有了一个字段叫X;

(2)只要底层类型不是指针或者接口类型,就可以在同一个package里对任意命名类型声明method;

(3)对于同一个类型,其所有的method必须使用不同的名字;

(4)在一个receiver声明中,类型要么为命名类型(Point)或者指向其的指针(*Point):

func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
} 

23、Interface

定义:

24、Interface value:

定义:由concrete type(具体类型)和该类的值组成,它们又被称为动态类型和动态值

注:由于Go为静态类型语言,类型是一个编译时概念,因此一种类型不是一个值(a type is not a value),

25、Go内建类型error为接口类型

26、Reflection

请参考:Golang的反射reflect深入理解和示例

27、类型转换

请参考:GO语言总结(5)——类型转换和类型断言

28、三个'.'号(即'...')的使用场景:

请参考:3 dots in 4 places

29.指针

相关参考:Golang研学:在用好Golang指针类型

补充

golang找到的一些参考资料:

Go语法指南,你遇到过哪些高质量的 Go 语言面试题?, golang 面试题(从基础到高级),Golang 面试题搜集

Golang 新手可能会踩的 50 个坑,

n、go run go build go install:

go run:go run 编译并直接运行程序,它会产生一个临时文件(但不会生成 .exe 文件),直接在命令行输出程序执行结果,方便用户调试。

go build:go build 用于测试编译包,主要检查是否会有编译错误,如果是一个可执行文件的源码(即是 main 包),就会直接生成一个可执行文件。

go install:go install 的作用有两步:第一步是编译导入的包文件,所有导入的包文件编译完才会编译主程序;第二步是将编译后生成的可执行文件放到 bin 目录下($GOPATH/bin),编译后的包文件放到 pkg 目录下($GOPATH/pkg)
相关参考:go run go build go install 命令解释

你可能感兴趣的:(学习笔记,个人总结)