Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1868999
  • 博文数量: 152
  • 博客积分: 3730
  • 博客等级: 上尉
  • 技术积分: 3710
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-02 14:36
个人简介

减肥,运动,学习,进步...

文章分类

全部博文(152)

文章存档

2016年(14)

2015年(17)

2014年(16)

2013年(4)

2012年(66)

2011年(35)

分类: 云计算

2016-05-30 22:02:45

多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠,复制一个slice只是对底层的数组创建了一个新的slice别名。一个slice由三部分构成:指针、长度、和容量。指针指向第一个slice元素对应的底层数组元素的地址。长度对应slice中元素的数目,长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。如果切片操作超出cap的上限将导致一个panic异常,但是超过len()则意味着扩展了slice.

对于其他类型的slice,必须自己展开每个元素进行比较。由于slice的元素是间接引用。一个固定值的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改。

在map中的元素并不是一个变量,因此不能对map的元素进行取地址操作。原因是map可能对着元素数量的增长而重新分配更大的内存空间。从而导致之前的地址无效。

map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序,在实践中遍历的顺序是随机的,每一次遍历的顺序都可能不同,这是故意的,使得每次随机的遍历顺序可以强制要求程序不依赖具体的哈希函数实现。如果要按照顺序遍历key/value.必须显式地对key进行排序。

通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value,如果key不存在,那么将得到value对应类型的零值。可以通过value, ok:= a[xxx],通过判断ok的值。

结构体是一种聚合的数据类型,是由零个或者多个任意类型的值聚合成的实体。每个值都称为结构体的成员。结构体变量的成员可以通过点操作符访问,也可以对成员取地址,然后通过指针访问。点操作符也可以和指向结构体的指针一起工作。结构体成员的输入顺序也很重要。不同顺序的成员对应不同的结构体类型。如果结构体成员名字是以大写字母开头的,那么该成员就是导出的,这是Go语言导出规则决定的,一个结构体可能同时包含导出和未导出的成员结构体类型的零值是每个成员都是零值。通常会将零值作为最合理的默认值。如果结构体没有任何成员,就是空结构体,协作struct {},大小为0。不不包含任何信息。但有时也是有价值的。比如可以通过空结构体作为map的value值来凸显key的作用。

在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用的原始变量。

结构体嵌入机制可以让一个命名的结构体包含另一个结构体类型的匿名成员,这样就可以通过简单的点运算符x.f来访问匿名成员链中嵌套的x.f.e.f成员。Go语言有一个特性可以只声明一个成员对应的数据类型而不指名成员名,这类成员就叫匿名成员,匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针(可以认为是减少写代码的方式,采用简短形式访问)。可以直接访问叶子属性而不需要给出完整的路径。匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,会导致名字冲突。所有匿名成员也有可见性的规则约束。在包内,即使不导出也是可以采用简短形式访问匿名成员嵌套的成员。但在包外部,不能访问不导出的成员变量

new这是一个用来分配内存的内建函数,但是不像在其它语言中,它并不初始化内存,只是将其置零。也就是说, new(T) 会为T 类型的新项目,分配被置零的存储,并且返回它的地址,一个类型为*T 的值由于new 返回的内存是被置零的,这会有助于你将数据结构设计成,每个类型的零值都可以使用,而不需要进一步初始化。这意味着,数据结构的用户可以使用new 来创建数据,并正确使用。

内建函数make(T, args ) 与new(T) 的用途不一样。它只用来创建slice,map和channel,并且返回一个初始化的(而不是置零),类型为T 的值(而不是*T )。这三个类型的背后是象征着,对使用前必须初始化的数据结构的引用。make 只用于map,slice和channel,并且不返回指针。要获得一个显式的指针,使用new 进行分配,或者显式地使用一个变量的地址

在Go中,数组是值。将一个数组赋值给另一个,会拷贝所有的元素。特别是,如果你给函数传递一个数组,其将收到一个数组的拷贝,而不是它的指针。数组的大小是其类型的一部分。类型[10]int 和[20]int 是不同的。数组为值这样的属性,可以很有用处,不过也会有代价;如果你希望类C的行为和效率,可以传递一个数组的指针。

切片持有对底层数组的引用,如果你将一个切片赋值给另一个,二者都将引用同一个数组。如果函数接受一个切片参数,那么其对切片的元素所做的改动,对于调用者是可见的,好比是传递了一个底层数组的指针。

因为把返回的函数赋给了一个变量,虽然函数在执行完一瞬间会销毁其执行环境,但是如果有闭包的话,闭包会保存外部函数的活动对象(变量),所以如果不把对闭包的引用消除掉,闭包会一直存在内存中,垃圾收集器不会销毁闭包占用的内存。闭包通过引用的方式使用外部函数的变量。


package main

import (
    "fmt"
)

func B() []func() {
    b := make([]func(), 3, 3)

    for i := 0; i < 3; i++ {
        b[i] = func() {
            fmt.Println(i) //是i的引用,执行时已经i=3,因此都是打印3
        }
    }

    return b
}

只调用了一次函数B,构成一个闭包,i 在外部函数B中定义,所以闭包维护该变量 i ,c[0]、c[1]、c[2]中的 i 都是闭包中 i 的引用。因此执行c:=B()后,i 的值已经变为3,故再调用c[0]()时的输出是3而不是0.


func C() []func() {
    c := make([]func(), 3, 3)

    for i := 0; i < 3; i++ {
        c[i] = func(j int) func() {
            return func() {
                fmt.Println(j)
            }
        }(i) //局部变量往内部传,因此每次打印的都是局部变量
    }
来的,在执行defer时计算
    return c
}

func D() {
    for j := 0; j < 3; j++ {
        defer (func() {
            fmt.Println(j) //输出,闭包,打印的也是j的引用,也就是最后的3,因此输出3个3,注意闭包是对外部变量的引用。
        })()
    }
}

func E() {
    for j := 0; j < 3; j++ {
        defer (func(i int) {
            fmt.Println(i)
        })(j) //defer调用是的参数是实时计算出,但输出顺序是调用的倒序
    }
}

func main() {
    b := B()
    b[0]()
    b[1]()
    b[2]()

    c := C()
    c[0]()
    c[1]()
    c[2]()
   D()
    E()
}

输出结果如下:
[ `go run closetest.go` | done: 3.6087882s ]
  3 <<----闭包是外部变量的引用
  3
  3
  0 <<---解决闭包的局部变量引用问题
  1
  2
  3  <<---闭包引用了局部变量,执行时i已经是3,因此全部输出3
  3
  3
  2  <<---逆序输出
  1
  0

阅读(2735) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~