Go语言并发编程系列: 第三章:Go语法介绍

本文详细介绍了Go语言的基本语法,包括标识符、保留关键字、变量和常量、运算符、基本数据类型、高级数据类型如数组、切片、字典、函数和方法、接口、结构体,以及流程控制语句如if、switch、for、defer等。通过本章,读者能够掌握Go语言的基础用法,为进行并发编程打下坚实基础。
摘要由CSDN通过智能技术生成

Go语言并发编程系列: 第一章:Go的前世今生
Go语言并发编程系列: 第二章:Go安装部署
Go语言并发编程系列: 第三章:Go语法介绍
Go语言并发编程系列: 第四章:多线程编程概述
Go语言并发编程系列: 第五章:Go的并发原理
Go语言并发编程系列: 第六章:Go锁的应用
Go语言并发编程系列: 第七章:实战-聊天机器人
Go语言并发编程系列: 第八章:实战-Go实现一个软件性能测试工具
Go语言并发编程系列: 第九章:实战-Go实现Concurrent Map
Go语言并发编程系列: 第十章:实战-Go实现网络爬虫


本章我们快速浏览一下Go语言的语法,具体内容如以上目录所示,下面我们一一讲解。

一,基本构成要素

Go语言的语法要素包含5方面内容:标识符,保留关键字,常量和变量量,分隔符和运算符。特别注意:Go语句最后都无需以分号结尾,这一点和Java等之类的面向对象语言有所区别。

1.2,标识符

标识符可以表示程序实体(这里的程序实体包括:变量,常量,函数和类型),即标识符是程序实体的名称。一般情况下,同一个代码块中不允许出现同名的标识符。标识符以英文字符,数字,以及下划线组成,标识符不能以数字或下划线开头。使用不同代码包中的程序实体需要加上代码包标识符,比如:log.myLogger。

此外,Go中还存在一些特殊的标识符,叫作预定义标识符,包括以下几种:

  • 所有基本数据类型名称
  • 接口类型error
  • 常量true, false等

所有的内置函数名称,比如:print, append, cap, close, complex等等。

1.3,保留关键字

关键字是指Go语言保留的一些字符,开发人员不能用他们进行程序实体的声明。Go语言关键字可以分为3类,包括:代码包声明,程序实体声明定义,程序流程控制。详细如下

  • 代码包声明。import 和 package.
  • 程序实体声明定义。chan, const, func, interface, map, struct, type, var .
  • 程序流程控制。go , select , break, case , continue, default , defer , else , fallthrough , for , goto , if , range , return , switch。

Go语言关键字共有25个,其中与并发编程相关的关键字有go, chan , select 。
这里特别说明一下关键字 type 的用途:自定义类型声明。我们可以使用它声明一个自定义数据类型,例如:

type testString string

这里把名为testString的类型声明为基本数据类型string的一个别名类型,反过来将,string类型是testString 类型的潜在类型。虽然类型和别名类型是两种不同的数据类型,但是它们的值可以进行相互类型转换,并且不会产生新的值也没有什么代价,例如:string(testString(“abc”))。

自定义的数据类型一般都会基于Go中一个或多个内置数据类型,就像上面的testString和string一样。

另外,还有一个特殊的类型,叫空接口。它的类型字面量是interface{}, 在Go语言中,任何类型都是空接口类型的实现类型。也就是说空接口类型是所有数据类型的基类型。

1.4,变量和常量

1.4.1,变量

变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。
变量可以通过变量名访问。
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
声明变量的一般形式是使用 var 关键字:

var identifier type

可以一次声明多个变量:

var identifier1, identifier2 type

根据值自行判定变量类型

var identifier1 = 1

省略 var

identifier1 := 1

例如:

var myString string

var myString1 , myString2 string

var myString2 = "abc"

myString3 := "abc"

1.4.2,常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

const identifier [type] = value

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

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

1.5,运算符

运算符用于执行特定算术或者算法或者逻辑操作的符号,操作的对象称为数据。Go语言中主要包括以下几类运算符:

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

1.5.1,算术运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。
在这里插入图片描述

1.5.2,关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。
在这里插入图片描述

1.5.3,逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。
在这里插入图片描述

1.5.4,位运算符

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:
在这里插入图片描述

1.5.5,赋值运算符

在这里插入图片描述

1.5.6,其他运算符

在这里插入图片描述

二,基本数据类型

Go语言基本数据类型包括:

  • 字符串类型。字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。
  • 布尔型。布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
  • 数字类型。Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。

主要数字类型如下:

类型描述
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)
float32IEEE-754 32位浮点型数
float64IEEE-754 64位浮点型数
complex6432 位实数和虚数
complex12864 位实数和虚数
byte类似 uint8
rune类似 int32
uint32 或 64 位
int与 uint 一样大小
uintptr无符号整型,用于存放一个指针

三,高级数据类型

3.1,数组

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

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

3.1.1,声明数组

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

var variable_name[SIZE] variable_type

例如:

var myarray[10] float32

以上,定了一个名称为myarray,长度为10,数据类型为float32的数组。

3.1.2,初始化数组

var myarray= [5]float32{200.0, 20.0, 31.4, 76.0, 42.0}

以上就是数组的初始化方式,其中{}中的元素数量不能大于[]的数字。

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

var myarray= [...]float32{200.0, 20.0, 31.4, 76.0, 42.0}

该实例与上面的实例是一样的,Go语言会根据元素的大小设置数组的大小。

3.1.3,访问数组

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

var myvar  float32 = myarray[3]

以上,读取了数组myarray第4个元素的值。

示例程序 chapter3/array.go , 如下:

package main

import "fmt"

/**
go语言数组
*/
func main() {
	var n [10]int
	var i, j int

	for i = 0; i < 10; i++ {
		n[i] = 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

3.2,切片

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

3.2.1,定义切片

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

var identifier []type

切片不需要说明长度。或使用make()函数来创建切片,如下:

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

也可以简写为:

myslice := make([]type, len)

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

make([]T, len, capacity)

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

3.2.2,初始化切片

直接初始化切片,[]表示是切片类型,初始化值依次是1,2,3,4,5,6。其cap = len = 6

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

初始化切片myslice,是数组myarray的引用

myslice := myarray[:] 

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

myslice := myarray[startIndex : endIndex] 

没有显式指定endIndex 时将表示一直到myarray的最后一个元素

myslice := myarray[startIndex : ] 

没有显式指定 startIndex 时将表示从myarray的第一个元素开始

myslice := myarray[ : endIndex] 

通过切片myslice初始化切片myslice1

myslice1 := myslice[startIndex : endIndex] 

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

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

3.2.3,len()和cap() 函数

切片是可索引的, len() 函数获取切片当前长度。 cap()函数获取切片的最大容量。
示例程序 chapter3/slice.go 如下:

package main

import "fmt"

func main() {
   var myslice = make([]int,5,9)

   printSlice(myslice)
}

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

以上程序运行输出结果为:

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

3.2.4,append()和copy() 函数

如果想增加切片的容量,则必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的程序描述了拷贝切片的 copy函数和向切片追加新元素的 append 函数。
示例程序 chapter3/slice3.go 如下:

package main

import "fmt"

func main() {

	var myslice []int
	printSlice(myslice)
	
	/* 向切片添加一个元素 */
	myslice= append(myslice, 0)
	printSlice(myslice)

	/* 同时添加多个元素 */
	myslice = append(myslice, 1,2,3,4,5,6)
	printSlice(myslice)

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

	/* 拷贝 myslice 的内容到 myslice1 */
	copy(myslice1,myslice)
	printSlice(myslice1)
}

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

以上程序输出:

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

3.2.3,空切片

切片在未初始化之前默认为 nil,长度为 0,示例程序 chapter3/slice1.go,如下所示:

package main

import "fmt"

func main() {
   var myslice []int

   printSlice(myslice)

   if(myslice == nil) {
      fmt.Printf("切片是空的,没有数据")
   }
}

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

以上程序运行结果为:

len=0 cap=0 slice=[]
切片是空的,没有数据

3.2.4,切片截取

可以通过设置下限及上限来设置截取切片,示例程序 chapter3/slice2.go 如下:

package main

import "fmt"

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

	/* 打印子切片从索引1(包含) 到索引4(不包含)*/
	fmt.Println(numbers[1:4]) //从原切片索引为1开始,切出长度为3,容量为8的新切片

	/* 默认下限为 0*/
	fmt.Println(numbers[:3]) //从原切片索引为0开始,切成长度为3,容量为9的新切片

	/* 默认上限为 len(s)*/
	fmt.Println(numbers[4:]) //从原切片索引4开始,切出长度为5,容量为5的新切片
}

func printSlice2(x []int) {
	fmt.Printf("长度=%d,容量=%d,切片值=%v\n", len(x), cap(x), x)
}

以上程序输出:

长度=9,容量=9,切片值=[0 1 2 3 4 5 6 7 8]
[1 2 3]
[0 1 2]
[4 5 6 7 8]

3.3,字典(Map)

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

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

  • 定义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)

示例程序 chapter3/map1.go 如下:

package main

import (
	"fmt"
)

/**
 go语言map应用
*/
func main() {
	var m map[string]string // 定义map
	m = make(map[string]string)

	m["Apple"] = "苹果"
	m["Orange"] = "橙子"
	m["Banana"] = "香蕉"
	m["Grape "] = "葡萄"

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

以上程序输出:

Apple 中文是: 苹果
Orange 中文是: 橙子
Banana 中文是: 香蕉
Grape  中文是: 葡萄
  • delete() 函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。示例程序 chapter3/map2.go 如下:

package main

import "fmt"

/**
 go语言map,delete函数
*/
func main() {
	/* 创建map */
	countryCapitalMap := map[string]string{"China": "Beijing", "Spain": "Madrid", "Canada": "Ottawa", "Russia": "Moscow"}

	fmt.Println("原始地图")

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

	/*删除元素*/
	delete(countryCapitalMap, "Spain")
	fmt.Println("西班牙条目被删除")

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

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

以上程序输出:

原始地图
China 首都是 Beijing
Spain 首都是 Madrid
Canada 首都是 Ottawa
Russia 首都是 Moscow
西班牙条目被删除
删除元素后地图
China 首都是 Beijing
Canada 首都是 Ottawa
Russia 首都是 Moscow

3.4,函数和方法

函数是基本的代码块,用于执行一个任务。Go 语言最少有个 main() 函数。你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。

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

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

3.4.1,函数定义

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

func function_name( [parameter list] ) [return_types] {
   //函数体
}

定义解析:

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
}

3.4.2,函数调用

当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。调用函数,向函数传递参数,并返回值。
示例程序 chapter3/function_call.go 如下:

package main

import "fmt"

func main() {
	/* 定义局部变量 */
	var a int = 200
	var b int = 400
	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()函数,执行结果为:

最大值是 : 400

3.4.3,函数返回多个值

Go 函数可以返回多个值,示例程序 chapter3/ function_return_multi.go 如下:

package main

import "fmt"

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

func main() {
	a, b := swap_a("World", "Hello")
	fmt.Println(a, b)
}

以上程序输出:

Hello World

3.4.4,函数参数的值传递和引用传递

函数如果使用参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。调用函数,可以通过两种方式来传递参数,值传递和引用传递,下面分别详述:

  • 函数值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
示例程序 chapter3/function_param_value.go 如下:

package main

import "fmt"

/**
go语言函数,函数值传递
*/
func main() {
	var a int = 100
	var b int = 200
	fmt.Println("交换前 a= ", a)
	fmt.Println("交换前 b= ", b)
	swap(a, b)

	fmt.Println("交换后 a= ", a)
	fmt.Println("交换后 b= ", 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
  • 函数引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
示例程序 chapter3 / function_param_reference.go, 交换函数 swap() 使用了引用传递, 具体如下:

package main

import "fmt"

/**
 go语言函数
引用传递
注意: 指针地址是不变的,只能交换值
*/
func main() {
	var a int = 400
	var b int = 500

	fmt.Println("交换前 a= ", a)
	fmt.Println("交换前 b= ", b)

	/**
	 * 调用 swap() 函数
	 * &a 指向 a 指针,a 变量的地址
	 * &b 指向 b 指针,b 变量的地址
	 */
	swap1(&a, &b)

	fmt.Println("交换后 a= ", a)
	fmt.Println("交换后 b= ", b)
}

/**
交换值,引用传递
*/
func swap1(x *int, y *int) {
	var temp int
	temp = *x  //保存 x 地址上的值
	*x = *y    //将 y 值赋给 x
	*y = temp  //将 temp 值赋给 y
}

以上程序输出:

交换前 a=  400
交换前 b=  500
交换后 a=  500
交换后 b=  400

3.4.5,函数作为实参

函数定义后可作为另外一个函数的实参数传入。代码示例 chapter3 / function_var.go 如下:

package main

import (
	"fmt"
	"math"
)

/**
go语言函数,函数作为变量
*/
func main() {
	//声明函数作为变量
	getSquareRoot := func(x float64) float64 {
		return math.Sqrt(x)
	}
	/**
	使用函数,我们将函数getSquareRoot作为参数传入到 fmt.Println 中
	 */
	fmt.Println(getSquareRoot(43543.643))
}

3.4.6,函数闭包

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

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

package main

import "fmt"

/**
 go语言函数,匿名函数,闭包
*/
func main() {
	/* nextNumber 为一个函数,初始 i 为 0 */
	nextnumber := getSequence()

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

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

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

以上程序输出结果:

1
2
3
4
5
6
1
2

3.4.7,函数方法

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

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

代码示例chapter3 / function_method.go 定义一个结构体类型和该类型的一个方法,具体如下:

package main

import "fmt"

/**
 go语言函数
函数方法,方法集
*/
func main() {
	var c Circle
	c.radius = 4

	fmt.Println("圆的面积: " , c.getArea())
}

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

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

以上代码输出:

圆的面积:  50.24

3.5,接口

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] {
   /* 方法实现*/
}

接口应用示例 chapter3/ interface.go 如下:

package main

import "fmt"

/**
go语言接口
*/
/**
定义Phone类型接口
*/
type Phone interface {
	call()
}

/**
Huaweiphone类型实现Phone类型接口
*/
type Huaweiphone struct {
}

func (huaweiphone Huaweiphone) call() {
	fmt.Println("I am huawei, I can call you!")
}

/**
IPhone类型实现Phone接口
*/
type IPhone struct {
}

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

func main() {
	var phone Phone

	phone = new(Huaweiphone)
	phone.call()

	phone = new(IPhone)
	phone.call()
}

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

I am huawei, I can call you!
I am iphone, I can call you!

3.6,结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。结构体表示一项记录,比如保存学校里学生的记录,每个学生有以下属性:

  • Name 姓名
  • Age 年龄
  • Sex 性别
  • homeAddress 家庭住址

3.6.1,定义结构体

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

type struct_variable_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}

示例程序 chapter3/ struct_def.go 如下:

package main

import "fmt"

type Students struct {
	name string
	age int
	sex string
	homeAddress string
}

func main() {

	// 创建一个新的结构体
	fmt.Println(Students{"王刚", 14, "男", "解放路21号"})

	// 也可以使用 key => value 格式
	fmt.Println(Students{name: "王玲", age: 15, sex: "女", homeAddress: "人民路20号"})

	// 忽略的字段为 0 或 空
	fmt.Println(Students{name: "陈龙", sex: "男"})
}

以上程序输出结果:

{王刚 14 男 解放路21}
{王玲 15 女 人民路20}
{陈龙 0}

3.6.2,访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:结构体.成员名。
示例程序 chapter3/ struct_call.go 如下:

package main

import "fmt"

type Students struct {
	name string
	age int
	sex string
	homeAddress string
}

func main() {

	var student1 Students        /* 声明 student1 为 Students 类型 */
	var student2 Students        /* 声明 student2 为 Students 类型 */

	/* student1  描述 */
	student1.name = "陈龙"
	student1.age = 21
	student1.sex = "男"
	student1.homeAddress = "解放大街234号"

	/* student2 描述 */
	student2.name = "王萍"
	student2.age = 23
	student2.sex = "女"
	student2.homeAddress = "人民路20号"

	/* 打印 student1 信息 */
	fmt.Printf( "student1 name : %s\n", student1.name)
	fmt.Printf( "student1 age : %d\n", student1.age)
	fmt.Printf( "student1 sex : %s\n", student1.sex)
	fmt.Printf( "student1 homeAddress : %s\n", student1.homeAddress)

	/* 打印 student2 信息 */
	fmt.Printf( "student2 name : %s\n", student2.name)
	fmt.Printf( "student2 age : %d\n", student2.age)
	fmt.Printf( "student2 sex : %s\n", student2.sex)
	fmt.Printf( "student2 homeAddress : %s\n", student2.homeAddress)
}

以上程序输出:

student1 name : 陈龙
student1 age : 21
student1 sex : 男
student1 homeAddress : 解放大街234号
student2 name : 王萍
student2 age : 23
student2 sex : 女
student2 homeAddress : 人民路20

3.6.3,结构体作为函数参数

Go允许我们可以像其他数据类型一样将结构体类型作为参数传递给函数。并以上实例的方式访问结构体变量,示例程序chapter3 /struct_param.go 如下:

package main

import "fmt"

type Students struct {
	name string
	age int
	sex string
	homeAddress string
}

func main() {

	var student1 Students        /* 声明 student1 为 Students 类型 */
	var student2 Students        /* 声明 student2 为 Students 类型 */

	/* student1  描述 */
	student1.name = "陈龙"
	student1.age = 21
	student1.sex = "男"
	student1.homeAddress = "解放大街234号"

	/* student2 描述 */
	student2.name = "王萍"
	student2.age = 23
	student2.sex = "女"
	student2.homeAddress = "人民路20号"

	/* 打印 student1 信息 */
	printStudent(student1)

	/* 打印 student2 信息 */
	printStudent(student2)
}

func printStudent(student Students ) {
	fmt.Printf( "student1 name : %s\n", student.name)
	fmt.Printf( "student1 age : %d\n", student.age)
	fmt.Printf( "student1 sex : %s\n", student.sex)
	fmt.Printf( "student1 homeAddress : %s\n", student.homeAddress)
}

以上程序输出:

student1 name : 陈龙
student1 age : 21
student1 sex : 男
student1 homeAddress : 解放大街234号
student1 name : 王萍
student1 age : 23
student1 sex : 女
student1 homeAddress : 人民路20

3.6.4,结构体指针

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

var struct_pointer *Students 

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

struct_pointer = &Students

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

struct_pointer.name

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

package main

import "fmt"

type Students struct {
	name string
	age int
	sex string
	homeAddress string
}

func main() {

	var student1 Students        /* 声明 student1 为 Students 类型 */
	var student2 Students        /* 声明 student2 为 Students 类型 */

	/* student1  描述 */
	student1.name = "陈龙"
	student1.age = 21
	student1.sex = "男"
	student1.homeAddress = "解放大街234号"

	/* student2 描述 */
	student2.name = "王萍"
	student2.age = 23
	student2.sex = "女"
	student2.homeAddress = "人民路20号"

	/* 打印 student1 信息 */
	printStudent(&student1)

	/* 打印 student2 信息 */
	printStudent(&student2)

}

func printStudent(student *Students ) {
	fmt.Printf( "student1 name : %s\n", student.name)
	fmt.Printf( "student1 age : %d\n", student.age)
	fmt.Printf( "student1 sex : %s\n", student.sex)
	fmt.Printf( "student1 homeAddress : %s\n", student.homeAddress)
}

以上程序输出:

student1 name : 陈龙
student1 age : 21
student1 sex : 男
student1 homeAddress : 解放大街234号
student1 name : 王萍
student1 age : 23
student1 sex : 女
student1 homeAddress : 人民路20

四,流程控制语句

4.1,if语句

if 语句由布尔表达式后紧跟一个或多个语句组成。 if 语句的语法如下:

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

具体示例如下:

package main

import "fmt"

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

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

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

具体示例如下:

package main

import "fmt"

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

4.2,switch语句

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。

switch 语句的语法如下:

switch var1 {
   case val1:
       ...
   case val2:
       ...
   default:
       ...
}

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。Type Switch 语法格式如下:

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

switch用户具体示例代码 chapter3 /switch.go 如下:

package main

import (
	"fmt"
)

/**
 go语言条件语句,switch语句
*/
func main() {
	var grade string = "B"
	var marks int = 60
	switch marks {
	case 90:
		grade = "A"
	case 80:
		grade = "B"
	case 50, 60, 70:
		grade = "C"
	default:
		grade = "D"
	}
	fmt.Println("你的等级是:", grade)

	switch {
	case grade == "A":
		fmt.Println("您的等级是A")
	case grade == "B":
		fmt.Println("您的等级是B")
	case grade == "C":
		fmt.Println("您的等级是C")
	default:
		fmt.Println("您的等级是D")
	}
	/**
	type switch应用
	*/
	var x interface{}
	switch i := x.(type) {
	case nil:
		fmt.Printf("x的类型: %T", i)
	case int:
		fmt.Println("是int类型")
	case float64:
		fmt.Println("是float64类型")
	case bool, string:
		fmt.Println("是bool类型或者string类型")
	default:
		fmt.Println("未知类型")
	}
}

以上代码输出:

你的等级是: C
您的等级是C
x的类型: <nil>

4.3,select语句

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

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

select 语句的语法如下:

select {
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}

select 语句语法详解:

  • 每个 case 都必须是一个通信。
  • 所有 channel 表达式都会被求值。
  • 所有被发送的表达式都会被求值。
  • 如果任意某个通信可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。否则:
    1,如果有 default 子句,则执行该语句。
    2,如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

具体示例代码 chapter3/select.go 如下:

package main

import "fmt"

func main() {
	var c1, c2, c3 chan int
	var i1, i2 int

	select {
	case i1 = <-c1:
		fmt.Printf("接收值:", i1, "from c1\n")
	case c2 <- i2:
		fmt.Printf("写入值:", i2, "向c2\n")
	case i3, ok := (<-c3):
		if ok {
			fmt.Printf("接收值", i3, "from c3\n")
		} else {
			fmt.Printf("c3 is closed\n")
		}
	default:
		fmt.Printf("no communication\n")
	}
}

以上代码输出:

no communication

以上代码提到了通道chan的使用,关于通道的使用我们将在go并发原理章节作详细介绍。

4.4,for和range语句

for 循环是一个循环控制结构,可以执行指定次数的循环。Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。具体语法格式如下:

和 C 语言的 for 一样:

for init; condition; post { }

和 C 的 while 一样:

for condition { }

和 C 的 for(;😉 一样:

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

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

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

示例:计算 1 到 100 的数字之和:

package main

import "fmt"

func main() {
        sum := 0
        for i := 0; i <= 100; i++ {
                sum += i
        }
        fmt.Println(sum)
}

输出结果:5050

init 和 post 参数是可选的,我们可以直接省略它,类似 While 语句。以下实例在 sum 小于 100 的时候计算 sum 自相加后的值:

package main

import "fmt"

func main() {
        sum := 1
        for ; sum <= 100; {
                sum += sum
        }
        fmt.Println(sum)

        // 这样写也可以,更像 While 语句形式
        for sum <= 100{
                sum += sum
        }
        fmt.Println(sum)
}

以上程序输出:

128
128

无限循环,示例:

package main

import "fmt"

func main() {
        sum := 0
        for {
            sum++ // 无限循环下去
        }
        fmt.Println(sum) // 无法输出
}

要停止无限循环,可以在命令窗口按下ctrl-c 。

for语句搭配 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。示例代码 chapter3/ range.go 如下:

package main

import "fmt"

/**
go语言范围(range)
*/
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

4.5,defer语句

defer语句是Go语言特殊的流程控制语句,它用于延迟执行指定的函数,它只能出现在函数内部,由defer关键字以及针对某个函数的调用表达式组成。这里被调用的函数称为延迟函数。简单示例chapter3 / defer.go如下:


package main

import "fmt"

func main() {
	var i int
	i = outerFunction()

	fmt.Printf( "最终返回值 : %d\n", i)
}

func outerFunction() int {

	defer fmt.Println("函数执行结束前一刻才会执行")

	fmt.Println("第一个执行")

	return 1
}

以上代码输出:

第一个执行
函数执行结束前一刻才会执行
最终返回值 : 1

其中,defer关键字后面是针对fmt.Println函数的调用表达式。代码里说明了延迟函数的执行时机。这里的outerFunction 称为外部函数,调用outerFunction的那个函数称为调用函数。具体的规则如下:

  • 当外部函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外部函数才会真正结束执行。
  • 当执行外部函数中的return语句时,只有其中所有的延迟函数都执行完毕后,外部函数才会真正返回。
  • defer语句在外部函数中的位置不限,并且数量不限。

在使用defer语句时还有两点需要注意。

  • 第一点。如果在延迟函数中使用外部变量,应该通过参数传入,示例如下:
package main

import "fmt"

func main() {
	printNumbers()
}

func printNumbers()  {
	for i := 0; i < 5 ; i++ {
		defer func() {
			fmt.Printf("%d", i)
		}()
	}
}

以上代码输出:55555。这正是延迟函数的执行时机引起的。等到5个延迟函数执行时i的值已经是5了。正确的做法是这样:

func printNumbers()  {
	for i := 0; i < 5 ; i++ {
		defer func(n int) {
			fmt.Printf("%d", n)
		}(i)
	}
}

以上程序输出:43210。为什么不是01234,这就涉及到延迟函数要注意的第二点,详细如下:

  • 第二点。同一个外部函数内多个延迟函数调用的执行顺序,会与其所属的defer语句的执行顺序完全相反。在执行每个defer语句时,Go会将该defer语句后的延迟函数压入一个栈。然后在外部函数执行结束的前一刻,Go会从这个栈中依次取出并执行。

五,总结

本章我们快速浏览了绝大部分Go的语法。通过本章我们已经能玩转一般的Go程序,本章讲解的知识属于Go并发编程的基础。下一章我们将利用本章的知识进行一个实战训练:开发一个聊天机器人程序!

关注以下公众号回复 go 获取完整示例源码。
在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值