首页 go
文章
取消

go

一. 基本规则约束

1.1 语言类型

编译型语言,直接编译成二进制可执行文件

1.2 变量类型

弱类型, 不需要像java那样子声明强类型

1.3 语句结尾分隔符

go不需要使用分号;进行声明语句结束作为结尾,go会自动将特定符号后的换行符号替换成分号。

特定符号包括:

  1. 标识符
  2. 字符串
  3. 数据型:整数,浮点数,虚数
  4. 关键字:break, continue, fallthrough, return
  5. 运算符: ++、–
  6. 分隔符: 各种右括号 )、]、}

表达式,语句, 函数, 方法

表达式:值、变量和操作符的组合。

语句:一段可执行代码,通常有赋值语句,if语句,for循环语句,return语句,import语句等

函数:在包中封装的一段代码,go中函数是一类公民,可以作为参数传递

方法:与结构体关联的函数

二. 语法结构

2.1 语法顺序

1
2
3
4
5
6
7
8
9
package 包名
import "包名"

var 变量名
const 常量名
type 类型定义
func 方法名(参数列表) {
  
}

2.2 变量声明

语法 var 变量名 类型 = 表达式

类型表达式可以省略其中一个,省略类型会根据初始值进行推断,省略初始表达式会根据类型进行零值初始化机制初始化。

2.2.1 零值机制

  1. 数据类型初始化为对应零值,布尔为false
  2. 字符类型为空字符串
  3. 接口或引用类型(slice,指针,map,chan和函数)对应为nil
  4. 数组和结构体,对应为每个字段对应类型的零值。

实际使用实例

符号名称作用
s := “”短变量声明一个或多个变量,只能在函数内部使用。
var s String显式声明变量显式声明变量类型,隐式初始化值。
var s = “”隐式声明变量隐式声明变量,并赋予初始化值,用得少。
var s String = “”显式声明显式声明,类型冗余。

实际应用中,一般使用前两种进行声明变量, 当变量类型很重要的话可以显式声明。

1
2
3
4
5
6
7
8
i, j = j, i //交换i j值


in, err := os.Open(infile)
// 左边的变量不一定都是刚刚声明的,像这个err后面就是赋值行为了.编译通过
out, err := os.Create(outfile)//一个赋值,一个初始化,编译通过
// 但是简短语句中一定要有一个新变量赋值,不能两个都是赋值行为,不然编译会失败
in, err := os.Create(outfile)//两个都是赋值,编译失败

2.3 自增减语句

规则一:++和–只能放在变量名后面,不能放在前面。

表达式合法性
–i非法
i–合法

规则二: ++和– 是语句,而非表达式,所以不能放在 等号 后面;

表达式合法性
j=i++非法
i++
j=i
合法

2.4 for循环语句

1
2
for initialization; condition; post {
}

initialization

可选项,必须是一个简单语句, 只在开始的时候执行一次

condition

可选项,是一个布尔表达式,在每次循环前执行

post

可选项,一个语句,在循环体结束后执行,然后再对condition求值

2.4.1 foreach go版本 range语法

通过range语法,可以遍历一个数据区间上的值,range每次会产生一对值,数据的索引和索引对应的元素值。

1
2
3
for key,value :=range os.Arg[1:] {

} 

2.5 空标识符

例如上面的range语法中,会产生一对key,value的变量,这个时候如果代码中不需要处理key,但是range又给了你key,如果是在java中就可以使用一个局部不使用的变量,但是go中不允许存在定义了但是又不使用的变量,这在编译器会检查报错。于是go提供了空标识符_, 表示丢弃不需要的标识符。

2.6 if 语句

1
2
3
if init; condition {
	//TODO..
}

和for循环语句一样,条件表达式中不需要括号,但是结构主题需要{}。

2.7 map操作

map规则,key可以是任意类型,

初始化map

1
make(map[string]int) // key是string, value是int

存入key,value

1
2
3
4
counts[input.Text()]++
//等价于
line := input.Text()
counts[line] = counts[line] + 1

2.7 方法与变量可见性控制

可见性由名字定义的位置和名字首字母的大小写共同决定

  1. 函数内部定义的变量,无论大小写都只能在函数内部可见。
  2. 函数外部定义的变量,当前包所有文件可见。
  3. 函数或变量首字母大写,包外文件也可见

2.8 命名

一个名字必须以字母或下划线开始,后面跟字母,数字,下划线。

规范:不能与关键字,内建常量和内建类型相同。

约定:

  1. 长度没限制,但是尽量短。只有作用域大生命周期长的要求用长名。例如局部变量直接用i,长变量用loopIndex。
  2. 优先使用驼峰命名而不是下划线分隔。
  3. 非关键字的内建名称可以重新定义,相当于覆盖内置语义,但是建议避免过度重定义导致语义混乱。

内建关键字(不可重新定义)

1
2
3
4
5
break     case    chan    const   continue
default   defer   else    for     fallthrough
func      go      goto    if      interface
import    map     package range   return
select    struct  switch  type    var

内建常量(可重新定义)

false true iota nil

内建函数(可重新定义)

append copy close cap complex delete image len make new panic real recover

内建类型(可重新定义)

int int8 int16 int32 int64

uint uint8 uint16 uint32 uint64 uintptr

float32 float64 complex64 complex128

bool byte error rune string

2.9 声明

go中需要声明的有四种

  1. 变量,var
  2. 常量,const
  3. 类型,type
  4. 方法,func

2.9.1 方法声明

1
2
3
4
5
func 方法名(参数列表 ) 返回列表[可选] {
  //第一句 
  //第一个return
  //最后一个语句
}

2.10 指针

一个专门存放地址的对象叫指针,指针有类型,也是一个对象。套娃,指针存放指针地址如何?

1
2
3
4
5
var i int = 5;
var a = &i; //a就是一个int指针类型,*int
fmt.Printf(*a) //*a就是这个指针指向的值
*a = 9; //通过指针重新给这个变量赋值
var b = *a; //因为*a代表的是一个变量的值,所以也可以出现在表达式的右边

指针变量的零值是nil,任何指向一个有效变量的指针, p != nil 为true。

指针间进行相等测试时,只有指针指向同一个地址,或者都是nil时才为true;

每次对变量做取地址赋给指针的动作都相当与给变量命名一个别名,这个别名可以

指针使用原则:

  1. 值传递的时候需要显式指定传递的是指针的修改原始数据的时候
  2. 结构体中需要定义递归结构的时候

2.11 类型

go中类型存在的初衷 - 表达那些内存中的数据结构一致,但是有着完全不同语义的场景。主要使用来分隔这种底层数据结构一样,但是不能互相运算的概念。 例如摄氏度和华氏度,一样都是数字,但是不能直接加减,要进行转换类型,也就是转换概念,有点像java中的枚举。

1
type 类型名称 类型

定义类型的几种写法

1
2
3
4
5
6
7
8
9
type a int        // 根据基本类型定义一个新类型
type b struct {   // 根据结构体定义一个类型
  aa int
  bb float64
}
type c b*[]       // 根据已存在类型定义一个新类型
type d interfacte { //定义一个接口类型
  func Write(int a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import "fmt"

type Celsius float64    // 摄氏温度
type Fahrenheit float64 // 华氏温度

const (
    AbsoluteZeroC Celsius = -273.15 // 绝对零度
    FreezingC     Celsius = 0       // 结冰点温度
    BoilingC      Celsius = 100     // 沸水温度
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }

func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
func (c Celsius) String() string {  //表示定义一个类型为Celsius的string方法
  return fmt.Sprintf("%g°C", c) 
}

2.12 包和文件

2.12.1 包的初始化

  1. 变量的初始化

    根据包级变量出现的顺序依次初始化,有初始表达式就执行初始表达式进行初始化,复杂数据类型就是用init()初始化函数来进行初始化,每个文件可以包含多个init函数。

  2. 多个go文件的初始化

    根据文件名排序,然后再一一编译。运行也是先排序,再import包,然后每个import文件再调用init()初始化函数。

2.13 函数

声明语法

1
2
3
func name(parameter-list) (result-list) {
    body
}

2.13.1 函数类型

函数类型被称为函数的签名,函数的签名由入参类型,返回值类型及顺序决定,如果两个方法的入参类型顺序,返回值类型都一致的话,就可以说两个方法是

2.13.2 参数传递

调用传递类型:值传递?引用传递?

深入理解Java中方法的参数传递机制

go中的参数传递是值传递, 因此传进函数内部的是实参的拷贝。和java中的参数传递是类似的。

go和java都一样分两种数据类型,基本类型和引用类型。

对于基本类型传参的时候传递的是基本类型的副本,在方法内修改的这个基础类型的时候不会影响原来的值。

对于引用类型传参的时候传递的是引用类型的副本,虽然是两个不同的引用了,但是其指向的内存地址还是一样的,所以会影响原来的值。

2.13.3 函数值

函数作为值进行传递的时候叫函数值。在go中,函数被作为第一类返回值,函数和其他值一样,拥有类型。可以直接使用这个函数作为形参进行构造入参。

这和java是不一样的,java中为了弥补这个特性,推出了函数式接口,用一个接口来表示一个函数,可以直接在这个接口中定义一个默认函数,然后把这个接口类当做是一个函数进行传递。

2.13.4 匿名函数

strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")

有函数名的函数只能在包级语法块中声明,但是通过函数字面量(function literal)绕开这个限制,直接在入参的时候传入一个匿名函数,匿名函数是一个没用函数名的函数,用完即弃。

匿名函数的好处是可以重用当前调用函数的词法环境。

2.13.5 可变参数

func sum(vals ...int) int {
	//todo
}

这会将入参隐式转换成一个数组。如果原来的参数已经是一个数组了,那么传参的时候在后面加上三个点。sum(array...)

** 2.13.6 Deferred函数**

类似于java中的finally功能,但比java的finally功能更强。

在java和go中,return xxx都不是一个原子命令,而是分步执行的。

1
2
3
设置返回值
调用defer
执行return动作

image-20220801120008771

在一个正常语句前面加上一个关键字 - defer,则defer关键字后面的语句会在本函数执行完毕以后才执行。

多个defer的执行顺序,多个defer的执行依赖如何处理?

同一个函数中多个defer语句执行顺序与声明顺序相defer后可以跟三种类型,语句,方法调用,函数声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func f() {
  defer fmt.Println("aa")//表达式
  //todo...
}

func f() {
  defer func() {fmt.Printf("defer executed")}() //return 前执行这个匿名函数
  //todo...
}

func f() {
  defer trace("slow work")() //会在一进来的时候执行,获取一个延迟执行的函数,return前执行
  //...lots of work...
  
}

func trace(msg string) func() {
  start := now
  log.Printf("enter, %s", msg)
  return func() {
    log.Printf("exit %s (%s)", msg, time.Since(start))
  }
} 
// 输出:
// 2015/11/18 09:53:26 enter bigSlowOperation
// 2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)

上面的例子有一个trigger的地方是, defer关键字后面是需要一个函数,这里是通过执行一个函数然后再返回一个 函数

2.13.7 Panic异常

panic异常类型

  1. 编译时异常
  2. 运行时异常
  3. 手动异常

go错误处理机制 在defer函数中,将panic异常当成err进行返回

处理逻辑 发生panic异常,程序中断运行,并且立即执行goroutine中的延迟函数,然后输出日志信息。

处理原则

  1. panic会引起程序崩溃,因此panic一般用于严重错误。
  2. 可以预料到的异常,例如输入错误,配置错误,IO异常,应该使用go的错误处理机制,优雅处理。
  3. 不要试图恢复其他包引起的panic异常,共有API应该将函数运行失败作为err返回,而不是panic

Must方法名约定 当一个函数犯法

2.14 方法

go中的方法与函数的区别就像java中的对象方法与静态方法的区别。

函数直接通过包名进行引用就行了,方法是属于某个结构体的。

2.14.1 面向对象

面向对象编程OOP是一种思想,但是没有具体的实现规范,而OOP在java和go中的实现体现的就是两种实现思路

java中的对象是以java类来进行区分的,方法和对象属性同在同一个类中。

go中的对象是以结构体的命名空间进行区分的,不是强关联的,而是通过结构体命名空间进行区分的。首先定义好对象结构体,然后在包中声明一个函数的时候在前面加上结构体声明,表示这是一个对象方法。调用的时候通过结构体.方法名进行调用。

1
2
3
4
5
6
7
8
9
10
11
struct Point{
  x,y int
}

func (p Point) z() float64 {
  //
}

//调用方式
p := Point{1, 2}//声明一个Point对象
p.z()//调用这个对象的方法z

上面的声明方法的时候在方法名前面加一个(p Point)的叫做方法的接收器, 早起面向对象留下来的概念产物, 掉起一个方法称为”向一个对象发送一个消息“。接收器命名原则简短,通常使用struct的第一个字母就可以了。 在java中的this和别的语言中的self其实也是一个接收器,这个接收器的主要作用就是可以在方法中操作同一个 命名空间的其他资源对象,例如这个结构体的数据或者这个结构体的别的方法。java中this的作用并不大,因为他们 在同一类里面,可以直接引用就可以了,但是go中没有强要求方法和结构体在同一个包下面,数据结构和这个对象的 方法是松耦合的,他们通过这个接收器关联在一起。

2.14.2 指针对象方法

声明一个方法的时候还可以声明称一个指针对象方法,要通过指针来进行调用这个方法。

1
2
3
func (p *Point) z() float64 {
  //指针对象方法
} 

使用指针做接收对象的时候,始终都会使用同一个调用对象,因为即使指针发生了值传递,但是指针指向的地址还是一样的。

  1. 不管方法的接收器是指针类型的还是非指针类型的,都可以通过指针或者非指针进行直接调用,编译器会隐式帮你做转换。
  2. 指针和非指针接收器接收器的选择判断,在go中做方法调用(非函数调用)的时候,调用者也会发生一次值拷贝, 并不是在传参的时候会发生值拷贝。所以如果对象特别大的时候,想提高效率的时候就需要用到指针接收器

2.14.3 方法值和方法表达式

如上面所说,go的中的数据结构和方法是松耦合的,两者之间通过”接收器”进行关联在一起。并且在go中方法是一等 公民,可以单独拿出来作为一个变量,这个变量就叫做”方法值”。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Point{
  I int
  J string
}
func (p Point) z() int {
  
}

a := Point{1, "1"}

zfromA := a.z //zfromP这个变量类型就是方法值,这个方法值绑定了接收器a了
zfromA() //直接调用接收器a上面的z方法

上面的方法值是已经绑定了某个接收器的,还有一种是连接收器也是运行时进行传参指定的。

1
2
3
b := Point.z   //b这个变量类型就是方法表达式,它的第一个参数就是接收器
b(a)  //直接调用z方法,第一个参数传入接收器

2.15 接口

go中的接口与java中的区别的,java中的接口是强实现,go中的接口没有直接的实现关系,只要方法名,入参类型, 返回类型一致就认为是此接口的一个实现。

1
2
3
4
5
6
7
8
package io
type Writer interface {
  Write(p []byte) (n int, err error)
}
w = os.Stdout           // OK: *os.File has Write method
w = new(bytes.Buffer)   // OK: *bytes.Buffer has Write method
w = time.Second         // compile error: time.Duration lacks Write method

上面的三个都有实现Writer(p []byte)这个方法,所以他们都是Writer接口的一个实现。

2.15.1 接口类型

1
2
3
4
5
6
type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
    *c += ByteCounter(len(p)) // convert int to ByteCounter
    return len(p), nil
}

主要有两种类型,一种是指针类型,一种是对象类型。 上面的方法接收者是指针类型*ByteCounter, 而非ByteCounter类型。但是直接使用ByteCounter作为接收器是 可以调用*ByteCounter类型的方法的,这是因为编译器隐式地将具体类型转换成了指针类型。但这只是一个语法糖,实际 上T类型不拥有所有的*T指针的方法。

由于只有*ByteCounterWrite(p []byte)方法,所以也只有*ByteCounter类型实现了Writer接口。

对于interface{}类型,没有任何方法,空接口类型对实现他的类型没有任何要求,可以将任意类型赋值给空接口。 空接口存在的意义是啥?

2.15.2 接口值

一个接口类型具体的实现都是在运行时才能知道的,一个接口值分为两部分,实际类型和实际值,也被称为接口的动态 类型和动态值。

下面三个例子解析接口被赋值时,动态类型和动态值分别是什么

1
2
3
4
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

第一个动态类型是*os.File指针类型描述符,动态值是os.Stdout的拷贝 第二个动态类型是*bytes.Buffer指针类型描述符,动态值是新分配的缓冲区的指针

2.15.3 接口值的比较

接口值可以用==或者!=进行比较,两者都为nil或者动态类型和动态值都相等的情况下相等。 但是要注意的是,如果两者动态类型是一样的,但是这个类型是不可比较的,那就会抛panic异常。

可比较的类型:基本类型和指针 不可比较的类型:切片,映射类型和函数

所以在比较接口类型或者包含接口类型的聚合类型(map,数组,List)的时候,只能比较那些你确定动态值和动态 类型的。

注意点:动态值为nil的接口不等于nil

1
2
3
4
5
6
7
8
9
10
11
12
var buf *bytes.Buffer
if(debug){
  buf = new(bytes.Buffer)
}
f(buf)

func f(out io.Writer) {
  if(out != nil) {
    out.Write([]byte("hello\n")) //这里会执行,并且panic
  }
}

上面成语的原意是想在非debug的时候不采集任何东西,但是注意debug=false的时候buf是一个动态类型为*bytes.Buffer ,动态值为nil的空接口,这个时候buf != nil是true的,Write方法任然会被调用,尝试获取缓冲区的时候就会发生 panic异常。

值得注意的是,有一些指针类型,例如*os.File,nil也是一个合法的接收器。

上面程序的解决办法就是一开始的时候将buf类型设置为bytes.Buffer,而不是他的指针类型。

2.15.4 排序接口

大多语言中都提供了内置的排序算法,不同于其他语言中排序都是和具体的序列类型关联, 但是go中不对具体的序列类型和其中的元素做假设(但必须要是一个序列),只要这个序列和sort包中的接口关联在一起就行了。 go中的内置排序算法要求待排序的数据关联上sort提供的接口,主要告诉go三个东西:1. 序列的长度。2. 两个元素的比较方式 3. 两个元素的置换方式

1
2
3
4
5
package sort

  Len() int
  Less(i,j int) bool
  Swap(i,j int) 

三. 官方工具

3.1 gofmt

3.2 goimports

四. 编程功能

4.1 命令行参数

os包的Args变量保存了程序的命令行参数,类型是一个可变长数组

五. 内置包

5.1 os包

主要用途

提供操作系统的交互函数和变量

api返回值说明
os.Open1. 被打开的文件*os.File
2. 内置error类型的值
打开一个文件,返回两个
   
   
   
   
   

5.2 strings包

主要用途

高效处理字符串,并提供各种便捷字符串操作函数。

5.3 bufio包

主要用途

处理输出输出

API

api入参出参说明
bufio.NewScanner(file *os.File)一个文件指针 创建一个输入流扫描器
    

5.4 fmt包

format包, 格式化

fmt.Printf - 不换行,指的是 print format

fmt.println - 换行,指的是print line

fmt.Printf函数,可以做一些格式化输出。这种格式化转换的符号,称之为动词

1
2
3
4
5
6
7
8
9
10
%d          十进制整数
%x, %o, %b  十六进制,八进制,二进制整数。
%f, %g, %e  浮点数: 3.141593 3.141592653589793 3.141593e+00
%t          布尔:true或false
%c          字符(rune) (Unicode码点)
%s          字符串
%q          带双引号的字符串"abc"或带单引号的字符'c'
%v          变量的自然形式(natural format)
%T          变量的类型
%%          字面上的百分号标志(无操作数)

转义符

符号说明
\t制表符
\n换行符
\a响铃
\b退格
\f换页
\v垂直换行符
\r回车
\’单引号
\”双引号
\|反斜杠 

冷知识🥶 - \r 与 \n 区别与历史 🥶

在计算机出现前,只有打字机.打字机的机头是物理结构,打完一行要移动到下一行,这个物理动作需要分解 成两步,1. 移动到最左边。2 移动到下一行。 于是打字机出现了两个控制字符,对应这两个动作就是\r 和 \n

\r 代表的是回车。最开始就是用来打字机将打印头定位到最左边

\n 代表的是换行。最开始就是用来打字机将打印后移动到下一行

window上的文件换行符是\r\n

unix/linux上的文件换行符是\n

mac上的换行符早期的是\t, 现在大家手上用的基本都是\n

于是现在两大主流系统unix系和windows系的互相打开对方的文件的时候就能看到一些奇怪的现象。

windows打开unix系的文件,会所有的文字都在一行里面显示出来

unix系打开windows的文件,会多处一个^M符号,特别是用windows机器打开过编辑过的linux脚本常碰到这个问题。

5.5 flag包

使用命令行参数设置对应变量的值

六. 内置函数

6.1 make

make()创建一个空map

### 6.2 new

new(T)函数创建一个匿名的T类型变量,初始化T类型的零值,然后返回变量地址, 返回的指针类型为*T。

1
p = new(int)  //p的类型为 p *int

new语法存在的意义, 语法糖,少写一个声明语句。

1
2
3
4
5
6
7
8
func newInt() *int {
    return new(int)
}

func newInt() *int {
    var dummy int
    return &dummy
}

七. 数据结构

go中的数据结构分为四类,基础数据类型, 复合类型,引用类型和接口类型。

基础数据类型:数字,字符串,布尔值

复合类型:数组,结构体,组合简单类型形成复杂结构类型

引用类型:指针,切片,字典,函数,通道、

接口类型:

7.1 整型

有符号:int,int8,int16,int32,int64

无符号:uint,uint8,uint16,uint32,uint64

int & uint

根据特定的CPU平台机器字大小,有32和64。当时我们不能对此做假设他是32或者64bit的,因为不同的编译器,在同样的硬件平台编译也可能产生不一样的大小。

Unicode字符的rune类型是和int32等价的类型,用于表示一个Unicode码点。

byte类型和uint类型也是等价类型

无符号整数uintptr,没有指定bit大小,但是可以可以容纳指针

7.1.1 二元运算符

go提供的算术运算,逻辑运算, 位运算划分优先级,一共分5级,优先级从左往右,从上往下

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

在go语言中+-*/ 可以用于数字间的运算

取模

取模%只能用于整数间的运算,并且取模符号总与被除数符号一致。-5%-2=-1

7.1.2 一元运算符

1
2
3
+ 一元加法(无效果)
- 负数      对于整数来说-x是0-x的简写    对于复数来说 -x表示的就是x的负数?这有什么区别
^ 按位取反

7.1.3 位运算符

1
2
3
4
5
6
&			位运算
|			位运算
^			位运算,作为二元运算的时候为按位异或, 作为一元运算符的是按位取反
&^		位运算,按位置零, x &^ y, x按照y中为1的位就置为0
>>		左移,0填充右边空缺位
<<		右移,无符号数右移,0填充左边空缺位。有符号数右移,符号位填充左边空缺位。

7.2 浮点数

7.3 复数

7.4 字符串

内存定义:一串不可改变的字节序列。

文本定义:将字符串的字节序列经过UTF8编码的unicode码点序列。

特性:不可变,一个字符串的字节序列永远不可变。不可变意味着字符串共享底层数据结构是安全的,于是复制字符串的代价是低廉的。另外一个字符串切片出一个子字符串也是可以共享相同的内存的,于是字符串切片也是安全低廉的

1
2
s := "hello" 
s[1]="1"  //compile error  不可变

字符串面值:使用反引号``标记一段字符串, 这段字符串中的所有字符都不会被转义,包括不可见字符。通常用来表示正则表达式。

字符串常用库

bytes, strings, strconv和unicode包 bytes主要包含

7.5 常量

声明语法

1
2
3
4
5
6
7
8
9
10
11
12
13
//普通定义
const (
	USD = 1
  EUR = 2
)

//常量生成器定义
type index int
const (
	USD index = iota
  EUR
  YUAN
)

常量都是在编译器完成计算,只能是基础类型:bool,string或者数字

常量生成器iota,第一行初始为0,后面每一个增加1。可以通表达式计算每个常量的值,每个表达式的iota+1

7.6 数组

声明方式

1
2
var q [3]int = [3]int{1, 2, 3}
q := [...]int{1,2,3}//省略号表示由后面的元素个数决定长度。

数组类型=长度+类型。数组类型是需要在编译器就知道的东西。所以同一种类型的不同长度是不一样的类型来的,这里和java不一样,java的数组类型仅仅与类型有关,和长度无关。

除了上面这种初始化,还可以结合常量生成器,写一些高级一点的写法。通过指定索引号进行指定

1
2
3
4
5
6
7
8
type index int
const (
  USD index = iota
  EUR
  YUAN
)

array := [...]string{USD:"$", EUR:"#", YUAN:"¥"}//指定index

比较

7.7 Slice切片

声明语法

1
var q []int = []int{0, 1, 2, 3}

slice和数组声明语法的区别在于一个slice声明没有长度选项。内置方法,len, cap

数据结构

slice底层数据结构:主要由两部分构成,一个指针和一个数组。

底层存储数据的结构其实就是一个数组, 但是slice会有自己的一个指针,这个指针指向这个数组内的元素。

比较

除了内部数据的不同以外,slice不能进行比较,不能使用==进行比较两个值。对于[]byte类型的slice可以使用bytes.Equal进行比较, 其他类型的slice就只能自己展开每个元素进行比较。

7.8 Map

声明语法

1
2
3
4
5
var m map = make(map[int]string)
age := map[int]string{
  32 : "Jack",
  19 : "Alice"
}

注意点:

  1. key必须可以用==号进行比较
  2. key不要用float进行比较,因为float的==比较可能不准确
  3. 不能对map中的元素进行取址操作,因为map的数据可能会因为增长二扩容,重hash的时候可能会导致内存地址改变

遍历

Map进行遍历的时候顺序是随机的,每次遍历的时候都是不一样的, 如果想要指定顺序的时候需要使用sort包对map的key进行排序。

然后根据排序后的key对来拿出这个map的元素,然后进行遍历这个排序后的key数组实现遍历这个map的效果。

1
2
3
4
5
6
7
8
9
10
import sort

var names []string
for name := range age {
  names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
  //遍历排序后的name数组
} 

取值

map的取值返回两个值,第一个元素值,第二个元素标志位。

1
age, ok := ages["Dennis"]

比较

map的比较跟slice一样,不支持直接比较,和nil比较除外。要比较两个map是否相等可以通过

SET

go中没直接提供set,但是可以通过map中key不重复的特性,用map的key作为set。

7.9 结构体

  • 定义
1
2
3
4
5
type xxxx struct {
  Name, Addr string
  Id 				 int
  Salary		 int
}
  • 结构体唯一性
  1. 属性的顺序对于结构体来说是有意义的,不同的顺序意味着不同的结构体。
  2. 结构体的属性可见性同样是通过命名的大小写来控制的
  3. 结构体不能包含自身的成员,但是可以包含自身成员的指针
  • 初始化

为赋值的属性将遵循零值机制

初始化的语法有两种

1
2
3
4
// 按顺序进行初始化
type Point struct {X,Y int}
p := Point{1, 2}
p := Point{X:1, Y:2}
  • 比较

只有结构体的全部成员都是可比较的的时候,那么这个结构体便也是可比较的。

八. 并发编程

8.1 Goroutines和Channels

参考

https://mp.weixin.qq.com/s/hJliPe60pzImRDVQbiAaDw https://www.cnblogs.com/tsiangleo/p/4433410.html

本文由作者按照 CC BY 4.0 进行授权

像素与分辨率

正则与常用命令操作