Go语言语法教程

开发环境

  1. 安装Golang
  2. 配置go的开发环境
    • vscode
    • Golang (JetBrain开发的IDE

基础语法

Hello World

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(...)可以将字符串输出到控制台,并在最后自动增加换行字符 。 使用 fmt.Print("hello, world\n")可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
  6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:

实例

1
2
3
4
5
6
package main
import "fmt"
func main()
{ *// 错误,{ 不能在单独的行上*
fmt.Println("Hello, World!")
}

行分隔符

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

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

标识符

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

格式化字符串

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

  • Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
  • Printf 根据格式化参数生成格式化的字符串并写入标准输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

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)
//url为格式,%d %s为占位符
fmt.Println(target_url)
}

输出结果为:

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

数据类型

  • 布尔型bool
  • 数字类型int
  • 字符串类型
    • 可通过加号拼接
    • 可通过==比较
  • 派生类型
    • 指针
    • 数组
    • 结构化
    • Channel
    • 函数
    • 切片
    • 接口interface
    • Map

数字类型

  • uint8 无符号 8 位整型 (0 到 255)
  • uint16 无符号 16 位整型 (0 到 65535)
  • uint32 无符号 32 位整型 (0 到 4294967295)
  • uint64 无符号 64 位整型 (0 到 18446744073709551615)
  • int8 有符号 8 位整型 (-128 到 127)
  • int16 有符号 16 位整型 (-32768 到 32767)
  • int32 有符号 32 位整型 (-2147483648 到 2147483647)
  • int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

浮点型

  • float32 IEEE-754 32位浮点型数
  • float64 IEEE-754 64位浮点型数
  • complex64 32 位实数和虚数
  • complex128 64 位实数和虚数

其他数字类型

  • byte 类似 uint8
  • rune 类似 int32
  • uint 32 或 64 位
  • int 与 uint 一样大小
  • uintptr 无符号整型,用于存放一个指针

语言变量

  • 可以一次声明多个变量
1
2
var identifier1, identifier2 type
var b, c int = 1, 2
  • bool类型的默认初值为false

  • 以下几种类型的初值为nil

1
2
3
4
5
6
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

值类型和引用类型

  • 变量间的赋值为值拷贝
  • 一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

语言常量

  • 常量是一个简单值的标识符,在程序运行时,不会被修改的量
  • 常量中的数据类型只可以是布尔型bool数字型(整数型、浮点型和复数)字符串型
  • 常量的定义格式:
1
const identifier [type] = value
  • 你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

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

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

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
)

iota 用法

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
}

以上实例运行结果为:

1
0 1 2 ha ha 100 100 7 8

再看个有趣的的 iota 实例:

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
const (
i=1<<iota
j=3<<iota
k
l
)

func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}

以上实例运行结果为:

1
2
3
4
i= 1
j= 6
k= 12
l= 24

iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2l=3<<3

简单表述:

  • i=1:左移 0 位,不变仍为 1。
  • j=3:左移 1 位,变为二进制 110,即 6。
  • k=3:左移 2 位,变为二进制 1100,即 12。
  • l=3:左移 3 位,变为二进制 11000,即 24。

注:<<n==*(2^n)

条件语句

if else

  • if后面没有括号
  • if后面必须接大括号{}
1
2
3
4
5
6
if 7%2 == 0 {
fmt.Println("7 is even")
}
else{
fmt.Println("7 is odd")
}

switch

  • switch后面的变量名不需要加括号
  • go语言中不需要加break也不会继续往下跑完所有的case
  • default 不论放在哪都是最后执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a := 2
switch a{
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("other")
}

t := time.Now()
switch{
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}

type-switch

1
2
3
4
5
6
7
8
9
10
switch x.(type)
{
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}

实例

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

import "fmt"

func main() {
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("未知型")
}
}

以上代码执行结果为:

1
x 的类型 :<nil>

fallthrough

  • 使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
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
package main

import "fmt"

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")
}
}

以上代码执行结果为:

1
2
3
2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true

从以上代码输出的结果可以看出:switch 从第一个判断表达式为 true 的 case 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。

循环

  • go语言中只有for循环,没有while、do while循环,也可以break continue跳出或者继续
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
i := 1
for{
fmt.Println("loop")
break
}
for j:=7;j<9;j++{
fmt.Println(j)
}

for n:=0;n<5;n++{
if n%2 == 0{
continue
}
fmt.Println(n)
}

for i<=3{
fmt.Println(i)
i = i + 1
}

goto语句

1
2
3
4
5
goto label;
..
.
label: statement;

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() {
/* 定义局部变量 */
var a int = 10

/* 循环 */
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
}

以上实例执行结果为:

1
2
3
4
5
6
7
8
9
10
a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19

函数

函数定义格式

1
2
3
func function_name( [parameter list] ) [return_types] {
//函数体
}
  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

实例

1
2
3
4
5
6
7
8
9
10
11
12
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 声明局部变量 */
var result int

if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}

返回多个值

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

import "fmt"

func swap(x, y string) (string, string) {
return y, x
}

func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}

以上实例执行结果为:

1
Runoob Google

函数参数

函数如果使用参数,该变量可称为函数的形参。

形参就像定义在函数体内的局部变量。

调用函数,可以通过两种方式来传递参数:

  • 值传递:在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
  • 引用传递:在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

函数用法

函数作为另外一个函数的实参

  • 函数定义后可作为另外一个函数的实参数传入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"math"
)

func main(){
/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}

/* 使用函数 */
fmt.Println(getSquareRoot(9))

}

闭包

  • 闭包是匿名函数,可在动态编程中使用
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
package main

import "fmt"

func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}

func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()

/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())

/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}

以上代码执行结果为:

1
2
3
4
5
1
2
3
1
2

方法

  • 方法就是一个包含了接受者的函数

  • 接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:

    1
    2
    3
    func (variable_name variable_data_type) function_name() [return_type]{
    /* 函数体*/
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

/* 定义结构体 */
type Circle struct {
radius float64
}

func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}

以上代码执行结果为:

1
圆的面积 =  314

变量作用域

  • 局部变量:函数内定义
  • 全局变量:函数外定义
  • 形式参数:函数定义中的变量

数组

  • 数组长度固定,在实际运用中,我们更多使用切片

  • 数组的声明

1
2
var arrayname [size]dataType
var balance [10]float32
  • 数组的初始化
1
2
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
  • 如果数组长度不确定,可以使用...代替数组的长度,编译器会根据元素个数自行推断数组的长度
1
2
3
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  • 如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
1
2
//  将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}
  • 初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
  • 如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
1
balance[4] = 50.0

以上实例读取了第五个元素。数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

多维数组

Go 语言支持多维数组,以下为常用的多维数组声明方式:

1
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

以下实例声明了三维的整型数组:

1
var threedim [5][10][4]int

二维数组

二维数组是最简单的多维数组,二维数组本质上是由一维数组组成的。二维数组定义方式如下:

1
var arrayName [ x ][ y ] variable_type

初始化二维数组

多维数组可通过大括号来初始值。以下实例为一个 3 行 4 列的二维数组:

1
2
3
4
5
a := [3][4]int{  
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}

注意:

以上代码中倒数第二行的}必须要有逗号,因为最后一行的}不能单独一行,也可以写成这样:

1
2
3
4
a := [3][4]int{  
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}} /* 第三行索引为 2 */

指针

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

以下实例演示了变量在内存中地址:

实例

1
2
3
4
5
6
package main
import "fmt"
func main() {
var a int = 10
fmt.Printf("变量的地址: %x\n", &a )
}

执行以上代码输出结果为:

1
变量的地址: 20818a220

声明

1
2
3
var var_name *var-type
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}

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

1
2
3
a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20

Go 空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

实例

1
2
3
4
5
6
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr )
}

以上实例输出结果为:

1
ptr 的值为 : 0

空指针判断:

1
2
if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
  • 指针数组
  • 指向指针的指针
  • 向函数传递指针参数

结构体

1
2
3
4
5
6
type struct_variable_type struct {
member definition
member definition
...
member definition
}
1
2
3
variable_name := structure_variable_type {value1, value2...valuen}

variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

实例

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

import "fmt"

type Books struct {
title string
author string
subject string
book_id int
}


func main() {

// 创建一个新的结构体
fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})

// 也可以使用 key => value 格式
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})

// 忽略的字段为 0 或 空
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}

输出结果为:

1
2
3
{Go 语言 www.runoob.com Go 语言教程 6495407}
{Go 语言 www.runoob.com Go 语言教程 6495407}
{Go 语言 www.runoob.com 0}

实例

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
package main

import "fmt"

type Books struct {
title string
author string
subject string
book_id int
}

func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */

/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.runoob.com"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700

/* 打印 Book1 信息 */
fmt.Printf( "Book 1 title : %s\n", Book1.title)
fmt.Printf( "Book 1 author : %s\n", Book1.author)
fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

/* 打印 Book2 信息 */
fmt.Printf( "Book 2 title : %s\n", Book2.title)
fmt.Printf( "Book 2 author : %s\n", Book2.author)
fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}

以上实例执行运行结果为:

1
2
3
4
5
6
7
8
Book 1 title : Go 语言
Book 1 author : www.runoob.com
Book 1 subject : Go 语言教程
Book 1 book_id : 6495407
Book 2 title : Python 教程
Book 2 author : www.runoob.com
Book 2 subject : Python 语言教程
Book 2 book_id : 6495700

切片

  • 切片可以任意更改长度
  • 使用make操作来创建一个切片
  • 不支持负数索引
  • slice原理:

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

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,长度为 0

1
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
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)
}

执行以上代码输出结果为:

1
2
3
4
5
6
7
8
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()

1
2
3
4
5
6
var numbers []int

numbers = append(numbers,1,2,3)//可同时追加单个或多个元素

numbers1 := make([]int , len(numbers),(cap(numbers)*2))
copy(numbers1,numbers)

Range

range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

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

以上代码中的 key 和 value 是可以省略。

如果只想读取 key,格式如下:

1
for key := range oldMap

或者这样:

1
for key, _ := range oldMap

如果只想读取 value,格式如下:

1
for _, value := range oldMap

实例

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
package main
import "fmt"

func main() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0

// 读取 key 和 value
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}

// 读取 key
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}

// 读取 value
for _, value := range map1 {
fmt.Printf("value is: %f\n", value)
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
key is: 4 - value is: 4.000000
key is: 1 - value is: 1.000000
key is: 2 - value is: 2.000000
key is: 3 - value is: 3.000000
key is: 1
key is: 2
key is: 3
key is: 4
value is: 1.000000
value is: 2.000000
value is: 3.000000
value is: 4.000000

实例

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
package main
import "fmt"
func main() {
//这是我们使用 range 去求一个 slice 的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在数组上使用 range 将传入索引和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range 也可以用在 map 的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}

//range也可以用来枚举 Unicode 字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
fmt.Println(i, c)
}
}

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

1
2
3
4
5
6
sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111

Map

  • 其他编程语言中可能叫做哈希or字典
  • 完全无序
  • 通过 key 来快速检索数据,key 类似于索引,指向数据的值。
  • 在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。
  • Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)

// 创建一个空的 Map
m := make(map[string]int)

// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)

// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值

// 修改键值对
m["apple"] = 5

// 获取 Map 的长度
len := len(m)

// 遍历 Map
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}

// 删除键值对
delete(m, "banana")

递归函数

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
func Factorial(n uint64)(result uint64) {
if (n > 0) {
result = n * Factorial(n-1)
return result
}
return 1
}
---
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-2) + fibonacci(n-1)
}
---
func sqrtRecursive(x, guess, prevGuess, epsilon float64) float64 {
if diff := guess*guess - x; diff < epsilon && -diff < epsilon {
return guess
}

newGuess := (guess + x/guess) / 2
if newGuess == prevGuess {
return guess
}

return sqrtRecursive(x, newGuess, guess, epsilon)
}

func sqrt(x float64) float64 {
return sqrtRecursive(x, 1.0, 0.0, 1e-9)
}

类型转换

1
2
var a int = 10
var b float64 = float64(a)

字符串str转换为整型变量num

1
2
3
4
5
6
7
var str string = "10"
var num int
num, _ = strconv.Atoi(str)

//整数to字符串
num := 123
str := strconv.Itoa(num)

strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 **_** 来忽略这个错误

1
2
3
4
5
6
7
8
9
func main() {
str := "123"
num, err := strconv.Atoi(str)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Printf("字符串 '%s' 转换为整数为:%d\n", str, num)
}
}

将字符串转换为浮点数

1
2
str := "3.14"
num, err := strconv.ParseFloat(str, 64)

将浮点数转换为字符串

1
2
num := 3.14
str := strconv.FormatFloat(num, 'f', 2, 64)

接口类型转换

类型断言

  • 将接口类型转换为指定类型
  • value 是接口类型的变量,type 或 T 是要转换成的类型。
1
2
3
value.(type) 
或者
value.(T)
1
2
3
4
5
6
7
var i interface{} = "Hello, World"
str, ok := i.(string)
if ok {
fmt.Printf("'%s' is a string\n", str)
} else {
fmt.Println("conversion failed")
}

以上实例中,我们定义了一个接口类型变量 i,并将它赋值为字符串 "Hello, World"。然后,我们使用类型断言将 i 转换为字符串类型,并将转换后的值赋值给变量 str。最后,我们使用 ok 变量检查类型转换是否成功,如果成功,我们打印转换后的字符串;否则,我们打印转换失败的消息。

类型转换

  • 将一个接口类型的值转换为另一个接口类型
  • T 是目标接口类型,value 是要转换的值。
  • 在类型转换中,我们必须保证要转换的值和目标接口类型之间是兼容的,否则编译器会报错。
1
T(value)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

type Writer interface {
Write([]byte) (int, error)
}

type StringWriter struct {
str string
}

//
func (sw *StringWriter) Write(data []byte) (int, error) {
sw.str += string(data)
return len(data), nil
}

func main() {
var w Writer = &StringWriter{}
sw := w.(*StringWriter)
sw.str = "Hello, World"
fmt.Println(sw.str)
}

接口interface

错误处理

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

我们可以在编码中通过实现 error 接口类型来生成错误信息。

函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息:

1
2
3
4
5
6
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}

在下面的例子中,我们在调用 Sqrt 的时候传递的一个负数,然后就得到了 non-nil 的 error 对象,将此对象与 nil 比较,结果为 true,所以 fmt.Println(fmt 包在处理 error 时会调用 Error 方法)被调用,以输出错误,请看下面调用的示例代码:

1
2
3
4
5
result, err:= Sqrt(-1)

if err != nil {
fmt.Println(err)
}