基本语法
# 基本语法
# 基础
# hello world
众所周知,学习一门语言最开始就是输出一句hello world,下面使用Goland实现
这里创建名为study-demo的项目,在根目录下创建hello.go文件
package main
import "fmt"
func main() {
fmt.Println("hello world!")
}
2
3
4
5
6
7
8
运行即可查看结果:
上述代码的相关解释:
- 在Go语言里,命名为 main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个名叫 main的包。一个可执行程序有且仅有一个main包。当编译器发现某个包的名字为main时,它一定也会发现名为main()的函数,否则不会创建可执行文件。main()函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明main包的代码所在的目录的目录名作为二进制可执行文件的文件名。
- 这里的import类似于java中的import,就是导入一些官方库或第三方库
# 变量
# 定义
var name string = "test"
name = "zdk"
2
var表示定义变量,在变量第一次声明时,必须存在,再次对变量赋值时,即可省略。这里如果定义的变量不使用,goland会提示报错
- var是声明变量的关键字,固定写法
- 第二个name是变量的名称
- 第三个string是变量的类型
- =号后即是变量值
这里其实可以发现它和java的写法是相反的
package main
import "fmt"
func main() {
var name string = "test"
name = "zdk"
fmt.Println(name)
var username string = "zdk"
username = "111"
fmt.Println(username)
}
2
3
4
5
6
7
8
9
10
11
12
13
# 多个定义
package main
import "fmt"
func main() {
var (
id int
password string
age int
)
//在未初始化时,输出的是语言设定类型的默认值
fmt.Println(id, password, age)
//输出:0 0 中间是空白的 string的默认值是""
// 这种方式可以打印变量的内存地址 &和c一样 为取地址符
fmt.Printf("username的值:%s,地址:%p\n", username, &username)
//username的值:111,地址:0xc00004c280
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。当一个变量被声明之后,如果没有显示的给变量赋值,系统自动赋予它该类型的默认值:
- 整型和浮点型变量的默认值为0和0.0。
- 字符串变量的默认值为空字符串。
- 布尔型变量默认为false。
- 切片、函数、指针变量的默认为nil。
# 短变量声明并初始化
package main
import "fmt"
func main() {
name := "xxx"
age := 18
}
2
3
4
5
6
7
8
9
可以使用
变量名 := 变量值
的方式简化变量定义,编译器会根据变量值推导变量类型
这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型。
它可以自动的推导出一些类型,但是使用也是有限制的;
定义变量,同时显式初始化。
不能提供数据类型。
只能用在函数内部。不能随便到处定义
因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。 注意:由于使用了:=,而不是赋值的=,因此推导声明写法的左值变量必须是没有定义过的变量。若定义过,将会发生编译错误。
# 变量交换
var a int = 100
var b int = 200
a, b = b, a
fmt.Println(a, b)
2
3
4
可使用以上方式方便地交换变量值
# 匿名变量
匿名变量的特点是一个下画线" _"," _"本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。例如:
package main
import "fmt"
func test() (int, int) {
return 100, 200
}
func main() {
a, _ := test()
_, b := test()
//输出结果为 100 200
fmt.Println(a, b)
// 在第一行代码中 我们只需要第一个返回值 所以第二个返回值为匿名变量
// 在第二行代码中 我们只需要第二个返回值 所以第一个返回值为匿名变量
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在编码过程中,可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。
匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。
# 变量的作用域
一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。
了解变量的作用域对我们学习Go语言来说是比较重要的,因为Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。如果不能理解变量的作用域,就有可能会带来一些不明所以的编译错误。
# 局部变量
在函数体内部声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。
package main
import (
"fmt"
)
func main(){
//声明局部变量a和b
var a int = 3
var b int = 4
//声明局部变量c
c := a + b
fmt.Printf("a = %d, b = %d, c = %d\n",a, b, b)
}
2
3
4
5
6
7
8
9
10
11
12
13
# 全局变量
在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用"import"关键字引入全局变量所在的源文件之后才能使用这个全局变量。 全局变量声明必须以var关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。
package main
import (
"fmt"
)
var name string = "zdk"
func main() {
var name string = "2222"
fmt.Println(name)
}
2
3
4
5
6
7
8
9
10
11
12
在go语言中,全局变量和局部变量可以使用相同的名称,但是在调用时会优先调用局部变量
# 常量
常量时一个简单值的标识符,在程序运行时,不会被修改的值。
常量中的数据类型只可以是布尔型、数字型(整型、浮点型、复数)和字符串型
const identifier [type] = value
你可以省略类型说明符[type],因为编译器可以根据变量的值来推断其类型。
- 显式类型定义:
const b string = "abc"
- 隐式类型定义:
const b = "abc"
多个相同类型的声明可以简写为:
const c_name1, c_name2 = value1, value2
示例:
package main
func main() {
const URL = "www.baidu.com"
const PI = 3.14
const LENGTH = 10
const WIDTH int = 5
//多重赋值
const a, b, c = 1, false, "str"
}
2
3
4
5
6
7
8
9
10
11
# iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。iota是go语言的常量计数器
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)
iota可以被用作枚举值
const(
a = iota
b = iota
c = iota
)
2
3
4
5
第一个iota等于0,每当iota在新的一行被使用时,它的值都会自动加1;所以a=0,b=1,c=2,可以简写为以下形式
const(
//一组常量中,如果某个常量没有初始值,默认和上一行的常量一致
a = iota
b
c
)
2
3
4
5
6
代码示例:
package main
import "fmt"
func main() {
const (
a = iota
b
c
d = "haha"
e
f = 100
g
h = iota
i
)
const (
j = iota
k
)
fmt.Println(a, b, c, d, e, f, g, h, i)
//输出 0 1 2 haha haha 100 100 7 8
fmt.Println(a, b, c, d, e, f, g, h, i, j, k)
//输出 0 1 2 haha haha 100 100 7 8 0 1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这里表示,iota是一直在增加的,它其实就是const语句块中常量的数量,然后体现了未赋值时,常量的值等于它上一个常量的值。
而每个const块中的iota都从0开始,所以两个const块之间的iota并无关系
# 基本数据类型
# 布尔
package main
import "fmt"
func main() {
//布尔值demo 默认值是false
var flag bool = true
fmt.Println(flag)
// 输出 类型:bool,值:true
fmt.Printf("类型:%T,值:%t\n", flag, flag)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数字
整型int和浮点型float32,float64,Go语言支持整型和浮点型数字,并且支持复数,其中的位运算采用补码。
Go也有基于架构的类型,例如:uint无符号、int有符号
# 整型
序号 | 类型和描述 |
---|---|
1 | unint8 无符号8位整型(0~255) |
2 | unint16 无符号16位整型(0~65535) |
3 | unint32 无符号32位整型(0~4294967295) |
4 | unint64 无符号64位整型(0~18446744073709551615) |
5 | int8 有符号8位整型(-128~127) |
6 | int16 有符号16位整型(-32768~32767) |
7 | int32 有符号32位整型(-2147483648~2147483647) |
8 | int64 有符号64位整型(-9223372036854775808~9223372036854775807) |
# 浮点型
编号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | comples64 32位实数和虚数 |
4 | comples128 64位实数和虚数 |
- 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位
- 尾数部分可能丢失,造成精度损失。-123.0000901
- golang的浮点型默认为float64
- 通常情况下,应该使用float64,因为它比float32更精确(保存精度高的数)
以下列出了其他更多的数字类型:
序号 | 类型和描述 |
---|---|
1 | byte 类似uint8 |
2 | rune 类型int32 |
3 | uint 32或64位 |
4 | int 与uint一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
# 字符串
这里不过多赘述,使用""定义时,变量类型为string,使用''定义时,变量类型实为int32,表示字符在ASCII码表中对应的值
字符串连接同样使用 + 号
# 数据类型转换
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go不存在隐式类型转换,因此所有的类型转换都必须显式地声明:
valueOfTypeB = typeB(valueOfTypeA)
类型A变量转为类型B -> 即 类型B的值 = 类型B(类型A的值)
func main(){
a := 5.0
b := int(a)
fmt.Printf("%T,%f\n",a,a)
fmt.Printf("%T,%d\n",b,b)
}
2
3
4
5
6
7
类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将int16转换为int32)。当从一个取值范围较大的类型转换到取值范围较小的类型时(将int32转换为int16或将float32转换为 int),会发生精度丢失(截断)的情况。
# 运算符
基本和java一致 不赘述
# 键盘交互
package main
import "fmt"
func main() {
var x, y int
fmt.Scanln(&x, &y)
fmt.Println("x:", x)
fmt.Println("y:", y)
}
2
3
4
5
6
7
8
9
10
11
12
13
# 流程控制
# if
if、else、else if这些与java的差不多,只是判断条件不用括号括住
func main(){
x := 10
y := 20
if x > y {
//操作
}else {
//操作
}
}
2
3
4
5
6
7
8
9
# switch
switch var1 {
case value1:
...
case value2:
...
default:
...
}
2
3
4
5
6
7
8
匹配项后面不需要再加break,switch默认情况下case最后自带break语句
switch {
case true:
...
case false:
...
default:
...
}
2
3
4
5
6
7
8
这里switch后面没有变量,其实表示
switch true
,即判断是否为true
# fallthrough
switch默认情况下匹配成功后不会执行其他case,如果我们需要执行后面的case,可以使用
fallthrough
穿透case,可以强制执行后面的case语句,fallthrough不会判断下一条case的表达式结果是否为true
package main
import "fmt"
func main() {
a := 1
switch a {
case 1:
fmt.Println("1111")
fallthrough
case 2:
fmt.Println("2222")
case 3:
fmt.Println("333")
case 4:
fmt.Println("4444")
default:
fmt.Println("default")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上述代码输出:
1111 2222
表明了添加了fallthrough的case,只会穿透到它的下一个case,并不会穿透后面的所有case,同时满足case后default是不执行的(和java一样)
# for
for i := 0; i < 10; i++ {
}
2
3
与java区别只是for后面的语句不加括号,类比上面的if
还有go提供的其他循环方式
package main
import "fmt"
func main() {
str := "jdasijd"
for i := 0; i < len(str); i++ {
//这样打印其实打印的是字符在ASCII码表中的数字
fmt.Print(str[i])
// uint8
fmt.Printf("%T",str[i])
}
// 只写一个变量 默认就是下标
for index := range str {
fmt.Print(index)
}
// 两个变量就是 下标 值
for index, element := range str {
fmt.Print(index)
//这样打印其实打印的是字符在ASCII码表中的数字
fmt.Print(element)
fmt.Printf("%c\n", element)
}
// 只需要值时可以使用匿名变量
for _, value := range str {
fmt.Print(element)
fmt.Printf("%c\n", element)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
上面代码中的str[i]的类型实际是uint8
# 数组/Slice
# 固定长度数组
//固定长度的数组
var arr1 [10]int
arr1 = [10]int{1, 2, 3}
fmt.Println(arr1)
2
3
4
# 切片
//切片 类型是 []int
var arr2 = []int{11, 22, 32}
fmt.Printf("%T\n", arr2)
for _, value := range arr2 {
fmt.Println(value)
}
2
3
4
5
6
在使用时操作和数组是完全一样的。且在被传递到函数中的时候,就不再是值拷贝了,而是引用传递,在函数中修改会被应用
# 声明方式
// 声明slice的方式
//1. 以下的声明方式 var定义后 slice还没有被赋予地址
// 它是 len = 0,slice1 = []
var slice1 []int
fmt.Printf("len = %d,slice1 = %v\n", len(slice1), slice1)
if slice1 == nil {
fmt.Println("slice1是nil")
} else {
fmt.Println("bushi")
}
// 使用make开辟3个int类型的长度的地址空间后 是 len = 3,slice1 = [0 0 0]
slice1 = make([]int, 3)
fmt.Printf("len = %d,slice1 = %v\n", len(slice1), slice1)
//2.显式直接声明 这里前面的[]int可省略 因为类型推断
var slice2 []int = []int{1, 2, 3}
fmt.Printf("len = %d,slice2 = %v\n", len(slice2), slice2)
//3.方式1的直接赋值
slice3 := make([]int, 3)
fmt.Printf("len = %d,slice3 = %v\n", len(slice3), slice3)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
make的第二个参数表示初始化的长度,第三个参数表示容量,如果第三参数不写,则默认等于第二个参数
var numbers []int = make([]int, 3, 5) fmt.Printf("len = %d,cap = %d", len(numbers), cap(numbers))
1
2输出:len = 3,cap = 5
# 追加
使用append方法对切片进行追加元素,参数2是可变参数,即可追加多个元素到末尾 但不能超过cap
//向numbers追加元素,参数2是可变参数,即可追加多个元素到末尾
numbers = append(numbers, 1)
fmt.Printf("len = %d,cap = %d\n", len(numbers), cap(numbers))
// len = 4,cap = 5 {0,0,0,1}
2
3
4
当此时的len已经达到cap,想要再次添加元素的时候,go会将numbers扩容为原来cap的2倍
numbers = append(numbers, 22, 33, 44)
fmt.Printf("len = %d,cap = %d\n", len(numbers), cap(numbers))
// len = 7,cap = 10
2
3
实际是重新开辟了内存,然后将原来的拷贝过去再append
# 截取
使用 切片[start:end]的方式进行截取,截取内容是下标左闭右开[start,end)
sub1 := numbers[0:2]
sub1[0] = 10086
fmt.Println("修改后numbers:", numbers)
fmt.Println("修改后sub1:", sub1)
//修改后numbers: [10086 0 0 1 22 33 44]
//修改后sub1: [10086 0]
2
3
4
5
6
这种截取方式实质两者还是指向同一个地址,修改时都会修改
# map
# 定义方式
package main
import "fmt"
func main() {
var map1 map[string]string
map1 = make(map[string]string, 10)
str := "zdk"
map1[str] = "111"
fmt.Println("map1[str]:", map1[str])
// 或者 添加时会自动扩容
map2 := make(map[string]string)
fmt.Println("map2:", map2)
// 或者显式值声明
map3 := map[string]string{
"one": "php",
"two": "c++",
"three": "python",
}
fmt.Println("map3:", map3)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 使用方式
package main
import "fmt"
func printMap(mapData map[int]string) {
for key, value := range mapData {
fmt.Println("key = ", key)
fmt.Println("value = ", value)
}
}
func main() {
userMap := make(map[int]string)
//添加
userMap[1] = "zdk1"
userMap[2] = "zdk2"
userMap[3] = "zdk3"
userMap[4] = "zdk4"
//遍历
printMap(userMap)
//删除
delete(userMap, 1)
//修改
userMap[3] = "woshi3545"
fmt.Println("-----------------")
printMap(userMap)
}
/** 输出
key = 1
value = zdk1
key = 2
value = zdk2
key = 3
value = zdk3
key = 4
value = zdk4
-----------------
key = 2
value = zdk2
key = 3
value = woshi3545
key = 4
value = zdk4
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44