《The Way to Go》Go语言入门笔记【1】(一到四章)

整理参考自:the-way-to-go_ZH_CN/01.2.md at master · unknwon/the-way-to-go_ZH_CN · GitHub

 一  概览

1 优点:快速编译,高效执行,易于开发。类型安全和内存安全的编程语言。虽然 Go 语言中仍有指针的存在,但并不允许进行指针运算。对于网络通信、并发和并行编程的极佳支持

2 代码的可读性是软件工程里最重要的一部分。作为强类型语言,隐式的类型转换是不被允许的,记住一条原则:让所有的东西都是显式的。这是第一门完全支持 UTF-8 的编程语言。

3 Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。值得注意的是,因为垃圾回收和自动内存分配的原因,Go 语言不适合用来开发对实时性要求很高的软件。

4 优点总结:

  • 简化问题,易于学习

  • 内存管理,简洁语法,易于使用

  • 快速编译,高效开发

  • 高效执行

  • 并发支持,轻松驾驭

  • 静态类型

  • 标准类库,规范统一

  • 易于部署

  • 文档全面

  • 免费开源

二 环境与配置

1 Go 不存在自我引导之类的功能。因此如果使用一个有不同指令集的编译器来构建 Go 程序,就需要针对操作系统和处理器架构(32 位操作系统或 64 位操作系统)进行区别对待。

2 从 Go 1 版本开始,gc 和 gccgo 在编译方面都有等价的功能

3 当你在创建目录时,文件夹名称永远不应该包含空格,而应该使用下划线 "_" 或者其它一般符号代替。

4 几个最为重要的环境变量:

  • $GOROOT 表示 Go 在你的电脑上的安装位置,它的值一般都是 $HOME/go,当然,你也可以安装在别的地方。

  • $GOARCH 表示目标机器的处理器架构,它的值可以是 386、amd64 或 arm。

  • $GOOS 表示目标机器的操作系统,它的值可以是 darwin、freebsd、linux 或 windows。

  • $GOBIN 表示编译器和链接器的安装位置,默认是 $GOROOT/bin,如果你使用的是 Go 1.0.3 及以后的版本,一般情况下你可以将它的值设置为空,Go 将会使用前面提到的默认值。

5 垃圾回收器 Go 拥有简单却高效的标记-清除回收器。它的主要思想来源于 IBM 的可复用垃圾回收器,旨在打造一个高效、低延迟的并发回收器。Go 的部署工作还是要比 Java 和 Python 轻松得多。因为 Go 不需要依赖任何其它文件,它只需要一个单独的静态文件

6 开发环境注意的几点:能够通过代码模版来简化编码过程从而提升编码速度。拥有断点、检查变量值、单步执行、逐过程执行标识库中代码的能力。能够对项目或包中的代码建立抽象语法树视图(AST-view)。

7 按照下面的一些有用的方法来达到基本调试的目的:

  • 在合适的位置使用打印语句输出相关变量的值(print/printlnfmt.Print/fmt.Println/fmt.Printf)。

  • fmt.Printf 中使用下面的说明符来打印有关变量的相关信息:

    • %+v 打印包括字段在内的实例的完整信息

    • %#v 打印包括字段和限定类型名称在内的实例的完整信息

    • %T 打印某个类型的完整说明

  • 使用 panic 语句来获取栈跟踪信息(直到 panic 时所有被调用函数的列表)。

  • 使用关键字 defer 来跟踪代码执行过程

8 遵循统一的代码风格是 Go 开发中无可撼动的铁律,因此你必须在编译或提交版本管理系统之前使用 `gofmt` 来格式化你的代码。

9 常用工具

  • go doc工具会从 Go 程序和包文件中提取顶级声明的首行注释以及每个对象的相关注释,并生成相关文档。
  • go install 是安装 Go 包的工具,类似 Ruby 中的 rubygems。主要用于安装非标准库的包文件,将源代码编译成对象文件。

  • go fix 用于将你的 Go 代码从旧的发行版迁移到最新的发行版,它主要负责简单的、重复的、枯燥无味的修改工作,

  • go test 是一个轻量级的单元测试框架。

10 Go 语言与 C 语言的性能差距大概在 10%~20% 之间,如果说 Go 比 C++ 要慢 20%,那么 Go 就要比任何非静态和编译型语言快 2 到 10 倍,并且能够更加高效地使用内存。任何一种语言都有其所擅长和薄弱的方面,Go 和 Python 在一般开发的平均水平测试中,Go 要比 Python 3 快 25 倍左右,少占用三分之二的内存,但比 Python 大概多写一倍的代码。

三 基本类型与语法

1 如果文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。

2 Go 代码中会使用到的 25 个关键字或保留字

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

 之所以这么少,为了简化在编译过程第一步中的代码解析。和其它语言一样,关键字不能够作标识符使用。除了以上,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数。

package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。所有的包名都应该使用小写字母

4 导入包短且优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义)

import (
   "fmt"
   "os"
)

5 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是它们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

6  包也可以作为命名空间使用,帮助避免命名冲突(名称冲突),如 pack1.Thing;可以通过使用包的别名来解决包名之间的名称冲突,如  import fm "fmt"。

main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。

8 符合规范的函数一般写成如下的形式:

func functionName(parameter_list) (return_value_list) {
   …
}
  • parameter_list 的形式为 (param1 type1, param2 type2, …)

  • return_value_list 的形式为 (ret1 type1, ret2 type2, …)

9  只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。

10 程序正常退出的代码为 0 即 Program exited with code 0;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。

11  注释不会被编译,但可以通过 godoc 来使用。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段

12

注释例子1

// Package superman implements methods for saving the world.
//
// Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world.
package superman

注释例子2:

// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() error {
   ...
}

13  函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如:

func FunctionName (a typea, b typeb) typeFunc

 可以在函数体中的某处返回使用类型为 typeFunc 的变量 var:

return var

14 编程格式的基本思路

  • 在完成包的 import 之后,开始对常量、变量和类型的定义或声明

  • 如果存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。

  • 如果当前包是 main 包,则定义 main 函数

  • 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。

package main

import (
   "fmt"
)

const c = "C"

var v int = 5

type T struct{}

func init() { // initialization of package
}

func main() {
   var a int
   Func1()
   // ...
   fmt.Println(a)
}

func (t T) Method1() {
   //...
}

func Func1() { // exported function Func1
   //...
}

15 强转:一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 int16 转换为 int32)。当从一个取值范围较大的转换到取值范围较小的类型时,会发生精度丢失(截断)的情况。当编译器捕捉到非法的类型转换时会引发编译时错误,否则将引发运行时错误。

16 常量的值必须是能够在编译时就能够确定的;你可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()

  • 正确的做法:const c1 = 2/3

  • 错误的做法:const c2 = getNumber()

17  枚举

const (
    a = iota
    b = iota
    c = iota
)

18 当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。

19 一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用。在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。  以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏,你任何的操作都只会影响内部代码块的局部变量。

20  var n int64 = 2 这种写法主要用于声明包级别的全局变量,全局变量允许声明但不使用,当你在函数体内声明局部变量时,应使用简短声明语法 :=

21 复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。这个内存地址被称之为指针。

指针属于引用类型,其它的引用类型还包括 slices,maps和 channel。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。

22 如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a

23 变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。init 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 backend()

func init() {
   // setup preparations
   go backend()
}

24 && 和 || 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(&& 左边的值为 false,|| 左边的值为 true),运算符右边的表达式将不会被执行。利用这个性质,如果你有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧以减少不必要的运算。

25 对于布尔值的好的命名能够很好地提升代码的可读性,例如is 或者 Is 开头的 isSortedisFinishedisVisible,使用这样的命名能够在阅读代码的获得阅读正常语句一样的良好体验

26 这些类型的长度都是根据运行程序所在的操作系统类型所决定的:

  • intuint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。

  • uintptr 的长度被设定为足够存放一个指针即可。

 27 由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。应该尽可能地使用 float64,因为 math 包中所有有关数学运算的函数都会要求接收这个类型。

28 增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘

29 %d 用于格式化整数%x%X 用于格式化 16 进制表示的数字),%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法)

30  位左移 <<

  • 用法:bitP << n

  • bitP 的位向左移动 n 位,右侧空白部分使用 0 填充;如果 n 等于 2,则结果是 2 的相应倍数,即 2 的 n 次方。 例如 1 << 10 // 等于 1 KB。使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:

type ByteSize float64
const (
	_ = iota // 通过赋值给空白标识符来忽略值
	KB ByteSize = 1<<(10*iota)
	MB
	GB
	TB
	PB
	EB
	ZB
	YB
)

31 rand实现了伪随机数的生成。rand.Float32 rand.Float64 返回介于 [0.0, 1.0) 之间的伪随机数,其中包括 0.0 但不包括 1.0。函数 rand.Intn 返回介于 [0, n) 之间的伪随机数。你可以使用 rand.Seed(value) 函数来提供伪随机数的生成种子。

32  字符只是整数的特殊用例。byte 类型是 uint8 的别名,在 ASCII 码表中,A 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的:

var ch byte = 'A' 
var ch byte = 65 
var ch byte = '\x41'

另外一种可能的写法是 \ 后面紧跟着长度为 3 的 8 进制数,例如:\377

33  Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。其实 rune 也是 Go 当中的一个类型,并且是 int32 的别名。在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'

34 包 unicode 包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):

  • 判断是否为字母unicode.IsLetter(ch)

  • 判断是否为数字unicode.IsDigit(ch)

  • 判断是否为空白符号unicode.IsSpace(ch)

35   字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组

  • \n:换行符

  • \r:回车符

  • \t:tab 键

  • \u\U:Unicode 字符

  • \\:反斜杠自身

非解释字符串

该类字符串使用反引号括起来,支持换行,例如:

  `This is a raw string \n` 中的 `\n\` 会被原样输出

注意事项: 获取字符串中某个字节的地址的行为是非法的,例如:&str[i]。 

在循环中使用加号 + 拼接字符串并不是最高效的做法,更好的办法是使用函数 strings.Join(),使用字节缓冲(bytes.Buffer)拼接更加给力

36  Go 中使用 strings 包来完成对字符串的主要操作

Contains 判断字符串 `s` 是否包含 `substr
Replace  用于将字符串 `str` 中的前 `n` 个字符串 `old` 替换为字符串 `new`,

 ToLower  将字符串中的 Unicode 字符全部转换为相应的小写字符:  

 strings.Fields(s) 将会利用 1 个或多个空白符号来作为动态长度的分隔符将字符串分割成若干小块,并返回一个 slice,如果字符串只包含空白符号,则返回一个长度为 0 的 slice。

 strings.Split(s, sep) 用于自定义分割符号来对指定字符串进行分割,同样返回 slice

 Join 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串

 strings.NewReader(str) 用于生成一个 Reader 并读取字符串中的内容,然后返回指向该 Reader 的指针,从其它类型读取内容的函数还有:

  • Read() 从 []byte 中读取内容。

  • ReadByte()ReadRune() 从字符串中读取下一个 byte 或者 rune。

与字符串相关的类型转换都是通过 strconv 包实现的

  • strconv.Itoa(i int) string 返回数字 i 所表示的字符串类型的十进制数。

  • strconv.Atoi(s string) (i int, err error) 将字符串转换为 int 型。

37   当前时间可以使用 time.Now() 获取,或者使用 t.Day()t.Minute() 等等来获取时间的一部分;如果你需要在应用程序在经过一定时间或周期执行某项任务(事件处理的特例),则可以使用 time.After 或者 time.Ticker。time.Sleep(d Duration) 可以实现对某个进程(实质上是 goroutine)时长为 d 的暂停。

// The time must be 2006-01-02 15:04:05
fmt.Println(t.Format("02 Jan 2006 15:04")) // 21 Dec 2011 08:52

38 指针

  •  取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。这个地址可以存储在一个叫做指针的特殊数据类型中。我们可以这样声明它:
var intP *int

intP = &i1
  • 一个指针变量可以指向任何一个值的内存地址。它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。
  •  可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用
  • 当一个指针被定义后没有分配到任何变量时,它的值为 nil。一个指针变量通常缩写为 ptr
  • 对于任何一个变量 var, 如下表达式都是正确的:var == *(&var)
  • 指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。
  • 在大多数情况下 Go 语言可以轻松创建指针,并且隐藏间接引用,如:自动反向引用对一个空指针的反向引用是不合法的,并且会使程序崩溃。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值