变量及数据类型
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的值返回true或false。
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

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指令执行前。具体如下图所示:

//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
注意:
-
recover()必须搭配defer使用。
-
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多路复用
取消
浙公网安备 33010602011771号