Golang入门

一.下载并配置环境

官网:所有版本 - Go 编程语言 Windows 下可以使用 .msi 后缀

下载目录\Go\bin 目录添加到 Path 环境变量

选择GoLand IDE 开发 go语言,下载地址:JetBrains GoLand:不只是 Go IDE

第一个go程序

1
2
3
4
5
6
7
package main

import "fmt"

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

1.fmt 包实现了格式化 IO(输入/输出)的函数。

2.当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected

运行结果如下:
image-20250702125446293

除了go run执行命令,可以使用 go build 命令来生成可执行二进制文件,和c语言一样。

二.数据类型和关键字

基本数据类型

数据类型 分类 描述 示例
bool 布尔型 布尔值,true或false var b bool = true
string 字符串 字符串类型 var s string = "hello"
int 整型 平台相关的有符号整数(32或64位) var i int = 42
int8 整型 8位有符号整数(-128到127) var i int8 = 127
int16 整型 16位有符号整数(-32768到32767) var i int16 = 32767
int32 整型 32位有符号整数(-2147483648到2147483647) var i int32 = 2147483647
int64 整型 64位有符号整数 var i int64 = 9223372036854775807
uint 整型 平台相关的无符号整数(32或64位) var u uint = 42
uint8 整型 8位无符号整数(0到255) var u uint8 = 255
uint16 整型 16位无符号整数(0到65535) var u uint16 = 65535
uint32 整型 32位无符号整数(0到4294967295) var u uint32 = 4294967295
uint64 整型 64位无符号整数 var u uint64 = 18446744073709551615
uintptr 整型 用于存放指针的无符号整数 var p uintptr
byte 整型 uint8的别名 var b byte = 'A'
rune 整型 int32的别名,表示Unicode码点 var r rune = '中'
float32 浮点型 32位浮点数 var f float32 = 3.14
float64 浮点型 64位浮点数 var f float64 = 3.141592653589793
complex64 复数 实部和虚部都是float32的复数 var c complex64 = complex(1, 2)
complex128 复数 实部和虚部都是float64的复数 var c complex128 = complex(1, 2)

复合数据类型

数据类型 描述 示例
数组 固定长度的同类型元素序列 var a [3]int = [3]int{1, 2, 3}
切片 动态长度的同类型元素序列 var s []int = []int{1, 2, 3}
结构体 不同类型字段的组合 type Point struct { X, Y int }
指针 存储内存地址 var p *int
函数 函数类型 var f func(int) int
接口 方法集合 type Writer interface { Write([]byte) (int, error) }
映射 键值对集合 var m map[string]int = make(map[string]int)
通道 用于goroutine间通信 var ch chan int = make(chan int)

Go语言关键字

关键字 类别 描述
var 声明 声明变量
const 声明 声明常量
type 声明 声明类型
func 声明 声明函数
package 声明 声明包
import 声明 导入包
if 控制流 条件判断
else 控制流 条件判断
switch 控制流 多分支选择
case 控制流 switch的分支
default 控制流 switch的默认分支
for 控制流 循环
range 控制流 用于迭代数组、切片、映射或通道
break 控制流 退出循环或switch
continue 控制流 跳过当前循环迭代
goto 控制流 无条件跳转
fallthrough 控制流 在switch中继续执行下一个case
defer 控制流 延迟执行函数
return 控制流 从函数返回
go 并发 启动goroutine
chan 并发 声明通道
select 并发 多通道选择
interface 类型 声明接口
struct 类型 声明结构体
map 类型 声明映射类型

预声明标识符

标识符 描述
true, false 布尔常量
iota 常量生成器
nil 指针/引用类型的零值
append 向切片追加元素
cap 获取容量
close 关闭通道
complex 构造复数
copy 复制切片
delete 删除映射元素
imag 获取复数虚部
len 获取长度
make 分配并初始化slice/map/chan
new 分配内存
panic 运行时错误
print, println 打印输出
real 获取复数实部
recover 处理panic

三.基础语法

变量声明和使用

1
2
3
4
5
6
7
8
9
10
//定义变量 
var a int = 100
var b = 200
c := 300
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
g, h := 123, "hello" // := 只能被用在函数体内(局部变量),而不可以用于全局变量的声明与赋值

另:局部变量没有在其代码块中使用,会编译错误

输入输出

Go 语言中使用 fmt.Sprintffmt.Printf 格式化字符串并赋值给新串:

  • Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
  • Printf 根据格式化参数生成格式化的字符串并写入标准输出。
1
2
3
4
5
6
7
8
9
// %d 表示整型数字,%s 表示字符串
var stockcode = 123
var enddate = "2020-12-31"
var url = "Code=%d&endDate=%s"
sprintf := fmt.Srintf(url, stockcode, enddate)
//输入
fmt.Scanf("%d%d",&num1,&num2)
//输出
fmt.Println(num1,num2)

值类型和引用类型

int、float、bool 和 string 这些基本类型都属于值类型,这些类型的变量直接指向存在内存中的值

当使用等号 = 将一个值类型变量的值赋值给另一个值类型变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝,通过 &i 来获取变量 i 的内存地址,这个内存地址称之为指针,这个指针实际上也被存在另外的某一个值中。

空白标识符用于占位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
_,numb,strs := numbers() //只获取函数返回值的后两个
fmt.Println(numb,strs)
}

//一个可以返回多个值的函数
func numbers()(int,int,string){
a , b , c := 1 , 2 , "str"
return a,b,c
}

常量

在程序运行时,不会被修改的量

1
2
3
4
5
6
7
const a = "abc"
//枚举
const (
Unknown = 0
Female = 1
Male // Male = 1 不提供初始值,则表示将使用上行的表达式。
)

常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值

iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

定义一组枚举值时,可以简化代码

iota 在 新const关键字出现时将被重置为 0

1
2
3
4
5
6
7
8
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
)

运算符

略 与其他语言基本一致

指针变量 ***** 和地址值 & 的区别

1
2
3
4
5
6
7
8
func main() {
var a int = 4
var ptr *int
ptr = &a
println("a的值为", a); // 4
println("*ptr为", *ptr); // 4
println("ptr为", ptr); // 824633794744
}

条件语句

除了 if..else switch,go独有select语句

select 是 Go 中的一个控制结构,类似于 switch 语句。

select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。

select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。

如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
select {
case <- channel1:
// 执行的代码
case value := <- channel2:
// 执行的代码
case channel3 <- value:
// 执行的代码

// 你可以定义任意数量的 case

default:
// 所有通道都没有准备好,执行的代码
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"time"
)

func main() {

c1 := make(chan string)
c2 := make(chan string)

go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()

for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
for true  {
fmt.Printf("这是无限循环。\n");
continue;
break;
}

LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}

函数

Go 语言函数定义格式如下

1
2
3
func function_name( [parameter list] ) [return_types] {
函数体
}

Go 函数可以返回多个值

函数可以作为参数——函数参数

1
2
3
4
5
6
7
/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}

/* 使用函数 */
fmt.Println(getSquareRoot(9))

Go 语言函数闭包(匿名函数)

匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。

匿名函数是一个”内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func getSequence() func() int {
i:=0
//在闭包中递增 i 变量
return func() int {
i+=1
return i
}
}

func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()

/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())

/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}

Go 语言函数方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。

“接受者”(Receiver)指的是定义在方法上的那个类型的变量,也就是调用方法的对象。它类似于其他面向对象语言中的“实例”或“对象”。

1
2
3
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

/* 定义结构体 */
type Circle struct {
radius float64
}

func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}

数组

在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的

定义数组

1
2
3
4
5
6
7
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//还可以通过指定下标来初始化元素
balance := [5]float32{0:2.0,1:7.0,3.0}
//二维数组
var a = [3][5]int {{1, 2, 3, 4, 5}, {0, 9, 8, 7, 6}, {3, 4, 5, 6, 7}}

获取数组元素

1
data := balance[3]		

Go 指针

与C语言一致

当一个指针被定义后没有分配到任何变量时,它的值为 nil,也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
swap(&a, &b);

fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}

/* 交换函数这样写更加简洁,也是 go 语言的特性,c++ 和 c# 是不能这么干的 */
func swap(x *int, y *int){
*x, *y = *y, *x
}

//也可以直接这样写
a, b = b, a

结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Student struct {
name string
age int
grade int
sex string
score float64
}

func main() {
student1 := Student{age: 12, grade: 3, sex: "Male", score: 100.0}
student1.name = "Tom"
student2 := Student{"Alex", 15, 2, "Female", 90.0}
fmt.Println(student1)
fmt.Println(student2)
}

输出:

1
2
{Tom 12 3 Male 100}
{Alex 15 2 Female 90}

Go 语言没有传统面向对象语言中的类(class)和继承(inheritance)概念,而是通过组合(composition)和接口(interface)来实现类似的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

// Animal 父结构体
type Animal struct {
Name string
}

// Speak 父结构体的方法
func (a *Animal) Speak() {
fmt.Println(a.Name, "says hello!")
}

// Dog 子结构体
type Dog struct {
Animal // 嵌入 Animal 结构体
Breed string
}

func main() {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}

dog.Speak() // 调用父结构体的方法
fmt.Println("Breed:", dog.Breed)
}

Go 语言切片(Slice)

Go 语言切片是对数组的抽象,相当于其他语言的动态数组

定义切片

1
2
3
4
5
//使用 make() 函数来创建切片
make([]T, length, capacity) //[]T:定义切片,数据类型为T length:初始长度 capacity(可选):容量
var numbers []int //定义空切片
//示例
slice1 := make([]int, 5)

初始化切片

1
2
3
4
5
6
7
8
9
10
11
12
13
s :=[] int {1,2,3} 	//初始化不指定长度的数组长度就是切片
s[1] = 1

arr := [5]int{1, 2, 3, 4, 5} //有长度,是数组
slice2 := arr[:] //对数组arr的引用 也可以引用切片
s := arr[startIndex:endIndex] //取索引之间的区间
s := arr[startIndex:] //从第几索引开始
s := arr[:endIndex] //到第几索引结束

/* 向切片添加一个元素 */
numbers = append(numbers, 1)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)

范围(Range)

range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

// 声明一个包含 2 的幂次方的切片
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
// 遍历 pow 切片,i 是索引,v 是值
for i, v := range pow {
// 打印 2 的 i 次方等于 v
fmt.Printf("2**%d = %d\n", i, v)
}
}

Go 语言Map(集合)

Map 是一种无序的键值对的集合。

定义,初始化Map

可以使用内建函数 make 或使用 map 关键字来定义 Map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)
// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
// 删除键值对
delete(m, "banana")
// 遍历 Map
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}

类型转换

go 不支持隐式转换类型,一般在前面直接加要转换的类型:

1
2
3
4
5
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count)
fmt.Printf("mean 的值为: %f\n",mean)

字符串类型转换

将一个字符串转换成另一个类型,可以使用以下语法:

1
2
3
var str string = "10"
var num int
num, _ = strconv.Atoi(str)

strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,可以使用空白标识符 _ 来忽略这个错误

Go 语言接口

接口(interface)是 Go 语言中的一种类型,用于定义行为的集合,它通过描述类型必须实现的方法,规定了类型的行为契约。

接口定义和实现

接口定义使用关键字 interface,其中包含方法声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定义一个结构体
type Circle struct {
Radius float64
}
// Circle 实现 Shape 接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func main() {
c := Circle{Radius: 5}
var s Shape = c // 接口变量可以存储实现了接口的类型
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}

错误处理

Go 语言的错误处理采用显式返回错误的方式,而非传统的异常处理机制。这种设计使代码逻辑更清晰,便于开发者在编译时或运行时明确处理错误。

error 接口

Go 标准库定义了一个 error 接口,表示一个错误的抽象。

error 类型是一个接口类型,这是它的定义:

1
2
3
type error interface {
Error() string
}
  • 实现 error 接口:任何实现了 方法的类型都可以作为错误。Error()
1
2
3
4
func main() {
err := errors.New("this is an error")
fmt.Println(err) // 输出:this is an error
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}

func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}

Errorf函数打印错误信息

1
2
error := fmt.Errorf("this is an error")
fmt.Println(error)

底层就是调用了error.New()

中断程序

panic函数,发生异常就终止程序运行

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
fmt.Println(div(10, 0))
}

func div(a int, b int) (result int) { //可以给返回类型命名 必须加括号
if b == 0 {
panic("除数不能为0") //报错并打印
fmt.Println("我运行了吗") //没运行
}
result = a / b
return result
}

恢复程序

deferrecover来实现panic捕获的异常,让程序继续执行

被 defer 修饰的函数会在当前函数返回之前执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func div(a, b int) (result int) {
defer func() { //在 div() 返回前会检查是否发生 panic。
if error := recover(); error != nil { // recover() 会捕获第一个panic
result = -1
fmt.Println(error) //打印捕获的错误信息
}
}()
if b == 0 {
panic("除数不能为0") //相当于return了
panic("未知错误") //不会被执行
}
return a / b
}
func main() {
fmt.Println(div(10, 0))
}

输出:

1
2
除数不能为0
-1

字符串操作

Go语言的编码方式是UTF-8,汉字占3个字节

如果字符串包括中文等,想计算字符的个数而不是字节数,需将字符串转换成rune类型数组

rune相当Java的char

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = "123你好バカヤロー"

func main() {
arr1 := []byte(a)
for _, v := range arr1 {
fmt.Print(v) // 打印字节
fmt.Printf("\t")
fmt.Printf(string(v)) // 打印字符
fmt.Printf("\t")
fmt.Printf("%c\n", v) // 打印字符
}
fmt.Println(len(arr1)) //24

arr2 := []rune(a)
for _, v := range arr2 {
fmt.Print(v)
fmt.Printf("\t")
fmt.Printf("%c\n", v)
}
fmt.Println(len(arr2)) //10
}

查找子串在字符串中出现的位置

1
2
3
4
5
6
7
8
9
func Index(s, sep string) int
func IndexByte(s string, c byte) int
func IndexRune(s string,r rune) int
func IndexAny(s, chars string) int
func IndexFunc(s string, f func(rune) bool) int
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int

判断字符串中是否包含子串

1
2
3
4
5
func Contains(s, substr string) bool
func ContainsRune(s string,r rune) bool
func ContainsAny(s, chars string) bool
func HasPrefix(s, prefix string) bool
func HasSuffix(s, suffix string) bool

正则表达式

在 Go 语言中,正则表达式通过 regexp 包来实现。

常用函数

  1. CompileMustCompile
    用于编译正则表达式。Compile 返回一个 *Regexp 对象和一个错误,而 MustCompile 在编译失败时会直接 panic。使用 Compile 函数时,务必检查返回的错误,以避免程序崩溃。
  2. MatchString
    检查字符串是否匹配正则表达式。
  3. FindStringFindAllString
    用于查找匹配的字符串。FindString 返回第一个匹配项,FindAllString 返回所有匹配项。
  4. ReplaceAllString
    用于替换匹配的字符串。
  5. Split
    根据正则表达式分割字符串。

常用正则表达式语法

1
2
3
4
5
6
7
8
9
10
.:匹配任意单个字符(除了换行符)。
*:匹配前面的字符 0 次或多次。
+:匹配前面的字符 1 次或多次。
?:匹配前面的字符 0 次或 1 次。
\d:匹配数字字符(等价于 [0-9])。
\w:匹配字母、数字或下划线(等价于 [a-zA-Z0-9_])。
\s:匹配空白字符(包括空格、制表符、换行符等)。
[]:匹配括号内的任意一个字符(例如 [abc] 匹配 a、b 或 c)。
^:匹配字符串的开头。
$:匹配字符串的结尾。

示例

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
pattern := `^[a-zA-Z0-9\.]+$` // 正则表达式
regex := regexp.MustCompile(pattern) // 创建一个正则表达式对象
reader := bufio.NewReader(os.Stdin) // 创建一个输入流
input, _ := reader.ReadString('\n') // 从输入流中读取一行数据
input = strings.TrimSpace(input) // 去除输入字符串中的空格
if regex.MatchString(input) {
fmt.Println("密码格式正确")
} else {
fmt.Println("密码格式错误")
}
}
1
2
3
regex := regexp.MustCompile(`[a-zA-Z]+`)
result := regex.FindAllString("abc123def456", -1)
// result 的值为 []string{"abc", "def"}

时间和日期函数

1
2
3
4
5
6
7
8
// 获取当前时间
t := time.Now()
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
fmt.Println(t.Format("2006.01.02 15:04:05"))
// 时间常量可搭配休眠函数使用
time.Sleep(time.Second * 2)
// 时间戳
fmt.Println(t.Unix())

类型断言

在 Go 语言中,类型断言(Type Assertion)是一种用于检查接口值的实际类型的机制。

类型断言是 Go 语言中处理接口类型的重要工具,它允许我们从接口值中提取出具体的类型,并对其进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  var i interface{} = "Hello, Go!"

// 尝试将 i 断言为 string 类型
if s, ok := i.(string); ok {
fmt.Println("处理字符串:", s)
} else if n, ok := i.(int); ok {
fmt.Println("处理整数:", n)
} else {
fmt.Println("无法处理的类型")
}

// 直接断言为 int 类型(会引发 panic)
    m := i.(int)
    fmt.Println("断言成功:", n)
}

Go 还提供了特殊的 type switch 语法来测试多种类型:

1
2
3
4
5
6
7
8
9
10
func printType(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("这是一个整数:", v)
case string:
fmt.Println("这是一个字符串:", v)
default:
fmt.Println("未知类型")
}
}

文件操作

Go 语言提供了丰富的标准库来支持文件处理,包括文件的打开、关闭、读取、写入、追加和删除等操作。

  1. os 是核心库:提供底层文件操作(创建、读写、删除等),大多数场景优先使用。
  2. io 提供通用接口:如 Reader/Writer,可与文件、网络等数据源交互。
  3. bufio 优化性能:通过缓冲减少 I/O 操作次数,适合频繁读写。

创建并写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func createAddWriteFile() {
// 创建文件
file, err := os.Create("123.txt")
if err != nil {
log.Fatal(err) // 打印错误并退出程序,底层调用os.Exit(1),并触发panic
}
// 写 字节
bytes := []byte{'1', 'b', '\r', '\n', '4'}
file.Write(bytes)
// 写 字符串
file.WriteString("\r\nHello World")

// 带缓存区的写入
w := bufio.NewWriter(file)
w.WriteString("こにちは")
// 将缓存中的数据写入文件
w.Flush()
defer file.Close()
}

打开并读入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func openAndReadFile() {
file, err := os.Open("123.txt")
file, err = os.OpenFile("123.txt", os.O_RDWR, 0666)
if err != nil {
log.Fatal(err)
}
// 第一次读取
buf := make([]byte, 10)
for {
count, err := file.Read(buf)
if err != nil && err != io.EOF {
log.Fatal(err)
}
fmt.Println(buf[:count])
if err == io.EOF {
break
}
}
// 重置文件指针到开头
_, err = file.Seek(0, 0)
if err != nil {
log.Fatal(err)
}
// 第二次读取(使用缓冲)
r := bufio.NewReader(file)
for {
readString, err := r.ReadString('\n')
fmt.Println(readString)
if err == io.EOF {
break
}
}
defer file.Close()
}

OpenFile打开模式

1
2
3
4
5
6
7
8
9
10
11
const (
// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
O_RDONLY int = syscall.O_RDONLY // open the file read-only.
O_WRONLY int = syscall.O_WRONLY // open the file write-only.
O_RDWR int = syscall.O_RDWR // open the file read-write.
O_APPEND int = syscall.O_APPEND // append data to the file when writing.
O_CREATE int = syscall.O_CREAT // create a new file if none exists.
O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist.
O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened.
)

打开权限

0.没有任何权限
1.执行权限(如果是可执行程序,可以运行)
2.写权限
3.写权限和执行权限
4.读权限
5.读权限和执行权限
6.读权限和写权限
7.读权限和写权限以及执行权限

文件是否存在

1
2
3
4
5
6
7
8
9
10
11
12
func isExist() {
_, err := os.Stat("123.txt")
if err != nil {
if os.IsExist(err) {
fmt.Println("文件已存在")
} else if os.IsNotExist(err){
fmt.Println("文件不存在")
}else {
fmt.Println("文件状态未知")
}
}
}

Go 并发

并发是指程序同时执行多个任务的能力。

Go 语言支持并发,通过 goroutines 和 channels 提供了一种简洁且高效的方式来实现并发。

Goroutines:

  • Go 中的并发执行单位,类似于轻量级的线程。
  • Goroutine 的调度由 Go 运行时管理,用户无需手动分配线程。
  • 使用 go 关键字启动 Goroutine。
  • Goroutine 是非阻塞的,可以高效地运行成千上万个 Goroutine。

Channel:

  • Go 中用于在 Goroutine 之间通信的机制。
  • 支持同步和数据共享,避免了显式的锁机制。
  • 使用 chan 关键字创建,通过 <- 操作符发送和接收数据。

Scheduler(调度器):

Go 的调度器基于 GMP 模型,调度器会将 Goroutine 分配到系统线程中执行,并通过 M 和 P 的配合高效管理并发。

goroutine 语法格式

1
go 函数名( 参数列表 )

通道(Channel)

使用 make 函数创建一个 channel,使用 <- 操作符发送和接收数据。如果未指定方向,则为双向通道。

1
2
3
4
5
6
7
8
9
10
11
12
13
ch := make(chan int)
//指定缓冲区大小:
ch := make(chan int, 100)
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
//通道使用完及时关闭
close(ch)

//控制读写权限
go func(c chan int) { //读写均可的channel c } (a)
go func(c <- chan int) { //只读的Channel } (a)
go func(c chan <- int) { //只写的Channel } (a)

实例

通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}

func main() {
s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
close(c)
fmt.Println(x, y, x+y)
}

使用select语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)

go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}

使用 WaitGroup

sync.WaitGroup 用于等待多个 Goroutine 完成。

同步多个 Goroutine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"sync"
)

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Goroutine 完成时调用 Done()
fmt.Printf("Worker %d started\n", id)
fmt.Printf("Worker %d finished\n", id)
}

func main() {
var wg sync.WaitGroup

for i := 1; i <= 3; i++ {
wg.Add(1) // 增加计数器
go worker(i, &wg)
}

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

其他高级特性

Context:

用于控制 Goroutine 的生命周期。

1
context.WithCancel、context.WithTimeout。

Mutex 和 RWMutex:

sync.Mutex 提供互斥锁,用于保护共享资源。

1
2
3
4
var mu sync.Mutex
mu.Lock()
// critical section
mu.Unlock()

Continued…

后面会持续补充



新ICP备2025018290号-1
本站总访问量