## Go语言的基本语法 [TOC] ## 1.Go 语言结构 在我们开始学习 Go 编程语言的基础构建模块前,让我们先来了解 Go 语言最简单程序的结构 ### Hello World 实例 Go 语言的基础组成有以下几个部分: * 包声明 * 引入包 * 函数 * 变量 * 语句 & 表达式 * 注释 让我们来写一个Hello World 代码 ``` packagemain import"fmt" func main(){ /* 这是我的第一个程序 */    fmt.Println("Hello, World!") } ``` 简单介绍以上代码各个部分: 1. 第一行代码*package main*定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。 2. 下一行*import "fmt"*告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。 3. 下一行*func main()*是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。 4. 下一行 /\*...\*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。 5. 下一行*fmt.Println(...)*可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。 ## 2.包的声明 Go语言以“包”作为程序项目的筐理单位。如果要正常运行Go语言的源文件’则必须先声明 它所属的包。每-个Go源文件的开头都是-个package声明,格式如下 ``` package package_name ``` package 是声明包名的关键字 package_name是包的名字 **Go语言的包昌有以下几点特性:** 1. 一个目录下的同级文件属于同一个包 2. 包名可以与自身目录名不同 3. main包是Go语言应用程序的入口包,一个Go语言应用程序必须有且仅有一个main包。 如果一个程序没有main包,则编译时将会报错,无法生成可执行文件 ## 3.包的导入 导入的包名使用英文双引号(” “)包围,格式如下: ``` import ”package_name“ ``` import 是导入包的关键字 package_name是导入包的名字 > 为了看起来直观一般会在packagc和jmport之间空一行,没有空格也不会报错,实际开发过程中,编辑器一般会提示没有导入的包或者自动帮助导入需要的包 import 也可以同时导入多个包 ``` import ( "time" "fmt" ) ``` 也可以设置包的别名 ``` import ( alias1 "time" alias2 "fmt" ) ``` 如果只想初始化某个包不使用导入包中的变量或者函数,则可以直接以下划线( _ )代替别名 ``` import ( _ "time" alias2 "fmt" ) ``` >如果已经用下划线(_)代替了别名,继续再调用这个包,则会在编译的时候返回”undefined:包名的错误。如"undefined: os" ## 4.main函数 main 函数是一种自定义函数,在Go语言中,所有函数都以关键字func开头,定义格式为: ``` func 函数名 (参数列表) (返回值列表) { 函数体 } ``` 具体说明如下: 1. 函数名:由字母、数字、下划线(_)组成,具中第1个字母不能为数字,并目在同一个 包内函数名称不能重复 2. 参数列表:一个参数由参数变量和参数类型组成,例如func foo(name string,age int) 3. 返回值列表:可以是返回值类型列表也可以是参数列表那样的变量名与类型的组台列表。 函数有返回值时,必须在函数体中使用return语句返回 4. 函数体:函数体星用大括号“{}"括起来的若干语句,它们完成了一个函数的具体功能 ``` func main(){     fmt.Println("Hello "+"World") } ``` ### 行分隔符 go 语言不需要在一行语句的最后用英文分号(;)结尾,因为Go编译器会自动完成这些工作,如果多个语句在同一行,则必须使用英文分号(;)将他们隔开。 ### 标记 Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成: ``` fmt //关键字 . // 标识符 Println //关键字 ( //符号 "Hello, World!" //字符串 ) //符号 ``` ## 5.注释 Go 语言中注释分为单行注释和多行注释,分别如下 ``` //单行注释 ``` ``` /* 多行注释 */ ``` ## 6.字符串连接 Go语言的字符串可以通过“+”号实现字符串连接 ``` func main(){     fmt.Println("Hello "+"World") } ``` 结果为:Hello World ## 7.关键字 | 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 | 除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数,见下表: | append | bool |byte | cap | close |complex | | --- | --- |--- |--- |--- |--- |--- | | complex64 | complex128 | uint16 | copy | false |float32 | | float64 | imag | uintptr| int | int8 |int16 | | uint32 | int32 | int64 | iota | len |make | | new | nil | panic | uint64 | print |println | | real | recover | string | true | uint|uint8| ## 8.变量 >Go建议使用驼峰式命名规则,如numberList 变量命名还遵循其他的规则,比如变量由下画线、字母、数字组成,首个字符不能是数字,并且不能使用语言预留的关键字。 ### 使用有具体含义的命名 避免泛化的变量命名,比如用temp、i、j、k之类的变量命名,虽然它们有一定的适用场合,但在绝大多数情况下建议使用有具体含义的命名。 ### 变量命名不宜过长,也不宜过短 变量命名以2~3个单词的长度为宜。 ### 变量命名中加上后缀或者前缀 这种命名方式适合变量的值有区间的情况,比如numberMax、numberMin、timeLast、timeBefore。 ### 声明与赋值 变量赋值的标准公式 ``` var 变量名 [类型] = 变量名 ``` 多个变量赋值的标准公式为: ``` var { 变量名1 (变量类型1) = 值1 变量名2 (变量类型2) = 值2 变量名n (变量类型n) = 值n } ``` 或者多个变量和变量(直在同-行 ``` var 变量名1,变量名2,变量名3 = 值1,值2,值3 ``` 比如: ``` //显示声明 var orderNo string orderNo="2021040503938267" //声明变量的同时赋值 orderId := "1789789776766868" ``` 哪一种更合适呢?建议使用显式的变量声明方式,这样程序代码的可读性更好,在阅读程序的过程中就知道数据的类型。 也可以多个变量一起声明与赋值,如 ``` var { strOne = "a" strTwo = "b" strThree = "c" } var strOne、strTwo、strThree strOne、strTwo、strThree= "a","b","c" ``` 总结,变量命名格式有标准格式、批量格式、简短格式 **标准格式** ``` var 变量名 变量类型 ``` 变量声明以关键字var开头,中间是变量名,后面是变量类型,行尾无须有分号 **批量格式** Go 语言还提供了一个批量设置的方法,形式如下: ``` var { age int name string pwd string } ``` **简短格式** ``` 名字:=表达式 ``` 需要注意的是**简短模式**有以下限制: * 只能用来定义变量,同时会显式初始化. * 不能提供数据类型 * 只能用任函数内部,即不能用来声明全局变量· ## 9.变量的作用域 变量的作用域包括局部作用域和全局作用域,即所谓的局部变量和全局变量。 >局部作用域表示变量仅在局部有效,比如在函数内,而且可以和全局的变量名相同,这时优先使用具有局部作用域的变量内的值。全局作用域表示变量全局有效,变量的声明在函数外部。 例如以下代码 **源码:base/example/global_value.go** ``` /* 声明全局变量 */ 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 ``` ## 10.常量 常量的值不会在运行过程中变动,更适用于全局作用域 常量的声明格式 ``` const 常量名 [类型] = 值 ``` 例如声明一个常量pi的方法 ``` //显式类型声明 const pi float32 = 3.1415926 //隐式类型声明 const pi = 3.1415926 ``` >常量的值必须是能够在编译时可被确定的 ### 常量生成器iota 常量声明可以使用常量生成器iota初始化,iota用于生成一组以相似规则初始化的常量,但是 不用每行都写一遍初始化表达式 在一个const声明语句中,在第1个声明的常量所在的行,iota会被置为0,之后的每—个有常量声明的行会被加1 在Go语言中,iota的用法如下: ``` type Direction int const { Up Direction =iota Down Left Right } ``` 在以上声明中Up的值为0, Down的值为1,其余以此类推 ## 11.运算符 运算符是用来在程序运行时执行数学运算或逻辑运算的符号, Go语言有几十种运算符,被分成十几个级别,有一些运算符的优先级不同有一些运算符的优 先级相同 ![](https://img.kancloud.cn/30/6f/306fdddc7691ec6716c29d9731d31c87_712x557.png) >记不清就加括号 ## 12.流程控制语句 ### 逻辑运算if 格式如下: ``` if 布尔类型的条件 { 执行语句 }else if 布尔类型的条件{ 执行语句 }else{ 执行语句 } ``` #### 案例-条件判断 **源码:base/example/if.go** ``` //条件操作 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) if a == b { fmt.Printf("第四行 - a 等于 b\n") } else { fmt.Printf("第五行 - a 不等于 b\n") } } ``` ``` 结果为:第五行 - a 不等于 b ``` ### for 循环 Go 语言的循环语句只支持for 关键字,规则如下 ``` for 初始值; 控制条件;值增加条件{ 执行语句 } ``` 当然也可以模拟do{} - while{} 语句 ``` i:=0 for { i++; if(i>=10){ break; } } ``` #### 案例-素数 **源码:base/example/for.go** ``` //循环 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 是素数 ``` #### 案例-99乘法表 **源码:base/example/for_multiplication_table.go** ~~~ package main import "fmt" //99 乘法表 func main() { for m := 1; m < 10; m++ { for n := 1; n <= m; n++ { fmt.Printf("%dx%d=%d ", n, m, m*n) } fmt.Println() } } ~~~ 结果为: ``` 1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9 1x4=4 2x4=8 3x4=12 4x4=16 1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81 ``` ### for-range 循环 for-range 循环结构使Go 语言特有的结构,可以遍历数组、切片、字符串、map、通道( channel ) for-range 的规则如下: ``` for key,val := range 复核变量值 { 执行语句 } ``` val 始终为集台中对应索引值的一个复制值,因此它一般只具有“只读’’属性,对它所做的任何修改都不会影响集合中原有的值 **源码:base/example/range.go** 遍历数组: ~~~ //数组的遍历 numsArr := [5]int{1,2, 3, 4,5} //遍历数组 for i, num := range numsArr { fmt.Printf("index:%d,value=%d\n", i,num) } ~~~ 遍历切片: ~~~ //切片的遍历 numsSlice := []int{2, 3, 4} //遍历数组 for i, num := range numsSlice { fmt.Printf("index:%d,value=%d\n", i,num) } ~~~ 遍历字符串: ~~~ //遍历字符串 for i, c := range "GoLand" { fmt.Printf("结果: %d %c\n", i, c) } ~~~ 遍历map: ~~~ //range遍历map kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %s\n", k, v) } ~~~ 遍历通道: ~~~ //遍历通道 queue := make(chan string, 2) queue <- "one" queue <- "two" close(queue) for elem := range queue { fmt.Println(elem) } ~~~ 结果为: ``` index:0,value=1 index:1,value=2 index:2,value=3 index:3,value=4 index:4,value=5 index:0,value=2 index:1,value=3 index:2,value=4 b -> banana a -> apple 结果: 0 G 结果: 1 o 结果: 2 L 结果: 3 a 结果: 4 n 结果: 5 d one two ``` ### switch-case 语句 Go语言改进了传统的switch-case语句的语法设计;case与case之间是独立的代码块,不 需要通过breake语句跳出当前case代码块以避免执行到下一行语句 代码如下 **源码:base/example/switch.go** ~~~ var res="a" switch res { case "a": fmt.Println("结果为a") case "b": fmt.Println("结果为b") case "c": fmt.Println("结果为c") default: fmt.Println("结果为 default") } ~~~ 一个分支多个值时 ~~~ var res="a" switch res { case "a","b": fmt.Println("结果为a或b") case "c": fmt.Println("结果为c") case "d": fmt.Println("结果为d") default: fmt.Println("结果为 default") } ~~~ 分支表达式 ~~~ var res="a" switch { case res=="a" || res=="b": fmt.Println("结果为a或b") default: fmt.Println("结果为 default") } ~~~ ### goto 语句 在Go语言中,可以通过语句跳转到标签,进行代码间的无条件跳转,此外,goto语句在快速跳出循环也有很大的作用 例如使用goto 语句跳出循环 例如使用goto 语句统一跳转错误提示 **源码:base/example/goto.go** ~~~ //goto 使用 func main() { for x := 0; x < 20; x++ { for y := 0; y < 20; y++ { if y == 2 { // 跳转到标签 goto breakHere } } } // 手动返回, 避免执行进入标签 return // 标签 breakHere: fmt.Println("done") } ~~~ ### continues 语句 Go 语言的continues 语句用于结束当前语句的循环,继续进行下一次的循环过程。它仅限在for循环内部使用,在continues 语句后面添加标签,表示结束标签对应的当前语句,开始下一次的循环 **源码:base/example/continue.go** ~~~ //continue 语法 func main() { //外层循环的标签 OuterLoop: //双层循环 for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { //使用 switch 进行数值分支判断 switch i { case 1: //换行输出 fmt.Println(i, j) //退出 OuterLoop 对应的循环之外 continue OuterLoop case 2: fmt.Println(i, j) continue OuterLoop case 3: fmt.Println(i, j) break OuterLoop } } } } ~~~ 结果为: ``` 1 0 2 0 3 0 ``` ### bresk 语句 Go 语言的break 语句可以结束for 、switch 、select 代码块,还可以在break 语句后面添加标签,可以实现快速退出某个标签对应的代码块。添加的标签必须在对应的for、switch、select 代码块上。 **源码:base/example/break.go** ~~~ //break 语法 func main() { //外层循环的标签 OuterLoop: //双层循环 for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { //使用 switch 进行数值分支判断 switch i { case 1: //换行输出 fmt.Println(i, j) //退出 OuterLoop 对应的循环之外 break OuterLoop } } } } ~~~ 结果为: ``` 1 0 ``` ## 13.总结 本章节主要介绍了Go 语言的基本语法,有包的声明方式、包的导入方式、main 函数的使用方式、Go语言的代码注释方式、字符串拼接方式(和Java 相同)、关键字及预定义字符的介绍,变量的声明规范及赋值形式、全局变量和局部变量的介绍、常量的声明规则和赋值介绍、Go 的运算符优先级介绍(记不清的时候用“()”)、流程控制语句的介绍(逻辑运算符if、switch-case,中断语句continues、break,循环控制语句 for、for-range,跳转语句goto)。