https://www.ogeek.net/article/126998.htm
go标准库文档https://studygolang.com/pkgdoc
1.
如果想要再本地直接查看go官方文档,可以再终端中运行:
userdeMacBook-Pro:~ user$ godoc -http=:8000
然后在浏览器中运行http://localhost:8000就能够查看文档了,如下图所示:
2.os.Args : Args保管了命令行参数,第一个是程序名
3.所有的go语言代码都只能放置在包中,每一个go程序都必须包含一个main包以及一个main函数。main()函数作为整个程序的入口,在程序运行时最先被执行。实际上go语言中的包还可能包含init()函数,它先于main()函数被执行
4.包名和函数名之间不会发生命名冲突
5.go语言针对处理的单元是包而非文件,如果所有文件的包声明都是一样的,那么他们属于同一个包
6.
1)
package main import ( "fmt" "os" "strings" "path/filepath" ) //"path/filepath"类的引用只需要其最后一部分,即filepath func main(){ who := "world!" //Args保管了命令行参数,第一个是程序名 fmt.Println(os.Args) //返回:[/var/folders/2_/g5wrlg3x75zbzyqvsd5f093r0000gn/T/go-build178975765/b001/exe/test] fmt.Println(filepath.Base(os.Args[0]))//返回传入路径的基础名,即文件名test if len(os.Args) > 1 {//则运行时是带有命令行参数的 who = strings.Join(os.Args[1:]," ")//返回一个由" "分隔符将os.Args[1:]中字符串一个个连接生成一个新字符串 } fmt.Println("Hello", who) //返回:Hello world! }
不带命令行参数的运行返回:
userdeMBP:go-learning user$ go run test.go [/var/folders/2_/g5wrlg3x75zbzyqvsd5f093r0000gn/T/go-build644012817/b001/exe/test] test Hello world!
带命令行参数的运行返回:
userdeMBP:go-learning user$ go run test.go i am the best [/var/folders/2_/g5wrlg3x75zbzyqvsd5f093r0000gn/T/go-build463867367/b001/exe/test i am the best] test Hello i am the best
2)import
支持两种加载方式:
1》相对路径
import "./model"
2》绝对路径
import "shorturl/model"
下面还有一些特殊的导入方式:
1〉点操作
import( . "fmt" )
这个点操作的作用就是这个包导入后,当你想要调用这个包的函数的时候,你就能够省略它的包名了,即fmt.Println()可以省略成Println()
2>别名操作
import( f "fmt" )
就是把上面的fmt包重命名成一个容易记忆的名字,调用可以变成f.Println()
3>_操作
import( _ "github.com/ziutek/mymysal/godrv" )
其作用就是引入该包,而不直接使用包里面的函数或变量,会先调用包中的init函数,这种使用方式仅让导入的包做初始化,而不使用包中其他功能
7.在go语言中,byte类型等同于uint8类型
8.注意:在go中++和--等操作符只会用于语句而非表达式中,可能只可以做成后缀操作符而非前缀操作符。即go中不会出现f(i++)或A[i]=b[++i]这样的表达式
9.相同结构的匿名类型等价,可以相互替换,但是不能有任何方法
任何命名的自定义类型都可以有方法,并且和这些方法一起构成该类型的接口。命名的自定义类型即时结构完全相同,也不能相互替换
接口也是一种类型,可以通过指定一组方法的方式定义。接口是抽象的,因此不可实例化。如果某个具体类型实现了某个接口所有的方法,那么这个类型就被认为实现了该接口。一个类型可以实现多个接口。
空接口(即没有定义方法的接口),用interface{}来表示。由于空接口没有做任何要求(因为它不需要任何方法),它可以用来表示任意值(效果相当于一个指向任意类型值的指针),无论这个值是一个内置类型的值还是一个自定义类型的值,如:
type Stack []interface{}
上面的空接口其实就相当于一个可变长数组的引用
⚠️在go语言中只讲类型和值,而非类、对象或实例
10.go语言的函数和方法均可返回单一值或多个值。go语言中报告错误的管理是函数或方法的最后一个返回值是一个错误值error,如:
item,error := someFunc()
11.无限循环的使用要配合break或return来停止该无限循环
... for{ item,err := haystack.Pop() if err != nil{ break } fmt.Println(item) }
12.
func (stack *Stack) Push(x interface{}){ *stack = append(*stack,x) }
其中(stack *Stack)中的stack即调用Push方法的值,这里成为接收器(在其他语言中是使用this,self来表示的)
如果我们想要修改接收器,就必须将接收器设为一个指针,如上面的*Stack,使用指针的原因是:
- 效率高;如果我们有一个很大的值,那么传入一个指向该值所在的内存地址的指针会比传入该值本身更便宜
- 是一个值可以被修改;将一个变量传入函数中其实只是传入了他的一个副本,对该值的所有改动都不会影响其原始值(即值传递)。所以如果我们想要更改原始值,必须传入指向原始值的指针
*stack表示解引用该指针变量,即引用的是该指针所指之处的实际Stack类型值13.go中的通道(channel)、映射(map)和切片(slice)等数据结构必须通过make()函数创建,make()函数返回的是该类型的一个引用14.如果栈没有被修改,那么可以使用值传递,如:
func (stack Stack) Top(interface{},error){ if len(stack) == 0{ return nil,errors.New("can't Top an empty stack") } return stack[len(stack)-1],nil }
可见该例子的接收器就没有使用指针,因为该例子没有修改栈
15.任何属于defer语句所对应的语句都保证会被执行,但是其后面对应的函数只会在该defer语句所在的函数返回时被调用(即其不马上运行,而是推迟到该函数的其他所有语句都运行完后再返回来运行它),如:
func main(){
....
defer inFile.Close()
....
defer outFile.Close()
....
}
在上面的例子可以保证打开的文件可以继续被它下面的命令使用,然后会在使用完后才关闭,即使是程序崩溃了也会关闭
16.在go语言中,panic是一个运行时错误(翻译为‘异常’)。可以使用内置的panic()函数来触发一个异常,还可以使用recover()函数来调用栈上阻止该异常的传播。理论上,go的panic/recover功能可以用于多用途的错误处理机制,但是我们并不推荐这么使用。更合理的错误处理方式时让函数或者方法返回一个error值。panic/recover机制的目的是处理真正的异常(即不可预料的异常),而不是常规错误
17.PDF中的1.7的例子很好,值得多看
18.打印到终端其实是有返回值的,一般都忽略,但是其实是应该检查其返回的错误值的
count , err := fmt.Println(x) //获取打印的字节数和相应的error值 _ , err := fmt.Println(x) //丢弃打印的字节数,返回error值 count , _ := fmt.Println(x) //获取打印的字节数,丢弃error值 fmt.Println(x) //忽略所有返回值
19.常量赋值的特性
1)使用iota
package main import "fmt" const ( a = iota b c ) func main(){ fmt.Println(a)//0 fmt.Println(b)//1 fmt.Println(c)//2 }
使用iota,则在const的第一个常量中被设为0,接下来增量为1
2)如果b设为字符串"test",那么c的值和b一样,但是iota还是一样递增
package main import "fmt" const ( a = iota b = "test" c d = iota e ) func main(){ fmt.Println(a)//0 fmt.Println(b)//test fmt.Println(c)//test fmt.Println(d)//3 fmt.Println(e)//4 }
3)如果不使用iota,则第一个常量值一定要显示定义值,后面的值和其前一个的值相同:
package main import "fmt" const ( a =1 b = "test" c d = 5 e ) func main(){ fmt.Println(a)//1 fmt.Println(b)//test fmt.Println(c)//test fmt.Println(d)//5 fmt.Println(e)//5 }
20.逻辑操作符
1)|| 和 &&
b1 || b2 :如果b1为true,则不会检测b2,表达式结果为true
b1 && b2 : 如果b1为false,则不会检测b2,表达结果为false
2)< 、<=、 == 、!= 、>= 、>
比较的两个值必须是相同类型的
如果两个是接口,则必须实现了相同的接口类型
如果有一个值是常量,那么它的类型必须和另一个类型相兼容。这意味着一个无类型的数值常量可以跟另一个任意数值类型的值进行比较
不同类型且非常量的数值不能直接比较,除非其中一个被显示地转换成与另一个相同类型的值,类型转换即type(value)
21.整数
go语言允许使用byte来作为无符号uint8类型的同义词
并且使用单个字符(即Unicode码点)的时候提倡使用rune来替代uint32
大多数情况下,我们只需要一种整形,即int
从外部程序(如从文件或者网络连接读写整数时,可能需要别的整数类型)。这种情况下需要确切地知道需要读写多少位,以便处理该整数时不会发生错乱
常用做法是将一个整数以int类型存储在内存中,然后在读写该整数的时候将该值显示地转换为有符号的固定尺寸的整数类型。byte(uint8)类型用于读或写原始的字节。例如,用于处理UTF-8编码的文本
大整数:
有时我们需要使用甚至超过int64位和uint64位的数字进行完美的计算。这种情况下就不能够使用浮点数,因为他们表示的是近似值。
因此Go的标准库种提供了无限精度的整数类型:用于整数的big.Int型以及用于有理数的big.Rat型(即包括可以表示分数的2/3和1.1496.但不包括无理数e或者Π)
但是无论什么时候,最好只使用int类型,如果int型不能满足则使用int64型,或者如果不是特别关心近似值的可以使用float32或者float64类型。
然而如果计算需要完美的精度,并愿意付出使用内存和处理器的代价,那么就使用big.Int或者big.Rat类型
22.Unicode编码——使用四个字节为每个字符编码
它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求
Unicode编码的使用意味着go语言可以包含世界上任意语言的混合,代码页没有任何的混乱和限制
每一个Unicode字符都有一个唯一的叫做“码点”的标识数字,其值从0x0到0x10FFFF,后者在go中被定义成一个常量unicode.MaxRune
23.字符串
go语言的字符串类型在本质上就与其他语言的字符串类型不同。Java的string、C++的std:string和python3的str类型都是定宽字符序列,即字符串中的每个字符的宽度是相同的。但是go的字符串是一个用UTF-8编码的变宽字符串。
UTF-8 使用一至四个字节为每个字符编码,其中大部分汉字采用三个字节编码,少量不常用汉字采用四个字节编码。因为 UTF-8 是可变长度的编码方式,相对于 Unicode 编码可以减少存储占用的空间,所以被广泛使用。
一开始可能觉得其他语言的字符串类型比go的好用,因为他们定长,所以字符串中的单个字符可以通过索引得到,而go语言只有在字符串中的字符都是7位ASCII字符(7位ASCII字符刚好能用一个UTF-8字节来表示,即都用一个单一的UTF-8字节表示)时才能索引
但在实际情况下,其并不是个问题,因为:
- 直接索引用得不多
- go支持一个字符一个字符的迭代,for index,char := range string{},每次迭代都产生一个索引位置和一个码点
- 标准库提供了大量的字符串搜索和操作函数
- 随时可以将字符串转换成一个Unicode码点切片(类型为[]rune),这个切片是可以直接索引的
所以:
[]rune(s)
能将一个字符串s转换成一个Unicode码点
如果想要转回来即:
s := string(chars)//chars的类型即[]rune或[]int32
上面两个时间代价为o(n)
len([]rune(s))
可以得到字符串s中字符的个数,当然你可以使用更快的:
utf8.RuneCountInString()
来替代
[]byte(s)
可以无副本地将字符串s转换成一个原始字节的切片数组,时间代价为o(1)
string(bytes)
无副本地将[]byte或[]uint8转换成一个字符串类型,不保证转换的字节是合法的UTF-8编码字节,时间代价为o(1)
举例:
package main import "fmt" func main(){ s := "hello world,i am coming" uniS := []rune(s) retS := string(uniS) bytS := []byte(s) retBytS := string(bytS) fmt.Println(s)//hello world,i am coming fmt.Println(uniS)//[104 101 108 108 111 32 119 111 114 108 100 44 105 32 97 109 32 99 111 109 105 110 103] fmt.Println(retS)//hello world,i am coming fmt.Println(len([]rune(s)))//23 fmt.Println(bytS)//[104 101 108 108 111 32 119 111 114 108 100 44 105 32 97 109 32 99 111 109 105 110 103] fmt.Println(retBytS)//hello world,i am coming }
⚠️
go中字符串是不可变的,即不能如下这样操作:
var s string = "hello" s[0] = 'c'
如果真的想要修改,可如下:
s := "hello" c := []byte(s)//将其转换成[]byte类型 c[0] = 'c' //然后更改 s2 := string(c) //然后再将其转换回来即可
虽然字符串不能修改,但是它可以进行切片操作
如果想要声明一个多行的字符,可以使用``来声明,如:
m := `hello
world`
使用``括起来的是Raw字符串,即字符串在代码中的形式就是打印时的形式,没有字符转义,换行也原样输出
24.字符串索引和切片
之前有说过,我们完全不需要切片一个字符串,只需要使用for...range循环将其一个字符一个字符地迭代,但是有些时候我们可能还是需要使用切片来获得一个子字符串,这时的解决办法是:
go中有个能够按字符边界进行切片得到索引位置的方法:
即strings包中的函数:
- strings.Index()
- strings.LastIndex()
举例:
package main import( "fmt" "strings" ) func main(){ //目的:得到该行文本的第一个和最后一个字 line := "i am the best" i := strings.Index(line, " ") //获得第一个空格的索引位置 firstWord := line[:i] //切片得到第一个字 j := strings.LastIndex(line, " ") //获得最后一个空格的索引 lastWord := line[j+1:] //切片得到最后一个字 fmt.Println(firstWord, lastWord) //输出:i best }
上面的例子坏处在于不适合处理任意的Unicode空白字符,如U+2028(行分隔符)或U+2029(段分隔符),这时候就使用:
- strings.IndexFunc(s string, f func(rune)bool)int :s中第一个满足函数f的位置i(该处的utf-8码值r满足f(r)==true),不存在则返回-1。
- strings.LastIndexFunc():s中最后一个满足函数f的unicode码值的位置i,不存在则返回-1。
- utf8.DecodeRuneInString():得到输入的string中的码点rune和其编码的字节数
举例:
package main import( "fmt" "strings" "unicode" "unicode/utf8" ) func main(){ line := "ra [email protected]\u2028var" i := strings.IndexFunc(line,unicode.IsSpace)//unicode.IsSpace是一个函数 firstWord := line[:i] j := strings.LastIndexFunc(line, unicode.IsSpace) r, size := utf8.DecodeRuneInString(line[j:]) lastWord := line[j+size:] fmt.Println(i,j,size)//2 7 3 fmt.Printf("%U\n",r) //U+2028 fmt.Println(firstWord, lastWord)//ra var }
func IsSpace(r rune) bool,IsSpace报告一个字符是否是空白字符
上面这个就是Unicode字符\u2028的字符、码点和字节,所以上面运行utf8.DecodeRuneInString()得到的r为U+2028,字节size为3
上面 s[0] == 'n',s[ len(s)-1 ] == 'e',但是问题在于第三个字符,其起始索引是2,但是如果我们使用s[2]只能得到该编码字符的第一个UTF-8字节,这并不是我们想要的
我们可以使用:
- utf8.DecodeRuneInString() : 解码输入的string中的第一个rune,最后返回该码点rune和其包含的编码的字节数
- utf8.DecodeLastRuneInString() : 解码输入的string中最后一个rune,最后返回该码点rune和其包含的编码的字节数
当然,实在想要使用索引[]的最好办法其实是将该字符串转换成[]rune(这样上面的例子将创建一个包含5个码点的rune切片,而非上面图中的6个字节),然后就能够使用索引调用了,代价就是该一次性转换耗费了CPU和内存o(n)。结束后可以使用string(char)来将其转换成字符
25.fmt
fmt包中提供的扫描函数(fmt.Scan()\fmt.Scanf()\fmt.Scanln())的作用是用于从控制台、文件以及其他字符串类型中读取数据
26.go语言提供了两种创建变量的语法,同时获得它们的指针:
- 使用内置的new()函数
- 使用地址操作符&
new(Type) === &Type{} (前提是该类型是可以使用大括号{}进行初始化的类型,如结构体、数组等。否则只能使用new()),&Type{}的好处是我们可以为其制定初始值
这两种语法都分配了一个Type类型的空值,同时返回一个指向该值的指针
先定义一个结构体composer:
type composer struct{ name string birthYear int }
然后我们可以创建composer值或只想composer值的指针,即*composer类型的变量
结构体的初始化可以使用大括号来初始化数据,举例:
package main import "fmt" type composer struct{ name string birthYear int } func main(){ a := composer{"user1",2001} //composer类型值 b := new(composer) //指向composer的指针 b.name, b.birthYear = "user2", 2002 //通过指针赋值 c := &composer{} //指向composer的指针 c.name, c.birthYear = "user3", 2003 d := &composer{"user4", 2004} //指向composer的指针 fmt.Println(a) //{user1 2001} fmt.Println(b,c,d) //&{user2 2002} &{user3 2003} &{user4 2004} }
当go语言打印指向结构体的指针时,它会打印解引用后的结构体内容,但会将取址操作符&作为前缀来表示它是一个指针
⚠️上面之所以b指针能够通过b.name来得到结构体中的值是因为在go语言中.(点)操作符能够自动地将指针解引用为它指向的结构体,都不会使用(*b).name这种格式
⚠️make、new操作
make用于内建类型(map、slice和channel)的内存分配。new用于各种类型的内存分配
1)new
new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即*T类型的值(指针),用于指向新分配的类型T的零值
2)make
make(T, args)只能创建map、slice和channel,并且返回的是有初始值(非零)的T类型,而不是*T指针。这是因为指向数据结构的引用在使用前必须被初始化。
比如一个slice是一个包含指向数据(内部array)的指针、长度len、容量cap的三项描述符,因此make初始化其内部的数据结构,然后填充适当的值
27.一旦我们遇到需要在一个函数或方法中返回超过四五个值的情况时,如果这些值是同一类型的话最好使用一个切片来传递,如果其值类型各异则最好传递一个指向结构体的指针。
传递一个切片或一个指向结构体的指针的成本都比较低(在64位的机器上一个切片占16位,一个映射占8字节),同时也允许我们修改数据
28.引用类型-映射、切片、通道、函数、方法
这样子当引用类型作为参数传入函数时,在函数中对该引用类型的改变都会作用于它本身,即引用传递
这是因为一个引用类型的变量指向的是内存中某个隐藏的值,其保存着实际的数据
package main import "fmt" func main(){ grades := []int{2,3,4,5} //整形切片 inflate(grades,3) fmt.Println(grades) //返回[6 9 12 15] } func inflate(numbers []int, factor int){ for i := range numbers{ numbers[i] *= factor } }
在上面的例子中我们没有使用for index,item := range numbers{}是因为这样的操作得到的是切片的副本,它的改变不会改变原始切片的值。所以这里我们使用的是for index := range语法
如果我们定义一个变量来保存一个函数,该变量得到的实际上是该函数的引用
29.数组、切片
- 数组是定长的 a := [3]int{1,2,3}/a := [...]int{1,2,3} ,[...]会自动计算数组长度,声明用 a:=[3]int
- 切片长度不定 b := []int{1,2,3}
a,b是不相同的。数组的cap()和len()函数返回的值是相同的,它也可以进行切片,即[i:j],但是返回的是切片,而非数组
数组是值传递,而切片是引用传递
在go的标准库中的所有公开函数中使用的都是切片而非数组,建议使用切片
当创建一个切片的时候,他会创建一个隐藏的初始化为零值的数组,然后返回一个引用该隐藏数组的切片。所以切片其实就是一个隐藏数组的引用
切片的创建语法:
- make([]Type, length, capacity) ,切片长度小于或等于容量
- make([]Type, length) ,下面三个长度等于容量
- []Type{} == make([]Type,0),空切片
- []Type{value1,...,valueN}
make()内置函数用来创建切片、映射、通道
可以使用append()内置函数来增加切片的容量
除了使用上面的方法还有一种方法:
package main import "fmt" func main(){ s := new([3]string)[:] //new()创建一个只想数组的指针,然后通过[:]得到该数组的切片 s[0], s[1], s[2] = "A", "B", "C" //赋值 fmt.Println(s) //返回[A B C] }
对切片的切片其实也是引用了同一个隐藏数组,所以如果对切片中的某个值进行了更改,那么该切片的切片中如果也包含该值,则发现也被更改了,举例:
package main import "fmt" func main(){ s := []string{"A", "B", "C"} t := s[1:] fmt.Println(s,t) //返回:[A B C] [B C] s[1] = "d" fmt.Println(s,t) //返回: [A d C] [d C] }
因为更改切片其实都是对底层的隐藏数据进行了修改
⚠️切片和字符串不同之处在于它不支持+或+=操作符,但是对切片进行添加、插入或删除的操作是十分简单的
- 遍历切片:for index,item := range amounts[:5] ,可遍历切片的前5个元素
- 修改切片中的项:for i:= range amounts{amounts[i] *= 3},不能使用上面的方法,那样得到的只是切片的副本,但是也有特别的情况,如下:
当切片包含的是自定义类型的元素时,如结构体
package main import "fmt" type Product struct{ name string price float64 } func main(){ products := []*Product{{"user1",11},{"user2",12},{"user3",13}} //等价于products := []*Product{&Product{"user1",11},&Product{"user2",12},&Product{"user3",13}} fmt.Println(products) //会自动调用Product的String方法,返回[user1 (11.00) user2 (12.00) user3 (13.00)] for _, product := range products{ product.price += 50 } fmt.Println(products) //返回[user1 (61.00) user2 (62.00) user3 (63.00)] } func (product Product) String() string{ return fmt.Sprintf("%s (%.2f)", product.name, product.price) }
如果没有定义Product.String()方法,那么%v格式符(该格式符在fmt.Println()以及类似的函数中被显示调用),输出的就是Product的内存地址,而非其内容,如:[0xc42000a080 0xc42000a0a0 0xc42000a0c0]
我们可以看见这里使用的是for index,item ...range迭代,这里能够成功的原因是product被赋值的是*Product的副本,这是一个指向底层数据的指针,所以成功
- 修改切片:append(array, ...args)内置函数,返回一个切片。如果原始切片中没有足够的容量,那么append()函数会隐式地创建一个新的切片,并将其原始切片的项复制进去,再在末尾添加上新的项,然后将新的切片返回,因此需要将append()的返回值赋值给原始切片变量
如果是想要从任意位置插入值,那么需要自己写一个函数,如:
package main import "fmt" func main(){ s := []string{"a","b","c","d"} x := InsertStringSlice(s,[]string{"e","f"},0) y := InsertStringSlice(s,[]string{"e","f"},2) z := InsertStringSlice(s,[]string{"e","f"},len(s)) fmt.Println(x) //[e f a b c d] fmt.Println(y) //[a b e f c d] fmt.Println(z) //[a b c d e f] } func InsertStringSlice(slice, insertion []string, index int)[]string{ return append(slice[:index],append(insertion,slice[index:]...)...) }
因为append()函数接受一个切片和一个或多个值作为参数,因此需要使用...(省略号语法)来将一个切片转换成它的多个元素值,因此上面的例子中使用了两个省略号语法
- 排序切片:sort.Sort(d)-排序类型为sort.interface的切片d、sort.Strings(s)-按升序排序[]string类型的切片s
sort.Sort(d)能够对任意类型进行排序,只要其类型提供sort.Interface接口中定义的方法:
type Interface interface { // Len方法返回集合中的元素个数 Len() int // Less方法报告索引i的元素是否比索引j的元素小 Less(i, j int) bool // Swap方法交换索引i和j的两个元素 Swap(i, j int) }
比如:
package main import( "fmt" "sort" "strings" ) func main(){ files := []string{"Test","uitl","Makefile","misc"} fmt.Printf("Unsorted: %q\n",files) //Unsorted: ["Test" "uitl" "Makefile" "misc"] sort.Strings(files)//区分大小写 fmt.Printf("underlying bytes:%q\n",files) //underlying bytes:["Makefile" "Test" "misc" "uitl"] SortFoldedStrings(files)//不区分大小写 fmt.Printf("Case insensitive:%q\n",files) //Case insensitive:["Makefile" "misc" "Test" "uitl"] } func SortFoldedStrings(slice []string){ sort.Sort(FoldedStrings(slice)) } type FoldedStrings []string func (slice FoldedStrings) Len() int { return len(slice) } func (slice FoldedStrings) Less(i,j int) bool{ return strings.ToLower(slice[i]) < strings.ToLower(slice[j]) } func (slice FoldedStrings) Swap(i,j int){ slice[i], slice[j] = slice[j], slice[i] }
- 搜索切片:sort.Search()
30.映射
map[string]float64:键为string类型,值为float64类型的映射
由于[]byte是一个切片,不能作为映射的键,但是我们可以先将[]byte转换成字符串,如string([]byte),然后作为映射的键字段,等有需要的时候再转换回来,这种转换并不会改变原有切片的数据
如果值的类型是接口类型,我们就可以创建一个满足这个接口定义的值作为映射的值,甚至我们可以创建一个值为空接口(interface{})的映射,这意味着任意类型的值都可以作为这个映射的值
不过当我们需要访问这个值时,需要使用类型开关和类型断言获得这个接口类型的实际类型,或者也可以通过类型检验来获得变量的实际类型
创建方式:
- make(map[keyType]ValueType,initialCapacity),随着加入的项越多,映射会自动扩容
- make(map[keyType]ValueType)
- map[keyType]ValueType{}
- map[keyType]ValueType{key1 : value1,...,keyN : valueN}
第二和第三种的结果是相同的
对于比较大的映射,最好是为其指定恰当的容量来提高性能
映射可以使用索引操作符[],不同在于其里面的值不用是int型,应该是映射的keyType值
映射的键也可以是一个指针,比如:
package main import "fmt" func main(){ triangle := make(map[*Point]string,3) triangle[&Point{89, 47, 27}] = "a" //使用&获得Point的指针 triangle[&Point{86, 65, 86}] = "b" triangle[&Point{7, 44, 45}] = "c" fmt.Println(triangle) //map[(89,47,27):a (86,65,86):b (7,44,45):c] } type Point struct{ x,y,z int} func (point Point) String() string{ return fmt.Sprintf("(%d,%d,%d)",point.x,point.y,point.z) }
如果想要按照某种方式来遍历,如按键序,则可以:
package main import( "fmt" "sort" ) func main(){ populationForCity := map[string]int{"beijing":12330000,"shanghai":11002002,"guangzhou":14559400} cities := make([]string,0,len(populationForCity)) for city := range populationForCity{ cities = append(cities,city) } sort.Strings(cities) for _,city := range cities{ fmt.Printf("%-10s %8d\n", city, populationForCity[city]) } }
该方法就是创建一个足够大的切片去保存映射里的所有键,然后对切片排序,遍历切片得到键,再从映射里得到这个键的值,然后就可以实现键顺序输出了
另一种方法就是:使用一个有序的数据结构,这个之后讲
上面是按键排序,按值排序当然也是可以的——映射反转
前提:映射的值都是唯一的,如:
package main import( "fmt" ) func main(){ populationForCity := map[string]int{"beijing":12330000,"shanghai":11002002,"guangzhou":14559400} cityForPopulation := make(map[int]string,len(populationForCity)) //与上面的populationForCity键值类型相反 for city, population := range populationForCity{ cityForPopulation[population] = city } fmt.Println(cityForPopulation) //map[14559400:guangzhou 12330000:beijing 11002002:shanghai] }
即创建一个与populationForCity键值类型相反的cityForPopulation,然后遍历populationForCity,并将得到的键值反转,然后插到cityForPopulation中即可
返回:
userdeMBP:go-learning user$ go run test.go beijing 12330000 guangzhou 14559400 shanghai 11002002
31.++(递增)\--(递减)操作符
这两个操作符都是后置操作符,并且没有返回值。因此该操作符不能用于表达式和语意不明的上下文。所以i = i++这样的代码是错误的
32.类型断言
一个interface{}的值可以用于表示任意Go类型的值。
我们可以使用类型开关、类型断言或者Go语言的reflect包的类型检查将一个interface{}类型的值转换成实际数据的值
在处理从外部源接收到的数据、想创建一个通用函数及在进行面向对象编程时,我们会需要使用interface{}类型(或自定义接口类型),因为你不知道接收的是什么类型的数据
为了访问底层值,我们需要进行类型断言来确定得到的数据的类型,类型断言的格式为:
resultOfType, boolean := expression.(Type) //安全断言类型 resultOfType := expression.(Type) //非安全断言类型,如果断言失败,则调用内置函数panic()
resultOfType返回的是expression的该Type类型的值,如下面的例子,i.(int)返回的j的值为99
举例说明:
package main import "fmt" func main(){ var i interface{} = 99 var s interface{} = []string{"left","right"} j := i.(int) //j是int类型的数据,或者失败发生了一个panic() // b := i.(bool) //b是bool类型的数据,或者失败发生了一个panic() fmt.Printf("%T -> %d\n", j, j) // fmt.Println(b) if i, ok := i.(int); ok { fmt.Printf("%T -> %d\n", i, i) } if s, ok := s.([]string); ok { fmt.Printf("%T -> %q\n", s, s) } }
返回:
userdeMBP:go-learning user$ go run test.go int -> 99 int -> 99 []string -> ["left" "right"]
如果删除上面的注释:
// b := i.(bool) //b是bool类型的数据,或者失败发生了一个panic() // fmt.Println(b)
将会调用panic()返回:
userdeMBP:go-learning user$ go run test.go panic: interface conversion: interface {} is int, not bool goroutine 1 [running]: main.main() /Users/user/go-learning/test.go:8 +0xe0 exit status 2
在例子中有i, ok := i.(int),在做类型断言的时候将结果赋值给与原始变量同名的变量是很常见的事,这叫做使用影子变量
⚠️只有在我们希望表达式是某种特定类型的值时才使用类型断言(如果目标类型可能是很多类型之一,我们可
请发表评论