前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【初识Go】| Day5 字典、字符串

【初识Go】| Day5 字典、字符串

原创
作者头像
yussuy
修改2020-12-21 10:34:58
3750
修改2020-12-21 10:34:58
举报
文章被收录于专栏:咸鱼的进阶学习之路

字典

字典定义

字典/哈希表是一种巧妙并且实用的数据字结构。它是一个无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。

代码语言:txt
复制
// 内置的make函数可以创建一个map:

var m1 map[string]int

m2 := make(map[int]interface{}, 100)

ages := map[string]int{

    "alice":   31,

    "charlie": 34,

}

// 相当于

ages := make(map[string]int)

ages["alice"] = 31

ages["charlie"] = 34

在定义字典时不需要为其指定容量,因为map是可以动态增长的,但是在可以预知map容量的情况下为了提高程序的效率也最好提前标明程序的容量。需要注意的是,不能使用不能比较的元素作为字典的key,例如数组,切片等。而value可以是任意类型的,如果使用interface{}作为value类型,那么就可以接受各种类型的值,只不过在具体使用的时候需要使用类型断言来判断类型。

字典操作

与数组和切片一样,我们可以使用len来获取字典的长度。

代码语言:txt
复制
len(m3)

在有些情况下,我们不能确定键值对是否存在,或者当前value存储的是否就是空值,go语言中我们可以通过下面这种方式很简便的进行判断。

代码语言:txt
复制
// 如果当前字典中存在key为name的字符串则取出对应的value,并返回true,否则返回false。

if value, ok := m3["name"]; ok {

        fmt.Println(value)

}

对于一个已经存在的字典,我们如何对其进行遍历呢?可以使用下面这种方式:

代码语言:txt
复制
for key, value := range m3 {

        fmt.Println("key: ", key, " value: ", value)

}

如果多运行几次上面的这段程序会发现每次的输出顺序并不相同,对于一个字典来说其默认是无序的,那么我们是否可以通过一些方式使其有序呢?你可以动手尝试一下。(提示:可以通过切片来做哦)

答:如果要按顺序遍历key/value对,我们必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。下面是常见的处理方式:

代码语言:txt
复制
import "sort"



var names []string

for name := range ages {

    names = append(names, name)

}

sort.Strings(names)

for \_, name := range names {

    fmt.Printf("%s\t%d\n", name, ages[name])

}

如果已经存在与字典中的值已经没有作用了,我们想将其删除怎么办呢?可以使用go的内置函数delete来实现。

代码语言:txt
复制
delete(ages, "alice") // remove element ages["alice"]

所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值,例如,即使map中不存在“bob”下面的代码也可以正常工作,因为ages"bob"失败时将返回0。

代码语言:txt
复制
ages["bob"] = ages["bob"] + 1

而且x += yx++等简短赋值语法也可以用在map上,所以上面的代码可以改写成

代码语言:txt
复制
ages["bob"] += 1

// 等同于

ages["bob"]++

但是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:

代码语言:txt
复制
\_ = &ages["bob"] // compile error: cannot take address of map element

禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。

除了上面的一些简单操作,我们还可以声明值类型为切片的字典以及字典类型的切片等等。

不仅如此我们还可以将函数作为值类型存入到字典中。

代码语言:txt
复制
func main() {

    m := make(map[string]func(a, b int) int)

    m["add"] = func(a, b int) int {

        return a + b

    }

    m["multi"] = func(a, b int) int {

        return a \* b

    }

    fmt.Println(m["add"](3, 2))

    fmt.Println(m["multi"](3, 2))

}

字符串

字符串定义

单行定义

一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码表上的字符时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。

UTF-8 是一种被广泛使用的编码格式,是文本文件的标准编码,其中包括 XML 和 JSON 在内也都使用该编码。由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用 1 至 4 个字节,这与其它编程语言如 C++Java 或者 Python 不同(Java 始终使用 2 个字节)。Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容,更深入地讲,字符串是字节的定长数组。

可以使用双引号""来定义字符串,字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:

  • \n:换行符
  • \r:回车符
  • \t:tab 键
  • \u 或 \U:Unicode 字符
  • \:反斜杠自身
代码语言:txt
复制
package main

import (

    "fmt"

)

func main() {

    var str = "C语言中文网\nGo语言教程"

    fmt.Println(str)

}

运行结果为:

C语言中文网

Go语言教程

一般的比较运算符(==、!=、<、<=、>=、>)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。字符串所占的字节长度可以通过函数 len() 来获取,例如 len(str)。

字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[ ]内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str0
  • 第 i 个字节:stri - 1
  • 最后 1 个字节:strlen(str)-1

需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。

注意:获取字符串中某个字节的地址属于非法行为,例如 &stri。

字符串是一种值类型,在创建字符串之后其值是不可变的,也就是说下面这样操作是不允许的。

代码语言:txt
复制
s := "hello"

s[0] = 'T'

编译器会提示cannot assign to s[0]。在C语言中字符串是通过\0来标识字符串的结束,而go语言中是通过长度来标识字符串是否结束的。

如果我们想要修改一个字符串的内容,我们可以将其转换为字节切片,再将其转换为字符串,但是也同样需要重新分配内存。

代码语言:txt
复制
func main() {

    s := "hello"

    b := []byte(s)

    b[0] = 'g'

    s = string(b)

    fmt.Println(s) //gello

}

与其他数据类型一样也可以通过len函数来获取字符串长度。

代码语言:txt
复制
len(s)

但是如果字符串中包含中文就不能直接使用byte切片对其进行操作,go语言中我们可以通过这种方式

代码语言:txt
复制
func main() {

    s := "hello你好中国"

    fmt.Println(len(s)) //17

    fmt.Println(utf8.RuneCountInString(s)) //9



    b := []byte(s)

    for i := 0; i < len(b); i++ {

        fmt.Printf("%c", b[i])

    } //helloä½ å¥½ä¸­å�½

    fmt.Println()



    r := []rune(s)

    for i := 0; i < len(r); i++ {

        fmt.Printf("%c", r[i])

    } //hello你好中国

}

在go语言中字符串都是以utf-8的编码格式进行存储的,所以每个中文占三个字节加上hello的5个字节所以长度为17,如果我们通过utf8.RuneCountInString函数获得的包含中文的字符串长度则与我们的直觉相符合。而且由于中文对于每个单独的字节来说是不可打印的,所以可以看到很多奇怪的输出,但是将字符串转为rune切片则没有问题。

多行定义

在Go语言中,使用双引号书写字符串的方式是字符串常见表达方式之一,被称为字符串字面量(string literal),这种双引号字面量不能跨行,如果想要在源码中嵌入一个多行字符串时,就必须使用`反引号,代码如下:

代码语言:txt
复制
const str = `第一行

第二行

第三行

\r\n

`

fmt.Println(str)

代码运行结果:

第一行

第二行

第三行

\r\n

反引号`,是键盘上 1 键左边的键,两个反引号间的字符串将被原样赋值到 str 变量中。

在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。

strings包

strings包提供了许多操作字符串的函数。在这里你可以看到都包含哪些函数https://golang.org/pkg/strings/

下面演示几个例子:

代码语言:txt
复制
func main() {

    var str string = "This is an example of a string"

    //判断字符串是否以Th开头

    fmt.Printf("%t\n", strings.HasPrefix(str, "Th"))

    //判断字符串是否以aa结尾

    fmt.Printf("%t\n", strings.HasSuffix(str, "aa"))

    //判断字符串是否包含an子串

    fmt.Printf("%t\n", strings.Contains(str, "an"))

}

strconv包

strconv包实现了基本数据类型与字符串之间的转换。在这里你可以看到都包含哪些函数https://golang.org/pkg/strconv/

下面演示几个例子:

代码语言:txt
复制
i, err := strconv.Atoi("-42") //将字符串转为int类型

s := strconv.Itoa(-42) //将int类型转为字符串

若转换失败则返回对应的error值。

字符串拼接

除了以上的操作外,字符串拼接也是很常用的一种操作,在go语言中有多种方式可以实现字符串的拼接,但是每个方式的效率并不相同,下面就对这几种方法进行对比。(关于测试的内容会放在后面的章节进行讲解,这里大家只要知道这些拼接方式即可)

1.SPrintf

代码语言:txt
复制
const numbers = 100



func BenchmarkSprintf(b \*testing.B) {

    b.ResetTimer()

    for idx := 0; idx < b.N; idx++ {

        var s string

        for i := 0; i < numbers; i++ {

            s = fmt.Sprintf("%v%v", s, i)

        }

    }

    b.StopTimer()

}

2.+拼接

代码语言:txt
复制
func BenchmarkStringAdd(b \*testing.B) {

    b.ResetTimer()

    for idx := 0; idx < b.N; idx++ {

        var s string

        for i := 0; i < numbers; i++ {

            s += strconv.Itoa(i)

        }

    }

    b.StopTimer()

}

3.bytes.Buffer

代码语言:txt
复制
func BenchmarkBytesBuf(b \*testing.B) {

    b.ResetTimer()

    for idx := 0; idx < b.N; idx++ {

        var buf bytes.Buffe

        for i := 0; i < numbers; i++ {

            buf.WriteString(strconv.Itoa(i))

        }

        \_ = buf.String()

    }

    b.StopTimer()

}

4.strings.Builder拼接

代码语言:txt
复制
func BenchmarkStringBuilder(b \*testing.B) {

    b.ResetTimer()

    for idx := 0; idx < b.N; idx++ {

        var builder strings.Builde

        for i := 0; i < numbers; i++ {

            builder.WriteString(strconv.Itoa(i))

        }

        \_ = builder.String()

    }

    b.StopTimer()

}

5.对比

代码语言:txt
复制
BenchmarkSprintf-8                68277         18431 ns/op

BenchmarkStringBuilder-8        1302448           922 ns/op

BenchmarkBytesBuf-8              884354          1264 ns/op

BenchmarkStringAdd-8             208486          5703 ns/op

可以看到通过strings.Builder拼接字符串是最高效的。

参考资料

http://shouce.jb51.net/gopl-zh/ch3/ch3-05.html

http://c.biancheng.net/view/17.html

https://github.com/datawhalechina/go-talent/blob/master/4.%E5%AD%97%E5%85%B8%E3%80%81%E5%AD%97%E7%AC%A6%E4%B8%B2.md

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字典
    • 字典定义
      • 字典操作
      • 字符串
        • 字符串定义
          • strings包
            • strconv包
              • 字符串拼接
              • 参考资料
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档