理解go语言编程-顺序编程
Go语言中提供了C/C++程序员期盼多年的多重赋值功能,比如下面这个交换i和j变量的语句:
i, j = j, i
在不支持多重赋值的语言中,交互两个变量的内容需要引入一个中间变量:
t = i; i = j; j = t;
Go的常量定义const Pi float64 = 3.14159265358979323846可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它 与字面常量一样,是无类型常量const zero = 0.0。
由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达式,比如试图以如下方式定义常量就会导致编译错误:const Home = os.GetEnv(“HOME”)
iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1.
1 | const ( |
布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。
1 | var b bool |
注意,:= 声明与定义只能放在函数中,或者使用var。
1 | var item = &memcache.Item { |
int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动 做类型转换
1 | var value2 int32 |
两个不同类型的整型数不能直接比较,比如int8类型的数和int类型的数不能直接比较,但各种类型的整型变量都可以直接与字面常量(literal)进行比较.
取反在C语言中是~x,而在Go语言中是^x.
float32等价于C语言的float类型, float64等价于C语言的double类型
fvalue2 := 12.0 其类型将被自动设为float64,而不管赋给它的数字是否是用32位长度表示的
1 | // p为用户自定义的比较精度,比如0.00001 |
复数的表示,对于一个复数z = complex(x, y),就可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(z)获得该复数的虚部,也就是y
1 | var value1 complex64 |
字符串遍历,一种是以字节数组的方式遍历,每个中文字符在UTF-8中占3个字节,而不是1个字节。另一种,以Unicode字符方式遍历时,每个字符的类型是rune(早期的Go语言用int类型表示Unicode字符),而不是byte。
1 | str := "Hello,世界" |
数组声明方法
1 | [32]byte // 长度为32的数组,每个元素为一个字节 |
在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。
1 | package main |
数组切片的数据结构可以抽象为以下3个变量:
- 一个指向原生数组的指针;
- 数组切片中的元素个数;
- 数组切片已分配的存储空间。
数组切片本质上是一个区间,你可以大致将[]T表示为:
1 | // 数组切片内部是指向数组的指针,所以可以改变所指向的数组元素 |
并非一定要事先准备一个数组才能创建数组切片。Go语言提供的内置函数make()可以用于灵活地创建数组切片,事实上还会有一个匿名数组被创建出来,只是不需要我们来操心而已。
1 | mySlice1 := make([]int, 5) // 创建一个初始元素个数为5的数组切片,元素初始值为0 |
可动态增减元素是数组切片比数组更为强大的功能。与数组相比,数组切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的 值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能。
cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数。
1 | mySlice = append(mySlice, 1, 2, 3) // 从尾端给mySlice加上3个元素 |
基于数组切片创建数组切片,如下,选择的oldSlicef元素范围甚至可以超过所包含的元素个数,比如newSlice
可以基于oldSlice的前6个元素创建,虽然oldSlice只包含5个元素。只要这个选择的范围不超过oldSlice存储能力(即cap()返回的值),那么这个创建程序就是合法的。newSlice中超出oldSlice元素的部分都会填上0。
1 | oldSlice := []int{1, 2, 3, 4, 5} |
内容复制,如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。
1 | slice1 := []int{1, 2, 3, 4, 5} |
在Go中,使用map不需要引入任何库,并且用起来也更加方便。myMap是声明的map变量名,string是键的类型,PersonInfo则是其中所存放的值类型。
1 | var myMap map[string] PersonInfo |
在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
在有返回值的函数中,不允许将“最终的”return语句包含在if…else…结构中,否则会编译失败
switch中,switch后面的表达式甚至不是必需的,只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case
Go语言中的循环语句只支持for关键字,而不支持while和do-while结构,简化为for表示while。for不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。for循环同样支持continue和break来控制循环,但是它提供了一个更高级的break,可以选择中断哪一个循环。
1 | for j := 0; j < 5; j++ { |
go函数如果参数列表中若干个相邻的参数类型的相同,比如上面例子中的a和b,则可以在参数列表中省略前面变量的类型声明
1 | func Add(a, b int)(ret int, err error) { |
形如…type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,类型…type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数。
1 | func myfunc(args ...int) { |
不定参数的传递,使用interface{}仍然是类型安全的,这和C/C++不太一样。
1 | func myfunc(args ...int) { |
匿名函数可以直接赋值给一个变量或者直接执行:
1 | f := func(x, y int) int { |
error处理
1 | type PathError struct { |
如果觉得一句话干不完清理的工作,也可以使用在defer后加一个匿名函数的做法:
1 | defer func() { |
defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行。
异常处理
1 | func panic(interface{}) |
一般情况下,recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。
1 | defer func() { |