变量及数据类型

Go的数据类型一共分为四大类:基础类型、聚合类型、引用类型和接口类型。

  • 基础类型分为:整数、浮点数、复数、布尔值、字符串、常量。这些基本类型都属于值类型,
  • 聚合类型包括:数组、结构体。这类不能包含自己,但是可以包含自己的指针类型
  • 引用类型包括:指针、slice、map、函数、通道
  • 接口类型

类型别名和自定义类型

自定义类型

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

//将MyInt定义为int类型 type MyInt int

通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性。

类型别名

类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

type TypeAlias = Type

我们之前见过的rune和byte就是类型别名,他们的定义如下:

type byte = uint8 type rune = int32

类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

//类型定义 type NewInt int  
//类型别名 type MyInt = int  
func main() {    
   var a NewInt    
   var b MyInt      
   fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt    
   fmt.Printf("type of b:%T\n", b) //type of b:int
}
结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

基础类型

常量

字符串

strings包

Compare

  • func Compare(a, b string) int

按照字典序比较两个字符串,通常情况下直接使用=,>,<会更快一些。

Contains,ContainsAny 和 ContainsRune

  • func Contains(s, substr string) bool

  • func ContainsAny(s, chars string) bool

  • func ContainsRune(s string, r rune) bool

    //Contains是否包含某个子字符串,返回true/false fmt.Println(strings.Contains("helloword","h")) //ContainsAny用于判断子串中是否具有一个字符在源串s中。子串为空,返回false。 fmt.Println(strings.ContainsAny("helloworld","ppp"))//false //ContainsRune用于判断Ascall码代表的字符是否在源串s中。 fmt.Println(strings.ContainsRune("aardvark", 97)) fmt.Println(strings.ContainsRune("timeout", 97))

Count

  • func Count(s, substr string) int

    //判断子串在源串中的数量,如果子串为空,则长度为源串的长度+1。
    fmt.Println(strings.Count("cheese", "e")) // 3
    fmt.Println(strings.Count("five", "")) // before & after each rune 5=4+1

EqualFold

  • func EqualFold(s, t string) bool

    //在不区分大小写的情况下,判断两个字符串是否相同。 fmt.Println(strings.EqualFold("aaa","AAa")) //true

Trim,TrimFunc,TrimLeft,TrimLeftFunc,TrimPrefix,TrimSuffix,TrimRight,TrimRightFunc

  • func Trim(s string, cutset string) string

  • func TrimFunc(s string, f func(rune) bool) string

  • func TrimLeft(s string, cutset string) string

  • func TrimLeftFunc(s string, f func(rune) bool) string

  • func TrimPrefix(s, prefix string) string

  • func TrimSuffix(s, suffix string) string

  • func TrimRight(s string, cutset string) string

  • func TrimRightFunc(s string, f func(rune) bool) string

    // Trim 包含在cutset中的元素都会被去掉.注意匹配规则值边缘匹配 fmt.Println(strings.Trim("iiiaaaibiii","i"))//aaaib fmt.Println(strings.Trim("iiiaaaibiii","ia"))//b // TrimFunc去掉满足条件的字符 fmt.Print(strings.TrimFunc("iiiHello, iGophers!!!", func(r rune) bool {  return r =='i' || r =='!' }))  //Hello, iGophers ​ // TrimLeft 去掉左边满足包含在cutset中的元素,直到遇到不在cutset中的元素为止 fmt.Print(strings.TrimLeft("¡¡¡Hello, Gophers!!!", "!¡")) // Hello, Gophers!!! ​ // TrimLeftFunc 去掉左边属于函数返回值部分,直到遇到不在cutset中的元素为止 fmt.Print(strings.TrimLeftFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {  return !unicode.IsLetter(r) && !unicode.IsNumber(r) })) // Hello, Gophers!!! ​ // TrimPrefix 去掉开头部分;TrimSuffix 去掉结尾部分 var s = "¡¡¡Hello, Gophers!!!" s = strings.TrimPrefix(s, "¡¡¡Hello, ") s = strings.TrimPrefix(s, "¡¡¡Howdy, ") fmt.Print(s)

TrimRight,TrimRightFunc和TrimLeft,TrimLeftFunc功能保持一直,无需赘述。

Fields, FieldsFunc【分割字符串】

  • func Fields(s string) []string
  • func FieldsFunc(s string, f func(rune) bool) []string

Fields:使用空白分割字符串。

FieldsFunc:根据传入的函数分割字符串,如果当前参数c不是数字或者字母,返回true作为分割符号。

//Fields:使用空白分割字符串。%q 是对字符串进行的转义,保证符go的语法
fmt.Printf("分割后的:%q\n",strings.Fields("hello world"))  //["hello" "world"]

//FieldsFunc:每次运行的满足f(c)的Unicode代码点c处拆分给定的字符串str,并返回由str组成的切片数组。,如果当前参数c不是数字或者字母,返回true作为分割符号。
// FieldsFunc(s string, f func(rune) bool) []string
f := func(c rune) bool {
return c == ';'
}
fmt.Printf("拆分后的:%q\n",strings.FieldsFunc("a,b;c,d",f)) //拆分后的:["a,b" "c,d"]

HasPrefix 和 HasSuffix

  • func HasPrefix(s, prefix string) bool

  • func HasSuffix(s, suffix string) bool

    //HasPrefix 和 HasSuffix 判断字符串是否是以某个子串作为开头或者结尾。
    fmt.Println(strings.HasPrefix("Gopher", "Go")) // true
    fmt.Println(strings.HasSuffix("Amigo", "go")) // true

Index,IndexAny,IndexByte,IndexFunc,IndexRune**

  • func Index(s, substr string) int
  • func IndexAny(s, chars string) int
  • func IndexByte(s string, c byte) int
  • func IndexFunc(s string, f func(rune) bool) int
  • func IndexRune(s string, r rune) int

Index,IndexAny,IndexByte,IndexFunc,IndexRune都是返回满足条件的第一个位置,如果没有满足条件的数据,返回-1。

//Index,IndexAny,IndexByte,IndexFunc,IndexRune都是返回满足条件的第一个位置,如果没有满足条件的数据,返回-1。
fmt.Println(strings.Index("hello","e")) //1
fmt.Println(strings.Index("hello","m")) //-1

// 子串中的任意字符在源串出现的位置
fmt.Println(strings.IndexAny("yachicken", "ca")) // 1
fmt.Println(strings.IndexAny("crwth", "aeiouy")) // -1

// IndexByte,字符在字符串中出现的位置
fmt.Println(strings.IndexByte("golang", 'g')) // 0
fmt.Println(strings.IndexByte("gophers", 'h')) // 3
fmt.Println(strings.IndexByte("golang", 'x')) // -1
// IndexFunc 满足条件的作为筛选条件
f1 := func(c rune) bool {
return unicode.Is(unicode.Han, c)
}
fmt.Println(strings.IndexFunc("Hello, 世界", f1)) // 7
fmt.Println(strings.IndexFunc("Hello, world", f1)) // -1
// 某个字符在源串中的位置
fmt.Println(strings.IndexRune("chicken", 'k')) // 4
fmt.Println(strings.IndexRune("chicken", 'd')) // -1

LastIndex,LastIndexAny,LastIndexByte和LastIndexFunc

  • func LastIndex(s, substr string) int

  • func LastIndexAny(s, chars string) int

  • func LastIndexByte(s string, c byte) int

  • func LastIndexFunc(s string, f func(rune) bool) int

    ​ //LastIndex,LastIndexAny,LastIndexByte,LastIndexFunc和Index,IndexAny,IndexByte,IndexFunc,IndexRune用法保持一致,从右往前计数。 fmt.Println(strings.LastIndex("Hello","l" )) //3

Map(修改字符串)

  • func Map(mapping func(rune) rune, s string) string

对字符串s中每一个字符执行map函数中的操作进行修改。

package main
 
import (
   "fmt";
   "strings"
)
 
func main() {
   modified:= func(r rune) rune {
       if r == 'e' {
           return '@'
      }
       return r
  }
 
   input:= "Hello, Welcome to GeeksforGeeks"
   fmt.Println(input)
 
   // using the function
   result:= strings.Map(modified, input)
   fmt.Println(result)
}
输出
Hello, Welcome to GeeksforGeeks
H@llo, W@lcom@ to G@@ksforG@@ks

Replace和ReplaceAll

  • func Replace(s, old, new string, n int) string
  • func ReplaceAll(s, old, new string) string
    
    //使用new来替换old,替换的次数为n。如果n为负数,则替换所有的满足条件的子串。
    fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) // oinky oinkky oink
    fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))// moo moo moo
​
    //ReplaceAll使用new替换所有的old,相当于使用Replace时n<0。
    fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo"))// moo moo moo
​

ReplaceAll使用new替换所有的old,相当于使用Replace时n<0。

Repeat

  • func Repeat(s string, count int) string

    //Repeat func Repeat(s string, count int) string重复一下s,count是重复的次数,不能传负数。 fmt.Println("ba" + strings.Repeat("na", 2))//banana

Split,SplitN,SplitAfter和SplitAfterN

  • func Split(s, sep string) []string

  • func SplitAfter(s, sep string) []string

  • func SplitAfterN(s, sep string, n int) []string

  • func SplitN(s, sep string, n int) []string

    fmt.Printf("%q\n", strings.Split("a,b,c", ",")) // ["a","b","c"] fmt.Printf("%q\n", strings.Split(" xyz ", "")) // [" " "x" "y" "z" " "] // SplitN 定义返回之后的切片中包含的长度,最后一部分是未被处理的。 fmt.Printf("%q\n", strings.SplitN("a,b,c", ",", 2)) // ["a", "b,c"] z := strings.SplitN("a,b,c", ",", 0) fmt.Printf("%q (nil = %v)\n", z, z == nil) // [] (nil = true) ​ // 使用sep分割,分割出来的字符串中包含sep,可以限定分割之后返回的长度。 fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 2)) // ["a,", "b,c"] ​ // 完全分割 fmt.Printf("%q\n", strings.SplitAfter("a,b,c", ",")) // ["a,","b,", "c"]

Join(生成字符串)

  • func Join(elems []string, sep string) string

使用某个sep,连接字符串。

//Join 使用某个sep,连接字符串。
s:=[]string{"h","l","l","e","o"}
fmt.Println(strings.Join(s,"-")) //h-l-l-e-o

strings.Builder(生成字符串)

strings.Builder使用Write方法来高效的构建字符串。它最小化了内存拷贝,耗费零内存,不要拷贝非零的Builder。

var b1 strings.Builder
for i:=3;i>=1;i--{
fmt.Fprintf(&b1,"%d...",i)
}
b1.WriteString("点火")
fmt.Println(b1.String())

输出结果:

3...2...1...ignition

strings.Builder作为字符串拼接的利器,建议加大使用力度。
func (b *Builder) Cap() int // 容量,涉及批量内存分配机制
func (b *Builder) Grow(n int) // 手动分配内存数量
func (b *Builder) Len() int // 当前builder中含有的所有字符长度
func (b *Builder) Reset() // 清空builder
func (b *Builder) String() string // 转化为字符串输出
func (b *Builder) Write(p []byte) (int, error) // 往builder写入数据
func (b *Builder) WriteByte(c byte) error // 往builder写入数据
func (b *Builder) WriteRune(r rune) (int, error) // 往builder写入数据
func (b *Builder) WriteString(s string) (int, error) // 往builder写入数据`

strings.Reader(读取字符串)

Reader通过读取字符串的方式,实现了接口io.Reader, io.ReaderAt, io.Seeker, io.WriterTo, io.ByteScanner和io.RuneScanner。零值Reader操作起来就像操作空字符串的io.Reader一样。

func NewReader(s string) *Reader // 初始化reader实例,在使用很多函数的时候需要传入string字符串 , 但是函数参数类型是io.Reader , 这时候就需要将string转换为Reader类型
func (r *Reader) Len() int // 未读字符长度
func (r *Reader) Read(b []byte) (n int, err error)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
func (r *Reader) ReadByte() (byte, error)
func (r *Reader) ReadRune() (ch rune, size int, err error)
func (r *Reader) Reset(s string) // 重置以从s中读
func (r *Reader) Seek(offset int64, whence int) (int64, error) // Seek implements the io.Seeker interface.
func (r *Reader) Size() int64 // 字符串的原始长度
func (r *Reader) UnreadByte() error
func (r *Reader) UnreadRune() error
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) // WriteTo implements the io.WriterTo interface.

Len,Size,Read

Len作用: 返回未读的字符串长度。 Size的作用:返回字符串的长度。 Read的作用: 读取字符串信息,读取之后会改变Len的返回值

r := strings.NewReader("abcdefghijklmn")
fmt.Println(r.Len())  // 输出14 初始时,未读长度等于字符串长度
var buf []byte
buf = make([]byte, 5)
readLen, err := r.Read(buf)
fmt.Println("读取到的长度:", readLen) //读取到的长度5
if err != nil { fmt.Println("错误:", err)}
fmt.Println(buf)      //adcde
fmt.Println(r.Len())    //9 读取到了5个 剩余未读是14-5
fmt.Println(r.Size())    //14 字符串的长度

ReadAt

  • func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)

读取偏移off字节后的剩余信息到b中,ReadAt函数不会影响Len的数值。

r := strings.NewReader("abcdefghijklmn")
var bufAt, buf []byte
buf = make([]byte, 5)
r.Read(buf)
fmt.Println("剩余未读的长度", r.Len())  //剩余未读的长度 9
fmt.Println("已读取的内容", string(buf)) //已读取的内容 abcde
bufAt = make([]byte, 256)
r.ReadAt(bufAt, 5)
fmt.Println(string(bufAt))       //fghijklmn
//测试下是否影响Len和Read方法
fmt.Println("剩余未读的长度", r.Len())  //剩余未读的长度 9
fmt.Println("已读取的内容", string(buf)) //已读取的内容 abcde

ReadByte,UnreadByte

  • func (r *Reader) ReadByte() (byte, error)
  • func (r *Reader) UnreadByte() error

ReadByte从当前已读取位置继续读取一个字节。 UnreadByte将当前已读取位置回退一位,当前位置的字节标记成未读取字节。 ReadByte和UnreadByte会改变reader对象的长度。

r := strings.NewReader("abcdefghijklmn")
//读取一个字节
b, _ := r.ReadByte()
fmt.Println(string(b))   // a
//int(r.Size()) - r.Len() 已读取字节数
fmt.Println(int(r.Size()) - r.Len()) // 1
//读取一个字节
b, _ = r.ReadByte()
fmt.Println(string(b))   // b
fmt.Println(int(r.Size()) - r.Len()) // 2
//回退一个字节
r.UnreadByte()
fmt.Println(int(r.Size()) - r.Len()) // 1
//读取一个字节
b, _ = r.ReadByte()
fmt.Println(string(b))

Seek

  • func (r *Reader) Seek(offset int64, whence int) (int64, error)

ReadAt方法并不会改变Len()的值,Seek的移位操作可以改变。offset是偏移的位置,whence是偏移起始位置,支持三种位置:io.SeekStart起始位,io.SeekCurrent当前位,io.SeekEnd末位。 offset可以是负数,当时偏移起始位与offset相加得到的值不能小于0或者大于size()的长度。

r := strings.NewReader("abcdefghijklmn")
var buf []byte
buf = make([]byte, 5)
r.Read(buf)
fmt.Println(string(buf), r.Len()) //adcde 9
buf = make([]byte, 5)
r.Seek(-2, io.SeekCurrent) //从当前位置向前偏移两位 (5-2)
r.Read(buf)
fmt.Println(string(buf), r.Len()) //defgh 6
buf = make([]byte, 5)
r.Seek(-3, io.SeekEnd) //设置当前位置是末尾前移三位
r.Read(buf)
fmt.Println(string(buf), r.Len()) //lmn 0
buf = make([]byte, 5)
r.Seek(3, io.SeekStart) //设置当前位置是起始位后移三位
r.Read(buf)
fmt.Println(string(buf), r.Len()) //defgh 6

strconv包

string与int类型转换

// Atoi 该函数用于将字符串类型的整数转换为int类型
strInt := "100"
num,err:= strconv.Atoi(strInt)
if err!=nil{
fmt.Println("转换失败")
}
fmt.Printf("type:%T\nvalue:%#v",num,num)

//Itoa 该函数用于将int类型数据数据转换为对应的字符串表现形式
num:=100
strInt:=strconv.Itoa(num)
fmt.Printf("type:%T\nvalue:%#v",strInt,strInt)

Parse系列

Parse类函数用于转换字符串为给定类型的值:ParseBool()ParseFloat()ParseInt()ParseUint()

ParseBool()

func ParseBool(str string) (value bool, err error)

//返回字符串表示的bool值。它只接受1、0、t、f、T、F、true、false、True、False、TRUE、FALSE;否则返回错误。
result, _ := strconv.ParseBool("1")
fmt.Printf("type:%T\n value:%#v\n", result, result)
// type:bool
// value:true

ParseInt()

func ParseInt(s string, base int, bitSize int) (i int64, err error)

返回字符串表示的整数值,接受正负号。

   base指定进制(2到36),如果base为0,则会从字符串前置判断,”0x”是16进制,”0”是8进制,否则是10进制;

   bitSize指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64;

   返回的err*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange。

// 返回10 , 十进制, 结果是int64
   result, _ := strconv.ParseInt("127", 0, 64)
   fmt.Printf("type:%T\n value:%#v\n", result, result)
   // type:int64
   // value:127

ParseUnit()

func ParseUint(s string, base int, bitSize int) (n uint64, err error)

ParseUint类似ParseInt但不接受正负号,用于无符号整型。

ParseFloat()

func ParseFloat(s string, bitSize int) (f float64, err error)

解析一个表示浮点数的字符串并返回其值。

如果s合乎语法规则,函数会返回最为接近s表示值的一个浮点数(使用IEEE754规范舍入)。

bitSize指定了期望的接收类型,32是float32(返回值可以不改变精确值的赋值给float32),64是float64;

返回值err是*NumErr类型的,语法有误的,err.Error=ErrSyntax;结果超出表示范围的,返回值f为±Inf,err.Error= ErrRange。

result, _ := strconv.ParseFloat("3.1415", 64)
   fmt.Printf("type:%T\n value:%#v\n", result, result)
   // type:float64
   // value:3.1415

Format系列

Format系列函数实现了将给定类型数据格式化为string类型数据的功能。

FormatBool()

func FormatBool(b bool) string

根据b的值返回truefalse

result := strconv.FormatBool(true)
fmt.Printf("type:%T\n value:%#v\n",result,result)
// type:string
// value:"true"

FormatInt()

func FormatInt(i int64, base int) string
返回i的base进制的字符串表示。base必须在2到36之间,结果中会使用小写字母'a'到'z'表示大于10的数字。

result := strconv.FormatInt(127, 2) // 二进制的字符串
   fmt.Printf("type:%T\n value:%#v\n", result, result)
   // type:string
   // value:"1111111"

FormatUint()

func FormatUint(i uint64, base int) string
同上,但是不支持负数。 是FormatInt()的无符号整数版本。

FormatFloat()

func FormatFloat(f float64, fmt byte, prec, bitSize int) string

函数将浮点数表示为字符串并返回。

bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。

fmt表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)。

prec控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。

result := strconv.FormatFloat(3.1415, 'e', 2, 32) // 注意,第二参数为字符,并非字符串。单引号
   fmt.Printf("type:%T\n value:%#v\n", result, result)
   // type:string
   // value:"3.14e+00"

Unicode包

1 概述

Go语言的字符串是使用 UTF-8 编码的。UTF-8 是 Unicode 的实现方式之一。本文内容包括:UTF-8 和 Unicode 的关系,Go语言提供的 unicode 包和 unicode/utf8 包的使用。

下面话不多说了,来一起看看详细的介绍吧

2 UTF-8 和 Unicode 的关系

Unicode一种字符集,是国际标谁化组织(ISO)设计的一个包括了地球上所有文化、所有字母和符号 的编码。他们叫它 Universal Multiple-Octet Coded Character Set,简称 UCS,也就是 Unicode。Unicode 为每一个 字符 分配一个唯一的 码点(Code Point),就是一个唯一的值。例如 康 的码点就是 24247,十六进制为 5eb7。

Unicode 字符集仅仅定义了字符与码点的对应关系,但是并没有定义该如何编码(存储)这个码值,这就导致了很多问题。例如由于字符的码值不同,导致所需要的存储空间是不一致的,计算机不能确定接下来的字符是占用几个字节。还有就是如果采用固定的长度假设都是4个字节来存储码点值,那么会导致空间的额外浪费,因为 ascii 码字符其实仅仅需要一个字节的空间。

UTF-8 就是解决如何为 Unicode 编码而设计的一种编码规则。可以说 UTF-8 是 Unicode 的实现方式之一。其特点是一种变长编码,使用1到4个字节表示一个字符,根据不同的符号而变化长度。UTF-8 的编码规则有二:

  • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于ASCII码字符,UTF-8 编码和 ASCII 码是相同的。
  • 对于 n 字节的符号(n > 1,2到4),第一个字节的前n位都设为1,第n + 1 位设为 0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

以下是编码规则:

`Unicode    | UTF-8``---------------------------------------------------------``0000 0000-0000 007F | 0xxxxxxx``0000 0080-0000 07FF | 110xxxxx 10xxxxxx``0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx``0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx``---------------------------------------------------------`

Go语言中,对于 Unicode 和 UTF-8 使用了 unicode 和 unicode/utf8 包来实现,下面是阅读 API 的总结和说明。

3 Unicode 包

Go语言中,提供了 Unicode 包,处理与 Unicode 相关的操作,整理如下:

Is(rangeTab *RangeTable, r rune) bool

检测 rune r 是否在 rangeTable 指定的字符范围内。

rangeTable 一个 Unicode 码值集合,通常使用 unicode 包中定义的集合。

判断字符是否出现在汉字集合中:

unicode.Is(unicode.Scripts["Han"], 'k')
// 返回 false
unicode.Is(unicode.Scripts["Han"], '康')
// 返回 true

In(r rune, ranges …*RangeTable) bool

检测 rune r 是否在多个 rangeTable 指定的字符范围内。

rangeTable 一个 Unicode 码值集合,通常使用 unicode 包中定义的集合。

unicode.In('康', unicode.Scripts["Han"], unicode.Scripts["Latin"])
// 返回 true
unicode.In('k', unicode.Scripts["Han"], unicode.Scripts["Latin"])
// 返回 true

IsDigit(r rune) bool

检测字符 rune r 是否是十进制数字字符。

unicode.IsDigit('9')
// 返回 true
unicode.IsDigit('k')
// 返回 false

IsNumber(r rune) bool

检测字符 rune r 是否是 Unicode 数字字符。

IsLetter(r rune) bool

检测一个字符 rune r 是否是字母

unicode.IsLetter('9')
// 返回 false
unicode.IsLetter('k')
// 返回 true

IsGraphic(r rune) bool

一个字符 rune r 是否是 unicode 图形字符。图形字符包括字母、标记、数字、符号、标点、空白。

unicode.IsGraphic('9')
// 返回 true
unicode.IsGraphic(',')
// 返回 true

IsControl(r rune) bool

检测一个字符 rune r 是否是 unicode 控制字符。

IsMark(r rune) bool

检测一个字符 rune r 是否是标记字符。

IsPrint(r rune) bool

检测一个字符 rune r 是否是的可打印字符,基本与图形字符一致,除ASCII空白字符U+0020。

IsPunct(r rune) bool

检测一个字符 rune r 是否是 unicode标点字符。

unicode.IsPunct('9')
// 返回 false
unicode.IsPunct(',')
// 返回 true

IsSymbol(r rune) bool

检测一个字符 rune r 是否是 unicode 符号字符。

IsLower(r rune) bool

检测一个字符 rune r 是否是小写字母。

unicode.IsLower('h')
// 返回 true
unicode.IsLower('H')
// 返回 false

IsUpper(r rune) bool

检测一个字符 rune r 是否是大写字母。

unicode.IsUpper('h')
// 返回 false
unicode.IsUpper('H')
// 返回 true

IsTitle(r rune) bool

检测一个字符 rune r 是否是Title字符。大部分字符的 Title 格式就是其大写格式,少数字符的 Title 格式是特殊字符,例如 ᾏᾟᾯ。

unicode.IsTitle('ᾯ')
// 返回 true
unicode.IsTitle('h')
// 返回 false
unicode.IsTitle('H')
// 返回 true

To(_case int, r rune) rune

将字符 rune r 转换为指定的格式,格式_case支持:unicode.UpperCase、unicode.LowerCase、unicode.TitleCase

unicode.To(unicode.UpperCase, 'h')
// 返回 H

ToLower(r rune) rune

将字符 rune r 转换为小写。

unicode.ToLower('H')
// 返回 h

ToUpper(r rune) rune

将字符 rune r 转换为大写。

unicode.ToUpper('h')
// 返回 H

ToTitle(r rune) rune

将字符 rune r 转换为 Title 字符。

unicode.ToTitle('h')
// 返回 H

unicode/utf8 包

DecodeLastRune(p []byte) (r rune, size int)

解码 []byte p 中最后一个 UTF-8 编码序列,返回该码值和长度。

utf8.DecodeLastRune([]byte("小韩说课"))
// 返回 35838 3
// 35838 就是课的 unicode 码值

DecodeLastRuneInString(s string) (r rune, size int)

解码 string s 中最后一个 UTF-8 编码序列,返回该码值和长度。

utf8.DecodeLastRuneInString("小韩说课")
// 返回 35838 3
// 35838 就是课的 unicode 码值

DecodeRune(p []byte) (r rune, size int)

解码 []byte p 中第一个 UTF-8 编码序列,返回该码值和长度。

utf8.DecodeRune([]byte("小韩说课"))
// 返回 23567 3
// 23567 就是 小 的 unicode 码值

DecodeRuneInString(s string) (r rune, size int)

解码 string s 中第一个 UTF-8 编码序列,返回该码值和长度。

utf8.DecodeRuneInString("小韩说课")
// 返回 23567 3
// 23567 就是 小 的 unicode 码值

EncodeRune(p []byte, r rune) int

将 rune r 的 UTF-8 编码序列写入 []byte p,并返回写入的字节数。p 满足足够的长度。

buf := make([]byte, 3)
n := utf8.EncodeRune(buf, '康')
fmt.Println(buf, n)
// 输出 [229 186 183] 3

FullRune(p []byte) bool

检测 []byte p 是否包含一个完整 UTF-8 编码。

buf := []byte{229, 186, 183} // 康
utf8.FullRune(buf)
// 返回 true
utf8.FullRune(buf[:2])
// 返回 false

FullRuneInString(s string) bool

检测 string s 是否包含一个完整 UTF-8 编码。

buf := "康" // 康
utf8.FullRuneInString(buf)
// 返回 true
utf8.FullRuneInString(buf[:2])
// 返回 false

RuneCount(p []byte) int

返回 []byte p 中的 UTF-8 编码的码值的个数。

buf := []byte("小韩说课")
len(buf)
// 返回 12
utf8.RuneCount(buf)
// 返回 4

RuneCountInString(s string) (n int)

返回 string s 中的 UTF-8 编码的码值的个数。

buf := "小韩说课"
len(buf)
// 返回 12
utf8.RuneCountInString(buf)
// 返回 4

RuneLen(r rune) int

返回 rune r 编码后的字节数。

utf8.RuneLen('康')
// 返回 3
utf8.RuneLen('H')
// 返回 1

RuneStart(b byte) bool

检测字节 byte b 是否可以作为某个 rune 编码的第一个字节。

buf := "小韩说课"
utf8.RuneStart(buf[0])
// 返回 true
utf8.RuneStart(buf[1])
// 返回 false
utf8.RuneStart(buf[3])
// 返回 true

Valid(p []byte) bool

检测切片 []byte p 是否包含完整且合法的 UTF-8 编码序列。

valid := []byte("小韩说课")
invalid := []byte{0xff, 0xfe, 0xfd}
utf8.Valid(valid)
// 返回 true
utf8.Valid(invalid)
// 返回 false

ValidRune(r rune) bool

检测字符 rune r 是否包含完整且合法的 UTF-8 编码序列。

valid := 'a'
invalid := rune(0xfffffff)
fmt.Println(utf8.ValidRune(valid))
// 返回 true
fmt.Println(utf8.ValidRune(invalid))
// 返回 false

ValidString(s string) bool

检测字符串 string s 是否包含完整且合法的 UTF-8 编码序列。

valid := "小韩说课"
invalid := string([]byte{0xff, 0xfe, 0xfd})
fmt.Println(utf8.ValidString(valid))
// 返回 true
fmt.Println(utf8.ValidString(invalid))
// 返回 false

bytes包

聚合类型

数组、结构体。这类不能包含自己,但是可以包含自己的指针类型

数组

import "fmt"

/*
长度固定
索引访问
长度也是类型的一部分
...定义数组,根据初始化数据,自动识别数组的长度。可通过索引,给制定位置赋值,未指定的默认是元素类型的零值
值类型
for index,value := range XX 遍历
如果数组元素是可比较的,则数组也是课比较的 == !=

*/
func main() {
//定义
// 指定长度。而且长度必须是常量表达式,保证编译的时候就可以确定
// 长度也是数组类型的一部分 . 所以数组赋值的时候要注意,一定要类型完全一样才可以,即元素类型和数组长度必须保证一样
var a [3]int
//元素初始值为元素类型的零值
fmt.Println(a)
fmt.Println(a[0])
fmt.Println(len(a))
   
//遍历:输出索引和元素
for i,v:=range a{
fmt.Printf("%d %d\n",i,v)
}
//遍历:仅输出元素
for _,v:=range a {
fmt.Printf("%d\n",v)
}

// ... 定义不指定长度的数组。数组长度由初始化数组的元素个数决定
q:=[...]int{1,2,3,4}
fmt.Println(len(q))  //4
//...指定位置初始换元素的值,未指定的则是元素类型的零值
r:=[...]int{99:-1} //索引99对应的值是-1 其他则全是0
fmt.Println(r)

//两个数组比较: 一个数组里的元素是可以比较的,这个数组就可以比较。 == !=
a:=[2]int{1,2}
b:=[...]int{1,2}
c:=[2]int{1,3}
fmt.Println(ab,ac,b==c) //true false false
   
   //值传递
   a := [2]int{1,2}
b:=a
fmt.Printf("a:%#v,b:%#v\n",a,b) //a:[2]int{1, 2},b:[2]int{1, 2}
b[0]=33
fmt.Printf("a:%v,b:%v\n",a,b) //a:[1 2],b:[33 2]
//值传递:引用变量a,只是将其赋值一份,所以修改b,不会影响a。即只是对原变量的值进行了引用,引用后的变量已经不再影响原变量。
// 传递数组指针,就会反映到a上
c:=&a
c[0]=44
fmt.Printf("a:%v,c:%v\n",a,*c) //a:[44 2],c:[44 2]

}

结构体

引用类型

基础

package main

import (
"fmt"
"strings"
)

func main() {
//1 定义Employee结构体 ,成员变量的顺序会影响结构体的同一性,即,成员变量一样且成员定义顺序一样才能保证类型一致
type Employee struct {
ID   int
Name string
}
//2 定义结构体变量
var bob Employee
//3 成员访问
// 3.1 直接通过 . 操作
bob.Name = "保罗"
bob.ID = 1
fmt.Printf("%#v\n", bob) //main.Employee{ID:1, Name:"保罗"}

// 按照成员顺序初始化
lili:=Employee{1,"丽丽"}
fmt.Println(lili)

//按照成员名字初始化
lili2:=Employee{ID:2,Name:"丽丽2"}
fmt.Println(lili2)

//3.2 获取成员变量的指针,通过指针访问
name := &bob.Name
*name = *name + "一号"
fmt.Printf("%#v\n", bob) //main.Employee{ID:1, Name:"保罗一号"}

//3.3 点号也可以用在结构体指针
var bob_ptr Employee = &bob
bob_ptr.Name = strings.Trim(bob_ptr.Name, "一号") + "二号" // bob_ptr.Name等价于(
bob_ptr).Name
fmt.Println(bob_ptr.Name)                              //保罗二号
//4 结构体的零值 有结构体成员的零值组成



}

空结构体

空结构体:strunct{} 没有任何成员变量 没有长度,不携带任何信息

成员变量限制

命名结构体s 不能包含他自己, 但是可以定义一个s的指针类型【聚合类型不可以包含他自己,对数组同样适用】

package main

import "fmt"

//案例:使用递归结构体 实现插入排序
type tree struct {  // invalid recursive type tree无效的递归类型树
value       int
left, right *tree
}
func main() {
a:=[]int{3,1,5,4,9}
fmt.Println(Sort(a))
}


//排序
func Sort(values []int) []int {
var root *tree
for _,v:=range  values{
root = add(root,v)
}
appendValues(values[:0],root)
return values
}
//将元素按照顺序追加到values里面,然后返回slice
func appendValues(values []int,t *tree) []int {
if t!=nil{
values = appendValues(values,t.left)
values = append(values,t.value)
values = appendValues(values,t.right)
}
return values
}
//遍历树的每个节点,跟value作对比
func add(t *tree,value int) *tree{
if t==nil{
//等价于返回 &tree{value:value}
t=new(tree)
t.value= value
return t
}
if value < t.value{
t.left = add(t.left,value)
}else {
t.right = add(t.right,value)
}
return t

}

结构体字面量

结构体的值 通过结构体字面量 来设置。也就是通过设置结构体的成员变量来设置

package main

import "fmt"

type Point struct {
	X int
	Y int
}
func main() {
	//1 结构体的值 通过结构体字面量 来设置。也就是通过设置结构体的成员变量来设置
	//方式1 按照成员定义的顺序设置值 可读性不好,在大型结构体会难
	p := Point{1, 3}
	fmt.Println(p)
	//方式2 常用 未指定的成员变量那么他的值就是该成员变量类型的零值
	P2 := Point{X: 1}
	fmt.Println(p2)
	
	
	//2 结构体通常都是通过指针的方式使用,可以通过下面两种方式创建、初始化一个结构体并获取它的地址
	pp:=&Point{X: 1,Y: 2} 
	//等价于 
	pp:=new(Point)
	*pp=Point{X: 1,Y: 2}

}
//3 结构体的值可以作为函数的参数或者函数的返回值。一般都是使用结构体指针的方式直接传给函数或者从函数返回
func setP(point *Point) *Point {
	point.X=2
	point.Y=4
	return point
}

结构体比较

1 如果结构体的所有成员都可以比较,那么这个结构体就可以比较 == !=
2 可比较的结构体类型都可以作为map的键类型

package main

import "fmt"

func main() {
//如果结构体的所有成员都可以比较,那么这个结构体就可以比较 == !=
type Point struct {
x,y int
}
p1:=Point{
x: 1,
y: 1,
}
p2:=Point{
x: 0,
y: 2,
}
fmt.Println(p1.y == p2.y && p1.x == p2.x) //false
fmt.Println(p1 == p2) //false
//可比较的结构体类型都可以作为map的键类型
point := make(map[Point]int)
point[Point{1,2}]++
}

结构体嵌套和匿名成员

package main

func main() {
// type Circle struct {
// //圆心坐标xy 半径radius
// X,Y,radius int
// }
// type Wheel struct {
// //圆心坐标xy 半径radius ,条辐的个数
// X,Y,Radius,Spokes int
// }
// var w Wheel
// w.X=8
// w.Y=8
// w.Radius=5
// w.Spokes=20
// Circle Wheel有共同的部分,就是圆心坐标


type Point struct {
X,Y int
}

//type Circle struct {
// Center Point
// Radius int
//}
//命名的结构体成员
//type Wheel struct{
// Circle Circle
// Spokes int
//}
//var w Wheel
//w.Circle.Center.X=8
//w.Circle.Center.Y=8
//w.Circle.Radius =5
//w.Spokes=20
//

// 定义不带名称的结构体成员,只需要指定类型即可,(匿名成员),这个结构体成员必须是一个命名类型或者指向命名类型的指针

type Circle struct {
Point  //匿名成员
Radius int
}
type Wheel struct{
Circle //匿名成员
Spokes int
}
var w Wheel
w.X=8 //等价于 w.Circle.Center.X=8
w.Y=8 //等价于 w.Circle.Center.Y=8
w.Radius=5 //等价于 w.Circle.Radius =5
w.Spokes=20




}

引用类型

引用类型包括:指针、slice、map、函数、通道

指针

package main
import "fmt"
func main()  {
	/*
	1 变量x存储值,变量x都有地址,指针的值是一个变量的地址(&x)
	2 指针类型零值:nil ,当一个指针指向变量时就 !=nil
	3 指针比较  当且仅当两个指针指向同一个变量或者两个都是nil的时候 才相等. 当一个指针指向变量时!=nil
	4 传递一个指针参数给函数,函数可以修改指针指向的变量的值
	5每次使用变量的地址或者复制一个指针,就创建了一个新的别名或者方式标记同一个变量
	*/
	//定义:x变量  p是整形指针(*int),指向的是x变量地址,用*p表示x变量
	x := 1  
	p := &x     //p是整形指针,指向x。 &x是x的地址
	fmt.Println(*p) // 1 *p获取变量的值
	*p = 2  //等于 x = 2
	fmt.Println(x) // 2

	//指针比较  当且仅当两个指针指向同一个变量或者两个都是nil的时候 才相等. 当一个指针指向变量时!=nil
	var a,b int
	fmt.Println(&a == &a, &a == &b, &a == nil)  //true false false

	// 传递一个指针参数给函数,函数可以修改指针指向的变量的值
	// 每次使用变量的地址或者复制一个指针,就创建了一个新的别名或者方式标记同一个变量
	v := 1
	increase(&v)   // v现在等于2 
	fmt.Println(increase(&v)) //3 (v现在等于3)
	
}
func increase(p *int) int{
	*p++
	return *p
}

切片

/*
1 slice : []T
2 元素类型相同   可变长
3 引用类型
4 定义:
 var name []T={}
 name := []T{}
 name := make([]T,len,cap)

5 底层数组:通过slice可以访问该数组的部分或全部元素,这个数组成为slice的底层数组

6 属性: 指针、容量、长度
指针: 指向数组的第一个可以被slice访问的元素
长度:slice中元素的个数
容量:从slice的起始元素 到 底层数组的最后一个元素间元素的个数, slice的长度永远小于等于容量

7 s[i:j]操作符会创建新的slice, 这个新的slice引用了序列s中从i到j-1位置的所有元素,s可以是数组、指向数组的指针、slice

8 获取元素:s[i]
9 同一个底层数组可以对应多个slice,这些slice可以引用数组的任何位置,这些slice之间的元素可以重叠,所以重叠部分的值会相互影响

10 零值:nil 值为nil的slice没有对应的底层数组, 长度和容量都为零(注意 也有非nil的slice长度和容量为零,比如:[]int{}或 make([]int,3)[3:])
*/

func main(){
months := [...]string{1:"January",2:"February",3:"March",4:"April",5:"May",6:"June",7:"July",8:"August",9:"September",10:"October",11:"November",12:"December"}
// s[i:j]操作符会创建新的slice, 这个新的slice引用了序列s中从i到j-1位置的所有元素,s可以是数组、指向数组的指针、slice
//Q2 summer 两个slice公用一个底层数组months,元素June是重叠的部分
Q2:=months[4:7]
summer:=months[6:9]
fmt.Println(Q2) //[April May June]
fmt.Println(summer) //[June July August]
//通过Q2改变重叠部分的值,summer相应的元素也会改变
Q2[2]="june"
Q2[2]="may"
fmt.Println(Q2)  //[April May may]
fmt.Println(summer)// [may July August]
fmt.Println(months) //[ January February March April May may July August September October November December] 因为slice包含指向底层数组的指针,所以底层数组相应的元素发生改变

//查看slcie 长度及容量 len() cap()
fmt.Printf("summer len:%d, summer cap: %d\n",len(summer),cap(summer)) //summer len:3, summer cap: 7

//slice引用长度不能超过被引用对象的容量cap(s),否则会异常
//fmt.Println(summer[:20]) //panic: runtime error: slice bounds out of range [:20] with capacity 7
//如果,slcie引用长度 超出被引用对象的长度,即len(s),则会在被引用对象的容量范围内扩展新的slice 注意 len(s) <= cap(s)
endlessSummer :=summer[:5]
fmt.Println(endlessSummer)


}

//--------------------------------------------------------

func main(){
//引用类型变量
//因为slice中包含了指向底层数组的指针,所以在将slice传递给一个函数,可以在函数内部修改底层数组的元素
a:=[]int{1,2,3,4,5}
reverse(a[:]) //生成新的slice
fmt.Println(a) //[5 4 3 2 1]
}
func reverse(s []int){
//实现[]int类型中元素的反转
for i,j:=0,len(s)-1;i<j;i,j =i +1,j-1{
s[i],s[j] = s[j],s[i]
}
}
//--------------------------------------------------------
func main() {
data:=[]string{"one","","three"}
//fmt.Printf("%q\n",nonempty(data)) //["one" "three"]
//fmt.Printf("%q\n",data) //["one" "three" "three"] 输入和输出的slice拥有相同的底层数组。底层数组的元素只是部分被修改
data=nonempty(data)
fmt.Println(data) // [one three]
}
//返回一个新的slice,并且元素都是非空字符串
func nonempty(strings []string)[]string{
//注意:输入和输出的slice拥有相同的底层数组
i:=0
for _,s:=range strings{
if s!=""{
strings[i]=s
i++
}
}
return  strings[:i]
}
//--------------------------------------------------------

func main(){
a:=[]int{1,2,3}
b:=[]int{1,2,3}
//slice不可以进行比较,不能使用 == 来判断 两个slice是否拥有相同的元素。 bytes.Equal可以比较两个字节slcie,其他的slice则需要 自己写函数比较
fmt.Println(equal(a,b)) //true
//slice 可以和nil作比较.
var c []int
fmt.Println(c==nil)//true

//零值:nil 值为nil的slice没有对应的底层数组, 长度和容量都为零(注意 也有非nil的slice长度和容量为零,比如:[]int{}或 make([]int,3)[3:])
//判断为空,使用len()来判断,因为s!=nil时,slice也有可能时空的

}

func equal(x,y []int) bool{
if len(x) !=len(y){
return false
}
for i:=range x {
if x[i]!=y[i] {
return false
}
}
return true
}

字典

package main

import (
"fmt"
)

/*
map[k]v
k 拥有相同的,可以通过操作符==来进行比较的数据类型,
v 拥有相同的数据类型
引用类型
声明:
1 var vaira map[KT]VT //只是声明 未初始化,对其添加元素,需要make分配内存
2 vaira := map[KT]VT{}
3 varia := make(map[KT]VT{})
*/

func main(){
//1 声明map的两种方式 方式1先声明在给初始值 方式2 声明一个带有初始值得map
ages:=make(map[string]int)
ages["赵六"]=13
ages["王五"]=14

//ages1:=map[string]int{
// "赵六":13,
// "王五":14,
//}

//2 空的map
emtymap:=map[string]int{}
fmt.Println(emtymap) //map[]

//3 访问元素,如果key不存在,则返回value元素类型的零值
fmt.Println(ages["赵六"]) //13

//4 删除元素,如果删除的元素不存在, delete是一个空操作,不会恐慌
ages1:=map[string]int{
"赵六":13,
"王五":14,
}
delete(ages1,"赵六")
fmt.Printf("%v\n",ages1) //map[王五:14]
delete(ages1,"张三")
//5 map元素不是一个变量,无法获取地址
//_=&ages1["赵六"]

//6 遍历map中的key
names:=make([]string,1,1)
for name :=range ages1{
names=append(names,name)
}
fmt.Println(names) //[ 王五]
//7 变量key value
for k,v:=range ages1{
fmt.Printf("%s:%v\n",k,v) //王五:14
}

//8 设置元素前,必须初始化map
//var ages3 map[string]int //此处只做了声明,并未分配内存
//ages3["丽丽"]=18 //panic: assignment to entry in nil map
   var ages3 map[string]int
ages3 = make(map[string]int) //分配内存
ages3["大朗"]=18
fmt.Println(ages3)

//9 判断一个元素是否在map中,
age,ok:=ages1["bob"]
if !ok{
fmt.Println("bob不在字典里,age:",age)//fmt.Println("bob不在字典里,age:",age)
}
if age,ok=ages1["bob"];!ok{
fmt.Println("bob不在字典里,age:",age)
}
//10 元素个数 len()
fmt.Println(len(ages1))


}


//11 map不可比较,唯一合法的比较就是和nil作比较。其他的比价需要自定义函数进行对比
func equal(x,y map[string]int) bool {
if len(x) != len(y){
return false
}
for k,xv := range x {
//注意:用!ok来区分 元素不存在 和 元素存在但值为零 的情况
if yv,ok:=y[k]; !ok || yv!=xv {
return false
}
}
return true

}


//--------------------------------------------------------

//12 slice是不可以直接比较的,所以,如果那么一个需求:一定要用slice作为key,怎么解决呢?
func main() {
var m = make(map[string]string)
a:=[]string{"a","b"}
m[k(a)]="我的key是一个slice"
fmt.Println(m) //map[["a" "b"]:我的key是一个slice]

}
func k(list []string) string {
// fmt.Sprintf将slice转换成字符串,使用%q来格式化slcie.方案适合任何不可直接比较的键类型
return fmt.Sprintf("%q",list)
}


//--------------------------------------------------------
//引用类型
func main() {
a:=map[string]string{
"张三":"男",
"丽丽":"女",
}

fmt.Println(test(a)) //map[丽丽:女 张三:人妖]
fmt.Println(a)  //map[丽丽:女 张三:人妖]
}
func test(ma map[string]string)  map[string]string {
ma["张三"]="人妖"
return ma
}

函数

基础

package main

import (
"fmt"
)

/*
1 定义
func name(形参列表) (结果列表){
函数体


1 go只有形参,没有关键字参数
2 按照形参列表传入实参,结果列表同理
3 return :用于函数返回结果列表,接收时返回结果列表是可以用 _ 忽略某个值
例如:
func test(a int, b string) (r1 int,r2 string){...} ==》调用 r1,r2 := test(1,"one")
func test(a , b string) (r1,r2 string){...} 简写 r1, _ := test("one","two")   _, r2 := test("one","two")
func test(a , b string) string{...}


2 函数的类型(函数签名):当两个函数拥有相同的形参列表和返回列表是,认为这两个函数的类型或签名是相同的。形参和返回值得名字不会影响到函数类型,采用简写也不影响。 例如test1 test2
3 实参是按值传递的,所以函数接受到的是实参的副本;修改函数的形参变量 并不会 影响到调用者提供的实参
如果传入的实参包含引用类型(指针,map,slice,函数或者channel),当使用形参变量是有可能会间接地修改实参变量
4 特殊的函数: 没有函数体。 这样的函数使用了go以外的语言实现
package math
func Sin(x float64) float64 //使用汇编语言实现
5 函数变量
函数变量也有类型,不同类型的函数变量不能复制
6 匿名函数
7 可变长参数 ...

*/
func main() {
r1,r2:=test(1,"one")
fmt.Printf("r1:%v,r2:%v\n",r1,r2)
_,r3:=test(1,"one")
fmt.Printf("r3:%v\n",r3)
//5 函数变量
a:=test
ar1,ar2:=a(2,"two")
fmt.Printf("ar1:%v,r2:%v\n",ar1,ar2) //ar1:2,r2:two
fmt.Printf("a type:%T, test1 type:%T\n",a,test1) // a type:func(int, string) (int, string), test1 type:func(string, string) (string, string)
//a = test1   //不同类型的函数变量不能复制

d:=test3(1,2,3,4,5)
fmt.Println(d) //[1 2 3 4 5]
//显示的生命一个slice 传给test3
values := []int{1,2,3}
e:=test3(values...)
fmt.Println(e) //[1 2 3]
}
//
func test(a int, b string) (r1 int,r2 string){
return a,b
}

func test1(a string, b string) (r1 string,r2 string){
return a,b
}

func test2(a , b string) (r1,r2 string){
return a,b
}
func test3 (values ...int) []int {
//values 是一个int类型的slice
return values

}

init函数

每个包可以包含任意多个 init 函数,这些函数都会在程序执行开始的时候被调用。所有被 编译器发现的 init 函数都会安排在 main 函数之前执行。

init 函数用在设置包、初始化变量 或者其他要在程序运行前优先完成的引导工作。

package main
/*
1 不能被其他函数调用,而是在main函数执行之前,自动被调用
2 init函数不能作为参数传入
3 不能有传入参数和返回值
*/
func init() {
fmt.Println("init1")
}

func init() {
fmt.Println("init2")
}
func main() {
fmt.Println("main")
}


输出
init1
init2
main

![](file://D:/%E8%B5%84%E6%96%99/%E7%AC%94%E8%AE%B0/GO/go.assets/1666766826931.png?lastModify=1695373172)

defer

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

package main

import "fmt"

func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}
输出
start
3
2
1

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

defer运行的时机

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:

![](file://D:/%E8%B5%84%E6%96%99/%E7%AC%94%E8%AE%B0/GO/go.assets/1666680699804.png?lastModify=1695373172)

//defer运行时机验证
package main

import "fmt"

func f1() int {
  x := 5
  defer func() {
     x++
  }()
  return x //x先赋值给返回变量,之后再返回。
}

func f2() (x int) {
  defer func() {
     x++
  }()
  return 5 //先5赋值给x,然后defer,x++,之后再返回6
}

func f3() (y int) {
  x := 5
  defer func() {
     x++
  }()
  return x //把5赋值给y defer并未对y进行操作,所以返回5
}
func f4() (x int) {
  defer func(x int) {
     x++
  }(x)
  return 5 //defer 对应的内部函数申请了局部变量,并未对外层变量进行修改
}
func main() {
  fmt.Println(f1())
  fmt.Println(f2())
  fmt.Println(f3())
  fmt.Println(f4())
}
输出
5
6
5
5

panic/recover

Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover模式来处理错误。 panic可以在任何地方引发,但recover只有在defer调用的函数中有效。 首先来看一个例子:

func funcA() {
   fmt.Println("func A")
}

func funcB() {
   panic("panic in B")
}

func funcC() {
   fmt.Println("func C")
}
func main() {
   funcA()
   funcB()
   funcC()
}

输出:
func A
panic: panic in B

goroutine 1 [running]:
main.funcB(...)
       .../code/func/main.go:12
main.main()
       .../code/func/main.go:20 +0x98

程序运行期间funcB中引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来,继续往后执行。

func funcA() {
   fmt.Println("func A")
}

func funcB() {
   defer func() {
       err := recover()
       //如果程序出出现了panic错误,可以通过recover恢复过来
       if err != nil {
           fmt.Println("recover in B")
      }
  }()
   panic("panic in B")
}

func funcC() {
   fmt.Println("func C")
}
func main() {
   funcA()
   funcB()
   funcC()
}
输出
func A
recover in B
func C

注意:

  1. recover()必须搭配defer使用。

  2. defer一定要在可能引发panic的语句之前定义。

    package main

    import (
    "fmt"
    )

    func ptest(a int) (r int, err error) {
    defer func() {
    p:=recover()
    if p !=nil{
    err=fmt.Errorf("出错了:%v",p)
    }
    }()
    //...如果隐藏
    for{
    if a >3{
    panic("大于三")
    }else{
    a+=1
    }
    }
    return r,err
    }

    func main() {
    _,err:=ptest(1)
    if err!=nil{
    fmt.Println(err)
    }
    for i:=0;i<5;i++ {
    fmt.Println(i)

    }

    }
    输出
    出错了:大于三
    0
    1
    2
    3
    4

new函数和make函数

https://www.jb51.net/jiaoben/285860bml.htm

Go语言中 new 和 make 是两个内置函数,主要用来创建并分配类型的内存。

在我们定义变量的时候,可能会觉得有点迷惑,不知道应该使用哪个函数来声明变量,

其实他们的规则很简单,new 只分配内存,而 make 只能用于 slice、map 和 channel 的初始化

new

new(T) 函数是一个分配内存的内置函数,为每个类型分配一片内存,并初始化为零值且返回其内存地址。

语法是 func new(Type) *Type

众所周知,一个已经存在的变量可以赋值给它的指针。

var p int
var v *int
v = &p
*v = 11
fmt.Println(*v)

那么如果它还不是一个变量呢?你可以直接赋值吗?

func main() {
	var v *int
	*v = 8
	fmt.Println(*v)
	// panic: runtime error: invalid memory address or nil pointer dereference
	// [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47df36]
	// goroutine 1 [running]:
	// main.main()
	// 	/tmp/sandbox1410772957/prog.go:9 +0x16
}

如何解决?可以通过 Go 提供 new 初始化地址来解决。

func main() {
	var v *int
	// v 是一个 int 类型的指针,v 的地址和 v 的值  0xc0000ba018 <nil>
	fmt.Println("v 是一个 int 类型的指针,v 的地址和 v 的值 ", &v, v)   
	// 分配给 v 一个指向的变量             
	v = new(int)    
	// v 是一个 int 类型的指针,v 的地址和 v 的值  0xc0000ba018 0xc000018030 0,此时已经分配给了 v 指针一个指向的变量,但是变量为零值                                                  
	fmt.Println("v 是一个 int 类型的指针,v 的地址, v 的值和 v 指向的变量的值 ", &v, v, *v) 
	*v = 8
	// v 是一个 int 类型的指针,v 的地址和 v 的值  0xc0000ba018 0xc000018030 8,此时又像这个变量中装填了一个值 8
	fmt.Println("v 是一个 int 类型的指针,v 的地址, v 的值和 v 指向的变量的值 ", &v, v, *v) 
	// 整个过程可以理解为给 v 指针指向了一个匿名变量
}

我们可以看到,初始化一个值为nil的指针变量并不是直接赋值。通过new返回一个值为0xc000018030 的指针指向新赋值的int类型,零值为其值。

此外,重要的是要注意,不同指针类型的零值是不同的。具体的你可以参考这篇文章。或者你可以浏览下面的代码。

type Name struct {
    P string
}
var av *[5]int
var iv *int
var sv *string
var tv *Name
av = new([5]int)
fmt.Println(*av) //[0 0 0 0 0 0]
iv = new(int)
fmt.Println(*iv) // 0
sv = new(string) 
fmt.Println(*sv) //
tv = new(Name)
fmt.Println(*tv) //{}

上面介绍了处理普通类型new()后如何赋值,这里是处理复合类型(array、struct)后如何赋值。但是在这里,我认为原文的作者讲述有错误,因为对 slice,map 和 channel 来说,new 只能开辟

数组实例

func main() {
	// 声明一个数组指针
	var a *[5]int
	fmt.Printf("a: %p %#v \n", &a, a) //a: 0xc04200a180 [5]int{0, 0, 0, 0, 0}
	// 分配一个内存地址给 a(数组指针)指向
	a = new([5]int)
	fmt.Printf("a: %p %#v \n", &a, a) //av: 0xc000074018 &[5]int{0, 0, 0, 0, 0}
	// 修改这个数组中的值
	(*a)[1] = 8
	fmt.Printf("a: %p %#v \n", &a, a) //av: 0xc000006028 &[5]int{0, 8, 0, 0, 0}
}

​```

结构体实例

type mystruct struct {
	name string
	age  int
}
func main() {
	var people *mystruct
	people = new(mystruct)
	people.name = "zhangsan"
	people.age = 11
	fmt.Printf("%v, %v", people.name, people.age) // zhangsan, 11
}
​```

make

make 专门用于创建 chan,map 和 slice 三种类型的内容分配,并且可以初始化它们。

make 的返回类型与其参数的类型相同,而不是指向它的指针,因为这三种数据类型本身就是引用类型。

其语法为:

func make(t Type, size ...IntegerType) Type

可以看到第二个为变长参数,用于指定开辟内存的大小,比如

对 slice 而言,需要指定 cap 和 length(cap 表示容量,length 表示长度,即可以使用的大小),要求 cap 是比 length 大的。

至于为什么不使用 new 来为这三种分配内存呢?我们来做一个实验。

func main() {
	var s *[]int
	fmt.Printf("s 的地址是: %p, s 的值是 %p\n", &s, s) // s 的地址是: 0xc00000e028, s 的值是 0x0
	s = new([]int)
	fmt.Printf("s 的地址是: %p, s 的值是 %p\n", &s, s) // s 的地址是: 0xc00000e028, s 的值是 0xc00011a018
	(*s)[0] = 1
	fmt.Println("s 的地址是: %p, s 的值是 %p\n", &s, s) // panic: runtime error: index out of range [0] with length 0
}
}
​```

可以看到在为 slice 赋值的时候报错了 length 为 0,至于具体的原因,有知道的朋友可以在评论区留言。

因此常推荐使用 make 来进行这三种类型的创建。

slice 实例

func main() {
	// 第一个 size 是 length,第二个 size 是 cap
	a := make([]int, 5, 10)
	// a: 0xc00011a018 []int{0, 0, 0, 0, 0},cap: 10, length: 5 
	fmt.Printf("a: %p %#v,cap: %d, length: %d \n", &a, a, cap(a), len(a)) 
}```

map 实例

func main() {
	// 第一个 string 是 key,第二个 string 是 value
	mapInstance := make(map[string]string, 5)
	mapInstance["第一名"] = "张三"
	mapInstance["第二名"] = "李四"
	mapInstance["第三名"] = "王五"
	fmt.Println(mapInstance) // map[第一名:张三 第三名:王五 第二名:李四]
}
​```

通道实例

func countNum(temp int, ch chan int) {
	i := temp + 1
	ch <- i
	fmt.Println("已经将 i 发往通道 c 中")
}
func main() {
	ch := make(chan int)
	go countNum(1, ch)
	res := <-ch
	fmt.Println("已经从 ch 中获取 i 并保存在 res 中")
	fmt.Println("res 是", res)
}```

参考

The difference between golang new and make
Go语言make和new关键字的区别及实现原理

方法

方法是函数的一种,它实际上就是与某个数据类型关联在一起的函数,示例如下:

type myint int
func (i myint) add(another int ) myint{
	i = i+myint(another)
	return i
}

从声明上看方法与函数的区别:func后加了一个有圆括号包裹的接收者声明,这里是(i myint)
接收着声明分为两部分:
右边:表明这个方法与那个数据类型关联,这里是myint
左右:表明 这个类型的值在当期那方法中的标识符


值方法:接收者类型是非指针的数据类型
指针方法:接收者类型是是某个数据类型的指针类型

值方法和指针方法遵循如下规则:
1 接收者变量代表的值时机是源值的一个复制品。
	如果这个值不是指针类型:对接受者变量的复制一般不会影响到源值,唯一例外的是,如果接受者类型是某个引用类型或者它的别名类型,即使是值方法,也可以改变源值。
	如果是指针类型:指针值与其复制品指向的是同一个值,,所以在指针方法中可以改变源值


package main

import (
"fmt"
"image/color"
"math"
)
/*

3 nil是一个合法的接受者
4 通过结构体内嵌组成类型 ,可以调用内嵌类型的方法。注意:内嵌类型方法的参数必须符合声明时的要求
*/
func main() {
//1 值接受者方法的调动
p:=Point{1,2}
q:=Point{4,6}
fmt.Println(p.Distance(q)) // 5

//2 指针接收者方法的调用
r:=&Point{1,2}
r.ScaleBy(2)
fmt.Println(*r)  //{2 4}

//3 nil是一个合法的接受者
a:=IniList{1,&IniList{2,nil}}
fmt.Println(a.Sum()) //3

//4 通过结构体内嵌组成类型
var cp ColoredPoint
cp.Y=1
cp.X=2
red:=color.RGBA{255,0,0,255}
blue:=color.RGBA{0,0,255,255}
var p2 = ColoredPoint{Point{1,1},red}
var q2 = ColoredPoint{Point{5,4},blue}
//可以通过类型为ColoredPoint的接受者调用内嵌类型Point的方法,即使ColoredPoint中没有声明Distance。内嵌类型方法的参数必须符合声明时的要求.
fmt.Println(p2.Distance(q2.Point))  //5
//fmt.Println(p2.Distance(q2)) // cannot use q2 (type ColoredPoint) as type Point in argument to p2.Point.Distance

}

type Point struct {
X,Y float64
}
// p成为Distance方法的接收者 接受者命名规则:一般使用类型名称的首字母
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X,q.Y-p.Y)
}
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}

type IniList struct {
Value int
Tail *IniList
}
//nil是一个合法的接受者,尤其是当nil是类型中有意义的零值(如 map slice)
func (list *IniList)Sum()int {
if list==nil{ //空列表
return 0
}
return  list.Value+list.Tail.Sum()
}

//通过结构体内嵌组成类型 Point的方法也被纳入到ColoredPoint
type ColoredPoint struct {
Point
Color color.RGBA
}

接口

接口类型声明

//方式1 
type Reader interface {
    Read(p []byte) (n int, err error)
}


type Writer interface {
	Write(p []byte) (n int, err error)
}

//方式2 嵌入式接口, 避免了方式3中的反复声明
type ReadWriter interface {
    Reader
    Writer
}
// 方式3
type ReadWriter interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}


接口实现

1 如果一个类型实现了一个接口的所有方法,那么这个类型就实现了这个接口。 开发通常说一个具体的类型“是一个”特定的接口类型,这就代表这该具体类型实现了该接口

2 接口的赋值规则:仅当一个表达式实现了一个接口时,这个表达式才可以赋值给该接口

var w io.Writer
w = os.Stdout           //ok: *os.File 有Write方法
w = new(bytes.Buffer)   //ok: *bytes.Buffer 有Write方法
w = time.Second         //编译错误: time.Duration缺少Write方法

3 空接口 interface{}: 对其实现类型没有任何要求,所以可以吧任何值赋值给空接口类型。

var any interface{}
any = 1
any = true
any = "hello"
any = map[string]int{"one":1}
any = new(bytes.Buffer)

接口值

1 一个接口类型的值(接口值)有两个部分: 一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值

2 接口的零值 就是把动态类型和值都设置为nil。

3 一个接口值是否为nil取决于它的动态类型。动态类型为nil 则接口值为nil。可以用 w == nil w!=nil来检测接口值是否是nil。调用一个nil接口的任何方法都会导致崩溃

var w io.Writer       // 初始化,并将动态类型和值都设置为nil
w = os.Stdout         // 把*os.File类型的值赋给了w 与 io.Writer(os.Stdout) 等价。接口值的动态类型:*os.File 动态值设置为os.Stdout的副本
w = new(bytes.Buffer) // 接口值的动态类型:*bytes.Buffer 动态值 是一个指向新分配缓冲区的指针
w = nil               // 把动态类型和值都设置为nil。恢复到刚声明时的状态

接口值的比较

两个接口值动态类型一致,且动态值是可以比较的,接口值是可以用 == != 操作符来比较的。

如果两个接口动态类型一致且动态值相等,这两个接口值相等 接口值是可以比较的。所以它们 可以用作为map的键,也可以作为switch语句的操作数

注意:如果接口值动态类型一致,但对应的动态值是不可比较的(比如slice)会导致程序崩溃,如下


func main() {
	var x interface{} = []int{1, 2, 3}
	fmt.Println(x == x)
}
//panic: runtime error: comparing uncomparable type []int
go语言
可完全比较的: 基础类型和指针
完全不可以比较的:slice map 函数


从上面来看 ,仅在能确认接口值包含的动态值是可以比较时,才比较接口值。所以在处理错误或者调试是,能拿到接口值的动态类型很有帮助

var w io.Writer
fmt.Printf("%T\n", w) //
w = os.Stdout
fmt.Printf("%T\n", w) //os.File
w = new(bytes.Buffer)
fmt.Printf("%T\n", w) //
bytes.Buffer

类型断言

类型断言是作用在接口值上的操作。类型断言会检查操作数的动态类型是否满足指定的断言类型

x.(T) x是一个接口类型的表达式,T是一个类型(称为断言类型)

1 如果T是一个具体类型,类型断言会检查x的动态类型是否就是T。如果成功 类型断言的结果就是x的动态值,类型就是T;如果失败,程序异常

2 如果T是一个接口类型,如果成功,结果的类型为接口类型T,并且保留x接口值中的动态类型和动态值。其实就是实现了转换

3 如果那种断言类型,如果操作数是一个空接口值,断言都失败

//1 如果T是一个具体类型,类型断言会检查x的动态类型是否就是T。如果成功 类型断言的结果就是x的动态值,类型就是T;如果失败,程序异常
var w io.Writer
w = os.Stdout
f := w.(*os.File)      //成功 f==os.Stdout
c := w.(*bytes.Buffer) //崩溃 panic: interface conversion: io.Writer is *os.File, not *bytes.Buffer
//2 如果T是一个接口类型,类型断言
type ByteCounter interface {
	Read(p []byte) (n int, err error)
}

func main() {
	var w io.Writer
	w = os.Stdout
	rw := w.(io.ReadWriter) //成功  *os.File 有 Read 和 Write方法
	rw = w.(ByteCounter) //崩溃:ByteCounter does not implement io.ReadWriter (missing Write method)
	fmt.Println(rw)
}

断言

在 Go 语言中,断言(Assertion)是一种类型转换的方式,它用于将一个接口类型转换为其他类型。本文将详细介绍 Go 语言中断言的使用方法,包括类型断言和类型选择。

类型断言 在 Go 语言中,类型断言是将一个接口类型转换为其他类型的方式。类型断言的语法如下:

value, ok := interfaceValue.(interfaceType) 1 其中,interfaceValue 表示接口值,interfaceType 表示接口类型,value 表示类型转换后的值,ok 表示类型转换是否成功。

例如,我们可以使用类型断言将一个 interface{} 类型转换为 int 类型:

func main() {
var i interface{} = 10
if v, ok := i.(int); ok {
fmt.Printf("i is a int, value is %d\n", v)
} else {
fmt.Println("i is not a int")
}
}```

在上面的代码中,我们定义了一个 interface{} 变量 i,并将其赋值为 10。然后,我们使用类型断言将 i 转换为 int 类型,并判断类型转换是否成功。如果类型转换成功,我们就可以使用转换后的值 v。

类型选择 在 Go 语言中,类型选择(Type Switch)是一种多分支的语句,用于根据接口值的类型进行不同的处理。类型选择的语法如下:

switch interfaceValue.(type) {
case type1:
// 处理 type1 类型
case type2:
// 处理 type2 类型
...
default:
// 处理其他类型
}``` 

其中,interfaceValue 表示接口值,type1 表示第一个类型,type2 表示第二个类型,default 表示其他类型。在每个分支中,我们可以根据类型进行不同的处理。

例如,我们可以使用类型选择来处理不同类型的值:

func main() {
values := []interface{}{10, "hello", true}
for _, v := range values {
switch v.(type) {
case int:
fmt.Printf("%d is a int\n", v)
case string:
fmt.Printf("%s is a string\n", v)
case bool:
fmt.Printf("%t is a bool\n", v)
default:
fmt.Println("unknown type")
}
}
}```

在上面的代码中,我们定义了一个 []interface{} 类型的变量 values,包含了不同类型的值。然后,我们使用类型选择来处理每个值的类型,并输出相应的信息。

总结 本文详细介绍了 Go 语言中断言的使用方法,包括类型断言和类型选择。断言是 Go 语言中非常重要的特性,可以帮助我们将接口类型转换为其他类型,并根据接口值的类型进行不同的处理。掌握断言的使用方法可以提高代码的灵活性和可维护性。

多态案例

// Sample program to show how polymorphic behavior with interfaces.
package main

import (
"fmt"
)

type notifier interface {
notify()
}

type user struct {
name  string
email string
}

func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}

type admin struct {
name  string
email string
}

func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}

func main() {

bill := user{"Bill", "bill@email.com"}
sendNotification(&bill)


lisa := admin{"Lisa", "lisa@email.com"}
sendNotification(&lisa)
}

func sendNotification(n notifier) {
n.notify()
}

嵌入类型

嵌入类 型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。 通过嵌入类型,与内部类型相关的标识符(解释:标识符就是程序实体的名字,比如类型名,方法名,,属性名,函数名。。。)会提升到外部类型上。这些被提升的标识符就像直 接声明在外部类型里的标识符一样,也是外部类型的一部分。这样外部类型就组合了内部类型包 含的所有属性,并且可以添加新的字段和方法。外部类型也可以通过声明与内部类型标识符同名 的标识符来覆盖内部标识符的字段或者方法。

// 这个示例程序展示如何将一个类型嵌入另一个类型,以及
// 内部类型和外部类型之间的关系
package main
import (
"fmt"
)

// user 在程序里定义一个用户类型
type user struct {
   name string
   email string
}

// notify 实现了一个可以通过 user 类型值的指针
// 调用的方法
func (u *user) notify() {
   fmt.Printf("Sending user email to %s<%s>\n",
       u.name,
       u.email)
}

// admin 代表一个拥有权限的管理员用户
type admin struct {
   user // 嵌入类型
   level string
}

// main 是应用程序的入口
func main() {
   // 创建一个 admin 用户
   ad := admin{
        user: user{
           name: "john smith",
           email: "john@yahoo.com",
        },
       level: "super",
}

// 我们可以直接访问内部类型的方法
ad.user.notify()

// 内部类型的方法也被提升到外部类型
ad.notify()
}

goroutine和通道

通道chan

    1 通道是可以让一个goroutine发送特定值到另一个goroutine的通讯机制
    2 每一个通道是一个具体类型的导管,叫做通道的元素类型
    3 ch := make(chan int) //ch的类型是 chan int
    4 引用类型:当复制或者作为参数传递到一个函数时,都引用同一份数据结构。零值 nil
    5 同种类型的通道 可以使用 == 符号比较。
    6 操作:发送(send) 、 接收(receive)、关闭(close)。 操作符 <-
    send语句从一个goroutine 传输一个值到另一个在执行接收表达式的goroutine
    关闭操作:设置一个标志位来指示值当前已发送完毕,这个通道后面没有值了;关闭后不可以再发送

    ch <- x      //发送语句 通道和值分别在 <-的 左,右
	x = <- ch    // 复制语句中的接收表达式 通道放在 <- 的后面
	<- ch        //接收语句,丢弃结果
	close(ch)

无缓冲通道(同步通道)

make(chan int)      //无缓冲通道
make(chan int,0)    //无缓冲通道
make(chan int,30)   //容量为3的缓冲通道
1 通道容量为0
2 发送或者接收将会阻塞。这种通道导致发送和接收goroutine同步话,因此无缓冲通道也成为同步通道
   发送操作将会阻塞,直到另一个goroutine在同一个通道上执行接收操作
   接收操作将会阻塞,直到另一个goroutine在同一个通道上执行发送操作
3 


管道

package main

import "fmt"

// 输出自然数的平方。
func main() {
	naturals := make(chan int)
	squares := make(chan int)

	// Counter
	go func() {
		// 发送无限个自然数
		for x := 0; ; x++ {
			naturals <- x
		}
	}()

	// Squarer
	go func() {
		for {
			// 接收无限个自然数 并计算平方
			x := <-naturals
			squares <- x * x
		}
	}()

	// Printer (in main goroutine)
	for {
		fmt.Println(<-squares)
	}
}
思考:如果要发送有限个数字咋办?

package main

import "fmt"

//!+
func main() {
	naturals := make(chan int)
	squares := make(chan int)

	// Counter
	go func() {
		for x := 0; x < 100; x++ {
			naturals <- x
		}
		close(naturals)
	}()

	// Squarer
	go func() {
		//range 遍历 从管道naturals 中取值
		for x := range naturals {
			squares <- x * x
		}
		//即使不关闭,垃圾回收期也会根据它是否可以访问 来决定是否回收它,而不是根据它是否关闭
		// 管道close 与文件的close是不一样的
		close(squares)
	}()

	// Printer (in main goroutine)
	for x := range squares {
		fmt.Println(x)
	}
}

//!-


单向通道类型

仅能操作发出或接收操作 chan<- int

只能发送的通道 <-chan int 只能接收的通道

在任何赋值操作中将双向通道转换为单向通道都是允许的。但是反过来不行

package main

import "fmt"
//参数设定:out仅用来发送, in仅用来接收
func counter(out chan<- int) {
	for x := 0; x < 100; x++ {
		out <- x
	}
	close(out)
}

func squarer(out chan<- int, in <-chan int) {
	for v := range in {
		out <- v * v
	}
	close(out)
}

func printer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

func main() {
	naturals := make(chan int) //双向通道,在任何赋值操作中将双向通道转换为单向通道都是允许的。但是反过来不行
	squares := make(chan int) //双向通道
	
	go counter(naturals) 
	go squarer(squares, naturals)
	printer(squares)
}

缓冲通道

缓冲通道桥有一个元素队列,队列最大长度在创建的时候通过make的容量参数设置。

发送会在元素队列尾部插入一个元素 接收操作从元素队列的头部移除一个元素

如果通道满了 发送操作会阻塞所在的goroutine直到另一个goroutine对他进行接收操作者;

反过来,如果通道是空的,接收操作goroutineh会阻塞,直到另一个goroutine在通道上发送数据

声明:ch = make(chan string, 3)

获取ch容量: cap(ch)

获取ch通道内元素个数: len(ch)

// 并发的向三个镜像地址发送请求,并将它们的响应通过缓冲通道进行发送,然后只接收第一个返回的响应
func mirroredQuery() string{
	responses := make(chan string,3)
	
	go func(){responses <- request("asia.gop1.io")}()
	go func(){responses <- request("europe.gop1.io")}()
	go func(){responses <- request("americas.gop1.io")}()
    return <-responses //返回最快的那个响应
}
func request(homestring string)(response string){
	/* ... */
}


goroutine泄漏:如果使用一个无缓冲的通道,两个比较慢的goroutine将被卡住,因为它们发送响应结果到通道没有goroutine接收。这种情况叫goroutine泄漏

package cake

import (
	"fmt"
	"math/rand"
	"time"
)

// 蛋糕制作过程 烤(bake) 加糖衣(icing),雕刻(inscribing)
type Shop struct {
	Verbose        bool
	Cakes          int           // number of cakes to bake
	BakeTime       time.Duration // time to bake one cake
	BakeStdDev     time.Duration // standard deviation of baking time
	BakeBuf        int           // buffer slots between baking and icing
	NumIcers       int           // number of cooks doing icing
	IceTime        time.Duration // time to ice one cake
	IceStdDev      time.Duration // standard deviation of icing time
	IceBuf         int           // buffer slots between icing and inscribing
	InscribeTime   time.Duration // time to inscribe one cake
	InscribeStdDev time.Duration // standard deviation of inscribing time
}

type cake int

func (s *Shop) baker(baked chan<- cake) {
	for i := 0; i < s.Cakes; i++ {
		c := cake(i)
		if s.Verbose {
			fmt.Println("baking", c)
		}
		work(s.BakeTime, s.BakeStdDev)
		baked <- c
	}
	close(baked)
}

func (s *Shop) icer(iced chan<- cake, baked <-chan cake) {
	for c := range baked {
		if s.Verbose {
			fmt.Println("icing", c)
		}
		work(s.IceTime, s.IceStdDev)
		iced <- c
	}
}

func (s *Shop) inscriber(iced <-chan cake) {
	for i := 0; i < s.Cakes; i++ {
		c := <-iced
		if s.Verbose {
			fmt.Println("inscribing", c)
		}
		work(s.InscribeTime, s.InscribeStdDev)
		if s.Verbose {
			fmt.Println("finished", c)
		}
	}
}

// Work runs the simulation 'runs' times.
func (s *Shop) Work(runs int) {
	for run := 0; run < runs; run++ {
		baked := make(chan cake, s.BakeBuf)
		iced := make(chan cake, s.IceBuf)
		go s.baker(baked)
		for i := 0; i < s.NumIcers; i++ {
			go s.icer(iced, baked)
		}
		s.inscriber(iced)
	}
}

// work blocks the calling goroutine for a period of time
// that is normally distributed around d
// with a standard deviation of stddev.
func work(d, stddev time.Duration) {
	delay := d + time.Duration(rand.NormFloat64()*float64(stddev))
	time.Sleep(delay)
}


// 

package cake_test

import (
	"testing"
	"time"

	"gopl.io/ch8/cake"
)

var defaults = cake.Shop{
	Verbose:      testing.Verbose(),
	Cakes:        20,
	BakeTime:     10 * time.Millisecond,
	NumIcers:     1,
	IceTime:      10 * time.Millisecond,
	InscribeTime: 10 * time.Millisecond,
}

func Benchmark(b *testing.B) {
	// Baseline: one baker, one icer, one inscriber.
	// Each step takes exactly 10ms.  No buffers.
	cakeshop := defaults
	cakeshop.Work(b.N) // 224 ms
}

func BenchmarkBuffers(b *testing.B) {
	// Adding buffers has no effect.
	cakeshop := defaults
	cakeshop.BakeBuf = 10
	cakeshop.IceBuf = 10
	cakeshop.Work(b.N) // 224 ms
}

func BenchmarkVariable(b *testing.B) {
	// Adding variability to rate of each step
	// increases total time due to channel delays.
	cakeshop := defaults
	cakeshop.BakeStdDev = cakeshop.BakeTime / 4
	cakeshop.IceStdDev = cakeshop.IceTime / 4
	cakeshop.InscribeStdDev = cakeshop.InscribeTime / 4
	cakeshop.Work(b.N) // 259 ms
}

func BenchmarkVariableBuffers(b *testing.B) {
	// Adding channel buffers reduces
	// delays resulting from variability.
	cakeshop := defaults
	cakeshop.BakeStdDev = cakeshop.BakeTime / 4
	cakeshop.IceStdDev = cakeshop.IceTime / 4
	cakeshop.InscribeStdDev = cakeshop.InscribeTime / 4
	cakeshop.BakeBuf = 10
	cakeshop.IceBuf = 10
	cakeshop.Work(b.N) // 244 ms
}

func BenchmarkSlowIcing(b *testing.B) {
	// Making the middle stage slower
	// adds directly to the critical path.
	cakeshop := defaults
	cakeshop.IceTime = 50 * time.Millisecond
	cakeshop.Work(b.N) // 1.032 s
}

func BenchmarkSlowIcingManyIcers(b *testing.B) {
	// Adding more icing cooks reduces the cost of icing
	// to its sequential component, following Amdahl's Law.
	cakeshop := defaults
	cakeshop.IceTime = 50 * time.Millisecond
	cakeshop.NumIcers = 5
	cakeshop.Work(b.N) // 288ms
}


并行循环

使用select多路复用

取消

posted on 2024-04-30 10:41  小胖子方法  阅读(46)  评论(0)    收藏  举报

导航