洛图_lottu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

命名

命名必须为下划线或字母开头,驼峰式。

格式

编译器会主动把特定符号后的换行符转换为分号, 因此换行符添加的位置会影响Go代码的正确解析(比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符++、--、)、]或}中的一个)。

变量

指针

  • 指针可以比较,只有指向同一个对象或者都为nil时相等。

  • 局部变量地址被返回后依然有效。

  • 在使用new时,如果两个类型都是空的,也就是说类型的大小是0,例如struct{}和 [0]int, 有可能有相同的地址(依赖具体的语言实现)。

  • new只是一个预定义的函数。

变量的作用域

  • 局部变量可能在函数返回之后依然存在。

  • 编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,这个选择并不是由用var还是new声明变量的方式决定的。

  • 逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。

赋值

  • 变量的自增和自减是语句不是表达式,不可以作为表达式赋值。

  • 元组赋值会先计算等号右边的所有表达式,然后赋值给左边。

  • 类型必须完全匹配才能,nil可以赋值给任何指针或引用类型的变量。

  • 对于两个值是否可以用==或!=进行相等比较的能力也和可赋值能力有关系:对于任何类型的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之亦然。

类型

  • 对于自定义的类型,即使底层类型相同,也是不同的类型。

  • 只有底层类型相同,才允许类型转换。

  • 类型转换错误只发生在编译阶段。

包和文件

  • 汉字开头的名字不导出

  • 包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化

  • 可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数,初始化函数不能被调用或引用。

作用域

  • 不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。

  • 导入的包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的包,当前包的其它源文件无法访问在当前源文件导入的包。

  • 控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。

基础数据类型

  • 有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型。这两种类型都有同样的大小,32或64bit,但是我们不能对此做任何的假设;因为不同的编译器即使在相同的硬件平台上可能产生不同的大小。

  • rune是和int32等价的类型,byte是和uint8等价的类型。

  • 无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

  • 有符号整数采用补码。

  • %取模运算符的符号和被取模数的符号总是一致的,因此-5%3和-5%-3结果都是-2。除法运算符/的行为则依赖于操作数是否全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。

  • &^ 位清空 (AND NOT)

  • 左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。

  • 应该避免对可能会超出目标类型表示范围的数值做类型转换,因为截断的行为可能依赖于具体的实现:

f := 1e100  // a float64
i := int(f) // 结果依赖于具体实现
  • 通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的[1]副词告诉Printf函数再次使用第一个操作数。第二,%后的#副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。

  • 字符使用%c参数打印,或者是用%q参数打印带单引号的字符

  • 用Printf函数的%g参数打印浮点数,将采用更紧凑的表示形式打印,并提供足够的精度,但是对应表格的数据,使用%e(带指数)或%f的形式打印可能更合适。

  • &&的优先级比||高(助记:&&对应逻辑乘法,||对应逻辑加法,乘法比加法优先级要高)

  • 字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变量分配一个新字符串值。

  • Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。

复合数据类型

数组

  • 在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。

  • 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。

slice

  • 因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名
  • 和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较。
  • 如果需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。
  • 除了和nil相等比较外,一个nil值的slice的行为和其它任意0长度的slice一样;例如reverse(nil)也是安全的。除了文档已经明确说明的地方,所有的Go语言函数应该以相同的方式对待nil值的slice和0长度的slice。
  • 在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。在第一种语句中,slice是整个数组的view。在第二个语句中,slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的。
  • copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,目标和源的位置顺序和dst = src赋值语句是一致的。

map

  • map中的元素并不是一个变量,因此不能对map的元素进行取址操作。禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。

  • 和slice一样,map之间也不能进行相等比较;唯一的例外是和nil进行比较。要判断两个map是否包含相同的key和value,我们必须通过一个循环实现。

结构体

  • 两种不同形式的写法不能混合使用。而且不能在外部包中用顺序赋值的技巧来偷偷地初始化结构体中未导出的成员(用成员名字的方式也不能初始化未导出的成员)。

  • 如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。

  • 因为结构体通常通过指针处理,可以用下面的写法来创建并初始化一个结构体变量,并返回结构体的地址:

pp := &Point{1, 2}
  • 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的。

函数

  • 固定大小栈会限制递归的深度,当你用递归处理大量数据时,需要避免栈溢出;除此之外,还会导致安全性问题。与此相反,Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。

方法

  • 例如p.Distance的表达式叫作选择器,由于方法和字段都是在同一命名空间,如果声明一个和成员变量同名的方法,编译器会报错,因为在调用例如p.X时有歧义。

  • 每种类型都有自己的命名空间,不同的方法调用指向了不同类型里的方法,例如p.add()q.add()

  • 可以给任意命名类型定义方法,只要这个命名类型的底层类型不是指针或interface。

  • 一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。

  • 如果接收器p是一个Point类型的变量,并且其方法需要一个Point指针作为接收器,我们可以用下面这种简短的写法:

p.ScaleBy(2)

编译器会隐式地帮我们用&p去调用ScaleBy这个方法。这种简写方法只适用于“变量”,不能通过一个无法取到地址的接收器来调用指针方法。

或者接收器实参是类型*T,形参是类型T。编译器会隐式地为我们解引用,取到指针指向的实际变量:

pptr.Distance(q) // implicit (*pptr)

总结:变量可以直接调用指针接收器定义的方法,指针可以直接调用变量接收器定义的方法。编译器会帮助做类型转换。

  • 如果使用变量类型作为选择器,会产生一次拷贝;如果使用指针类型作为选择器,这种情况下,选择器只是产生了一个别名,对其进行的修改会作用于变量,因为其指向同一快内存。

  • nil也是合法的接收器类型,如果直接写nil.Get("item")的形式,则无法通过编译,因为nil的字面量编译器无法判断其准确类型。

  • 虽然可以通过引用来操作内部值,但在方法想要修改引用本身时是不会影响原始值的,比如把他置换为nil,或者让这个引用指向了其它的对象,调用方都不会受影响。(可以参考C语言的指针)

并发

  • go后跟的函数的参数会在go语句自身执行时被求值。

垃圾回收机制

  • Go如何知道一个变量何时可以回收,基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。

  • 虽然Go的垃圾回收机制会回收不被使用的内存,但是这不包括操作系统层面的资源,比如打开的文件、网络连接。因此必须显式的释放这些资源。

posted on 2021-09-02 11:05  睡不醒的黄瓜鱼  阅读(2)  评论(0编辑  收藏  举报