go圣经笔记

121目录​​​​​​​

2.程序结构

2.1命名

2.2声明

2.3变量

2.3.1. 简短变量声明

2.3.2. 指针

2.3.3. new函数

​​​​​​​2.3.4. 变量的生命周期

10.包和工具

10.1包简介

10.2. 导入路径


2.程序结构

2.1命名

  1. 大写字母和小写字母是不同的名字,如hand和Hand
  2. 关键字不能用于自定义名字

如:break default func if else

  1. 预定义名字

内建常量:true false nil

内建类型:int uint float complex bool byte error string  rune

内建函数:make len cap copy close new append delete complex  real imag panic  recover

预定义名字不能用于定义名字

  1. 4.驼峰式命名

QuoteRuneToASCII和parseRequestLine这样的函数命名,大小写来分隔而不是下划线;

像ASCII和HTML这样的缩略词则避免使用大小写混合的写法。

2.2声明

  1. Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。
  2. 在包一级声明语句声明的名字可在整个包对应的每个源文件中访问,相比之下,局部声明的名字就只能在函数内部很小的范围被访问。
  3. 一个函数的声明由一个函数名字、参数列表(由函数的调用者提供参数变量的具体值)、一个可选的返回值列表和包含函数定义的函数体组成。
  4. 在局部定义的常量作为调用函数的参数。

2.3变量

  1. var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。变量声明的一般语法如下:
  2. var 变量名字 类型 = 表达式
  3.  其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。
  4. 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、指针、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
  5. 也可以在一个声明语句中同时声明一组变量,或用一组初始化表达式声明并初始化一组变量。
  6. 如果省略每个变量的类型,将可以声明多个类型不同的变量(类型由初始化表达式推导):
var i, j, k int                 // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string

5. 

  1. 初始化表达式可以是字面量或任意的表达式。
  2. 在包级别声明的变量会在main入口函数执行前完成初始化(§2.6.2),局部变量将在声明语句被执行到的时候完成初始化。
  3. 一组变量也可以通过调用一个函数,由函数返回的多个返回值初始化

2.3.1. 简短变量声明

  1. “名字 := 表达式”用于声明初始化局部变量。
  2. 变量的类型根据表达式来自动推导。

2.3.2. 指针

  1. 一个指针的值是另一个变量的地址
  2. *int,指针被称之为“指向int类型的指针”。
  3. 如果指针名字为p,那么可以说“p指针指向变量x”,或者说“p指针保存了x变量的内存地址”。
  4. *p表达式对应p指针指向的变量的值。这里为int类型的值。

2.3.3. new函数

  1. 另一个创建变量的方法:调用内建的new函数
  2. new(T):T类型的匿名变量,如new(int),初始化为零值
  3. 每次调用new函数都是返回一个新的变量的地址
  4. new只是一个预定义的函数,也可以将new被定义为int类型的变量名,但在函数内部无法使用内置的new函数

​​​​​​​2.3.4. 变量的生命周期

  1. 指的是在程序运行期间变量有效存在的时间段

2.6包和文件

  • 一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中。
  • 每个包对应一个独立的名字空间。如:要引用Decode函数,在image包中的Decode函数和在unicode/utf16包中的 Decode函数是不同的。要在外部引用该函数,必须显式使用image.Decode或utf16.Decode形式访问。
  • 如果一个名字是大写字母开头,那么这个名字是可导出的
  • 每个源文件都是以包的声明语句开始,用来指明包的名字。
  • 包级别的常量名都是以大写字母开头,他们可以被外部代码访问

2.6.1导入包

例如gopl.io/ch2/tempconv包的名字一般是tempconv。要使用gopl.io/ch2/tempconv包,需要先导入:

//cf为绑定的包的别名

gopl.io/ch2/cf

package main

import (
    "fmt"
    "os"
    "strconv"

    "gopl.io/ch2/tempconv"
)

如果导入了一个包,但是又没有使用该包将被当作一个编译错误处理。这种强制规则可以有效减少不必要的依赖。 

 2.6.2包的初始化

包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化:

var a = b + c // a 第三个初始化, 为 3
var b = f()   // b 第二个初始化, 为 2, 通过调用 f (依赖c)
var c = 1     // c 第一个初始化, 为 1

func f() int { return c + 1 }

对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,还有一些没有初始化表达式的,例如某些表格数据初始化并不是一个简单的赋值过程。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数

func init() { /* ... */ }
  • 这样的init初始化函数除了不能被调用或引用。
  • 如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。
  • 初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依赖的包都已经完成初始化工作了。

3.基础数据类型

Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。

本章介绍基础类型:包括:数字、字符串和布尔型。

3.1整型
 

  • 有符号整数类型:int8、int16、int32和int64,分别对应8、16、32、64bit大小的有符号整数,无符号整数类型:uint8、uint16、uint32和uint64。
  • unincode字符rune类型是和int32等价的类型。这两个名称可以互换使用。
  • byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据
  • 无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针
  • 即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,
  • 有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位
  • Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照优先级递减的顺序排列:
*      /      %      <<       >>     &       &^
+      -      |      ^
==     !=     <      <=       >      >=
&&
||
  •  取模运算符%仅用于整数间的运算。
  • 不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit位是1的话,那么最终结果可能是负的,例如int8的例子:
  • var u uint8 = 255
    fmt.Println(u, u+1, u*u) // "255 0 1"
    
    var i int8 = 127
    fmt.Println(i, i+1, i*i) // "127 -128 1"

两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型。

==    等于
!=    不等于
<     小于
<=    小于等于
>     大于
>=    大于等于

 两个相同类型的值可以用==和!=进行比较。

对于整数,+x是0+x的简写,-x则是0-x的简写;对于浮点数和复数,+x就是x,-x则是x 的负数。

Go语言还提供了以下的bit位操作运算符,前面4个操作运算符并不区分是有符号还是无符号数:

&      位运算 AND
|      位运算 OR
^      位运算 XOR
&^     位清空(AND NOT)
<<     左移
>>     右移

^作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;

&^用于按位置零(AND NOT)

它使用了Printf函数的%b参数打印二进制格式的数字;其中%08b中08表示打印至少8个字符宽度,不足的前缀部分用0填充:

var x uint8 = 1<<1 | 1<<5
var y uint8 = 1<<1 | 1<<2

fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
fmt.Printf("%08b\n", y) // "00000110", the set {1, 2}

fmt.Printf("%08b\n", x&y)  // "00000010", the intersection {1}
fmt.Printf("%08b\n", x|y)  // "00100110", the union {1, 2, 5}
fmt.Printf("%08b\n", x^y)  // "00100100", the symmetric difference {2, 5}
fmt.Printf("%08b\n", x&^y) // "00100000", the difference {5}

for i := uint(0); i < 8; i++ {
    if x&(1<<i) != 0 { // membership test
        fmt.Println(i) // "1", "5"
    }
}

fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6}
fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}

Go语言提供了无符号数的运算,但即使数值本身不可能出现负数,我们还是倾向于使用有符号的int类型.

如果len函数返回一个无符号数,那么i也将是无符号的uint类型,然后条件i >= 0则永远为真。在三次迭代之后,也就是i == 0时,i--语句将不会产生-1,而是变成一个uint类型的最大值(可能是$2^64-1$),然后medals[i]表达式运行时将发生panic异常(§5.9),也就是试图访问一个slice范围以外的元素: 

medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
    fmt.Println(medals[i]) // "bronze", "silver", "gold"
}

无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等 

算术和逻辑运算的二元操作中必须是相同的类型:

在很多场景,会遇到类似下面代码的常见的错误:

var apples int32 = 1
var oranges int16 = 2
var compote int = apples + oranges // compile error

当尝试编译这三个语句时,将产生一个错误信息:

invalid operation: apples + oranges (mismatched types int32 and int16)

这种类型不匹配的问题可以有几种不同的方法修复,最常见方法是将它们都显式转型为一个常见类型:

var compote = int(apples) + int(oranges)

任何大小的整数字面值都可以用以0开始的八进制格式书写,例如0666;或用以0x或0X开头的十六进制格式书写,例如0xdeadbeef。十六进制数字可以用大写或小写字母。

如今八进制数据通常用于POSIX操作系统的文件访问权限标志;

十六进制数字则更强调数字值的bit位模式。

当使用fmt包打印一个数值时,我们可以用%d、%o或%x参数控制输出的进制格式,就像下面的例子:

	o:=0666
	//%之后的[1]副词告诉Printf函数再次使用第一个操作数。第二,%后的#副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。
	fmt.Printf("%d %[1]o %#[1]o\n",o)// "438 666 0666",printf要控制输出格式如换行字符\n,println无法控制格式会自动换行
	fmt.Printf("%d %[1]x %#[1]x %#[1]X\n",x)
	// Output:
	// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF

 字符使用%c参数打印,或者是用%q参数打印带单引号的字符:

	fmt.Printf("%d %[1]c %[1]q\n",ascii)//"97 a 'a'"
	fmt.Printf("%d %[1]c %[1]q\n",unicode)//"22269 国 '国'"
	fmt.Printf("%d %[1]q\n",newline)//"10 '\n'"

4.1数组

数组是一个固定长度的特定类型元素组成的序列,一个数组可以由菱格或多个元素组成,因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩的动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组。数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度➖1的位置。内置的len函数将返回数组中元素的个数。

func main(){
	var a[3]int//3个整数integers
	fmt.Println(a[0])//打印第一个元素
	fmt.Println(a[len(a)-1])//打印最后一个元素,a[2]

	//遍历的打印数组a的索引下标和元素
	for i,v:=range a{
		fmt.Printf("%d %d\n",i,v)
	}

	//只打印元素
	for _,v:=range a{
		fmt.Println("%d\n",v)
	}
}

默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0.我们也可以使用数组字面值来初始化数组:

	var q [3]int=[3]int{1,2,3}
	var r [3]int=[3]int{1,2}
	fmt.Println(r[2])//"0"
	fmt.Println(q[2])

 在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。因此,上面q数组的定义可以简化为

    p:=[...]int{1,2,3}
	fmt.Println("%T\n",p)//"[3]int",%T输出值的类型

 数组的长度是数组类型的恶意个组成部分,因此[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、map和结构体字面值的写法都很相似。上面的形式是直接提供顺序初始化值序列,但是也可以指定一个索引和对应值列表的方式初始化,就像下面这样:

	type Currency int
	const(
		USD Currency =iota//美元
		EUR //欧元
		GDP //英镑
		RMB//人民币
	)
	//USD等常量为索引,字符为索引对应的值
	symbol:=[...]string{USD:"$",EUR:"€",GDP:"£",RMB:"¥"}
	fmt.Println(RMB,symbol[RMB]) // "3 ¥"

在这种形式的数组字面值形式中,初始化索引的顺序是无关紧要的,而且没用到的索引可以省略,和前面提到的规则一样,为指定初始值的元素将用零值初始化,例如,

r := [...]int{99: -1}

 定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其他元素都是用0初始化。

如果一个数组的元素类型是可以互相比较的,那么数组类型也是可以相互比较的,这时候我们剋可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。不相等比较运算符!=遵循同样的规则。

作为一个真实的例子,crypto/sha256包的Sum256函数对一个人一的字节slice类型的数据生成一个对应的信息摘要。消息摘要有256bit大小,因此对应[32]byte数据类型。如果两个消息摘要是相同的,那么可以认为两个消息本身也是相同;如果消息摘要不同,那么消息本身必然也是不同的。下面的例子用SHA256算大分别生成“x”和“X”两个信息的摘要:

上面例子中,两个消息虽然只有一个字符的差异,但是生成的消息摘要///

4.2  Slice

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分组成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。​​​​​​​

要注意的是slice类型的变量s和数组类型的变量a的初始化语法差异。slice和数组的字面语法很相似,它们都是用花括弧包含一些列的初始化元素,但是对于slice并没有指明序列的长度。这回隐式的创建一个合适大小的数组,然后slice的指针指向底层的数组,就像数组字面值一样,slice的字面值也可以按顺序制定初始化值序列,或是通过索引和元素值指定,或者用两种风格的混合语法初始化。

和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是都含有全部相等元素。

slice唯一合法的比较操作是和nil比较,例如:

if summer == nil { /* ... */ }

一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0,但是也有非nil值的slice长度和容量也是0的,例如[]int或make([]int,3)[3:]。与任意类型的nil值一样,我们可以用[]int(nil)。一个nil值的slice并没有底层数组。

如果需要测试一个slice是否是空的,使用len(s)来判断,而不应该用s==nil来判断。出了和nil相等比较外,一个nil值的slice的行为其它任意0长度的一样。

内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。

4.2.1append函数

内置的append函数用于向slice追加元素:

更新slice变量不仅对调用append函数是必要的,实际上对任何可能导致长度、容量或底层数组变化的操作都是必要的。要正确的使用slice,需要记住尽管底层数组的元素是渐渐访问的,但是slice对应结构本身的指针、长度和容量部分是直接访问的。要更新这些信息需要

4.3Map

哈希表是一种巧妙并且实用的数据结构。他是一个无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。

在go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,但是key和value之间可以是不同的数据类型。其中K对应的key必须是支持比较运算符==的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但是讲浮点数用作key类型则是一个坏的想法,正如第三章提到的,最坏的情况可能是出现的NaN和任何浮点数都不相等。对于V对应的value数据类型则没有任何的限制。

内置的make函数可以创建一个map:

Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一个遍历的顺序都不相同。这是故意的,么次都适用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,我们必须显示的对key进行排序,可以使用sort包的strings函数对字符串slice进行排序。

4.5JSON 

JavaScript对象表示法(JSON)是一种用于发送和接受结构化信息的标准协议。在类似的协议中,JSON并不是唯一的一个标准协议。XML、ASN.1和Google的Protocol Buffers缓冲协议都是类似的协议,并且有着各自的特色,但是由于简洁性、可读性和流行程度等原因,JSON是应用最广泛的一个。Go语言对谢谢标准格式的编码和解码都有良好的支持,由标准库中的encoding/json、encoding/xml、encoding/asn1等包提供支持,并且这类包都有着类似的API接口。本节,我们将对重要的encoding/json包的用法做个概述。

Json是对JavaScript中各种类型的值--字符串、数字、布尔值和对象-- Unicode文本编码。它可以用有效可读的方式表示第三章的基础数据类型和本章的数组、slice、结构体和map等聚合数据类型。

基本的Json类型有数字、布尔值、字符串,其中字符串是以双引号包含的Unicode字符序列,支持和Go语言类似的反斜杠转义特性,不过Json使用的是\Uhhh转义数字来表示一个UTF-16码点较大的字符需要用4个字节表示;而且UTF-16还有大端和小端的问题),而不是Go语言的Rune类型。

这些基础类型可以通过Json和对象类型进行递归组合。一个Json数组是一个有序的值序列,写在一个方括号中并以逗号分隔;一个Json数组可以用于编码Go语言的数组和slice。一个json对象是一个字符串到值的映射,写成一系列的name:value对形式,用花括号包含以逗号分隔;JSon的对象类型可以用于编码Go语言的map类型和结构图。

考虑一个应用程序,该程序负责收集各种电影评论并提供反馈功能。它的Movie数据类型和一个典型的表示电影的值列表如下所示。

10.包和工具

10.1包简介

  1. 每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问
  2. 每个包还通过控制包内名字的可见性和是否导出来实现封装特性。
  3. 第一点,所有导入的包必须在每个文件的开头显式声明
  4. 禁止包的环状依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。
  5. 编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。

10.2. 导入路径

  1. 每个包是由一个全局唯一的字符串所标识的导入路径定位。出现在import语句中的导入路径也是字符串。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值