-
Go 语言原生支持 Unicode,它可以处理全世界任何语言的文本
-
一个函数的声明由 func 关键字、函数名、参数列表、返回值列表以及包含在大括号里的函数体组成
-
Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字 break、continue、fallthrough或 return 中的一个、运算符和分隔符 ++、--、)、] 或 } 中的一个)
-
和大多数编程语言类似,区间索引时,Go 语言里也采用左闭右开形式,即,区间包括第一个索引元素,不包括最后一个
-
对 string 类型,+
运算符连接字符串
-
j=i++
非法,而且 ++
和 --
都只能放在变量名后面,因此 --i
也非法
-
空标识符(blank identifier),即 _(也就是下划线),可用于在任何语法需要变量名但逻辑程序不需要的时候使用
-
Printf 有一大堆转换,Go程序员称之为动词(verb),常见:
动词 |
说明 |
%d |
十进制整数 |
%x , %o , %b |
十六进制,八进制,二进制整数 |
%f , %g , %e |
浮点数:3.141593 3.141592653589793 3.141593e+00 |
%t |
布尔:true或false |
%c |
字符(rune)(Unicode码点) |
%s |
字符串 |
%q |
带双引号的字符串"abc"或带单引号的字符'c' |
%v |
变量的自然形式(natural format) |
%T |
变量的类型 |
%% |
字面上的百分号标志(无操作数) |
-
实参通过值的方式传递(函数的形参是实参的拷贝) -->> 对形参进行修改不会影响实参;
如果实参包括引用类型,如指针、slice、map、function、channel 等类型,实参可能会由于函数的间接调用被修改;例如,map 作为参数传递给某函数时,该函数接收这个引用的一份拷贝(copy,或译为副本),被调用函数对 map 底层数据结构的任何修改,调用者函数都可以通过持有的 map 引用看到(类似于 C++ 里的引用传递)
-
bufio.Scanner
、ioutil.ReadFile
和 ioutil.WriteFile
都使用 *os.File
的 Read
和 Write
方法,但是,大多数程序员很少需要直接调用那些低级(lower-level)函数
-
变量声明的一般语法:var variableName type = initExpr
,其中 type
和 initExpr
可以省略其中之一,但不可都省略;
-
元组赋值,另一种形式的赋值语句,允许同时更新多个变量的值;在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值
-
Go 语言习惯在 if
中处理错误然后直接返回,如此可以保证正常执行的语句且不需要代码缩进
f, err := os.Open(fname)
if err != nil {
return err
}
f.ReadByte()
f.Close()
-
uintptr
类型,一种整数类型,没有指定具体的 bit 大小,但是足以容纳指针;
uintptr
只有在底层编程时才需要,特步是 Go 语言和 C 语言函数库或操作系统接口相交互的地方;
-
位清空运算符 &^
,用于按位置零(AND NOT):如果对应 y
中 bit 位为 1 的话,表达式 z = x &^ y
的结果是 z
中的对应的 bit 位为 0,否则 z
中对应的 bit 位等于 x
中的对应的 bit 位
-
一个字符串是一个不可改变的字节序列(),可以包含任意的数据,包括 byte 值 0,但是通常是用来包含人类可读的文本;
-
Go 语言中,str[i]
并以一定是字符串 str
的第 i
个字符,而是第 i
个字节,这样的设计使得处理 UTF-8 编码的文本更加容易,因为一个字符可能会由多个字节组成
-
字符串,不可变序列,意味着两个字符串共享相同的底层数据也是安全的,且复制任何长度的字符串的代价都是低廉的,没有必要分配新内存
-
字符串,切片操作,s[i:j]
,产生一个新的字符串,也可以安全地共享相同的内存,没有必要分配新内存
-
Go 语言中,常量表达式的值在编译期计算,而不是在运行期,且每种常量的潜在类型都是基础类型:boolean
、string
或 number
;常量在运行期可以防止在运行期被意外或恶意地修改;
-
Go 语言中,iota
是一个预声明的标识符,用于表示连续的无类型整数常量,它的初始值是 0,每次被调用都会自动加 1;
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
-
数组是一个由固定长度的特定类型元素组成的序列,可以由零个或多个元素组成;
如果在数组字面值中,数组长度(常量)位置出现的是 ...
省略号,则表示数组的长度是根据初始化值的个数来计算;
如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,可以通过 ==
比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的;
函数参数传递的机制导致传递大的数组类型将是低效的,且对数组参数的任何修改都是发生在复制的数组上,并不能直接修改调用时的原始数组;
-
切片(slice),可以增长和收缩的动态序列(每个元素都有相同的类型),切片是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且切片的底层确实引用一个数组对象,由三部分构成:指针、长度和容量;
切片之间不能比较,唯一合法的比较操作是和 nil
比较;如果需要测试一个 slice 是否为空,可使用 len(s) == 0
来判断;
内置的 make
函数创建一个指定元素类型、长度和容量的 slice,其中容量部分可以省略,此时容量等于长度: make([]T, len) <==> make([]T, len, len)
.
在底层,make
创建了一个匿名的数组变量,然后返回一个 slice 指向这个数组;只有通过返回的 slice 才能引用底层的数组变量;
内置的 append
函数可以向 slice 尾部添加元素,当 slice 的底层数组没有足够的空间存放新添加的元素时,append
会创建一个新的更大的数组来存放新元素;返回的 slice 会指向这个新创建的数组;
-
Stack 可以通过 slice 模式实现
func push(stack []int, value int) []int {
stack = append(stack, value)
return stack
}
func top(stack []int) int {
return stack[len(stack)-1]
}
func pop(stack []int) []int {
stack = stack[:len(stack)-1]
return stack
}
func remove(stack []int, index int) []int {
if index >= len(stack) {
return stack
}
copy(stack[index:], stack[index+1:])
return stack[:len(stack)-1]
}
func displayStack(stack []int) {
fmt.Printf("[ ")
for _, val := range stack {
fmt.Printf("%d ", val)
}
fmt.Printf("]\n")
}
-
哈希表(hash),一种巧妙且使用的数据结构,是一个无序的 {key: value}
对的集合,其中所有的 key
都是不同的,且给定的 key
可以在常数时间复杂度内检索、更新或删除对应的 valud
;Go 语言中,Map 是对 hash 表的引用,可写为 map[Key]Value
,其中 key
必须是支持 ==
比较运算的数据类型(不推荐使用浮点型作为 key
),value
对数据类型没有任何限制;
内置的 delete
函数可以从 map 中删除元素: delete(map, key)
;如果 key
不在 map 中,delete
函数也是安全的;
和 slice 一样,map 之间不能进行相等比较,唯一的例外是和 nil
比较;要判断两个 map 是否包含相同的 key 和 value,必须通过一个循环实现;
Go 语言中没有提供 set 类型,但是 map 的 key 可以用来模拟 set 的功能;
-
Go 语言中,所有的函数参数都是值拷贝传递的,函数参数将不再是函数调用时的原始变量,而是它的一份副本,函数参数和原始变量将会引用不同的值;
-
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,即可用 ==
或 !=
进行比较;
-
组合 是 Go 语言中面向对象编程的核心
-
将一个 Go 语言中类似 movies
的结构体 slice 转换成 JSON 的过程叫编组(marshaling),并且只有结构体中被导出的成员才会被编码,JSON 包只会编码结构体中的可导出成员,并且默认使用成员的名字作为 JSON 数据的键;可通过 json.Marshal
完成;
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
}
var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
{Title: "Cool Hand Luke", Year: 1967, Color: true,
Actors: []string{"Paul Newman"}},
{Title: "Bullitt", Year: 1968, Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
}
data, err := json.Marshal(movies)
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
Marshal 函数返回一个编码后的字节 slice,包含很长的字符串,且没有空白缩进;可以使用 json.MarshalIndent
函数生成整齐缩进的输出;
// ...
data, err := json.MarshalIndent(movies, "", " ")
// ...
将 JSON 数据解码为 Go 语言中的数据结构,叫做解组(unmarshaling),可通过 json.Unmarshal
完成;
var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"
-
在函数调用时,Go 语言没有默认参数值,也没有任何方法可以通过参数名指定形参,即形参和返回值的变量名对于函数调用者没有任何意义;
-
Go 语言中如果没有函数体的函数声明,表示该函数不是以 Go 实现的,而是由非 Go 语言实现的;func Sin(x float64) float64
-
Go 语言使用可变栈,栈的大小按需增加(初始时很小),栈的大小限制在 1GB 以内,这使得我们在使用递归时不必担心溢出和安全问题;
-
错误处理策略:
-
传播错误:如果函数内部没有处理错误,那么错误就会被传播到函数的调用者,函数的调用者也可能不处理,这样错误就会继续传播;
当错误被转播到最顶层时(例如,main 函数)处理错误,错误信息应该提供清晰的从原因到后果的因果链;由于错误信息经常是以链式组合在一起的,所以错误信息中应该避免大写、换行和标点符号,以免破坏错误信息的格式;
即可一般而言,被调用函数 f(x)
会将调用信息和参数信息作为发生错误时的上下文放在错误信息中并返回给调用者,调用者需要添加一些错误信息中不包含的信息;
-
重试失败的操作:如果错误的发生是偶然性的,或由不可预知的问题导致的,一个明智的选择是重新尝试失败的操作,且需要限制重试的时间间隔、重试次数,防止无限制的重试;
-
输出错误信息并结束程序:如果程序不能继续运行,那么应该输出错误信息并结束程序;
NOTE: 该策略只应在 main 函数中执行,而不应该在库函数中执行;库函数应该仅向上传播错误,除非该错误意味着程序内部包含不一致性,即遇到了 BUG,才能在库函数中结束程序;
-
只输出错误信息:不需要中断程序的运行,可以通过 log
包的函数或标准错误流输出错误信息;
-
忽略错误:如果错误不重要,可以忽略错误,但是需要在注释中说明忽略的原因;
-
Go 语言中,函数类型的零值是 nil
,调用值为 nil
的函数值会引起 panic 错误;
函数值可以与 nil
比较,但是两个函数值只有在它们都是 nil
的情况下才相等;
函数值之间是不可比较的,也不能用函数值作为 map 的 key;
-
当匿名函数需要被递归调用时,我们需要先声明一个变量,再将匿名函数赋值给这个变量;否则,函数字面量无法与其自身绑定;
-
参数数量可变的函数,称为可变参数函数;在生命可变参数函数时,需要在参数列表的最后一个参数类型前加上省略符号 ...
,这表示该函数会接收任意数量的该类型参数;
在函数体中,可变参数被看作相应类型的切片 []T
;虽然在可变参数函数内部中 ...int
型参数的行为看起来像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的;
如果原始参数已是 slice 类型,只需在最后一个参数后加上省略符号 ...
即可. 例如 values := []int {1, 2, 3, 4}; sum(values...)
;
可变参数函数,将常被用于格式化字符串,例如 fmt.Printf
和 log.Printf
;
-
可以在一个函数中执行多条 defer 语句,且执行顺序与声明顺序相反(类似于栈的先进后出);
defer 语句中的函数会在 return 语句更新返回值变量后再执行,且 defer 语句中的函数可能会读取/修改有名返回值;
defer 语句中的函数在其所在的 goroutine 即将终止时执行,而不是在 go
语句的时候就执行;
defer 语句中的函数参数会在执行 defer 语句时被计算,而不是在实际调用时计算;
通过 defer 机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放;
调试复杂程序时,defer 机制也常用于记录何时进入和退出函数;
-
Go 语言中,方法调用过程中,接收器(监视使用其类型的第一个字母,而是 this 或 self) 参数一般会出现在方法名之前;
p.Distance(...)
的表达式叫做选择器,因为他会选择合适的对应 p
对象的 Distance
方法;由于方法和字段都是在同一个命名空间,所以当方法名和字段名相同时,编译器会报错(field and method with the same xxx
);
一般约定:如果一个类型里有一个指针作为接收器的方法,那么在该类型的所有方法中,都必须用指针作为接收器,或者都不用指针作为接收器;
-
Go 语言中,可以为一些简单的数值、字符串、slice、map 定义一些附加行为;
-
使用内嵌结构体的方式,可将使我们定义字段特别多的复杂类型,将字段先按小类型分组,然后定义小类型的方法,之后再把它们组合起来。