会编程的羽流云
二、Go语言基础入门——Go基础语法与使用
前言
最近买了两本书准备学习一下Go语言,其中一本是《Go语言高并发与微服务实战》另外一本是《Go Web编程实战派》,第一本是为了深入了解一下Go语言在高并发中的魅力,买第二本的原因是因为刚刚从Java转到Go语言,所以想更快的学习一些工作中可能接触到的Go Web相关的知识。
众所周知,Go语言是Google于2009年开源的一门编程语言,原名GoLanguage也叫Golang。它可以在不损失应用程序性能的情况下极大地降低代码的复杂度。相比较其他编程语言,它具备了简洁、快速、安全、并行、有趣、开源、内存管理、数组安全、编译迅速等等一些列特点。
话不多少,我会先学习《Go Web编程实战派》这本书,跟随我一起走进Go语言学习的海洋吧!!!
# 基础语法
我们以及完成了Go语言的第一个程序,接下来本章博客将从Go语言的基础语法以及使用方面进行学习,虽然这些知识对于有其他语言开发经验的同学很简单,但我相信还还会从中发现更多知识点。
## 行分隔符
在Go语言中,并不需要像Java、PHP等语言那样需要在一行语句的最后使用分号结尾,因为这些工作编译器已经帮我们完成了。但是如果在同一行有多条语句,那就必须使用分号进行分隔。但是这种方式在实际开发中并不鼓励书写哦!
## 注释
注释分为单行与多行,格式如下
单行注释:单行注释是最常见的注释形式,可以在任何地方进行使用。
//单行注释
多行注释:也被称之为“块注释”。
/*
多行注释多行注释
*/
标识符
标识符通常用来对变量、类型等程序实体进行命名。一个标识符实际上就是一个或者多个字符、数字、下划线组成的。第一个字符不能为数字或者Go的关键字。
正确命名:product、user、add、user_name、abc_123、resultValue、_tmp、k
错误命名:switch(Go语言关键字)、3ab(以数字开头)、c-d(运算符不允许)
字符串连接
Go语言可以通过加号实现字符串拼接,示例如下:
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello " + "World")
}
## 关键字
在Go语言中有25个关键字或保留字
关键词如下
关键字 | 关键字 | 关键字 | 关键字 | 关键字 |
---|---|---|---|---|
continue | for | import | return | var |
const | fallthrough | if | range | type |
chan | else | goto | package | switch |
case | defer | go | map | struct |
break | default | func | interface | select |
最新版本Go语言中还有30几个预定义标识符,可以按照以下划分:
常量相关预定义标识符:true、false、iota、nil类型相关预定义标识符:int、int8、int16、in32、int64、uint、uint8、uint16、uint32、uint64、uintpr、float32、float64、complex128、complex64、bool、byte、rune、string、error
函数相关预定义标识符:make、len、cap、new、append、copy、close、delete、complex、reat、image、panic、revover
变量
声明
Go语言是静态强类型语言,因此变量是有明确类型的,编译器也会检查变量类型的正确性。当一个变量被声明后,系统会自动赋予它该类型的零值或者空值(int->0、float->0.0、bool->false、string->空字符串、指针类型->nil)。变量的命名规则依旧遵循“驼峰”命名法。变量的声明可以分为标准格式、批量格式、简短格式这三种形式。
标准格式
var 变量名 变量类型变量声明以var开头,中间是变量名 后面是变量类型
批量格式
var(age int
name string
balance float32
)
简短格式
名字 := 表达式
需要注意,简短模式有一下的限制
只能用来定义变量,同时会显式初始化
不能提供数据类型
只能用在函数内部,即不能用来声明全局变量
赋值
给单个变量赋值
var 变量名 [类型] = 变量值
egvar name string = "zhuangyan"
var name = "zhuangyan"
name := "zhuangyan"
给多个变量赋值
var( 变量名1 (变量类型) = 变量值1
变量名2 (变量类型) = 变量值2
......
)
或者
var 变量名1,变量名2,变量名3 = 变量值1,变量值2,变量值3
或者
变量名1,变量名2,变量名3 := 变量值1,变量值2,变量值3
变量作用域
Go语言变量可以分为局部变量和全局变量。
局部变量:在函数体中被声明的变量被称为局部变量,作用域自在函数体内。
package main
import "fmt"
func main() {
//声明局部变量
var local1, local2, local3 int
//初始化参数
local1 = 1
local2 = 2
local3 = 3
fmt.Printf("local1 = %d, local2 = %d ,local3 = %d", local1, local2, local3)
}
运行结果
local1 = 1, local2 = 2 ,local3 = 3
全局变量:在函数体外声明的变量被称为全局变量。全局变量可以在整个包甚至在外部包(被导出后)中使用,也可以在任何函数中使用。全局变量和局部变量的名称可以相同,但是函数内部的局部变量会被优先考虑。
package main
import "fmt"
//声明全局变量
var global int
var local3 int = 333
func main() {
//声明局部变量
var local1, local2, local3 int
//初始化参数
local1 = 1
local2 = 2
local3 = 3
global = 4
fmt.Printf("local1 = %d, local2 = %d ,local3 = %d, global = %d", local1, local2, local3, global)
}
运行结果
local1 = 1, local2 = 2 ,local3 = 3, global = 4
常量
常量的声明
Go语言的常量使用关键字const声明。常量实在编译时才被创建,即便声明在函数内部也是如此,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。由于编译期有限制,声明常量的表单时必须为能被编译器求值的常量表达式。格式如下
const 常量名 [类型] = 常量值
或者
const(
常量名1 [类型] = 常量值1
常量名2 [类型] = 常量值2
)
eg正确做法:const pi = 3.14159 、 const c1 = 5/2
错误做法:const url = os.GetEnc("url") 错误原因:os.GetEnc("url") 只有在运行期才能知道返回结果
常量生成器iota
常量声明可以使用常量生成器iota初始化。iota用于生成一组以相似规则初始化的常量,但是不需要每一行进行初始化表达式。可以在const声明语句的第一行使用iota,之后美增加一行iota会加1,并且每出现一个新的const声明语句iota会重新被初始化为0。
举个例子
package main
import "fmt"
//声明常量
const (
North int = iota
East
South
West
)
func main(){
fmt.Printf("North = %d, East = %d ,South = %d, West = %d", North, East, South, West)
}
运行结果
North = 0, East = 1 ,South = 2, West = 3
流程控制语句
if-else(分支结构)
常见的书写方式有一下几种
if b > 10 {
fmt.Println("b大于10")
}
或者
if b > 10 {
fmt.Println("b大于10")
} else {
fmt.Println("b小于等于10")
}
或者
if b > 10 {
fmt.Println("b大于10")
} else if b == 10 {
fmt.Println("b等于10")
} else {
fmt.Printf("b小于10")
}
需要注意关键字if和else之后的左大括号必须和关键字在同一行,如果使用了else-if结构,那么前一段代码的右大括号必须和else if语句在同一行,这个是强制的规则,不满足则编译不通过
for循环
与大多数语言不同的是,Go中的循环语句只支持for关键字,不支持while和do-while结构。关键字for的基本用法与C语言和C++语言非常接近:
product := 1
for i :=1; i < 5; i++ {
product *= 1
}
可以看到很大的不同就是for后面的表达式不需要使用括号括起来了,Go语言还进一步考虑无线循环的场景,让开发者不需要写for(;;)和do{}-while(),直接间换乘一下写法
i := 0
for {
i++
if i > 50 {
break
}
}
或者可以简化为
var i int
for i <= 50 {
i++
}
在使用循环语句时,需要注意一下几点左大括号必须与for处于同一行
Go语言中for循环和c一样,都允许在循环条件中定义和初始化变量。唯一的区别是,Go语言不支持以逗号为检核的多赋值语句,必须使用平行赋值的方式来初始化多个变量
Go语言的for循环同样支持continue和break来控制循环,但是他提供了更高级的break——可以选择中断哪一个循环,如下例:
JumpLoop:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 5 {
break JumpLoop
}
fmt.Println(j)
}
}
for-range循环
for-range循环结构是Go语言的一种迭代结构,其应用十分的广泛。for-range可以遍历数组、切片、字符串、map及通道(channel)。 一般形式如下:
for key, val := range 复合变量值 { //...逻辑语句
}
需要注意的是,val始终为集合中对应索引值的复制值。所以对他所做的修改都不会影响集合原有的值。
通过for-range遍历的返回值有一定的规律:数组、切片、字符串返回索引和值
map返回键和值
通道只返回通道内的值
package main
import "fmt"
func main() {
//遍历数组、切片
//key,value代表切片的下标和下标所对应的值
fmt.Printf("遍历数组、切片\n")
for key, value := range []int{0, 1, -1, -2} {
fmt.Printf("key:%d value:%d\n", key, value)
}
//遍历字符串
//key,value分别代表字符串的索引和索引对应的字符
fmt.Printf("\n遍历字符串\n")
var str = "hi 加油"
for key, value := range str {
fmt.Printf("key:%d value:0x%x\n", key, value)
}
//变量中的value的实际值为rune类型,以16进制打印字符的编码
//遍历map
//key,value分别代表map的索引建和索引键对应的值
//在对map遍历时输出的键值是无序的,如果需要有序则需要对结果排序
fmt.Printf("\n遍历map\n")
m := map[string]int{
"go": 100,
"java": 100,
}
for key, value := range m {
fmt.Println(key, value)
}
//遍历channel
//在遍历通道时只输出一个值,即通道所对应类型的值数据
fmt.Printf("\n遍历channel\n")
c := make(chan int)
go func() {
c <- 7
c <- 8
c <- 9
close(c)
}()
for v := range c {
fmt.Println(v)
}
}
运行结果:
遍历数组、切片
key:0 value:0
key:1 value:1
key:2 value:-1
key:3 value:-2
遍历字符串
key:0 value:0x68
key:1 value:0x69
key:2 value:0x20
key:3 value:0x52a0
key:6 value:0x6cb9
遍历map
go 100
java 100
遍历channel
7
8
9
在使用for-range循环遍历某个对象的时候,往往不会同时使用到key和value的值,而是使用其中的一个值,这个时候可以采用一些技巧让代码变得更简单。
比如将前面的map遍历修改一下:
fmt.Printf("\n遍历map\n")
m := map[string]int{
"go": 100,
"java": 100,
}
for _, value := range m {
fmt.Println(value)
}
运行结果:
遍历map
100
100
上面的修改我们将key修改为(_),这个下划线就是匿名变量,可以理解为占位符。匿名变量本身不参加空间分配,也不会占用一个变量名称。
switch-case语句
Go语言中的switch-case语句要比C语言的switch-case语句更加通用,表达式的值不必为常量,甚至不必为整数。
Go语言改进了传统的switch-case语句的语法设计,case与case之间是独立的代码块,不需要通过break语句去避免执行到下一行。
var a = "love"
switch a {
case "love":
fmt.Println("love")
case "programming":
fmt.Println("programming")
default:
fmt.Println("none")
}
运行结果:
love
当然,Go语言还支持一些新的写法
一个分支多个值分支表达式
eg:
1、
var a = "love"
switch a {
case "love","like":
fmt.Println("love")
case "programming":
fmt.Println("programming")
default:
fmt.Println("none")
}
2、
var r int = 6
switch {
case r > 1 && r < 10:
fmt.Println(r)
}
goto
在Go语言中,可以通过goto语句跳转到标签,进行代码之间的无条件跳转。另外,goto语句在快速跳出循环、避免重复退出方面也有一定的帮助。使用goto语句能够简化一些代码的实现过程。
在满足条件的时候,如果需要连续退出两层代码,则需要传统编码方式:
a := []int{0, 1, -1, -2, -3};
var flag = false
for key,value :=range a {
for key1,value2 := range a {
if key == 2 {
fmt.Printf("%d %d",key,value)
}
if key1 == 3 {
flag = true
break
}
fmt.Printf("%d %d",key1,value2)
}
if flag {
break
}
}
将上面的代码使用Go语言的goto进行优化:
a := []int{0, 1, -1, -2, -3};
for key,value := range a {
for key1,value2 := range a {
if key == 2 {
fmt.Printf("第一处打印:%d %d\n",key,value)
}
if key1 == 3 {
goto breakTag
}
fmt.Printf("第二处打印%d %d",key1,value2)
}
breakTag:
fmt.Printf("\n")
}
在上述的代码中,我们使用goto语句“goto breakTag”来跳转到指明的标签处。breakTag是自定义的标签。上述代码中的标签只能被goto使用,不影响代码块的执行流程。在定义breakTag标签之前有一个return语句,此处如果手动返回,则在不满足条件的时候也会执行breakTag代码。
在日常开发中,经常会遇到“多错误处理”的问题,在多错误处理往往存在很多重复的问题:
err := getUserInfo()
if err != nil {
fmt.Printf(err)
exitProeccess()
return
}
err = getEmail()
if err != nil{
fmt.Printf(err)
exitProeccess()
return
}
//......
fmt.Printf("over")
在上述的代码中我们有很多重复的代码。如果后期我们继续增加条件,则需要重复的去去编写雷同代码进行重复修改,极容易出现疏忽或遗漏。这个时候我们可以使用goto语句进行处理:
err := getUserInfo()
if err != nil {
goto breakTag
}
err = getEmail()
if err != nil{
goto breakTag
}
breakTag:
fmt.Printf(err)
exitProeccess()
fmt.Printf("over")
return
break
Go语言中break语句可以结束for、switch和select的代码块。另外还可以在break语句后面添加标签,表示退出某个便签对应的代码块。田间的标签必须定义在对应的for、switch和select的代码块上。
JumpLoop:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 5 {
break JumpLoop
}
fmt.Println(j)
}
}
continue
在Go语言中,continue语句是用来结束当前循环,并开始下一次的循环迭代过程。它仅限在for循环中使用。在continue语句后面添加标签,表示结束结束标签对应的语句的当前循环,并开始下一次的外层循环。
for y := 0; y < 2; y++ {
outerLoop:
for x := 0;x < 2 ; x++ {
for i := 0; i < 3 ;i++ {
for j := 0 ; j < 5 ;j++ {
switch j {
case 3:
fmt.Println(y,x,i,j)
continue outerLoop
}
}
}
}
}
我来了