Go 语言入门指南:基础语法和常用特性解析 | 豆包MarsCode AI刷题

44 阅读12分钟

1.GO语言特性

  • 高性能、高并发: Go语言对于并发的支持是纯天然的,仅需一个关键字,就可以开启一个异步协程。
  • 语法简单: GO语言的语法类似于C/C++,并在其基础上进行了大幅度的简化,以此换来了更好的维护性和平滑的学习曲线
  • 快速编译: Go 是一门编译型语言,Go 语言的工具链将源代码及其依赖转换成计算机的机器指令(静态编译),编译后的二进制文件不依赖额外的运行环境,编译速度也非常快。
  • 丰富的标准库: 从字符串处理到源码AST解析,功能强大且丰富的标准库是Go语言坚实的基础。
  • 完善的工具链: Go有着完善的开发工具链,涵盖了编译,测试,依赖管理,性能分析等方方面面。 Go 语言提供的工具都通过一个单独的命令 go 调用,go 命令有一系列子命令。
  • 垃圾回收: Go有着优秀的GC性能,大部分情况下GC延时都不会超过1毫秒。

2. GO语言入门

2.1 配置开发环境

  1. 安装Golang: 浏览器输入go.dev,打开Golang的官网,点击download。
    • 打不开可以尝试Golang中国镜像( studygolang.coom/dl
    • 如果访问 github 的速度非常慢,可以配置 go mod proxy,打开 goproxy.cn/ 按照提示操作即可,配置完成你下载第三方包的速度会大大加快。
  2. 配置集成开发环境: GO的开发环境可以选择VSCode或者是Golang。
    • 只需在VSCode中的左边扩展里搜索Go插件然后安装 image.png

2.2 基础语法

Hello World

  • 示例代码
package main

import (
	"fmt"
)

func main() {
	fmt.Println("hello world")
}
  • 代码解释
    • package关键字代表的是当前go文件属于哪一个包,启动文件通常是main包,启动函数是main函数,在自定义包和函数时命名应当尽量避免与之重复。
    • import是导入关键字, 声明必须跟在文件的 package 声明之后。括号里面的是被导入的包名。标准库的FMT包主要是用来往屏幕输入输出字符串、格式字符串的。注意:必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。
    • func是函数声明关键字,用于声明一个函数。
    • fmt.Println("hello world")是一个语句,调用了fmt包下的Println函数进行控制台输出

注意:Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析。举个例子,函数的左括号 { 必须和 func 函数声明在同一行上,且位于末尾,不能独占一行。

  • 运行程序

    在命令行输入 go run fileName.go 。也可以在go build来编译,编译完成后直接./helloworld就可以运行

nil

在Go语言中,nil 是一个预声明的**标识符,用来表示:

  1. 一个指针、通道、函数、接口或切片类型的变量没有任何指向或包含的值(即没有指向任何东西)。
  2. 一个函数没有返回值。
  3. 一个方法没有绑定到特定的接收者。

变量

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

变量声明

在GO中的类型声明是后置的,声明方式如下:

  • 第一种:指定变量类型,如果没有初始化,则变量默认为零值。例如: var b int
  • 第二种:根据值自行判断变量类型。例如: var d = true
  • 第三种:使用 := (如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误) 例如: f := "Runoob"

示例代码

package main
import (
	"fmt"
	"math"
)
func main() {
	var a = "initial"
	var b, c int = 1, 2
	var d = true
	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
	const s string = "constant"
	const h = 500000000
	const i = 3e20 / h
	fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

控制流程

示例代码

package main
import (
	"fmt"
	"time"
)
func main() {
	// If 语句
	if 8 > 5 {
		fmt.Println("8 is greater than 5")
	} else {
		fmt.Println("8 is not greater than 5")
	}
	// Switch 语句
	i := 2
	switch i {
	case 1:
		fmt.Println("i is 1")
	case 2:
		fmt.Println("i is 2")
	case 3:
		fmt.Println("i is 3")
	default:
		fmt.Println("i is none of the above")
	}
	// For 循环
	for i := 0; i < 3; i++ {
		fmt.Println("iteration", i)
	}
	// For 循环的另一种形式,相当于 C 语言中的 while 循环
	for {
		fmt.Println("loop forever")
		break // 需要一个 break 来退出循环,否则会无限循环
	}
}
条件语句
if else

Go语言和C/C++的if else语句的区别

  • Go语言的if语句不需要括号包围条件表达式。
  • Go语言的if语句中,条件后面不需要分号。
  • Go语言支持if语句的简短形式,即可以在条件语句中直接声明变量(短变量声明),该变量的作用域仅在if代码块内。

注意:Go 没有三目运算符,所以不支持  ?:  形式的条件判断。

switch

Go语言和C/C++的switch语句的区别

  • Go语言的switch不需要break语句,每个case块执行完毕后会自动退出。
  • case后面可以是任何类型的表达式,不仅仅是整数。
  • 如果没有指定default,并且没有匹配的case,则switch语句什么也不做,直接结束。
  • Go语言的switch可以不使用任何变量或表达式,直接根据条件判断执行不同的case块。

循环语句

Go语言和C/C++的循环语句的区别

  • Go语言的for循环更加简洁,不需要初始化、条件和迭代部分,可以直接只写条件。
  • Go语言的for循环中,条件后面不需要分号。
  • Go语言没有whiledo-while循环,所有的循环控制都使用for循环实现。

数组

GO语言的数组与C/C++类似。不过在实际开发过程中,因为数组的长度是固定的,我们更多使用的是切片。

声明数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var arrayName [size]dataType

初始化数组

  • 指定数组长度的方式初始化:在声明时,数组中的每个元素会根据其数据类型进行默认初始化,对于整数类型,初始值为0。例如:var a [5]int
  • 使用值列表的方式初始化: a := [5]int{1, 2, 3, 4, 5}

示例代码

package main
import "fmt"
func main() {
	var a [5]int
	a[4] = 100
	fmt.Println("get:", a[2])
	fmt.Println("len:", len(a))
	b := [5]int{1, 2, 3, 4, 5}
	fmt.Println(b)
	var twoD [2][3]int
	for i := 0; i < 2; i++ {
		for j := 0; j < 3; j++ {
			twoD[i][j] = i + j
		}
	}
	fmt.Println("2d: ", twoD)
}

切片(Slice)

Go 语言切片是对数组的抽象。Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。

定义切片

  • 声明一个未指定大小的数组来定义切片:var sclicName []type
  • 使用make() 函数来创建切片slice1:=make([]T, length, capacity)

切片初始化

  • 直接初始化切片:s :=[] int {1,2,3 }
  • 初始化切片 s,是数组 arr 的引用:s := arr[startIndex:endIndex]
  • 通过切片 s 初始化切片 s1
s1 := s[startIndex:endIndex] 
s :=make([]int,len,cap) 

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0

切片截取

slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。新的slice将只有j-i个元素。如果i位置的索引被省略的话将使用0代替,如果j位置的索引被省略的话将使用len(s)代替。

len() 和 cap() 函数

len(splic):切片是可索引的,并且可以由 len() 方法获取长度。

cap(splic):切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

package main
import "fmt"
func main() {
	s := make([]string, 3)
	s[0] = "a"
	s[1] = "b"
	s[2] = "c"

	s = append(s, "d")
	s = append(s, "e", "f")
	fmt.Println(s) // [a b c d e f]

	c := make([]string, len(s))
	copy(c, s)
	fmt.Println(c) // [a b c d e f]

	fmt.Println(s[2:5]) // [c d e]
	fmt.Println(s[:5])  // [a b c d e]
	fmt.Println(s[2:])  // [c d e f]

	good := []string{"g", "o", "o", "d"}
	fmt.Println(good) // [g o o d]
}

map

map的特点

  • Map 是一种无序的键值对的集合。
  • Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
  • Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。
  • 在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。
  • Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

定义map

可以使用内建函数 make 或使用 map 关键字来定义 Map:

/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)

创建 Map

使用字面量创建 Map

m := map[string]int{
    "apple": 1,
    "banana": 2,
    "orange": 3,
}

获取元素

// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"]  // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值

修改元素

// 修改键值对
m["apple"] = 5

获取 Map 的长度

// 获取 Map 的长度
len := len(m)

遍历 Map

// 遍历 Map
for k, v := range m {
    fmt.Printf("key=%s, value=%d\n", k, v)
}

删除元素

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key

// 删除键值对
delete(m, "banana")

range

对于一个 slice 或者一个 map 的话,我们可以用 range 来快速遍历,这样代码能够更加简洁。 range 遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。如果我们不需要索引的话,我们可以用下划线来忽略。

package main

import "fmt"

func main() {
	nums := []int{2, 3, 4}
	sum := 0
	for i, num := range nums {
		sum += num
		if num == 2 {
			fmt.Println("index:", i, "num:", num) // index: 0 num: 2
		}
	}
	fmt.Println(sum) // 9

	m := map[string]string{"a": "A", "b": "B"}
	for k, v := range m {
		fmt.Println(k, v) // b 8; a A
	}
	for k := range m {
		fmt.Println("key", k) // key a; key b
	}
}

函数

函数定义

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

函数返回多个值

Go 函数可以返回多个值,例如:

package main  
  
import "fmt"  
  
func swap(x, y string) (stringstring) {  
   return y, x  
}  
  
func main() {  
   a, b := swap("Google""Runoob")  
   fmt.Println(a, b)  //执行结果:Runoob Google
}

指针

相比 C 和 C++ 里面的指针,支持的操作很有限。指针的一个主要用途就是对于传入参数进行修改。

  • 什么是指针

一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。指针声明格式:

var var_name *var-type

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */
  • 如何使用指针

    • 定义指针变量。
    • 为指针变量赋值。
    • 访问指针变量中指向地址的值。
    • 在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
  • 示例代码

package main

import "fmt"

func add2(n int) {
	n += 2
}

func add2ptr(n *int) {
	*n += 2
}

func main() {
	n := 5
	add2(n)
	fmt.Println(n) // 5
	add2ptr(&n)
	fmt.Println(n) // 7
}