【golang】 搭建知识体系:basics

go简介

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

  • 特色

    • 简洁、快速、安全
    • 并行、有趣、开源
    • 内存管理、数组安全、编译迅速
    • Go 语言最主要的特性:
      • 自动垃圾回收
      • 更丰富的内置类型
      • 函数多返回值
      • 错误处理
      • 匿名函数和闭包
      • 类型和接口
      • 并发编程
      • 反射
      • 语言交互性
  • 用途

    Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

    对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

  • hello go

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

    func main(){
    fmt.Println("hello go!!!")
    }
    [root@192 go]# go run hello.go #go run 执行
    hello go!!!
    [root@192 go]# go build hello.go #go build 编译生成二进制
    [root@192 go]# ll
    total 1896
    -rwxr-xr-x. 1 root root 1937013 Mar 14 10:00 hello
    -rw-r--r--. 1 root root 70 Mar 14 09:59 hello.go
    [root@192 go]# ./hello
    hello go!!!



程序结构
  • 包声明

  • 引入包

  • 函数

  • 变量

  • 语句 & 表达式

  • 注释

    1
    2
    3
    4
    5
    6
    7
    8
    package main

    import "fmt"

    func main() {// 注意{ 不能在单独的行上,否则会报错
    /* 这是我的第一个简单的程序 */
    fmt.Println("Hello, World!")
    }
    1. 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
    2. 下一行 import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
    3. 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
    4. 下一行 // 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
    5. 下一行 fmt.Println(…) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
      使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。
      Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
    6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
基础语法
  • Go 标记

​ Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:

1
fmt.Println("Hello, World!")

​ 6 个标记是(每行一个):

1
2
3
4
5
6
1. fmt
2. .
3. Println
4. (
5. "Hello, World!"
6. )
  • 行分隔符

在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。

如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

以下为两个语句:

1
2
fmt.Println("Hello, World!")
fmt.Println("菜鸟教程:runoob.com")
  • 注释

注释不会被编译,每一个包应该有相关注释。

单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:

1
2
3
4
5
// 单行注释
/*
Author by 菜鸟教程
我是多行注释
*/
  • 标识符

标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

以下是有效的标识符:

1
2
mahesh   kumar   abc   move_name   a_123
myname50 _temp j a23b9 retVal

以下是无效的标识符:

  • 1ab(以数字开头)

  • case(Go 语言的关键字)

  • a+b(运算符是不允许的)

  • 字符串连接

Go 语言的字符串可以通过 + 实现:

实例

1
2
3
4
5
package main
import "fmt"
func main() {
fmt.Println("Google" + "Runoob")
}
  • 关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:

appendboolbytecapclosecomplexcomplex64complex128uint16
copyfalsefloat32float64imagintint8int16uint32
int32int64iotalenmakenewnilpanicuint64
printprintlnrealrecoverstringtrueuintuint8uintptr

程序一般由关键字、常量、变量、运算符、类型和函数组成。

程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。

程序中可能会使用到这些标点符号:.、,、;、: 和 …。

  • Go 语言的空格

Go 语言中变量的声明必须使用空格隔开,如:

1
var age int

语句中适当使用空格能让程序更易阅读。

无空格:

1
fruit=apples+oranges

在变量与运算符间加入空格,程序看起来更加美观,如:

1
fruit = apples + oranges; 
  • 格式化字符串

Go 语言中使用 fmt.Sprintf 格式化字符串并赋值给新串:

实例

package main

import (
“fmt”
)

1
2
3
4
5
6
7
8
func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
}

输出结果为:

1
Code=123&endDate=2020-12-31

Go 可以使用 fmt.Sprintf 来格式化字符串,格式如下:

1
fmt.Sprintf(格式化样式, 参数列表…)
  • 格式化样式:字符串形式,格式化符号以 % 开头, %s 字符串格式,%d 十进制的整数格式。
  • 参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。

Go 字符串格式化符号:

格 式描 述
%v按值的本来值输出
%+v在 %v 基础上,对结构体字段名和值进行展开
%#v输出 Go 语言语法格式的值
%T输出 Go 语言语法格式的类型和值
%%输出 % 本体
%b整型以二进制方式显示
%o整型以八进制方式显示
%d整型以十进制方式显示
%x整型以十六进制方式显示
%X整型以十六进制、字母大写方式显示
%UUnicode 字符
%f浮点数
%p指针,十六进制方式显示
数据类型

在 Go 编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

Go 语言按类别有以下几种数据类型:

序号类型和描述
1布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型
  • 数字类型

Go 也有基于架构的类型,例如:int、uint 和 uintptr。

序号类型和描述
1uint8 无符号 8 位整型 (0 到 255)
2uint16 无符号 16 位整型 (0 到 65535)
3uint32 无符号 32 位整型 (0 到 4294967295)
4uint64 无符号 64 位整型 (0 到 18446744073709551615)
5int8 有符号 8 位整型 (-128 到 127)
6int16 有符号 16 位整型 (-32768 到 32767)
7int32 有符号 32 位整型 (-2147483648 到 2147483647)
8int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
  • 浮点型
序号类型和描述
1float32 IEEE-754 32位浮点型数
2float64 IEEE-754 64位浮点型数
3complex64 32 位实数和虚数
4complex128 64 位实数和虚数
  • 其他数字类型

以下列出了其他更多的数字类型:

序号类型和描述
1byte 类似 uint8
2rune 类似 int32
3uint 32 或 64 位
4int 与 uint 一样大小
5uintptr 无符号整型,用于存放一个指针
变量

变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。

变量可以通过变量名访问。

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

声明变量的一般形式是使用 var 关键字:

1
var identifier type

可以一次声明多个变量:

1
var identifier1, identifier2 type
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main() {
var a string = "Runoob"
fmt.Println(a)

var b, c int = 1, 2
fmt.Println(b, c)
}
以上实例输出结果为:

Runoob
1 2
  • 变量声明

第一种,指定变量类型,如果没有初始化,则变量默认为零值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var v_name v_type
v_name = value
零值就是变量没有做初始化时系统默认设置的值。
package main
import "fmt"
func main() {

// 声明一个变量并初始化
var a = "RUNOOB"
fmt.Println(a)

// 没有初始化就为零值
var b int
fmt.Println(b)

// bool 零值为 false
var c bool
fmt.Println(c)
}
以上实例执行结果为:

RUNOOB
0
false
  • 数值类型(包括complex64/128)为 0

  • 布尔类型为 false

  • 字符串为 **””**(空字符串)

  • 以下几种类型为 nil

    • ```go
      var a *int
      var a []int
      var a map[string] int
      var a chan int
      var a func(string) int
      var a error // error 是接口
      package mainimport “fmt”func main() {
      var i int
      var f float64
      var b bool
      var s string
      fmt.Printf("%v %v %v %q\n", i, f, b, s)
      
      }
      输出结果是:0 0 false “”
      1
      2
      3
      4
      5

      **第二种,根据值自行判定变量类型。**

      ```go
      var v_name = value
1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
var d = true
fmt.Println(d)
}
输出结果是:

true

第三种,如果变量已经使用 var 声明过了,再使用 *:=* 声明变量,就产生编译错误,格式:

1
v_name := value
1
2
var intVal int 
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明

直接使用下面的语句即可:

1
2
3
4
5
intVal := 1 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句

intVal := 1 相等于:
var intVal int
intVal =1
  • 多变量声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误


// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

var x, y int
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
)

var c, d int = 1, 2
var e, f = 123, "hello"

//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"

func main(){
g, h := 123, "hello"
println(x, y, a, b, c, d, e, f, g, h)
}
以上实例执行结果为:

0 0 0 false 1 2 123 hello 123 hello
  • 简短形式,使用 := 赋值操作符

a := 50 或 b := false。

a 和 b 的类型(int 和 bool)将由编译器自动推断。

这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。

  • 注意事项

如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。

如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a。

如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误;

但是全局变量是允许声明但不使用的。 同一类型的多个变量可以声明在同一行,如:

1
var a, b, c int

多变量可以在同一行进行赋值,如:

1
2
3
var a, b int
var c string
a, b, c = 5, 7, "abc"

上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:

1
a, b, c := 5, 7, "abc"

右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 “abc”。

这被称为 并行 或 同时 赋值。

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

1
const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

1
const c_name1, c_name2 = value1, value2
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 LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, "str" //多重赋值

area = LENGTH * WIDTH
fmt.Printf("面积为 : %d", area)
println()
println(a, b, c)
}
以上实例运行结果为:

面积为 : 50
1 false str

常量还可以用作枚举:

1
2
3
4
5
const (
Unknown = 0
Female = 1
Male = 2
)

常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:

实例

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)

func main(){
println(a, b, c)
}

以上实例运行结果为:

1
abc 3 16
  • iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

1
2
3
4
5
const (
a = iota
b = iota
c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

1
2
3
4
5
const (
a = iota
b
c
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
运算符

注意:go没有三目运算符

优先级分类运算符结合性
1逗号运算符,从左到右
2赋值运算符=、+=、-=、*=、/=、 %=、 >>=、 <<=、&=、^=、|=从右到左
3逻辑或||从左到右
4逻辑与&&从左到右
5按位或|从左到右
6按位异或^从左到右
7按位与&从左到右
8相等/不等==、!=从左到右
9关系运算符<、<=、>、>=从左到右
10位移运算符<<、>>从左到右
11加法/减法+、-从左到右
12乘法/除法/取余*(乘号)、/、%从左到右
13单目运算符+ 、-、 !、 ~、 (type)*、 & sizeof (待确认)从右到左
14后缀运算符( )、[ ]、->、.、++、–从左到右

http://c.biancheng.net/view/5559.html

条件语句

Go 语言提供了以下几种条件判断语句:

语句描述
if 语句if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if…else 语句if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
switch 语句switch 语句用于基于不同条件执行不同动作。
select 语句select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
1.if 
if 布尔表达式 {
/* 如果条件为 true 则执行以下语句 */
}
2.if else
if 布尔表达式 {
/* 如果条件为 true 则执行以下语句 */
} else {
/* 如果条件为 false 则执行以下语句 */
}
3.if嵌套
if 布尔表达式1 {
/* if 条件语句为 true 执行 */
if 布尔表达式2 {
/* if 条件语句为 true 执行 */
}
}
4.switch
switch var1 {
case val1:
...
case val2:
...
default:
...
}
/* 定义局部变量 */
var grade string = "B"
var marks int = 90

switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}

switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" );
}
5.Type Switch
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

Type Switch 语法格式如下:

switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
var x interface{}

switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
6.fallthrough
使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true
func main(){
switch {
case false:
fmt.Println("1、case 条件语句为 false")
fallthrough
case true:
fmt.Println("2、case 条件语句为 true")
fallthrough
case false:
fmt.Println("3、case 条件语句为 false")
fallthrough
case true:
fmt.Println("4、case 条件语句为 true")
case false:
fmt.Println("5、case 条件语句为 false")
fallthrough
default:
fmt.Println("6、默认 case")
}
}
以上代码执行结果为:
2case 条件语句为 true
3case 条件语句为 false
4case 条件语句为 true
从以上代码输出的结果可以看出:switch 从第一个判断表达式为 truecase 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true
7.select
select是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
select 语句的语法如下:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
以下描述了 select 语句的语法:
每个 case 都必须是一个通信
所有 channel 表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行,其他被忽略。
如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
1.如果有 default 子句,则执行该语句。
2.如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
以上代码执行结果为:
no communication
循环语句
函数
匿名函数

https://www.jianshu.com/p/91140fdd6e91

变量作用域
数组
指针
结构体

注意结构体标签如,Age int json:"age,omitempty"

https://www.cnblogs.com/liyutian/p/10050320.html

切片

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片:

你可以声明一个未指定大小的数组来定义切片:

1
var identifier []type

切片不需要说明长度。

或使用 make() 函数来创建切片:

1
2
3
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

1
make([]T, length, capacity)

这里 len 是数组的长度并且也是切片的初始长度。

  • 切片初始化
1
s :=[] int {1,2,3 } 

直接初始化切片,**[]** 表示是切片类型,**{1,2,3}** 初始化值依次是 1,2,3,其 cap=len=3

1
s := arr[:] 

初始化切片 s,是数组 arr 的引用。

1
s := arr[startIndex:endIndex] 

将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

1
s := arr[startIndex:] 

默认 endIndex 时将表示一直到arr的最后一个元素。

1
s := arr[:endIndex] 

默认 startIndex 时将表示从 arr 的第一个元素开始。

1
s1 := s[startIndex:endIndex] 

通过切片 s 初始化切片 s1。

1
s :=make([]int,len,cap) 

通过内置函数 make() 初始化切片s,**[]int** 标识为其元素类型为 int 的切片。

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

以下为具体实例:

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
var numbers = make([]int,3,5)

printSlice(numbers)
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v**\n**",len(x),cap(x),x)
}

以上实例运行输出结果为:

1
len=3 cap=5 slice=[0 0 0]
  • 空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0,实例如下:

实例

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

import "fmt"

func main() {
var numbers []int

printSlice(numbers)

if(numbers == nil){
fmt.Printf("切片是空的")
}
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v**\n**",len(x),cap(x),x)
}

以上实例运行输出结果为:

1
2
len=0 cap=0 slice=[]
切片是空的
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

func main() {
/* 创建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)

/* 打印原始切片 */
fmt.Println("numbers ==", numbers)

/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])

/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])

/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])

numbers1 := make([]int,0,5)
printSlice(numbers1)

/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)

/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)

}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
执行以上代码输出结果为:

len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]
  • append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

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
27
28
29
30
31
32
33
34
35
36
37
38
package main

import "fmt"

func main() {
var numbers []int
printSlice(numbers)

/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)

/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)

/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)

/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)

/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}

func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
以上代码执行输出结果为:

len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
范围
Map(集合)
递归函数
类型转换
接口
错误处理
并发

channel

参考: https://www.runoob.com/w3cnote/go-channel-intro.html

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。

它的操作符是箭头 <-

1
2
ch <- v    // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v

(箭头的指向就是数据的流向)

就像 map 和 slice 数据类型一样, channel必须先创建再使用:

1
ch := make(chan int)
  1. channel类型

    Channel类型的定义格式如下:

    1
    ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

    它包括三种类型的定义。可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。

    1
    2
    3
    chan T          // 可以接收和发送类型为 T 的数据
    chan<- float64 // 只可以用来发送 float64 类型的数据
    <-chan int // 只可以用来接收 int 类型的数据

    <-总是优先和最左边的类型结合。

    1
    2
    3
    4
    chan<- chan int    // 等价 chan<- (chan int)
    chan<- <-chan int // 等价 chan<- (<-chan int)
    <-chan <-chan int // 等价 <-chan (<-chan int)
    chan (<-chan int)

    使用make初始化Channel,并且可以设置容量:

    1
    make(chan int, 100)

    容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。
    如果没有设置容量,或者容量设置为0, 说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking)。如果设置了缓存,就有可能不发生阻塞, 只有buffer满了后 send才会阻塞, 而只有缓存空了后receive才会阻塞。一个nil channel不会通信。

    可以通过内建的close方法可以关闭Channel。

    你可以在多个goroutine从/往 一个channel 中 receive/send 数据, 不必考虑额外的同步措施。

    Channel可以作为一个先入先出(FIFO)的队列,接收的数据和发送的数据的顺序是一致的。

    channel的 receive支持 multi-valued assignment,如

    1
    v, ok := <-ch         

    它可以用来检查Channel是否已经被关闭了。

    1. send语句
      send语句用来往Channel中发送数据, 如ch <- 3
      它的定义如下:
    1
    2
    SendStmt = Channel "<-" Expression .
    Channel = Expression .

    在通讯(communication)开始前channel和expression必选先求值出来(evaluated),比如下面的(3+4)先计算出7然后再发送给channel。

    1
    2
    3
    4
    5
    c := make(chan int)
    defer close(c)
    go func() { c <- 3 + 4 }()
    i := <-c
    fmt.Println(i)

    send被执行前(proceed)通讯(communication)一直被阻塞着。如前所言,无缓存的channel只有在receiver准备好后send才被执行。如果有缓存,并且缓存未满,则send会被执行。

    往一个已经被close的channel中继续发送数据会导致run-time panic

    往nil channel中发送数据会一致被阻塞着。

    1. receive 操作符
      <-ch用来从channel ch中接收数据,这个表达式会一直被block,直到有数据可以接收。

    从一个nil channel中接收数据会一直被block。

    从一个被close的channel中接收数据不会被阻塞,而是立即返回,接收完已发送的数据后会返回元素类型的零值(zero value)。

    如前所述,你可以使用一个额外的返回参数来检查channel是否关闭。

    1
    2
    3
    x, ok := <-ch
    x, ok = <-ch
    var x, ok = <-ch

    如果OK 是false,表明接收的x是产生的零值,这个channel被关闭了或者为空。

  2. blocking

    默认情况下,发送和接收会一直阻塞着,直到另一方准备好。这种方式可以用来在gororutine中进行同步,而不必使用显示的锁或者条件变量。

    如官方的例子中x, y := <-c, <-c这句会一直等待计算结果发送到channel中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import "fmt"
    func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
    sum += v
    }
    c <- sum // send sum to 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 // receive from c
    fmt.Println(x, y, x+y)
    }
  3. Buffered channels

  4. Range

  5. select

    1. timeout
  6. Timer and Ticker

  7. close

  8. 同步

开发工具

其他:

  1. 符号…

    https://blog.csdn.net/jeffrey11223/article/details/79166724

  2. package作用以及定义

    https://www.jianshu.com/p/df33ee0d51ce

  3. go test 测试