Go语言-数据

字符串

字符串是不可变字节(byte)序列,其本身是一个复合结构。内置函数len可以返回字节数组长度,cap不接受字符串类型参数。字符串的默认值为”“而不是nil。Go语言中使用‘`’防转义。支持”!=、==、<、>、+、+=”操作符。允许以索引访问字节数字(非字符),但不能获取元素地址。

func main(){
    str := "abcde"
    fmt.Println(str[1])
    fmt.Println(&str[1]) //错误
}

用for遍历字符串时,分byte与rune两种方式

package main
import(
    "fmt"
)
func main(){
    str := "abcde"
    for i := 0;i < len(str);i++{   //byte
        fmt.Printf("%d: [%c]\n",i,str[i])
    }
    for i,s := range str{        //rune:返回数组索引以及字符
        fmt.Printf("%d: [%c]\n",i,s)
    }
}

输出:
0: [a]
1: [b]
2: [c]
3: [d]
4: [e]
0: [a]
1: [b]
2: [c]
3: [d]
4: [e]

转换

要修改字符串,需将其转换为可变类型([]runne或[]byte),待转换完成再转换回来。但不管如何转换都必须重新分配内存,并复制数据。

str := "abcd"
str1 := []byte(str)   //转换为二进制[]byte类型
str2 := string(str1)  //再次转换为字符串

用append函数,可将string直接追加到[]byte内:

var s []byte
s = append(s,"abc"...)

用加法操作符拼接字符串时,每次都须重新分配内存。如此在构建“超大”字符串时,性能就显得极差,解决的办法就是预先分配足够大的内存。

Unicode

类型rune专门用来存储Unicode编码,它是int32的别名,使用单引号的字面量其默认类型就是rune。

package main
import(
    "fmt"
)
func main(){
    str := '中'
    fmt.Printf("%T",str)
}

输出:int32

数组

定义数组元素时,数组长度必须是非负整型常量表达式,长度是类型组成部分。也就是说类型相同但是长度不同的数组不属于同一类型。

package main
import(
    "fmt"
)
func main(){
    var a [4]int            //定义长度为4in同型数组,元素自动初始化为0
    b := [4]int{2,5}        //未提供初始值得元素自动初始化为0
    c := [4]int{5,3:10}     //可指定索引位置初始化
    d := [...]int{1,2,3}    //不指定数组长度,编译器按初始化数组元素的个数确定数组长度
    e := [...]int{10,3:100} //支持索引初始化,但数组长度与之相关
    fmt.Println(a,b,c,d,e)
    fmt.Println(len(a),len(b),len(c),len(d),len(e))
}

输出:
[0 0 0 0] [2 5 0 0] [5 0 0 10] [1 2 3] [10 0 0 100]
4 4 4 3 4
对于结构等复合类型,可省略元素初始化类型标签。

package main
import(
    "fmt"
)
func main(){
    type user struct{
        name string
        age int
    }
    d := [...]user{
        {"Tom",20},     //省略了类型标签
        {"Jarry",18},
    }
    fmt.Printf("%#v\n",d)
}

输出:
[2]main.user{main.user{name:”Tom”, age:20}, main.user{name:”Jarry”, age:18}}
在定义多维数组时,仅允许第一维度使用”…”

 a := [...][2]int{
    {1,2}.
    {3,4}
 }

内置函数len和cap都返回第一维度长度。当然数组也支持”==、!=”操作符。

指针

在Go语言里也有指针数据与数组指针,相信很多C++程序员对此已经很熟悉了。指针数组是指元素为指针类型的数组,数组指针是指针指向数组的地址。

x,y := 1,2
a := [...]int{&x,&y} //指针数组
p := &a              //存储数组地址的指针

复制

与C数组变量隐式作为指针使用不同,Go数组是值类型,赋值和传参操作都会复制整个数组数据。如果需要可改用指针或切片以此避免数据复制

package main
import(
    "fmt"
)
func test(x *[2]int){               
    fmt.Printf("x: %p, %v\n",x,*x)
    x[1] += 100
}
func main(){
    a := [2]int{10,20}
    test(&a)
    fmt.Printf("a: %p, %v\n",&a,a)
}

输出:
x: 0xc04203e1d0, [10 20]
a: 0xc04203e1d0, [10 120]
从输出我们可以看出地址相同,也就是说使用了同一个数组

切片

切片(slice)本身是一个只读对象,其工作机制类似数组指针的一种包装,可基于数组或者数组指针创建切片,以开始和结束索引位置确定所引用的数组片段。
x := […]int{0,1,2,3,4,5,6,7,8,9}

expressionslicelencap
x[:][0 1 2 3 4 5 6 7 8 9]1010
x[2:5][2 3 4]38
x[2:5:7][2 3 4]35
x[4:][4 5 6 7 8 9]66
x[:4][0 1 2 3]410
x[:4:6][0 1 2 3]46

属性cap表示切片所引用数组片段的真实长度,len用于限定可读的写元素数量。
与数组相同切片同样可以使用索引号访问元素内容。可直接创建切片对象,无需预先准备数组。因为是引用类型须使用make函数或显示初始化语句,它会自动完成底层数组内存分配。

package main
import(
    "fmt"
)
func main(){
    s1 := make([]int,3,5)  //制定len、cap,底层数据初始化为0
    s2 := make([]int,3)    //省略cap,和len相等
    s3 := []int{1,2,5:5}   //按初始化元素分配底层数组
    fmt.Println(s1,len(s1),cap(s1))
    fmt.Println(s2,len(s2),cap(s2))
    fmt.Println(s3,len(s3),cap(s3))
}

输出:
[0 0 0] 3 5
[0 0 0] 3 3
[1 2 0 0 0 5] 6 6
切片只是很小的结构体对象,用来代替数组传参可避免复制开销。还有make函数允许在运行期动态制定数组长度,绕开了数组类型必须使用编译期常量的限制。但是并非所有时候都适合用切片代替数组,因为切片底层数组可能会再堆上分配内存。而且小数组在栈上拷贝的消耗也未必就比make代价大。

reslice

将切片视作[cap]silce数据源,据此创建新切片对象。不能超出cap,但不受len限制。新建切片依旧指向原底层数组,也就是说修改对所有关联切片可见。

package main
import(
    "fmt"
)
func main(){
    d := [...]int{0,1,2,3,4,5,6,7,8,9}
    s1 := d[3:7]
    s2 := s1[1:3]
    for i := range s2{
        s2[i] += 10
    }
    fmt.Println(d)
    fmt.Println(s1)
    fmt.Println(s2)
}

输出:
[0 1 2 3 14 15 6 7 8 9]
[3 14 15 6]
[14 15]

append

向切片尾部(slice[len])添加数据,返回新的切片对象。如超出cap限制,则为新的切片对象重新分配数组。新分配的数组长度为原cap的两倍。向nil切片追加数据时,会为其分配底层数组数据。

package main
import(
    "fmt"
)
func main(){
    s := make([]int,0,100)
    s1 := s[:2:4]
    s2 := append(s1,1,2,3,4,5,6)      //超出s1 cap限制重新分配
    fmt.Printf("s1: %p: %v\n",&s1[0],s1)
    fmt.Printf("s2: %p: %v\n",&s2[0],s2)
    fmt.Printf("s data: %v\n",s[:10])
    fmt.Printf("s1 cap: %d,s2 cap: %d \n",cap(s1),cap(s2))
}

输出:
s1: 0xc04204c380: [0 0]
s2: 0xc0420460c0: [0 0 1 2 3 4 5 6] //数组地址不同,证明重新分配
s data: [0 0 0 0 0 0 0 0 0 0] //append并未向原数组写数据
s1 cap: 4,s2 cap: 8 //新数组是原数组cap的2倍

copy

在两个切片对象间复制数据,允许指向同一底层数组,允许目标区间重叠。最终复制长度以较短的切片长度(len)为准。

package main
import(
    "fmt"
)
func main(){
    s := []int{0,1,2,3,4,5,6,7,8,9}
    s1 := s[5:8]
    n := copy(s[4:],s1)      //同意底层数组的不同区间复制
    fmt.Println(n,s)
    s2 := make([]int,6)     
    m := copy(s2,s)          //不同数据间的复制
    fmt.Println(m,s2)
}

输出:
3 [0 1 2 3 5 6 7 7 8 9]
6 [0 1 2 3 5 6]

字典

字典是一种使用频率极高的数据结构,作为无序键值对集合,字典要求key必须支持相等运算符(==,!=)的数据类型,比如数字、字符串、指针、数组、结构体,以及对应的接口类型。
字典是引用类型,使用make函数或初始化表达语句来创建

package main
import(
    "fmt"
)
func main(){
    dict := make(map[string]int)  //使用make创建字典
    dict["a"] = 1                 //为字典赋值
    dict["b"] = 2
    dict2 := map[int]struct{      //使用初始表达式。值为匿名结构类型
        x int
    }{
        1 :{x:100},
        2 :{x:200},
    }
    fmt.Println(dict,dict2)
}

输出:
map[a:1 b:2] map[2:{200} 1:{100}]
基本操作演示

package main
import(
    "fmt"
)
func main(){
    dict := make(map[string]int)
    dict["a"] = 1
    dict["b"] = 2

    dict["a"] = 10                //修改
    dict["c"] = 3                 //新增
    if v,ok := dict["d"];ok{      //使用ok-idiom判断key是否存在,返回值
        fmt.Println(v)
    }
    fmt.Println(dict)
    delete(dict,"b")              //删除键值对。不存在时不会报错
    fmt.Println(dict)
}

输出:
map[a:10 b:2 c:3]
map[a:10 c:3]
访问不存在的键值,默认返回零值,不会引发错误,但推荐使用ok-idiom模式。万一存储的value本就是零呢!
函数len返回当前键值对数量,cap不解释字典类型。另外因内存访问安全和哈希算法缘故,字典被设计为“not addressable”,故不能直接修改value成员。正确的做法是返回整个value待修改完成后再设置字典键值,或者直接使用指针类型。

package main
import(
    "fmt"
)
type user struct{
    name string
    age int
}
func main(){
    m := map[int]user{
        1:{"Tom",20},
    }
    u := m[1]              //返回整个value值,修改后再赋值
    u.age += 1
    m[1] = u
    fmt.Println(m)
    m2 := map[int]*user{      //使用指针
        1:&user{"Jim",22},
    }
    m2[1].age += 1
    fmt.Println(m2[1])
}

输出:
map[1:{Tom 21}]
&{Jim 23}
注:
- 不能对nil字典进行写操作,但却能读
- 内容为空的字典与nil是不同的
- 在迭代期间删除或新增键值时安全的
- 运行时会对字典并发操作做出检测,如果某个任务正在对字典进行写操作,那么其它任务就不能对该字典进行并发操作,否则会造成进程崩溃,可用sync.RWMutex实现同步。正如C++中的加锁。
- 字典本身就是指针包装,传参时无需再次取地址
- 在创建时预先准备足够的空间有助于提升性能

结构

结构体(struct)将多个不同类型命名字段序列打包成一个复合类型。字段名必须唯一,可用”_”补位,支持使用自身指针类型成员。不管结构体包含多少字段,其内存总是一次性分配的,各字段在相邻的地址空间按定义顺序排列。

package main
import(
    "fmt"
)
type node struct{
    _ int
    id int
    next *node      //自身指针成员
}
func main(){
    n1 := node{
        id:1,
    }
    n2 := node{
        id:2,
        next:&n1,
    }
    fmt.Println(n1,n2)
}

输出:
{0 1 } {0 2 0xc0420443c0}
可按顺序初始化全部字段,或使用命名方式初始化指定字段。

package main
import(
    "fmt"
)

func main(){
    type user struct{
        name string
        age int
    }
    u1 := user{"Tom",12}
    fmt.Println(u1)

}

输出:
{Tom 12}
可直接定义匿名结构类型变量,或者作为字段类型,但因其缺少类型标识,在作为字段类型时无法直接初始化。

package main
import(
    "fmt"
)

func main(){
    u := struct{            //直接定义匿名结构变量
        name string
        age int
    }{
        name:"Tom",
        age:18,
    }

    type file struct{
        name string
        attr struct{      //定义匿名结构类型字段
            owner int
            perm int
        }
    }
    f := file{
        name:"Test",
    }
    f.attr.owner = 1
    f.attr.perm = 2
    fmt.Println(u,f)
}

输出:
{Tom 18} {Test {1 2}}

- 只有所在字段全部支持时,才能做相等操作
- 可使用指针直接操作结构字段,但不能是多级指针

空结构

空结构(struct{})是指没有字段的结构类型。它比较特殊,因为无论是其自身,还是作为数组类型其长度均为零。空结构可作为通道元素类型,用于事件通知。

func main{
    exit := make(chan struct{})
    go func(){
        fmt.Println("hello word!")
        exit <- struct{}{}
    }()
    <-exit
}

匿名字段

匿名字段是指没有名字,仅有类型的字段,也被称作嵌入字段或嵌入类型。

type attr struct{
    perm int
}
type file struct{
    name string
    attr         //匿名字段,仅有类型名
}

匿名字段只是隐式的以类型名作为字段名字,可直接引用匿名字段的成员,但初始化时须当作独立字段。不仅仅是结构体,除接口指针和多级指针以外的任何命名类型都可作为匿名字段。
不能将基础类型和其指针类型同时嵌入,因为两者隐式名字相同。

字段标签

字段标签(tag)并不是注释,而是用来对字段进行描述的元数据。它不属于数据成员但却是类型的组成部分。在运行期可使用反射获取标签信息。它常被用作格式校验,数据库关系映射等。

type user struct{
    name string `姓名`  //字段标签
    age int `年龄`
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值