GO语言基础语法探究:简洁高效的编程之道

文章目录

  • 前言
  • Go词法单元
    • token
    • 标识符
      • 关键字( 25个 )
      • 内置数据类型标识符( 20个 )
      • 内置函数( 15个 )
      • 常量值标识符( 4个)
      • 空白标识符( 1个 )
    • 操作符和分隔符
    • 字面常量
  • 变量和常量
    • 变量
    • 常量
  • 基本数据类型
    • 布尔类型
    • 整型
    • 浮点型
    • 复数类型
    • 字符串
    • rune类型
  • 复合数据类型
    • 指针
    • 数组
      • 数组初始化
      • 数组的特点
      • 数组相关操作
    • 切片
      • 切片的创建
      • 切片支持的操作
      • 字符串和切片的相关转换
    • map
      • map的创建
      • map支持的操作
    • struct
      • struct类型字面量
      • 自定义struct类型
      • struct类型变量的初始化
    • 其他复合类型
  • 控制结构
    • if 语句
    • switch 语句
    • for 语句
    • 标签和跳转
      • 标签
      • goto
      • break
      • continue
      • return 和函数调用
  • 总结

前言

GO语言是由Google开发的一门开源编程语言,它的设计目标是简洁高效。GO语言作为一门静态类型、编译型语言,兼具了高效的执行速度和易于开发维护的特性。在本篇博客中,我们将深入探讨GO语言的基础语法,重点突出其独特之处以及如何利用这些特性编写优雅的代码。

Go词法单元

在介绍 Go 语言具体语法之前,先介绍一下现代高级语言的源程序内部的几个概念: token 、
关键字、标识符、操作符、 分隔符和字面量。这种语言层面通用的概念非常重要,这些概念能够帮助程序员更好地掌握语言的语法结构 。

token

token 是构成源程序的基本不可再分割的单元。编译器编译源程序的第一步就是将源程序分割为一个个独立的 token,这个过程就是词法分析。 Go 语言的 token 可以分为关键字、标识符 、操作符、分隔符和字面常量等。
token的分类
Go 语言里面的 token 是怎么分割的昵? Go 的 token 分隔符有两类 : 一类是操作符,还有一类自身没有特殊含义,仅用来分隔其他 token ,被称为纯分隔符。

  • 操作符: 操作符就是一个天然的分隔符,同时其自身也是一个 token, 语句如下所示 。

sum := a + b

“:=”和“+”既是分隔符,也是token,所有这个简单的语句被分割为5个token:“sum”、“:=”、“a”、“+”、“b”。

  • 纯分隔符: 其本身不具备任何语法含义,只作为其他 token 的分割功能。包括空格、制表符、换行符和回车符 , 多个相邻的空格或者制表符会被编译器看作分隔符处理,比如如下语句

package main

这是一个包声明的语句, package 和 main 之间可以有任意多个空格或者制表符 , Go 编译器会将其作为一个分隔符处理,最后分离出来两个token:package 和 main 。

标识符

编程语言的标识符用来标识变量、类型 、 常量等语法对象的符号名称,其在语法分析时作为一个 token 存在 。编程语言的标识符总体上分为两类 : 一类是语言设计者预留的标识符, 一类是编程者可以自定义的标识符。 前者一般由语言设计者确定 , 包括语言的预声明标识符及用于后续言扩展的保留字;后者是用户在编程过程中自行定义的变量名、常量名、函数名等一切符合语言规范的标识符。
Go 的标识符构成规则是: 开头一个字符必须是字母或下划线,后面跟任意多个字符、数字或下划线 , 并且区分大小写, Unicode 字符也可以作为标识符的构成,但是一般不推荐这么使用。我们在定义新的标识符时要避开 Go 语言预声明标识符,以免引起混乱。

9aa //这不是一个合法标识符,不是以字母或下画线开头
-aa //这不是一个合法标识符,不是以字母或下画线开头
aa //这是一个合法标识符
_aa //这是一个合法标识符
aa911 //这是一个合法标识符
_aa911 //这是一个合法标识符

Go 语言预声明的标识符包括关键字、内置数据类型标识符、常量值标识符 、 内置函数和空白标识符。在写 Go 源程序的过程中,用户自定义标识符用在包名、函数名 、 自定义类型名、变量名和常量名等上。

关键字( 25个 )

编程语言里面的关键字是指语言设计者保留的有特定语法含义的标识符,这些关键宇有自己独特的用途和语法含义,它们一般用来控制程序结构,每个关键字都代表不同语义的语法糖 。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

内置数据类型标识符( 20个 )

丰富的内置类型支持是高级语言的基本特性,基本类型也是构造用户自定义类型的基础。为了标识每种内置数据类型,Go 定义了一套预声明标识符,这些标识符用在变量或常量声明时。Go 语言内置了 20 个预声明数据类型标识符。

  • 数值( 16 个)
    • 整型( 12 个)
      byte int int8 int16 int32 int64
      uint unint8 uint16 uint32 uint64 uintprt
    • 浮点型( 2 个)
      float32 float64
    • 复数型( 2 个)
      complex64 complex128
  • 字符和字符串型( 2 个)
    string rune
  • 接口型( 1 个)
    error
  • 布尔型( 1 个)
    bool

Go 是一种强类型静态编译型语言,在定义变量和常量时需要显式地指出数据类型,当然Go 也支持自动类型推导,在声明初始化内置类型变量时, Go 可以自动地进行类型推导。但是在定义新类型或函数时,必须显式地带上类型标识符 。

内置函数( 15个 )

make new len cap append copy delete panic recover close complex real image
Print Println

内置函数也是高级语言的一种语法糖,由于其是语言内置的,不需要用 import 引入,内置
函数具有全局可见性 。 注意到其都是以小写字母开头的,但是并不影响其全局可用性 。

常量值标识符( 4个)

true false //true 和 false 表示 bool 类型的两常量值:具和假
iota //用在连续的枚举类型的声明中
nil //指针/引用型的变量的默认值就是 nil

Go 的常量值标识符代表的是一个常量值,这个常量值表达特殊的含义,不好使用常量字面量直接表述时,就使用 一个预先声明的标识符代替。

空白标识符( 1个 )

_

空白标识符有特殊的含义,用来声明-个匿名的变量,该变量在赋值表达式的左端,空白标识符引用通常被用作占位,比如忽略函数多个返回值中的一个和强制编译器做类型检查。

操作符和分隔符

操作符就是语言所使用的符号集合, 包括运算符、显式的分隔符,以及其他语法辅助符号。操作符不但自身是一个 token,具备语法含义,同时其自身也是分隔其他 token 的分隔符。另外还有一类分隔符本身没有什么含义,仅仅起到分隔 token 的功能,这里纯粹的分隔符有 4 个:空格、制表符、回车和换行。

分隔符可以细分如下几类。

  • 算术运算符( 5 个 )
    算术计算顺序是按照优先级从左到右进行的, 当然也可以使用括号来改变操作数的结合顺序。

+ - * / %

  • 位运算符( 6 个 )

& | ^ &^ >> <<

  • 赋值和赋值复核运算符 ( 13 个 )

:= = += -= *= /= %= &= |= ^= &^= >>= <<=

  • 比较运算符( 6 个 )

> >= < <= == !=

  • 括号( 6 个 )

( ) { } [ ]

  • 逻辑运算符( 3 个 )

&& || !

  • 自增自减操作符( 2 个)

++ --

注意:Go语言里面自增、自减操作符是语句而不是表达式

  • 其他运算符( 6 个 )

: , ; . ... <-

字面常量

编程语言源程序中表示固定值的符号叫作字面常量,简称字面量。 一般使用裸字符序列来表示不同类型的值。字面量可以被编程语言编译器直接转换为某个类型的值。 Go 的字面量可以出现在两个地方:一是用于常量和变量的初始化, 二是用在表达式里或作为函数调用实参。变量初始化语句中如果没有显式地指定变量类型 ,则 Go 编译器会结合字面量的值自动进行类型推断。 Go 中的字面量只能表达基本类型的值, Go 不支持用户定义的字面量。

字面量有如下几类:

  • 整型字面量
    整型字面量使用特定字符序列来表示具体的整型数值,常用于整型变量或常量的初始化。
  • 浮点型字面量
    浮点型字面量使用特定字符序列来表示一个浮点数值。它支持两种格式 : 一种是标准的数学记录法,比如0.1; 另一种是科学计数法的表示,比如 1E6。
  • 复数类型字面量
    复数类型字面量使用特定的字符序列来表示复数类型的常量值。
  • 字符型字面量
    Go 的源码采用的是 UTF心的编码方式, UTF- 8 的宇符占用的字节数可以有 l ~4 个字节,
    Rune 宇符常量也有多种表现形式,但是使用“ ’ ’ ”(单引号)将其括住。
  • 字符串字面量
    字符串字面量的基本表现形式就是使用“ " " ”将字符序列包括在内,双引号里面可以是UTF- 8 的字符字面量 ,也可以是其编码值 。

变量和常量

高级语言通过一个标识符来绑定一块特定的内存,后续对特定的内存的操作都可以使用该标识符来代替。这类绑定某个存储单元的标识符又可以分为两类,一类称之为“变量”,一类称之为“常量”。顾名思义,变量表示指向的内存可以被修改,常量表示指向的内存不能被修改。

变量

变量:使用一个名称来绑定一块内存地址 , 该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址里面存放的内容可以改变。

Go 的基本类型变量声明有两种。

1. 显式的完整声明

var varName dataType [ = value]
  • 关键字var用于变量声明
  • varName是变量名标识符
  • dataType是基本类型
  • value是变量的初始值,初始值可以是字面量,也可以是其他变量名,还可以是一个表达式;如果不指定初始值,则Go默认将该变量初始化为类型的零值。
  • Go的变量声明后就会立即为其分配空间。
var a int = 1
var a int = 2*3
var a int = b

2.短类型声明

varName := value
  • := 声明只能出现在函数内(包括在方法内)。
  • 此时Go编译器自动进行数据类型推断。

Go支持多个类型变量同时声明并赋值。例如:

a,b := 1,"hello"

变量具有如下几个属性。

  • 变量名
    Go 中使用自定义标识符来声明一个变量。
  • 变量值
    变量实际指向的是地址里存放的值,变量的值具体怎么解析是由变量的类型来决定的。在初始化变量值时,我们可以使用字面量,也可以使用其他的变量名。
  • 变量存储和生存期
    Go 语言提供自动内存管理,通常程序员不需要特别关注变量的生存期和存放位置 。编译器使用栈逃逸技术能够自动为变量分配空间 : 可能在栈上,也可能在堆上 。
  • 类型信息
    类型决定了该变量存储的值怎么解析,以及支持哪些操作和运算,不同类型的变量支持的操作和运算集是不一样的。
  • 可见性和作用域
    Go 内部使用统一的命名空间对变量进行管理,每个变量都有一个唯一 的名字,包名是这个名字的前缀。

常量

常量使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义常量时指定的类型决定,而且该内存地址里面存放的内容不可以改变 。 Go 中常量分为布尔型、宇符串型和数值型常量。常量存储在程序的只读段里( .rodata section ) 。

预声明标识符 iota 用在常量声明中,其初始值为 0。一组多个常量同时声明时其值逐行增加, iota 可以看作自增的枚举变量,专 门用来初始化常量。

// 类似枚举的iota
	const (
		c0 = iota 	// c0 == 0
		c1 = iota 	// c1 == 1
		c2 = iota 	// c2 == 2
	)
	// 简写模式
	const (
		a = iota // a == 0
		b        // b == 1
		c        // c == 2
	)
	// 注意iota 逐行递增
	const (
		// << 右移多少就是乘多少个2
		d = 1 << iota 	// d == 1 (iota == 0)
		e = 1 << iota   // e == 2 (iota == 1)
		f = 3 			// f == 3 (iota == 2, unused)
		g = 1 << iota   // g == 8 (iota == 3)
	)
	const (
		u = iota * 42 			// u == 0     (untyped integer constant)
		v float64 = iota * 42	// v == 42.0  (float64 constant)
		w = iota * 42 			// w == 84    (untyped integer constant)
	)
	
	// 分开的const语句,iota计数会被重置为0
	const x = iota 		// x == 0
	const y = iota 		// y == 0

基本数据类型

Go 是一种强类型的静态编译语言,类型是高级语言的基础,有了类型,高级语言才能对不同类型抽象出不同的运算,编程者才能在更高的抽象层次上操纵数据,而不用关注具体存储和运算细节。

  • Golang 是强类型语言
  • 在赋值过程中, 类型必须保持一致
  • 变量必须先定义后使用, 且必须被用到
  • Golang 会为每个变量设置默认值
  • 变量不能重名
  • Golang 会根据值类型做变量类型推断

Go 语言内置七类基本数据类型( 20 个具体子类型)。

布尔类型: bool
整型 : byte int int8 intl6 init32 int64 uint uint8 uintl6 uint32 uint64 uintptr
浮点型 : float32 float64
复数: comlex64 complexl28
字符 : rune
字符串: string
错误类型:error

布尔类型

布尔类型关键字是 bool,布尔类型只有两个值: true 和 fasle,阳e 和 fals巳 是 Go 内置的两
个预声明标识符 。

var ok bool
ok = true

ok := false

布尔型数据和整型数据不能进行相互转换。

var a bool
a= 1 //error 1是整型字面量

比较表达式和逻辑表达式的结果都是布尔类型数据 。

var b bool = (x > y) && (x >0)

if 和 for i吾句的条件部分一定是布尔类型的值或表达式 。

if a <= b {
		print(b)
else {
		print(a)
		}
for ; true ; { //等价于 C 语言的 while (1)
}

声明的布尔型变量如不指定初始化值,则默认为 false 。

var b bool   // b is fals e

整型

Go 语言内置了12种整数类型,分别是 byte、int 、int8、int16、init32、int64、uint、uint8、uintl6、uint32、uint 64、uintptr。其 中 byte 是 uint8 的别名,不同类型的整型必须进行强制类型转换。

var a int = 1
var b int32 = 2
b = a 			 //error

整型支持算术运算和位操作,算术表达式和位操作表达式的结果还是整型。

var a int = (1+2)*3
var b int = 1000>>2

浮点型

浮点型用于表示包含小数点的数据 , Go 语言内置两种浮点数类型,分别是 float32 和 float64 。浮点数有两个注意事项:

  1. 浮点数字面量被自动类型推断为 float64 类型。
  2. 计算机很难进行浮点数的精确表示和存储 , 因此两个浮点数之间不应该使用=或 != 进行比较操作,高精度科学计算应该使用 math 标准库 。

复数类型

Go 语言内置的复数类型有两种,分别是 complex64 和 complex l28,复数在计算机里面使用两个浮点数表示 , 一个表示实部, 一个表示虚部。 complex64 是由两个 float32 构成的, complex l28是 由两个 float64 构成的。复数的字面量表示和数学表示法一样。

var value1 complex64 = 3.2 + 12i
value2 := 3.1 + 6i

//Go有三个内置函数处理复数
//complex、real和imag,分别返回复数的复数、实部和虚部
fmt.Println(complex(3, 2)) // "3+2i"
fmt.Println(real(3 + 2i))  // "3"	
fmt.Println(imag(3 + 2i))  // "2"

字符串

Go 语言将字符串作为一种原生的基本数据类型, 字符串的初始化可以使用字符串字面量。
例如 :

var a = "hello,world"
  1. 字符串是常量,可以通过类似数组 的索引访问其字节单元,但是不能修改某个字节的值。
    例如 :
var a = "hello,world "
b := a[0]
a[1] ='a' //error
  1. 宇符串转换为切片 []byte(s) 要慎用,尤其是当数据量较大时(每转换一次都需复制内容)。
    例如:
a := "hello,world!"
b : = []byte(a)
  1. 字符串尾部不包含 NULL 字符,这一点和 C/C++不一样。

  2. 字符串类型底层实现是一个二元的数据结构,一个是指针指 向字节数组的起点,另 一个是长度 。
    在这里插入图片描述

  3. 基于字符串创建的切片和原字符串指向相同的底层字符数组 , 一样不能修改 , 对字符串的切片操作返回的子串仍然是string,而非 slice。
    例如 :

a := "hello,world!"
b := a[0 : 4]
c := a[1 : ]
d := a[ : 4)
  1. 字符串和切片的转换: 字符串可以转换为字节数组 ,也可以转换为 Unicode 的字数组。
    例如 :
a := "hello,世界!"
b := []byte(a)
c := []rune(a)
  1. 字符串的运算。
    例如 :
a := "hello"
b := "world"

// 字符串拼接
c := a + b
println(c)
e := len(a) // 字符串长度
println(e)

d := "hello 世界!"
for i := 0; i < len(d); i++ { // 遍历字节数组
	fmt.Println(d[i])
}

for i, ch := range d { // 遍历rune字符数组
	fmt.Println(i, ch)
}

rune类型

Go 内置两种字符类型 : 一种是 byte 的字节类类型( byte 是 uint 的别名),另一种是表示Unicode 编码的字符 rune。 rune 在 Go 内部是 int32 类型的别名,占用 4 个字节。 Go 语言默认的字符编码就是 UTF-8 类型的,如果需要特殊的编码转换,则使用 Unicode/UTF-8 标准包。

复合数据类型

复合数据类型就是由其他类型组合而成的类型。 Go 语言基本的复合数据类型有指针、数组、切片、字典( map )、通道、结构和接口,它们的字面量格式如下 :

* pointerType		//指针类型使用*后面跟其指向的类型名
[n] elementType		//数组类型使用[n]后面跟数组元素类型来表示,n表示该数组的长度
[] elementType		//切片类型使用[]后面跟切片元素类型来表示
map [keyType]valueType	//map 类型使用 map[键类型]值类型来表示
chan valueType		//通道使用 chan 后面跟通道元素类型来表示

struct {			//结构类型使用 struct{}将各个结构字段扩起来表示
	feildType feildType
	feildType feildYype
	···
}

interface {			//接口类型使用 interface{}将各个方法括起来表示
	method1(inputParams)(returnParams)
	method1(inputParams)(returnParams)
	···
}

指针

Go 语言支持指针,指针的声明类型为*T, Go 同样支持多级指针**T 。通过在变量名前加&来获取变量的地址。指针的特点如下。

  1. 在赋值语句中,*T 出现在 “=” 左边表示指针声明,*T 出现在 “=” 右边表示取指针指向的值( varName 为变量名)。示例如下 :
var a = 11
p : = &a	// *p 和 a 的值都是 11
  1. 结构体指针访问结构体字段仍然使用“ . ”点操作符, Go 语言没有“ ->”操作符 。 例如 :
type User struct {
	name string
	age  int
}
andes := User{
	name : "andes",
	age : 18,
}
p := &andes
fmt.Println(p.name) //通过“.”操作符来访问结构体的字段
  1. Go不支持指针的运算
    Go 由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在 C和 C++里面指针运算很容易出现问题 , 因此 Go 直接在语言层面禁止指针运算。
a := 1234
p := &a
p++		//不允许,报non-numeric type *int 错误
  1. 函数中允许返回局部变量的地址 。
    Go 编译器使用“栈逃逸 ” 机制将这种局部变量的空间分配在堆上。 例如:
func sum(a, b int) *int {
	sum := a + b
	return &sum		//允许,sum 会分配在heap上
}

数组

数组的类型名是[n]elemetType,其中n是数组长度,elementType是数组元素类型。比如一个包含2个int类型元素的数组类型可表示为[2]int。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。

数组初始化

var arr [2]int	//声明一个有两个整型的数组,但元素默认值都是0,一很少这样使用
array := [...]int{1,2,3}	//不指定长度,但是由后面的初始化列表数量来确定其长度
a := [3]int{1:1, 2:3}		//指定总长度,并通过索引值进行初始化,没有初始化元素时使用类型默认值
a := [...]int{1:1, 2:3}		//不指定总长度,通过索引值进行初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值。

数组的特点

  • 数组创建完长度就固定了,不可以再追加元素。
  • 数组是值类型的,数组赋值或作为函数参数都是值拷贝。
  • 数组长度是数组类型的组成部分,[10]int 和 [20]int 表示不同的类型。
  • 可以根据数组创建切片。

数组相关操作

  1. 数组元素访问
a := [...]int{1,2,3}
b := a[0]
for i,v := range a {

}
  1. 数组长度
a := [...]int{1,2,3}
alengh := len(a)
for i := O; i < alengh ; i++ {

}

切片

Go 语言的数组的定长性和值拷贝限制了其使用场景, Go 提供了另一种数据类型 slice (切片),这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型 。
在这里插入图片描述
Go 为切片维护三个元素一一指向底层数组的指针、切片的元素数量和底层数组的容量。
在这里插入图片描述

切片的创建

  • 由数组创建
    创建语法如下:array[b:e],其中,array表示开始索引,可以不指定,默认是0;e表示结束索引,可以不指定,默认是len(array)。array[b:e]表示创建一个包含e-b个元素的切片,第一个元素是array[b],最后一个元素是array[e-1]。例如:
var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //创建有10个int类型的数组
var slice = array[2:5] //创建一个slice,从array[2]开始,到array[5]结束,但不包含array[5]
//slice的长度是3,容量是8
//slice的容量是从它的第一个元素开始,到其底层数组元素末尾的个数
var slice1 = array[2:5:7] 
//slice的长度是3,容量是5
var slice2 = array[2:] 
//slice的长度是8,容量是8
var slice3 = array[:5]
//slice的长度是5,容量是10
var slice4 = array[:]
//slice的长度是10,容量是10
fmt.Println(slice) //输出[3 4 5]
fmt.Println(slice1) //输出[3 4 5]
fmt.Println(slice2) //输出[3 4 5 6 7 8 9 10]
fmt.Println(slice3) //输出[1 2 3 4 5]
fmt.Println(slice4) //输出[1 2 3 4 5 6 7 8 9 10]
  • 通过内置函数make创建切片
    注意:由make创建的切片各元素被默认初始化为切片元素类型的零值。例如:
//len = 10, cap = 10
a := make([]int, 10)

//len = 5, cap = 10
b := make([]int, 5, 10)

fmt.Println(a) //输出[0 0 0 0 0 0 0 0 0 0]
fmt.Println(b) //输出[0 0 0 0 0]

// 直接声明切片类型变量是没有意义的
var c []int
fmt.Println(c) //输出[]

切片支持的操作

  • 内置函数len()返回切片长度
  • 内置函数cap()返回切片底层数组容量
  • 内置函数append()对切片追加元素
  • 内置函数copy()用于复制一个切片
a := [...]int{0,1,2,3,4,5,6}
b := make([]int,2,3)
c := a[2:5]

fmt.Println(len(b)) //2
fmt.Println(cap(b)) //3
b = append(b, 1)
fmt.Println(b)      //[0 0 1]
fmt.Println(len(b)) //3
fmt.Println(cap(b)) //3 
b = append(b, c...)
fmt.Println(b)      //[0 0 1 2 3 4]
fmt.Println(len(b)) //6
fmt.Println(cap(b)) //6 //容量不够,扩容成原来的2倍,底层的数组也会变成原来的2倍

d := make([]int, 2, 2)
copy(d,c) //copy函数,将c中的元素复制到d中,只会复制长度较小的那个slice的长度
fmt.Println(d)      //[2 3]
fmt.Println(len(d)) //2
fmt.Println(cap(d)) //2

字符串和切片的相关转换

str := "hello,世界" //通过字符串字面量初始化一个字符串str
a := []byte(str)	//将字符串转换为[]byte类型切片
b := []rune(str)	//将字符串转换为[]rune类型切片

map

Go 语言内置的字典类型叫 map。map 的类型格式是: map[K]T ,其中 K 可 以是任意可以进
行比较的类型, T 是值类型。 map 也是一种引用类型 。

map的创建

  • 使用字面量创建
	map1 := map[string]int{"a":1, "b":2, "c":3}
	fmt.Println(map1["a"])	//输出1
	fmt.Println(map1["b"])	//输出2
  • 使用内置的make函数创建
make(map[K]T) //map 的容量使用默认位
make(map[K]T,len) //map 的容量使用给定的 len 值

map2 := make(map[int]string)
map2[1] = "a"
map2[2] = "b"
map2[3] = "c"

fmt.Println(map2[1]) //a
fmt.Println(map2[2]) //b

map支持的操作

  • map 的单个键值访问格式为 mapName[key], 更新某个 key 的值时 mapName[key]放到等号左边,访 问某个 key 的值时 mapName[key]放在等号的右边。
  • 可以使用 range 遍历一个 map 类型变量,但是不保证每次选代元素的顺序。
  • 删除 map 中的某个键值 , 使用如下语法: delete(mapName,key) 。 delete 是内置函数,用来删除 map 中的某个键值对 。
  • 可以使用内置的 len()函数返回 map 中的键值对数量。
mp := make(map[int]string)
mp[1] = "tom"
mp[1] = "pony"
mp[2] = "jaky"
mp[3] = "andes"
delete (mp , 3)

fmt.Println(mp[1])
fmt.Println(len(mp))	//len 函数返回 map 中的键值对的数量

for k, v := range mp {  //range 支持遍历 mp ,但不保证每次遍历次序是一样的
	fmt.Println("key=", k, "value=", v)
}
  • Go 内置的 map 不是并发安全的,并发安全的 map 可以使用标准包 sync中的 map 。
  • 不要直接修改 map value 内某个元素的值,如果想修改 map 的某个键值,则必须整体赋
    值。例如 :
	type User struct{
		Name string
		age int
	}
	ma := make(map[int]User)
	andes := User{"andes", 18}
	
	ma[1] = andes
	// ma[1].age = 20 //error 不能通过map引用直接修改结构体的值
	andes.age = 20
	
	fmt.Println(ma[1].age) //18
	ma[1] = andes	//必须整体替换value
	fmt.Println(ma[1].age) //20

struct

Go 中的 struct 类型和 C 类似,由多个不同类型元素组合而成。这里面有两层含义:第一 ,struct 结构中的类型可以是任意类型:第二,struct 的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求)。
struct 有两种形式:一种是 struct 类型字面量,另一种是使用 type 声明的自定义 struct 类型。

struct类型字面量

声明格式如下:

struct {
	FeildName FeildType
	FeildName FeildType
	FeildName FeildType
}

自定义struct类型

声明格式如下:

type TypeName struct {
		FeildName FeildType
		FeildName FeildType
		FeildName FeildType
}

实际使用 struct 字面量的场景不多,更多的时候是通过 type 自定义一个新的类型来实现的 。Type 是自定义类型的关键字,不但支持 struct 类型的创建,还支持任意其他子定义类型的创建。

struct类型变量的初始化

type Person struct {
	Name string
	Age int
}

type Student struct {
	*Person
	Number int
}

// 按照类型声明顺序,逐个赋值
// 不推荐这种初始化方式,一旦struct的定义发生变化,这里的初始化也要跟着变化
a := Person{"andes", 18}

// 推荐使用Feild:value的方式初始化,这样即使struct定义发生变化,初始化的顺序也不会变化
b := Person{
	Name: "andes",
	Age: 18,
}

s := Student {
	Person: p,
	Number: 001,
}

其他复合类型

接口、通道之后介绍

控制结构

程序执行从本质上来说就是两种模式:顺序和跳转。

  • 顺序就是按照程序指令在存储器上的存放顺序逐条执行。
  • 跳转就是遇到跳转指令就跳转到某处继续线性执行。

Go 是一门高级语言,其源程序虽然经过了高度的抽象并封装了很多语法糖,但还是跳不出这个模式(这里暂时不考虑 goroutine 引入并发后的执行视图变化) 。
顺序在 Go 里面体现在从 main 函数开始逐条向下执行,就像我们的程序源代码顺序一样 ;跳转在 Go 里面体现为多个语法糖,包括 goto 语句和函数调用 、 分支( if、 switch 、 select ) 、循环( for )等。跳转分为两种: 一种是无条件跳转,比如函数调用和 goto 语句;一种是有条件的跳转,比如分支和循环 。
顺序语句很简单,就是我们天然写程序的从前往后的顺序。

Go 的源代码的顺序并不一定是编译后最终可执行程序的指令顺序,这里面涉及语言的运行时和包的加载过程 。 上面论述的主要目的是使读者从宏观上整体理解程序的执行过程,建立一个从源代码到执行体的大体映射概念,这个概念不那么精准,但对我们理解源程序到目标程序的构建非常有帮助。

if 语句

特点

  • if后面的条件判断子句不需要小括号括起来。
  • { 必须放在行尾,和 if 或 if else 放在一行。
  • if 后面可以带一个简单的初始化语句,并以分号分割,该简单语句声明的变量的作用域是整个 if 语句块,包括后面的 else if 和 else 分支。
  • Go语言没有条件运算符(三目运算),这也符合Go的设计哲学,只提供一种方法做事情。
  • if 分支语句遇到return 后直接返回,遇到 break 则跳过 break 下方的 if 语句块。
a := 1
if a > 0 {
	println("a是正数")
} else if a < 0 {
	println("a是负数")
} else {
	println("a是0")
}

//在初始化语句中声明变量
if b := 1; b > 0 {
	println("b是正数")
} else {
	println("b是非正数")
}
  • 尽量减少条件语句的复杂度,如果条件语句太多、太复杂,则建议放到函数里面封装起来。
  • 尽量减少 if 语句的嵌套层次,通过重构让代码变得扁平,便于阅读。

switch 语句

switch 语句会根据传入的参数检测井执行符合条件的分支。
switch 的语法特点如下:

  • switch 和 if 语句一样, switch 后面可以带一个可选的简单的初始化语句。
  • switch 后面的表达式也是可选的,如果没有表达式, 则 case 子句是一个布尔表达式 ,而不是一个值,此时就相当于多重 if else 语句。
  • switch 条件表达式的值不像 C 语言那样必须限制为整数, 可以是任意支持相等比较运算的类型变量。
  • 通过 fallthough 语句来强制执行下一个 case子句(不再判断下一个 case 子句的条件是否满足)。
  • switch 支持 default 语句, 当所有的 case 分支都不符合时, 执行 default 语句, 并且 default语句可以放到任意位置,并不影响 switch 的判断逻辑。
  • switch 和 .(type) 结合可以进行类型的查询。
switch i := "y"; i { //switch后面可以带上一个初始化语句,初始化语句和if一样,作用域只在switch内部
case "y", "Y": //多个条件用逗号分隔
	println("yes")
	fallthrough //fallthrough语句会强制执行后面的case代码
case "n", "N":
	println("no")
}
//输出yes no

score := 85
grade := ""
switch {
case score >= 90:
	grade = "A"
case score >= 80:
	grade = "B"
case score >= 70:
	grade = "C"
case score >= 60:
	grade = "D"
default:
	grade = "F"
}
fmt.Println("grade:", grade) //grade: B

for 语句

Go 语言仅支持一种循环语句, 即 for 语句,同样遵循 Go 的设计哲学,只提供一种方法做事情,把事情做好。

  • 类似 C 里面的 for 循环语句
for init; condition; post {}
  • 类似 C 里面的 while 循环语句
for condition {}
  • 类似 C 里面的 while(1) 死循环语句
for {}

for 还有一种用法,是对数组、切片、宇符串、 map 和通道的访问,语法格式如下:

// for循环访问map
for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
	println(key, value)
}
// for循环访问array
for i, v := range [3]int{1, 2, 3} {
	println(i, v)
}
// for循环访问slice
for i, v := range []int{1, 2, 3, 4} {
	println(i, v)
}
// for循环访问字符串
for i, c := range "go" {
	println(i, c)
}
// for循环访问channel
channel := make(chan int)
for value := range channel {
	println(value)
}

标签和跳转

标签

Go 语言使用标签 ( Lable )来标识一个语句的位置,用于 goto 、 break 、 continue 语句的跳转。标签的语法是:

Lable: Statement

标签的具体作用和使用见下面的 goto 、 break 、 continue 。

goto

goto 语句用于函数的内部的跳转,需要配合标签一起使用, 具体的格式如下:

goto Lable

goto Lable 的语义是跳转到标签名后的语句处执行, goto 语句有以下几个特点:

  • goto 语句只能在函数内跳转。
  • goto 语句不能跳过内部变量声明语句,这些变量在 goto 语句的标签语句处又是可见的。
    例如:
	goto L	//BAD,跳过v := 3 这条语句是不允许的
	v := 3
L:
  • goto 语句只能跳到同级作用域或者上层作用域内,不能跳到内部作用域内。
    例如:
if n%2 == 1 {
	goto L1
}
for n > 0 {
	f()
	n--
L1:	//Unused label 'L1'
	f()
	n--
}

break

break 用于函数内跳出 for、switch、select 语句的执行,有两种使用格式:

  • 单独使用,用于跳出 break 当前所在的 for、switch、select 语句的执行。
  • 和标签一起使用,用于跳出标签所标识的 for、switch、select 语句的执行,可用于跳出多重循环,但标签和 break 必须在同一个函数内。
    例如:
L1:
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if i == 3 {
				fmt.Println("i:", i, "j:", j)
				break L1 //跳出到L1标签
			}
			if j == 5 {
				break //跳出离它最近的for循环,j == 5之后的不再打印
				fmt.Println("i:", i, "j:", j)
			}
			fmt.Println("i:", i, "j:", j)
		}
	}

continue

continue 用于跳出 for 循环的本次选代,跳到 for 循环的下一次选代的 post 语句处执行,也有两种使用格式 :

  • 单独使用,用于跳出 continue 当前所在的 for 循环的本次迭代。
  • 和标签一起使用,用于跳出标签所标识的 for 语句的本次选代,但标签和 continue 必须在同一个函数内。
    例如:
L2:
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if i == 2 {
				continue L2 //跳出到L1标签所在的for循环i++处执行
				fmt.Println("i:", i, "j:", j)
			}
			if j == 5 {
				continue //跳出离它最近的for循环,j == 5 之后的会接着打印
				fmt.Println("i:", i, "j:", j)
			}
			fmt.Println("i:", i, "j:", j)
		}
	}

return 和函数调用

return语句也能引发控制流程的跳转,用于函数和方法的退出。函数和方法的调用也能引发程序控制流的跳转。

总结

通过本篇博客的介绍,我们了解了GO语言的基础语法特性,包括简洁的变量声明与赋值、灵活的控制结构。GO语言的设计目标是简洁高效,其语法特性使得代码编写和阅读更加舒适,也有助于提高代码的可维护性。让我们一起享受GO语言编程的乐趣吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/61130.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【单片机】51单片机串口的收发实验,串口程序

这段代码是使用C语言编写的用于8051单片机的串口通信程序。它实现了以下功能&#xff1a; 引入必要的头文件&#xff0c;包括reg52.h、intrins.h、string.h、stdio.h和stdlib.h。 定义了常量FSOC和BAUD&#xff0c;分别表示系统时钟频率和波特率。 定义了一个发送数据的函数…

春秋云镜 CVE-2020-26048

春秋云镜 CVE-2020-26048 CuppaCMS 任意文件上传 靶标介绍 CuppaCMS是一套内容管理系统&#xff08;CMS&#xff09;。 CuppaCMS 2019-11-12之前版本存在安全漏洞&#xff0c;攻击者可利用该漏洞在图像扩展内上传恶意文件&#xff0c;通过使用文件管理器提供的重命名函数的自…

区块链媒体发稿:区块链媒体宣发常见问题解析

据统计&#xff0c;由于区块链应用和虚拟货币的兴起&#xff0c;越来越多媒体对区块链领域开展报导&#xff0c;特别是世界各国媒体宣发全是热火朝天。但是&#xff0c;随着推卸责任媒体宣发的五花八门&#xff0c;让很多人因而上当受骗&#xff0c;乃至伤害一大笔资产。身为投…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK获取相机当前数据吞吐量(C#)

Baumer工业相机堡盟工业相机如何通过BGAPISDK里函数来获取相机当前数据吞吐量&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机的数据吞吐量的技术背景CameraExplorer如何查看相机吞吐量信息在BGAPI SDK里通过函数获取相机接口吞吐量 Baumer工业相机通过BGAPI SDK获取…

基于Vgg16和Vgg19深度学习网络的步态识别系统matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022A 3.部分核心程序 ................................................................ % 设置训练选项options …

【FAQ】EasyGBS平台通道显示在线,视频无法播放并报错400的排查

EasyGBS是基于国标GB28181协议的视频云服务平台&#xff0c;它可以支持国标协议的设备接入&#xff0c;在视频能力上能实现直播、录像存储、检索与回放、云台控制、告警上报、语音对讲、平台级联等功能&#xff0c;既能作为业务平台使用&#xff0c;也能作为能力层平台调用。 我…

uniapp引入inconfont

app,h5端引入 uniapp本身的全局设置中有个iconfontsrc属性 所以只需要 1.iconfont将需要的icon添加至项目 2.下载到本地解压后,将其中的ttf文件,放在static静态目录下 3.在page.json中对全局文件进行配置tabBar(导航图标) “iconfontSrc”: “static/font/iconfont.ttf”, …

【Linux】【docker】安装sonarQube免费社区版9.9

文章目录 ⛺sonarQube 镜像容器⛺Linux 安装镜像&#x1f341;出现 Permission denied的异常&#x1f341;安装sonarQube 中文包&#x1f341;重启服务 ⛺代码上传到sonarQube扫描&#x1f341;java语言配置&#x1f341;配置 JS TS Php Go Python⛏️出现异常sonar-scanner.ba…

观察者模式(C++)

定义 定义对象间的一种一对多(变化)的依赖关系&#xff0c;以便当一个对象(Subject)的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并自动更新。 ——《设计模式》GoF 使用场景 一个对象&#xff08;目标对象&#xff09;的状态发生改变&#xff0c;所有的依赖对…

ruoyi-cloud-notes01

1、Maven中的dependencyManagement Maven中的dependencyManagement元素提供了一种管理依赖版本号的方式。在dependencyManagement元素中声明所依赖的jar包的版本号等信息&#xff0c;那么所有子项目再次引入此依赖jar包时则无需显式的列出版本号。Maven会沿着父子层级向上寻找…

Spring-1-透彻理解Spring XML的Bean创建--IOC

学习目标 上一篇文章我们介绍了什么是Spring,以及Spring的一些核心概念&#xff0c;并且快速快发一个Spring项目&#xff0c;实现IOC和DI&#xff0c;今天具体来讲解IOC 能够说出IOC的基础配置和Bean作用域 了解Bean的生命周期 能够说出Bean的实例化方式 一、Bean的基础配置 …

VBA技术资料MF38:VBA_在Excel中隐藏公式

【分享成果&#xff0c;随喜正能量】佛祖也无能为力的四件事&#xff1a;第一&#xff0c;因果不可改&#xff0c;自因自果&#xff0c;别人是代替不了的&#xff1b;第二&#xff0c;智慧不可赐&#xff0c;任何人要开智慧&#xff0c;离不开自身的磨练&#xff1b;第三&#…

Mr. Cappuccino的第56杯咖啡——Mybatis拦截器

Mybatis拦截器 概述应用场景项目结构实现分页查询其它拦截器的使用 概述 Mybatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用&#xff0c;通过织入拦截器&#xff0c;在不同节点修改一些执行过程中的关键属性&#xff0c;从而影响SQL的生成、执行和返回结果…

【计算机视觉|语音分离】期望在嘈杂环境中聆听:一个用于语音分离的不依赖于讲话者的“音频-视觉模型”

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Looking to Listen at the Cocktail Party: A Speaker-Independent Audio-Visual Model for Speech Separation 链接&#xff1a;Looking to listen at the cocktail party: a speaker-in…

人工智能学习1——特征提取和距离

强人工智能和弱人工智能&#xff1a; 强人工智能&#xff1a;和人脑一样 弱人工智能&#xff1a;不一定和人脑思考方式一样&#xff0c;但是可以达到相同的效果&#xff0c;弱人工智能并不弱 —————————————————————————————————— 机器学习能…

2023年电赛---运动目标控制与自动追踪系统(E题)OpenMV方案

前言 &#xff08;1&#xff09;废话少说&#xff0c;很多人可能无法访问GitHub&#xff0c;所以我直接贴出可能要用的代码。此博客还会进行更新&#xff0c;先贴教程和代码 &#xff08;2&#xff09; <1>视频教程&#xff1a; https://singtown.com/learn/49603/ <2…

自己实现Linux 的 cp指令

cp指令 Linux的cp指令就是复制文件&#xff1a; cp: 拷贝(cp 拷贝的文件 要拷贝到的地址或文件)&#xff0c;cp b.c test.c 将b.c拷成test.c的一个新文件 Linux 系统初识_mjmmm的博客-CSDN博客 实现思路 打开源文件读文件内容到缓冲区创建新文件将读到的文件内容全部写入新文…

在家下载Springer、IEEE、ScienceDirect等数据库论文的论文下载工具

Springer、IEEE、ScienceDirec数据库是我们查找外文文献常用数据库&#xff0c;当我们没有数据库使用权限的时该如何下载这些数据库的学术论文呢&#xff1f;下面就讲解一下在家下载数据库学术文献的论文下载工具。 一、查找下载外文文献&#xff0c;我们可以谷歌学术检索&…

LeetCode-Java(05)

19. 删除链表的倒数第 N 个结点 两个方法&#xff0c;方法一是先走一遍链表得出链表长度&#xff0c;再走第二遍&#xff0c;找到倒数第n个数。方法二是双指针&#xff0c;首先快指针就比慢指针多走n步&#xff0c;然后这俩指针同步走&#xff0c;快指针走到头了&#xff0c;慢…

python-Excel数据模型文档转为MySQL数据库建表语句(需要连接数据库)-工作小记

将指定Excel文档转为create table 建表语句。该脚本适用于单一且简单的建表语句 呈现效果 代码 # -*- coding:utf-8 -*- # Time : 2023/8/2 17:50 # Author: 水兵没月 # File : excel_2_mysql建表语句.py import reimport pandas as pd import mysql.connectordb 库名mydb m…