**本人是第六届字节跳动青训营(后端组)的成员。本文由博主本人整理自该营的日常学习实践,首发于稀土掘金:🔗Go语言入门指南:基础语法和常用特性解析 | 青训营
本文主要梳理自第六届字节跳动青训营(后端组)-Go语言原理与实践第一节(王克纯老师主讲)。同时博主也结合了自己的理解和其他参考资料,对Go语言基础语法和常用特性进行解析。后续若有需要补充的地方,本文会相应地进行更新。
如何安装和配置Go的开发环境,这里就不细说了,有需要的朋友可以自己去搜索教程。
我的go开发环境:
*本地IDE:GoLand 2023.1.2
*go:1.20.6
其它参考资料:
《Go语言圣经》
面向加薪学习-欢喜哥
Go语言官方文档
一、Hello World程序
1、在GoLand中运行一个Hello World
首先,新建一个go的项目:
写下如下代码:
没错,Go语言中的Hello World程序长这样:
package main // 声明 main 包 import "fmt" // 导入 fmt 包,打印字符串时需要用到 func main(){ // 声明 main 主函数 fmt.Println("Hello, World!") // 打印 }
Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(静态编译)。Go语言提供的工具都通过一个单独的命令go
调用,go
命令有一系列子命令。最简单的一个子命令就是run。这个命令编译一个或多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。
在终端输入 go run main.go
即可编译并且运行这个go文件,go build main.go
即可编译而不运行这个文件。
在GoLand中,直接右键,点击运行(或点击func main左边的绿色小箭头),就能运行出第一个Go语言程序了:
Go语言原生支持Unicode,它可以处理全世界任何语言的文本。
接下来我们来讨论一下这个Hello World程序本身。
2、package包
声明包
Go语言以包(package)为管理单位,包类似于其它语言里的库(libraries)或者模块(modules)。
每个 Go 源文件必须以一条package声明语句,先声明它所属的包。比如上面Hello world程序中的package main,它表示该文件属于哪个包。一般来说,程序的包名由目录名来指定:
不过,一个go文件声明的包名也可以与其目录名不一致,但同一个目录下的go文件声明的包名必须一致:
main包与main函数
main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。
在main包里的main函数也很特殊,它是整个程序执行时的入口(C系语言差不多都这样,比如Java,C#)。main函数所做的事情就是程序所做的。当然,main函数一般调用其它包里的函数完成很多工作(如:fmt.Println)。
也就是说,main包是一个go语言程序的入口包,func main()是go语言的入口函数。一个go语言项目程序中:
必须有且仅有一个main包;
main 函数只能声明在 main 包中,不能声明在其他包中;
一个 main 包中也必须有且仅有一个 main 函数。
如果一个程序没有main包或main包中没有main函数,那么它在编译的时候将会出错。无法生成执行文件。
如果执意运行,GoLand会弹出下面的界面,提示没有入口包main(若用命令行执行则会报错):
从main函数的声明可以得知,在 Go 语言中,所有函数都以关键字 func 开头的,定义格式如下:
func 函数名 (参数列表) (返回值列表){ 函数体 }
总结go语言中的包
Go 语言的包与文件夹是一一对应的,它具有以下几点特性:
一个目录下的同级文件属于同一个包。
包名可以与其目录名不同。
main 包是 Go 语言程序的入口包,一个 Go 语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包,那么编译时将会出错,无法生成可执行文件。
3、import导入包
在包声明之后,是 import 语句,用于导入程序中所依赖的包,导入的包名使用双引号""包围,格式如下:
import "name" // 其中 import 是导入包的关键字,name 为所导入包的名字。
有一点需要注意:导入的包中不能含有代码中没有使用到的包,否则 Go 编译器会报编译错误,例如 imported and not used: "xxx",”xxx” 表示包名。如果在GoLand中出现了冗余的包,它会自动给你删除冗余包的导入。也可以使用一个 import 关键字导入多个包,此时需要用括号( )将包的名字包围起来,并且每个包名占用一行,也就是写成下面的样子:
import( "name1" "name2" )
二、程序结构
1、命名
Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母( Unicode 字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。 且大写字母和小写字母是不同的。
Go语言中关键字有25个,关键字不能用于自定义名字:
对于Go语言字段的命名,有以下注意点:
- 如果一个名字是在函数内部定义,那么它就只在函数内部有效。
- 如果是在函数外部定义,那么将在当前包的所有文件中都可以访问(相当于是全局变量)。
- 名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的(注:必须是在函数外部定义的包级名字;包级函数名本身也是包级名字),那么它是可以被外部的包访问的(相当于Java中的public),例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。
- 包本身的名字一般总是用小写字母。
此外,命名的长度没有逻辑上的限制,但是Go语言的编码风格是尽量使用短小的名字(尤其是局部变量)。通常来说,如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义。
习惯上,Go语言的变量以 驼峰式 命名,而不是用下划线分隔。而像ASCII和HTML这样的缩略词,则避免使用大小写混合的写法,它们可能被称为htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtml。
2、变量
王克纯讲师的给出的变量声明代码如下:
package main import ( "fmt" "math" ) func main() { var a = "initial" //声明string类型变量,省略变量类型 var b, c int = 1, 2 //完整的声明 var d = true //声明bool类型变量 var e float64 //声明浮点类型变量 f := float32(e) //短变量声明 g := a + "foo" //用加号连接两个字符串 fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0 fmt.Println(g) // initialapple }
非常清晰易懂。Go语言是一门强类型语言,每一个变量都有它自己的变量类型,常见的变量类型包括字符串,整数,浮点型,布尔型等。Go 语言的字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串。
在Go语言里面,大部分运算符的使用和优先级都和 C 或者 C++ 类似,这里就不再概述。
下面梳理了一下Go中变量的声明几种规则。
声明变量的一般形式是使用 var 关键字。
var 变量名字 类型 = 表达式 var num int = 10
其中,“变量的类型”
或“=表达式
”
这两个部分可以省略其中的一个。也就是说,也可以这样写:
var num1 = 10 //省略类型 var num2 int //省略初始化表达式
- 如果省略的是类型信息,那么将根据等号右边的初始化表达式来推导该变量是什么类型。
- 如果初始化表达式被省略,那么将用零值初始化该变量。
数值类型变量对应的零值是0
布尔类型变量对应的零值是false
字符串类型对应的零值是空字符串
接口或引用类型(包括slice、指针、map、chan和函数)变量对应的零值是nil(意义相当于其它语言的null)
数组或结构体等聚合类型对应的零值是每个元素或字段都对应的该类型的零值
零值初始化机制可以确保每个声明的变量总是有一个值,因此在Go语言中不存在未初始化的变量。
接下来来讲一讲如何声明变量。
方法一:声明一个变量但不赋值(采用默认值)
package main import "fmt" func main(){ // 方法一:声明一个变量, 默认的值是0 var a int fmt.Println("a = ", a) fmt.Printf("a的类型是: %T\n", a) // %T 是一种格式化占位符,用于在 fmt.Printf 或 fmt.Sprintf 等函数中, //将变量的类型信息格式化并插入到字符串中。 } /*输出: a = 0 a的类型是: int */
方法二:声明一个变量,并初始化一个值
package main import "fmt" func main(){ // 方法二:声明一个变量, 初始化一个值 var num1 int = 100 fmt.Printf("num1 = %d, type of num1 = %T\n", num1, num1) var num2 string = "hello" fmt.Printf("num2 = %s, num2的类型是: %T\n", num2, num2) }
方法三:在初始化时省去数据类型,自动推导当前数据类型
package main import "fmt" func main(){ // 方法三:在初始化时省去数据类型,自动推导当前数据类型 var num1 = 100 fmt.Printf("num1 = %d, type of num1 = %T\n", num1, num1) var num2 = "hello" fmt.Printf("num2 = %s, num2的类型是: %T\n", num2, num2) }
方法四:多变量声明
也可以在一个声明语句中同时声明一组变量,或用一组初始化表达式声明并初始化一组变量。如果省略每个变量的类型,可以声明多个类型不同的变量(每个变量的类型都由初始化表达式去推导):
var i, j, k int // int, int, int var b, f, s = true, 2.3, "four" // bool, float64, string
这样,就可以用以下的方式来交换两个变量的值,这比C语言,Java更加简洁。
i, j = j, i // 交换 i 和 j 的值
方法五:短变量声明
在函数内部(只能在函数内),有一种简短变量声明语句的形式,可用于声明和初始化局部变量。
它以名字 := 表达式
形式声明变量,变量的类型根据表达式来自动推导。
anim := gif.GIF{LoopCount: nframes} freq := rand.Float64() * 3.0 t := 0.0
因为短变量的声明方式更加简洁和灵活,它被广泛用于大部分的局部变量的声明和初始化。
var形式的声明语句往往是用于需要显式指定变量类型的地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
和var形式声明语句一样,简短变量声明语句也可以用来声明和初始化一组变量:
i, j := 0, 1
但是,《Go语言圣经》中指出,这种同时声明多个变量的方式应该限制只在可以提高代码可读性的地方使用,比如for语句的循环的初始化语句部分。
总而言之,需要记住,:=是一个变量声明语句,而=是一个变量赋值操作。
和普通var形式的变量声明语句一样,短变量声明语句也可以用函数的返回值来声明和初始化变量:
f, err := os.Open(name) //声明变量f,err,并接收函数的返回值来初始化它们 if err != nil { return err } // ...use f... f.Close()
短变量声明的注意事项
短变量声明中,左边的变量可能并不是全部都是刚刚声明的。如果有一些变量在与当前语句相同的作用域中已经声明过了,那么短变量声明语句对这些已经声明过的变量就只有赋值行为了。(如果是在外部的作用域中声明的,那依然会在短变量声明时重新创建一个新的变量。)
例如,在下面的代码中,两个语句的功能是不同的:
in, err := os.Open(infile) //声明了in和err两个变量 // ... out, err := os.Create(outfile) //只声明了out一个变量,然后对已经声明的err进行了赋值操作
并且,简短变量声明语句中必须至少要声明一个新的变量:
f, err := os.Open(infile) // ... f, err := os.Create(outfile) // compile error: no new variables //解决的方法是第二个简短变量声明语句改用普通的多重赋值语句 var f,err = os.Create(outfile)
3、常量
常量就是只读属性,定义后不允许被修改。定义一个常量,使用 const 关键字且定义的时候就要赋值。
package main import ( "fmt" "math" ) func main() { const s string = "constant" //声明常量 const h = 500000000 const i = 3e20 / h fmt.Println(s, h, i, math.Sin(h), math.Sin(i)) }
可以使用const来定义枚举类型:
package main import "fmt" // const来定义枚举类型 const ( BEIJING = 0 SHANGHAI = 1 SHENZHEN = 2 ) func main() { fmt.Println("BEIJING = ", BEIJING) // 0 fmt.Println("SHANGHAI = ", SHANGHAI) // 1 fmt.Println("SHENZHEN = ", SHENZHEN) // 2 }
4、if-else
package main import "fmt" func main() { if 7%2 == 0 { fmt.Println("7 is even") } else { fmt.Println("7 is odd") } if 8%4 == 0 { fmt.Println("8 is divisible by 4") } if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") } }
Go语言里面的 if-else 写法和 C 或者 C++ 类似。不同点一个是 if 后面不加括号。如果你写了括号,那么在保存的时候编译器会自动去掉。第二个不同点是 Golang 里面的 if 它必须后面接大括号,不能像 C 或者 C++ 一样直接把 if 后面的大括号另起一行写。
(比较有趣的一点是,当写错了,刷个新或者切屏一下再切回来,编译器就会很贴心地帮你把错误的书写方式更改过来了。)
5、for
package main import "fmt" func main() { i := 1 //死循环 for { fmt.Println("loop") break //使用break跳出循环 } for j := 7; j < 9; j++ { fmt.Println(j) } for n := 0; n < 5; n++ { if n%2 == 0 { continue //使用continue跳过当前循环 } fmt.Println(n) } for i <= 3 { fmt.Println(i) i = i + 1 } }
在Go里没有while,do while循环,只有一种for循环(毕竟一个for完全够用)。
最简单的for循环就是for后面啥也不写,这代表一个死循环。循环途中想要退出,使用break。
也可以使用经典的C中的循环,即 for(i := 0; i <= n; i++)。这中间三段,任何一段都可以省略。
在循环里面,可以用 break 或者 continue 来跳出或者继续循环,这与C语言循环中的break和continue的用法是相似的。
6、switch
package main import ( "fmt" "time" ) func main() { a := 2 switch a { case 1: fmt.Println("one") case 2: fmt.Println("two") case 3: fmt.Println("three") case 4, 5: fmt.Println("four or five") default: fmt.Println("other") } t := time.Now() switch { case t.Hour() < 12: fmt.Println("It's before noon") default: fmt.Println("It's after noon") } } /* 输出: two It's after noon */
Go语言里面的 switch 分支结构与 C 或者 C++ 的不同点在于:
在Go语言中不需要显式地加break。执行完匹配的case代码块后,它会直接退出 switch-case ,如果没有任何一个匹配,会执行 default 的代码块。Go不像C一样,如果不显式地加 break 就会继续往下跑完所有的 case。(正如上面的代码第一个输出的是two,而不是把case 2下面的打印语句全部执行一遍。 )
相比C或者C++,Go语言里面的switch-case功能更加强大。switch后可以使用任意的变量类型甚至函数,可以用来取代任意的 if-else 语句。
(1)一个case多个条件
在 Go 中,case 后可以接多个条件,多个条件之间是 或 的关系,用逗号 , 相隔。
month := 5 switch month { case 1, 3, 5, 7, 8, 10, 12: fmt.Println("该月份有 31 天") case 4, 6, 9, 11: fmt.Println("该月份有 30 天") case 2: fmt.Println("该月份闰年为 29 天,非闰年为 28 天") default: fmt.Println("输入有误!") }
(2)选择语句高级写法
switch
还有另外一种写法,它包含一个 statement
可选语句部分,该可选语句在表达式之前运行:
switch month := 5; month { case 1, 3, 5, 7, 8, 10, 12: fmt.Println("该月份有 31 天") case 4, 6, 9, 11: fmt.Println("该月份有 30 天") case 2: fmt.Println("该月份闰年为 29 天,非闰年为 28 天") default: fmt.Println("输入有误!") }
这样,这里 month 变量的作用域就仅限于这个 switch 内了。
(3)fallthrough 语句
正常情况下 switch-case 语句在执行时只要有一个 case 满足条件,就会直接退出 switch-case ,如果一个都没有满足,才会执行 default 的代码块。不同于其他语言需要在每个 case 中添加 break 语句才能退出。
使用 fallthrough 语句可以在已经执行完成的 case 之后,向下“穿透”一层。
package main import ( "fmt" "time" ) func main() { a := 2 switch a { case 1: fmt.Println("one") case 2: fmt.Println("two") fallthrough case 3: fmt.Println("three") case 4, 5: fmt.Println("four or five") default: fmt.Println("other") } } /* 输出: two three */
fallthrough 语句是 case 子句的最后一个语句。如果它出现在了 case 语句的中间,编译会不通过。
(4)无表达式的 switch
switch 后面的表达式可以省略,然后在case中写条件分支。此时switch-case的作用,相当于一个 if-elseif-else。
score := 88 switch { case score >= 90 && score <= 100: fmt.Println("grade A") case score >= 80 && score < 90: fmt.Println("grade B") case score >= 70 && score < 80: fmt.Println("grade C") case score >= 60 && score < 70: fmt.Println("grade D") case score < 60: fmt.Println("grade E") }
这比使用多个 if-else 逻辑更加清晰。
(5)switch 后可接函数
switch
后面可以接一个函数,只要保证 case
后的值类型与函数的返回值一致即可。
package main import "fmt" func getResult(args ...int) bool { for _, v := range args { if v < 60 { return false } } return true } func main() { chinese := 88 math := 90 english := 95 switch getResult(chinese, math, english) { case true: fmt.Println("考试通过") case false: fmt.Println("考试未通过") } }
【字节跳动青训营】后端笔记整理-1 | Go语言入门指南:基础语法和常用特性解析(二)+
https://developer.aliyun.com/article/1521856?spm=a2c6h.13148508.setting.18.439a4f0eAvjRuU