Go语言基础语法
一、创建工程
说明:
(1)go.mod文件是go项目依赖管理文件,相当于前端的package.json,也就是Java项目中的Maven的pom.xml。
二、打印数据到控制台
(1)引入fmt
(2)使用fmt.Sprintf,参数为打印的变量(非必须)
(3)用Println()打印
例如:
package mainimport(
"fmt"
)func main(){
//%d表示整型数字,%s表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)//输出结果为"Code=123&endDate=2020-12-31"
}
Go字符串格式化符号:
%v :按值的本来值输出
%+v:在%v的基础上,对结构体字段名和值进行展开
%#v:输出Go语言语法格式的值
%T:输出Go语言语法格式的类型和值
%%:输出%本体
%b:类型以二进制方式显示
%o:类型以八进制的方式显示
%d:类型以十进制的方式显示
%x:类型以十六进制的方式显示
%X:整数以十六进制、字母大写方式显示
%U:Unicode字符
%f:浮点数
%p:指针,十六进制方式显示
三、Go语言的变量
3.1 变量类型
3.1.1 基本类型
整型:int8、int16、int32、int64、uint8、uint16、uint32、uint64
浮点型:float32、float64
复数型:complex64、complex128
其他常用数字类型:byte(uint8)、int[32|64]、
字节类型:rune(int32),类似与其他语言里的char,可以表示任何Unicode字符,包括ASCII字符、中文字符、特殊符号等
字符串型:string(默认值为""),注意,Go的字符串有双引号和反引号两种,即"abc"和 abc
.
布尔型:bool,值为true或false(默认值为false)
3.1.2 派生类型
3.1.2.1 指针
var a=10;var b *int = & a;
注意:Go中的指针只可以通过"&“取指针的地址,通过”*"取指针指向的数据,不支持对指针进行自增或自减运算,不支持对指针进行下标运算。
3.1.2.2数组和切片
Go中切片和数组是两个不同的数据结构,区别如下,
数组长度固定,内存连续且是值类型(即数组作为参数传给函数时,会复制整个数组)。
切片长度可变,内存连续,且是引用类型(切片实际上是一个数据结构,它包含了指向底层数组的指针、切片的长度(len)和容量(cap)。所以切片本身是一个引用类型),切片提供了更灵活的操作,如append,copy。
var arr [2]int = [2]int{2, 3, 4}//定义数组
var arr []int = []int{2, 3, 4}//定义切片
例如:
package mainimport "fmt"func main() {// 数组var arr [5]intarr[0] = 1arr[1] = 2fmt.Println("Array:", arr)// 切片s := []int{1, 2, 3}fmt.Println("Slice:", s)// 切片的append操作s = append(s, 4, 5)fmt.Println("Appended Slice:", s)// 数组传递给函数(值传递)modifyArray(arr)fmt.Println("Modified Array:", arr) // 输出:Modified Array: [1 2 0 0 0]// 切片传递给函数(引用传递)modifySlice1(s)fmt.Println("Modified1 Slice:", s) // 输出:Modified Slice: [1 2 3 4 5 ],值未变modifySlice2(s)fmt.Println("Modified2 Slice:", s)
}func modifyArray(arr [5]int) {arr[2] = 3
}func modifySlice1(s []int) {s = append(s, 6)
}
func modifySlice2(s []int) {s[0] = 2}
说明:可以看出通过 append 操作向切片添加元素时,如果切片容量足够,append 会在原切片的基础上添加元素并更新长度。但是,如果切片容量不足,append 会创建一个新的底层数组,将原切片的内容复制到新数组中,并添加新元素,然后返回指向这个新数组的切片。
为了解决这个问题,可以使用指针作为参数或者返回新切片的值:
package mainimport "fmt"func main() {// 切片s := []int{1, 2, 3}fmt.Println("Slice:", s)// 切片传递给函数(引用传递)modifySlice1(&s)fmt.Println("Modified1 Slice:", s) // 输出:Modified Slice: [1 2 3 4 5 ],值未变s1 := modifySlice2(s)fmt.Println("Modified2 Slice:", s)fmt.Println("Modified2 Slice:", s1)
}func modifySlice1(s *[]int) {*s = append(*s, 6)
}
func modifySlice2(s []int) []int {s = append(s, 7)return s
}
执行结果:
另外,若切片是基于数组创建,则在数组长度范围内修改切片,数组的值也发生变化,超过数组长度后则增加的那部分内容不会影响原数组:
package mainimport "fmt"func main() {// 定义一个数组arr := [5]int{1, 2, 3, 4, 5}fmt.Println("数组:", arr)// 定义一个切片并引用数组的一部分slice := arr[1:4]fmt.Println("切片:", slice)// 修改切片中的值,同时会影响到数组slice[0] = 99fmt.Println("修改切片后,数组:", arr)slice = append(slice, 9)slice = append(slice, 10)fmt.Println("增长切片后,切片:", slice)fmt.Println("增长切片后,数组:", arr)
}
执行结果如下:
3.1.2.3 映射(字典)
例如:
var a map[String]int//声明了一个变量a,类型为map,键类型为字符串,值类型为整型
3.1.2.4 结构体
type User struct {Id intUsername stringAge byte}var user Useruser.Id = 20
注意:Go结构体传参是传值,不是传引用。
3.1.2.5 接口
在Go语言中,接口(interface)是一种非常重要的类型,它定义了一组方法签名,但不实现这些方法。接口由类型来具体实现,而一个类型只要实现了接口中的所有方法,它就隐式地实现了该接口,无需显式声明。
接口的定义
接口通过type关键字和interface类型来定义。接口内部可以包含零个或多个方法签名。
type Animal interface {Speak() string
}
在这个例子中,Animal接口定义了一个方法Speak,它返回一个字符串。
实现接口
在Go中,一个类型只要实现了接口中的所有方法,它就隐式地实现了该接口。不需要显式声明。
type Dog struct{}func (d Dog) Speak() string {return "Woof!"
}type Cat struct{}func (c Cat) Speak() string {return "Meow!"
}
在这个例子中,Dog和Cat类型都实现了Animal接口,因为它们都提供了Speak方法。
使用接口
接口可以作为函数参数、返回值和变量类型。
func MakeAnimalSpeak(a Animal) {fmt.Println(a.Speak())
}func main() {dog := Dog{}cat := Cat{}MakeAnimalSpeak(dog) // 输出: Woof!MakeAnimalSpeak(cat) // 输出: Meow!
}
空接口
空接口interface{}是一个特殊的接口,它不包含任何方法。因此,所有类型都隐式地实现了空接口。空接口通常用于表示任意类型。
func PrintAnything(v interface{}) {fmt.Println(v)
}func main() {PrintAnything(42)PrintAnything("Hello, World!")PrintAnything(true)
}
类型断言
有时我们需要将接口类型的变量转换回其具体的类型,这时可以使用类型断言。
func main() {var i interface{} = "Hello"s, ok := i.(string)if ok {fmt.Println(s)} else {fmt.Println("Type assertion failed")}
}
在这个例子中,i.(string)是一个类型断言,它将i断言为string类型。如果断言成功,ok为true,并且s将包含i的值;如果断言失败,ok为false。
类型选择
类型选择(type switch)是另一种处理接口类型变量的方法,它可以根据变量的动态类型执行不同的分支。
func WhatTypeIsIt(i interface{}) {switch v := i.(type) {case int:fmt.Println("It's an int:", v)case string:fmt.Println("It's a string:", v)case bool:fmt.Println("It's a bool:", v)default:fmt.Println("Unknown type")}
}func main() {WhatTypeIsIt(42)WhatTypeIsIt("Hello")WhatTypeIsIt(true)
}
在这个例子中,switch v := i.(type)会根据i的实际类型执行不同的分支。
总结
Go语言的接口提供了一种灵活且强大的方式来定义和使用多态性。通过接口,我们可以编写更加通用和可维护的代码。
3.2 变量声明
3.2.1 完整声明
Go语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
声明变量的一般形式是使用var关键字。
语法:
var <变量名> <类型>;
可以一次声明多个变量:
var <变量名1>,<变量名2> <类型>;
例如:
var a string = "ABC"
var b,c int = 1, 2
注意:如果没有初始化,则变量默认值为零。
3.2.2 根据值自动判定变量类型
var d = true
上述例子的var有些多余,可以用赋值操作符:来简写,称之为“初始化声明”:
b := true
注意:初始化声明只能被用在函数体内,而不可以用于全局变量的声明和赋值。
3.3 Go语言常量
使用关键字const替代var,常量的数据类型只能是基本类型,可以省略数据类型。
const b string="abc"
const b = "abc"
3.4 关键字go
go关键字用来启动一个新的goroutine。
package mainimport ("fmt""time"
)func hello() {fmt.Println("Hello world goroutine")
}func main() {go hello() // 启动新的 Goroutinefmt.Println("Hello main goroutine")time.Sleep(1 * time.Second) // 等待新的 Goroutine 执行完成
}
执行结果如下:
3.5 Go的通道chan
在 Go 语言中,chan 关键字用于创建通道(channel),通道是一种用于在 goroutine 之间进行通信的类型化管道。通过通道,你可以在不同的 goroutine 之间安全地传递数据。
(1)使用make函数来创建通道
通道的类型由其传递的数据类型决定。
(2)发送和接受数据
发送数据:通道 <- 值
接收数据:值 := <-通道
例如:
ch := make(chan int) // 在一个 goroutine 中发送数据
go func() { ch <- 42
}() // 在主 goroutine 中接收数据
value := <-ch
fmt.Println(value) // 输出: 42
(3)带缓冲的通道
默认情况下,通道是无缓冲的,这意味着发送操作会阻塞,直到另一方准备好接收数据。你可以通过提供一个缓冲区大小来创建一个带缓冲的通道。
例如:
ch := make(chan int, 2) // 创建一个带缓冲区的通道,可以存储 2 个整数
带缓冲的通道在缓冲区未满时不会阻塞发送操作,在缓冲区为空时不会阻塞接收操作。
(4)关闭通道
你可以使用 close 函数来关闭一个通道。关闭通道后,无法再向通道发送数据,但可以继续从通道接收数据,直到通道为空。
例如:
ch := make(chan int) go func() { ch <- 42 close(ch)
}() value, ok := <-ch
if ok { fmt.Println(value) // 输出: 42
} else { fmt.Println("通道已关闭")
}
3.6 range关键字
在 Go 语言中,range 关键字具有多重用途,主要用于遍历数组、切片(slice)、映射(map)、字符串以及通道(channel)。下面分别介绍这些用法:
遍历数组和切片
对于数组和切片,range 会返回两个值:索引和对应位置的元素值。
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers { fmt.Println(index, value)
}
在上面的例子中,index 是元素的索引,而 value 是对应索引的元素值。
遍历映射
对于映射,range 会返回两个值:键和对应的值。
scores := map[string]int{"Alice": 90, "Bob": 85}
for key, value := range scores { fmt.Println(key, value)
}
在这个例子中,key 是映射的键,而 value 是与键关联的值。
遍历字符串
对于字符串,range 会返回两个值:字符的索引(字节位置)和对应的 Unicode 码点(rune)。
str := "Hello, 世界"
for index, runeValue := range str { fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}
注意,对于非 ASCII 字符(如中文),一个字符可能会占用多个字节。因此,这里的索引是指字节在字符串中的位置,而不是字符在字符串中的位置(对于多字节字符,后者可能更有用,但 Go 的 range 在字符串上不提供这种索引)。
遍历通道
对于通道,range 会持续地从通道中接收数据,直到通道被关闭。
ch := make(chan int)
go func() { for i := 0; i < 5; i++ { ch <- i } close(ch)
}() for value := range ch { fmt.Println(value)
}
在这个例子中,range 会阻塞,直到从通道 ch 中接收到数据。当通道被关闭且没有更多数据可读时,range 循环会结束。
注意事项
当使用 range 遍历映射时,遍历的顺序是随机的,每次运行程序时可能会得到不同的顺序。
在遍历数组、切片或字符串时,如果你只需要索引或值中的一个,可以使用下划线 _ 来忽略另一个。
在遍历通道时,确保通道最终会被关闭,否则 range 循环将永远阻塞。
- range 在内部使用了值拷贝,因此遍历过程中修改元素的值(对于数组和切片)不会影响原始数组或切片。然而,如果你传递的是一个指向元素的指针(例如,切片中的元素是指针类型),则可以通过指针修改原始数据。
3.7 Go语言select语句
select 语句允许你在多个通道操作上进行等待。select 会阻塞,直到其中一个 case 可以运行。如果有多个 case 都准备好了,select 会随机选择一个执行。如果所有的通道都没有准备好,就会执行default块中的代码。
ch1 := make(chan string)
ch2 := make(chan string) go func() { ch1 <- "来自 ch1 的消息"
}() go func() { ch2 <- "来自 ch2 的消息"
}() for i := 0; i < 2; i++ { select { case msg1 := <-ch1: fmt.Println("收到:", msg1) case msg2 := <-ch2: fmt.Println("收到:", msg2) default://代码}
}
执行结果;
四、Go语言的特点
(1)并发支持
内置轻量级的并发机制,称为goroutine,可以通过goroutine和通道,方便地编写并发程序。
(2)高性能
通过优化编译器和运行时环境,以及并发机制地支持,提供了出色地性能。
(3)内存安全
具有内置地垃圾回收机制,避免了常见的内存错误。
(4)跨平台
编译器可以将Go代码编译为机器码,支持多种操作系统和体系结构。
(5)丰富的标准库
涵盖了网络编程、文件操作、加密解密并发编程等各个方面。
五、内存回收机制
Go对局部变量的生命周期不做假设,而是根据是否被引用了来决定对象被创建在堆上还是栈上,这一过程称之为内存escape。Go预言的内存回收机制规定,只要有一个指针指向引用一个变量,那么这个变量就不会被释放,因此在Go语言中返回函数参数或临时变量是安全的。例如:
type TimesMatcher struct{base int
}
func NewTimesMatcher(base int) *TimesMatcher{return &TimesMatcher{base:base}
}
func main(){p := NewTimesMatcher(3)
}
上面代码中的指针p为野指针,因为返回的栈内存在函数结束时会被释放。
六、函数
Go的函数可以接收输入参数,并返回一个或多个值,报错错误值。
6.1 语法
func functionName(parameters) (results) {// 函数体
}
说明:
result是函数返回类型列表,可以为空,如果有多个返回值,它们之间用逗号分割,并用括号括起来。
例如:
func Add(a int, b int) int {//接收两个整数并返回它们的和return a + b
}
调用函数只需写出函数名和必要的参数值即可:
result := Add(3, 4)
fmt.Println(result) // 输出: 7
6.2 返回值(错误值)
Go语言的函数可以返回多个值,这对处理错误特别有用,因为你可以同时返回结果和错误状态:
func ReadFile(filename string) (string, error) {// 假设这里有一个读取文件的操作// 如果读取成功,返回文件内容和nil// 如果读取失败,返回空字符串和相应的错误return "", errors.New("file not found") // 示例错误
}
调用这个函数时,需要准备接收两个返回值:
content, err := ReadFile("example.txt")
if err != nil {fmt.Println("Error:", err)return
}
fmt.Println("File content:", content)
6.3 延迟调用defer
defer语句用于延迟执行一个函数,直到包含它的函数执行完毕。它通常用于确保资源被释放,如关闭文件或解锁互斥锁。
例如,
func main() {f, err := os.Open("example.txt")if err != nil {log.Fatal(err)}defer f.Close() // 确保文件在函数结束时被关闭// 处理文件...
}
七、panic
在Go语言中,panic表示运行时发生了严重错误,导致程序无法继续正常执行。它是Go语言的一种异常处理机制,用于在程序遇到无法恢复的错误时,立即停止当前goroutine的执行,并开始进行栈展开(unwinding the stack),即逐级回溯调用栈,直到找到能够处理该panic的地方或程序完全终止。
panic可以由代码中的panic语句显式触发,也可以由运行时错误隐式触发。常见的会引发panic的运行时错误包括:
除数为零:进行除法运算时,如果除数为零,会引发panic。
索引越界:访问数组或切片的索引超出其长度时,会引发panic。
空指针解引用:对一个未初始化(即为nil)的指针进行解引用操作,会引发panic。
发送或接收已关闭的通道数据:向已关闭的通道发送数据或从已关闭且没有数据的通道接收数据,都可能引发panic。
类型断言失败:当进行类型断言时,如果实际类型与断言的类型不匹配,会引发panic。
当panic发生时,如果没有通过recover()函数进行捕获和处理,程序会打印出panic的详细情况,包括错误信息和调用栈信息,然后终止运行。因此,在编写Go程序时,需要特别注意可能导致panic的情况,并考虑使用recover()函数进行异常处理,以提高程序的健壮性和稳定性。