干货分享,感谢您的阅读!备注:本博客将自己初步学习GO的总结进行分享,希望大家通过本博客可以在短时间内快速掌握GO的基本程序编码能力,如有错误请留言指正,谢谢!
一、初步了解Go语言
(一)Go语言诞生的主要问题和目标
-
多核硬件架构: 随着计算机硬件的发展,多核处理器成为主流,使得并行计算变得普遍。然而,传统的编程语言在处理多核并行性时可能面临困难,因为它们缺乏合适的原生支持。Go语言通过引入轻量级的协程(goroutine)和通道(channel)机制,使得并发编程变得更加容易。开发者可以轻松地创建数千个并发执行的协程,而无需担心线程管理的复杂性。
-
超大规模分布式计算集群: 随着云计算和分布式系统的崛起,构建和维护超大规模的分布式计算集群变得越来越常见。这些集群需要能够高效处理大量的请求、数据共享和协调。Go语言的并发特性和通道机制使得编写分布式系统变得更加容易,开发者可以使用协程和通道来处理并发任务、消息传递和协调工作。
-
Web模式导致的开发规模和更新速度增加: Web应用的兴起带来了前所未有的开发规模和持续更新的需求。传统的编程语言在开发大型Web应用时可能会面临可维护性、性能和开发效率等问题。Go语言通过其简洁的语法、高效的编译速度以及并发支持,使得开发者能够更快速地迭代和部署Web应用,同时也能够更好地处理高并发的网络请求。
综合来看,Go语言在诞生时确实着重解决了多核硬件架构、超大规模分布式计算集群和Web模式下的开发规模与速度等技术挑战,它的设计目标之一是提供一种适应现代软件开发需求的编程语言,使开发者能够更好地应对这些挑战。
(二)Go语言应用典型代表
Go语言在当下应用开发中已经得到广泛应用,许多知名公司和项目都使用Go语言来构建各种类型的应用。以下是一些代表性的产品和项目,它们使用了Go语言作为核心开发语言:
这些仅仅是Go语言应用的一小部分示例,实际上还有许多其他的项目和产品也在使用Go语言来构建高性能、可靠且易于维护的应用程序。这表明Go语言在现代应用开发中发挥了重要作用,特别是在分布式系统、云计算和高性能应用领域。
(三)Java、C++、C程序员在学习编写Go时存在的误区
当Java、C++、C等编程语言的程序员开始学习编写Go语言时,可能会遇到一些误区,因为Go在某些方面与这些传统语言有所不同。以下是一些常见的误区:
-
过度使用传统的并发模型: 传统的编程语言如Java、C++、C在处理并发时通常使用线程和锁来实现,但在Go中,使用协程(goroutine)和通道(channel)是更好的方式。新学习Go的程序员可能会继续使用传统的并发模型,而不充分利用Go的轻量级协程和通道,从而失去了Go的并发优势。
-
过度使用指针: C和C++等语言强调指针的使用,但Go语言在设计时避免了过多的指针操作。新学习Go的程序员可能会过度使用指针,导致代码变得复杂。在Go中,尽量避免使用指针,除非真正需要对值进行修改。
-
忽视错误处理: Go鼓励显式地处理错误,而不是简单地忽略它们。这与一些其他语言的习惯不同,其中错误往往被忽略或简单地抛出。新学习Go的程序员可能会忽视错误处理,导致潜在的问题未被检测到。
-
过度使用全局变量: 在C和C++等语言中,全局变量可能是常见的做法。然而,在Go中,全局变量的使用被视为不良实践。Go鼓励使用局部变量和传递参数的方式来传递数据,以避免引入不必要的耦合和副作用。
-
不熟悉切片和映射: Go中的切片和映射是强大的数据结构,但对于其他语言的程序员来说可能不太熟悉。学习如何正确使用切片和映射是很重要的,因为它们在Go中广泛用于集合和数据处理。
-
错误的Go风格: 每种语言都有其独特的编码风格和惯例。新学习Go的程序员可能会在Go代码中应用其他语言的编码风格,这可能会使代码难以阅读和理解。
为了避免这些误区,学习Go的程序员应该投入时间去理解Go语言的核心概念,包括并发模型、错误处理、数据结构等,同时积极参与Go社区,阅读Go的官方文档和示例代码,以便更好地适应Go的设计理念和最佳实践。
二、环境准备(以Mac说明)
(一)环境设置
在macOS上设置Go语言开发环境非常简单,可以按照以下步骤进行操作:
-
使用Homebrew安装: 如果您使用Homebrew包管理器,这是最方便的方法。打开终端,并运行以下命令来安装Go语言:
brew install go
-
手动安装: 如果想手动安装Go语言,可以按照以下步骤操作:
a. 访问官方网站下载安装包`goX.X.X.darwin-amd64.pkg
b. 双击下载的安装包,按照指示运行安装程序。按照默认设置即可,安装路径通常是
/usr/local/go
。 -
设置环境变量: 一旦安装完成,需要将Go语言的二进制路径添加到自己的终端配置文件中的PATH环境变量中。这样就可以在终端中直接运行Go命令。
a. 打开终端,并使用文本编辑器(如nano、vim或任何您喜欢的编辑器)编辑终端配置文件。例如:
nano ~/.bash_profile
b. 在文件中添加以下行(根据安装路径进行调整),然后保存并退出编辑器:
export PATH=$PATH:/usr/local/go/bin
c. 使配置生效,可以运行以下命令或者重启终端:
source ~/.bash_profile
-
验证安装: 打开终端,输入以下命令来验证Go是否已正确安装:
go version
如果看到了Go的版本号,表示安装成功。
(二)IDE选择说明
我个人使用的GoLand,直接官网下载后,上网购买破解版即可,这里不在多说!
三、Go语言程序学习
创建自己的工程目录/Users/zyf/zyfcodes/go/go-learning,新建src目录。
(一)第一个Go语言编写
src目录下创建chapter1/hello目录,新建hello.go文件,编写代码如下:
package main
import (
"fmt"
"os"
)
/**
* @author zhangyanfeng
* @description 第一个godaima
* @date 2023/8/20 23:45
* @param
* @return
**/
func main() {
if len(os.Args) > 1 {
fmt.Println("Hello World", os.Args[1])
}
}
这段代码是一个简单的Go语言程序,它接受命令行参数并打印出一条带参数的 "Hello World" 消息。下面是对代码的逐行分析:
-
package main
: 声明这个文件属于名为 "main" 的包,这是一个Go程序的入口包名。 -
import ("fmt" "os")
: 引入了两个标准库包,分别是 "fmt" 用于格式化输出,和 "os" 用于与操作系统交互。 -
func main() { ... }
: 这是程序的入口函数,它会在程序运行时首先被调用。 -
if len(os.Args) > 1 { ... }
: 这个条件语句检查命令行参数的数量是否大于1,也就是判断是否有参数传递给程序。os.Args
是一个字符串切片,它包含了所有的命令行参数,第一个参数是程序的名称。 -
fmt.Println("Hello World", os.Args[1])
: 如果有参数传递给程序,就会执行这行代码。它使用fmt.Println
函数打印一条消息,消息由字符串 "Hello World" 和os.Args[1]
组成,os.Args[1]
表示传递给程序的第一个参数。
综上所述,这段代码涵盖了以下知识点:
-
包导入和使用标准库:通过
import
关键字导入 "fmt" 和 "os" 包,然后在代码中使用这些包提供的函数和类型。 -
命令行参数获取:使用
os.Args
获取命令行参数。 -
条件语句:使用
if
条件语句来判断是否有命令行参数传递给程序。 -
字符串操作:使用字符串连接操作将 "Hello World" 与命令行参数拼接在一起。
-
格式化输出:使用
fmt.Println
函数将消息输出到标准输出。
注意:如果没有传递参数给程序,那么这段代码不会打印任何消息。如果传递了多个参数,代码只会使用第一个参数并忽略其他参数。
在该目录下执行“go run hello.go ZYF”,运行结果为“Hello World ZYF”。
(二)基本程序结构编写学习
src目录下创建chapter2
1.变量
前提:chapter2目录下创建variables,学习总结如下:
- 变量声明: 使用
var
关键字声明一个变量,例如:var x int
。 - 类型推断: 可以使用
:=
操作符进行变量声明和赋值,Go会根据右侧的值自动推断变量类型,例如:y := 5
。 - 变量赋值: 使用赋值操作符
=
给变量赋值,例如:x = 10
。 - 多变量声明: 可以同时声明多个变量,例如:
var a, b, c int
。 - 变量初始化: 变量可以在声明时进行初始化,例如:
var name string = "John"
。 - 零值: 未初始化的变量会被赋予零值,数字类型为0,布尔类型为
false
,字符串类型为空字符串等。 - 短变量声明: 在函数内部,可以使用短变量声明方式,例如:
count := 10
。
新建fib_test.go,背景:简单实用斐波那契数列进行练习
package variables
import "testing"
func TestFibList(t *testing.T) {
a := 1
b := 1
t.Log(a)
for i := 0; i < 5; i++ {
t.Log(" ", b)
tmp := a
a = b
b = tmp + a
}
}
func TestExchange(t *testing.T) {
a := 1
b := 2
// tmp := a
// a = b
// b = tmp
a, b = b, a
t.Log(a, b)
}
下面逐个解释代码中涉及的知识点:
-
package variables
: 声明了一个名为 "variables" 的包,这是一个用于测试的包名。 -
import "testing"
: 导入了Go语言的测试框架 "testing" 包,用于编写和运行测试函数。 -
在测试函数内部,声明了两个整数变量func TestFibList(t *testing.T) { ... }
: 定义了一个测试函数 "TestFibList",该函数用于测试斐波那契数列生成逻辑。这是一个测试函数的标准命名,以 "Test" 开头,接着是被测试的函数名。a
和b
,并将它们初始化为 1,这是斐波那契数列的前两个数。使用t.Log(a)
打印变量a
的值到测试日志中。使用循环来生成斐波那契数列的前 5 个数,每次迭代都会将b
的值打印到测试日志,并更新a
和b
的值以生成下一个数。 -
在测试函数内部,声明了两个整数变量func TestExchange(t *testing.T) { ... }
: 定义了另一个测试函数 "TestExchange",该函数用于测试变量交换的逻辑。a
和b
,并分别将它们初始化为 1 和 2。使用注释的方式展示了一种变量交换的写法(通过中间变量),但实际上被注释掉了。然后使用a, b = b, a
这一行代码来实现a
和b
的交换,这是Go语言中的一种特有的交换方式,不需要额外的中间变量。使用t.Log(a, b)
打印交换后的变量值到测试日志中。
2.常量
前提:chapter2目录下创建constant,学习总结如下:
- 常量声明: 使用
const
关键字声明一个常量,例如:const pi = 3.14159
。 - 常量赋值: 常量的值在声明时必须被赋值,一旦赋值后不可修改。
-
枚举常量: 可以使用一组常量来模拟枚举,例如:
const ( Monday = 1 Tuesday = 2 // ... )
- 类型指定: 常量的类型也可以被指定,例如:
const speed int = 300000
。 - 常量表达式: 常量可使用表达式计算,例如:
const secondsInHour = 60 * 60
。 - 无类型常量: 常量可以是无类型的,根据上下文自动推断类型。例如,
const x = 5
会被推断为整数类型。
新建constant_test.go,写代码如下:
package constant
import "testing"
const (
Monday = 1 + iota
Tuesday
Wednesday
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestConstant1(t *testing.T) {
t.Log(Monday, Tuesday)
}
func TestConstant2(t *testing.T) {
a := 1 //0001
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
下面逐个解释代码中涉及的知识点:
-
package constant
: 声明了一个名为 "constant" 的包,这是一个用于测试的包名。 -
import "testing"
: 导入了Go语言的测试框架 "testing" 包,用于编写和运行测试函数。 -
第一个常量块中,使用了const (...)
: 定义了两个常量块。iota
常量生成器来定义了一系列从 1 开始递增的常量。在这个例子中,Monday
被赋值为 1,Tuesday
被赋值为 2,Wednesday
被赋值为 3。iota
在常量块中每次被使用时会递增一次,因此后续的常量会依次递增;第二个常量块中,使用了iota
来定义了一系列按位左移的常量。在这个例子中,Readable
被赋值为 1,Writable
被赋值为 2(二进制中的 10),Executable
被赋值为 4(二进制中的 100)。位运算中,左移操作可以将二进制数向左移动指定的位数。 -
使用func TestConstant1(t *testing.T) { ... }
: 定义了一个测试函数 "TestConstant1",用于测试第一个常量块中定义的常量。t.Log(Monday, Tuesday)
打印常量Monday
和Tuesday
的值到测试日志中。 -
在测试函数内部,声明了一个整数变量func TestConstant2(t *testing.T) { ... }
: 定义了另一个测试函数 "TestConstant2",用于测试位运算和常量的使用。a
,并将其初始化为 1,即二进制中的 0001。使用位运算和按位与操作来检查变量a
是否具有Readable
、Writable
和Executable
属性。例如,a&Readable == Readable
表达式检查a
的二进制表示是否含有Readable
标志位。使用t.Log()
打印三个表达式的结果到测试日志中。
3.数据类型
前提:chapter2目录下创建 type,学习总结如下:
主要数据类型说明
Go语言具有丰富的内置数据类型,这些数据类型用于表示不同类型的值和数据。以下是对Go语言中一些主要数据类型的总结分析:
-
整数类型(Integer Types):Go语言提供不同大小的整数类型,如
int
、int8
、int16
、int32
和int64
。无符号整数类型有uint
、uint8
、uint16
、uint32
和uint64
。整数类型的大小取决于计算机的架构,例如32位或64位。 -
浮点数类型(Floating-Point Types):Go语言提供
float32
和float64
两种浮点数类型,分别对应单精度和双精度浮点数。 -
复数类型(Complex Types):Go语言提供
complex64
和complex128
两种复数类型,分别对应由两个浮点数构成的复数。 -
布尔类型(Boolean Type):布尔类型用于表示真(
true
)和假(false
)的值,用于条件判断和逻辑运算。 -
字符串类型(String Type):字符串类型表示一系列字符。字符串是不可变的,可以使用双引号
"
或反引号`
来定义。 -
字符类型(Rune Type):字符类型
rune
用于表示Unicode字符,它是int32的别名。通常使用单引号'
来表示字符,如'A'
。 -
数组类型(Array Types):数组是具有固定大小的同类型元素集合。声明数组时需要指定元素类型和大小。
-
切片类型(Slice Types):切片是对数组的一层封装,是动态长度的可变序列。切片不保存元素,只是引用底层数组的一部分。
-
映射类型(Map Types):映射是键值对的无序集合,用于存储和检索数据。键和值可以是任意类型,但键必须是可比较的。
-
结构体类型(Struct Types):结构体是一种用户定义的复合数据类型,可以包含不同类型的字段,每个字段有一个名字和类型。
-
接口类型(Interface Types):接口是一种抽象类型,用于定义一组方法。类型实现了接口的方法集合即为实现了该接口。
-
函数类型(Function Types):函数类型表示函数的签名,包括参数和返回值类型。函数可以作为参数传递和返回。
-
通道类型(Channel Types):通道是用于在协程之间进行通信和同步的一种机制。通道有发送和接收操作。
-
指针类型(Pointer Types):指针类型表示变量的内存地址。通过指针可以直接访问和修改变量的值。
Go语言的数据类型具有清晰的语法和语义,支持丰富的内置功能。合理选择和使用不同的数据类型可以提高程序的效率和可读性。
具体代码展开分析
package main
import "fmt"
type Person struct {
FirstName string
LastName string
Age int
}
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func add(a, b int) int {
return a + b
}
func subtract(a, b int) int {
return a - b
}
type Operation func(int, int) int
func main() {
fmt.Println("整数类型(Integer Types)")
var x int = 10
var y int64 = 100
fmt.Println(x)
fmt.Println(y)
fmt.Println("浮点数类型(Floating-Point Types)")
var a float32 = 3.14
var b float64 = 3.14159265359
fmt.Println(a)
fmt.Println(b)
fmt.Println("布尔类型(Boolean Type)")
var isTrue bool = true
var isFalse bool = false
fmt.Println(isTrue)
fmt.Println(isFalse)
fmt.Println("字符串类型(String Type)")
str1 := "Hello, "
str2 := "Go!"
concatenated := str1 + str2
fmt.Println(concatenated)
fmt.Println("切片类型(Slice Types)")
numbers := []int{1, 2, 3, 4, 5}
fmt.Println(numbers)
// 修改切片元素
numbers[0] = 10
fmt.Println(numbers)
// 切片操作
subSlice := numbers[1:4]
fmt.Println(subSlice)
fmt.Println("映射类型(Map Types)")
ages := map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
fmt.Println(ages)
fmt.Println("Alice's age:", ages["Alice"])
// 添加新的键值对
ages["Charlie"] = 22
fmt.Println(ages)
fmt.Println("结构体类型(Struct Types)")
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
fmt.Println(person)
fmt.Println("Name:", person.FirstName, person.LastName)
fmt.Println("接口类型(Interface Types)")
var shape Shape
circle := Circle{Radius: 5}
shape = circle
fmt.Println("Circle Area:", shape.Area())
fmt.Println("函数类型(Function Types)")
var op Operation
op = add
result := op(10, 5)
fmt.Println("Addition:", result)
op = subtract
result = op(10, 5)
fmt.Println("Subtraction:", result)
fmt.Println("通道类型(Channel Types)")
messages := make(chan string)
go func() {
messages <- "Hello, Go!"
}()
msg := <-messages
fmt.Println(msg)
fmt.Println("指针类型(Pointer Types)")
x = 10
var ptr *int
ptr = &x
fmt.Println("Value of x:", x)
fmt.Println("Value stored in pointer:", *ptr)
*ptr = 20
fmt.Println("Updated value of x:", x)
}
下面逐个解释代码中涉及的知识点:
-
type Person struct { ... }
: 定义了一个结构体类型Person
,表示一个人的信息,包括FirstName
、LastName
和Age
字段。 -
type Shape interface { ... }
: 定义了一个接口类型Shape
,该接口要求实现一个方法Area()
返回一个float64
类型。 -
type Circle struct { ... }
: 定义了一个结构体类型Circle
,表示一个圆的半径。func (c Circle) Area() float64 { ... }
:为Circle
类型实现了Shape
接口的Area()
方法,用于计算圆的面积。 -
func add(a, b int) int { ... }
: 定义了一个函数add
,用于执行整数相加操作。 -
func subtract(a, b int) int { ... }
: 定义了一个函数subtract
,用于执行整数相减操作。 -
type Operation func(int, int) int
: 定义了一个函数类型Operation
,它接受两个整数参数并返回一个整数结果。 -
main() { ... }
: 程序的入口函数。
- 定义了多种不同类型的变量,包括整数、浮点数、布尔、字符串、切片、映射、结构体、接口、函数、通道和指针类型。
- 演示了不同类型变量的初始化、赋值、访问以及基本操作。
- 使用切片操作提取部分切片。
- 演示了映射的使用,包括添加新的键值对和访问键值对。
- 演示了结构体的定义和初始化,并访问结构体字段。
- 展示了接口的使用,将
Circle
类型赋值给Shape
类型变量,并调用接口方法。 - 演示了函数类型的定义和使用,将不同函数赋值给
Operation
类型变量,并进行调用。 - 使用通道来实现并发通信,通过匿名函数在 goroutine 中发送和接收消息。
- 演示了指针的使用,包括创建指针变量、通过指针修改变量的值等操作。
Go语言中类型转换说明
Go语言支持类型转换,但需要注意一些规则和限制。类型转换用于将一个数据类型的值转换为另一个数据类型,以便在不同的上下文中使用。以下是有关Go语言中类型转换的一些重要信息:
-
基本类型之间的转换: 可以在基本数据类型之间进行转换,但是必须注意类型的兼容性和可能导致的数据丢失。例如,从
int
到float64
的转换是安全的,但从float64
到int
可能导致小数部分被截断。 -
显示类型转换: 在Go中,使用强制类型转换来显式指定将一个值转换为另一个类型。语法是:
destinationType(expression)
。例如:float64(10)
。 -
非兼容类型之间的转换: 对于不兼容的类型,编译器不会自动进行转换。例如,不能直接将一个
string
类型转换为int
类型。 -
类型别名的转换: 如果有类型别名(Type Alias),在转换时需要注意使用别名的兼容性。
以下是一些示例来展示类型转换:
package main
import "fmt"
func main() {
// 显式类型转换
var x int = 10
var y float64 = float64(x)
fmt.Println(y)
// 类型别名的转换
type Celsius float64
type Fahrenheit float64
c := Celsius(25)
f := Fahrenheit(c*9/5 + 32)
fmt.Println(f)
}
4.运算符
前提:chapter2目录下创建 operator,学习总结如下:
其实这部分和其他语言都差不多,个人觉得没啥可复习巩固的。Go语言支持多种运算符,用于执行各种算术、逻辑和比较操作。
常规运算符
以下是一些常见的运算符及其在Go中的使用方式和知识点:
算术运算符(Arithmetic Operators):
+
:加法-
:减法*
:乘法/
:除法%
:取模(取余数)
赋值运算符(Assignment Operators):
=
:赋值+=
:加法赋值-=
:减法赋值*=
:乘法赋值/=
:除法赋值%=
:取模赋值
逻辑运算符(Logical Operators):
&&
:逻辑与(AND)||
:逻辑或(OR)!
:逻辑非(NOT)
比较运算符(Comparison Operators):
==
:等于!=
:不等于<
:小于>
:大于<=
:小于等于>=
:大于等于
位运算符(Bitwise Operators):
&
:按位与(AND)|
:按位或(OR)^
:按位异或(XOR)<<
:左移>>
:右移
其他运算符:
&
:取地址运算符*
:指针运算符++
:自增运算符--
:自减运算符
在使用运算符时,需要考虑以下几点:
- 运算符的操作数必须与运算符的预期类型匹配。
- 某些运算符具有更高的优先级,需要使用括号来明确优先级。
- 运算符的操作数可以是变量、常量、表达式等。
新建operator_test.go,以下是一些示例来展示运算符的使用:
package operator
import (
"fmt"
"testing"
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestOperatorBasic(t *testing.T) {
// 算术运算符
a := 10
b := 5
fmt.Println("Sum:", a+b)
fmt.Println("Difference:", a-b)
fmt.Println("Product:", a*b)
fmt.Println("Quotient:", a/b)
fmt.Println("Remainder:", a%b)
// 逻辑运算符
x := true
y := false
fmt.Println("AND:", x && y)
fmt.Println("OR:", x || y)
fmt.Println("NOT:", !x)
// 比较运算符
fmt.Println("Equal:", a == b)
fmt.Println("Not Equal:", a != b)
fmt.Println("Greater Than:", a > b)
fmt.Println("Less Than:", a < b)
fmt.Println("Greater Than or Equal:", a >= b)
fmt.Println("Less Than or Equal:", a <= b)
}
func TestCompareArray(t *testing.T) {
a := [...]int{1, 2, 3, 4}
b := [...]int{1, 3, 2, 4}
// c := [...]int{1, 2, 3, 4, 5}
d := [...]int{1, 2, 3, 4}
t.Log(a == b)
//t.Log(a == c)
t.Log(a == d)
}
func TestBitClear(t *testing.T) {
a := 7 //0111
a = a &^ Readable
a = a &^ Executable
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
下面逐个解释代码中涉及的知识点:
-
const (...)
: 定义了三个常量Readable
、Writable
和Executable
,使用位移操作生成不同的值。 -
算术运算符:展示了加法、减法、乘法、除法和取余运算;逻辑运算符:展示了逻辑与、逻辑或和逻辑非运算;比较运算符:展示了等于、不等于、大于、小于、大于等于和小于等于运算。func TestOperatorBasic(t *testing.T) { ... }
: 定义了一个测试函数 "TestOperatorBasic",用于测试基本运算符的使用。 -
声明了两个整数数组func TestCompareArray(t *testing.T) { ... }
: 定义了一个测试函数 "TestCompareArray",用于测试数组的比较。a
和b
,以及另一个数组d
,其中数组a
和数组d
的内容相同;使用比较运算符==
检查数组a
和b
是否相等,以及数组a
和d
是否相等。 -
声明一个整数变量func TestBitClear(t *testing.T) { ... }
: 定义了一个测试函数 "TestBitClear",用于测试位清除操作。a
,并将其初始化为7
,即二进制表示0111;
使用位清除操作&^
将a
中的Readable
和Executable
位清除;使用按位与运算&
检查a
是否具有Readable
、Writable
和Executable
属性。
按位清除运算符 &^
在Go语言中,&^
是按位清除运算符(Bit Clear Operator)。它用于将某些位置上的位清零,即将指定位置上的位设置为0。&^
运算符在处理二进制位操作时非常有用。
&^
运算符执行以下操作:
- 对于每个位,如果右侧操作数的对应位为 0,则结果位与左侧操作数相同。
- 对于每个位,如果右侧操作数的对应位为 1,则结果位被强制设置为 0。
这意味着,&^
运算符用于“清除”左侧操作数的特定位,使其与右侧操作数的相应位不受影响。写个代码验证下:
func TestOther(t *testing.T) {
var a uint8 = 0b11001100 // 二进制表示,十进制为 204
var b uint8 = 0b00110011 // 二进制表示,十进制为 51
result := a &^ b
fmt.Printf("a: %08b\n", a) // 输出:11001100
fmt.Printf("b: %08b\n", b) // 输出:00110011
fmt.Printf("Result: %08b\n", result) // 输出:11000000
fmt.Println("Result (Decimal):", result) // 输出:192
}
5.条件语句(Conditional Statements)
前提:chapter2目录下创建 condition,学习总结如下:
if
语句
if
语句用于基于条件来决定是否执行某段代码。它的基本语法如下:
if condition {
// 代码块
} else if anotherCondition {
// 代码块
} else {
// 代码块
}
switch
语句
switch
语句用于基于表达式的不同值执行不同的代码分支。与其他语言不同,Go的switch
可以自动匹配第一个满足条件的分支,而无需使用break
语句。它的语法如下:
switch expression {
case value1:
// 代码块
case value2:
// 代码块
default:
// 代码块
}
创建condition_test.go进行验证分析, 具体代码如下:
package condition
import (
"fmt"
"testing"
)
func TestConditionIf(t *testing.T) {
age := 18
if age < 18 {
fmt.Println("You are a minor.")
} else if age >= 18 && age < 60 {
fmt.Println("You are an adult.")
} else {
fmt.Println("You are a senior citizen.")
}
}
func TestConditionSwitch(t *testing.T) {
dayOfWeek := 3
switch dayOfWeek {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
case 4:
fmt.Println("Thursday")
case 5:
fmt.Println("Friday")
default:
fmt.Println("Weekend")
}
}
func TestSwitchMultiCase(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2:
t.Logf("%d is Even", i)
case 1, 3:
t.Logf("%d is Odd", i)
default:
t.Logf("%d is not 0-3", i)
}
}
}
func TestSwitchCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch {
case i%2 == 0:
t.Logf("%d is Even", i)
case i%2 == 1:
t.Logf("%d is Odd", i)
default:
t.Logf("%d is unknow", i)
}
}
}
下面逐个解释每个测试函数的内容:
-
根据年龄的不同情况,通过func TestConditionIf(t *testing.T) { ... }
:测试if
语句的使用。if
、else if
和else
分支判断是否为未成年人、成年人或老年人。 -
func TestConditionSwitch(t *testing.T) { ... }
:测试switch
语句的使用。根据dayOfWeek
的值,使用switch
语句输出对应的星期几。 -
func TestSwitchMultiCase(t *testing.T) { ... }
:测试switch
语句多个case
值的情况。使用switch
语句判断每个数字的奇偶性,并输出相应的信息。 -
func TestSwitchCaseCondition(t *testing.T) { ... }
:测试switch
语句中的条件表达式。使用switch
语句通过对数字取余判断数字的奇偶性,并输出相应的信息。
这些测试函数展示了Go语言中条件语句的不同用法,包括基于条件的分支判断和多个 case
值的处理,以及在 switch
语句中使用条件表达式的情况。
6.循环语句(Loop Statements)
前提:chapter2目录下创建 loop,学习总结如下:
for
循环
for
循环用于重复执行代码块,支持初始化语句、循环条件和循环后的语句。它的基本形式如下:
for initialization; condition; post {
// 代码块
}
在初始化语句中,您可以初始化循环变量,然后在循环体中使用条件来控制循环,最后在 post
语句中执行递增或递减操作。
for
循环的简化形式
Go语言的 for
循环还可以简化成只有循环条件部分,类似于其他语言中的 while
循环:
for condition {
// 代码块
}
range
循环
range
循环用于迭代数组、切片、映射、字符串等可迭代的数据结构。它返回每次迭代的索引和值。示例:
for index, value := range iterable {
// 使用 index 和 value
}
创建loop_test.go进行验证分析, 具体代码如下:
package loop
import (
"fmt"
"testing"
)
func TestLoopFor(t *testing.T) {
for i := 1; i <= 5; i++ {
fmt.Println("Iteration:", i)
}
}
func TestLoopForBasic(t *testing.T) {
i := 1
for i <= 5 {
fmt.Println("Iteration:", i)
i++
}
}
func TestLoopForRange(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
func TestLoopForUnLimit(t *testing.T) {
i := 1
for {
fmt.Println("Iteration:", i)
i++
if i > 5 {
break
}
}
}
下面逐个解释每个测试函数的内容:
-
func TestLoopFor(t *testing.T) { ... }
:测试基本的for
循环。使用for
循环,从 1 到 5 迭代输出循环迭代次数。 -
func TestLoopForBasic(t *testing.T) { ... }
:测试不带初始化语句的for
循环。使用for
循环,从 1 到 5 迭代输出循环迭代次数,但没有在循环头部声明初始化语句。 -
func TestLoopForRange(t *testing.T) { ... }
:测试使用for range
迭代切片。定义一个整数切片numbers
,使用for range
循环迭代切片中的每个元素,输出元素的索引和值。 -
func TestLoopForUnLimit(t *testing.T) { ... }
:测试无限循环及break
语句。使用无限循环和break
语句,在循环体内部判断是否终止循环,当i
大于 5 时退出循环。
这些测试函数展示了Go语言中不同类型的 for
循环的用法,包括标准的计数循环、不带初始化语句的循环、遍历切片以及无限循环与循环终止条件。
7.跳转语句(Jump Statements)
前提:chapter2目录下创建 jump,学习总结如下:
Go语言也支持几种跳转语句,用于在循环和条件中控制流程:
-
break
:跳出循环。 -
continue
:跳过本次循环迭代,继续下一次迭代。 -
goto
:在代码中直接跳转到指定标签处(不推荐使用)。
创建jump_test.go进行验证分析, 具体代码如下:
package jump
import (
"fmt"
"testing"
)
func TestJumpBreak(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
break
}
fmt.Println("Iteration:", i)
}
}
func TestJumpContinue(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println("Iteration:", i)
}
}
func TestJumpGoto(t *testing.T) {
i := 1
start:
fmt.Println("Iteration:", i)
i++
if i <= 5 {
goto start
}
}
下面逐个解释每个测试函数的内容:
-
func TestJumpBreak(t *testing.T) { ... }
:测试break
语句的使用。使用for
循环迭代从 1 到 5,但当迭代变量i
等于 3 时,使用break
语句终止循环。 -
func TestJumpContinue(t *testing.T) { ... }
:测试continue
语句的使用。使用for
循环迭代从 1 到 5,但当迭代变量i
等于 3 时,使用continue
语句跳过该次迭代继续下一次迭代。 -
func TestJumpGoto(t *testing.T) { ... }
:测试goto
语句的使用。使用goto
语句实现了一个无限循环,即使用标签start
和goto start
在循环体内部跳转到循环的起始位置。循环的终止条件是当i
大于 5 时。
这些测试函数展示了Go语言中的循环控制跳转语句,包括用于终止循环的 break
、用于跳过当前迭代的 continue
,以及用于无限循环的 goto
语句。
(三)常用集合和字符串
src目录下创建chapter3,在Go语言中,集合是存储一组值的数据结构。常用的集合类型包括数组、切片、映射和通道。
1.数组
前提:chapter3目录下创建 array,学习总结如下:
Go语言中的数组是一种固定长度、同类型元素的集合。
数组的特点
- 数组的长度在声明时指定,且在创建后不可更改。
- 数组是值类型,当数组被赋值给新变量或作为参数传递时,会创建一个新的副本。
- 数组在内存中是连续存储的,支持随机访问。
数组的声明和初始化
var arrayName [size]dataType
-
arrayName
:数组的名称。 -
size
:数组的长度,必须是一个常量表达式。 -
dataType
:数组存储的元素类型。
数组的初始化方式
// 使用指定的值初始化数组
var arr = [5]int{1, 2, 3, 4, 5}
// 根据索引初始化数组
var arr [5]int
arr[0] = 10
arr[1] = 20
// 部分初始化
var arr = [5]int{1, 2}
// 自动推断数组长度
arr := [...]int{1, 2, 3, 4, 5}
数组的访问和遍历
// 访问单个元素
value := arr[index]
// 遍历数组
for index, value := range arr {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
数组作为函数参数
数组在函数参数传递时会创建副本,因此对函数内的数组修改不会影响原始数组。如果需要在函数内修改原始数组,可以传递指向数组的指针。
func modifyArray(arr [5]int) {
arr[0] = 100
}
func modifyArrayByPointer(arr *[5]int) {
arr[0] = 100
}
多维数组
Go语言支持多维数组,例如二维数组和三维数组。多维数组的初始化和访问与一维数组类似,只需要指定多个索引。
var matrix [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
数组在存储固定数量的同类型元素时非常有用,但由于其固定长度的限制,通常在实际开发中更常用的是切片,它具有动态长度的特性。切片可以根据需要进行增加、删除和重新分配,更加灵活。
创建array_test.go进行验证分析, 具体代码如下:
package array
import "testing"
func TestArrayInit(t *testing.T) {
var arr [3]int
arr1 := [4]int{1, 2, 3, 4}
arr3 := [...]int{1, 3, 4, 5}
arr1[1] = 5
t.Log(arr[1], arr[2])
t.Log(arr1, arr3)
}
func TestArrayTravel(t *testing.T) {
arr3 := [...]int{1, 3, 4, 5}
for i := 0; i < len(arr3); i++ {
t.Log(arr3[i])
}
for _, e := range arr3 {
t.Log(e)
}
}
func TestArraySection(t *testing.T) {
arr3 := [...]int{1, 2, 3, 4, 5}
arr3_sec := arr3[:]
t.Log(arr3_sec)
}
下面逐个解释每个测试函数的内容:
-
使用不同的方式初始化数组func TestArrayInit(t *testing.T) { ... }
:测试数组的初始化。arr
,arr1
和arr3;
修改arr1
的第二个元素为5;
使用t.Log()
输出不同数组的元素值和内容。 -
使用func TestArrayTravel(t *testing.T) { ... }
:测试数组的遍历。for
循环遍历数组arr3
,分别输出每个元素的值;使用for range
循环遍历数组arr3
,同样输出每个元素的值。 -
创建一个数组切片func TestArraySection(t *testing.T) { ... }
:测试数组切片的使用。arr3_sec
,基于整个数组arr3;
使用t.Log()
输出数组切片arr3_sec
的内容。
2.切片
前提:chapter3目录下创建 slice,学习总结如下:
Go语言中的切片(Slice)是对数组的一层封装,提供了更灵活的动态长度序列。
切片的特点
- 切片是引用类型,它不保存数据,只是引用底层数组的一部分。
- 切片是动态长度的,可以根据需要进行扩容或缩减。
- 切片是可索引的,并且可以通过切片索引进行切割。
切片的声明和初始化
var sliceName []elementType
切片的初始化方式
// 声明切片并初始化
var slice = []int{1, 2, 3, 4, 5}
// 使用 make 函数创建切片
var slice = make([]int, 5) // 创建长度为 5 的 int 类型切片
// 使用切片切割已有数组或切片
newSlice := oldSlice[startIndex:endIndex] // 包括 startIndex,但不包括 endIndex
切片的内置函数和操作
-
len(slice)
:返回切片的长度。 -
cap(slice)
:返回切片的容量,即底层数组的长度。 -
append(slice, element)
:将元素追加到切片末尾,并返回新的切片。 -
copy(destination, source)
:将源切片中的元素复制到目标切片。
切片的遍历
for index, value := range slice {
// 使用 index 和 value
}
切片作为函数参数
切片作为参数传递给函数时,函数内部对切片的修改会影响到原始切片。
func modifySlice(s []int) {
s[0] = 100
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
modifySlice(numbers)
fmt.Println(numbers) // 输出:[100 2 3 4 5]
}
切片在Go语言中广泛用于处理动态数据集,例如集合、列表、队列等。它提供了方便的方法来管理元素,同时避免了固定数组的限制。在实际应用中,切片经常被用于存储和处理变长数据。
创建slice_test.go进行验证分析, 具体代码如下:
package slice
import (
"fmt"
"testing"
)
func TestSlice(t *testing.T) {
// 声明和初始化切片
numbers := []int{1, 2, 3, 4, 5}
fmt.Println("Original Slice:", numbers)
// 使用 make 函数创建切片
slice := make([]int, 3)
fmt.Println("Initial Make Slice:", slice)
// 添加元素到切片
slice = append(slice, 10)
slice = append(slice, 20, 30)
fmt.Println("After Append:", slice)
//