Go 语言入门指南:基础语法篇 |豆包MarsCode AI 刷题

171 阅读6分钟

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 mainfunc 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
}