golang基础归纳
1. hello-world
package main import "fmt" func main(){ fmt.Println("Hello world, Go Go!"); fmt.Printf("type of Hello is %T\n", "Hello") }
package main--每一个Go文件都应该在开头进行package name的声明(注:只有可执行程序的包名为main)。包用于代码的封装与重用,这里包名为main。位于第一行。
import "fmt"--导入fmt包,下面代码要使用。
每个标准库下都有doc.go,介绍该包的用法等。如fmt/doc.go记录printf格式话字符串说明。
2. 注释
单行注释://
多行注释:/* */
3. 空白符
_在Go中被用作空白符,可以表示任何类型的任何值。
4. 类型
布尔:bool // true,false
字符串:string
数字类型:int8,int16,int32,int64,int
uint8,uint16,uint32,uint64,uint
float32,float64
complex64,complex128
byte
rune
注:int,根据不同的底层平台,表示32或64位整型。除非对整形的大小有特定的需求,否则通常应该使用int表示整型。
注:byte是uint8的别名,rune是int32的别名。
注:+操作符用于拼接字符串。
nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。 var x = nil 错误
var intval []string fmt.Println(unsafe.Sizeof(intval)) // 24
5. 类型转换
Go有着非常严格的强类型特征,没有自动类型提升或类型转换,不允许将一种类型的变量赋值给另一种类型的变量。
若要类型转换,需要显式类型装换,如int(f)等。
i := 60.5 j := 50 sum := i + float64(j)
自声明类型也不能和相同原类型混合使用:
var str string = "Hello" type myString string var customName myString = "world" customName = str // 不允许
在golang中字面量是无类型(untyped)的,无类型以为着可以复制给类似类型的常量或变量。32.0是无类型的浮点数字面量,因此可以赋值给任意数字相关类型的变量或常量。如:var a int64 = 32.0 var d complex64 = 32.0 var f rune = 32.0
类型别名
type myint int // 新类型 type intalias = int // 类型别名,所有使用intalias的地缝都可以使用int
引入类型别名的用途:1)在大规模重构项目代码的时候,尤其是将一个类型从一个包移动到另一个包中的时候,有些代码会使用新包中的类型,有些代码使用旧包中的类型,最典型的是context包。2)允许一个庞大的包分解成内部的几个小包,但是小包中的类型需要集中暴露在上层的大包中。
在 Go 中,你可以为任意类型定义别名,比如数组、结构体、指针、函数、接口、Slice、Map、Channel 等,包括为自定义类型定义别名。
type rune = int32 type byte = uint8
6. 格式说明符
%T:打印变量的类型,包括接口interface的底层具体类型
%v:打印变量的值,包括接口interface底层值
%p:打印变量地址,本质等价与%x
fmt.Printf("type of sum is %T\n", sum)
参考:https://studygolang.com/articles/2644 golang fmt格式占位符
占位符 说明 举例 输出
%v 相应值的默认格式。 Printf("%v", people) {zhangsan},
%+v 打印结构体时,会添加字段名 Printf("%+v", people) {Name:zhangsan}
%#v 相应值的Go语法表示 Printf("#v", people) main.Human{Name:"zhangsan"}
%T 相应值的类型的Go语法表示 Printf("%T", people) main.Human
%% 字面上的百分号,并非值的占位符 Printf("%%") %
布尔占位符
占位符 说明 举例 输出
%t true 或 false。 Printf("%t", true) true
整数占位符
占位符 说明 举例 输出
%b 二进制表示 Printf("%b", 5) 101
%c 相应Unicode码点所表示的字符 Printf("%c", 0x4E2D) 中
%d 十进制表示 Printf("%d", 0x12) 18
%o 八进制表示 Printf("%d", 10) 12
%q 单引号围绕的字符字面值,由Go语法安全地转义 Printf("%q", 0x4E2D) '中'
%x 十六进制表示,字母形式为小写 a-f Printf("%x", 13) d
%X 十六进制表示,字母形式为大写 A-F Printf("%x", 13) D
%U Unicode格式:U+1234,等同于 "U+%04X" Printf("%U", 0x4E2D) U+4E2D
浮点数和复数的组成部分(实部和虚部)
占位符 说明 举例 输出
%b 无小数部分的,指数为二的幂的科学计数法,
与 strconv.FormatFloat 的 'b' 转换格式一致。例如 -123456p-78
%e 科学计数法,例如 -1234.456e+78 Printf("%e", 10.2) 1.020000e+01
%E 科学计数法,例如 -1234.456E+78 Printf("%e", 10.2) 1.020000E+01
%f 有小数点而无指数,例如 123.456 Printf("%f", 10.2) 10.200000
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 Printf("%g", 10.20) 10.2
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 Printf("%G", 10.20+2i) (10.2+2i)
字符串与字节切片
占位符 说明 举例 输出
%s 输出字符串表示(string类型或[]byte) Printf("%s", []byte("Go语言")) Go语言
%q 双引号围绕的字符串,由Go语法安全地转义 Printf("%q", "Go语言") "Go语言"
%x 十六进制,小写字母,每字节两个字符 Printf("%x", "golang") 676f6c616e67
%X 十六进制,大写字母,每字节两个字符 Printf("%X", "golang") 676F6C616E67
指针占位符%p
%p 十六进制,0x前缀 printf("%p", &people) 0x4f57f0
其它标记
占位符 说明 举例 输出
+ 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。
Printf("%+q", "中文") "\u4e2d\u6587"
- 在右侧而非左侧填充空格(左对齐该区域)
# 备用格式:为八进制添加前导 0(%#o) Printf("%#U", '中') U+4E2D
为十六进制添加前导 0x(%#x)或 0X(%#X),为 %p(%#p)去掉前导 0x;
如果可能的话,%q(%#q)会打印原始 (即反引号围绕的)字符串;
如果是可打印字符,%U(%#U)会写出该字符的
Unicode 编码形式(如字符 x 会被打印成 U+0078 'x')。
' ' (空格)为数值中省略的正负号留出空白(% d);
以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开
0 填充前导的0而非空格;对于数字,这会将填充移到正负号之后
golang没有 '%u' 点位符,若整数为无符号类型,默认就会被打印成无符号的。
7. sizeof
Go的unsafe包提供一个Sizeof函数,该函数接收变量并返回它的字节大小。unsafe包应该小心使用,因为使用unsafe包可能带来可移植性问题。
8. 变量
var name type name = initalvalue var name type = initalvalue var name = initalvalue // 类型推断 var name1, name2 type = initalvalue1, initalvalue2 var ( name1 = initalvalue1 name2 = initalvalue2 ) // 一条语句声明不同类型变量 name := initalvalue // 简短声明用:=
注:简短声明要求:=操作符左边的所有变量都要有初始值;要求:=操作符的左边至少有一个变量是尚未声明的。
注:简短声明必须使用显示初始化;
不能提供数据类型,编译器会自动推导;
只能在函数内部使用简短模式(不能定义全局变量);不能用于结构体字段赋值。
注意:go语言中定义的变量必须被用到,否则会报错。
变量的生命周期(参考:http://c.biancheng.net/view/4034.html)
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。
变量的生命周期与变量的作用域有着不可分割的联系:
- 全局变量:它的生命周期和整个程序的运行周期是一致的;
- 局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
- 形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。
栈的概念在上一节《变量逃逸》中介绍过,它和堆的区别在于:
堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);
栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }
中定义的局部变量。
在程序的编译阶段,编译器会根据实际情况自动选择在栈或者堆上分配局部变量的存储空间,不论使用 var 还是 new 关键字声明变量都不会影响编译器的选择。
在实际的开发中,并不需要刻意的实现变量的逃逸行为,因为逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
虽然Go语言能够帮助我们完成对内存的分配和释放,但是为了能够开发出高性能的应用我们任然需要了解变量的声明周期。例如,如果将局部变量赋值给全局变量,将会阻止 GC 对这个局部变量的回收,导致不必要的内存占用,从而影响程序的性能。
9. 常量
const intZero int = 0
双引号中的任何值都是Go中的字符串常量。
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。
无类型的常量有一个与它们相关联的默认类型,并且当且仅当一行代码需要时才提供它。在声明中 var name = "Sam"
, name
需要一个类型,它从字符串常量 Sam
的默认类型中获取。
const a = 5 var intVar int = a var int32Var int32 = a var float64Var float64 = a var complex64Var complex64 = a fmt.Println("intVar", intVar, "\nint32Var", int32Var, "\nfloat64Var", float64Var, "\ncomplex64Var", complex64Var)
a
的值是 5
,a
的语法是通用的(它可以代表一个浮点数、整数甚至是一个没有虚部的复数),因此可以将其分配给任何兼容的类型。这些常量的默认类型可以被认为是根据上下文在运行中生成的。 var intVar int = a
要求 a
是 int
,所以它变成一个 int
常量。 var complex64Var complex64 = a
要求 a
是 complex64
,因此它变成一个复数类型。
连续常量或位可通过iota设置:const iota = 0
iota是golang语言的常量计数器,只能在常量的表达式中使用。
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
const ( Monday = itoa + 1 // 1 Tuesday // 2 Wednesday // 3 ) const ( Open = 1 << iota // 0b00000001 Close // 0b00000010 Pending // 0b00000100 )
10. 函数
func functionname(parametername1 type, parametername1 type) returntype { // 函数体(具体实现的功能) }
如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。
从函数中可以返回一个命名值。一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。
func rectProps(length, width float64)(area, perimeter float64) { area = length * width perimeter = (length + width) * 2 return // 不需要明确指定返回值,默认返回 area, perimeter 的值 }
11. 包
包用于组织Go源代码,提供了更好的可重用性和可读性。
属于某一个包的源文件都应该放置于一个单独命名的文件夹里。按照 Go 的惯例,应该用包名命名该文件夹。
导出名字
在 Go 中,任何以大写字母开头的变量或者函数都是被导出的名字。其它包只能访问被导出的函数和变量。
同一个包中,所有变量和函数都可调用,无论首字母是否大小写。
init函数
所有包都可以包含一个或多个init 函数,同一文件也可包含多个init函数。
init 函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它。init 函数的形式如下:
func init() {
}
init 函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性。
包的初始化顺序
1)首先初始化包级别(Package Level)的变量
2)紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用(同一包下按文件名首字母顺序初始化,同一文件按先后顺序初始化)。
3)如果一个包导入了另一个包,会先初始化被导入的包(导入包按文件夹下文件名首字母初始化,同上;连续导入时按导入顺序初始化)。尽管一个包可能会被导入多次,但是它只会被初始化一次。
包前加点将包的命名空间导入
import ( . "github.com/smarystreets/goconvey/convey" ) Convey()
这样调用包的方法,不需要加包名。
空白标识符
导入了包,却不在代码中使用它,这在 Go 中是非法的。有两种处理方法:
1)错误屏蔽器。在导入包后,用空白符引用包的变量。var _ = rectangle.Area
2)导入包语句前使用空白符。_"geometry/rectangle"
12. 条件判断
if condition { } else if condition { } else { }
或
if statement; condition { }
注:else 语句应该在 if 语句的大括号 } 之后的同一行中。如果不是,编译器会不通过。
13. 循环
for是Go语言中唯一的循环语句。Go中没有while和do while循环。
for initialisation; condition; post { }
其中initialization和post可以省略,而只使用condition
for i <= 10 { //semicolons are ommitted and only condition is present fmt.Printf("%d ", i) i += 2 }
若condition也省略则表示无限循环。
continue、break配合标签(label)可用于多层循环跳出,break直接退出标签循环,continue继续下次循环。
14. switch
基础用法和C语言相似,包含switch…case…case…default。
通过用逗号分隔,可以在一个 case 中包含多个表达式。 case 1,2, 3, 4, 5:
switch中表达式可省略。如果省略表达式,则表示这个 switch 语句等同于 switch true
,并且每个 case
表达式都被认定为有效,相应的代码块也会被执行。
num := 75 switch { // 表达式被省略了 case num >= 0 && num <= 50: fmt.Println("num is greater than 0 and less than 50") case num >= 51 && num <= 100: fmt.Println("num is greater than 51 and less than 100") case num >= 101: fmt.Println("num is greater than 100") }
在 Go 中,每执行完一个 case 后,会从 switch 语句中跳出来,不再做后续 case 的判断和执行。使用 fallthrough
语句可以在已经执行完成的 case 之后,把控制权转移到下一个 case 的执行代码中。
switch num := number(); { // num is not a constant case num < 50: fmt.Printf("%d is lesser than 50\n", num) fallthrough case num < 100: fmt.Printf("%d is lesser than 100\n", num) fallthrough case num < 200: fmt.Printf("%d is lesser than 200", num) }
fallthrough 语句应该是 case 子句的最后一个语句。如果它出现在了 case 语句的中间,编译器将会报错:fallthrough statement out of place
字符串switch:switch varStr {case "foo", "bar", "zoo": .... }
15. 数组
Go语言不允许混合不同类型的元素。
[n]T,n表示数组中元素的数量,T代表每个元素的类型。
var a [3]int声明了一个长度为3的整型数组。数组中的所有元素都被自动赋值为数组类型的零值。
数组的索引从0开始到length-1结束,长度用函数len()计算。
a := [3]int{10, 11, 12} //简略声明
a := [3]int{10}
a := […]int{10, 11, 12} //忽略数组长度,用…代替
数组的大小是类型的一部分,因此[5]int和[20]int是不同类型。
Go中的数组是值类型而不是引用类型。这意味着当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。对新变量的更改,不会影响原始数组。
用==比较数组:相同维数且含有相同个数元素的数组才可以比较,每个元素都相同的才相等。
for i, v := range arr {} // range方法遍历数组
for _, v := range arr {} //忽略索引
a := [3][2]string{ {"lion", "tiger"}, {"cat", "dog"}, {"pigeon", "peacock"}, // this comma is necessary. The compiler will complain if you omit this comma }
多维数组最后一组末尾要加逗号,否则根据Go语言的规则会自动插入分号。
16. 切片
切片是由数组简历的一种方便、灵活且功能强大的包装(wrapper)。切片本身不拥有任何数据,只是对现有数组的引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
对切片的所有修改都会反映到底层数组上。
切片不能比较大小。
切片用[]T表示。
a[start:end]创建一个从a数组索引start开始到end-1结束的切片。
numa := [3]int{78, 79 ,80} nums1 := numa[:] // creates a slice which contains all elements of the array
numa[:]缺少开始和结束值,开始和结束默认值分别是0和len(numa)。[i:j]取切片时,j省略的话就是切片len,j必须大于i,否则panic。
切片的长度是切片中的元素数。切片的容量是从创建切片索引开始的底层数组中元素数。
func make([]T,len,cap)[]T 通过传递类型,长度和容量来创建切片。容量是可选参数, 默认值为切片长度。make 函数创建一个数组,并返回引用该数组的切片。
make()创建的切片,默认元素值都是类型零值。之后用append()是在make()创建的切片之后添加元素(切片长度处添加元素),即从slice[len(sli)]开始。
func append(s[]T,x ... T)[]T用于向切片追加元素。
append可以对nil切片增加元素。
如果切片由数组支持,并且数组本身的长度是固定的,那么切片如何具有动态长度,以及内部发生了什么?
当新的元素被添加到切片时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量是旧切片(append操作的切片)的两倍。
(若添加元素超过容量的两倍空间,则超过时每次增加两个元素空间(容量为2的倍数))
make([]int, 0)生成的切片的容量和长度都是0,若增加一个元素时,容量和长度都是1;若增加两个元素,容量和长度都是2;若增加超过2个元素,则长度是元素数,容量为长度的最接近偶数,如3个元素,则长度为3,容量为4。
fmt.Printf("addr %p\n", slice) ;可直接用%p打印slice的地址(不加&,若加&则为slice变量地址,一直相等)。
var names []string //zero value of a slice is nil if names == nil { fmt.Println("slice is nil going to append") names = append(names, "John", "Sebastian", "Vinay") fmt.Println("names contents:",names) }
切片类型的零值为 nil。一个 nil 切片的长度和容量为 0。随后可用append()追加元素。
veggies := []string{"potatoes", "tomatoes", "brinjal"} fruits := []string{"oranges", "apples"} food := append(veggies, fruits...)
可以使用 ... 运算符将一个切片添加到另一个切片。
值传递
切片在内部可看作由一个结构体类型表示:
type slice struct { Length int Capacity int ZerothElement *byte }
切片包含长度、容量和指向数组第零个元素的指针。当切片传递给函数时,即使它通过值传递,指针变量也将引用相同的底层数组。因此,当切片作为参数传递给函数时,函数内所做的更改也会在函数外可见。
内存优化
切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收。这里需要重点注意的是,在切片引用时数组仍然存在内存中。
一种解决方法是使用 copy 函数 func copy(dst,src[]T)int 来生成一个切片的副本。这样我们可以使用新的切片,原始数组可以被垃圾回收。
17. 可变参数的函数
如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最后一个参数。请注意只有函数的最后一个参数才允许是可变的。
可变参数函数的工作原理是把可变参数转换为一个新的切片。
func find(num int, nums ...int) { for i, v := range nums { if v == num { fmt.Println(num, "found at index", i, "in", nums) } } } find(30, 10, 20, 30, 40)
find(30)也是合法的,此时 nums 是一个长度和容量为 0 的 nil 切片。
有一个可以直接将切片传入可变参数函数的语法糖,你可以在在切片后加上 ... 后缀。如果这样做,切片将直接传入函数,不再创建新的切片。
find(89, nums…) // true
一个易错例程:
package main import ( "fmt" ) func change(s ...string) { s[0] = "Go" s = append(s, "playground") fmt.Println(s) } func main() { welcome := []string{"hello", "world"} change(welcome...) fmt.Println(welcome) } output: [Go world playground] [Go world]
可变参数传递可变参数给内部函数怎样调用呢?本质上将可变参数理解为切片即可。
package main import ( "fmt" "os" "os/exec" "strings" ) func main(){ sliceFunc(os.Args[1:]...) } func sliceFunc(cmd... string){ fmt.Println(cmd) if len(cmd) == 0 { fmt.Printf("Usage: %s args...\n", os.Args[0]) os.Exit(-1) } fmt.Println(cmdFunc(cmd...)) } func cmdFunc(cmd... string) string { fmt.Printf("cmd slice len: %d, value:%v\n", len(cmd), cmd) result, err := exec.Command(cmd[0], cmd[1:]...).Output() if err != nil { fmt.Println("Command failed:", err.Error()) } // return string(result) // with '\n' return strings.TrimSpace(string(result)) }
18. 键值对map
map将键值关联的内置类型,通过相应的键可以获取到值。
make(map[type of key]type of value):创建map。
map 的零值是 nil。如果你想添加元素到 nil map 中,会触发运行时 panic。因此 map 必须使用 make 函数初始化。
var set map[int] bool ; set is nil
set := map[int] bool {} ; set is not nil,此方法初始化map也可行
package main import "fmt" func main() { set := map[int]string {} if set != nil { fmt.Println(set) } else { set = make(map[int]string) set[0]="hello" fmt.Println(set) } var set1 map[int] interface {} if set1 != nil { fmt.Println(set1) } else { set1 = make(map[int]interface{}) set1[0]="hello" set1[2]=100 fmt.Println(set1) } }
$ go run map.go map[] map[0:hello 2:100]
键不一定只能是 string 类型。所有可比较的类型,如 boolean,interger,float,complex,string 等,都可以作为键。关于可比较的类型,如果你想了解更多,请访问 http://golang.org/ref/spec#Comparison_operators。
注意:struct也可以作为key,但此时struct必须是可比较的。
注:GO中map非线程安全,并发读写可能破坏结构(go为了避免你犯错,直接panic,其实没有遵循happens before原则,一个协程写,另一个协程读,是可能存在不可见问题的)。参考:左耳朵耗子 疫苗:JAVA HASHMAP的死循环
获取map中元素
访问map中不存在的元素,map会返回该元素类型的零值。
可通过如下语法判断key是否存在,如果ok是true,表示key存在,key对应的值就是value,反之key不存在。
value, ok := map[key]
for range遍历map
personSalary := map[string]int{ "steve": 12000, "jamie": 15000, } personSalary["mike"] = 9000 for key, value := range personSalary { fmt.Printf("personSalary[%s] = %d\n", key, value) }
注意,当使用 for range 遍历 map 时,不保证每次执行程序获取的元素顺序相同。
delete(map, key):删除 map 中 key ,这个函数没有返回值,若元素不存在不报错。
len(man):获取map长度。 Map不支持cap(),没有容量的概念。
Map是引用类型,当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
map 之间不能使用 == 操作符判断,== 只能用来检查 map 是否为 nil。判断两个 map 是否相等的方法是遍历比较两个 map 中的每个元素。map类型是不可比较的。
map的value本身不可寻址,但可以通过map[key]修改value值。如m["foo"].name="wang"错误,但m["foo"]=student{name:"wang"}。
19. 字符串
string是数据类型,不是引用或指针类型,零值是空字符串(“”),而非nil。 if str == "" {}
字符串是可比较的。if "wang" == varStr {} switch varStr {case "foo", "bar", "zoo": .... }
字符串就是一个字节切片。Go 中的字符串是兼容 Unicode 编码的,并且使用 UTF-8 进行编码。
len(s)返回字符串中字节的数量,中文需要使用utf8.RuneCountInString()。
在 UTF-8 编码中,一个代码点可能会占用超过一个字节的空间。需要使用rune。中文用[]rune(string),英文用[]byte(string)
rune 是 Go 语言的内建类型,它也是 int32 的别称。在 Go 语言中,rune 表示一个代码点。代码点无论占用多少个字节,都可以用一个 rune 来表示。
func printChars(s string) { runes := []rune(s) for i:= 0; i < len(runes); i++ { fmt.Printf("%c ",runes[i]) } } // rune切片构造字符串 runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
func RuneCountInString(s string) (n int):获取字符串长度,需导入包unicode/utf8。
字符串是不可变得。为了修改字符串,可以把字符串转化为一个 rune 切片。然后这个切片可以进行任何想要的改变,然后再转化为一个字符串。
func mutate(s []rune) string { s[0] = 'a' return string(s) } fmt.Println(mutate([]rune(h)))
字符串可以使用==,>, <直接按字典序以字节方式比较大小,区分大小写,直观简单;
strings.Compare(a, b string) int,按字典序以字节方式比较大小,区分大小写,与==(0), >(1), <(-1)相比速度更快。
func
EqualFold(s, t string) bool,
EqualFold reports whether s and t, interpreted as UTF-8 strings,
are equal under Unicode case-folding. 不区分大小写,UTF8.
package main import ( "fmt" "sort" ) func main() { ss := []string{"wang", "qqqq", "hua", "hello world"} sort.Strings(ss) fmt.Println(ss) // sss := sort.Reverse(sort.StringSlice(ss)) // sort.Sort(sss) sort.Sort(sort.Reverse(sort.StringSlice(ss))) fmt.Println(ss) } // 字符串切片排序 [hello world hua qqqq wang] [wang qqqq hua hello world]
字符串表示
Golang的双引号(“)和反引号(`)都可用于表示一个常量字符串,不同在于:
- 双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)
- 反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式。
而单引号则用于表示Golang的一个特殊类型:rune,类似其他语言的byte但又不完全一样,是指:码点字面量(Unicode code point),不做任何转义的原始内容。
20. 指针
*T指向一个T类型的变量。
指针的零值时nil。
var a int = 25 var b *int = &a *b++ fmt.Printf("Type of b is %T\n", b) fmt.Println("b is", b) output: Type of b is *int b is 0xc420072010 a is 26
不要向函数传递数组的指针,而应该使用切片。
对于数组:(*arr)[x]与arr[x]等价,arr是数组指针。
var arr *[3]int = [3]int{90, 100, 110}
Go 并不支持其他语言(例如 C)中的指针运算,如b++,但支持*b++。
21. 结构体
type Employee struct { firstName, lastName string age, salary int }
Employee被称为命名结构体。
//creating structure using field names emp1 := Employee{ firstName: "Sam", age: 25, salary: 500, lastName: "Anderson", } //creating structure without using field names emp2 := Employee{"Thomas", "Paul", 29, 800} // creating structure pointer with zero value var pemp *Employee pemp = &Employee{} pemp2 := &Employee{}
当定义好的结构体并没有被显式地初始化时,该结构体的字段将默认赋为零值。仅为某些字段指定初始值时,忽略的字段会赋值为零值。
对于结构体指针, emp8.firstName 与 (*emp8).firstName等价。
匿名字段的名称就默认为它的类型。
type Person struct { string int } var p1 Person p1.string = "naveen" p1.int = 50 fmt.Println(p1)
提升字段(Promoted Fields)
如果是结构体中有匿名的结构体类型字段,则该匿名结构体里的字段就称为提升字段。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。
type Address struct { city, state string } type Person struct { name string age int Address } var p Person p.name = "Naveen" p.age = 50 p.Address = Address{ city: "Chicago", state: "Illinois", } fmt.Println("Name:", p.name) fmt.Println("Age:", p.age) fmt.Println("City:", p.city) //city is promoted field fmt.Println("State:", p.state) //state is promoted field
导出结构体和字段
如果结构体名称以大写字母开头,则它是其他包可以访问的导出类型(Exported Type)。同样,如果结构体里的字段首字母大写,它也能被其他包访问到。
结构体相等性(Structs Equality)
结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的。如果两个结构体变量的对应字段相等,则这两个变量也是相等的。如果结构体包含不可比较的字段,则结构体变量也不可比较。
结构体只能比较是否相等,但是不能比较大小。相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关。
常见的有 bool、数值型、字符、指针、数组等,像切片、map、函数等是不能比较的。具体可以参考 Go 说明文档。https://golang.org/ref/spec#Comparison_operators
type name struct { firstName string lastName string } name1 := name{"Steve", "Jobs"} name2 := name{"Steve", "Jobs"} if name1 == name2 { fmt.Println("name1 and name2 are equal") }
22. defer
注:log.Fatal()底层调用os.Exit(1),直接返回,不会调用defer函数。log.Fatal()只有在init()或读取配置文件失败的条件下使用,生产环境下不建议使用。
含有defer语句的函数,会在该函数将要返回之前(return之后,若有panic则panic语句后defer(既然panic了,后续程序不能执行,函数返回,调用defer,defer中可包含recover恢复程序)),调用另一个函数。
defer是Go语言提供的一种用于注册延迟调用的机制,每一次defer都会把函数压入栈中,当前函数返回前再把延迟函数取出来并执行。
在 Go 语言中,并非在调用延迟函数的时候才确定实参,而是当执行 defer
语句的时候,就会对延迟函数的实参进行求值,若实参是函数则要先运行函数计算结果。
package main import ( "fmt" "time" ) func finished(v int){ fmt.Printf("Finished something:%d\n", v) } func process(v int){ defer finished(v) fmt.Printf("Start processing...%d\n", v) time.Sleep(2*time.Second) v = 3 fmt.Printf("End processing...%d\n", v) } func main(){ process(5) } output: Start processing...5 End processing...3 Finished something:5
当一个函数内多次调用 defer
时,Go 会把 defer
调用放入到一个栈中,随后按照后进先出(Last In First Out, LIFO)的顺序执行。
采坑点
defer最容易采坑的地方是和带命名返回参数的函数一起使用时。
defer语句定义时,对外部变量的引用有两种方式,分别是作为函数参数和作为闭包引用。作为函数参数,则在defer定义时就把值传递给defer,并被缓存起来;作为闭包引用的话,则会在defer函数真正调用时根据整个上下文确定当前的值。
return xxx
并不是一个原子指令,经过编译后变成三条指令:1)返回值=xxx;2)调用defer函数;3)空的return。
1,3步才是return语句真正的命令,第2步是defer定义的语句,这里就有可能操作返回值。
参考:5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里!
http://mian.topgoer.com/%E7%AC%AC%E5%8D%81%E5%85%AB%E5%A4%A9/
func increaseA() int { var i int defer func() { i++ }() return i }
// 0
函数 increaseA() 是匿名返回值,返回局部变量,同时 defer 函数也会操作这个局部变量。对于匿名返回值来说,可以假定有一个变量存储返回值,比如假定返回值变量为 anony,上面的返回语句可以拆分成以下过程:
annoy = i i++ return
由于 i 是整型,会将值拷贝给 anony,所以 defer 语句中修改 i 值,对函数返回值不造成影响,所以返回 0 。
func f3() (r int) { // 1.赋值 r = 1 // 2.r 作为函数参数,不会修改要返回的那个 r 值 defer func(r int) { r = r + 5 }(r) // 3.空的 return return // return 1 }
第二步,r 是作为函数参数使用,是一份复制,defer 语句里面的 r 和 外面的 r 其实是两个变量,里面变量的改变不会影响外层变量 r,所以不是返回 6 ,而是返回 1。
24. strings和strconv
strings
strings.HasPrefix(s string, preffix string) bool:
判断字符串s是否以prefix开头
stirngs.HasSuffix(s string, suffix string) bool:
判断字符串s是否以suffix结尾
strings.Index(s string, str string) int:
判断str在s中首次出现的位置,如果没有出现,则返回-1
strings.LastIndex(s string,str string) int:
判断str在s中最后出现的位置,如果没有出现,则返回-1
strings.Replace(str string,old string,new string,n int):
字符串替换
strings.Count(str string,count int)string:
字符串计数
strings.Repeat(str string,count int) string:
重复count次str
strings.ToLower(str string)
转换为小写
strings.ToUpper(str string)string:
转换为大写
strings.TrimSpace(str string):
去掉字符串首位空白字符
strings.Trim(str string,cut string):
去掉字符串首尾cut字符
strings.TrimLeft(str string,cut string):
去掉字符串首部cut字符
strings.TrimRight(str string,cunt string):
去掉字符串尾部cut字符
strings.Field(str string):
返回str空格分隔的所有子串的slice
string.Split(str string,split string):
返回str split分割的所有子串的slice
strings.Join(s1 []string,sep string):
用sep把s1中的所有元素连接起来
strconv
strconv.Itoa(i int):把一个整数转换成字符串
strconv.Atoi (str string)(int,errror): 把一个字符串转换成整数
25. 终端操作
os.Stdin:标准输入
os.Stdout:标准输出
os.Stderr:标准错误输出
26. 运算符
&^按位清零
将运算符左边的值按运算符右边值的位清零(右边为1清0,右边为0白痴原值)
1 &^ 0 ---1 1&^1 -- 0 0 &^ 1 ---0 0 &^ 0 --- 0
27. Context
根Context:通过context.Background()创建
子Context:context.WithCancel(parentContext)
ctx, cancel := context.WitchCancel(context.Background())
当前Context被取消后,基于它的context都会被取消
接收取消通知 <- ctx.Done()
28. Unmarshal
func Unmarshal(data []byte, v interface{}) error
Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError. v必须是指针。
29. golang中分为值类型和引用类型
-
值类型分别有:int系列、float系列、bool、string、数组和结构体
-
引用类型有:指针、slice切片、管道channel、接口interface、map、函数等
值类型的特点是:变量直接存储值,内存通常在栈中分配
引用类型的特点是:变量存储的是一个地址,这个地址对应的空间里才是真正存储的值,内存通常在堆中分配。当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
30. 发送信号
var stopChan = make(chan os.Signal, 2) signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) <-stopChan // wait for SIGINT syscall.Kill(syscall.Getpid(), syscall.SIGINT) stopChan <- syscall.SIGINT Stopchan <- syscall.SIGINT
os.SIGINT与os.Interrupt值相同
var ( Interrupt Signal = syscall.SIGINT Kill Signal = syscall.SIGKILL )
参考:
1. How to write Go Code https://golang.google.cn/doc/code.html
2. https://golang.google.cn/ 提供go在线测试环境和文档
3. https://golang.google.cn/doc/ go相关文档
4. https://golang.google.cn/pkg/ go标准库 Golang标准库文档 中文文档
5. https://godoc.org/ go实用库搜索
7. Go 系列教程(Golang tutorial series) go语言中文网
8. https://github.com/geektime-geekbang/go_learning 极客时间学习
9. Golang tutorial series 英文原版
10. http://www.topgoer.com/ 基础、框架、面试 全面
当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。
http://topgoer.com/%E5%B8%B8%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93/Context.html?h=context
11. 跟煎鱼学go https://eddycjy.gitbook.io/golang/
12. Go语言学习之路/Go语言教程 liwenzhou
13. GO编程模式 左耳朵耗子