golang基础学习以及代码实例

2023-12-28 11:28:50

一、Go语言基础

这是我整理非常全的go语言基础知识点以及代码实例,对GO有情趣的同学可以通过这个总结以及代码实例快速入门!加油同学们!

1 Go介绍

是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

相比java,go没有jvm进行代码编译,直接把代码转换为二进制代码,执行效率更高。为每个平台单独编译二进制文件,也支持跨平台。

2、基础知识

1. Go程序开发注意事项(重点

  • Go源文件以“go"为扩展名。
  • Go应用程序的执行入口是main0函数。
  • Go语言严格区分大小写。
  • Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也体现出Golang的简洁性。
  • Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同个,否则报错
  • go语言定义的变量或者import的包如果没有使用到,代码不能编译通过。
  • 大括号都是成对出现的,缺一不可

2.Go语言的常用转义字符(escape char)

  • \t 一个制表符,实现对其的功能
  • \n 换行符
  • \ \ 打印一个“\”
  • \ " 打印一个“"”
  • \r 一个回车符

3.注释两种形式

  • // 行注释 快捷键 ctrl+/ 官方推荐 行注释

  • /* 要注释的内容 */ 块注释

4.规范的代码风格

正确的注释和注释风格:
Go官方推荐使用行注释来注释整个方法和语句。 带看Go源码
正确的缩进和空白
使用一次tab操作,实现缩进,默认整体向右边移动,时候用shift+tab整体向左移
运算符两边习惯性各加一个空格。比如: 2 + 4*5。

5.工作空间

即创建GOPATH目录,并在GOPATH下建立三个目录:

bin:存放编译后生成的可执行文件(.exe)
pkg:存放编译后生成的包文件(.a)
src:存放项目源码(.go)

3、Golang的常量、变量与命名规则

第一个简单的go程序,hello.go

package main

import "fmt"

func main() {
	fmt.Println("hello world!")
	// fmt.Println("hello world!")
	// fmt.Println("hello world!")
	for i := 1; i < 10; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%d * %d = %d\t", j, i, j*i)
		}
		fmt.Println()
	}
}

1. 常量

Go语言中的常量是在程序运行时不可更改的值。常量可以是数字、字符(rune)、字符串或布尔类型。常量的声明使用关键字const

常量的语法如下:

const identifier [type] = value

其中,identifier是常量的名称,type是常量的类型(可省略),value是常量的值。

常量的特点:

  1. 常量在声明时必须进行初始化。
  2. 常量的值不能改变,不能再次赋值。
  3. 在声明常量时,可以使用表达式来初始化常量。

下面是一些常量的示例代码:

package main

import "fmt"

func main() {
	// 声明整型常量
	const a int = 10
	fmt.Println(a) // 输出:10

	// 声明浮点型常量
	const b float64 = 3.14
	fmt.Println(b) // 输出:3.14

	// 声明字符串常量
	const c string = "Hello, World!"
	fmt.Println(c) // 输出:"Hello, World!"

	// 声明布尔型常量
	const d bool = true
	fmt.Println(d) // 输出:true

	// 使用表达式初始化常量
	const e = 2 * 3.14
	fmt.Println(e) // 输出:6.28
}

在Go语言中,常量的命名规则与变量相同,使用驼峰命名法。另外,常量也支持枚举类型的定义,可以一次声明多个常量,并按顺序赋予不同的值。

驼峰命名法(Camel Case)是一种命名规则,常用于变量、函数、常量等标识符的命名。它的命名规则是将多个单词连接在一起,每个单词的首字母大写(除了第一个单词)。这种命名方式的形状和骆驼的驼峰背部形状相似,因此得名。

驼峰命名法有两种常见的风格:小驼峰命名法(lower camel case)和大驼峰命名法(upper camel case)。

  1. 小驼峰命名法(lower camel case):
    • 第一个单词以小写字母开始,后续单词的首字母大写。
    • 比如:myVariableName, myFunctionName, myConstantValue
  2. 大驼峰命名法(upper camel case):
    • 每个单词的首字母都大写。
    • 比如:MyStructName, MyPackageName, MyEnumValue

使用驼峰命名法的好处是可以使代码更易读、更具可读性,并且符合了Go语言的命名惯例。

2. 变量

在Go中,变量用于存储数据,可以是基本类型(比如整型、字符串、布尔值)或者复合类型(比如数组、切片、结构体)。下面我将详细介绍Go语言中的变量,并提供一些代码示例。

(1) 变量声明和初始化
  • 变量声明使用关键字 var,后跟变量名称和类型。
  • 变量可以同时声明和初始化,也可以只声明后再赋值。
  • 使用 := 可以进行变量的自动类型推导和初始化。

示例代码:

var a int          // 声明一个整型变量,初始值为0
var b string       // 声明一个字符串变量,初始值为空字符串
c := 10            // 自动推导为整型变量,并赋值为10
d, e := "Hello", 3.14  // 自动推导为字符串和浮点型变量,并分别赋值

(2) 变量赋值和类型转换
  • 变量可以通过赋值运算符 = 来给其赋值。
  • 在赋值过程中,如果需要进行类型转换,需要使用显式的类型转换。

示例代码:

var a int
a = 10              // 将10赋值给变量a

var b float64
b = float64(a)      // 将变量a的值转换为float64类型并赋给变量b

var c int
c = int(b)          // 将变量b的值转换为int类型并赋给变量c

(3) 变量的作用域
  • 变量的作用域可以是全局的(在函数外部声明)或局部的(在函数内部声明)。
  • 局部变量只能在其声明的函数中使用,全局变量可以在整个包内使用。
  • 在函数内部,如果有同名的局部变量和全局变量,优先使用局部变量。

示例代码:

var globalVariable int  // 全局变量

func main() {
    localVariable := 10  // 局部变量,只能在main函数中使用
    
    fmt.Println(localVariable)  // 输出:10
    fmt.Println(globalVariable) // 输出:0
    
    shadowVariable := 20  // 声明一个与全局变量同名的局部变量
    fmt.Println(shadowVariable) // 输出:20,优先使用局部变量
}

3. 命名规则

Go语言的命名规则如下:

  1. 变量名、函数名、常量名:使用驼峰式命名法,即首字母小写,后续每个单词首字母大写。例如:myVariableName。
  2. 类型名:使用驼峰式命名法,即每个单词首字母大写。例如:MyTypeName。
  3. 包名:使用简短、全小写的名字,可以是一个单词或者缩写。例如:mypackage。
  4. 常量名:全部大写,单词间使用下划线分隔。例如:MAX_SIZE。
package main

import "fmt"

const MAX_SIZE = 10

type MyTypeName struct {
	myField int
}

func myFunction(myVariableName int) {
	fmt.Println(myVariableName)
}

func main() {
	myVar := 5
	myFunction(myVar)

	mt := MyTypeName{myField: 10}
	fmt.Println(mt.myField)
}

在上述代码示例中,我们遵守了Go语言的命名规则,变量名使用驼峰式命名法(myVariableName),常量名使用全大写并使用下划线分隔(MAX_SIZE),类型名使用驼峰式命名法(MyTypeName),包名使用全小写(main),函数名使用驼峰式命名法(myFunction)。

4.数据类型

Go语言的数据类型分为基本类型和复合类型。

1. 基本类型

  • bool:布尔类型,true或false。
  • int、int8、int16、int32、int64:整型,表示带符号的整数,位数分别为8、16、32、64位。
  • uint、uint8、uint16、uint32、uint64、uintptr:无符号整型,位数分别为8、16、32、64位。
  • float32、float64:浮点型,表示带小数位的数字。
  • complex64、complex128:复数类型,用于表示实部和虚部为浮点数的复数。
  • byte:别名,代表uint8类型,通常用于表示一个ASCII字符。
  • rune:别名,代表int32类型,通常用于表示一个Unicode字符。
  • string:字符串类型,由一串字节数组组成。

2. 复合类型

  • array:数组,拥有固定数量的相同类型元素的集合。
  • slice:切片,动态数组,可以增加或减少长度。
  • map:映射,一种存储键值对的无序集合。
  • struct:结构体,用于定义自定义的复合数据类型。
  • pointer:指针,存储变量的内存地址。
  • function:函数,用于封装和重用代码块。

代码示例:

package main

import "fmt"

func main() {
	// 声明变量并赋初值
	var num1 int = 10
	var num2 float32 = 3.14
	var flag bool = true
	var str string = "Hello, Go!"

	// 输出变量的值和类型
	fmt.Printf("num1: %d, type: %T\n", num1, num1)
	fmt.Printf("num2: %.2f, type: %T\n", num2, num2)
	fmt.Printf("flag: %t, type: %T\n", flag, flag)
	fmt.Printf("str: %s, type: %T\n", str, str)

	// 数组示例
	var arr [3]int
	arr[0] = 1
	arr[1] = 2
	arr[2] = 3
	fmt.Println("arr:", arr)

	// 切片示例
	slice := []int{1, 2, 3, 4, 5}
	fmt.Println("slice:", slice)

	// 映射示例
	m := make(map[string]int)
	m["a"] = 1
	m["b"] = 2
	fmt.Println("map:", m)

	// 结构体示例
	type Person struct {
		Name string
		Age  int
	}
	p := Person{Name: "Alice", Age: 20}
	fmt.Println("person:", p)

	// 指针示例
	ptr := &num1
	fmt.Println("pointer:", ptr)

	// 函数示例
	add := func(a, b int) int {
		return a + b
	}
	result := add(3, 4)
	fmt.Println("result:", result)
}

3. 值类型和引用类型

  • 值类型(Value Type):包括基本数据类型(如整型、浮点型、布尔型等)以及数组和结构体。值类型在赋值或者作为参数传递时,会进行值拷贝,即会创建一个新的副本。

  • 引用类型(Reference Type):包括切片、映射、通道和接口。引用类型的变量存储的是数据的地址,而不是数据本身。在赋值或者传递参数时,只是复制了一个指向同一块数据的指针。

下面是一个简单的代码实例来说明值类型和引用类型的区别:

package main

import "fmt"

func modifyValue(val int) {
    val = 100
}

func modifySlice(slice []int) {
    slice[0] = 100
}

func main() {
    // 值类型示例
    num := 10
    fmt.Println("Before modifyValue:", num)
    modifyValue(num)
    fmt.Println("After modifyValue:", num)

    // 引用类型示例
    numbers := []int{1, 2, 3}
    fmt.Println("Before modifySlice:", numbers)
    modifySlice(numbers)
    fmt.Println("After modifySlice:", numbers)
}

输出结果:
Before modifyValue: 10
After modifyValue: 10
Before modifySlice: [1 2 3]
After modifySlice: [100 2 3]

在上述代码中,modifyValue函数接收一个值类型的参数val,并尝试修改它的值。但在main函数中调用modifyValue后,原始变量num的值并未改变。

modifySlice函数接收一个引用类型的参数slice,并修改其第一个元素的值。在main函数中调用modifySlice后,原始变量numbers的对应元素值被修改。

这是因为值类型在赋值或者传递时会发生拷贝,而引用类型则是共享同一块数据内存地址。

4. 字符串类型

Go语言的字符串类型是以字节序列形式存储的不可变的Unicode字符集。字符串类型使用双引号(“”)或反引号(``)括起来。

在Go语言中,字符串类型是值类型,即在赋值或传递参数时会进行拷贝。但是,由于字符串是不可变的,实际上拷贝的是指向底层数据的指针和长度信息,而不会重新分配新的内存空间。

下面是一个简单的代码示例来说明字符串类型的特点:

package main

import "fmt"

func main() {
    str1 := "Hello, World!" // 使用双引号定义字符串
    str2 := `I'm a string`  // 使用反引号定义字符串

    // 访问字符串中的单个字符
    fmt.Println("str1[0]:", str1[0]) // 输出:72 (Unicode码对应的ASCII值)
    fmt.Println("str2[2]:", str2[2]) // 输出:m (Unicode码对应的字符)

    // 字符串拼接
    str3 := "Hello, " + "Go!"
    fmt.Println("str3:", str3) // 输出:Hello, Go!

    // 字符串长度
    fmt.Println("Length of str1:", len(str1)) // 输出:13
    fmt.Println("Length of str2:", len(str2)) // 输出:12

    // 字符串切片
    slice := str1[7:12]
    fmt.Println("Slice of str1:", slice) // 输出:World

    // 修改字符串(不可行,字符串是不可变的)
    // str1[0] = 'h' // 编译错误

    // 转换为字节数组
    bytes := []byte(str1)
    bytes[0] = 'h'
    str4 := string(bytes)
    fmt.Println("str4:", str4) // 输出:hello, World!
}

在上述代码中,我们通过len函数可以获取字符串的长度。同时,使用索引访问字符串中的单个字符,注意每个字符对应的是Unicode码。此外,可以使用切片操作获取子串,但是无法修改原始字符串的内容。

如果需要修改字符串,可以将其转换为字节数组,进行修改后再转换回字符串形式。这是因为字节数组是可变的。

需要注意的是,每个ASCII字符用一个字节表示,而非ASCII字符则可能占用多个字节。因此,在处理含有非ASCII字符的字符串时,需要特别注意字节和字符之间的转换以及长度计算。

5. 数组类型

Go语言中的数组是一种固定长度、具有相同类型的数据结构。数组的长度是在定义时就确定的,且不能更改。

定义数组的语法如下:

var 变量名 [长度]类型

其中,变量名是数组的标识符,长度是一个常量表达式,类型是数组中元素的类型。例如,定义一个包含5个整数的数组:

var numbers [5]int

可以直接给数组赋初值:

var numbers = [5]int{1, 2, 3, 4, 5}

也可以使用索引来给数组元素赋值:

numbers[0] = 1
numbers[1] = 2
// ...

访问数组元素的方式也是使用索引:

value := numbers[0]

需要注意的是,数组的索引从0开始,最大索引为长度减1。

数组的长度可以使用内置函数len()获取:

length := len(numbers)

在Go语言中,数组是值类型,当数组被传递给函数时,会进行值的复制。如果想要在函数中修改数组的值,可以使用指针或切片。

下面是一个示例代码,展示了数组的基本操作:

package main

import "fmt"

func main() {
	var numbers = [5]int{1, 2, 3, 4, 5}

	// 访问数组元素
	value := numbers[0]
	fmt.Println("第一个元素:", value)

	// 修改数组元素
	numbers[0] = 10
	fmt.Println("修改后的数组:", numbers)

	// 数组长度
	length := len(numbers)
	fmt.Println("数组长度:", length)
}

6. 切片(slice)(变长数组)

Go语言中的切片(slice)是一种动态数组,与固定长度的数组相比,切片具有更灵活的长度。切片是基于数组的一种封装,可以按需增加或缩减其长度。

定义切片的语法如下:

var 变量名 []类型

其中,变量名是切片的标识符,类型是切片中元素的类型。例如,定义一个整数切片:

var numbers []int

切片可以使用make()函数来创建,并指定切片的长度和容量:

numbers := make([]int, 5, 10)

上述代码创建了一个长度为5,容量为10的整数切片。长度表示切片当前存储的元素个数,容量表示底层数组的大小。

切片可以直接通过索引来访问和修改元素,使用与数组相同的语法:

numbers[0] = 1  // 修改第一个元素
value := numbers[0]  // 访问第一个元素

切片的长度可以使用内置函数len()获取:

length := len(numbers)

切片还提供了一些操作函数和方法,例如追加元素、拷贝切片、截取切片等。以下是一些常用的操作:

追加元素:

numbers = append(numbers, 6, 7)  // 追加一个或多个元素

拷贝切片:

newSlice := make([]int, len(numbers))
copy(newSlice, numbers)  // 将numbers拷贝到newSlice

截取切片:

sliced := numbers[1:3]  // 获取索引1到3的子切片(不包含索引3)

下面是一个示例代码,展示了切片的基本操作:

package main

import "fmt"

func main() {
	numbers := make([]int, 0, 5)
	fmt.Println("初始切片:", numbers)

	// 追加元素
	numbers = append(numbers, 1, 2, 3, 4, 5)
	fmt.Println("追加元素后的切片:", numbers)

	// 修改元素
	numbers[0] = 10
	fmt.Println("修改后的切片:", numbers)

	// 切片长度和容量
	length := len(numbers)
	capacity := cap(numbers)
	fmt.Println("切片长度:", length)
	fmt.Println("切片容量:", capacity)

	// 拷贝切片
	newSlice := make([]int, length)
	copy(newSlice, numbers)
	fmt.Println("拷贝的切片:", newSlice)

	// 截取切片
	sliced := numbers[1:3]
	fmt.Println("截取的切片:", sliced)
}

7. 字典类型

字典(Dictionary)是Go语言中一种无序的数据结构,它由键值对(key-value)组成。字典中的每个键(key)都是唯一的,而值(value)可以是任意类型的数据。

在Go语言中,字典的声明形式如下:

var 字典名 map[键类型]值类型

其中,键类型和值类型可以是任意合法的数据类型,例如string、int、bool等。

字典的初始化可以通过make函数或者字面量的方式来完成。使用make函数初始化字典时,需要指定字典的键类型和值类型,并且分配内存空间,例如:

字典名 := make(map[键类型]值类型)

使用字面量初始化字典时,可以直接在大括号中定义键值对,例如:

字典名 := map[键类型]值类型{1:1,2:2,
    ...
}

字典的基本操作包括添加元素、删除元素、检查元素是否存在以及获取元素的值。

添加元素可以通过键值对的方式实现,例如:

字典名[] =

删除元素可以使用delete函数,例如:

delete(字典名,)

检查元素是否存在可以使用以下方式:

, 存在 := 字典名[]

获取元素的值可以直接通过键访问,例如:

:= 字典名[]

下面是一个使用字典的代码实例:

package main

import "fmt"

func main() {
    // 初始化字典
    scores := make(map[string]int)

    // 添加元素
    scores["Alice"] = 100
    scores["Bob"] = 90
    scores["Charlie"] = 95

    // 获取元素的值
    fmt.Println(scores["Alice"])

    // 检查元素是否存在
    score, exists := scores["Bob"]
    if exists {
        fmt.Println("Bob's score:", score)
    }

    // 删除元素
    delete(scores, "Charlie")

    // 遍历字典
    for name, score := range scores {
        fmt.Println(name, ":", score)
    }
}

8. 通道类型

通道(channel)是Go语言中一种用于在多个goroutine之间进行通信和数据同步的机制。它可以想象成一个队列,goroutine可以通过发送和接收操作来向通道发送和接收数据。

通道类型使用chan关键字进行声明,其语法为:

var 变量名 chan 数据类型

其中,数据类型表示通道中传输的数据类型。

通道的创建使用make()函数来完成,语法如下:

变量名 := make(chan 数据类型)

通道有两种类型:带缓冲的通道和非缓冲的通道。

  • 带缓冲的通道可以在没有接收方的情况下,缓存一定数量的元素。当通道的缓存已满时,发送操作将被阻塞,直到有接收方接收元素并释放空间。

    变量名 := make(chan 数据类型, 缓冲大小)
    
  • 非缓冲的通道又被称为同步通道,它没有缓存空间,只有发送操作和接收操作同时准备好时,数据才能够安全地传输。

    变量名 := make(chan 数据类型)
    

以下是一个示例代码,说明如何使用通道进行数据的发送和接收:

package main

import "fmt"

func main() {
    // 创建一个缓冲大小为3的整型通道
    ch := make(chan int, 3)

    // 向通道发送数据
    ch <- 1
    ch <- 2
    ch <- 3

    // 从通道接收数据
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

输出结果:
1
2
3

在上面的代码中,我们首先创建了一个缓冲大小为3的整型通道ch。然后通过ch <- 数据的方式向通道发送数据。最后使用<-ch的方式从通道接收数据,并将其打印输出。

需要注意的是,在通道中发送和接收数据都是阻塞的操作。当通道为空时,接收操作会被阻塞;当通道满时,发送操作会被阻塞。这种机制可以让我们有效地控制goroutine之间的同步。

9. 错误类型

Go语言的错误类型主要有两种:预定义错误类型和自定义错误类型。

  1. 预定义错误类型: 预定义错误类型在Go语言的errors包中定义,并且是实现了内置的error接口的具体类型。常用的预定义错误类型有以下几种:
  • error:最基本的错误类型,表示一个普通错误。
  • New:返回一个指定错误信息的基础error类型值。
  • Errorf:根据格式化字符串返回一个带有错误信息的新error类型值。
  • Unwrap:返回该错误的下层错误。
  • Is:报告该错误是否与目标错误类型相同。
  • As:将该错误的下层错误赋给目标错误类型的变量。

(1) 以下是一个使用预定义错误类型的代码示例:

package main

import (
	"errors"
	"fmt"
)

func divide(x, y float64) (float64, error) {
	if y == 0 {
		return 0, errors.New("除数不能为零")
	}
	return x / y, nil
}

func main() {
	result, err := divide(10, 0)
	if err != nil {
		fmt.Println("发生错误:", err)
	} else {
		fmt.Println("计算结果:", result)
	}
}

(2) 自定义错误类型: 在Go语言中,可以通过实现error接口来创建自定义的错误类型。自定义的错误类型需要实现Error()方法,该方法用于返回错误信息的字符串表示。可以根据需求添加自定义的字段和方法。

以下是一个使用自定义错误类型的代码示例:

package main

import (
	"errors"
	"fmt"
)

type MyError struct {
	Code    int
	Message string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("错误码:%d,错误信息:%s", e.Code, e.Message)
}

func divide(x, y float64) (float64, error) {
	if y == 0 {
		return 0, &MyError{Code: 1001, Message: "除数不能为零"}
	}
	return x / y, nil
}

func main() {
	result, err := divide(10, 0)
	if err != nil {
		fmt.Println("发生错误:", err)
	} else {
		fmt.Println("计算结果:", result)
	}
}

10. 其他

1. 类型别名

Go语言中的类型别名是指给一个已存在的类型定义一个新的名称,以便于在代码中更加清晰地表示类型的含义。类型别名不会创建新的类型,它只是为现有的类型提供了一个别名。

在Go语言中,可以使用type关键字来定义类型别名。例如,我们可以将int类型的别名定义为myInt

package main

import "fmt"

type myInt int

func main() {
    var num myInt = 10
    fmt.Println(num)
}

上面的代码中,我们定义了一个myInt类型的别名,它实际上就是int类型的别名。我们可以像使用int类型一样使用myInt类型,并且可以将myInt类型的值赋给int类型的变量,反之亦然。

类型别名在代码中可以增加可读性和语义,特别是当需要对相同类型的不同含义进行区分时特别有用。例如,在处理不同单位的长度时,可以使用类型别名来表示具体的含义。

除了基本类型之外,类型别名也可以应用于结构体、接口等复杂类型。下面是一个使用类型别名的结构体实例:

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

type Employee Person

func main() {
    emp := Employee{
        Name: "张三",
        Age:  30,
    }
    fmt.Println(emp)
}

上面的代码中,我们定义了一个Person结构体类型,然后使用Employee类型别名来表示Person类型。这样,我们可以用Employee类型来创建结构体实例,其字段和方法与Person类型完全相同。

通过类型别名,我们可以更好地区分不同的数据类型,提高代码的可读性和可维护性。需要注意的是,类型别名只在代码中有意义,编译后的程序并不会保留别名信息。

2. 类型转换

在Go语言中,类型转换是将一个类型的值转换为另一个类型的过程。类型转换通常用于不同类型之间的赋值或表达式的计算。

Go语言中的类型转换使用T(v)的形式,其中T表示要转换的目标类型,v表示要转换的值。下面是几种基本类型的转换示例:

package main

import "fmt"

func main() {
    var num int = 10
    var flt float64 = float64(num) // int类型转换为float64类型
    fmt.Println(flt)
    
    var flt2 float64 = 3.14
    var num2 int = int(flt2) // float64类型转换为int类型
    fmt.Println(num2)
    
    var char byte = 'A'
    var num3 int = int(char) // byte类型转换为int类型
    fmt.Println(num3)
}

上面的代码中,我们分别将整数类型、浮点数类型和字节类型进行了类型转换。需要注意的是,类型转换是有风险的,可能会导致数据丢失或溢出。例如,将一个较大的浮点数转换为整数类型时会丢失小数部分。

对于复杂类型,如结构体或自定义类型,类型转换需要满足两个类型之间的兼容性。如果两个类型的底层结构相同,可以使用强制类型转换来实现。以下是一个结构体类型转换的示例:

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

type Employee struct {
    Name     string
    Age      int
    Position string
}

func main() {
    var p Person = Person{
        Name: "张三",
        Age:  30,
    }
    
    var emp Employee = Employee(p) // Person类型转换为Employee类型
    fmt.Println(emp)
}

在上面的代码中,我们将一个Person类型的值转换为了Employee类型。由于两个结构体的底层结构相同,因此可以进行类型转换。

需要注意的是,类型转换是有限制的,在不同的类型之间不能进行随意的转换。只有在类型之间存在兼容性或底层结构相同的情况下,才能进行类型转换。否则,在进行类型转换时会出现编译错误。

在使用类型转换时,需要确保转换的安全性和准确性,并避免潜在的数据丢失或溢出问题。理解类型之间的兼容性和底层结构对类型转换非常重要。

5. 运算符与表达式

Go语言的运算符包括以下几种:

  1. 算术运算符:用于执行基本的算术操作,如加法、减法、乘法、除法和取模。
    • 加法:+
    • 减法:-
    • 乘法:*
    • 除法:/
    • 取模(取余数):%
  2. 关系运算符:用于比较两个值之间的关系,返回布尔值(true或false)。
    • 相等:==
    • 不相等:!=
    • 大于:>
    • 小于:<
    • 大于等于:>=
    • 小于等于:<=
  3. 逻辑运算符:用于对布尔类型的值进行逻辑运算,返回布尔值。
    • 与:&&
    • 或:||
    • 非:!
  4. 位运算符:用于对数字进行二进制位操作。
    • 按位与:&
    • 按位或:|
    • 按位异或:^
    • 按位取反:~
    • 左移:<<
    • 右移:>>
  5. 赋值运算符:用于给变量赋值。
    • 简单赋值:=
    • 加法赋值:+=
    • 减法赋值:-=
    • 乘法赋值:*=
    • 除法赋值:/=
    • 模赋值:%=
  6. 其他运算符:
    • 自增:++
    • 自减:–

以下是一些代码示例:

package main

import "fmt"

func main() {
	a := 10
	b := 20

	// 算术运算符
	fmt.Println(a + b) // 输出:30
	fmt.Println(a - b) // 输出:-10
	fmt.Println(a * b) // 输出:200
	fmt.Println(a / b) // 输出:0
	fmt.Println(a % b) // 输出:10

	// 关系运算符
	fmt.Println(a == b) // 输出:false
	fmt.Println(a != b) // 输出:true
	fmt.Println(a > b)  // 输出:false
	fmt.Println(a < b)  // 输出:true
	fmt.Println(a >= b) // 输出:false
	fmt.Println(a <= b) // 输出:true

	// 逻辑运算符
	fmt.Println(true && false) // 输出:false
	fmt.Println(true || false) // 输出:true
	fmt.Println(!true)         // 输出:false

	// 位运算符
	c := 5 // 二进制表示为 0101
	d := 3 // 二进制表示为 0011
	fmt.Println(c & d)  // 按位与,输出:1(二进制 0001)
	fmt.Println(c | d)  // 按位或,输出:7(二进制 0111)
	fmt.Println(c ^ d)  // 按位异或,输出:6(二进制 0110)
	fmt.Println(^d)    // 按位取反,输出:-4(二进制 1100)
	fmt.Println(c << 1) // 左移一位,输出:10(二进制 1010)
	fmt.Println(c >> 1) // 右移一位,输出:2(二进制 0010)

	// 赋值运算符
	e := 10
	e += 5 // 等价于 e = e + 5
	fmt.Println(e) // 输出:15

	// 其他运算符
	f := 1
	f++            // 等价于 f = f + 1
	fmt.Println(f) // 输出:2
}

6. 输入输出

Go语言的输入输出涉及到多种方式和知识点,包括标准输入输出、文件操作、网络传输等。下面列举了一些常见的Go语言输入输出知识点和相应的代码实例:

1. 标准输入输出

  • 使用fmt.Printffmt.Println函数打印输出到标准输出。

示例代码:

package main

import "fmt"

func main() {
    name := "John"
    age := 25
    fmt.Printf("My name is %s and my age is %d\n", name, age)
    fmt.Println("This is a new line")
}

  • 使用fmt.Scan函数从标准输入读取数据,并存储到指定的变量中。

示例代码:

package main

import "fmt"

func main() {
    var name string
    fmt.Print("Enter your name: ")
    fmt.Scan(&name)
    fmt.Printf("Hello, %s!\n", name)
}

2. 文件操作

  • 使用os.Open函数打开文件,并使用bufio.NewReader函数创建一个带有缓冲区的读取器,然后通过读取器逐行读取文件内容。

示例代码:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("data.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println(line)
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading file:", err)
    }
}

3. 网络传输

  • 使用net包提供的相关函数进行网络通信,如net.Dialnet.Listen等。具体实现根据不同的场景而定。

以上仅是一些常见的Go语言输入输出知识点和相应的代码示例,还有许多其他的输入输出方式和细节部分可以根据具体需求进行更深入的学习和探索。

7. 条件语句

Go语言的条件语句主要包括if语句和switch语句。下面是它们的知识点和代码实例:

1. if语句

  • if语句根据条件表达式的结果来决定是否执行某段代码。
  • if语句的基本格式为:if 条件表达式 { 代码块 }
  • 如果条件表达式的结果为true,则执行代码块;否则跳过代码块。
  • 可以在if语句后面添加else语句或else if语句,用于处理多个条件情况。

代码实例:

package main

import "fmt"

func main() {
    num := 10
    if num < 0 {
        fmt.Println("数字小于0")
    } else if num > 0 {
        fmt.Println("数字大于0")
    } else {
        fmt.Println("数字等于0")
    }
}

2. switch语句

  • switch语句根据表达式的值进行条件判断,并执行相应的代码块。
  • switch语句的基本格式为:switch 表达式 { case 值1: 代码块1 case 值2: 代码块2 default: 默认代码块 }
  • 表达式的值与每个case后面的值进行比较,如果匹配,则执行相应的代码块;如果没有匹配的值,则执行默认代码块。

代码实例:

package main

import "fmt"

func main() {
    num := 2
    switch num {
    case 1:
        fmt.Println("数字等于1")
    case 2, 3:
        fmt.Println("数字等于2或3")
    default:
        fmt.Println("数字不符合条件")
    }
}

8.循环

Go语言的循环语句主要包括for循环和range循环。下面是它们的知识点和代码实例:

1. for循环:

  • for循环用于重复执行一段代码,可以控制循环的次数。
  • for循环的基本格式为:for 初始化语句; 条件表达式; 后续语句 { 代码块 }
  • 初始化语句用于初始化循环变量;条件表达式用于判断是否继续执行循环;后续语句用于更新循环变量。
  • 当条件表达式为true时,重复执行代码块;当条件表达式为false时,跳出循环。

代码实例:

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
}

2. range循环

  • range循环用于遍历数组、切片、映射或通道等数据结构的元素。
  • range循环的基本格式为:for index, value := range 数据结构 { 代码块 }
  • index表示当前元素的索引(或键),value表示当前元素的值。
  • 可以使用"_"来忽略index或value,如果只关心其中一个。

代码实例:

package main

import "fmt"

func main() {
    arr := [3]string{"Go", "语言", "循环"}
    for index, value := range arr {
        fmt.Println(index, value)
    }
}

3.跳转语句

Go语言提供了以下几种循环跳转语句:

  1. break:用于终止当前循环,并跳出循环体。可以在for循环、switch语句和select语句中使用。

    package main
    
    import "fmt"
    
    func main() {
        for i := 0; i < 10; i++ {
            if i == 5 {
                break
            }
            fmt.Println(i)
        }
    }
    使用break语句跳出循环。
    
  2. continue:用于跳过当前迭代,并进入下一次迭代。可以在for循环中使用。

    package main
    
    import "fmt"
    
    func main() {
        for i := 0; i < 10; i++ {
            if i%2 == 0 {
                continue
            }
            fmt.Println(i)
        }
    }
    在上述代码中,当i为偶数时,使用`continue`语句跳过当前迭代,直接进入下一次迭代
    
  3. goto:用于无条件地跳转到指定标签的位置。可以在函数内部任意位置使用。

    package main
    
    import "fmt"
    
    func main() {
        for i := 0; i < 10; i++ {
            if i == 5 {
                goto end
            }
            fmt.Println(i)
        }
    
    end:
        fmt.Println("Loop ended")
    }
    
    

    在上述代码中,当i等于5时,使用goto语句跳转到end标签处。

    需要注意的是,使用goto语句会使代码流程变得难以理解,容易产生混乱,所以应谨慎使用。

    希望以上内容对你有所帮助,如果还有其他问题,请随时提问。

9.函数

1.包声明和导包

在Go语言中,包声明部分位于每个源文件的开头,并使用package关键字进行声明。下面是包声明部分的知识点和代码实例:

  • 包名:包名用于标识当前文件所属的包,并与其所在的目录相对应。包名应使用小写字母,并且推荐使用简短、有意义的名称。
package main

在上述代码中,main是包名,表示该文件属于main包。

  • 导入其他包:可以使用import关键字导入其他包,以便在当前文件中使用其他包提供的功能。可以导入标准库包、第三方包或自定义包。
package main

import "fmt"

在上述代码中,通过import导入了fmt包,以便在代码中使用其提供的函数。

  • 引用可见性:Go语言中,对外部可见的标识符需要以大写字母开头,否则只能在当前包内部使用。
package main

import "example.com/mypackage"

func main() {
    mypackage.MyFunction() // 可以访问MyFunction函数
    // mypackage.myPrivateFunction() // 不能访问myPrivateFunction函数
}

在上述代码中,导入了名为mypackage的自定义包,并可以使用其中的公开函数MyFunction,但不能访问其中的私有函数myPrivateFunction

  • 命名冲突:当导入的多个包中存在相同的标识符时,可以使用import的别名来解决冲突。
package main

import (
    "fmt"
    myfmt "example.com/mypackage/fmt"
)

func main() {
    fmt.Println("Hello")        // 输出:Hello(使用标准库的fmt包)
    myfmt.Println("MyPackage")  // 输出:MyPackage(使用自定义包的fmt包)
}

2.函数声明

Go语言中的函数声明包括函数名称、参数列表、返回值列表和函数体。

1.函数名称

函数名称是用来唯一标识函数的标识符。例如:

func add(a, b int) int {
    return a + b
}
2.参数列表

参数列表包括函数的输入参数,每个参数都由参数名称和参数类型组成,多个参数之间使用逗号分隔。函数在调用时需要传入实际参数。例如:

func add(a, b int) int { // a和b都是int类型的参数
    return a + b
}
3.返回值列表

返回值列表定义函数的返回值类型,可以有多个返回值,每个返回值都由返回值类型声明。函数执行完毕后会返回相应的返回值。例如:

func divmod(a, b int) (int, int) { // 返回两个int类型的返回值
    return a / b, a % b
}
4.函数体

函数体是函数的具体实现部分,包括函数内部的代码逻辑。函数体由一对花括号{}包围。例如:

func add(a, b int) int {
    return a + b // 函数体中的代码逻辑
}

函数声明代码实例:

package main

import (
    "fmt"
)

// 声明一个函数,计算两个数的和
func add(a, b int) int {
    return a + b
}

func main() {
    // 调用函数并打印结果
    result := add(3, 4)
    fmt.Println(result) // 输出:7
}

3.参数传递

在Go语言中,函数参数可以通过值传递或引用传递的方式进行传递。

1.值传递

将参数的值复制一份,传递给函数进行操作,不会修改原始参数的值。示例代码如下:

package main

import "fmt"

// 值传递
func changeValue(a int) {
    a = 10 // 修改函数内部的副本
}

func main() {
    var num = 5
    changeValue(num)
    fmt.Println(num) // 输出:5,原始参数的值不变
}

2.引用传递

将参数的内存地址传递给函数,函数可以直接操作原始参数的值。示例代码如下:

package main

import "fmt"

// 引用传递
func changeValue(a *int) {
    *a = 10 // 修改原始参数的值
}

func main() {
    var num = 5
    changeValue(&num) // 传入num的地址
    fmt.Println(num) // 输出:10,原始参数的值被修改
}

3.切片、映射和接口类型的传递

切片、映射和接口类型是引用类型,在函数间传递时,只复制了一个指向底层数据结构的指针,多个函数可以共享相同的底层数据。示例代码如下:

package main

import "fmt"

// 引用传递
func modifySlice(s []int) {
    s[0] = 10 // 修改底层数组
}

func main() {
    nums := []int{1, 2, 3}
    modifySlice(nums)
    fmt.Println(nums) // 输出:[10 2 3],原始参数的底层数组被修改
}

总结:

  • 值传递不会修改原始参数的值,只在函数内部操作副本。
  • 引用传递可以直接修改原始参数的值,特别是切片、映射和接口类型。
  • 在函数间传递大型数据结构时,使用指针或引用类型可以减少内存拷贝和提高性能。

需要注意的是,在Go语言中,函数参数的传递方式默认是值传递,如果需要引用传递,需要使用指针作为参数。另外,切片、映射和接口类型默认是引用类型,无需额外操作即可实现引用传递。

4.闭包

Go语言的函数闭包是一种特殊的函数类型,它可以访问其词法环境中定义的变量。闭包在Go语言中被广泛用于实现函数式编程和并发编程。

闭包的基本概念:

  1. 函数可以作为返回值:闭包函数可以作为其它函数的返回值,形成嵌套的函数结构。
  2. 函数可以作为参数传递:闭包函数可以作为参数传递给其他函数。

闭包的特点:

  1. 闭包函数可以访问外部函数中定义的局部变量。
  2. 闭包函数在创建时会保留对外部变量的引用,使得外部变量在函数执行期间始终有效。
  3. 外部变量的生命周期可能会被延长,直到闭包函数不再被引用时才会被释放。

下面是一个简单的示例代码,展示了闭包的使用方法:

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	a := adder()
	fmt.Println(a(1))  // 输出结果:1
	fmt.Println(a(2))  // 输出结果:3
	fmt.Println(a(3))  // 输出结果:6
	
	b := adder()
	fmt.Println(b(10)) // 输出结果:10
	fmt.Println(b(20)) // 输出结果:30
}

在上述代码中,函数adder是一个闭包函数。它定义了一个局部变量sum,并返回了一个匿名函数。这个匿名函数可以访问和修改adder函数的局部变量sum。在main函数中我们创建了两个闭包实例a和b,它们拥有独立的sum变量。当调用闭包实例时,sum会持续累加传入的参数。

上述示例中,第一次调用闭包实例a时,sum初始化为0,之后每次调用闭包函数时,sum都会被累加。而第一次调用闭包实例b时,sum又重新初始化为0,因此与实例a完全独立。

闭包函数的应用场景包括但不限于:

  1. 延迟执行:通过闭包函数可以实现延迟执行的需求,将需要延迟执行的操作封装在闭包函数中,等到需要时再调用。
  2. 数据封装:通过闭包函数可以实现将数据和操作封装在一起,形成一个真正的对象。
  3. 并发编程:在并发编程中,闭包可以用于共享变量的访问控制,避免竞争条件的发生。

5.defer语句

Go语言中的一个关键字,用于延迟函数的执行。它常用于在函数返回之前释放资源、关闭文件或者解锁资源等操作。下面是关于defer语句的一些知识点:

  1. defer语句会在包含它的函数执行完毕之前被执行,不论函数是正常返回还是发生了panic异常。
  2. 在函数中可以使用多个defer语句,它们的执行顺序是按照后进先出(LIFO)的顺序执行的。
  3. defer语句中的函数参数是在defer语句声明时就会被求值的,而不是在实际执行时。
  4. defer语句通常用于处理资源的释放,可以保证在任何情况下都能正确地执行这些释放操作。

下面是一些defer语句的代码实例:

1.释放资源
func openFile() {
    file := open("file.txt")
    defer file.Close() // 在函数返回前关闭文件
    // 使用文件进行读写操作
}
2.计算函数运行时间
func measureTime() {
    defer timeTrack(time.Now(), "FunctionName") // 在函数返回前计算运行时间
    // 函数逻辑代码
}

func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    log.Printf("%s took %s", name, elapsed)
}
3.锁的释放
func lockUnlock() {
    mu.Lock()
    defer mu.Unlock() // 在函数返回前释放锁定
    // 临界区逻辑代码
}
4.错误处理
func openFile() error {
    file, err := open("file.txt")
    if err != nil {
        return err
    }
    defer func() {
        if err := file.Close(); err != nil { // 在函数返回前关闭文件,并处理关闭过程中的错误
            log.Println(err)
        }
    }()
    // 使用文件进行读写操作
    return nil
}

10.结构体

Go语言中的结构体是一种用户自定义的复合数据类型,用于存储不同类型的数据字段。结构体由一系列字段组成,每个字段都有自己的名称和类型。

下面是关于Go语言结构体的一些常见知识点以及相应的代码示例:

1.定义结构体类型

结构体类型的定义使用关键字type,后面跟着结构体的名称以及字段列表。字段列表由字段名和字段类型组成,多个字段之间使用逗号分隔。

type Person struct {
    Name string
    Age  int
}

2.创建结构体实例

可以使用var关键字声明一个结构体变量,并使用结构体名称{}的形式进行初始化。

var p Person
p.Name = "Alice"
p.Age = 20

也可以直接在声明时进行初始化:

p := Person{Name: "Bob", Age: 25}

3.访问结构体字段

可以使用.操作符来访问结构体实例中的字段。

fmt.Println(p.Name)
fmt.Println(p.Age)

4.结构体指针

可以通过&操作符获取结构体的指针,通过指针修改结构体中的字段值。

p := &Person{Name: "Cathy", Age: 30}
p.Name = "David"

5.匿名结构体

可以在声明结构体变量时直接定义一个匿名的结构体。

p := struct {
    Name string
    Age  int
}{Name: "Eva", Age: 35}

6.结构体嵌套

可以在结构体中嵌套其他结构体,形成更复杂的数据结构。

type Address struct {
    City  string
    State string
}

type Person struct {
    Name    string
    Age     int
    Address Address
}

p := Person{
    Name: "Frank",
    Age:  40,
    Address: Address{
        City:  "New York",
        State: "NY",
    },
}

fmt.Println(p.Address.City)

7.方法

可以为结构体定义方法,可以在方法内部访问结构体的字段和调用其他函数。

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())

以上是关于Go语言结构体的一些知识点和代码示例。需要注意的是,在实际开发中还有更多高级的用法和技巧,可以根据具体需求进行深入学习和探索。

11.接口

Go语言的接口(interface)是一种定义了一组方法的类型。接口类型是一种抽象的类型,它不会提供具体的实现,只定义了方法的签名。任何实现了接口中所有方法的类型都被称为实现了该接口。

1.接口的基础语法

下面是关于Go语言接口的知识点以及一个简单的代码实例:

  1. 接口的定义:
    • 使用type关键字定义接口类型,例如:type Writer interface {}
    • 接口中定义了一组方法的签名,没有具体的实现内容
  2. 接口的实现:
    • 使用type关键字定义类型,并在类型内实现接口中定义的所有方法
    • 使用类型的指针或值都可以实现接口
    • 类型实现接口时不需要显式声明,只要实现了接口中的所有方法即可
  3. 接口的使用:
    • 变量可以声明为接口类型,接口变量可以持有任何实现了该接口的类型的值
    • 调用接口变量的方法时,根据实际的类型来执行相应的方法

下面是一个简单的代码实例,演示了接口的定义、实现和使用:

package main

import "fmt"

// 定义一个接口
type Shape interface {
    Area() float64
}

// 定义一个结构体类型
type Circle struct {
    radius float64
}

// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}

// 定义另一个结构体类型
type Rectangle struct {
    width, height float64
}

// 实现Shape接口的Area方法
func (r Rectangle) Area() float64 {
    return r.width * r.height
}

func main() {
    // 声明一个接口类型的变量
    var s Shape

    // 创建一个Circle类型的变量,并赋值给接口变量s
    c := Circle{radius: 5}
    s = c

    // 调用接口变量的方法
    fmt.Println("圆的面积:", s.Area())

    // 创建一个Rectangle类型的变量,并赋值给接口变量s
    r := Rectangle{width: 4, height: 3}
    s = r

    // 调用接口变量的方法
    fmt.Println("矩形的面积:", s.Area())
}

在上面的代码中,Shape接口定义了一个方法Area()。Circle和Rectangle分别实现了Shape接口中的Area()方法,它们都是Shape接口的实现类型。在main函数中,通过接口变量s来调用不同类型的方法,实现了对应类型的功能。

2.反射

在Go语言中,反射(reflection)是一种在运行时动态检查对象类型和值的机制。使用反射,我们可以在不知道具体类型的情况下,操作和查询对象的属性、方法和类型信息。下面是Go语言反射的知识点及代码实例。

1.反射的基本概念

在Go语言中,反射通过reflect包来实现。reflect包提供了TypeValue两个结构体,分别用于表示类型和值的信息。

2.获取类型信息

通过reflect.TypeOf()函数可以获取一个值的类型信息。该函数返回一个reflect.Type类型的值,代表对象的类型。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	str := "Hello, reflection!"
	t := reflect.TypeOf(str)
	fmt.Println(t) // 输出: string
}
3.获取值信息

通过reflect.ValueOf()函数可以获取一个值的值信息。该函数返回一个reflect.Value类型的值,代表对象的值。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	num := 42
	v := reflect.ValueOf(num)
	fmt.Println(v) // 输出: 42
}
4.利用反射修改值

通过reflect.Value提供的Set()方法,可以通过反射修改一个变量的值。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	num := 42
	v := reflect.ValueOf(&num) // 使用指针,才能修改值
	p := v.Elem()
	p.SetInt(100)
	fmt.Println(num) // 输出: 100
}
5.利用反射调用方法

通过reflect.Value提供的MethodByName()Call()方法,可以通过反射调用一个对象的方法。

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct {
	Name string
}

func (ms MyStruct) SayHello() {
	fmt.Println("Hello from", ms.Name)
}

func main() {
	ms := MyStruct{Name: "Alice"}
	v := reflect.ValueOf(ms)
	method := v.MethodByName("SayHello")
	method.Call(nil) // 输出: Hello from Alice
}

通过反射,我们可以动态地获取对象的类型和值信息,并在运行时进行操作。但是需要注意的是,反射的使用会带来一定的性能开销,因此在性能要求较高的场景下需要慎重使用。

二、Go的一些常用包

1. fmt

fmt包是Go语言中用于格式化输入输出的标准库,提供了丰富的函数和方法来对数据进行格式化、打印和读取。下面是fmt包的一些常用知识点以及相应的代码实例:

1.格式化输出

  • Print函数:用于将给定参数格式化为字符串,并输出到标准输出。
fmt.Print("Hello, World!")
  • Printf函数:支持格式化输出,根据格式字符串将可变数量的参数格式化为字符串,并输出到标准输出。
name := "Alice"
age := 28
fmt.Printf("Name: %s, Age: %d", name, age)
  • Println函数:类似Print函数,但在输出后会自动添加换行符。
fmt.Println("Hello,", "World!")

2.格式化输入

  • Scan函数:从标准输入读取数据,并将其赋值给指定变量。
var name string
fmt.Scan(&name)
  • Sscanf函数:根据指定的格式字符串,从输入字符串中解析数据并赋值给指定变量。
var name string
input := "Alice Smith"
fmt.Sscanf(input, "%s %s", &firstName, &lastName)

3.其他格式化操作

  • 格式化占位符:
    • %d:有符号十进制整数
    • %s:字符串
    • %f:浮点数
    • %t:布尔值
    • %v:任意值的默认格式化
age := 28
name := "Alice"
fmt.Printf("Name: %s, Age: %d", name, age)
  • 宽度和精度控制:
value := 3.1415926
fmt.Printf("Value: %.2f", value) // 输出:Value: 3.14
  • 输出到字符串:
name := "Alice"
age := 28
result := fmt.Sprintf("Name: %s, Age: %d", name, age)

2. math

math包是Go语言中用于数学计算的标准库,提供了许多数学函数和常量。下面是math包的一些常用知识点以及相应的代码实例:

1.常量

  • math.Pi:圆周率π的近似值。
fmt.Println(math.Pi) // 输出:3.141592653589793

2数学函数

  • Abs函数:返回一个数的绝对值。
num := -10
result := math.Abs(num)
fmt.Println(result) // 输出:10
  • Ceil函数:返回大于或等于给定浮点数的最小整数值。
num := 3.14
result := math.Ceil(num)
fmt.Println(result) // 输出:4
  • Floor函数:返回小于或等于给定浮点数的最大整数值。
num := 3.14
result := math.Floor(num)
fmt.Println(result) // 输出:3
  • Round函数:返回给定浮点数的四舍五入值。
num := 3.54
result := math.Round(num)
fmt.Println(result) // 输出:4
  • Max函数:返回两个数中较大的那个数。
num1 := 10
num2 := 20
result := math.Max(num1, num2)
fmt.Println(result) // 输出:20
  • Min函数:返回两个数中较小的那个数。
num1 := 10
num2 := 20
result := math.Min(num1, num2)
fmt.Println(result) // 输出:10
  • Pow函数:返回给定底数的指定次幂。
base := 2
exponent := 3
result := math.Pow(base, exponent)
fmt.Println(result) // 输出:8
  • Sqrt函数:返回给定数的平方根。
num := 16
result := math.Sqrt(num)
fmt.Println(result) // 输出:4
  • Trunc函数:返回去掉小数部分的整数。
num := 3.14
result := math.Trunc(num)
fmt.Println(result) // 输出:3

3. strings

strings包是Go语言中用于字符串处理的标准库,提供了许多字符串相关的函数。下面是strings包的一些常用知识点以及相应的代码实例:

1.字符串操作

  • Compare函数:比较两个字符串,并根据比较结果返回一个整数值。
str1 := "hello"
str2 := "world"
result := strings.Compare(str1, str2)
fmt.Println(result) // 输出:-1 (str1 < str2)
  • Concat函数:将多个字符串连接起来。
str1 := "hello"
str2 := "world"
result := strings.Concat(str1, str2)
fmt.Println(result) // 输出:helloworld
  • HasPrefix函数:检查字符串是否以指定的前缀开头。
str := "hello world"
prefix := "hello"
result := strings.HasPrefix(str, prefix)
fmt.Println(result) // 输出:true
  • HasSuffix函数:检查字符串是否以指定的后缀结尾。
str := "hello world"
suffix := "world"
result := strings.HasSuffix(str, suffix)
fmt.Println(result) // 输出:true
  • Index函数:返回字符串中第一次出现指定子串的位置。
str := "hello world"
substr := "world"
result := strings.Index(str, substr)
fmt.Println(result) // 输出:6
  • LastIndex函数:返回字符串中最后一次出现指定子串的位置。
str := "hello world"
substr := "o"
result := strings.LastIndex(str, substr)
fmt.Println(result) // 输出:7

2.字符串切片和拼接

  • Split函数:以指定的分隔符将字符串拆分成切片。
str := "hello,world"
sep := ","
result := strings.Split(str, sep)
fmt.Println(result) // 输出:[hello world]
  • Join函数:将切片中的字符串按照指定的分隔符拼接成一个字符串。
strSlice := []string{"hello", "world"}
sep := ","
result := strings.Join(strSlice, sep)
fmt.Println(result) // 输出:hello,world

3.字符串替换和修剪

  • Replace函数:将字符串中指定的字符替换为另一个字符。
str := "hello world"
old := "world"
new := "golang"
result := strings.Replace(str, old, new, -1)
fmt.Println(result) // 输出:hello golang
  • Trim函数:去掉字符串开头和结尾指定字符集合中的字符。
str := "  hello world  "
cutset := " "
result := strings.Trim(str, cutset)
fmt.Println(result) // 输出:hello world

4. bytes

bytes包是Go语言中的一个标准库,提供了对字节处理的一系列函数和方法。以下是bytes包的一些常见知识点以及代码实例:

1.字节切片操作

  • func Compare(a, b []byte) int:比较两个字节切片 a 和 b,返回一个整数表示它们的大小关系。 示例:

    a := []byte("hello")
    b := []byte("world")
    result := bytes.Compare(a, b)
    fmt.Println(result) // 输出结果为-1,表示a小于b
    
  • func Contains(b, subslice []byte) bool:判断字节切片 b 中是否包含子切片 subslice。 示例:

    b := []byte("hello world")
    subslice := []byte("world")
    result := bytes.Contains(b, subslice)
    fmt.Println(result) // 输出结果为true
    
  • func Count(s, sep []byte) int:统计字节切片 s 中非重叠出现的 sep 的次数。 示例:

    s := []byte("hello hello hello")
    sep := []byte("hello")
    result := bytes.Count(s, sep)
    fmt.Println(result) // 输出结果为3
    
  • func Index(s, sep []byte) int:返回字节切片 s 中第一次出现 sep 的索引位置,如果找不到则返回 -1。 示例:

    s := []byte("hello world")
    sep := []byte("world")
    result := bytes.Index(s, sep)
    fmt.Println(result) // 输出结果为6
    

2. 字节切片拼接与分割

  • func Join(s [][]byte, sep []byte) []byte:使用 sep 作为分隔符,将二维字节切片 s 进行拼接。 示例:

    s := [][]byte{[]byte("hello"), []byte("world")}
    sep := []byte(", ")
    result := bytes.Join(s, sep)
    fmt.Println(string(result)) // 输出结果为"hello, world"
    
  • func Split(s, sep []byte) [][]byte:将字节切片 s 按照 sep 进行分割,并返回二维字节切片。 示例:

    s := []byte("hello,world")
    sep := []byte(",")
    result := bytes.Split(s, sep)
    for _, v := range result {
        fmt.Println(string(v))
    }
    // 输出结果为:
    // hello
    // world
    

3. 字节切片修改与替换

  • func Replace(s, old, new []byte, n int) []byte:将字节切片 s 中的前 n 个 old 替换为 new。 示例:

    s := []byte("hello hello hello")
    old := []byte("hello")
    new := []byte("hi")
    result := bytes.Replace(s, old, new, 2)
    fmt.Println(string(result)) // 输出结果为"hi hi hello"
    
  • func ToUpper(s []byte) []byte:将字节切片 s 中的所有字符转换为大写。 示例:

    s := []byte("hello world")
    result := bytes.ToUpper(s)
    fmt.Println(string(result)) // 输出结果为"HELLO WORLD"
    

以上仅是bytes包中一部分常用函数的示例,还有更多的函数和方法可供使用。你可以通过查阅Go语言官方文档来获取更详细的信息:https://golang.org/pkg/bytes/

三、go高级篇

1. 内存分配机制

1. 内存分配机制基本概念

Go语言的内存分配机制主要有以下几个方面的知识点:

  • 垃圾回收(Garbage Collection,GC):Go语言使用自动垃圾回收来管理内存,它会在程序运行过程中自动进行内存回收,释放不再使用的对象占用的内存空间。

  • 栈和堆:Go语言的内存管理采用了栈和堆的方式。栈是用来存放函数调用时的局部变量和函数返回值等临时数据的区域,它的空间是连续的。堆是用来存放动态分配的内存空间的区域,它的空间是离散的。

  • 栈上分配(Stack Allocation):Go语言对于一些较小的对象,会直接在栈上分配内存空间,这样可以减少内存的分配和释放的开销。

  • 堆上分配(Heap Allocation):对于大对象或者生命周期较长的对象,Go语言会在堆上分配内存空间,并由垃圾回收器来负责回收这部分内存。

  • 引用计数(Reference Counting):除了垃圾回收外,Go语言的内存管理还会使用引用计数的方法来辅助垃圾回收。通过记录对象被引用的次数,当引用计数为零时可以确定对象不再被使用,从而释放其占用的内存空间。

以下是一个简单的Go语言代码示例,展示了内存分配和回收的操作:

package main

import "fmt"

func main() {
    // 分配一个整型变量到栈上
    x := 10
    fmt.Println(x)     // 输出:10

    // 分配一个整型指针到堆上
    y := new(int)
    *y = 20
    fmt.Println(*y)    // 输出:20

    // 将堆上分配的内存释放掉
    // 在Go语言中,不需要手动释放堆上分配的内存,垃圾回收器会自动处理
}

在这个示例中,变量 x 是直接分配在栈上的,而变量 y 是通过 new() 函数分配到堆上的。当不再需要 y 变量时,不需要手动释放内存,垃圾回收器会自动回收这部分内存。

2. 内存函数

Go语言提供了一些与内存操作相关的函数,用于对内存进行分配、释放和操作。以下是一些常用的内存函数及其知识点以及代码实例:

  1. make 函数:

    用于创建切片、映射和通道等引用类型的对象,并进行内存分配。make函数会返回一个已分配内存的值,并初始化为默认值。 示例:

    slice := make([]int, 5, 10)  // 创建一个切片,长度为5,容量为10
    
  2. new 函数:

    用于分配内存空间,返回指向该内存空间的指针。new函数返回的指针指向已分配但未初始化的零值。 示例:

    ptr := new(int)  // 分配一个int类型大小的内存空间,并返回指向该空间的指针
    
  3. append 函数:

    用于向切片追加元素,并在需要时进行内存扩容。如果切片的容量不足以容纳新元素,append函数会自动重新分配更大的内存空间。 示例:

    slice := []int{1, 2, 3}
    slice = append(slice, 4)  // 向切片追加一个元素
    
  4. copy 函数:

    用于将源切片的内容复制到目标切片。copy函数会根据目标切片的容量来确定要复制的元素个数。 示例:

    src := []int{1, 2, 3}
    dst := make([]int, len(src))
    copy(dst, src)  // 将src切片的内容复制到dst切片
    
  5. delete 函数:

    用于从映射中删除指定键值对。 示例:

    m := map[string]int{
        "apple": 5,
        "banana": 3,
    }
    delete(m, "apple")  // 从映射m中删除键"apple"
    
  6. unsafe 包:

    提供了一些用于进行底层内存操作的函数,但使用不当可能会导致不安全和不可预测的行为。在普通的应用程序开发中,尽量避免使用unsafe包。 示例:

    import "unsafe"
    
    type Person struct {
        Name string
        Age  int
    }
    
    p := &Person{Name: "Alice", Age: 20}
    namePtr := (*string)(unsafe.Pointer(p))  // 将Person类型的指针转换为string类型的指针
    

2.并发编程设计

并发编程是指在程序中同时执行多个独立的任务或操作的能力。Go语言提供了丰富的并发编程支持,包括goroutine和channel等特性。下面是一些常见的并发编程知识点和代码实例:

1. Goroutine(协程)

  • 使用go关键字来启动一个新的goroutine。
  • 可以在函数前添加go关键字来将该函数调用转换为一个goroutine。
func main() {
    go fun1() // 启动一个goroutine
    fun2()
}

func fun1() {
    // goroutine的逻辑
}

func fun2() {
    // 主线程的逻辑
}

2. Channel(通道)

  • Channel是一种用于在goroutine之间通信的机制,类似于线程间的消息队列。
  • 使用make函数创建一个channel。
  • 使用<-操作符来发送和接收数据。
func main() {
    ch := make(chan int) // 创建一个int类型的channel

    go func() {
        ch <- 42 // 发送数据到channel
    }()

    value := <-ch // 从channel接收数据
    fmt.Println(value)
}

3. select语句

  • select语句用于处理多个channel的发送和接收操作。
  • select会等待多个channel中的任意一个可用,并执行相应的操作。
func main() {
    ch1 := make(chan int)
    ch2 := make(chan string)

    go func() {
        time.Sleep(time.Second)
        ch1 <- 42
    }()

    go func() {
        time.Sleep(time.Second * 2)
        ch2 <- "Hello"
    }()

    select {
    case value := <-ch1:
        fmt.Println("Received from ch1:", value)
    case message := <-ch2:
        fmt.Println("Received from ch2:", message)
    }
}

4. Mutex(互斥锁)

  • Mutex是一种用于保护共享资源的机制,防止多个goroutine同时访问共享资源。
  • 使用sync包中的Mutex类型来创建互斥锁。
import (
    "sync"
)

var count int
var mutex sync.Mutex

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Count:", count)
}

func increment(wg *sync.WaitGroup) {
    mutex.Lock()
    defer mutex.Unlock()

    count++
    wg.Done()
}

5.多进程和多线程

Go语言对并发编程提供了丰富的支持,包括多进程和多线程。下面是Go语言中相关的知识点以及代码示例:

1.并发模型
  • Go语言通过goroutine实现并发,它是一种轻量级的执行单位。使用关键字go可以启动一个goroutine。
  • Goroutine是由Go语言的调度器进行管理的,每个goroutine都有自己的栈空间。
2.多进程编程
  • Go语言通过os包提供了相关的多进程编程功能,可以使用os包的StartProcess函数创建并控制子进程。
  • 以下是一个示例代码,使用os.StartProcess创建一个子进程并执行命令:
package main

import (
	"fmt"
	"os"
)

func main() {
	cmd := "/bin/ls"
	args := []string{"-l", "-a"}

	// 创建子进程
	processAttr := &os.ProcAttr{
		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
	}

	process, err := os.StartProcess(cmd, args, processAttr)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// 等待子进程退出
	state, err := process.Wait()
	if err != nil {
		fmt.Println("Wait error:", err)
		return
	}

	fmt.Println("Exit status:", state.ExitCode())
}
3.多线程编程
  • Go语言中的并发模型主要基于goroutine和channel,可以通过goroutine实现多线程并发编程。
  • 使用关键字go启动一个goroutine,可以在函数调用前添加该关键字即可。
  • 以下是一个示例代码,使用goroutine并发执行多个任务:
package main

import (
	"fmt"
	"sync"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()

	fmt.Printf("Worker %d starting\n", id)
	// 模拟工作
	for i := 0; i < 5; i++ {
		fmt.Printf("Worker %d working on task %d\n", id, i)
	}
	fmt.Printf("Worker %d completed\n", id)
}

func main() {
	var wg sync.WaitGroup

	// 启动多个goroutine
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	// 等待所有goroutine完成
	wg.Wait()
	fmt.Println("All workers completed")
}

3.网络编程

Go语言的网络编程主要涉及以下几个知识点:

1. TCP/ UDP套接字编程

Go语言提供了net包来支持TCP和 UDP套接字编程。常用的函数有ListenDialAcceptConnect等,可以通过这些函数创建和管理套接字连接。以下是一个TCP服务器的代码示例:

package main

import (
	"fmt"
	"net"
)

func handleConnection(conn net.Conn) {
	buffer := make([]byte, 1024)
	_, err := conn.Read(buffer)
	if err != nil {
		fmt.Println("Error reading:", err.Error())
		return
	}
	fmt.Println("Received message:", string(buffer))
	conn.Close()
}

func main() {
	listener, err := net.Listen("tcp", "localhost:8888")
	if err != nil {
		fmt.Println("Error listening:", err.Error())
		return
	}
	defer listener.Close()

	fmt.Println("Server started, listening on localhost:8888")

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Error accepting connection:", err.Error())
			return
		}
		go handleConnection(conn)
	}
}

2. HTTP服务器编程

Go语言内置支持HTTP服务器,使用net/http包可以轻松创建一个HTTP服务器并处理HTTP请求。以下是一个简单的HTTP服务器代码示例:

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, World!")
}

func main() {
	http.HandleFunc("/", handler)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("Error starting server:", err.Error())
		return
	}
}

3. WebSocket编程

Go语言通过github.com/gorilla/websocket包提供了对WebSocket的支持。可以使用该包创建WebSocket服务器和客户端,并进行双向通信。以下是一个简单的WebSocket服务器代码示例:

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func echo(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	for {
		messageType, message, err := conn.ReadMessage()
		if err != nil {
			log.Println(err)
			return
		}

		fmt.Println("Received message:", string(message))

		err = conn.WriteMessage(messageType, message)
		if err != nil {
			log.Println(err)
			return
		}
	}
}

func main() {
	http.HandleFunc("/ws", echo)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("Error starting server:", err)
	}
}

以上是Go语言网络编程的一些常用知识点和简单示例。除了TCP/UDP、HTTP和WebSocket,Go语言还提供了一些其他网络编程相关的功能和包,如TLS/SSL、FTP、SMTP等。

文章来源:https://blog.csdn.net/qq_42746084/article/details/135203548
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。