go语言知识点

目录

一、Go语言特色... 5

1.1 Go 语言用途... 5

1.2 Go语言结构... 5

实例... 5

二、基础语法... 6

2.1 Go 标记... 6

2.2 行分隔符... 6

2.3注释... 6

2.4标识符... 7

2.5 字符串连接... 7

实例... 7

2.6 关键字... 7

2.7 语言的空格... 8

三、数据类型... 8

3.1 布尔型... 9

3.2 数字类型... 9

3.3 字符串类型... 9

3.4派生类型... 9

3.5数字类型... 9

3.6 浮点型... 10

3.7 其他数字类型... 11

四、Go语言变量... 11

4.1 变量声明格式... 11

4.2 多变量声明... 12

五、go语言常量... 13

5.1 常量格式... 13

实例... 14

5.2 iota特殊常量... 14

5.2.1 iota 用法... 15

六、Go 语言运算符... 20

6.1算术运算符... 20

实例... 21

6.2关系运算符... 22

实例... 22

6.3逻辑运算符... 23

实例... 24

6.4 位运算符... 25

实例... 26

6.5 赋值运算符... 27

实例... 28

6.6其他运算符... 29

实例... 29

6.7运算符优先级... 30

实例... 30

七、Go 语言条件语句... 31

7.1 条件语句的结构... 31

7.2 if 语句... 32

语法... 32

实例... 33

7.3 if...else 语句... 34

语法... 34

实例... 34

7.4 if 语句嵌套... 35

语法... 35

实例... 35

7.5 switch 语句... 36

语法... 36

实例... 37

7.5.1 Type Switch. 38

7.5.2 fallthrough. 39

7.6 select 语句... 40

语法... 41

实例... 41

八、Go 语言循环语句... 42

8.1 循环结构图... 42

8.1.1 for 循环... 43

8.1.2 循环嵌套... 46

8.2 循环控制语句... 47

8.2.1 break 语句... 47

8.2.2 continue 语句... 49

8.2.3 goto 语句... 50

8.3 无限循环... 52

实例... 52

九、Go 语言函数... 52

9.1 函数定义... 53

实例... 53

9.2 函数调用... 54

实例... 54

9.3 函数返回多个值... 55

实例... 55

9.4 函数参数... 55

9.4.1函数值传递值... 56

9.4.2 函数引用传递值... 57

9.5 函数用法... 58

9.5.1 函数作为实参... 58

9.5.2函数闭包... 59

9.5.3 函数方法... 60

十、Go 语言变量作用域... 61

10.1 变量可声明的地方... 61

10.2 局部变量... 61

实例... 61

10.3 全局变量... 62

实例... 62

实例... 63

10.4 形式参数... 63

实例... 63

10.5初始化局部和全局变量... 64

十一、Go 语言数组... 64

11.1 数组的定义... 64

11.2 声明数组... 65

11.3 初始化数组... 65

11.4 访问数组元素... 66

实例... 66

11.5多维数组... 67

11.5.1二维数组... 67

11.5.2初始化二维数组... 67

11.5.3 访问二维数组... 68

11.6向函数传递数组... 69

11.6.1方式一... 69

11.6.2方式二... 69

十二、Go 语言指针... 71

12.1 什么是指针... 72

12.2 如何使用指针... 72

12.2.1指针使用流程... 72

实例... 73

12.3 Go空指针... 73

12.4 Go 语言指针数组... 74

12.5 Go 语言指向指针的指针... 75

12.5.1 指向指针的指针变量声明格式... 76

12.6 Go 语言指针作为函数参数... 76

实例... 77

十三、Go 语言结构体... 78

13.1定义结构体... 78

13.2 访问结构体成员... 79

实例... 79

13.3结构体作为函数参数... 80

实例... 80

13.4 结构体指针... 82

实例... 82

十四、Go 语言切片(Slice) 83

14.1定义切片... 84

14.2 切片初始化... 84

14.3 len() 和 cap() 函数... 84

14.4 空(nil)切片... 85

实例... 85

14.5 切片截取... 86

实例... 86

12.4 append() 和 copy() 函数... 87

实例... 87

十五、Go 语言范围(Range) 88

实例... 88

十六、Go 语言Map(集合) 89

16.1 定义 Map. 89

实例... 90

16.2 delete() 函数... 91

实例... 91

十七、Go 语言递归函数... 92

17.1 语法格式... 92

17.2 阶乘... 92

实例... 92

17.3 斐波那契数列... 93

实例... 93

十八、Go 语言类型转换... 94

18.1 语言转换格式... 94

实例... 94

十九、Go 语言接口... 95

实例... 95

实例... 95

二十、Go 错误处理... 96

实例... 97

二十一、Go 并发... 98

21.1 语法格式... 99

实例... 99

21.2 通道(channel)... 100

实例... 100

21.3通道缓冲区... 101

实例... 101

21.4 Go 遍历通道与关闭通道... 102

实例... 102

 

 

一、Go语言特色

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

简洁、快速、安全

并行、有趣、开源

内存管理、数组安全、编译迅速

1.1 Go 语言用途

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

对于高性能分布式系统领域而言,Go 语言无疑比大多数其他语言有更高的开发效率

 

1.2 Go语言结构

有以下基础部分组成:

包声明

引入包

函数

变量

语句 & 表达式

注释

 

实例

package main

import "fmt"

func main() {
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}

 

 

二、基础语法

2.1 Go 标记

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

fmt.Println("Hello, World!")

6 个标记是(每行一个)

1. fmt

2. .

3. Println

4. (

5. "Hello, World!"

6. )


2.2 行分隔符

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

以下为两个语句:

fmt.Println("Hello, World!")
fmt.Println("菜鸟教程:runoob.com")

 

2.3注释

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

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

// 单行注释

/*

 Author by 菜鸟

 我是多行注释

 */


2.4标识符

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

以下是有效的标识符:

mahesh   kumar   abc   move_name   a_123

myname50   _temp   j   a23b9   retVal

以下是无效的标识符:

  • 1ab(以数字开头)
  • caseGo 语言的关键字)
  • a+b(运算符是不允许的)

 

2.5 字符串连接

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

实例

package main
import "fmt"
func main() {
    fmt.Println("Google" + "Runoob")
}

以上实例输出结果为:

GoogleRunoob

 

2.6 关键字

Go的关键字或保留字

除以上还有36个标识符

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

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

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

 

2.7 语言的空格

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

var age int;

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

无空格:

fruit=apples+oranges;

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

fruit = apples + oranges;

Go语言变量的声明必须以空格隔开 例:var  age int;

 

三、数据类型

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

 

3.1 布尔型

值只可以是常量truefalse 例:var yan bool = true

3.2 数字类型

整型 int 和浮点型 float32float64Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。

3.3 字符串类型


字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本

 

3.4派生类型


包括:

(a) 指针类型(Pointer)

(b) 数组类型

(c) 结构化类型(struct)

(d) Channel 类型

(e) 函数类型

(f) 切片类型

(g) 接口类型(interface)

(h) Map 类型

 

3.5数字类型

  • 序号

类型和描述

1

uint8
无符号 8 位整型 (0 255)

2

uint16
无符号 16 位整型 (0 65535)

3

uint32
无符号 32 位整型 (0 4294967295)

4

uint64
无符号 64 位整型 (0 18446744073709551615)

5

int8
有符号 8 位整型 (-128 127)

6

int16
有符号 16 位整型 (-32768 32767)

7

int32
有符号 32 位整型 (-2147483648 2147483647)

8

int64
有符号 64 位整型 (-9223372036854775808 9223372036854775807)

3.6 浮点型

序号

类型和描述

1

float32
IEEE-754 32位浮点型数

2

float64
IEEE-754 64位浮点型数

3

complex64
32 位实数和虚数

4

complex128
64 位实数和虚数


3.7 其他数字类型

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

序号

类型和描述

1

byte
类似 uint8

2

rune
类似 int32

3

uint
32 64

4

int
uint 一样大小

5

uintptr
无符号整型,用于存放一个指针

 

四、Go语言变量

4.1 变量声明格式

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

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

第一种:如果变量没出始化,默认值为0,格式:

var 变量名 数据类型(typevar 变量名1, 变量名2 数据类型(type

第二种:根据值自行判定变量类型,格式:

Var 变量名 = value()

第三种:省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误,格式:

变量名 := value

 

4.2 多变量声明

//类型相同多个变量, 非全局变量

var 变量1, 变量2, 变量3  类型

变量1, 变量2, 变量3 = 值1, 值2, 值3

 

var 变量1, 变量2, 变量3 =值1, 值2, 值3 // 和 python 很像,不需要显示声明类型,自动推断

 

变量1, 变量2, 变量3 := 值1, 值2, 值3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误

 

 

// 这种因式分解关键字的写法一般用于声明全局变量

var (

变量1 类型1

变量2 类型2

)

 

例:

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

 

五、go语言常量

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

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

5.1 常量格式

常量的定义格式:

Const 常量名 [常量类型type] =

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

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

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

 

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

  const 常量名1, 常量名2 = 1, 2

 

例:常见的应用

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

 

常量可以做枚举

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

实例

package main

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

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

 

5.2 iota特殊常量

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

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

iota 可以被用作枚举值:

const (

    a = iota

    b = iota

    c = iota

)

 

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

const (

    a = iota

    b

    c

)

5.2.1 iota 用法

实例

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

再看个有趣的的 iota 实例:

实例

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

以上实例运行结果为:

i= 1

j= 6

k= 12

l= 24

iota 表示从 0 开始自动加 1,所以 i=1<<0j=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。

 

 

Go并发

Go 语言支持并发,只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

 

例如:

go f(x, y, z)

开启一个新的 goroutine:

f(x, y, z)

Go 允许使用 go 语句开启一个新的运行期线程, goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

实例

package main

import (
        "fmt"
        "time"
)

func say(string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("world")
        say("hello")
}

 

通道(channel)

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // v 发送到通道 ch

v := <-ch  // ch 接收数据

           // 并把值赋给 v

声明一个通道很简单,使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须又接收端相应的接收数据。

以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

实例

package main

import "fmt"

func sum([]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 中接收

        fmt.Println(x, y, x+y)
}

输出结果为:

-5 17 12

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

实例

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}

执行输出结果为:

1

2

Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

实例

package main

import (
        "fmt"
)

func fibonacci(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 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

 

六、Go 语言运算符

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

·


6.1算术运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10B 值为 20

运算符

描述

实例

+

相加

A + B 输出结果 30

-

相减

A - B 输出结果 -10

*

相乘

A * B 输出结果 200

/

相除

B / A 输出结果 2

%

求余

B % A 输出结果 0

++

自增

A++ 输出结果 11

--

自减

A-- 输出结果 9

以下实例演示了各个算术运算符的用法:

实例

package main

import "fmt"

func main() {

   var a int = 21
   var b int = 10
   var c int

   c = a + b
   fmt
.Printf("第一行 - c 的值为 %d\n", c )
   c = a - b
   fmt
.Printf("第二行 - c 的值为 %d\n", c )
   c = a * b
   fmt
.Printf("第三行 - c 的值为 %d\n", c )
   c = a / b
   fmt
.Printf("第四行 - c 的值为 %d\n", c )
   c = a % b
   fmt
.Printf("第五行 - c 的值为 %d\n", c )
   a++
   fmt.Printf("第六行 - a 的值为 %d\n", a )
   a=21   // 为了方便测试,a 这里重新赋值为 21
   a--
   fmt.Printf("第七行 - a 的值为 %d\n", a )
}

以上实例运行结果:

第一行 - c 的值为 31

第二行 - c 的值为 11

第三行 - c 的值为 210

第四行 - c 的值为 2

第五行 - c 的值为 1

第六行 - a 的值为 22

第七行 - a 的值为 20

 

6.2关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10B 值为 20

运算符

描述

实例

==

检查两个值是否相等,如果相等返回 True 否则返回 False

(A == B) False

!=

检查两个值是否不相等,如果不相等返回 True 否则返回 False

(A != B) True

>

检查左边值是否大于右边值,如果是返回 True 否则返回 False

(A > B) False

<

检查左边值是否小于右边值,如果是返回 True 否则返回 False

(A < B) True

>=

检查左边值是否大于等于右边值,如果是返回 True 否则返回 False

(A >= B) False

<=

检查左边值是否小于等于右边值,如果是返回 True 否则返回 False

(A <= B) True

以下实例演示了关系运算符的用法:

实例

package main

import "fmt"

func main() {
   var a int = 21
   var b int = 10

   if( a == b ) {
      fmt.Printf("第一行 - a 等于 b\n" )
   } else {
      fmt.Printf("第一行 - a 不等于 b\n" )
   }
   if ( a < b ) {
      fmt.Printf("第二行 - a 小于 b\n" )
   } else {
      fmt.Printf("第二行 - a 不小于 b\n" )
   } 
   
   
if ( a > b ) {
      fmt.Printf("第三行 - a 大于 b\n" )
   } else {
      fmt.Printf("第三行 - a 不大于 b\n" )
   }
   /* Lets change value of a and b */
   a = 5
   b = 20
   if ( a <= b ) {
      fmt.Printf("第四行 - a 小于等于 b\n" )
   }
   if ( b >= a ) {
      fmt.Printf("第五行 - b 大于等于 a\n" )
   }
}

 

6.3逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 TrueB 值为 False

运算符

描述

实例

&&

逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False

(A && B) False

||

逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False

(A || B) True

!

逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True

!(A && B) True

以下实例演示了逻辑运算符的用法:

实例

package main

import "fmt"

func main() {
   var a bool = true
   
var b bool = false
   
if ( a && b ) {
      fmt.Printf("第一行 - 条件为 true\n" )
   }
   if ( a || b ) {
      fmt.Printf("第二行 - 条件为 true\n" )
   }
   /* 修改 a b 的值 */
   a = false
   b 
= true
   
if ( a && b ) {
      fmt.Printf("第三行 - 条件为 true\n" )
   } else {
      fmt.Printf("第三行 - 条件为 false\n" )
   }
   if ( !(a && b) ) {
      fmt.Printf("第四行 - 条件为 true\n" )
   }
}

以上实例运行结果:

第二行 - 条件为 true

第三行 - 条件为 false

第四行 - 条件为 true

 

6.4 位运算符

位运算符对整数在内存中的二进制位进行操作。

下表列出了位运算符 &, |, ^ 的计算:

p

q

p & q

p | q

p ^ q

0

0

0

0

0

0

1

0

1

1

1

1

1

1

0

1

0

0

1

1

假定 A = 60; B = 13; 其二进制数转换为:

A = 0011 1100

 

B = 0000 1101

 

-----------------

 

A&B = 0000 1100

 

A|B = 0011 1101

 

A^B = 0011 0001

Go 语言支持的位运算符如下表所示。假定 A 60B 13

实例演示了位运算符的用法:

实例

package main

import "fmt"

func main() {

   var a uint = 60      /* 60 = 0011 1100 */  
   
var b uint = 13      /* 13 = 0000 1101 */
   var c uint = 0          

   c 
= a & b       /* 12 = 0000 1100 */ 
   fmt
.Printf("第一行 - c 的值为 %d\n", c )

   c = a | b       /* 61 = 0011 1101 */
   fmt.Printf("第二行 - c 的值为 %d\n", c )

   c = a ^ b       /* 49 = 0011 0001 */
   fmt.Printf("第三行 - c 的值为 %d\n", c )

   c = a << 2     /* 240 = 1111 0000 */
   fmt.Printf("第四行 - c 的值为 %d\n", c )

   c = a >> 2     /* 15 = 0000 1111 */
   fmt.Printf("第五行 - c 的值为 %d\n", c )
}

以上实例运行结果:

第一行 - c 的值为 12

第二行 - c 的值为 61

第三行 - c 的值为 49

第四行 - c 的值为 240

第五行 - c 的值为 15

 

6.5 赋值运算符

下表列出了所有Go语言的赋值运算符。

运算符

描述

实例

=

简单的赋值运算符,将一个表达式的值赋给一个左值

C = A + B A + B 表达式结果赋值给 C

+=

相加后再赋值

C += A 等于 C = C + A

-=

相减后再赋值

C -= A 等于 C = C - A

*=

相乘后再赋值

C *= A 等于 C = C * A

/=

相除后再赋值

C /= A 等于 C = C / A

%=

求余后再赋值

C %= A 等于 C = C % A

<<=

左移后赋值

C <<= 2 等于 C = C << 2

>>=

右移后赋值

C >>= 2 等于 C = C >> 2

&=

按位与后赋值

C &= 2 等于 C = C & 2

^=

按位异或后赋值

C ^= 2 等于 C = C ^ 2

|=

按位或后赋值

C |= 2 等于 C = C | 2

以下实例演示了赋值运算符的用法:

实例

package main

import "fmt"

func main() {
   var a int = 21
   var c int

   c =  a
   fmt
.Printf(" 1 - =  运算符实例,c 值为 = %d\n", c )

   c +=  a
   fmt
.Printf(" 2 - += 运算符实例,c 值为 = %d\n", c )

   c -=  a
   fmt
.Printf(" 3 - -= 运算符实例,c 值为 = %d\n", c )

   c *=  a
   fmt
.Printf(" 4 - *= 运算符实例,c 值为 = %d\n", c )

   c /=  a
   fmt
.Printf(" 5 - /= 运算符实例,c 值为 = %d\n", c )

   c  = 200; 

   c <<
=  2
   fmt.Printf(" 6  - <<= 运算符实例,c 值为 = %d\n", c )

   c >>=  2
   fmt.Printf(" 7 - >>= 运算符实例,c 值为 = %d\n", c )

   c &=  2
   fmt.Printf(" 8 - &= 运算符实例,c 值为 = %d\n", c )

   c ^=  2
   fmt.Printf(" 9 - ^= 运算符实例,c 值为 = %d\n", c )

   c |=  2
   fmt.Printf(" 10 - |= 运算符实例,c 值为 = %d\n", c )

}

 

6.6其他运算符

下表列出了Go语言的其他运算符。

运算符

描述

实例

&

返回变量存储地址

&a; 将给出变量的实际地址。

*

指针变量。

*a; 是一个指针变量

以下实例演示了其他运算符的用法:

实例

package main

import "fmt"

func main() {
   var a int = 4
   var b int32
   var c float32
   var ptr *int

   /* 运算符实例 */
   fmt.Printf(" 1 - a 变量类型为 = %T\n", a );
   fmt.Printf(" 2 - b 变量类型为 = %T\n", b );
   fmt.Printf(" 3 - c 变量类型为 = %T\n", c );

   /*  & * 运算符实例 */
   ptr = &a     /* 'ptr' 包含了 'a' 变量的地址 */
   fmt.Printf("a 的值为  %d\n", a);
   fmt.Printf("*ptr %d\n", *ptr);
}

以上实例运行结果:

1 - a 变量类型为 = int

2 - b 变量类型为 = int32

3 - c 变量类型为 = float32

a 的值为  4

*ptr 4

 

6.7运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

优先级

运算符

7

^ !

6

* / % << >> & &^

5

+ - | ^

4

== != < <= >= >

3

<-

2

&&

1

||

当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。

以上实例运行结果:

实例

package main

import "fmt"

func main() {
   var a int = 20
   var b int = 10
   var c int = 15
   var d int = 5
   var e int;

   e = (+ b) * c / d;      // ( 30 * 15 ) / 5
   fmt.Printf("(a + b) * c / d 的值为 : %d\n",  e );

   e = ((+ b) * c) / d;    // (30 * 15 ) / 5
   fmt.Printf("((a + b) * c) / d 的值为  : %d\n" ,  e );

   e = (+ b) * (/ d);   // (30) * (15/5)
   fmt.Printf("(a + b) * (c / d) 的值为  : %d\n",  e );

   e = a + (* c) / d;     //  20 + (150/5)
   fmt.Printf("a + (b * c) / d 的值为  : %d\n" ,  e );  
}

以上实例运行结果:

(a + b) * c / d 的值为 : 90

((a + b) * c) / d 的值为  : 90

(a + b) * (c / d) 的值为  : 90

a + (b * c) / d 的值为  : 50

 

七、Go 语言条件语句

条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。

7.1 条件语句的结构

https://www.runoob.com/wp-content/uploads/2015/06/decision_making.jpg

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

语句

描述

if 语句

if 语句 由一个布尔表达式后紧跟一个或多个语句组成。

if...else 语句

if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。

if 嵌套语句

你可以在 if  else if 语句中嵌入一个或多个 if  else if 语句。

switch 语句

switch 语句用于基于不同条件执行不同动作。

select 语句

select 语句类似于 switch 语句,但是select会随机执行一个可运行的case

如果没有case可运行,它将阻塞,直到有case可运行。

 

7.2 if 语句

if 语句由布尔表达式后紧跟一个或多个语句组成。

语法

Go 编程语言中 if 语句的语法如下:

 

if 布尔表达式 {

   /* 在布尔表达式为 true 时执行 */

}

If 在布尔表达式为 true 时,其后紧跟的语句块执行,如果为 false 则不执行。

流程图如下:

https://www.runoob.com/wp-content/uploads/2015/06/if_statement.jpg

实例

使用 if 判断一个数变量的大小:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10
 
   
/* 使用 if 语句判断布尔表达式 */
   if a < 20 {
       /* 如果条件为 true 则执行以下语句 */
       fmt.Printf("a 小于 20\n" )
   }
   fmt.Printf("a 的值为 : %d\n", a)
}

以上代码执行结果为:

a 小于 20

a 的值为 : 10

 

7.3 if...else 语句

if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。

语法

Go 编程语言中 if...else 语句的语法如下:

if 布尔表达式 {

   /* 在布尔表达式为 true 时执行 */

} else {

  /* 在布尔表达式为 false 时执行 */

}

If 在布尔表达式为 true 时,其后紧跟的语句块执行,如果为 false 则执行 else 语句块。

流程图如下:

https://www.runoob.com/wp-content/uploads/2015/06/if_else_statement.jpg

实例

使用 if else 判断一个数的大小:

package main

import "fmt"

func main() {
   /* 局部变量定义 */
   var a int = 100;
 
   
/* 判断布尔表达式 */
   if a < 20 {
       /* 如果条件为 true 则执行以下语句 */
       fmt.Printf("a 小于 20\n" );
   } else {
       /* 如果条件为 false 则执行以下语句 */
       fmt.Printf("a 不小于 20\n" );
   }
   fmt.Printf("a 的值为 : %d\n", a);

}

以上代码执行结果为:

a 不小于 20

a 的值为 : 100

 

7.4 if 语句嵌套

可以在 if else if 语句中嵌入一个或多个 if else if 语句。

语法

Go 编程语言中 if...else 语句的语法如下:

if 布尔表达式 1 {

   /* 在布尔表达式 1 true 时执行 */

   if 布尔表达式 2 {

      /* 在布尔表达式 2 true 时执行 */

   }

}

可以以同样的方式在 if 语句中嵌套 else if...else 语句

实例

嵌套使用 if 语句:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
 
   
/* 判断条件 */
   if a == 100 {
       /* if 条件语句为 true 执行 */
       if b == 200 {
          /* if 条件语句为 true 执行 */
          fmt.Printf("a 的值为 100 b 的值为 200\n" );
       }
   }
   fmt.Printf("a 值为 : %d\n", a );
   fmt.Printf("b 值为 : %d\n", b );
}

以上代码执行结果为:

a 的值为 100 b 的值为 200

a 值为 : 100

b 值为 : 200

 

7.5 switch 语句

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。

switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break

switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 

语法

Go 编程语言中 switch 语句的语法如下:

switch var1 {

    case val1:

        ...

    case val2:

        ...

    default:

        ...

}

变量 var1 可以是任何类型,而 val1 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。

您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3

流程图:

https://www.runoob.com/wp-content/uploads/2015/06/switch_statement.jpg

实例

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   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" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}

以上代码执行结果为:

优秀!

你的等级是 A

 

7.5.1 Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

Type Switch 语法格式如下:

switch x.(type){

    case type:

       statement(s);     

    case type:

       statement(s);

    /* 你可以定义任意个数的case */

    default: /* 可选 */

       statement(s);

}

实例

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

以上代码执行结果为:

x 的类型 :<nil>

7.5.2 fallthrough

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true

实例

package main

import "fmt"

func main() {

    switch {
    case false:
            fmt.Println("1case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("2case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("4case 条件语句为 true")
    case false:
            fmt.Println("5case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6、默认 case")
    }
}

以上代码执行结果为:

2case 条件语句为 true

3case 条件语句为 false

4case 条件语句为 true

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

 

7.6 select 语句

select Go 中的一个控制结构,类似于用于通用的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

语法

Go 编程语言中 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 或值进行求值。

实例

select 语句应用演示:

package main

import "fmt"

func main() {
   var c1, c2, c3 chan int
   var i1, i2 int
   select {
      case i1 = <-c1:
         fmt.Printf("received ", i1, " from c1\n")
      case c2 <- i2:
         fmt.Printf("sent ", i2, " to c2\n")
      case i3, ok := (<-c3):  // same as: i3, ok := <-c3
         if ok {
            fmt.Printf("received ", i3, " from c3\n")
         } else {
            fmt.Printf("c3 is closed\n")
         }
      default:
         fmt.Printf("no communication\n")
   }    
}

以上代码执行结果为:

no communication

 

八、Go 语言循环语句

8.1 循环结构图

以下为大多编程语言循环程序的流程图: 

https://www.runoob.com/wp-content/uploads/2015/06/loop_architecture.jpg

Go 语言提供了以下几种类型循环处理语句:

循环类型

描述

for 循环

重复执行语句块

循环嵌套

在 for 循环中嵌套一个或多个 for 循环


8.1.1 for 循环

for循环是一个循环控制结构,可以执行指定次数的循环。

语法

Go语言的For循环有3中形式,只有其中的一种使用分号。

for init; condition; post { }

C while 一样:

for condition { }

C for(;;) 一样:

for { }

  • init 一般为赋值表达式,给控制变量赋初值;
  • condition 关系表达式或逻辑表达式,循环控制条件;
  • post 一般为赋值表达式,给控制变量增量或减量。

for语句执行过程如下:

  • 先对表达式1赋初值;
  • 判别赋值表达式 init 是否满足给定条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。

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

for key, value := range oldMap {

    newMap[key] = value

}

for语句语法流程如下图所示:

https://www.runoob.com/wp-content/uploads/2015/06/go_for_loop.jpg

实例

package main

import "fmt"

func main() {

   var b int = 15
   var a int

   numbers := [6]int{1, 2, 3, 5} 

   
/* for 循环 */
   for a := 0; a < 10; a++ {
      fmt.Printf("a 的值为: %d\n", a)
   }

   for a < b {
      a++
      fmt.Printf("a 的值为: %d\n", a)
   }

   for i,x:= range numbers {
      fmt.Printf(" %d x 的值 = %d\n", i,x)
   }   
}

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

a 的值为: 0

a 的值为: 1

a 的值为: 2

a 的值为: 3

a 的值为: 4

a 的值为: 5

a 的值为: 6

a 的值为: 7

a 的值为: 8

a 的值为: 9

a 的值为: 1

a 的值为: 2

a 的值为: 3

a 的值为: 4

a 的值为: 5

a 的值为: 6

a 的值为: 7

a 的值为: 8

a 的值为: 9

a 的值为: 10

a 的值为: 11

a 的值为: 12

a 的值为: 13

a 的值为: 14

a 的值为: 15

0 x 的值 = 1

1 x 的值 = 2

2 x 的值 = 3

3 x 的值 = 5

4 x 的值 = 0

5 x 的值 = 0

 

8.1.2 循环嵌套

Go 语言允许用户在循环内使用循环。

语法

以下为 Go 语言嵌套循环的格式:

for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s);
   }
   statement(s);
}

实例

以下实例使用循环嵌套来输出 2 100 间的素数:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var i, j int

   for i=2; i < 100; i++ {
      for j=2; j <= (i/j); j++ {
         if(i%j==0) {
            break; // 如果发现因子,则不是素数
         }
      }
      if(j > (i/j)) {
         fmt.Printf("%d  是素数\n", i);
      }
   }  
}

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

2  是素数

3  是素数

5  是素数

7  是素数

11  是素数

13  是素数

17  是素数

19  是素数

 

 

8.2 循环控制语句

循环控制语句可以控制循环体内语句的执行过程。

GO 语言支持以下几种循环控制语句:

控制语句

描述

break 语句

经常用于中断当前 for 循环或跳出 switch 语句

continue 语句

跳过当前循环的剩余语句,然后继续进行下一轮循环。

goto 语句

将控制转移到被标记的语句。

 

 8.2.1 break 语句

Go 语言中 break 语句用于以下两方面:

  1. 用于循环语句中跳出循环,并开始执行循环之后的语句。
  2. break switch(开关语句)中在执行一条case后跳出语句的作用。

语法

break 语法格式如下:

break;

break 语句流程图如下:

https://www.runoob.com/wp-content/uploads/2015/06/go_break_statement.jpg

实例

在变量 a 大于 15 的时候跳出循环:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* for 循环 */
   for a < 20 {
      fmt.Printf("a 的值为 : %d\n", a);
      a++;
      if a > 15 {
         /* 使用 break 语句跳出循环 */
         break;
      }
   }
}

以上实例执行结果为:

a 的值为 : 10

a 的值为 : 11

a 的值为 : 12

a 的值为 : 13

a 的值为 : 14

a 的值为 : 15

 

8.2.2 continue 语句

Go 语言的 continue 语句 有点像 break 语句。但是 continue 不是跳出循环,而是跳过当前循环执行下一次循环语句。

for 循环中,执行 continue 语句会触发for增量语句的执行。

语法

continue 语法格式如下:

continue;

continue 语句流程图如下:

https://www.runoob.com/wp-content/uploads/2015/06/go_continue_statement.jpg

实例

在变量 a 等于 15 的时候跳过本次循环执行下一次循环:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

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

以上实例执行结果为:

a 的值为 : 10

a 的值为 : 11

a 的值为 : 12

a 的值为 : 13

a 的值为 : 14

a 的值为 : 16

a 的值为 : 17

a 的值为 : 18

a 的值为 : 19

 

8.2.3 goto 语句

Go 语言的 goto 语句可以无条件地转移到过程中指定的行。

goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。

但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。

语法

goto 语法格式如下:

goto label;

..

.

label: statement;

goto 语句流程图如下:

https://www.runoob.com/wp-content/uploads/2015/06/go_goto_statement.jpg

实例

在变量 a 等于 15 的时候跳过本次循环并回到循环的开始语句 LOOP 处:

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++     
   
}  
}

以上实例执行结果为:

a的值为 : 10

a的值为 : 11

a的值为 : 12

a的值为 : 13

a的值为 : 14

a的值为 : 16

a的值为 : 17

a的值为 : 18

a的值为 : 19

 

8.3 无限循环

如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式来执行无限循环:

实例

package main

import "fmt"

func main() {
    for true  {
        fmt.Printf("这是无限循环。\n");
    }
}

 

 

九、Go 语言函数

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数。

通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。

函数声明告诉了编译器函数的名称,返回类型,和参数。

Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。


9.1 函数定义

Go 语言函数定义格式如下:

func 函数名( [parameter list] ) [返回类型 ] {

   函数体

}

函数定义解析:

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

实例

以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 num2,并返回这两个参数的最大值:

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

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

9.2 函数调用

当创建函数时,定义了函数需要做什么,通过调用该函数来执行指定任务。

调用函数,向函数传递参数,并返回值,例如:

实例

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
   var ret int

   /* 调用函数并返回最大值 */
   ret = max(a, b)

   fmt.Printf( "最大值是 : %d\n", ret )
}

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 定义局部变量 */
   var result int

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

以上实例在 main() 函数中调用 max()函数,执行结果为:

最大值是 : 200

 

9.3 函数返回多个值

Go 函数可以返回多个值,例如:

实例

package main

import "fmt"

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

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

以上实例执行结果为:

Runoob Google

 

9.4 函数参数

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

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

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

传递类型

描述

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,

将不会影响到实际参数。

引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,

将影响到实际参数。

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

9.4.1函数值传递值

传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

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

以下定义了 swap() 函数:

/* 定义相互交换值的函数 */

func swap(x, y int) int {

   var temp int

 

   temp = x /* 保存 x 的值 */

   x = y    /* y 值赋给 x */

   y = temp /* temp 值赋给 y*/

 

   return temp;

}

接下来,让我们使用值传递来调用 swap() 函数:

package main

 

import "fmt"

 

func main() {

   /* 定义局部变量 */

   var a int = 100

   var b int = 200

 

   fmt.Printf("交换前 a 的值为 : %d\n", a )

   fmt.Printf("交换前 b 的值为 : %d\n", b )

 

   /* 通过调用函数来交换值 */

   swap(a, b)

 

   fmt.Printf("交换后 a 的值 : %d\n", a )

   fmt.Printf("交换后 b 的值 : %d\n", b )

}

 

/* 定义相互交换值的函数 */

func swap(x, y int) int {

   var temp int

 

   temp = x /* 保存 x 的值 */

   x = y    /* y 值赋给 x */

   y = temp /* temp 值赋给 y*/

 

   return temp;

}

以下代码执行结果为:

交换前 a 的值为 : 100

交换前 b 的值为 : 200

交换后 a 的值 : 100

交换后 b 的值 : 200

9.4.2 函数引用传递值

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

引用传递指针参数传递到函数内,以下是交换函数 swap() 使用了引用传递:

/* 定义交换值函数*/

func swap(x *int, y *int) {

   var temp int

   temp = *x    /* 保持 x 地址上的值 */

   *x = *y      /* y 值赋给 x */

   *y = temp    /* temp 值赋给 y */

}

以下我们通过使用引用传递来调用 swap() 函数:

package main

 

import "fmt"

 

func main() {

   /* 定义局部变量 */

   var a int = 100

   var b int= 200

 

   fmt.Printf("交换前,a 的值 : %d\n", a )

   fmt.Printf("交换前,b 的值 : %d\n", b )

 

   /* 调用 swap() 函数

   * &a 指向 a 指针,a 变量的地址

   * &b 指向 b 指针,b 变量的地址

   */

   swap(&a, &b)

 

   fmt.Printf("交换后,a 的值 : %d\n", a )

   fmt.Printf("交换后,b 的值 : %d\n", b )

}

 

func swap(x *int, y *int) {

   var temp int

   temp = *x    /* 保存 x 地址上的值 */

   *x = *y      /* y 值赋给 x */

   *y = temp    /* temp 值赋给 y */

}

以上代码执行结果为:

交换前,a 的值 : 100

交换前,b 的值 : 200

交换后,a 的值 : 200

交换后,b 的值 : 100

 

 

9.5 函数用法

函数用法

描述

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

函数定义后可作为另外一个函数的实参数传入

闭包

闭包是匿名函数,可在动态编程中使用

方法

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

9.5.1 函数作为实参

Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数 math.sqrt(),实例为:

实例

package main

import (
   "fmt"
   "math"
)

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

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

}

以上代码执行结果为:

3

 

9.5.2函数闭包

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量,代码如下:

package main

 

import "fmt"

 

func getSequence() func() int {

   i:=0

   return func() int {

      i+=1

     return

   }

}

 

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

1

2

9.5.3 函数方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:

func (variable_name variable_data_type) function_name() [return_type]{

   /* 函数体*/

}

下面定义一个结构体类型和该类型的一个方法:

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

}

以上代码执行结果为:

面积 =  314

 

 

 

十、Go 语言变量作用域

作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。

10.1 变量可声明的地方

Go 语言中变量可以在三个地方声明:

函数内定义的变量称为局部变量

函数外定义的变量称为全局变量

函数定义中的变量称为形式参数

 

10.2 局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

以下实例中 main() 函数使用了局部变量 a, b, c

实例

package main

import "fmt"

func main() {
   /* 声明局部变量 */
   var a, b, c int 

   
/* 初始化参数 */
   a = 10
   b = 20
   c = a + b

   fmt
.Printf ("结果: a = %d, b = %d and c = %d\n", a, b, c)
}

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

结果: a = 10, b = 20 and c = 30

 

10.3 全局变量

在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。

全局变量可以在任何函数中使用,以下实例演示了如何使用全局变量:

实例

package main

import "fmt"

/* 声明全局变量 */
var g int

func main() {

   /* 声明局部变量 */
   var a, b int

   /* 初始化参数 */
   a = 10
   b = 20
   g = a + b

   fmt
.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)
}

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

结果: a = 10, b = 20 and g = 30

 

Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。实例如下:

实例

package main

import "fmt"

/* 声明全局变量 */
var g int = 20

func main() {
   /* 声明局部变量 */
   var g int = 10

   fmt.Printf ("结果: g = %d\n",  g)
}

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

结果: g = 10

10.4 形式参数

形式参数会作为函数的局部变量来使用。实例如下:

实例

package main

import "fmt"

/* 声明全局变量 */
var a int = 20;

func main() {
   /* main 函数中声明局部变量 */
   var a int = 10
   var b int = 20
   var c int = 0

   fmt.Printf("main()函数中 a = %d\n",  a);
   c = sum( a, b);
   fmt.Printf("main()函数中 c = %d\n",  c);
}

/* 函数定义-两数相加 */
func sum(a, b int) int {
   fmt.Printf("sum() 函数中 a = %d\n",  a);
   fmt.Printf("sum() 函数中 b = %d\n",  b);

   return a + b;
}

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

main()函数中 a = 10

sum() 函数中 a = 10

sum() 函数中 b = 20

main()函数中 c = 30

 

10.5初始化局部和全局变量

不同类型的局部和全局变量默认值为:

数据类型

初始化默认值

int

0

float32

0

pointer

nil

 

 

十一、Go 语言数组

11.1 数组的定义

Go 语言提供了数组类型的数据结构。

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。

相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。

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

https://www.runoob.com/wp-content/uploads/2015/06/goarray.png


11.2 声明数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var 变量名 [SIZE大小] 数据类型

以上为一维数组的定义方式。例如以下定义了数组 balance 长度为 10 类型为 float32

例:var balance [10] float32

 

11.3 初始化数组

以下演示了数组初始化:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。

如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:

 var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

该实例与上面的实例是一样的,虽然没有设置数组的大小。

 balance[4] = 50.0

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

https://www.runoob.com/wp-content/uploads/2015/06/array_presentation.jpg


11.4 访问数组元素

数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:

var salary float32 = balance[9]

以上实例读取了数组balance10个元素的值。

以下演示了数组完整操作(声明、赋值、访问)的实例:

实例

package main

import "fmt"

func main() {
   var n [10]int /* n 是一个长度为 10 的数组 */
   var i,int

   /* 为数组 n 初始化元素 */         
   
for i = 0; i < 10; i++ {
      n[i] = i + 100 /* 设置元素为 i + 100 */
   }

   /* 输出每个数组元素的值 */
   for j = 0; j < 10; j++ {
      fmt.Printf("Element[%d] = %d\n", j, n[j] )
   }
}

以上实例执行结果如下:

Element[0] = 100

Element[1] = 101

Element[2] = 102

Element[3] = 103

Element[4] = 104

Element[5] = 105

Element[6] = 106

Element[7] = 107

Element[8] = 108

Element[9] = 109

 

11.5多维数组

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

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

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

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

 

11.5.1二维数组

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

var arrayName [ x ][ y ] variable_type

variable_type Go 语言的数据类型,arrayName 为数组名,二维数组可认为是一个表格,x 为行,y 为列,下图演示了一个二维数组 a 为三行四列:

https://www.runoob.com/wp-content/uploads/2015/06/two_dimensional_arrays.jpg

二维数组中的元素可通过 a[ i ][ j ] 来访问。

 

11.5.2初始化二维数组

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

a = [3][4]int{ 

 {0, 1, 2, 3} ,   /*  第一行索引为 0 */

 {4, 5, 6, 7} ,   /*  第二行索引为 1 */

 {8, 9, 10, 11},   /* 第三行索引为 2 */

}

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

a = [3][4]int{ 

 {0, 1, 2, 3} ,   /*  第一行索引为 0 */

 {4, 5, 6, 7} ,   /*  第二行索引为 1 */

 {8, 9, 10, 11}}   /* 第三行索引为 2 */

 

11.5.3 访问二维数组

二维数组通过指定坐标来访问。如数组中的行索引与列索引,例如:

val := a[2][3]

var value int = a[2][3]

 

以上实例访问了二维数组 val 第三行的第四个元素。

二维数组可以使用循环嵌套来输出元素:

package main

 

import "fmt"

 

func main() {

   /* 数组 - 5 行 2 列*/

   var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}

   var i, j int

 

   /* 输出数组元素 */

   for  i = 0; i < 5; i++ {

      for j = 0; j < 2; j++ {

         fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )

      }

   }

}

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

a[0][0] = 0

a[0][1] = 0

a[1][0] = 1

a[1][1] = 2

a[2][0] = 2

a[2][1] = 4

a[3][0] = 3

a[3][1] = 6

a[4][0] = 4

a[4][1] = 8

 

11.6向函数传递数组

如果想向函数传递数组参数,需要在函数定义时,声明形参为数组,可以通过以下两种方式来声明:

11.6.1方式一

形参设定数组大小:

void myFunction(param [10]int)

{

.

.

.

}

11.6.2方式二

形参未设定数组大小:

void myFunction(param []int)

{

.

.

.

}

实例

让我们看下以下实例,实例中函数接收整型数组参数,另一个参数指定了数组元素的个数,并返回平均值:

func getAverage(arr []int, size int) float32
{
   var i int
   var avg, sum float32  

   for i = 0; i < size; ++{
      sum += arr[i]
   }

   avg = sum / size

   return avg;
}

接下来我们来调用这个函数:

package main

import "fmt"

func main() {
   /* 数组长度为 5 */
   var  balance = []int {1000, 2, 3, 17, 50}
   var avg float32

   /* 数组作为参数传递给函数 */
   avg = getAverage( balance, 5 ) ;

   /* 输出返回的平均值 */
   fmt.Printf( "平均值为: %f ", avg );
}
func getAverage(arr []int, size int) float32 {
   var i,sum int
   var avg float32  

   
for i = 0; i < size;i++ {
      sum += arr[i]
   }

   avg = float32(sum) / float32(size)

   return avg;
}

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

平均值为: 214.399994

以上实例中我们使用的形参并未设定数组大小。

浮点数计算输出有一定的偏差,你也可以转整型来设置精度。

实例

package main
import (
    "fmt"
)
func main() {
    a := 1.69
    b := 1.7
    c := a * b      // 结果应该是2.873
    fmt.Println(c)  // 输出的是2.8729999999999998
}

设置固定精度:

实例

package main
import (
    "fmt"
)
func main() {
    a := 1690           // 表示1.69
    b := 1700           // 表示1.70
    c := a * b          // 结果应该是2873000表示 2.873
    fmt.Println(c)      // 内部编码
    fmt.Println(float64(c) / 1000000) // 显示
}

 

 

十二、Go 语言指针

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

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

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

实例

package main

import "fmt"

func main() {
   var a int = 10   

   fmt
.Printf("变量的地址: %x\n", &a  )
}

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

变量的地址: 20818a220

 

12.1 什么是指针

一个指针变量指向了一个值的内存地址。

指针声明格式如下:

var 指针变量名(var_name) *指针数据类型(var-type)

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:

var ip *int        /* 指向整型*/

var fp *float32    /* 指向浮点型 */

本例中这是一个指向 int float32 的指针。

 

12.2 如何使用指针

12.2.1指针使用流程

定义指针变量。

为指针变量赋值。

访问指针变量中指向地址的值。

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

实例

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

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

a 变量的地址是: 20818a220

ip 变量储存的指针地址: 20818a220

*ip 变量的值: 20

 

12.3 Go空指针

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

nil 指针也称为空指针。

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

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

查看以下实例:

实例

package main

import "fmt"

func main() {
   var  ptr *int

   fmt.Printf("ptr 的值为 : %x\n", ptr  )
}

以上实例输出结果为:

ptr 的值为 : 0

空指针判断:

if(ptr != nil)     /* ptr 不是空指针 */

if(ptr == nil)    /* ptr 是空指针 */

 

 

 

12.4 Go 语言指针数组

了解指针数组前,先看个实例,定义了长度为 3 的整型数组:

package main

 

import "fmt"

 

const MAX int = 3

 

func main() {

 

   a := []int{10,100,200}

   var i int

 

   for i = 0; i < MAX; i++ {

      fmt.Printf("a[%d] = %d\n", i, a[i] )

   }

}

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

a[0] = 10

a[1] = 100

a[2] = 200

有一种情况,可能需要保存数组,这样就需要使用到指针。

以下声明了整型指针数组:

var ptr [MAX]*int;

ptr 为整型指针数组。因此每个元素都指向了一个值。以下实例的三个整数将存储在指针数组中:

package main

 

import "fmt"

 

const MAX int = 3

 

func main() {

   a := []int{10,100,200}

   var i int

   var ptr [MAX]*int;

 

   for  i = 0; i < MAX; i++ {

      ptr[i] = &a[i] /* 整数地址赋值给指针数组 */

   }

 

   for  i = 0; i < MAX; i++ {

      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )

   }

}

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

a[0] = 10

a[1] = 100

a[2] = 200

 

 

12.5 Go 语言指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:

https://www.runoob.com/wp-content/uploads/2015/06/pointer_to_pointer.jpg

12.5.1 指向指针的指针变量声明格式

格式:var ptr **int;

以上指向指针的指针变量为整型。

访问指向指针的指针变量值需要使用两个 * ,如下所示:

package main

 

import "fmt"

 

func main() {

 

   var a int

   var ptr *int

   var pptr **int

 

   a = 3000

 

   /* 指针 ptr 地址 */

   ptr = &a

 

   /* 指向指针 ptr 地址 */

   pptr = &ptr

 

   /* 获取 pptr 的值 */

   fmt.Printf("变量 a = %d\n", a )

   fmt.Printf("指针变量 *ptr = %d\n", *ptr )

   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)

}

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

变量 a = 3000

指针变量 *ptr = 3000

指向指针的指针变量 **pptr = 3000

 

12.6 Go 语言指针作为函数参数

Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。

以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的值

实例

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交换前 a 的值 : %d\n", a )
   fmt.Printf("交换前 b 的值 : %d\n", b )

   /* 调用函数用于交换值
   * &a 指向 a 变量的地址
   * &b 指向 b 变量的地址
   */
   swap(&a, &b);

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

func swap(*int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *= *y      /* y 赋值给 x */
   *= temp    /* temp 赋值给 y */
}

以上实例允许输出结果为:

交换前 a 的值 : 100

交换前 b 的值 : 200

交换后 a 的值 : 200

交换后 b 的值 : 100

 

 

十三、Go 语言结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

 

13.1定义结构体

结构体定义需要使用 type struct 语句。struct 语句定义一个新的数据类型,结构体有中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type 结构体类型 struct {

   member definition;

   member definition;

   ...

   member definition;

}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}

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

实例如下:

实例

package main

import "fmt"

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


func main() {

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

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

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

输出结果为:

{Go 语言 www.fenlin88.com Go 语言小狗 6495407}

{Go 语言 www.fenlin88.com Go 语言小狗 6495407}

{Go 语言 www.fenlin88.com  0}


13.2 访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名"

结构体类型变量使用 struct 关键字定义,实例如下:

实例

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

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

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

 

13.3结构体作为函数参数

你可以像其他数据类型一样将结构体类型作为参数传递给函数。并以以上实例的方式访问结构体变量:

实例

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 信息 */
   printBook(Book1)

   /* 打印 Book2 信息 */
   printBook(Book2)
}

func printBook( book Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}

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

Book title : Go 语言
Book author : www.runoob.com
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.runoob.com
Book subject : Python 语言教程
Book book_id : 6495700

13.4 结构体指针

你可以定义指向结构体的指针类似于其他指针变量,格式如下:

var struct_pointer *Books

以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:

struct_pointer = &Book1;

使用结构体指针访问结构体成员,使用 "." 操作符:

struct_pointer.title;

接下来让我们使用结构体指针重写以上实例,代码如下:

实例

package main

import "fmt"

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

func main() {
   var Book1 Books        /* Declare Book1 of type Book */
   var Book2 Books        /* Declare Book2 of type Book */

   /* 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 信息 */
   printBook(&Book1)

   /* 打印 Book2 信息 */
   printBook(&Book2)
}
func printBook( book *Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}

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

Book title : Go 语言

Book author : www.runoob.com

Book subject : Go 语言小狗

Book book_id : 6495407

Book title : Python 小狗

Book author : www.runoob.com

Book subject : Python 语言小狗

Book book_id : 6495700

 

 

十四、Go 语言切片(Slice)

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

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

 

14.1定义切片

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

var identifier []type

切片不需要说明长度。

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

var slice1 []type = make([]type, len)

 

也可以简写为

 

slice1 := make([]type, len)

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

make([]T, length, capacity)

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

14.2 切片初始化

s :=[] int {1,2,3 }

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

s := arr[:]

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

s := arr[startIndex:endIndex]

arr中从下标startIndexendIndex-1 下的元素创建为一个新的切片

s := arr[startIndex:]

缺省endIndex时将表示一直到arr的最后一个元素

s := arr[:endIndex]

缺省startIndex时将表示从arr的第一个元素开始

s1 := s[startIndex:endIndex]

通过切片s初始化切片s1

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

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

 

14.3 len() cap() 函数

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

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

以下为具体实例:

实例

package main

import "fmt"

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

   printSlice(numbers)
}

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

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

len=3 cap=5 slice=[0 0 0]

 

14.4 (nil)切片

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

实例

package main

import "fmt"

func main() {
   var numbers []int

   printSlice(numbers)

   if(numbers == nil){
      fmt.Printf("切片是空的")
   }
}

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

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

len=0 cap=0 slice=[]

切片是空的


14.5 切片截取

可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:

实例

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([]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]

 

12.4 append() copy() 函数

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

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

实例

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([]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]

 

 

十五、Go 语言范围(Range)

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

实例

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将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
    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)
    }
}

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

sum: 9

index: 1

a -> apple

b -> banana

0 103

1 111

 

十六、Go 语言Map(集合)

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

16.1 定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map nil */

var map_variable map[key_data_type]value_data_type

 

/* 使用 make 函数 */

map_variable := make(map[key_data_type]value_data_type)

如果不初始化 map,那么就会创建一个 nil mapnil map 不能用来存放键值对

实例

package main

import "fmt"

func main() {
    var countryCapitalMap map[string]string /*创建集合 */
    countryCapitalMap = make(map[string]string)

    /* map插入key - value,各个国家对应的首都 */
    countryCapitalMap [ "France" ] = "巴黎"
    countryCapitalMap [ "Italy" ] = "罗马"
    countryCapitalMap [ "Japan" ] = "东京"
    countryCapitalMap [ "India " ] = "新德里"

    /*使用键输出地图值 */ 
    
for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [country])
    }

    /*查看元素在集合中是否存在 */
    capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
    /*fmt.Println(capital) */
    /*fmt.Println(ok) */
    if (ok) {
        fmt.Println("American 的首都是", capital)
    } else {
        fmt.Println("American 的首都不存在")
    }
}

以上实例运行结果为:

France 首都是 巴黎

Italy 首都是 罗马

Japan 首都是 东京

India  首都是 新德里

American 的首都不存在


16.2 delete() 函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。实例如下:

实例

package main

import "fmt"

func main() {
        /* 创建map */
        countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}

        fmt.Println("原始地图")

        /* 打印地图 */
        for country := range countryCapitalMap {
                fmt.Println(country, "首都是", countryCapitalMap [ country ])
        }

        /*删除元素*/ delete(countryCapitalMap, "France")
        fmt.Println("法国条目被删除")

        fmt.Println("删除元素后地图")

        /*打印地图*/
        for country := range countryCapitalMap {
                fmt.Println(country, "首都是", countryCapitalMap [ country ])
        }
}

以上实例运行结果为:

原始地图

India 首都是 New delhi

France 首都是 Paris

Italy 首都是 Rome

Japan 首都是 Tokyo

法国条目被删除

删除元素后地图

Italy 首都是 Rome

Japan 首都是 Tokyo

India 首都是 New delhi

 

 

十七、Go 语言递归函数

递归,就是在运行的过程中调用自己。

17.1 语法格式

语法格式如下:

func recursion() {
   recursion() /* 函数调用自身 */
}

func main() {
   recursion()
}

Go 语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。

递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成斐波那契数列等。

 

17.2 阶乘

以下实例通过 Go 语言的递归函数实例阶乘:

实例

package main

import "fmt"

func Factorial(uint64)(result uint64) {
    if (n > 0) {
        result = n * Factorial(n-1)
        return result
    
}
    return 1
}

func main() {  
    
var i int = 15
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

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

15 的阶乘是 1307674368000

 

17.3 斐波那契数列

以下实例通过 Go 语言的递归函数实现斐波那契数列:

实例

package main

import "fmt"

func fibonacci(int) int {
  if n < 2 {
   return n
  
}
  return fibonacci(n-2) + fibonacci(n-1)
}

func main() {
    var i int
    for i = 0; i < 10; i++ {
       fmt.Printf("%d\t", fibonacci(i))
    }
}

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

0    1    1    2    3    5    8    13    21    34

 

十八、Go 语言类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

18.1 语言转换格式

Go 语言类型转换基本格式如下:

type_name(expression)

type_name 为类型,expression 为表达式。

实例

以下实例中将整型转化为浮点型,并计算结果,将结果赋值给浮点型变量:

package main

import "fmt"

func main() {
   var sum int = 17
   var count int = 5
   var mean float32
   
   mean 
= float32(sum)/float32(count)
   fmt.Printf("mean 的值为: %f\n",mean)
}

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

mean 的值为: 3.400000

 

十九、Go 语言接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

实例

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen 
[return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

实例

package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone 
= new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call()。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhoneIPhone。然后调用call()方法,输出结果如下:

I am Nokia, I can call you!

I am iPhone, I can call you!

 

 

二十、Go 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

error类型是一个接口类型,这是它的定义:

type error interface {

    Error() string

}

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

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

func Sqrt(f float64) (float64, error) {

    if f < 0 {

        return 0, errors.New("math: square root of negative number")

    }

    // 实现

}

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

result, err:= Sqrt(-1)

 

if err != nil {

   fmt.Println(err)

}

实例

package main

import (
    "fmt"
)

// 定义一个 DivideError 结构
type DivideError struct {
    dividee int
    divider int
}

// 实现 `error` 接口
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`

    return fmt.Sprintf(strFormat, de.dividee)
}

// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }
            errorMsg = dData.Error()
            return
    } else {
            return varDividee / varDivider, ""
    }

}

func main() {

    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // 当被除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }

}

执行以上程序,输出结果为:

100/10 =  10

errorMsg is: 

    Cannot proceed, the divider is zero.

    dividee: 100

divider: 0

 

二十一、Go 并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

21.1 语法格式

goroutine 语法格式:

go 函数名( 参数列表 )

例如:

go f(x, y, z)

 

开启一个新的 goroutine:

f(x, y, z)

Go 允许使用 go 语句开启一个新的运行期线程, goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

实例

package main

import (
        "fmt"
        "time"
)

func say(string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("world")
        say("hello")
}

执行以上代码,你会看到输出的 hello world 是没有固定先后顺序。因为它们是两个 goroutine 在执行:

world

hello

hello

world

world

hello

hello

world

world

hello

 

21.2 通道(channel

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // v 发送到通道 ch

v := <-ch  // ch 接收数据

           // 并把值赋给 v

声明一个通道,使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须又接收端相应的接收数据。

以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

实例

package main

import "fmt"

func sum([]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 中接收

        fmt.Println(x, y, x+y)
}

输出结果为:

-5 17 12

21.3通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

实例

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}

执行输出结果为:

1

2

21.4 Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

实例

package main

import (
        "fmt"
)

func fibonacci(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 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

执行输出结果为:

0

1

1

2

3

5

8

13

21

34

 

 

猜你喜欢

转载自blog.csdn.net/fenlin88l/article/details/90409595