快速学习GO语言总结

发布于:2023-08-24 ⋅ 阅读:(50) ⋅ 点赞:(0)

备注:本博客将自己初步学习GO的总结进行分享,希望大家通过本博客可以在短时间内快速掌握GO的基本程序编码能力,如有错误请留言指正,谢谢!

一、初步了解Go语言

(一)Go语言诞生的主要问题和目标

  1. 多核硬件架构: 随着计算机硬件的发展,多核处理器成为主流,使得并行计算变得普遍。然而,传统的编程语言在处理多核并行性时可能面临困难,因为它们缺乏合适的原生支持。Go语言通过引入轻量级的协程(goroutine)和通道(channel)机制,使得并发编程变得更加容易。开发者可以轻松地创建数千个并发执行的协程,而无需担心线程管理的复杂性。

  2. 超大规模分布式计算集群: 随着云计算和分布式系统的崛起,构建和维护超大规模的分布式计算集群变得越来越常见。这些集群需要能够高效处理大量的请求、数据共享和协调。Go语言的并发特性和通道机制使得编写分布式系统变得更加容易,开发者可以使用协程和通道来处理并发任务、消息传递和协调工作。

  3. Web模式导致的开发规模和更新速度增加: Web应用的兴起带来了前所未有的开发规模和持续更新的需求。传统的编程语言在开发大型Web应用时可能会面临可维护性、性能和开发效率等问题。Go语言通过其简洁的语法、高效的编译速度以及并发支持,使得开发者能够更快速地迭代和部署Web应用,同时也能够更好地处理高并发的网络请求。

综合来看,Go语言在诞生时确实着重解决了多核硬件架构、超大规模分布式计算集群和Web模式下的开发规模与速度等技术挑战。它的设计目标之一是提供一种适应现代软件开发需求的编程语言,使开发者能够更好地应对这些挑战。

(二)Go语言应用典型代表

Go语言在当下应用开发中已经得到广泛应用,许多知名公司和项目都使用Go语言来构建各种类型的应用。以下是一些代表性的产品和项目,它们使用了Go语言作为核心开发语言:

这些仅仅是Go语言应用的一小部分示例,实际上还有许多其他的项目和产品也在使用Go语言来构建高性能、可靠且易于维护的应用程序。这表明Go语言在现代应用开发中发挥了重要作用,特别是在分布式系统、云计算和高性能应用领域。

(三)Java、C++、C程序员在学习编写Go时存在的误区

当Java、C++、C等编程语言的程序员开始学习编写Go语言时,可能会遇到一些误区,因为Go在某些方面与这些传统语言有所不同。以下是一些常见的误区:

  1. 过度使用传统的并发模型: 传统的编程语言如Java、C++、C在处理并发时通常使用线程和锁来实现,但在Go中,使用协程(goroutine)和通道(channel)是更好的方式新学习Go的程序员可能会继续使用传统的并发模型,而不充分利用Go的轻量级协程和通道,从而失去了Go的并发优势。

  2. 过度使用指针: C和C++等语言强调指针的使用,但Go语言在设计时避免了过多的指针操作。新学习Go的程序员可能会过度使用指针,导致代码变得复杂。在Go中,尽量避免使用指针,除非真正需要对值进行修改。

  3. 忽视错误处理: Go鼓励显式地处理错误,而不是简单地忽略它们。这与一些其他语言的习惯不同,其中错误往往被忽略或简单地抛出。新学习Go的程序员可能会忽视错误处理,导致潜在的问题未被检测到。

  4. 过度使用全局变量: 在C和C++等语言中,全局变量可能是常见的做法。然而,在Go中,全局变量的使用被视为不良实践。Go鼓励使用局部变量和传递参数的方式来传递数据,以避免引入不必要的耦合和副作用。

  5. 不熟悉切片和映射: Go中的切片和映射是强大的数据结构,但对于其他语言的程序员来说可能不太熟悉。学习如何正确使用切片和映射是很重要的,因为它们在Go中广泛用于集合和数据处理。

  6. 错误的Go风格: 每种语言都有其独特的编码风格和惯例。新学习Go的程序员可能会在Go代码中应用其他语言的编码风格,这可能会使代码难以阅读和理解。了解和遵循Go的编码规范是很重要的。

为了避免这些误区,学习Go的程序员应该投入时间去理解Go语言的核心概念,包括并发模型、错误处理、数据结构等,同时积极参与Go社区,阅读Go的官方文档和示例代码,以便更好地适应Go的设计理念和最佳实践。

二、环境准备(以Mac说明)

(一)环境设置

在macOS上设置Go语言开发环境非常简单,可以按照以下步骤进行操作:

  1. 使用Homebrew安装: 如果您使用Homebrew包管理器,这是最方便的方法。打开终端,并运行以下命令来安装Go语言:

    brew install go
  2. 手动安装: 如果您希望手动安装Go语言,可以按照以下步骤操作:

    a. 访问官方网站下载安装包`goX.X.X.darwin-amd64.pkg

    b. 双击下载的安装包,按照指示运行安装程序。按照默认设置即可,安装路径通常是/usr/local/go

  3. 设置环境变量: 一旦安装完成,您需要将Go语言的二进制路径添加到您的终端配置文件中的PATH环境变量中。这样您就可以在终端中直接运行Go命令。

    a. 打开终端,并使用文本编辑器(如nano、vim或任何您喜欢的编辑器)编辑您的终端配置文件。例如:

    nano ~/.bash_profile

    b. 在文件中添加以下行(根据您的安装路径进行调整),然后保存并退出编辑器:

    export PATH=$PATH:/usr/local/go/bin

    c. 使配置生效,可以运行以下命令或者重启终端:

    source ~/.bash_profile
  4. 验证安装: 打开终端,输入以下命令来验证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" 消息。下面是对代码的逐行分析:

  1. package main: 声明这个文件属于名为 "main" 的包,这是一个Go程序的入口包名。

  2. import ("fmt" "os"): 引入了两个标准库包,分别是 "fmt" 用于格式化输出,和 "os" 用于与操作系统交互。

  3. func main() { ... }: 这是程序的入口函数,它会在程序运行时首先被调用。

  4. if len(os.Args) > 1 { ... }: 这个条件语句检查命令行参数的数量是否大于1,也就是判断是否有参数传递给程序。os.Args 是一个字符串切片,它包含了所有的命令行参数,第一个参数是程序的名称。

  5. fmt.Println("Hello World", os.Args[1]): 如果有参数传递给程序,就会执行这行代码。它使用 fmt.Println 函数打印一条消息,消息由字符串 "Hello World" 和 os.Args[1] 组成,os.Args[1] 表示传递给程序的第一个参数。

综上所述,这段代码涵盖了以下知识点:

  1. 包导入和使用标准库:通过 import 关键字导入 "fmt" 和 "os" 包,然后在代码中使用这些包提供的函数和类型。

  2. 命令行参数获取:使用 os.Args 获取命令行参数。

  3. 条件语句:使用 if 条件语句来判断是否有命令行参数传递给程序。

  4. 字符串操作:使用字符串连接操作将 "Hello World" 与命令行参数拼接在一起。

  5. 格式化输出:使用 fmt.Println 函数将消息输出到标准输出。

注意:如果没有传递参数给程序,那么这段代码不会打印任何消息。如果传递了多个参数,代码只会使用第一个参数并忽略其他参数。

在该目录下执行“go run hello.go ZYF”,运行结果为“Hello World ZYF”。

(二)基本程序结构编写学习

src目录下创建chapter2

1.变量

前提:chapter2目录下创建variables,学习总结如下:

  1. 变量声明: 使用var关键字声明一个变量,例如:var x int
  2. 类型推断: 可以使用:=操作符进行变量声明和赋值,Go会根据右侧的值自动推断变量类型,例如:y := 5
  3. 变量赋值: 使用赋值操作符=给变量赋值,例如:x = 10
  4. 多变量声明: 可以同时声明多个变量,例如:var a, b, c int
  5. 变量初始化: 变量可以在声明时进行初始化,例如:var name string = "John"
  6. 零值: 未初始化的变量会被赋予零值,数字类型为0,布尔类型为false,字符串类型为空字符串等。
  7. 短变量声明: 在函数内部,可以使用短变量声明方式,例如: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)
}

下面逐个解释代码中涉及的知识点:

  1. package variables: 声明了一个名为 "variables" 的包,这是一个用于测试的包名。

  2. import "testing": 导入了Go语言的测试框架 "testing" 包,用于编写和运行测试函数。

  3. func TestFibList(t *testing.T) { ... }: 定义了一个测试函数 "TestFibList",该函数用于测试斐波那契数列生成逻辑。这是一个测试函数的标准命名,以 "Test" 开头,接着是被测试的函数名。

    • 在测试函数内部,声明了两个整数变量 ab,并将它们初始化为 1,这是斐波那契数列的前两个数。
    • 使用 t.Log(a) 打印变量 a 的值到测试日志中。
    • 使用循环来生成斐波那契数列的前 5 个数,每次迭代都会将 b 的值打印到测试日志,并更新 ab 的值以生成下一个数。
  4. func TestExchange(t *testing.T) { ... }: 定义了另一个测试函数 "TestExchange",该函数用于测试变量交换的逻辑。

    • 在测试函数内部,声明了两个整数变量 ab,并分别将它们初始化为 1 和 2。
    • 使用注释的方式展示了一种变量交换的写法(通过中间变量),但实际上被注释掉了。然后使用 a, b = b, a 这一行代码来实现 ab 的交换,这是Go语言中的一种特有的交换方式,不需要额外的中间变量。
    • 使用 t.Log(a, b) 打印交换后的变量值到测试日志中。

2.常量

前提:chapter2目录下创建constant,学习总结如下:

  1. 常量声明: 使用const关键字声明一个常量,例如:const pi = 3.14159
  2. 常量赋值: 常量的值在声明时必须被赋值,一旦赋值后不可修改。
  3. 枚举常量: 可以使用一组常量来模拟枚举,例如:
    const (
        Monday = 1
        Tuesday = 2
        // ...
    )
  4. 类型指定: 常量的类型也可以被指定,例如:const speed int = 300000
  5. 常量表达式: 常量可使用表达式计算,例如:const secondsInHour = 60 * 60
  6. 无类型常量: 常量可以是无类型的,根据上下文自动推断类型。例如,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)
}

下面逐个解释代码中涉及的知识点:

  1. package constant: 声明了一个名为 "constant" 的包,这是一个用于测试的包名。

  2. import "testing": 导入了Go语言的测试框架 "testing" 包,用于编写和运行测试函数。

  3. const (...): 定义了两个常量块。

    • 第一个常量块中,使用了 iota 常量生成器来定义了一系列从 1 开始递增的常量。在这个例子中,Monday 被赋值为 1,Tuesday 被赋值为 2,Wednesday 被赋值为 3。iota 在常量块中每次被使用时会递增一次,因此后续的常量会依次递增。

    • 第二个常量块中,使用了 iota 来定义了一系列按位左移的常量。在这个例子中,Readable 被赋值为 1,Writable 被赋值为 2(二进制中的 10),Executable 被赋值为 4(二进制中的 100)。位运算中,左移操作可以将二进制数向左移动指定的位数。

  4. func TestConstant1(t *testing.T) { ... }: 定义了一个测试函数 "TestConstant1",用于测试第一个常量块中定义的常量。

    • 使用 t.Log(Monday, Tuesday) 打印常量 MondayTuesday 的值到测试日志中。
  5. func TestConstant2(t *testing.T) { ... }: 定义了另一个测试函数 "TestConstant2",用于测试位运算和常量的使用。

    • 在测试函数内部,声明了一个整数变量 a,并将其初始化为 1,即二进制中的 0001。
    • 使用位运算和按位与操作来检查变量 a 是否具有 ReadableWritableExecutable 属性。例如,a&Readable == Readable 表达式检查 a 的二进制表示是否含有 Readable 标志位。
    • 使用 t.Log() 打印三个表达式的结果到测试日志中。

3.数据类型

前提:chapter2目录下创建 type,学习总结如下:

主要数据类型说明

Go语言具有丰富的内置数据类型,这些数据类型用于表示不同类型的值和数据。以下是对Go语言中一些主要数据类型的总结分析:

  1. 整数类型(Integer Types):Go语言提供不同大小的整数类型,如intint8int16int32int64。无符号整数类型有uintuint8uint16uint32uint64。整数类型的大小取决于计算机的架构,例如32位或64位。

  2. 浮点数类型(Floating-Point Types):Go语言提供float32float64两种浮点数类型,分别对应单精度和双精度浮点数。

  3. 复数类型(Complex Types):Go语言提供complex64complex128两种复数类型,分别对应由两个浮点数构成的复数。

  4. 布尔类型(Boolean Type):布尔类型用于表示真(true)和假(false)的值,用于条件判断和逻辑运算。

  5. 字符串类型(String Type):字符串类型表示一系列字符。字符串是不可变的,可以使用双引号"或反引号`来定义。

  6. 字符类型(Rune Type):字符类型rune用于表示Unicode字符,它是int32的别名。通常使用单引号'来表示字符,如'A'

  7. 数组类型(Array Types):数组是具有固定大小的同类型元素集合。声明数组时需要指定元素类型和大小。

  8. 切片类型(Slice Types):切片是对数组的一层封装,是动态长度的可变序列。切片不保存元素,只是引用底层数组的一部分。

  9. 映射类型(Map Types):映射是键值对的无序集合,用于存储和检索数据。键和值可以是任意类型,但键必须是可比较的。

  10. 结构体类型(Struct Types):结构体是一种用户定义的复合数据类型,可以包含不同类型的字段,每个字段有一个名字和类型。

  11. 接口类型(Interface Types):接口是一种抽象类型,用于定义一组方法。类型实现了接口的方法集合即为实现了该接口。

  12. 函数类型(Function Types):函数类型表示函数的签名,包括参数和返回值类型。函数可以作为参数传递和返回。

  13. 通道类型(Channel Types):通道是用于在协程之间进行通信和同步的一种机制。通道有发送和接收操作。

  14. 指针类型(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)
}

下面逐个解释代码中涉及的知识点:

  1. type Person struct { ... }: 定义了一个结构体类型 Person,表示一个人的信息,包括 FirstNameLastNameAge 字段。

  2. type Shape interface { ... }: 定义了一个接口类型 Shape,该接口要求实现一个方法 Area() 返回一个 float64 类型。

  3. type Circle struct { ... }: 定义了一个结构体类型 Circle,表示一个圆的半径。

    func (c Circle) Area() float64 { ... }:为 Circle 类型实现了 Shape 接口的 Area() 方法,用于计算圆的面积。
  4. func add(a, b int) int { ... }: 定义了一个函数 add,用于执行整数相加操作。

  5. func subtract(a, b int) int { ... }: 定义了一个函数 subtract,用于执行整数相减操作。

  6. type Operation func(int, int) int: 定义了一个函数类型 Operation,它接受两个整数参数并返回一个整数结果。

  7. main() { ... }: 程序的入口函数。

  • 定义了多种不同类型的变量,包括整数、浮点数、布尔、字符串、切片、映射、结构体、接口、函数、通道和指针类型。
  • 演示了不同类型变量的初始化、赋值、访问以及基本操作。
  • 使用切片操作提取部分切片。
  • 演示了映射的使用,包括添加新的键值对和访问键值对。
  • 演示了结构体的定义和初始化,并访问结构体字段。
  • 展示了接口的使用,将 Circle 类型赋值给 Shape 类型变量,并调用接口方法。
  • 演示了函数类型的定义和使用,将不同函数赋值给 Operation 类型变量,并进行调用。
  • 使用通道来实现并发通信,通过匿名函数在 goroutine 中发送和接收消息。
  • 演示了指针的使用,包括创建指针变量、通过指针修改变量的值等操作。

Go语言中类型转换说明

Go语言支持类型转换,但需要注意一些规则和限制。类型转换用于将一个数据类型的值转换为另一个数据类型,以便在不同的上下文中使用。以下是有关Go语言中类型转换的一些重要信息:

  1. 基本类型之间的转换: 可以在基本数据类型之间进行转换,但是必须注意类型的兼容性和可能导致的数据丢失。例如,从intfloat64的转换是安全的,但从float64int可能导致小数部分被截断。

  2. 显示类型转换: 在Go中,使用强制类型转换来显式指定将一个值转换为另一个类型。语法是:destinationType(expression)。例如:float64(10)

  3. 非兼容类型之间的转换: 对于不兼容的类型,编译器不会自动进行转换。例如,不能直接将一个string类型转换为int类型。

  4. 类型别名的转换: 如果有类型别名(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)
}

下面逐个解释代码中涉及的知识点:

  1. const (...): 定义了三个常量 ReadableWritableExecutable,使用位移操作生成不同的值。

  2. func TestOperatorBasic(t *testing.T) { ... }: 定义了一个测试函数 "TestOperatorBasic",用于测试基本运算符的使用。

    • 算术运算符:展示了加法、减法、乘法、除法和取余运算。
    • 逻辑运算符:展示了逻辑与、逻辑或和逻辑非运算。
    • 比较运算符:展示了等于、不等于、大于、小于、大于等于和小于等于运算。
  3. func TestCompareArray(t *testing.T) { ... }: 定义了一个测试函数 "TestCompareArray",用于测试数组的比较。

    • 声明了两个整数数组 ab,以及另一个数组 d,其中数组 a 和数组 d 的内容相同。
    • 使用比较运算符 == 检查数组 ab 是否相等,以及数组 ad 是否相等。
  4. func TestBitClear(t *testing.T) { ... }: 定义了一个测试函数 "TestBitClear",用于测试位清除操作。

    • 声明一个整数变量 a,并将其初始化为 7,即二进制表示 0111
    • 使用位清除操作 &^a 中的 ReadableExecutable 位清除。
    • 使用按位与运算 & 检查 a 是否具有 ReadableWritableExecutable 属性。

按位清除运算符 &^

在Go语言中,&^ 是按位清除运算符(Bit Clear Operator)。它用于将某些位置上的位清零,即将指定位置上的位设置为0。&^ 运算符在处理二进制位操作时非常有用。

&^ 运算符执行以下操作:

  1. 对于每个位,如果右侧操作数的对应位为 0,则结果位与左侧操作数相同。
  2. 对于每个位,如果右侧操作数的对应位为 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)
		}
	}
}

下面逐个解释每个测试函数的内容:

  1. func TestConditionIf(t *testing.T) { ... }:测试 if 语句的使用。

    根据年龄的不同情况,通过 ifelse ifelse 分支判断是否为未成年人、成年人或老年人。
  2. func TestConditionSwitch(t *testing.T) { ... }:测试 switch 语句的使用。根据 dayOfWeek 的值,使用 switch 语句输出对应的星期几。

  3. func TestSwitchMultiCase(t *testing.T) { ... }:测试 switch 语句多个 case 值的情况。使用 switch 语句判断每个数字的奇偶性,并输出相应的信息。

  4. 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
		}
	}
}

下面逐个解释每个测试函数的内容:

  1. func TestLoopFor(t *testing.T) { ... }:测试基本的 for 循环。使用 for 循环,从 1 到 5 迭代输出循环迭代次数。

  2. func TestLoopForBasic(t *testing.T) { ... }:测试不带初始化语句的 for 循环。使用 for 循环,从 1 到 5 迭代输出循环迭代次数,但没有在循环头部声明初始化语句。

  3. func TestLoopForRange(t *testing.T) { ... }:测试使用 for range 迭代切片。定义一个整数切片 numbers,使用 for range 循环迭代切片中的每个元素,输出元素的索引和值。

  4. 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
	}
}

下面逐个解释每个测试函数的内容:

  1. func TestJumpBreak(t *testing.T) { ... }:测试 break 语句的使用。使用 for 循环迭代从 1 到 5,但当迭代变量 i 等于 3 时,使用 break 语句终止循环。

  2. func TestJumpContinue(t *testing.T) { ... }:测试 continue 语句的使用。使用 for 循环迭代从 1 到 5,但当迭代变量 i 等于 3 时,使用 continue 语句跳过该次迭代继续下一次迭代。

  3. func TestJumpGoto(t *testing.T) { ... }:测试 goto 语句的使用。使用 goto 语句实现了一个无限循环,即使用标签 startgoto start 在循环体内部跳转到循环的起始位置。循环的终止条件是当 i 大于 5 时。

这些测试函数展示了Go语言中的循环控制跳转语句,包括用于终止循环的 break、用于跳过当前迭代的 continue,以及用于无限循环的 goto 语句。

(三)常用集合和字符串

src目录下创建chapter3,在Go语言中,集合是存储一组值的数据结构。常用的集合类型包括数组、切片、映射和通道。

1.数组

前提:chapter3目录下创建 array,学习总结如下:

Go语言中的数组是一种固定长度、同类型元素的集合。以下是关于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)
}

下面逐个解释每个测试函数的内容:

  1. func TestArrayInit(t *testing.T) { ... }:测试数组的初始化。

    • 使用不同的方式初始化数组 arrarr1arr3
    • 修改 arr1 的第二个元素为 5
    • 使用 t.Log() 输出不同数组的元素值和内容。
  2. func TestArrayTravel(t *testing.T) { ... }:测试数组的遍历。

    • 使用 for 循环遍历数组 arr3,分别输出每个元素的值。
    • 使用 for range 循环遍历数组 arr3,同样输出每个元素的值。
  3. 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)

	// 复制切片
	copySlice := make([]int, len(slice))
	copy(copySlice, slice)
	fmt.Println("Copied Slice:", copySlice)

	// 切片切割
	subSlice := numbers[1:3]
	fmt.Println("Subslice:", subSlice)

	// 修改切片的值会影响底层数组和其他切片
	subSlice[0] = 100
	fmt.Println("Modified Subslice:", subSlice)
	fmt.Println("Original Slice:", numbers)
	fmt.Println("Copied Slice:", copySlice)

	// 遍历切片
	for index, value := range slice {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}
}

下面逐个解释每个测试函数的内容:

func TestSlice(t *testing.T) { ... }:测试切片的基本操作。

  • 声明和初始化切片 numbers,输出初始切片内容。
  • 使用 make 函数创建初始容量为 3 的切片 slice,输出初始切片内容。
  • 使用 append 函数向切片 slice 添加元素。
  • 使用 copy 函数复制切片 slice 到新的切片 copySlice
  • 使用切片 numbers 进行切片切割,创建子切片 subSlice
  • 修改 subSlice 的第一个元素为 100,输出修改后的切片和原始切片,以及复制的切片。
  • 使用 for range 循环遍历切片 slice,输出每个元素的索引和值。

这个测试函数展示了Go语言中切片的各种操作,包括切片的创建、添加元素、复制切片、切片切割、修改切片元素等。

3.Map

前提:chapter3目录下创建 map,学习总结如下:

Go语言中的映射(Map)是键值对的无序集合,也被称为关联数组或字典。以下是关于映射的知识总结和应用:

映射的特点

  • 映射用于存储一组键值对,其中每个键都是唯一的。
  • 映射是无序的,无法保证键值对的顺序。
  • 键可以是任何可比较的类型,值可以是任意类型。
  • 映射是引用类型,可以被赋值和传递给函数。

映射的声明和初始化

var mapName map[keyType]valueType

映射的初始化方式

// 声明和初始化映射
var ages = map[string]int{
    "Alice": 25,
    "Bob":   30,
    "Eve":   28,
}

// 使用 make 函数创建映射
var ages = make(map[string]int)

映射的操作

  • 添加键值对:ages["Charlie"] = 35
  • 删除键值对:delete(ages, "Eve")
  • 获取值:value := ages["Alice"]

映射的遍历

for key, value := range ages {
    fmt.Printf("Name: %s, Age: %d\n", key, value)
}

映射作为函数参数

映射作为参数传递给函数时,函数内部对映射的修改会影响到原始映射。

func modifyMap(m map[string]int) {
    m["Alice"] = 30
}

func main() {
    ages := map[string]int{
        "Alice": 25,
        "Bob":   30,
    }
    modifyMap(ages)
    fmt.Println(ages) // 输出:map[Alice:30 Bob:30]
}

映射在Go语言中用于存储和检索数据,是一种非常常用的数据结构。它在存储一组关联的键值对时非常有用,比如存储姓名与年龄的对应关系、单词与定义的对应关系等。在实际应用中,映射是处理和存储键值数据的重要工具。

创建map_test.go进行验证分析, 具体代码如下:

package my_map

import (
	"fmt"
	"testing"
)

func TestBasic(t *testing.T) {
	// 声明和初始化映射
	ages := map[string]int{
		"Alice": 25,
		"Bob":   30,
		"Eve":   28,
	}
	fmt.Println("Original Map:", ages)

	// 添加新的键值对
	ages["Charlie"] = 35
	fmt.Println("After Adding:", ages)

	// 修改已有键的值
	ages["Bob"] = 31
	fmt.Println("After Modification:", ages)

	// 删除键值对
	delete(ages, "Eve")
	fmt.Println("After Deletion:", ages)

	// 获取值和检查键是否存在
	age, exists := ages["Alice"]
	if exists {
		fmt.Println("Alice's Age:", age)
	} else {
		fmt.Println("Alice not found")
	}

	// 遍历映射
	for name, age := range ages {
		fmt.Printf("Name: %s, Age: %d\n", name, age)
	}
}

type Student struct {
	Name  string
	Age   int
	Grade string
}

func TestComplex(t *testing.T) {
	// 声明和初始化映射,用于存储学生信息和成绩
	studentScores := make(map[string]int)
	studentInfo := make(map[string]Student)

	// 添加学生信息和成绩
	studentInfo["Alice"] = Student{Name: "Alice", Age: 18, Grade: "A"}
	studentScores["Alice"] = 95

	studentInfo["Bob"] = Student{Name: "Bob", Age: 19, Grade: "B"}
	studentScores["Bob"] = 85

	// 查找学生信息和成绩
	aliceInfo := studentInfo["Alice"]
	aliceScore := studentScores["Alice"]
	fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", aliceInfo.Name, aliceInfo.Age, aliceInfo.Grade, aliceScore)

	// 遍历学生信息和成绩
	for name, info := range studentInfo {
		score, exists := studentScores[name]
		if exists {
			fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", info.Name, info.Age, info.Grade, score)
		} else {
			fmt.Printf("No score available for %s\n", name)
		}
	}
}

下面逐个解释每个测试函数的内容:

  1. func TestBasic(t *testing.T) { ... }:测试映射的基本操作。

    • 声明和初始化映射 ages,存储人名和年龄的键值对。
    • 输出初始映射内容。
    • 使用 ages["Charlie"] 添加新的键值对。
    • 使用 ages["Bob"] 修改已有键的值。
    • 使用 delete 函数删除键值对。
    • 使用 age, exists 来获取值并检查键是否存在。
    • 使用 for range 循环遍历映射,输出每个键值对的信息。
  2. type Student struct { ... }:定义了一个名为 Student 的结构体,用于存储学生信息。

  3. func TestComplex(t *testing.T) { ... }:测试包含复杂值的映射操作。

    • 声明和初始化两个映射,studentScores 用于存储学生分数,studentInfo 用于存储学生信息。
    • 添加学生信息和分数到映射。
    • 使用 studentInfo["Alice"] 获取学生信息,使用 studentScores["Alice"] 获取学生分数。
    • 使用 for range 循环遍历映射,输出每个学生的信息和分数。

这些测试函数展示了Go语言中映射的各种操作,包括创建、添加、修改、删除键值对,检查键是否存在,以及遍历映射的键值对。

4.实现Set

前提:chapter3目录下创建 set,学习总结如下:

在Go语言中,虽然标准库没有提供内置的Set类型,但你可以使用多种方式来实现Set的功能。以下是几种常见的实现Set的方式介绍:

使用切片

创建set_slice_test.go练习

使用切片来存储元素,通过遍历切片来检查元素是否存在。这是一个简单的实现方式,适用于小型的集合。

package set

import (
	"fmt"
	"testing"
)

type IntSet struct {
	elements []int
}

func (s *IntSet) Add(element int) {
	if !s.Contains(element) {
		s.elements = append(s.elements, element)
	}
}

func (s *IntSet) Contains(element int) bool {
	for _, e := range s.elements {
		if e == element {
			return true
		}
	}
	return false
}

func TestSet(t *testing.T) {
	set := IntSet{}
	set.Add(1)
	set.Add(2)
	set.Add(3)
	set.Add(2) // Adding duplicate, should be ignored

	fmt.Println("Set:", set.elements) // Output: [1 2 3]
}

使用映射

创建set_map_test.go练习

使用映射来存储元素,映射的键代表集合的元素,值可以是任意类型。这样的实现方式更快速,适用于大型的集合,因为映射的查找复杂度为 O(1)

package set

import (
	"fmt"
	"testing"
)

type Set map[int]bool

func (s Set) Add(element int) {
	s[element] = true
}

func (s Set) Contains(element int) bool {
	return s[element]
}

func TestSetMap(t *testing.T) {
	set := make(Set)
	set.Add(1)
	set.Add(2)
	set.Add(3)
	set.Add(2) // Adding duplicate, should be ignored

	fmt.Println("Set:", set) // Output: map[1:true 2:true 3:true]
}

使用第三方库

创建set_third_test.go练习

为了避免自行实现,你可以使用一些第三方库,例如 github.com/deckarep/golang-set,它提供了更丰富的Set功能。

添加个代理:go env -w GOPROXY=https://goproxy.io,direct

然后安装包:go get github.com/deckarep/golang-set

package set

import (
	"fmt"
	"github.com/deckarep/golang-set"
	"testing"
)

func TestSetThird(t *testing.T) {
	intSet := mapset.NewSet()
	intSet.Add(1)
	intSet.Add(2)
	intSet.Add(3)
	intSet.Add(2) // Adding duplicate, will be ignored

	fmt.Println("Set:", intSet) // Output: Set: Set{1, 2, 3}
}

以上是几种实现Set的方式,你可以根据需求和性能考虑选择适合的实现方式。第三方库可以提供更多功能和性能优化,适用于大规模的数据集合。

5.字符串

前提:chapter3目录下创建 string,学习总结如下:

字符串的声明与初始化

在Go语言中,字符串是由一系列字符组成的,可以使用双引号 " 或反引号 ``` 来声明和初始化字符串。

package main

import "fmt"

func main() {
    str1 := "Hello, World!"   // 使用双引号声明
    str2 := `Go Programming` // 使用反引号声明

    fmt.Println(str1) // Output: Hello, World!
    fmt.Println(str2) // Output: Go Programming
}

字符串的长度

使用内置函数 len() 可以获取字符串的长度,即字符串中字符的个数。

package main

import "fmt"

func main() {
    str := "Hello, 世界!"
    length := len(str)
    fmt.Println("String Length:", length) // Output: String Length: 9
}

字符串的索引与切片

字符串中的字符可以通过索引访问,索引从0开始。可以使用切片操作来获取字符串的子串。

package main

import "fmt"

func main() {
    str := "Hello, World!"

    // 获取第一个字符
    firstChar := str[0]
    fmt.Println("First Character:", string(firstChar)) // Output: First Character: H

    // 获取子串
    substring := str[7:12]
    fmt.Println("Substring:", substring) // Output: Substring: World
}

字符串拼接

使用 + 运算符可以将两个字符串连接成一个新的字符串。另外,strings.Join 函数用于将字符串切片连接成一个新的字符串,可以用来拼接多个字符串。

最后,使用字节缓冲可以在不产生多余字符串副本的情况下进行高效的字符串拼接。

package main

import (
    "fmt"
    "strings"
    "bytes"
)

func main() {
    str1 := "Hello, "
    str2 := "World!"
    
    result := str1 + str2
    fmt.Println("Concatenated String:", result) // Output: Concatenated String: Hello, World!

    strSlice := []string{"Hello", " ", "World!"}
    result := strings.Join(strSlice, "")
    fmt.Println(result) // Output: Hello World!

    var buffer bytes.Buffer
    
    buffer.WriteString(str1)
    buffer.WriteString(str2)
    
    result := buffer.String()
    fmt.Println(result) // Output: Hello, World!
}

多行字符串

使用反引号 ``` 来创建多行字符串。

package main

import "fmt"

func main() {
    multiLineStr := `
        This is a
        multi-line
        string.
    `
    fmt.Println(multiLineStr)
}

字符串迭代

使用 for range 循环迭代字符串的每个字符。

package main

import "fmt"

func main() {
    str := "Go语言"
    
    for _, char := range str {
        fmt.Printf("%c ", char) // Output: G o 语 言
    }
}

字符串和字节数组之间的转换

在Go语言中,字符串和字节数组之间可以进行相互转换。

package main

import "fmt"

func main() {
    str := "Hello"
    bytes := []byte(str) // 转换为字节数组
    strAgain := string(bytes) // 字节数组转换为字符串

    fmt.Println("Bytes:", bytes)       // Output: Bytes: [72 101 108 108 111]
    fmt.Println("String Again:", strAgain) // Output: String Again: Hello
}

字符串比较

字符串的比较可以使用 ==!= 运算符。当然还有其他函数类型的直接应用的:strings.Compare 函数用于比较两个字符串,并根据比较结果返回一个整数。

也可以使用自定义的比较函数来比较字符串,根据自己的需求定义比较逻辑。

package main

import (
    "fmt"
    "strings"
)


func customCompare(str1, str2 string) bool {
    // 自定义比较逻辑
    return str1 == str2
}

func main() {
    str1 := "Hello"
    str2 := "World"

    if str1 == str2 {
        fmt.Println("Strings are equal")
    } else {
        fmt.Println("Strings are not equal") // Output: Strings are not equal
    }


    result := strings.Compare(str1, str2)
    if result == 0 {
        fmt.Println("Strings are equal")
    } else if result < 0 {
        fmt.Println("str1 is less than str2")
    } else {
        fmt.Println("str1 is greater than str2") // Output: str1 is less than str2
    }

    if customCompare(str1, str2) {
        fmt.Println("Strings are equal")
    } else {
        fmt.Println("Strings are not equal") // Output: Strings are not equal
    }
}

这些基本概念和操作可以帮助你更好地理解和使用Go语言中的字符串。要注意字符串的不可变性,以及与其他数据类型的转换和比较。

创建string_test.go练习

package string

import (
	"strconv"
	"strings"
	"testing"
)

func TestString(t *testing.T) {
	var s string
	t.Log(s) //初始化为默认零值“”
	s = "hello"
	t.Log(len(s))
	//s[1] = '3' //string是不可变的byte slice
	//s = "\xE4\xB8\xA5" //可以存储任何二进制数据
	s = "\xE4\xBA\xBB\xFF"
	t.Log(s)
	t.Log(len(s))
	s = "中"
	t.Log(len(s)) //是byte数

	c := []rune(s)
	t.Log(len(c))
	//	t.Log("rune size:", unsafe.Sizeof(c[0]))
	t.Logf("中 unicode %x", c[0])
	t.Logf("中 UTF8 %x", s)
}

func TestStringToRune(t *testing.T) {
	s := "中华人民共和国"
	for _, c := range s {
		t.Logf("%[1]c %[1]x", c)
	}
}

func TestStringFn(t *testing.T) {
	s := "A,B,C"
	parts := strings.Split(s, ",")
	for _, part := range parts {
		t.Log(part)
	}
	t.Log(strings.Join(parts, "-"))
}

func TestConv(t *testing.T) {
	s := strconv.Itoa(10)
	t.Log("str" + s)
	if i, err := strconv.Atoi("10"); err == nil {
		t.Log(10 + i)
	}
}

下面逐个解释每个测试函数的内容:

  1. func TestString(t *testing.T) { ... }:测试字符串的基本操作。

    • 声明一个字符串变量 s,输出其默认零值。
    • 将字符串赋值为 "hello",输出字符串长度。
    • 尝试修改字符串的某个字符,但会报错,因为字符串是不可变的。
    • 使用字符串存储二进制数据和 Unicode 编码。
    • 使用字符串存储一个中文字符,并输出其长度。
    • 将字符串转换为 rune 类型切片,输出切片长度和中文字符的 Unicode 和 UTF-8 编码。
  2. func TestStringToRune(t *testing.T) { ... }:测试字符串到 rune 的转换。

    声明一个包含中文字符的字符串 s,通过 range 遍历将字符串转换为 rune 类型并输出。
  3. func TestStringFn(t *testing.T) { ... }:测试字符串相关的函数。

    声明一个包含逗号分隔的字符串 s,使用 strings.Split 函数拆分字符串并输出每个部分。使用 strings.Join 函数将拆分的部分合并为一个新的字符串,并输出。
  4. func TestConv(t *testing.T) { ... }:测试字符串与其他类型的转换。

    • 使用 strconv.Itoa 将整数转换为字符串。
    • 拼接字符串和整数,并输出结果。
    • 使用 strconv.Atoi 将字符串转换为整数,并进行加法运算,处理错误情况。

这些测试函数展示了Go语言中字符串的各种操作,包括字符串长度、UTF-8 编码、rune 类型转换、字符串拆分和合并,以及字符串与其他类型的转换。

(四)函数

src目录下创建chapter4,在Go语言中,函数是一种用于执行特定任务的代码块,可以被多次调用。以下是关于Go语言函数的一些重要知识点:

函数的声明

在Go中,函数的声明由关键字 func 开始,后面跟着函数名、参数列表、返回值和函数体。

func functionName(parameters) returnType {
    // 函数体
    // 可以包含多个语句
    return returnValue
}

函数参数

函数可以有零个或多个参数,参数由参数名和参数类型组成。参数之间使用逗号分隔。

func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

多返回值

Go语言的函数可以返回多个值。返回值用括号括起来,逗号分隔。

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

命名返回值

函数可以声明命名的返回值,在函数体内可以直接使用这些名称进行赋值,最后不需要显式使用 return 关键字。

func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return
    }
    result = a / b
    return
}

可变数量的参数

Go语言支持使用 ... 语法来表示可变数量的参数。这些参数在函数体内作为切片使用。

func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

函数作为参数

在Go语言中,函数可以作为参数传递给其他函数。

func applyFunction(fn func(int, int) int, a, b int) int {
    return fn(a, b)
}

func add(a, b int) int {
    return a + b
}

func main() {
    result := applyFunction(add, 3, 4)
    fmt.Println(result) // Output: 7
}

匿名函数和闭包

Go语言支持匿名函数,也称为闭包。这些函数可以在其他函数内部定义,并访问外部函数的变量。

func main() {
    x := 5
    fn := func() {
        fmt.Println(x) // 闭包访问外部变量
    }
    fn() // Output: 5
}

defer语句

defer 语句用于延迟执行函数,通常用于在函数返回前执行一些清理操作。

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

以上是一些关于Go语言函数的基本知识点。函数在Go中扮演着非常重要的角色,用于组织代码、实现功能模块化和提高代码的可维护性。

验证一:基本使用用例验证

在chapter4下新建basic,在创建func_basic_test.go练习

package basic

import (
	"errors"
	"fmt"
	"testing"
)

// 普通函数
func greet(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

// 多返回值函数
func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

// 命名返回值函数
func divideNamed(a, b int) (result int, err error) {
	if b == 0 {
		err = errors.New("division by zero")
		return
	}
	result = a / b
	return
}

// 可变数量的参数函数
func sum(numbers ...int) int {
	total := 0
	for _, num := range numbers {
		total += num
	}
	return total
}

// 函数作为参数
func applyFunction(fn func(int, int) int, a, b int) int {
	return fn(a, b)
}

// 匿名函数和闭包
func closureExample() {
	x := 5
	fn := func() {
		fmt.Println(x)
	}
	fn() // Output: 5
}

// defer语句
func deferExample() {
	defer fmt.Println("World")
	fmt.Println("Hello") // Output: Hello World
}

func TestBasic(t *testing.T) {
	greet("Alice") // Output: Hello, Alice!

	q, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Quotient:", q) // Output: Quotient: 5
	}

	qNamed, errNamed := divideNamed(10, 0)
	if errNamed != nil {
		fmt.Println("Error:", errNamed) // Output: Error: division by zero
	} else {
		fmt.Println("Quotient:", qNamed)
	}

	total := sum(1, 2, 3, 4, 5)
	fmt.Println("Sum:", total) // Output: Sum: 15

	addResult := applyFunction(func(a, b int) int {
		return a + b
	}, 3, 4)
	fmt.Println("Addition:", addResult) // Output: Addition: 7

	closureExample()

	deferExample()
}

验证二:业务小举例

在chapter4下新建biz,在创建func_biz_test.go练习,假设你正在开发一个简单的订单处理系统,需要计算订单中商品的总价和应用折扣。你可以使用函数来处理这些业务逻辑。以下是一个简单的示例:

package biz

import (
	"fmt"
	"testing"
)

type Product struct {
	Name  string
	Price float64
}

func calculateTotal(products []Product) float64 {
	total := 0.0
	for _, p := range products {
		total += p.Price
	}
	return total
}

func applyDiscount(amount, discount float64) float64 {
	return amount * (1 - discount)
}

func TestBiz(t *testing.T) {
	products := []Product{
		{Name: "Product A", Price: 10.0},
		{Name: "Product B", Price: 20.0},
		{Name: "Product C", Price: 30.0},
	}

	total := calculateTotal(products)
	fmt.Printf("Total before discount: $%.2f\n", total)

	discountedTotal := applyDiscount(total, 0.1)
	fmt.Printf("Total after 10%% discount: $%.2f\n", discountedTotal)
}

(五)面向对象编程

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到