Golang笔记(下)

该博客是Golang学习笔记(下),涵盖错误处理、并发编程和正则表达式内容。错误处理介绍了error类型、信息格式化、自定义错误等;并发编程讲解了进程、线程、协程概念,以及协程创建、通道使用和select语句;还提及了正则表达式相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Golang学习笔记(下)

前篇:Golang学习笔记(上)

在这里插入图片描述

十四、错误处理

14.1使用error类型
func New(text string) error

例子:

package main

import (
    "errors" // 导入errors包
    "fmt"
)

func main() {
    var number, divisor int
    fmt.Println("请输入一个整数作为分子:")
    fmt.Scan(&number)
    fmt.Println("请输入一个整数作分母:")
    fmt.Scan(&divisor)
    result, err := divide(number, divisor)
    fmt.Println("result", result)
    if err != nil {
        fmt.Println(err)
    }
}

// 除法函数,参数number是分子,参数divisor是分母
func divide(number, divisor int) (float32, error) {
    if divisor == 0 {
        // 返回多值数据
        return 0.0, errors.New("分母不能为0")
    }
    return float32(number / divisor), nil
}

14.2错误信息格式化

使用fmt包中的Errorf函数

func divide(number, divisor int) (float32, error) {
    if divisor == 0 {
        // 返回多值数据
        error := fmt.Errorf("您输入的分母是:%d", divisor)
        return 0.0, error
    }
    return float32(number / divisor), nil
}

14.3自定义错误类型
// 自定义错误类型
type error interface {
    Error() string
}

举例

package main

import (
    "fmt"
)

// 自定义错误类型结构体
type DivisionByZero struct {
    // 错误信息
    message string
}

// 实现error接口的Error()方法
func (d DivisionByZero) Error() string {
    return d.message
}

// 除法函数,参数number是分子,参数divisor是分母
func divide(number, divisor int) (float32, error) {
    if divisor == 0 {
        // 创建一个DivisionByZero错误实例,并返回
        return 0.0, DivisionByZero{message: "除数不能为0"}
    }
    return float32(number) / float32(divisor), nil
}

func main() {
    var number, divisor int
    fmt.Println("请输入一个整数作为分子:")
    fmt.Scan(&number)
    fmt.Println("请输入一个整数作分母:")
    fmt.Scan(&divisor)
    result, err := divide(number, divisor)
    if err != nil {
        // 打印具体的错误信息
        fmt.Println("发生错误:", err.Error())
        return
    }
    fmt.Printf("result: %f\n", result)
}

14.4错误机制
  • go语言提供了panic()和recover()函数

  • defer关键字实现错误处理

延迟执行

defer语句用来在函数最终要返回前被执行,用来释放资源,在错误处理的过程中,一个非常重要的环节就资源释放,无论执行成功,还是执行错误,都应该保证释放这些资源,此时可以使用defer关键字延迟执行,保证在程序运行完成之前释放资源。

当有多个defer语句时,defer语句遵守后进先出原则。

进入宕机状态

用于触发一个运行时错误。

  1. 自动进入当即状态,当发生运行期错误时自动进入。

  2. 手动触发,程序员可以根据自己的需要通过panic()函数可以手动触发宕机状态。

package main

import "fmt"

func main() {
    intSclical := []int{1, 2, 3}
    // 下标月结引发的宕机
    fmt.Println(intSclical[3])
}

输出:

D:Go/bin/go.exe run panic.go [F:/Code/Go/src]
panic: runtime error: index out of range [3] with length 3

goroutine 1 [running]:
main.main()
    F:/Code/Go/src/panic.go:8 +0x15
exit status 2
错误: 进程退出代码 1.

手动宕机

package main

import "fmt"

func main() {
    intSclical := []int{1, 2, 3}
    // 下标超出索引范围,自动引发的宕机
    // fmt.Println(intSclical[3])
    var index int = -1
    fmt.Println("请输入下标索引: ")
    fmt.Scan(&index)
    if index > 3 {
        panic("索引超出范围")
    } else {
        fmt.Println("输出元素为:", intSclical[index])
    }
}

14.5从宕机状态恢复

使用recover()函数从宕机状态恢复

package main

import (
    "fmt"
)

// 除法函数,参数number是分子,参数divisor是分母
func divide(number, divisor int) (float32, bool) {
    if divisor == 0 {
        // 触发panic,表示发生了严重的错误
        panic("除数不能为0")
    }
    return float32(number) / float32(divisor), true
}

func main() {
    var number, divisor int
    fmt.Println("请输入一个整数作为分子:")
    fmt.Scan(&number)
    fmt.Println("请输入一个整数作分母:")
    fmt.Scan(&divisor)

    // 使用defer和recover来捕获panic
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("程序遇到严重错误:", r)
        }
    }()

    result, ok := divide(number, divisor)
    if !ok {
        // 实际上,由于我们使用了panic,这里的ok检查是多余的
        // 因为panic会导致程序停止执行,不会到达这里
        fmt.Println("发生错误,无法执行除法")
        return
    }
    fmt.Printf("result: %f\n", result)
}

十五、并发编程

15.1进程
  • 一个进程就是一个执行中的程序

  • 每一个进程都有自己独立的一块内存空间

  • 每一个进程都有一组系统资源

15.2线程
  • 线程和进程都是一段完成特定功能的代码。

  • 它们都是程序中的单个顺序控制流程

  • 多个线程共享一块内存空间和一组系统资源。

  • 在切换不同线程之间时,系统的开销比切换进程要小得多,称为轻量级进程。

  • 一个进程可以包含多个线程。

15.3协程
  • 协程(Goroutines)是一种轻量级的线程。

  • 不阻塞线程,但可以挂起的计算过程。

  • 挂起几乎没有开销。

  • 底层库处理一部阻塞任务。

  • 协程代码流程顺序,无需大量的回调函数。

  • 类似同步代码,已于理解、调试和开发。

15.4创建协程
  • go语言的并发编程时通过协程(Goroutines)实现的。

  • 在Go中创建协程很简单,只要在函数或方法前加go关键字,则会创建一个新协程。

创建协程示例

package main

import "fmt"

func display(str string) {
    fmt.Println(str)
}

func main() {
    // 创建协程,异步调用函数
    go display("welcome to Beijing!")
    // 正常调用函数
    display("Hello World")
}

每次运行的结果不一致,原因

在这里插入图片描述

也就是说主线程会调用display(“Hello World!”)和协程中的dispaly(“Welcome to Beijing!”),这两调用的过程时随机的,谁先谁后不一定。

两个协程执行是异步的,哪个先执行,哪个后执行,都是不确定的,中就是并发程序的特点。

15.5通道

在这里插入图片描述

声明通道

  1. 使用chan关键字声明,语法如下:
    var Channel_name chan Type

  2. 使用make()函数创建通道,语法如下:
    channel_name := make(chan Type)

举例


package main

import "fmt"

func main() {
	// 1.使用chan声明通道
	var mychannel1 chan int
	fmt.Printf("mychannel1:%V\n", mychannel1)
	fmt.Printf("mychannel1类型:%T\n", mychannel1)

	// 2.通过make()函数创建通道
	mychannel2 := make(chan int)
	fmt.Printf("mychannel2:%v\n", mychannel2)
	fmt.Printf("mychannel类型:%T\n", mychannel2)
}

发送和接收数据

  1. 向通道发生数据语法如下:

    mychannel<-data

  2. 从通道接收数据语法如下:

    接收数据变量:=<-mychannel

关闭通道

使用close()函数关闭通道。

示例代码:

package main

import "fmt"

func display(chstr chan string) {
	// 向通道发生数据
	chstr <- "Hello,World!"
}

func main() {
	// 1.使用chan声明通道
	var mychannel1 chan int
	fmt.Printf("mychannel1:%V\n", mychannel1)
	fmt.Printf("mychannel1类型:%T\n", mychannel1)

	// 2.通过make()函数创建通道
	mychannel2 := make(chan int)
	fmt.Printf("mychannel2:%v\n", mychannel2)
	fmt.Printf("mychannel类型:%T\n", mychannel2)

	// 发生和接收数据
	message := make(chan string)
	// 启动协程
	go display(message)
	// 从通道中接收数据
	msg := <-message
	fmt.Println(msg)

	// 关闭通道
	ch := make(chan int, 3)
	ch <- 2
	ch <- 3
	close(ch)
	fmt.Println("关闭通道")

	// 试图向关闭的通道发生数据(error)
	// ch <- 4
	fmt.Println("关闭通道")

	// 试图从关闭的通道中接收数据
	msg1 := <-ch
	fmt.Println("从通道中接收数据")
	mgs2, ok := <-ch
	fmt.Println("从通道中接收的数据:", msg)
	fmt.Println(ok)
	fmt.Println(msg1, mgs2)
}

遍历通道

package main

import "fmt"

// 遍历通道
func producer(chnl chan int) {
	for i := 0; i < 10; i++ {
		chnl <- i
	}
}

func main() {
	// 创建int类型通道
	ch := make(chan int)
	// 创建协程
	go producer(ch)
	// 遍历从通道中接收的数据
	for v := range ch {
		fmt.Println("接受:", v)
	}
}

单向通道和双向通道

channel_name := make(<-chan Typr) // 只接收数据

channel_name := make(chan<- Typr) // 只发送数据

package main

import "fmt"

func main() {
	// 创建只接收的数据通道
	mychannel1 := make(<-chan string)

	// 创建只发送的数据通道
	mychannel2 := make(chan<- string)
	fmt.Printf("mychannel1:%v\n", mychannel1)
	fmt.Printf("mychannel1类型:%T\n", mychannel1)
	fmt.Printf(<-mychannel2) // error:invalid operation: cannot receive from send-only channel mychannel2 (variable of type chan<- string)

	fmt.Printf("mychannel2:%v\n", mychannel2)
	fmt.Printf("mychannel2类型:%T\n", mychannel2)
}	

缓冲区通道

  • 无缓冲区通道

无缓冲区通道,通道中无法存储数据。发送和接收数据时是同步的,无缓冲区通道使用make()函数创建,自谦创建的都是无缓冲区通道。

有两种情况下会造成程序的阻塞:

  1. 当一个协程A将数据发送给通道时,其他协程还未接收数据时,协程A被迫阻塞,知道其他协程接收数据后,协程A才能继续执行。

  2. 当一个协程A试图从通道接收数据时,如果通道A中没有数据,则会等待其他协程发送,知道其他协程发送数据后,协程A才能继续执行。

  • 有缓冲区通道

有缓冲区通道,通道中可以存储数据,发送和接受时是异步的,创建有缓冲区的通道可以使用make(chan Type int)函数,

该函数的第一个参数是通道中的数据类型,第二个参数是设置缓冲区的大小。

在这里插入图片描述

15.6使用select语句

语法:

select {
    case 通道1:
        语句组1
    case 通道2:
        语句组2
    ...
    case 通道n:
        语句组n
    default:
        语句组n+1
}

举例:

package main

import (
	"fmt"
	"time"
)

func process(ch chan string) {
	time.Sleep(5 * time.Second)
	ch <- "Hello World."
}

func main() {
	// 声明两个通道c1和c2
	c1 := make(chan string)
	c2 := make(chan string)

	// 协程处理匿名函数
	go func() {
		// 休眠1秒
		time.Sleep(1 * time.Second)
		c1 <- "one"
	}()
	// 协程处理匿名函数
	go func() {
		// 休眠2秒
		time.Sleep(2 * time.Second)
		// 发送数据
		c2 <- "two"
	}()

	// 循环遍历从通道中接收的数据
	for i := 0; i < 2; i++ {
		select {
		// 从c1通道接收数据
		case msg1 := <-c1:
			fmt.Println("received", msg1)
		// 从c2通道接收数据
		case msg2 := <-c2:
			fmt.Println("received", msg2)
		}
	}

	// default
	ch := make(chan string)
	// 启动协程
	go process(ch)
	// 一直遍历从通道中取数据
	for {
		time.Sleep(1 * time.Second)
		select {
		case v := <-ch:
			fmt.Println("接收数据:", v)
			return
		default:
			fmt.Println("没有数据接收。")
		}
	}
}
	for {
		time.Sleep(1 * time.Second)
		select {
		case v := <-ch:
			fmt.Println("接收数据:", v)
			return
		default:
			fmt.Println("没有数据接收。")
		}
	}
}

十六、正则表达式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

childish_tree

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值