本文来自网易云社区
作者:盛国存
var m1 map[string]int
m2 := make(map[string]int)
上述就是常见的创建map的方式,但是m1、m2还是有区别的,m1是nil,m2是一个空map;常规的遍历map也是用 range 的方式就可以,
for k, v := range m {
fmt.Println(k, v)
}
当然细心的会发现,在遍历的过程中是不能保证顺序的,当然要是想顺序遍历,需要自己手动对key进行排序,可以将key存进slice,然后再通过slice遍历相关的key获取map的值。
m[key] 一般就是这样获取map的值
var map1 = map[string]string{
"name" : "shengguocun",
"gender" : "male",
"city" : "hangzhou",
}
value1 := map1["age"]
fmt.Println(value1)
先来猜测一下,上述这段代码可以运行么?会不会报错?
答案是不会,这就是Go语言和别的语言不一样的地方,上述的例子中 value1 的值是一个空字符串,map中当key不存在时,会获取value类型的初始值。
gender, ok := map1["gender"]
if ok {
fmt.Println("Gender 的值为 : ", gender)
}else {
fmt.Println("Key 不存在")
}
既然Go语言的出现就是为了解决别的语言的痛点,所以在使用过程中不再需要每次获取某个 key 的时候都要去 isset 判断一下,Go的获取map的值的时候第二个返回值就是别的语言 isset 的功能;存在返回 true ,不存在返回 false。
delete函数,就可以直接删除指定的key的值
这是Go语言的官方文档,不难理解比如要删除上面的 map1 的 city 的值
delete(map1, "city")
直接调用就可以
为什么要把key单独拿出来说呢?因为map底层使用的是hash表,所以map的key必须可以比较相等;换句话说就是除了 slice、map、function的内建类型都可以作为key。
rune就是Go语言的字符串类型,其实可以理解为是 int32 的一个別名,下面我们通过例子来深入理解一下rune
s1 := "你好,杭州"
fmt.Println(s1)
for _, ch := range []byte(s1) {
fmt.Printf("%X ", ch)
}
fmt.Println()
for i, ch := range s1 {
fmt.Printf("(%d %X) ", i, ch)
}
输出结果
你好,杭州
E4 BD A0 E5 A5 BD 2C E6 9D AD E5 B7 9E
(0 4F60) (3 597D) (6 2C) (7 676D) (10 5DDE)
从上述的例子我们可以直接的看出来,其实就是将UTF-8编码解码,然后再转成Unicode之后将它存放进一个rune(int32)中
UTF-8编码的rune长度统计
count := utf8.RuneCountInString(s1)
fmt.Println("Rune Count :", count)
输出结果为:
Rune Count : 5
字符串的输出操作
bytes := []byte(s1)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
用rune实现上述同样的功能
for _, ch := range []rune(s1) {
fmt.Printf("%c ", ch)
}
go语言仅支持封装,不支持继承和多态;这句话怎么理解呢?就是说在Go语言内部没有class,只有struct;也没有复杂的继承和多态,那继承和多态的任务又是通过什么实现的呢?Go是面向接口编程,可以通过接口来实现继承和多态的相关的任务,后面我会再进行介绍。 下面先来介绍一下struct的创建:
type Node struct {
Value int
Left, Right *Node
}
通过type、struct关键字创建结构体类型,当然在创建了结构体类型之后,就可以创建相关类型的变量
var root tree.Node
root = tree.Node{Value:1}
root.Value = 2
root.Left = &tree.Node{Value:3}
root.Right = &tree.Node{}
结构体的方法的创建和普通的函数创建没有太大的区别,只是在方法名前面添加一个接收者,就相当于其他语言的this
func (node Node) Print() {
fmt.Print(node.Value, " ")
}
上述就是一个值接收者打印出Node的Value的值的方法。 当然要是需要改变Value的值的时候,就需要一个指针接收者。
func (node *Node) SetValue(value int) {
node.Value = value
}
有一个疑问,要是对一个值为nil的Node进行 SetValue 操作会发生什么?
var pRoot *tree.Node
pRoot.SetValue(1)
虽说nil指针可以调用方法,但是下面的Value是拿不到,自然就会报下面的错了
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x20c3]
goroutine 1 [running]:
panic(0x8f100, 0xc42000a070)
实际使用过程中可以添加相关的判断在做处理。结合上面的知识我们不难写出一个树的遍历的方法的代码
func (node *Node) Traverse() {
if node == nil {
return
}
node.Print()
node.Left.Traverse()
node.Right.Traverse()
}
当然上面的例子已经在不经意间提前引入了package的概念
在面向对象中,我们想要扩展一下别人的类,我们通常继承一下就好了,但是Go语言中没有继承的概念,我们该如何处理呢?
在大规模的重构项目代码的时候,尤其是将一个类型从一个包移动到另一个包中的时候,有些代码使用新包中的类型,有些代码使用旧包中的类型
基本语法就是:
type identifier = Type
比如内建的byte类型,其实是uint8的类型别名,而rune其实是int32的类型别名。
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
通过别名的方式就可以拓展了,比如
type T1 struct{}
type T3 = T1
func (t1 T1) say(){}
func (t3 *T3) greeting(){}
func main() {
var t1 T1
// var t2 T2
var t3 T3
t1.say()
t1.greeting()
t3.say()
t3.greeting()
}
当然要是T1也定义了 greeting 的方法,那么编译会报错的,因为有重复的方法定义。
比如我们想扩展上面的树的包,实现一个自己的中序遍历,该如何实现呢?通过代码来理解一下使用组合的概念
type myNode struct {
node *tree.Node
}
func (myNodeNode *myNode) Traverse() {
if myNodeNode == nil || myNodeNode.node == nil {
return
}
left := myNode{myNodeNode.node.Left}
right := myNode{myNodeNode.node.Right}
left.ownFunc()
myNodeNode.node.Print()
right.ownFunc()
}
如何查看自己的GOPATH呢?
~ sheng$ echo $GOPATH
/Users/verton/GoLangProject
go get url
这样是可以获取GitHub上面的三方的库,但是Golang.org上面要是不能翻墙是获取不了的,这里我给大家介绍一个新的工具 gopm
sheng$ go get github.com/gpmgo/gopm
一行命令就可以装好了,这个时候再get三方的库就毫无压力了,因为这个国内有相关的镜像
gopm get -g url
采用-g 参数,可以把依赖包下载到GOPATH目录中
从上述的目录结构上我们可以看出来,src pkg 是对应的,src 是我们的代码的位置以及三方库的位置,pkg 是build的中间过程,可以暂时先不用关注,bin下面就是可执行文件。
很多语言都有duck typing的概念, 用一个简单的例子来描述一下这个概念
大黄鸭是鸭子么?这个答案是要看基于什么角度来看,从生物角度来看,那它当然不是鸭子,连基本的生命都没有;但是从duck typing的角度来看它就是一个鸭子,因为它外部长得像鸭子,通俗点概括一下duck typing的概念就是:描述事物的外部行为而非内部结构。
从严格意义上讲,go语言只能说是类似duck typing,go语言不是动态绑定的,go语言是编译时绑定的。
在Go语言中,接口interface其实和其他语言的接口意思也没什么区别。一个结构体必须实现了一个接口的所有方法,才能被一个接口对象接受,这一点和Java语言中的接口的要求是一样的。interface理解其为一种类型的规范或者约定。
type Retriever interface{
Get(url string) string
}
这样就定义了一个接口,它包含一个Get函数。
现在我们就来实现一下这个接口。比如我们做一个拉取某个页面的操作
package rick
import (
"net/http"
"net/http/httputil"
)
type Retriever struct {
}
func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
package main
import (
"shengguocun.com/retriever/rick"
"fmt"
)
type Retriever interface{
Get(url string) string
}
func download(r Retriever) string {
return r.Get("http://www.shengguocun.com")
}
func main() {
var r Retriever
r = rick.Retriever{}
fmt.Println(download(r))
}
上述rick.Retriever就实现了Retriever接口。
继续使用上面的例子
var r Retriever
r = rick.Retriever{}
fmt.Printf("\n %T %v \n", r, r)
会输出什么呢?
rick.Retriever {}
这就是常规的值传递,没有什么特别的地方。要是 Retriever 这个struct很大,我们不希望通过传值的方法去拷贝,而是通过指针访问Get方法。
func (r *Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
var r Retriever
r = &rick.Retriever{}
fmt.Printf("\n %T %v \n", r, r)
这时候的Type、Value又是什么?
*rick.Retriever &{}
我们可以看到是一个指针,所以我们一般用到接口的指针,因为它的肚子里含有一个指针,通常我们会说“接口变量自带指针”,那我们现在用两个图来总结一下上面的概念
概括为:接口变量里面可以是实现者的类型和实现者的值,或者是接口类型里面可以是实现者的类型和实现者的指针,同时指向实现者。
说到这里要提到一个特殊的接口,空接口 interface{} ,对于空接口 interface{} 其实和泛型的概念很像,任何类型都实现了空接口。在方法需要返回多个类型的时候,返回值的类型我们一般定义为 interface{} 。
这时我们现在引入获取接口变量肚子里的类型的另外一种写法,叫 Type Assertion(断言)。比如
var a interface{}
fmt.Println("Are you ok?", a.(string))
然而上述的写法一旦断言失败,会报出panic错误,当然这样的程序就显得十分的不友好。我们需要在断言前进行一个判断。
value, ok := a.(string)
if !ok {
fmt.Println("断言失败,这不是一个string类型")
return
}
fmt.Println("值为:", value)
另外我们可以结合switch进行类型判断
var r interface{}
r = balabalaFunction()
switch v := r.(type) {
case bool:
fmt.Println("type bool...")
case int:
fmt.Println("type int...")
}
Tips:转换类型的时候如果是string可以不用断言,使用fmt.Sprint()函数可以达到想要的效果。
相关阅读:A Bite of GoLang (1)
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。