01 安装
02 Hello World
新建一个文件 main.go,写入
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
执行 go run main.go 会输出
$ go run .
Hello World!
我们的第一个 Go 程序就完成了,接下来我们逐行来解读这个程序:
- package main:声明了 main.go 所在的包,Go 语言中使用包来组织代码。一般一个文件夹即一个包,包内可以暴露类型或方法供其他包使用。
- import “fmt”:fmt 是 Go 语言的一个标准库/包,用来处理标准输入输出。
- func main:main 函数是整个程序的入口,main 函数所在的包名也必须为
main。 - fmt.Println(“Hello World!”):调用 fmt 包的 Println 方法,打印出 “Hello World!”
go run main.go,其实是 2 步:
- go build main.go:编译成二进制可执行程序
- ./main:执行该程序
03 变量与内置数据类型
3.1 变量
Go 语言是静态类型,声明变量必须先明确变量类型。并且类型声明在变量后面。
var a int
var a int = 1
var a = 1
var a = 1,因为 1 是 int 类型的,所以赋值时,a 自动被确定为 int 类型,所以类型名可以省略不写,这种方式还有一种更简单的表达:
a := 1
msg := "Hello World!"
3.2 简单类型
var a int8 = 10
var c1 byte = 'a'
var b float32 = 12.2
var msg = "Hello World!"
ok := false
nil
3.3 字符串
在 Go 语言中,字符串使用 UTF8 编码,UTF8 的好处在于,如果基本是英文,每个字符占 1 byte,和 ASCII 编码是一样的,非常节省空间,如果是中文,一般占3字节。包含中文的字符串的处理方式与纯 ASCII 码构成的字符串有点区别。
我们看下面的例子:
package main
import (
"fmt"
"reflect"
)
func main() {
str1 := "Golang"
str2 := "Go语言"
fmt.Println(reflect.TypeOf(str2[2]).Kind()) // uint8
fmt.Println(str1[2], string(str1[2])) // 108 l
fmt.Printf("%d %c\n", str2[2], str2[2]) // 232 è
fmt.Println("len(str2):", len(str2)) // len(str2): 8
}
- reflect.TypeOf().Kind() 可以知道某个变量的类型,我们可以看到,字符串是以 byte 数组形式保存的,类型是 uint8,占1个 byte,打印时需要用 string 进行类型转换,否则打印的是编码值。
- 因为字符串是以 byte 数组的形式存储的,所以,
str2[2]的值并不等于语。str2 的长度len(str2)也不是 4,而是 8( Go 占 2 byte,语言占 6 byte)。
正确的处理方式是将 string 转为 rune 数组
str2 := "Go语言"
runeArr := []rune(str2)
fmt.Println(reflect.TypeOf(runeArr[2]).Kind()) // int32
fmt.Println(runeArr[2], string(runeArr[2])) // 35821 语
fmt.Println("len(runeArr):", len(runeArr)) // len(runeArr): 4
转换成 []rune 类型后,字符串中的每个字符,无论占多少个字节都用 int32 来表示,因而可以正确处理中文。
3.4 数组与切片
声明数组
var arr [5]int
var arr2 [5][5]int
var arr = [5]int{1, 2, 3, 4, 5}
使用 [] 索引/修改数组
arr := [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
arr[i] += 100
}
fmt.Println(arr)
数组的长度不能改变,如果想拼接2个数组,或是获取子数组,需要使用切片。切片是数组的抽象。切片使用数组作为底层结构。切片包含三个组件:容量,长度和指向底层数组的指针,切片可以随时进行扩展
slice1 := make([]float32, 0) // 长度为0的切片
slice2 := make([]float32, 3, 5) // [0 0 0] 长度为3容量为5的切片
fmt.Println(len(slice2), cap(slice2)) // 3 5
// 添加元素,切片容量可以根据需要自动扩展
slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4]
fmt.Println(len(slice2), cap(slice2)) // 7 12
// 子切片 [start, end)
sub1 := slice2[3:] // [1 2 3 4]
sub2 := slice2[:3] // [0 0 0]
sub3 := slice2[1:4] // [0 0 1]
// 合并切片
combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0]
- 声明切片时可以为切片设置容量大小,为切片预分配空间。在实际使用的过程中,如果容量不够,切片容量会自动扩展。
sub2...是切片解构的写法,将切片解构为 N 个独立的元素。
3.5 字典
map 类似于 java 的 HashMap、Python 的 dict,是一种存储键值对的数据结构。
m1 := make(map[string]int)
m2 := map[string]string {
"Sam" : "Male",
"Alice" : "Female",
}
m1["Tom"] = 18
指针
指针就是某个值的地址,符号定义使用 *,对于一个已经存在的变量,使用 & 获取该变量的地址:
str := "Golang"
var p *string = &str
*p = "Hello"
fmt.Println(str) //同时修改了 str 的值
一般来说,指针通常在函数传递参数,或者给某个类型定义新的方法时使用。Go 语言中,参数是按值传递的,如果不使用指针,函数内部将会拷贝一份参数的副本,对参数的修改并不会影响到外部变量的值。如果参数使用指针,对参数的传递将会影响到外部变量。
func add(num int) {
num += 1
}
func realAdd(num *int) {
*num += 1
}
func main() {
num := 100
add(num)
fmt.Println(num) //num 没有变化
realAdd(&num)
fmt.Println(num) //指针传递,num 被修改
}
04 流程控制
4.1 条件语句 if else
age := 18
if age < 18 {
fmt.Printf("Kid")
} else {
fmt.Print("Adult")
}
if age := 18; age < 18 {
fmt.Print("Kid")
} else {
fmt.Printf("Adult")
}
4.2 swicth
tyoe Gender int8
const (
MALE Gender = 1
FEMALE Gender = 2
)
gender := MALE
switch gender {
case FEMALE:
fmt.Println("female")
case MALE:
fmt.Println("male")
default:
fmt.Println("unknown")
}
// male
- 在这里,使用了
type关键字定义了一个新的类型 Gender。 - 使用 const 定义了 MALE 和 FEMALE 2 个常量,Go 语言中没有枚举(enum)的概念,一般可以用常量的方式来模拟枚举。
- 和其他语言不同的地方在于,Go 语言的 switch 不需要 break,匹配到某个 case,执行完该 case 定义的行为后,默认不会继续往下执行。如果需要继续往下执行,需要使用 fallthrough,例如:
switch gender {
case FEMALE:
fmt.Println("female")
fallthrough
case MALE:
fmt.Println("male")
fallthrough
default:
fmt.Println("unknown")
}
// 输出结果
// male
// unknown
4.3 for 循环
一个简单的累加例子,break 和 continue 用法与其他语言没有区别
sum := 0
for i := 0; i < 10; i++ {
if sum > 50 {
break
}
sum += i
}
对数组、切片、字典使用 for range 遍历:
nums := []int{10, 20, 30, 40}
for i, num := range nums{
fmt.Println(i, num)
}
m2 := map[string]string{
"Sam": "Male",
"Alice": "Female",
}
for key, value := range m2 {
fmt.Println(key, value)
}
05 函数
5.1 参数和返回值
典型的函数定义如下,使用关键字 func,函数参数可以有多个,返回值也支持有多个。特别的,package main 中 func main() 约定为可以执行程序的入口。
func funcName(paraml Type1, param2 Type2, ...)(return1 Type3, ...) {
}
实现 2 个数的加法和除法
func add(num1 int, num2 int) int {
return num1 + num2
}
func div(num1 int, num2 int) (int, int) {
return num1 / num2, num1 % num2
}
func main() {
quo, rem := div(100, 17)
fmt.Println(quo, rem)
fmt.Println(add(100, 17))
}
也可以给返回值命名,简化 return,例如 add 可以改写为
func add(num1 int, num2 int) (ans int) {
ans = num1 + num2
return
}