《Go语言圣经》阅读笔记

《Go语言圣经》电子书
《The Go Programming Language》源码

之前看了《go by example》似乎有必要找一本go的书看一下。

第一章 入门

Hello, World

Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析。

  • 函数的左括号{必须和func函数声明在同一行上,且位于末尾,不能独占一行。
  • 而在表达式x + y中,可在+后换行,不能在+前换行(译注:以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误)。

命令行参数

自增语句i++给i加1;这和i += 1以及i = i + 1都是等价的。对应的还有i--给i减1。它们是语句,而不像C系的其它语言那样是表达式。

  • 所以j = i++非法,而且++--都只能放在变量名后面,因此--i也非法。
func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

s += sep + arg中,每次每次循环迭代字符串s的内容都会更新。+=连接原字符串、空格和下个参数,产生新字符串,并把它赋值给s。s原来的内容已经不再使用,将在适当时机对它进行垃圾回收。

第二章 程序结构

在Go语言中,返回函数中局部变量的地址也是安全的。例如下面的代码,调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。

var p = f()

func f() *int {
    v := 1
    return &v
}

编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。

func f() {
    var x int
    x = 1
    global = &x
}

func g() {
    y := new(int)
    *y = 1
}

f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量*y将是不可达的,也就是说可以马上被回收的。因此,y并没有从函数g中逃逸,编译器可以选择在栈上分配y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。

第三章 基础数据类型

Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。本章介绍基础类型,包括:数字、字符串和布尔型。复合数据类型——数组和结构体——是通过组合简单类型,来表达更加复杂的数据结构。引用类型包括指针、切片、字典、函数、通道,虽然数据种类很多,但它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。

3.5. 字符串

一个字符串是一个不可改变的字节序列。

3.6. 常量

  • iota的用法
type Weekday int
const (
    Sunday Weekday = iota		//0
    Monday						//1
    Tuesday						//2
    Wednesday					//3
    Thursday					//4
    Friday						//5
    Saturday					//6
)

type Flags uint
const (
    FlagUp Flags = 1 << iota // 1 is up 
    FlagBroadcast            // 2 supports broadcast access capability
    FlagLoopback             // 4 is a loopback interface
    FlagPointToPoint         // 8 belongs to a point-to-point link
    FlagMulticast            // 16 supports multicast access capability
)
  • 关于常量的类型,与类型转换,如下例子
const a float32 = 1.1111111111111111
const b = 1.1111111111111111

var t1 float64 = a	// panic:cannot use a (type float32) as type float64 in assignment
var t2 float64 = float64(a)
var t3 float64 = b

//---------------------------------------------------
var f float64 = 212
fmt.Println((f - 32) * 5 / 9)     // "100"; (f - 32) * 5 is a float64
fmt.Println(5 / 9 * (f - 32))     // "0";   5/9 is an untyped integer, 0
fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float

第四章 复合数据类型

4.1. 数组

  • 定义数组可以用如下方式
var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}

//b长度为2
b := [...]int{1, 2}
//定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其它元素都是用0初始化。
r := [...]int{99: -1}
  • 对函数来说,对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。如下例子
func zero(ptr *[32]byte) {	//将数组ptr清零,传递的时候也要传递数组的引用
    for i := range ptr {
        ptr[i] = 0
    }
}

4.2. Slice

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址

	months := [...]string{1:"January", 12:"December"}	//数组
	fmt.Printf("%T\n", months)	// [13]string
	fmt.Printf("%T", months[:]) // []string
  • 如果切片操作超出cap(nums )的上限将导致一个panic异常,但是超出len(nums)则是意味着扩展了slice,因为新slice的长度会变大:
	nums := [...]int{1, 2, 3, 4, 5, 6, 7}
	slice1 := nums[1:3]	// [2 3]
	slice2 := nums[1:10] // invalid slice index 10 (out of bounds for 7-element array)
	slice3 := slice1[:4] // [2 3 4 5]
  • 因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名。
	var nums []int
	nums = append(nums, 1)
	nums1 := nums
	nums[0] = 3 //修改nums也会修改nums1
  • 测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。
var s []int    // len(s) == 0, s == nil
s = nil        // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{}    // len(s) == 0, s != nil
  • 在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的。
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

4.2.1. append函数

func appendInt(x []int, y int) []int {
    var z []int
    zlen := len(x) + 1
    if zlen <= cap(x) {
        // There is room to grow.  Extend the slice.
        z = x[:zlen]
    } else {
        // There is insufficient space.  Allocate a new array.
        // Grow by doubling, for amortized linear complexity.
        zcap := zlen
        if zcap < 2*len(x) {
            zcap = 2 * len(x)
        }
        z = make([]int, zlen, zcap)
        copy(z, x) // a built-in function; see text
    }
    z[len(x)] = y
    return z
}

func main() {
	nums1 := []int{1,2,3}
	fmt.Println(len(nums1), cap(nums1))	// 3 3
	nums2 := appendInt(nums1, 1)
	fmt.Println(len(nums2), cap(nums2)) // 4 6
	nums3 := appendInt(nums2, 1)
	fmt.Println(len(nums3), cap(nums3)) // 5 6
	//nums2与nums3引用同一个底层数组,即len(nums2) < len(nums3) 但是cap(nums2) == cap(nums2)
	
	nums1[0] = 100
	fmt.Println(nums1, nums2, nums3)	// [100 2 3] [1 2 3 1] [1 2 3 1 1]
	nums2[0] = 200
	fmt.Println(nums1, nums2, nums3)	// [100 2 3] [200 2 3 1] [200 2 3 1 1] 
}

4.3. Map

  • 创建map的方法,只要是可以通过==比较的类型就可以作为key
ages := make(map[string]int) // mapping from strings to ints
ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}
  • 删除map中元素的方法。
    delete(ages, "alice") // remove element ages["alice"]

  • 禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。
    _ = &ages["bob"] // compile error: cannot take address of map element

  • Map的value类型也可以是一个聚合类型,比如是一个map或slice。其中addEdge函数惰性初始化map是一个惯用方式,也就是说在每个值首次作为key时才初始化。addEdge函数显示了如何让map的零值也能正常工作;即使from到to的边不存在,graph[from][to]依然可以返回一个有意义的结果。

var graph = make(map[string]map[string]bool)

func addEdge(from, to string) {
    edges := graph[from]
    if edges == nil {
        edges = make(map[string]bool)
        graph[from] = edges
    }
    edges[to] = true
}

func hasEdge(from, to string) bool {
    return graph[from][to]
}

4.4. 结构体

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==!=运算符进行比较。相等比较运算符==将比较两个结构体的每个成员。可比较的结构体类型和其他可比较的类型一样,可以用于map的key类型。

  • 结构体的申明定义的三种方式
type Node struct {
	a int 
	b int
}

func main() {
	var node1 Node
	node1.a = 1
	node1.b = 2
	
	node2 := Node{1, 2}
	node3 := Node{a : 1, b : 2}
	fmt.Println(node1, node2, node3)
}

第八章 Goroutines和Channels

一、Go语言的由来      Go语言亦叫Golong语言,是由谷歌Goggle公司推出。Go语言的主要开发者有:肯.汤姆逊(Ken Thompson)、罗布.派克(Rob Pike)和罗伯特.格里泽默(Robert Griesemer)。这三个都是大神,稍介绍一下他们的贡献:     肯.汤姆逊(Ken Thompson):图灵奖得主,Uinx发明人,B语言作者(C语言前身),还做飞行员,后来被谷歌挖走。     罗布.派克(Rob Pike):Unix团队和Plan 9操作系统计划的成员,与Ken老爷子共事多年,并共创出广泛使用的UTF-8 字元编码。     罗伯特.格里泽默(Robert Griesemer):曾协助制作Java的HotSpot编译器,和Chrome浏览器的JavaScript引擎V8。     膜拜一下大神的容颜:Ken老爷子(左),Rob Pike(右)         二、开发Go语言的初衷     根据Go语言开发者自述,近10多年,从单机时代的C语言到现在互联网时代的Java,都没有令人满意的开发语言,而 C++往往给人的感觉是,花了100%的经历,却只有60%的开发效率,产出比太低,Java和C#的哲学又来源于C++。并且,随着硬件的不断升级,这些语言不能充分的利用硬件及CPU。因此,一门高效、简洁、开源的语言诞生了。 三、Go语言的特点    Go语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发速度和易维护性,有人形容Go语言:Go = C + Python , 说明Go语言既有C静态语言程序的运行速度,又能达到Python动态语言的快速开发。 Go语言有以下特性: 1.自动垃圾回收     C/C++最头疼的就是指针问题,一不小心就野指针了或者又越界了。在Go语言里再也不用担心,也不用考虑delete或者free,系统自动会回收。 2.函数可以返回多个值     这个很神奇,大多数语言只能返回一个值,Go语言可以返回多个值。这个功能使得开发者再不用绞尽脑汁的想到底怎么返回值的设计,也不用为了传值专门定义一个结构体。 3.并发编程     Go语言天然并发,只需要关键字“go”就可以让函数并发执行,使得并发编程变得更为简单,这也是Go语言最大的优势。 四、Go语言能做什么开发     Go语言是非常有潜力的语言,是因为它的应用场景是目前互联网非常热门的几个领域,比如区块链开发、大型游戏服务端开发、分布式/云计算开发。像Goggle、阿里、京东等互联网公司都开始用Go语言开发自己的产品。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值