Go语言概述

1.Go语言简介

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

  • Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

2.Go语言特色

  • 简洁、快速、安全
  • 并行、有趣、开源
  • 内存管理、v数组安全、编译迅速

3.Go 语言用途

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

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

Go语言结构

1.Hello World 实例

(1)Go 语言的基础组成有以下几个部分:

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

(2)接下来让我们来看下简单的代码,该代码输出了”Hello World!”:

1
2
3
4
5
6
7
8
package main

import "fmt"

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

(3)让我们来看下以上程序的各个部分:

  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);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

2.执行 Go 程序

(1)让我们来看下如何编写 Go 代码并执行它。步骤如下:

  • 打开编辑器如Sublime2,将以上代码添加到编辑器中。
  • 将以上代码保存为 hello.go
  • 打开命令行,并进入程序文件保存的目录中。
  • 输入命令 go run hello.go 并按回车执行代码。

(2)如果操作正确你将在屏幕上看到 “Hello World!” 字样的输出。

1
2
$ go run hello.go
Hello, World!

Go语言基础语法

1.行分隔符

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

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

2.注释

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

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

1
2
3
4
5
// 单行注释
/*
Author by w3cschoolW3Cschool教程
我是多行注释
*/

3.关键字

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

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

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

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

Go语言数据类型

1.Go 语言数据类型

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

2.数字类型

  • Go 也有基于架构的类型,例如:int、uint 和 uintptr。
序号 类型和描述
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)
  • 浮点型:
序号 类型和描述
1 float32 IEEE-754 32位浮点型数
2 float64 IEEE-754 64位浮点型数
3 complex64 32 位实数和虚数
4 complex128 64 位实数和虚数

3.其他数字类型

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

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

Go语言变量

1.变量声明

(1)第一种,指定变量类型,先声明后赋值

1
2
//var v_name v_type
//v_name = value

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

1
//var v_name = value

(3)第三种,省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。

1
2
3
4
5
//v_name := value
// 例如
var a int = 10
var b = 10
c : = 10

2.多变量声明

(1)类型相同多个变量, 非全局变量

1
2
3
4
5
6
7
8
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 //出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误


(2)类型不同的多个变量,全局变量

1
2
3
4
var (
vname1 v_type1
vname2 v_type2
)

(3)实例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a int = 12

var b int
b = 13

c := 14

var d, e, f int
d, e, f = 15, 16, 17

g, h, i := 18, 19, “work”

var(
j int
k string
)
j = 21
k = "hello"

3.值类型和引用类型

(1)所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:

img

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:

img

你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。值类型的变量的值存储在栈中。

内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。

(2)更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。

一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。

img

这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。

同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。

当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。

如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

Go语言常量

1.常量的定义格式

1
const identifier [type] = value

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

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

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

1
const c_name1, c_name2 = value1, value2

2.iota

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

(2)在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。

(4)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
)

(5)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

Go语言运算符

Go语言条件语句

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

语句 描述
if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if…else 语句 if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句 你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
switch 语句 switch 语句用于基于不同条件执行不同动作。
select 语句 select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

1.if语句

1
2
3
if 布尔表达式 {   
/* 在布尔表达式为 true 时执行 */
}

2.if…else语句

1
2
3
4
5
if 布尔表达式 {   
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}

3.if嵌套语句

1
2
3
4
5
6
if 布尔表达式 1 {   
/* 在布尔表达式 1 为 true 时执行 */
if 布尔表达式 2 {
/* 在布尔表达式 2 为 true 时执行 */
}
}

4.switch语句

1
2
3
4
5
6
7
8
switch var1 {    
case val1:
...
case val2:
...
default:
...
}

5.select语句

参考:select用法

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

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

(2)语法

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

1
2
3
4
5
6
7
8
9
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或值进行求值。

(3)实例

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

以上代码执行结果为:

1
no communication

GO语言循环语句

1.for循环

(1)和 C 语言的 for 一样:

1
for init; condition; post { }

(2)和 C 的 while 一样:

1
for condition { }

(3)和 C 的 for(;;) 一样:

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

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

1
2
3
for key, value := range oldMap {    
newMap[key] = value
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
}
}

2.循环嵌套

1
2
3
4
5
6
7
8
for [condition |  ( init; condition; increment ) | Range]
{
for [condition | ( init; condition; increment ) | Range]
{
statement(s);
}
statement(s);
}

实例:

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

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

3.循环控制语句

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

控制语句 描述
break 语句 经常用于中断当前 for 循环或跳出 switch 语句
continue 语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。
goto 语句 将控制转移到被标记的语句。

Go语言函数

1.函数定义

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

1
2
3
func function_name( [parameter list] ) [return_types]{
函数体
}

(2)函数定义解析:

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

(3)实例

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

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
}

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
25
26
27
28
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()函数,执行结果为:

1
最大值是 : 200

3.函数返回多个值

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

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

import "fmt"

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

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

以上实例执行结果为:

1
Kumar Mahesh

4.函数参数

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

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

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

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

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

5.函数用法

函数用法 描述
函数作为值 函数定义后可作为值来使用
闭包 闭包是匿名函数,可在动态编程中使用
方法 方法就是一个包含了接受者的函数

(1)函数作为值

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

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
3

(2)闭包

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

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

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

(3)方法

Go语言变量作用域

Go语言数组

1.声明数组

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

1
var variable_name [SIZE] variable_type

以上为一维数组的定义方式。数组长度必须是整数且大于 0。例如以下定义了数组 balance 长度为 10 类型为 float32:

1
var balance [10] float32

2.初始化数组

  • 方式一

    1
    2
    3
    4
    5
    func main(){
    var arr [3]int = [3]int{1,2,3}
    //不允许写成 var arr [3]int = {1,2,3}
    fmt.Println(arr) //[1 2 3]
    }
  • 方式二

    1
    2
    3
    4
    5
    6
    7
    func main(){
    var arr [3]int
    arr[0] = 1
    arr[1] = 2
    arr[2] = 3
    fmt.Println(arr) //[1 2 3]
    }
  • 方式三
    这是方式一的一种简化形式。

    1
    2
    3
    4
    func main(){
    var arr = [3]int{1,2,3}
    fmt.Println(arr) //[1 2 3]
    }
  • 方式四
    方式一的简化形式,也是用得最多的一种方式。

    注意::=符号只允许在函数中使用,即只能在声明局部变量的时候使用,而var没有这个限制。

    1
    func main(){	arr := [3]int{1,2,3}	fmt.Println(arr) //[1 2 3]}
  • 方式五
    让编译器根据初始值的个数自行推断数组的长度。以…来代替[]中的数字,注意若[]中为空则表示为切片。

    1
    2
    3
    4
    func main(){
    var arr = [...]int{1,2,3}
    fmt.Println(arr) //[1 2 3]
    }
  • 方式六
    利用指定索引值的方式来初始化。

    1
    2
    3
    4
    func main(){
    arr := [5]int{0:1,4:3} //例如 0:1 表示数组下标0对应的值为1
    fmt.Println(arr) // [1 0 0 0 3]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var arr[3]int = [3]int{1,2,3}

    var arr1[3]int
    arr1[0] = 1
    arr1[1] = 2
    arr1[2] = 3

    var arr2 = [3]int{1,2,3}

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

    var arr4 = [...]int{1,2,3}

    arr5 := [3]int{0:1,2:3}

Go语言指针

1.什么是指针

(1)一个指针变量可以指向任何一个值的内存地址。

(2)类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

1
var var_name *var-type

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

1
var ip *int        /* 指向整型*/var fp *float32    /* 指向浮点型 */

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

2.如何使用指针

(1)指针使用流程:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
a 变量的地址是: 20818a220ip 变量的存储地址: 20818a220*ip 变量的值: 20

3.Go 空指针

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

​ nil 指针也称为空指针。

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

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

​ 查看以下实例:

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
var ptr *int

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

以上实例输出结果为:

1
ptr 的值为 : 0

(3)空指针判断:

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

4.Go指针更多内容

接下来我们将为大家介绍Go语言中更多的指针应用:

内容 描述
Go 指针数组 你可以定义一个指针数组来存储地址
Go 指向指针的指针 Go 支持指向指针的指针
Go 像函数传递指针参数 通过引用或地址传参,在函数调用时可以改变其值

Go语言结构体

1.定义结构体

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

1
2
3
4
5
6
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}

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

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

2.访问结构体成员

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

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

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.w3cschool.cn"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.w3cschool.cn"
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.w3cschool.cn
Book 1 subject : Go 语言教程
Book 1 book_id : 6495407
Book 2 title : Python 教程
Book 2 author : www.w3cschool.cn
Book 2 subject : Python 语言教程
Book 2 book_id : 6495700

3.结构体作为函数参数

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

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
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.w3cschool.cn"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.w3cschool.cn"
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);
}

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

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

4.结构体指针

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

1
var struct_pointer *Books

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

1
struct_pointer = &Book1;

(3)使用结构体指针访问结构体成员,使用 “.” 操作符:

1
struct_pointer.title;

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

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 /* Declare Book1 of type Book */
var Book2 Books /* Declare Book2 of type Book */

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

/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.w3cschool.cn"
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);
}

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

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

Go语言切片(Slice)

1.定义切片

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

1
var slice []type

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

1
2
3
4
5
var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)

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

1
make([]type, length, capacity)

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

2.切片初始化

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

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

1
s := arr[:] 

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

1
s := arr[startIndex:endIndex] 

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

1
s := arr[startIndex:] 

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

1
s := arr[:endIndex] 

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

1
s1 := s[startIndex:endIndex] 

(6)通过切片s初始化切片s1

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

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

3.len() 和 cap() 函数

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

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

(3)以下为具体实例:

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]

4.空(nil)切片

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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=[]
切片是空的

5.切片截取

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

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]

6.append() 和 copy() 函数

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

(2)下面的代码描述了从拷贝切片的 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
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)
}

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

1
2
3
4
5
len=0 cap=0 slice=[]
len=1 cap=2 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=8 slice=[0 1 2 3 4]
len=5 cap=16 slice=[0 1 2 3 4]

Go语言范围(Range)

1.Go 语言范围

(1)Go 语言中 range 关键字用于for循环中迭代数组(array)、切片(slice)、链表(channel)或集合(map)的元素。

(2)在数组和切片中它返回元素的索引值,在集合中返回 key-value 对的 key 值,对于映射,它返回下一个键值对的键。

(2)Range返回一个值或两个值。如果在Range表达式的左侧只使用了一个值,则该值是下表中的第一个值。

Range表达式 第一个值 第二个值[可选的]
Array 或者 slice a [n]E 索引 i int a[i] E
String s string type 索引 i int rune int
map m map[K]V 键 k K 值 m[k] V
channel c chan E 元素 e E none

(4)实例

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
package main
import "fmt"
func main() {
//(1)这是我们使用range去求一个slice的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)

//(2)在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}

//(3)range也可以用在map的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}

//(4)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

Go语言Map(集合)

1.定义 Map

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

1
2
3
4
5
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable = make(map[key_data_type]value_data_type)

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

(3)实例

下面实例演示了创建和使用map:

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

import "fmt"

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

/* map 插入 key-value 对,各个国家对应的首都 */
countryCapitalMap["France"] = "Paris"
countryCapitalMap["Italy"] = "Rome"
countryCapitalMap["Japan"] = "Tokyo"
countryCapitalMap["India"] = "New Delhi"

/* 使用 key 输出 map 值 */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}

/* 查看元素在集合中是否存在 */
captial, ok := countryCapitalMap["United States"]
/* 如果 ok 是 true, 则存在,否则不存在 */
if(ok){
fmt.Println("Capital of United States is", captial)
}else {
fmt.Println("Capital of United States is not present")
}
}

以上实例运行结果为:

1
2
3
4
5
Capital of France is Paris
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of India is New Delhi
Capital of United States is not present

2.delete() 函数

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

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 main() {
/* 创建 map */
countryCapitalMap := map[string] string {"France":"Paris","Italy":"Rome","Japan":"Tokyo","India":"New Delhi"}

fmt.Println("原始 map")

/* 打印 map */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}

/* 删除元素 */
delete(countryCapitalMap,"France");
fmt.Println("Entry for France is deleted")

fmt.Println("删除元素后 map")

/* 打印 map */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}
}

以上实例运行结果为:

1
原始 mapCapital of France is ParisCapital of Italy is RomeCapital of Japan is TokyoCapital of India is New DelhiEntry for France is deleted删除元素后 mapCapital of Italy is RomeCapital of Japan is TokyoCapital of India is New Delhi

Go语言递归函数

1.Go 语言递归函数

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

语法格式如下:

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

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

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

2.阶乘

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

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

import "fmt"

func Factorial(x int) (result int) {
if x == 0 {
result = 1;
} else {
result = x * Factorial(x - 1);
}
return;
}

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

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

1
15 的阶乘是 1307674368000

3.斐波那契数列

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

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

import "fmt"

func fibonacci(n 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))
}
}

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

1
0   1   1   2   3   5   8   13  21  34

Go语言类型转换

1.Go 语言类型转换

(1)类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:

1
type_name(expression)

type_name 为类型,expression 为表达式。

(2)实例

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

1
2
3
4
5
6
7
8
9
10
11
12
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)
}

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

1
mean 的值为: 3.400000

2.go 不支持隐式转换类型

示例:

1
2
3
4
5
6
7
8
9
package main
import "fmt"

func main() {
var a int64 = 3
var b int32
b = a
fmt.Printf("b 为 : %d", b)
}

此时会报错

1
2
cannot use a (type int64) as type int32 in assignment
cannot use b (type int32) as type string in argument to fmt.Printf

但是如果改成 b = int32(a) 就不会报错了:

1
2
3
4
5
6
7
8
9
package main
import "fmt"

func main() {
var a int64 = 3
var b int32
b = int32(a)
fmt.Printf("b 为 : %d", b)
}

GO语言接口

1.Go 语言接口

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

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 定义接口 */
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] {
/* 方法实现*/
}

实例

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
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类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法,输出结果如下:

1
2
I am Nokia, I can call you!
I am iPhone, I can call you!

(2)

GO语言错误处理

1.Go 错误处理

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

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

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

(2)我们可以在编码中通过实现 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")
}
// 实现
}

(3)在下面的例子中,我们在调用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)
}

实例

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

}

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

1
2
3
4
5
100/10 =  10
errorMsg is:
Cannot proceed, the divider is zero.
dividee: 100
divider: 0

(4)