Fork me on GitHub

Go编程语法注意事项

看完《Go语言实战》和《Go核心编程》这两本教程,自己总结了一些使用Go编程的要领和注意事项

  • 使用 := 赋值操作符,这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明;

  • 全局变量允许声明但不使用,但是局部变量声明后不使用,则编译会报错;

  • 如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,但是两个变量的类型必须相同;

  • Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑;

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

  • 获取字符串中某个字节的地址的行为是非法的,例如:

    1
    2
    str := "hello world"
    &str[i]
  • switch语句case中不需要break来结束;

  • 未初始化的map的值是nil。map默认是无序的,既不是按照key的顺序排列,也不是按照value排序的;

  • 无论是结构体还是结构体指针类型,都使用点号来获取结构体字段(不同于C/C++,指针使用->来获取成员变量);

  • 如果File是一个结构体类型,那么表达式

    1
    2
    new(File)
    &File{ }

    是等价的

  • 在一个结构体中,对于每一种数据类型只能有一个匿名字段;

  • 内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法,比较类似于面向对象里面的继承;

  • 空接口: type Any interface{ },不实现任何方法,所以任何类型都实现了空接口. 可以给一个空接口类型的变量赋任何类型的值, 每个空接口类型变量占用2个字节:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针,空接口类似于C语言的*void指针;

  • 复制数据切片至空接口切片必须逐一赋值,因为内存布局不一样;

  • 结构struct中只有被导出字段(首字母大写)才是可设置的;

  • 实际上,反射是通过检查一个接口的值,变量首先被转换成空接口;

  • 类型可以通过内嵌多个接口来提供像多重继承一样的特性;

  • 函数重载通过空接口实现;

  • 封装:
    1)包范围内的:通过标识符首字母小写,对象只在它所在的包内可见;
    2)可导出的:通过标识符首字母大写,对象对所在包以外也可见;

  • 继承:

    用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重
    继承可以通过内嵌多个类型实现;

  • 多态:

    用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和
    接口是松耦合的,并且多重继承可以通过实现多个接口实现;

  • 字节切片初始化方式:

    1)圆括号会进行类型转换;
    2)花括号则直接赋值;

  • 当调用panic时,所有的defer语句都会保证执行,并把控制权交还给panic的函数调用者;

  • panic会导致栈被展开直到defer修饰的recover()被调用或者程序中止,recover只能在defer修饰的函数;

  • 执行go test用于测试单个文件时,一定要后跟被测试的源代码文件,如果原文件有其他的引用,有需要一并跟上,否则会提示引用找不到的错误;

  • 协程工作在相同的地址空间,所以共享内存的方式一定是同步的;协程是轻量的,比线程更轻;

  • 经验法则,对于n个核心的情况设置GOMAXPROCS 为n-1以获得最佳性能,也同样需要遵守这条规则:协程的数量 > 1+GOMAXPROCS > 1;

  • 如果在某一时间只有一个协程在执行,不要设置GOMAXPROCS!

  • GOMAXPROCS等同于(并发的)线程数量,在一台核心数多于1个的机器上,会尽可能有等同于核心数的线程在并行运行;

  • 协程会随着程序的结束而自动消亡;

  • 协程是独立的处理单元,一旦陆续启动一些协程,你无法确定他们是什么时候真正开始执行的。你的代码逻辑必须独立于协程调用的顺序;

  • 通道只能传输一种类型的数据, 所有的类型都可以用于通道,空接口interface{}也可以。甚至可以(有时非常有用)创建通道的通道;

  • 一个无缓冲通道只能包含1个元素, 带缓冲的通道包含多个元素;

  • 关闭通道:

    只有发送者才需要关闭通道,接收者不需要关闭通道;

  • 所有处于同一个文件夹里的代码文件,必须使用同一个包名。按照惯例,包和文件夹同名;

  • import导入包时,如果包名前有下划线_,表示初始化该包,但是不是用包里的变量;

  • 程序中每个代码文件里的 init 函数都会在 main 函数执行前调用;

  • 包中任意一个公开的标识符。这些标识符以大写字母开头。以小写字母开头的标识符是不公开的,不能被其其包中的代码直接访问;

  • 包名应该使用全小写命名

  • 如果使用 … 替代数组的长度,Go 语言会根据初始化时数组元素的数量来确定该数组的长度;

  • 如果在[ ]运算符里指定了一个值,那么创建的就是数组而不是切片。只有为空的时候,才会创建切片;

  • nil切片不同于空切片, 成员指针变量的值不一样,前者值为nil,后者值为0;

  • 切片赋值,共享底层数组;

  • 如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 操作创建新的底层数组,与原有的底层数组分离。新切片与原有的底层数组分离后,可以安全地进行后续修改;

  • 在函数间传递切片,就是要在函数间以值的方式传递切片,这里值传递是指的拷贝地址,实际的切片元素值是不用拷贝的;

  • 在64位架构的机器上,一个切片需要 24 字节的内存:指针字段需要 8 字节,长度和容量字段分别需要 8 字节;

  • 由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会复制切片本身,不会涉及底层数组,值传递;

  • 用type声明的新类型与原类型是两个完全不同的类型,不能互相赋值;

  • go语言里面有两种类型的接受者(1-值接收者;2-指针接收者)

    两种类型的方法,可以彼此互相调用;

    值接收者使用值的副本来调用方法,而指针接受者使用实际值来调用方法(方法对值的修改会影响实际值)。

    在实际编写代码时,还要看值类型,是否方便复制(如果值中包含未公开类型,则不能复制)

  • 接口:

    用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现;

    1
    2
    3
    type Maker interface {
    Make()
    }
  • 当一个标识符的名字以小写字母开头时,这个标识符就是未公开的,即包外的代码不可见。
    如果一个标识符以大写字母开头,这个标识符就是公开的,即被包外的代码可见

  • 带缓冲的通道,当主动close通道后,goroutine依然可以从通道接收数据,但是不能再往通道发送数据了

  • iota的使用,默认为0,逐步递增;

  • 字符串类型在 go里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节

  • 在定义常量组时,如果不提供初始值,则表示将使用上一行的表达式;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const (
    Sunday = 0
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    )

    Sunday为0,其他常量均为0

  • 可以通过将非可变参数置于可变参数前面的方式来混合使用它们,注意:非可变参数只能放在可变参数前面;

  • 使用os.Exit(0)函数退出程序,之前声明的defer函数不会执行;

  • go语言有指针,但是没有指针运算,不能对指针进行偏移;

  • 初始化顺序:

    全局变量 -> init() -> main();

  • package 基本的管理单元:同一个package下面,可以有非常多的不同文件,只要每个文件的头部 都有 如 “package xxx” 的相同name, 表示这些文件属于同一个包;

  • import导入包滥用时,可能会存在循环依赖的问题,需要主动避免;

  • 空切片和nil切片是两个完全不同的概念;
    nil切片:只声明,不做初始化
    空切片:声明后初始化为空

您的鼓励是我持之以恒的动力