Go 语言入门指南:基础语法和常用特性解析 实践🎉🎉🎉|青训营

这是一篇实践文章。在本篇文章中,将会介绍Go语言的常用特性解析和部分常用基础语法。

1. 基础语法

因为部分基础语法已在之前的笔记文章中有过叙述,在此不做过多介绍。再补充为介绍到的部分基础语法。

1.1 Package

一般情况下,一个文件夹可以作为一个 package,同一个 package 内部的变量、类型、方法等可以相互看到。

Go 语言也有 PublicPrivate 的概念,粒度是包。

如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见

1.2 Modules

Go Modules是 Go 1.11 版本之后引入,之前使用 $GOPATH 机制。

Go Modules 是较为完善的包管理工具。同时支持代理,可以配置高速的第三方镜像包下载服务。

通过 go mod init '文件名'初始化Module。 在当前文件夹下生成了go.mod,这个文件是用来记录当前模块的模块名以及所有依赖包的版本。

若是在import()里添加外部依赖,运行 go run.会自动触发依赖包的下载,并将具体的版本信息记录在go.mod里。

若是在包中新增了子包,只需要在需要引入子包内容的文件import中添加模块名/子目录名即可。

1.3 字符串操作

  • 字符串长度: 使用len()函数可以获取字符串的长度,即字符串中字符的个数。
go 复制代码
str := "Hello, World!"
length := len(str)
fmt.Println(length) // 输出:13
  • 字符串拼接: 使用+操作符可以将两个字符串进行拼接。
go 复制代码
str1 := "Hello"
str2 := "World"
result := str1 + " " + str2
fmt.Println(result) // 输出:Hello World
  • 字符串截取: 使用切片操作可以截取字符串的一部分。
go 复制代码
str := "Hello, World!"
substring := str[0:5]
fmt.Println(substring) // 输出:Hello
  • 字符串查找: 使用strings包中的Contains函数可以在字符串中查找特定的子串。
go 复制代码
str := "Hello, World!"
contains := strings.Contains(str, "World")
fmt.Println(contains) // 输出:true
  • 字符串替换: 使用strings包中的Replace 函数可以将字符串中的某个子串替换为另一个子串。
go 复制代码
str := "Hello, World!"
newStr := strings.Replace(str, "World", "Golang", -1)
fmt.Println(newStr) // 输出:Hello, Golang!
  • 字符串分割: 使用strings包中的Split函数可以将字符串按照指定的分隔符进行分割。
go 复制代码
str := "Hello, World!"
parts := strings.Split(str, ", ")
fmt.Println(parts) // 输出:[Hello World!]
  • 字符串转换: 使用strconv包中的Atoi函数可以将字符串转换为其他类型的值,如整数、浮点数等。
go 复制代码
str := "100"
num, _ := strconv.Atoi(str)
fmt.Println(num) // 输出:100
  • 字符串大小写转换: 使用strings包中的ToUpper, ToLower函数可以将字符串中的字母转换为大写或小写。
go 复制代码
str := "Hello, World!"
upper := strings.ToUpper(str)
lower := strings.ToLower(str)
fmt.Println(upper) // 输出:HELLO, WORLD!
fmt.Println(lower) // 输出:hello, world!
  • 字符串统计: 使用strings包中的Count函数用于统计一个子串在字符串中出现的次数。
go 复制代码
str := "Hello, Hello, Hello"
count := strings.Count(str, "Hello")
fmt.Println(count) // 输出:3
  • 字符串前缀判断: 使用strings包中的HasPrefix函数用于判断字符串是否以指定的前缀开头。
go 复制代码
str := "Hello, World!"
hasPrefix := strings.HasPrefix(str, "Hello")
fmt.Println(hasPrefix) // 输出:true
  • 字符串后缀判断: 使用strings包中的HasSuffix函数用于判断字符串是否以指定的后缀结尾。
go 复制代码
str := "Hello, World!"
hasSuffix := strings.HasSuffix(str, "World!")
fmt.Println(hasSuffix) // 输出:true
  • 字符串字串首位判断: 使用strings包中的Index函数用于查找子串在字符串中第一次出现的位置。
go 复制代码
str := "Hello, World!"
index := strings.Index(str, "World")
fmt.Println(index) // 输出:7
  • 字符串切片连接: 使用strings包中的Join函数用于将字符串切片按照指定的分隔符连接成一个字符串。
go 复制代码
strs := []string{"Hello", "World", "Golang"}
result := strings.Join(strs, "-")
fmt.Println(result) // 输出:Hello-World-Golang
  • 字符串重复: 使用strings包中的Repeat函数用于将一个字符串重复多次。
go 复制代码
str := "Hello"
result := strings.Repeat(str, 3)
fmt.Println(result) // 输出:HelloHelloHello

2. 常用特性解析

2.1 并发编程 goroutine

Go语言应该是实现并发最简易的语言。Go 语言提供了 sync 和 channel 两种方式支持协程(goroutine)的并发。

  • goroutine(协程): goroutine是Go语言中的轻量级线程,可以在并发编程中实现并行执行。通过关键字go可以创建一个新的goroutine,它会在一个独立的线程中运行。与传统的线程相比,goroutine的创建和销毁开销很小,可以高效地创建大量的goroutine。
  • channel(通道): channel是用于在不同goroutine之间进行通信和同步的机制。它可以用于在goroutine之间传递数据,实现数据的安全共享。channel提供了阻塞和非阻塞的发送和接收操作,可以确保数据的同步和顺序。

sync包是一个非常重要的包,它提供了一些用于同步和保护共享资源的机制

  • mutex(互斥锁): mutex是一种用于保护共享资源的锁机制。通过使用mutex,可以确保在同一时间只有一个goroutine可以访问共享资源,从而避免了竞态条件和数据竞争。
  • WaitGroup(等待组): WaitGroup用于等待一组goroutine的执行完成。通过Add、Done和Wait方法,可以控制并发执行的goroutine数量,并在所有goroutine执行完成后进行等待。 接下来是一个使用sync.WaitGroup实现并发下载N个资源的示例代码:
go 复制代码
package main

import (
	"fmt"
	"net/http"
	"sync"
)

// 创建一个WaitGroup
var wg sync.WaitGroup

func main() {
	// 定义下载的资源数量
	resourceCount := 5

	// 设置WaitGroup的计数器
	wg.Add(resourceCount)

	// 并发下载资源
	for i := 0; i < resourceCount; i++ {
		go func(index int) {
			// 在goroutine执行完成后,调用Done方法减少WaitGroup的计数器
			defer wg.Done()

			// 构造要下载的资源URL
			url := fmt.Sprintf("https://example.com/resource-%d", index)

			// 发起HTTP请求下载资源
			resp, err := http.Get(url)
			if err != nil {
				fmt.Printf("Error downloading resource %d: %v\n", index, err)
				return
			}
			defer resp.Body.Close()

			fmt.Printf("Resource %d downloaded\n", index)
		}(i)
	}

	// 等待所有并发协程执行结束
	wg.Wait()

	fmt.Println("All resources downloaded")
}

首先定义了要下载的资源数量resourceCount,并创建了一个WaitGroup。然后,使用for循环并发地下载资源。每个goroutine执行完成后,都会调用WaitGroup的Done方法来减少计数器。最后,调用Wait方法来阻塞主goroutine,直到所有的goroutine都执行完成。

运行测试后并发执行与非并发执行速度大大提升。

下面是通过模拟生产者和消费者的场景来使用channel:

go 复制代码
package main

import (
	"fmt"
	"time"
)

func producer(ch chan<- int) {
	for i := 0; i < 5; i++ {
		fmt.Println("Producer sending:", i)
		ch <- i // 发送数据到channel
		time.Sleep(time.Second)
	}
	close(ch) // 关闭channel
}

func consumer(ch <-chan int) {
	for num := range ch { 
                // 从channel接收数据,直到channel关闭
		fmt.Println("Consumer received:", num)
		time.Sleep(2 * time.Second)
	}
}

func main() {
	ch := make(chan int) // 创建一个无缓冲的channel

	go producer(ch) // 启动生产者goroutine
	go consumer(ch) // 启动消费者goroutine

	time.Sleep(10 * time.Second) // 等待一段时间,让生产者和消费者完成     
}

首先创建了一个无缓冲的channel,用于在生产者和消费者之间传递数据。生产者goroutine通过for循环向channel发送数据,并使用time.Sleep模拟一段耗时的生产过程。消费者goroutine通过range循环从channel接收数据,并使用time.Sleep模拟一段耗时的消费过程。

在main函数中,启动生产者和消费者的goroutine,并通过time.Sleep等待一段时间,完成生产和消费操作。

在生产者函数中使用ch <- i语句将数据发送到channel,而在消费者函数中使用num := <-ch语句从channel接收数据。在接收数据时,使用range循环来遍历channel中的所有数据。

生产者函数中使用了close(ch)语句来关闭channel。关闭channel后,接收方仍然可以从channel中接收数据,但是不能再向channel发送数据。在消费者函数中,当channel关闭后,for循环会自动结束。

channel的用法:通过ch <- data向channel发送数据,通过data := <-ch从channel接收数据,使用close(ch)关闭channel。channel可以实现不同goroutine之间的数据传递和同步。

2.2 单元测试 unit test

  • 所有测试文件以 test.go 结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到 TestMain 中

单元测试-覆盖率

  • 一般覆盖率:50%~60%,较高覆盖率80%+
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

下面是一个Go语言单元测试示例:

go 复制代码
package main

import (
	"testing"
)

// 计算两个整数的和
func Add(a, b int) int {
	return a + b
}

// 测试函数
func TestAdd(t *testing.T) {
	result := Add(2, 3)
	expected := 5

	if result != expected {
		t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
	}
}

定义了一个函数Add,用于计算两个整数的和。然后,编写TestAdd的测试函数,用于测试Add函数的功能。

在测试函数中,调用Add函数并将结果与预期值进行比较。如果结果与预期值不相等,使用t.Errorf函数来输出错误信息。

运行单元测试,在命令行中使用go test命令:

go 复制代码
$ go test

运行测试时,Go语言会自动查找当前目录及其子目录中的_test.go文件,并执行其中的测试函数。

2.3 基准测试

Go语言提供了基准测试框架,基准测试是指测试一段程序的运行性能及耗费 CPU 的程度,而我们在实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码做性能分析,这就用到了基准测试。使用方法类似于单元测试。

下面是一个简单的Go语言基准测试示例:

go 复制代码
package main

import (
	"testing"
)

// 计算斐波那契数列的第n个数
func Fibonacci(n int) int {
	if n <= 1 {
		return n
	}
	return Fibonacci(n-1) + Fibonacci(n-2)
}

// 基准测试函数
func BenchmarkFibonacci(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Fibonacci(20)
	}
}

定义了一个函数Fibonacci,用于计算斐波那契数列的第n个数。然后,编写了一个名为BenchmarkFibonacci的基准测试函数,用于测试Fibonacci函数的性能。

在基准测试函数中,使用b.N来指定要运行的迭代次数。在每次迭代中,调用Fibonacci函数来计算斐波那契数列的第20个数。

运行基准测试,在命令行中使用go test命令,并指定-bench参数:

go 复制代码
$ go test -bench=.

运行基准测试时,Go语言会自动查找当前目录及其子目录中的_test.go文件,并执行其中的基准测试函数。通过-bench参数,可以指定要运行的基准测试函数。

基准测试会输出每次迭代的执行时间和每秒执行的迭代次数,以及其他相关信息。

3. 常用库

3.1 Fmt常用库

输出 fmt.Print

三个函数 Print, Println, Printf最终都调用Fpintf。

  • 格式化占位符之通用占位符
占位符 说明
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 打印值的类型
%% 百分号
  • 格式化占位符之布尔型
占位符 说明
%t true或false
  • 格式化占位符之整型
占位符 说明
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode 格式:U+1234,等价于"U+%04X"
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
  • 格式化占位符之复数
  • 格式化占位符之字符串和[]byte
占位符 说明
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f)
%X 每个字节用两字符十六进制数表示(使用A-F)
  • 格式化占位符之指针
  • 格式化占位符之宽度标识符
  • 格式化占位符之其他特殊符
  • Fprint。将内容输出到一个io.Writer接口类型的变量w中。一般用在写文件中。
  • Sprint。把传入的数据生成并返回一个字符串。
  • Errorf。根据format参数生成格式化字符串并返回一个包含该字符串的错误。

fmt.Scan

  • 从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符
  • 本函数返回成功扫描的数据个数(n)和遇到的任何错误(error)

fmt.Scanf 实际使用的是Fscanf,以format定义的格式来进行输入。

fmt.Scanln

Scanln函数会一次性读取多个输入值,并将它们存储到传入的参数中。使用Scanln函数时,需要提供与输入值对应的变量作为参数。

fmt.Fsanf 将内容从一个io.Reader接口类型的变量r中读取出来,将连续的以空格分隔的值存储到由格式确定的连续的参数中。

3.2 Json处理

对于一个已有的结构体,每个字段的第一字母是大写,也就是公开字段,那这个结构体就用JSON.Marshal去序列化,变成一个JSON 的字符串。 序列化之后的字符串也能够用JSON.Unmarshal去反序列化到一个空的变量里面。 默认序列化出来的字符串,风格是大写字母开头,而不是下划线。 可以在后面用json tag 等语法来去修改输出 JSON 结果里面的字段名。

3.3 时间处理

  • time包:time包是Go语言中用于处理时间的核心包。提供了用于表示和操作时间的类型和函数。

    • time.Now()函数:用于获取当前的时间。
    • time.Parse(layout, value)函数:用于将字符串解析为时间类型。
    • time.Format(layout, t)函数:用于将时间格式化为字符串。
    • time.Duration类型:表示时间间隔,可以用于计算时间差等操作。
  • time.Time类型:time.Time类型表示一个具体的时间点。包含了年、月、日、时、分、秒、纳秒等信息,可以进行各种时间操作。

    • t.Year()t.Month()t.Day()等方法:用于获取时间的年、月、日等信息。
    • t.Hour()t.Minute()t.Second()等方法:用于获取时间的小时、分钟、秒等信息。
    • t.Add(duration)方法:用于在时间上增加一个时间间隔。
    • t.Sub(u)方法:用于计算两个时间之间的时间间隔。
  • time.Duration类型:time.Duration类型表示一个时间间隔。可以用于表示一段时间的长度,例如1小时、30分钟等。

    • time.Secondtime.Minutetime.Hour等常量:表示不同单位的时间间隔。
    • duration.Hours()duration.Minutes()duration.Seconds()等方法:用于获取时间间隔的小时、分钟、秒等信息。
  • time.Timer类型:time.Timer类型表示一个定时器,用于在指定的时间后触发一个事件。

    • time.NewTimer(duration)函数:创建一个定时器,指定触发事件的时间间隔。
    • timer.Reset(duration)方法:重置定时器的触发时间。
    • <-timer.C表达式:在定时器的通道上阻塞,直到定时器触发。
  • time.Tick(duration)函数:创建一个定时器的通道,每隔指定的时间间隔就会向通道发送一个时间值。

  • time.Sleep(duration)函数:使当前的goroutine休眠指定的时间间隔。

  • time.LoadLocation(name)函数:根据时区名字加载对应的时区。

3.4 数字解析

  • strconv包:strconv包是Go语言中用于字符串和基本数据类型之间转换的核心包。提供了用于数字解析的函数。

    • strconv.Atoi(s)函数:将字符串解析为int类型的整数。如果解析失败,会返回错误。
    • strconv.ParseInt(s, base, bitSize)函数:将字符串解析为指定进制和位数的整数。base表示进制,可以是2、8、10、16;bitSize表示整数的位数,可以是0、8、16、32、64。如果解析失败,会返回错误。
    • strconv.ParseFloat(s, bitSize)函数:将字符串解析为指定位数的浮点数。bitSize表示浮点数的位数,可以是32、64。如果解析失败,会返回错误。

4. 实践结语

在完成了Go语言入门指南的实践之后,我对这门语言有了更深的了解和体会。Go语言以其简洁、高效和并发安全的特性,成为了现代编程领域中备受关注的语言之一。

首先,Go语言的基础语法非常简洁明了,易于学习和理解。它摒弃了一些复杂的语法和概念,使得代码更加清晰和易读。同时,Go语言还提供了丰富的内置类型和函数,方便开发者进行各种操作和处理。

其次,Go语言的并发特性给我留下了深刻的印象。通过goroutine和channel的机制,可以方便地实现并发编程,充分利用多核处理器的性能。这种并发模型的设计使得编写高效且可靠的并发程序变得更加容易。

此外,Go语言还具有良好的性能和可扩展性。它的编译器能够将代码快速编译成机器码,执行速度较快。同时,Go语言还提供了丰富的标准库和第三方库,可以满足各种需求,从而提高开发效率。

在这次实践中,我深刻感受到了Go语言的简洁和高效,它让我在编写代码时更加专注于问题的解决,而不是纠结于语法和细节。同时,Go语言的并发特性也让我对并发编程有了更深入的理解和实践。

Go语言是一门非常优秀的编程语言,它的简洁、高效和并发安全的特性使得它在现代编程领域中有着广泛的应用。通过这次实践,我对Go语言的学习和使用充满了信心,我相信它将成为我未来开发的重要工具之一。

相关推荐
夭要7夜宵3 天前
Go 垃圾回收 | 豆包MarsCode AI刷题
青训营笔记
末班车4224 天前
前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记
VanceLLF5 天前
神奇数字组合 | 豆包MarsCode AI刷题
青训营笔记
lann5 天前
Go 程序的优化 | 豆包MarsCode AI刷题
青训营笔记
用户52281271049785 天前
性能优化与调试技巧 | 豆包MarsCode AI刷题
青训营笔记
千慌百风定乾坤7 天前
Go 语言入门指南:基础语法和常用特性解析(下) | 豆包MarsCode AI刷题
青训营笔记
FOFO7 天前
青训营笔记 | HTML语义化的案例分析: 粗略地手绘分析juejin.cn首页 | 豆包MarsCode AI 刷题
青训营笔记
滑滑滑9 天前
后端实践-优化一个已有的 Go 程序提高其性能 | 豆包MarsCode AI刷题
青训营笔记
柠檬柠檬9 天前
Go 语言入门指南:基础语法和常用特性解析 | 豆包MarsCode AI刷题
青训营笔记
用户967136399659 天前
计算最小步长丨豆包MarsCodeAI刷题
青训营笔记