Go -- 函数、方法和字符串

以下内容都来源于Go语言101: 字符串 -Go语言101

字符串

关于字符串的一些简单事实

  • 字符串值(和布尔以及各种数值类型的值)可以被用做常量。
  • Go支持两种风格的字符串字面量表示形式:双引号风格(解释型字面表示)和反引号风格(直白字面表示)。具体介绍请阅读前文
  • 字符串类型的零值为空字符串。一个空字符串在字面上可以用""或者``来表示。
  • 我们可以用运算符+和+=来衔接字符串。
  • 字符串类型都是可比较类型。同一个字符串类型的值可以用==和!=比较运算符来比较。 并且和整数/浮点数一样,同一个字符串类型的值也可以用>、<、>=和<=比较运算符来比较。 当比较两个字符串值的时候,它们的底层字节将逐一进行比较。如果一个字符串是另一个字符串的前缀,并且另一个字符串较长,则另一个字符串为两者中的较大者。

Go中的字符串和Java中的字符串相同,都是不可修改的。底层是一个个字符组成的串。

语法糖

字符串当作字节切片使用

内置函数copy和append可以用来复制和添加切片元素。 事实上,做为一个特例,如果这两个函数的调用中的第一个实参为一个字节切片的话,那么第二个实参可以是一个字符串。 (对于append函数调用,字符串实参后必须跟随三个点...。) 换句话说,在此特例中,字符串可以当作字节切片来使用。

package main

import "fmt"

func main() {
	hello := []byte("Hello ")
	world := "world!"

	// helloWorld := append(hello, []byte(world)...) // 正常的语法
	helloWorld := append(hello, world...)            // 语法糖
	fmt.Println(string(helloWorld))

	helloWorld2 := make([]byte, len(hello) + len(world))
	copy(helloWorld2, hello)
	// copy(helloWorld2[len(hello):], []byte(world)) // 正常的语法
	copy(helloWorld2[len(hello):], world)            // 语法糖
	fmt.Println(string(helloWorld2))
}

函数

变长参数和变长参数函数类型

一个函数的最后一个参数可以是一个变长参数。一个函数可以最多有一个变长参数。一个变长参数的类型总为一个切片类型。 变长参数在声明的时候必须在它的(切片)类型的元素类型前面前置三个点...,以示这是一个变长参数。 两个变长函数类型的例子:

func (values ...int64) (sum int64)
func (sep string, tokens ...string) string

通道

通道类型和值

和数组、切片以及映射类型一样,每个通道类型也有一个元素类型。 一个通道只能传送它的(通道类型的)元素类型的值。

通道可以是双向的,也可以是单向的。

  • 字面形式chan T表示一个元素类型为T的双向通道类型。 编译器允许从此类型的值中接收和向此类型的值中发送数据。
  • 字面形式chan<- T表示一个元素类型为T的单向发送通道类型。 编译器不允许从此类型的值中接收数据。
  • 字面形式<-chan T表示一个元素类型为T的单向接收通道类型。 编译器不允许向此类型的值中发送数据。

双向通道chan T的值可以被隐式转换为单向通道类型chan<- T和<-chan T,但反之不行(即使显式也不行)。 类型chan<- T和<-chan T的值也不能相互转换。

通道操作详解

为了让解释简单清楚,在本文后续部分,通道将被归为三类:

  1. 零值(nil)通道;
  2. 非零值但已关闭的通道;
  3. 非零值并且尚未关闭的通道。

下表简单地描述了三种通道操作施加到三类通道的结果。

操作

一个零值nil通道

一个非零值但已关闭的通道

一个非零值且尚未关闭的通道

关闭

产生恐慌

产生恐慌

成功关闭(C)

发送数据

永久阻塞

产生恐慌

阻塞或者成功发送(B)

接收数据

永久阻塞

永不阻塞(D)

阻塞或者成功接收(A)

我们可以认为一个通道内部维护了三个队列(均可被视为先进先出队列):

  1. 接收数据协程队列(可以看做是先进先出队列但其实并不完全是,见下面解释)。此队列是一个没有长度限制的链表。 此队列中的协程均处于阻塞状态,它们正等待着从此通道接收数据。
  2. 发送数据协程队列(可以看做是先进先出队列但其实并不完全是,见下面解释)。此队列也是一个没有长度限制的链表。 此队列中的协程亦均处于阻塞状态,它们正等待着向此通道发送数据。 此队列中的每个协程将要发送的值(或者此值的指针,取决于具体编译器实现)和此协程一起存储在此队列中。
  3. 数据缓冲队列。这是一个循环队列(绝对先进先出),它的长度为此通道的容量。此队列中存放的值的类型都为此通道的元素类型。 如果此队列中当前存放的值的个数已经达到此通道的容量,则我们说此通道已经处于满槽状态。 如果此队列中当前存放的值的个数为零,则我们说此通道处于空槽状态。 对于一个非缓冲通道(容量为零),它总是同时处于满槽状态和空槽状态。

一些通道的使用例子

来看一些通道的使用例子来加深一下对上一节中的解释的理解。

一个简单的通过一个非缓冲通道实现的请求/响应的例子:

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int) // 一个非缓冲通道
	go func(ch chan<- int, x int) {
		time.Sleep(time.Second)
		// <-ch    // 此操作编译不通过
		ch <- x*x  // 阻塞在此,直到发送的值被接收
	}(c, 3)
	done := make(chan struct{})
	go func(ch <-chan int) {
		n := <-ch      // 阻塞在此,直到有值发送到c
		fmt.Println(n) // 9
		// ch <- 123   // 此操作编译不通过
		time.Sleep(time.Second)
		done <- struct{}{}
	}(c)
	<-done // 阻塞在此,直到有值发送到done
	fmt.Println("bye")
}

(c, 3) 是启动 goroutine 时传递的参数,具体含义如下:

  • c:表示将通道 c 传递给 goroutine,使得这个 goroutine 可以向通道 c 发送值。
  • 3:表示将整数 3 传递给 goroutine,这个整数将在 goroutine 中被使用。

整个 go func(ch chan<- int, x int) {...} 这个表达式是一个匿名函数(函数字面量)的启动,创建了一个新的 goroutine,这个 goroutine 接收两个参数:一个是通道 c,一个是整数 3

在这个匿名函数中,通过 ch <- x*x3*3 的结果发送到通道 c。由于通道 c 是非缓冲通道,这个发送操作会阻塞,直到有其他 goroutine 准备好接收这个值。

这样的设计可以确保在主 goroutine 中创建的两个 goroutines 之间建立了同步关系,第一个 goroutine 发送数据,而第二个 goroutine 接收数据。整体上,这种通过通道进行数据传递和同步的机制是 Go 中协程之间通信的一种常见方式。

demo解释:先定义一个通道 c,然后定义一个匿名函数,用之前定义的通道 c 发送数据。然后又定义了一个通道done,然后定义一个匿名函数,用之前定义的通道done接收数据。

这行代码 done <- struct{}{} 实际上是向通道 done 发送一个空结构体 {}。在 Go 语言中,通常使用空结构体来表示不包含任何信息的数据。struct{}{} 创建了一个空的结构体实例。

在这个特定的例子中,这行代码的目的是通知主 goroutine 已经完成了任务。通道 done 是一个非缓冲通道,因此发送操作 done <- struct{}{} 会导致发送者阻塞,直到有其他 goroutine 准备好从通道中接收这个值。

在主 goroutine 中,<-done 这行代码会阻塞,直到有值发送到通道 done。因此,当后台 goroutine 执行 done <- struct{}{} 时,主 goroutine 将收到通道中的值,解除阻塞,从而继续执行后面的代码,打印 "bye"。

简而言之,这行代码的作用是通过非缓冲通道 done 向主 goroutine 发送一个信号,告知主 goroutine 后台 goroutine 的任务已经完成,可以继续执行。这是一种在并发编程中用于同步协程的常见模式。

方法

方法声明

在Go中,我们可以为类型T和*T显式地声明一个方法,其中类型T必须满足四个条件:

  1. T必须是一个定义类型
  2. T必须和此方法声明定义在同一个代码包中;
  3. T不能是一个指针类型;
  4. T不能是一个接口类型
// Age和int是两个不同的类型。我们不能为int和*int
// 类型声明方法,但是可以为Age和*Age类型声明方法。
type Age int
func (age Age) LargerThan(a Age) bool {
	return age > a
}
func (age *Age) Increase() {
	*age++
}

// 为自定义的函数类型FilterFunc声明方法。
type FilterFunc func(in int) bool
func (ff FilterFunc) Filte(in int) bool {
	return ff(in)
}

// 为自定义的映射类型StringSet声明方法。
type StringSet map[string]struct{}
func (ss StringSet) Has(key string) bool {
	_, present := ss[key]
	return present
}
func (ss StringSet) Add(key string) {
	ss[key] = struct{}{}
}
func (ss StringSet) Remove(key string) {
	delete(ss, key)
}

// 为自定义的结构体类型Book和它的指针类型*Book声明方法。

type Book struct {
	pages int
}

func (b Book) Pages() int {
	return b.pages
}

func (b *Book) SetPages(pages int) {
	b.pages = pages
}

属主参数的传参是一个值复制过程

和普通参数传参一样,属主参数的传参也是一个值复制过程。 所以,在方法体内对属主参数的直接部分的修改将不会反映到方法体外。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ming__GoGo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值