五. go 常见数据结构实现原理之 string, iota

一. string

  1. golang中对string的解释:
  1. 8比特字节的集合
  2. 可以为空(长度为0),但不会是nil
  3. string对象不可以修改
  1. 查看string 数据结构: string数据结构跟切片有些类似,例如byte切片,只不过切片还有一个表示容量的成员
//src/runtime/string.go:stringStruct
type stringStruct struct {
	//字符串的首地址
    str unsafe.Pointer
    //字符串的长度
    len int
}
  1. string 拼接源码
// 生成一个新的string,返回的string和切片共享相同的空间
func rawstring(size int) (s string, b []byte) { 
    p := mallocgc(uintptr(size), nil, false)
    stringStructOf(&s).str = p
    stringStructOf(&s).len = size
    *(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}
    return
}

其它问题

  1. string为什么是不可修改的: Go的实现中string不包含内存空间,只有一个内存的指针,好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝, 因为string通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所以才有了string不可修改的约定
  2. []byte转换成string一定会拷贝内存吗: byte切片转换成string时并不会拷贝内存,而是直接返回一个string,这个string的指针(string.str)指向切片的内存

二. iota

基础

  1. iota是个特殊常量,可以在const中生成自增的整数序列,每个const块中使用时从0开始,并且在每一行常量声明中递增1(不包括空行和注释,但是包括"_"下划线),但不会分配给任何常量。 如果有表达式,iota会作为表达式的一部分进行计算。下方LOG_EMERG值为0,每个常量递增1
type Priority int
const (
    LOG_EMERG Priority = iota //0
    LOG_ALERT //1
    LOG_CRIT //2
    LOG_ERR //......
    LOG_WARNING
    LOG_NOTICE
    LOG_INFO
    LOG_DEBUG //7
)
  1. 注意点1
  1. 如果在iota下声明的某一个常量没有继续沿用iota,而是手动指定了一个值,那么声明的这个变量就是手动指定的值
  2. 如果iota声明的常量序列中,某一个常量手动设置了值,该变量下方相邻的其它变量如果没有重新指定位iota,并且没有手动设置其他值时,那么会继续沿用上方手动指定的值,入下方代码d设置为ss,下方常量e的值也是ss
  3. 在一个iota声明常量序列中,整个序列中每声明一个常量,不管内部有没有真实使用iota设置,iota都是累加,如下方代码d,e使用的是手动设置的ss,但是iota还是累加了这两次,最终g为5,h为6
const (
	a = iota //0
	b //1
	c //2
	//如果在iota下声明的某一个常量没有继续沿用iota
	//而是手动指定了一个值,那么声明的这个变量就是指定的值
	//并且该变量下方继续声明的变量,入下方的"e"在不重新指定回iota时,
	//并且没有手动设置其他值时,那么该变量值与上方指定的值相同,
	//所以下方e的值也是ss
	d = "ss" //ss
	e //ss
	//在一个iota声明的变量序列中,虽然上方有的变量没有使用iota,iota还是会默认加一
	//重新将iota赋值给g
	g = iota //5
	h //6
)
  1. iota与位移
const (
	a = iota //使用iota初始值,所以a等于0
	b
	c
	d = 1 << iota //表示iota左移1为,当前iota为3左移1位值为8
	e //继续沿用上方的逻辑,iota此时变为4左移1位值位16
	f //继续沿用上方逻辑,iota此时为5左移1位置位32
	g = iota //重新设置回iota,那么g的值就是6
	h = 1e6
)

const (
	a= 1 << iota //表示iota左移1位,当前iota值为0,左移1位等于1
	b //延续上方iota左移运算,iota此时为1左移1位等于2
	c //iota此时为2,左移1位等于4
	d = iota //重新设置会iota值为3
	e //4
	f= 1e6
)
  1. 下方每个常量值是多少:
    mutexLocked = 1, mutexWoken =2;mutexStarving = 4;mutexWaiterShift = 3;starvationThresholdNs =1000000
const (
	//iota初始值为0,
	//然后执行<<左移操作,将一个数的二进制表示向左移动指定的位数,当前也就是移动1位,mutexLocked 为1
    mutexLocked = 1 << iota // mutex is locked
    mutexWoken
    mutexStarving
    mutexWaiterShift = iota
    starvationThresholdNs = 1e6
)
  1. 下方每个常量值是多少:(下划线也会累加iota)

bit0 = 1, mask0 = 0, bit1 = 2, mask1 = 1, bit3 = 8, mask3 = 7

const (
    bit0, mask0 = 1 << iota, 1<<iota - 1
    bit1, mask1
    _, _
    bit3, mask3
)
  1. 根据以上问题引出iota的定义: iota代表了const声明块的行索引(下标从0开始)
const (
    bit0, mask0 = 1 << iota, 1<<iota - 1   //const声明第0行,即iota==0
    bit1, mask1                            //const声明第1行,即iota==1, 表达式继承上面的语句
    _, _                                   //const声明第2行,即iota==2
    bit3, mask3                            //const声明第3行,即iota==3
)0行的表达式展开即bit0, mask0 = 1 << 0, 1<<0 - 1,所以bit0 == 1,mask0 == 0;
第1行没有指定表达式继承第一行,即bit1, mask1 = 1 << 1, 1<<1 - 1,所以bit1 == 2,mask1 == 1;
第2行没有定义常量
第3行没有指定表达式继承第一行,即bit3, mask3 = 1 << 3, 1<<3 - 1,所以bit0 == 8,mask0 == 7

原理

  1. const块中每一行在GO中使用spec数据结构描述,spec声明如下
    ValueSpec struct {
        Doc     *CommentGroup // associated documentation; or nil
        Names   []*Ident      // value names (len(Names) > 0)
        Type    Expr          // value type; or nil
        Values  []Expr        // initial values; or nil
        Comment *CommentGroup // line comments; or nil
    }
  1. 当前只关注ValueSpec.Names,该切片中保存了一行中定义的常量,如果一行定义N个常量,那么ValueSpec.Names切片长度即为N, const块实际上是spec类型的切片,用于表示const中的多行, 伪代码,可以清晰的看出iota实际上是遍历const块的索引,每行中即便多次使用iota,其值也不会递增
    for iota, spec := range ValueSpecs {
        for i, name := range spec.Names {
            obj := NewConst(name, iota...) //此处将iota传入,用于构造常量
            ...
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值