《Go 语言程序设计》读书笔记 (一)基础类型和复合类型

前言

最近在读《Go 语言程序设计》这本书想经过看书巩固一下本身的基础知识,把已经积累的点经过看书学习再编织成一个网,这样看别人写的优秀代码时才能更好理解。当初工做中须要使用 Go开发项目时看了网上很多教程,好比 uknown 翻译的《the way to go》看完基本上每次使用遇到不会的时候还会再去翻阅,此次把书中的重点还有一些平时容易忽视的Go语言中各类内部结构(类型、函数、方法)的一些行为整理成读书笔记。git

由于《Go 语言程序设计》不是针对初学者的,因此我只摘选最重要的部分并适当补充和调换描述顺序力求用最少的篇幅描述清楚每一个知识点。github

《Go 语言程序设计》在线阅读地址:yar999.gitbooks.io/gopl-zh/con…数组

若是刚接触 Go建议先去读 《the-way-to-go》在线阅读地址:github.com/unknwon/the…缓存

命名:

  • 函数名、变量名、常量名、类型名、包名等全部的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面能够跟任意数量的字母、数字或下划线。安全

  • 大写字母和小写字母是不一样的:heapSort和Heapsort是两个不一样的名字。bash

  • 关键字不可用于命名数据结构

  • break      default       func     interface   select
    case       defer         go       map         struct
    chan       else          goto     package     switch
    const      fallthrough   if       range       type
    continue   for           import   return      var
    复制代码
  • 推荐驼峰式命名函数

  • 名字的开头字母的大小写决定了名字在包外的可见性。若是一个名字是大写字母开头的,那么它能够被外部的包访问,包自己的名字通常老是用小写字母。学习

声明:

  • Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数。

变量:

  • var声明语句能够建立一个特定类型的变量,而后给变量附加一个名字,而且设置变量的初始值。变量声明的通常语法以下:ui

    var 变量名字 类型 = 表达式
    复制代码

    其中“类型”或“= 表达式”两个部分能够省略其中的一个。若是省略的是类型信息,那么将 根据初始化表达式来推导变量的类型信息。若是初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每一个元素或字段都是对应该类型的零值。

    零值初始化机制能够确保每一个声明的变量老是有一个良好定义的值,所以在Go语言中不存在未初始化的变量。这个特性能够简化不少代码,并且能够在没有增长额外工做的前提下确保边界条件下的合理行为。例如:

    var s string
    fmt.Println(s) // ""
    复制代码

字符串:

  • 文本字符串一般被解释为采用UTF8编码的Unicode码点(rune)序列。

  • 内置的len函数能够返回一个字符串中的字节数目(不是rune字符数目),索引操做s[i]返回第i个字节的字节值,i必须知足0 ≤ i< len(s)条件约束。

  • 字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,固然咱们也能够给一个字符串变量分配一个新字符串值。能够像下面这样将一个字符串追加到另外一个字符串:

    s := "left foot"
    t := s
    s += ", right foot"
    复制代码

    这并不会致使原始的字符串值被改变,可是变量s将由于+=语句持有一个新的字符串值,可是t依然是包含原先的字符串值。

    由于字符串是不可修改的,所以尝试修改字符串内部数据的操做也是被禁止的:

    s[0] = 'L' // compile error: cannot assign to s[0]
    复制代码
  • 每个UTF8字符解码,不论是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,若是遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符'\uFFFD',在印刷中这个符号一般是一个黑色六角或钻石形状,里面包含一个白色的问号"�"。当程序遇到这样的一个字符,一般是一个危险信号,说明输入并非一个完美没有错误的UTF8字符串。

  • 字符串的各类转换:

    string接受到[]rune的类型转换,能够将一个UTF8编码的字符串解码为Unicode字符序列:

    // "program" in Japanese katakana
    s := "プログラム"
    fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0"
    r := []rune(s)
    fmt.Printf("%x\n", r)  // "[30d7 30ed 30b0 30e9 30e0]"
    复制代码

    (在第一个Printf中的% x参数用于在每一个十六进制数字前插入一个空格。)

    若是是将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码:

    fmt.Println(string(r)) // "プログラム"
    复制代码

    将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:

    fmt.Println(string(65))     // "A", not "65"
    fmt.Println(string(0x4eac)) // "京"
    复制代码

    若是对应码点的字符是无效的,则用'\uFFFD'无效字符做为替换:

    fmt.Println(string(1234567)) // "�"
    复制代码

复合数据类型:

  • 基本数据类型,它们能够用于构建程序中数据结构,是Go语言的世界的原子。以不一样的方式组合基本类型能够构造出复合数据类型。咱们主要讨论四种类型——数组、slice、map和结构体,数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据须要动态增加。

数组:

  • 数组的长度是数组类型的一个组成部分,所以[3]int和[4]int是两种不一样的数组类型。数组的长度必须是常量表达式,由于数组的长度须要在编译阶段肯定。

    q := [3]int{1, 2, 3}
    q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
    复制代码

Slice:

  • 长度对应slice中元素的数目;长度不能超过容量,容量通常是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。

  • x[m:n]切片操做对于字符串则生成一个新字符串,若是x是[]byte的话则生成一个新的[]byte。

  • slice并非一个纯粹的引用类型,它其实是一个相似下面结构体的聚合类型:

    type IntSlice struct {
        ptr      *int
        len, cap int
    }
    复制代码

Map:

  • 在Go语言中,一个map就是一个哈希表的引用,map类型能够写为map[K]V,其中K和V分别对应key和value。map中全部的key都有相同的类型,全部的value也有着相同的类型,可是key和value之间能够是不一样的数据类型。

  • map中的元素并非一个变量,所以咱们不能对map的元素进行取址操做:

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

    禁止对map元素取址的缘由是map可能随着元素数量的增加而从新分配更大的内存空间,从而可能致使以前的地址无效。

  • map上的大部分操做,包括查找、删除、len和range循环均可以安全工做在nil值的map上,它们的行为和一个空的map相似。可是向一个nil值的map存入元素将致使一个panic异常:

    ages["carol"] = 21 // panic: assignment to entry in nil map
    复制代码

    在向map存数据前必须先建立map。

  • 和slice同样,map之间也不能进行相等比较;惟一的例外是和nil进行比较。要判断两个map是否包含相同的key和value,咱们必须经过一个循环实现。

结构体:

  • 下面两个语句声明了一个叫Employee的命名的结构体类型,而且声明了一个Employee类型的变量dilbert:

    type Employee struct {
        ID        int
        Name      string
        Address   string
        DoB       time.Time
        Position  string
        Salary    int
        ManagerID int
    }
    
    var dilbert Employee
    复制代码

    dilbert结构体变量的成员能够经过点操做符访问,好比dilbert.Name和dilbert.DoB。由于dilbert是一个变量,它全部的成员也一样是变量,咱们能够直接对每一个成员赋值:

    dilbert.Salary -= 5000 // demoted, for writing too few lines of code
    复制代码

    或者是对成员取地址,而后经过指针访问:

    position := &dilbert.Position
    *position = "Senior " + *position // promoted, for outsourcing to Elbonia
    复制代码
  • 若是结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员。未导出的成员只能在包内部访问,在外部包不可访问。

  • 结构体类型的零值中每一个成员其类型的是零值。一般会将零值做为最合理的默认值。例如,对于bytes.Buffer类型,结构体初始值就是一个随时可用的空缓存,还有sync.Mutex的零值也是有效的未锁定状态。有时候这种零值可用的特性是天然得到的,可是也有些类型须要一些额外的工做。

  • 由于结构体一般经过指针处理,能够用下面的写法来建立并初始化一个结构体变量,并返回结构体的地址:

    pp := &Point{1, 2}
    复制代码
  • Go语言有一个特性让咱们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下面的代码中,Circle和Wheel各自都有一个匿名成员。咱们能够说Point类型被嵌入到了Circle结构体,同时Circle类型被嵌入到了Wheel结构体。

    type Circle struct {
        Point
        Radius int
    }
    
    type Wheel struct {
        Circle
        Spokes int
    }
    复制代码

    得益于匿名嵌入的特性,咱们能够直接访问叶子属性而不须要给出完整的路径:

    var w Wheel
    w.X = 8            // equivalent to w.Circle.Point.X = 8
    w.Y = 8            // equivalent to w.Circle.Point.Y = 8
    w.Radius = 5       // equivalent to w.Circle.Radius = 5
    w.Spokes = 20
    复制代码
  • 外层的结构体不只仅是得到了匿名成员类型的全部成员,并且也得到了该类型导出的所有的方法。这个机制能够用于将一个有简单行为的对象组合成有复杂行为的对象。

相关文章
相关标签/搜索