Go语言的100个常识

1 channel实现CSP模型
2 内存分配:tcmalloc
3 动态库buildmode功能???
4 缺乏真正意义上的调试器吗???
5 依赖包管理问题???
6 编译器将未使用的局部变量定义当作错误
7 函数可以返回函数类型func test() func(int) {
	return func(x int) {
		println("x:", x)
	}
}
8 defer定义延迟调用,无论函数是否出错都确保结束前被调用
9 ok-idiom(A跌目)模式:多返回值中用一个名为ok的布尔值来标记操作是否成功,因为很多操作默认返回零值,所以需要额外说明
10 结构中的匿名字段,结构的实例可以直接调用匿名字段的方法和属性
11 接口使用duck type方式
12 channel和goroutine实现通信代替共享内存的CSP模型

1 计算机中变量是一段或者多段用来存储数据的内存,类型决定变量内存的长度和存储格式,所以我们只能修改变量值不能修改类型
2 内存分配发生在运行时,编译后的机器码不使用变量名而是直接使用内存地址访问目标数据,所以编码阶段采用易于阅读的变量名
3 惯例建议以组的方式整理多行变量定义 var {x,y int }
4 简短声明一般用于函数多返回值,以及if for switch等语句中定义局部变量
5 未使用的局部变量会编译出错,全局变量不报错
6 命名建议字母或下划线开始,多字母数字和下划线组合,局部变量优先短名
7 常量实在预处理阶段展开成指令数据,变量是在运行期分配存储内存.(所以常量无法寻址,没有地址)
8 byteuint8的别名 runeint32的别名 别名直接可以相互赋值不需要类型转换
9 拥有相同的底层结构不代表就属于别名
10 new为指定类型分配零值内存返回指针;make是引用类型专用的创建函数(内存分配和属性初始化)
11 多个type可以组合初始化
12 未命名类型:数组、切片、字典、通道等类型与具体元素类型或长度等属性相关的类型,可以用type将其改变成命名类型
13 对于未命名类型 struct tag不同也属于不同类型,字段顺序不同也属于不同类型。

1 程序=算法+数据 算法:解决问题的过程,小到加法指令大到分布式集群
2 乘幂和绝对值运算在math包的Pow和Abs中
3 自增自减只能作为独立语句
4 指针是实体会分配内存空间,内存地址是内存中每个字节单元的唯一编号
5 指针类型指向相同地址或nil则相等,但是不能做加减和类型转换
6 unsafe.Pointer将指针转换为uintptr进行加减运算,但可能造成非法访问
7 指针不能用->,统一使用.
8 复合类型初始化,必须包含类型标签;左花括号必须在类型尾部;多成员都好隔开;多行右侧必须是逗号或者花括号
9 switch 无需显式执行break,但是想顺序执行需要显式执行fallthrough
10 range迭代是复制数据
11 goto只能跳转到同级代码,不能跨级别
12 break用于switch for select,终止整个语句块执行
13 continue只用于for循环,终止后续逻辑立即进入下一轮循环

1 函数无需前置声明;不支持命名嵌套定义;不支持同名重载;不支持默认参数;支持不定长参数;支持多返回值;支持命名返回值;支持匿名函数和闭包 
2 函数类型只支持nil判断,不支持其他比较操作
3 从函数返回局部变量指针是安全的,编译器会通过逃逸分析来决定是否在堆上分配内存;所以参数尽量减少值拷贝
4 函数建议命名规则:动词+名称;避免不必要的缩写(printError优于printErr);避免使用类型关键字;使用习惯用语(init表示初始化,is/has返回布尔值);用反义词命名行为相反的函数
5 不管是指针、引用类型还是其天涯类型参数,都是值拷贝传递,区别在于拷贝的目标对象
6 指针传递坏处在于延长该变量的声明周期,也可能导致他分配到堆上增加性能消耗
7 函数参数在函数内部有效,作用域是整个函数内部
8 变参  func test(a ...int){}   test(a[:]...)
9 命名返回值的问题:  新定义的同名局部变量会引起同名遮蔽:xx is shadowed during return ;此时实名return即可
10 闭包 匿名函数能够使用上下文的环境中的数据(最终数据)
11 延迟调用defer 常用于资源释放 解除锁定 错误处理等 先入后出。 延迟调用开销很大,性能要求高压力大的算法尽量避免使用
12 error是接口类型
13 panic会引发函数中断执行defer ,在defer中使用recover捕获panic提交的错误对象(recover只能在defer中执行才有效)
14 多个panic仅最后一个被捕获
15 runtime/debug.PrintStrack()可以打印完整的堆栈信息
16 不可恢复性、导致系统无法正常工作的错误才会使用panic (文件系统没权限操作、服务端口被占用、数据库未启动等)

1 字符串是不可变字节(byte)序列,可用len获取长度,不可用cap; ` 支持跨行;允许字节数组访问,单不允许字节数组取地址
2 用切片指向数组时,底层还是指向该字符串
3 range遍历可以打印出汉字,len遍历出的汉字是乱码
4 append可以向[]byte追加  =var bs []byte  bs=append(bs,"abc"...)
5 字符串加法运算每次都会重新分配内存,构建大字符串性能极差;方法1:strings.Join  方法2:bytes.Buffer  小字符串拼接使用fmt.Sprintf text/template等
6 utf8.RuneCountInString(s)代替len获取带汉字的字符串长度
7 长度是数组的类型组成部分,元素类型相同长度不同的数组不是同一类型
8 多维数组,只第一维支持...  [...][10]
9 如果元素支持== !=操作,则数组也支持
10 数组是值类型
11 切片:不是动态数组或数组指针;内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内。可以理解为数组指针的包装
12 切片本身是只读对象,工作机制类似数组指针的包装   右半开区间  数组必须addressable
type slice struct{
	array unsafe.Pointer 
	len int
	cap int
}
13 切片引用数组时,切片指针会指向数组地址;访问越界会报错;append会追加数组,当长度大于cap时会重新分配地址,则切片和数组就相互独立了
14 切片 var a[]intnil,仅代表他为初始化,但依旧分配内存;且a[:]依旧是nil
15 如果切片长时间占用大数组的少量数据,建议切片单独分配地址,以让大数组尽早释放
16 可将字符串直接复制到[]byte  => b:=make([]byte,3) n:=copy(b,"abcdefhg")=>n=3,b=[97 98 99]

17 字典的key必须支持== != 如数字、字符串、指针、数组、结构、接口
18 if v,ok:=m["d"];ok{存在} 使用ok-idiom模式判断key是否存在
19 delete(m,"d"),删除不存在的key不报错
20 map使用range迭代每次顺序不定
21 map被设计成 no addressable,所有没法修改value的成员(如果value是个结构或者数组等)  ;改进方法1:先获取完整value,修改后再赋值回去;方法2:value采用指针类型
因为value是指针,所有可以通过指针修改指针指向的数据。
22 map并发操作,某任务针对map写操作,其他任务对该map的读写删除都会导致进程崩溃;可用sync.RWMutex实现同步(不要使用defer23 map对象本身就是指针包装,传参不需要取地址
24 map创建时和slice一样要预选分配足够地址,减少扩张时不必要的内存分配和重新哈希操作=>make(map[int]int,1000)
25 对于海量小对象,应该直接用字典存储键值数据拷贝而不是指针,这样减少扫描对象的数量缩短垃圾回收时间。
26 字典不会收缩内存,适当替换新对象是有必要的。。。

27 结构推荐命名初始化,以防扩充结构时报错
28 匿名结构
u:=struct{
	name string
}{
	name:"xxx",
}
29 只有所有成员都支持==操作时,结构才支持相等操作
30 匿名字典隐式的以类型名为字段名称,使用时可以直接饮用匿名字段的成员,但是初始化时必须当做独立字段。(但是隐式字段是外部类型的话,隐式名称不包含包名)
31 除接口指针 多级指针外的任何命名类型都可作为匿名类型
32 不能讲基础类型和其指针类型同时匿名,因为他们的匿名名称相同
33 字段标签是对字段描述的元数据,是类型的组成部分;运行期间可用反射获取标签信息,通常作为格式校验和数据库关系映射等
p1:=p{
	name:"xxx",
	sex:1,
}
v:=reflect.ValueOf(p1)
t:=v.Type()
for i,n:=0,t.NumField();i<n;i++{
	fmt.Printf("%s:%v\n",t.Field(i).Tag,v.Field(i))
}
33 reflect.StructTag提供了更完善的功能

1 前置实例接收参数-receiver
2 receiver是基础类型则会被复制,指针类型则必须能获取实例地址
3 receiver类型选择:不修改的小对象或固定值用T;引用类型、字符串、函数等指针包装对象用T;修改实例状态用*T;包含Mutex等同步字段用*T,大对象或不确定情况用*T;;
4 匿名类型的方法也存在同名遮蔽的特性。(可实现类似覆盖操作)
5 T的方法集是 receiver T;*T的方法集是receiver T+*T
6 匿名嵌入S,T包含 receiver S;匿名嵌入*S,T包含 receiver S+*S; 匿名嵌入S或*S,*T都包含 receiver S+*S;
7 方法集仅影响接口实现和方法表达式转换。匿名字段就是为方法集准备的

1 chan,一次性事件使用chanclose效率更高
向closechan发数据panic
从已关闭的chan接收数据返回已缓存数据或零值
无论收发,nil通道都会阻塞

2 同步问题应该用锁或原子变量来操作
对性能要求较高时,赢避免使用defer unlock 
读写并发时,用RWMutex性能更好
对单个数据的读写保护建议使用读写锁
严格测试,尽可能打开数据竞争检查

3 通道倾向于解决逻辑层次的并发处理架构
4 锁用来保护局部范围内的数据安全

1 从内部结构看,接口自身也是一种结构类型,只是编译器会对其作出很多限制。
2 可以匿名嵌入其他接口(但是方法名不能重复),这样实现了所有的方法才算实现了该接口。不能循环嵌入和嵌入自身
3 接口存储的复制品也是unaddressable的
4 接口的类型还原使用 ok-idiom模式

1 for range 读取chan会循环读取,直到chan关闭
2 select会随机选择一个可用通道做收发操作
3 底层实现来看,通道只是个队列
4 chan常用于传递数据或作事件通知
5 缓冲区大小仅是内部属性,不属于类型组成部分。通道变量本身就是指针,可用相等操作判断是否为同一对象或nil
6 cap len返回chan的缓冲区大小和已缓存的数据;同步chan返回0
7 chan可用ok-idiom模式判断是否关闭 !ok即为关闭;用range来循环读取直到chan关闭
8 同步chan直接读会堵塞,但是close以后会直接返回nil结束
9 重复关闭chanpanic
10 make直接创建单向通道是没有意义的,通常使用类型转换来获取单向通道,并赋值给操作双方。
	c:=make(chan int)  
	var send chan<- int =c
	var recv <-chan int =c 
11 不能close接收端,无法将单向通道转换回去	
12 selectfor循环组合使用,select是随机的
13 nilchan不会被select选择
14select用来写数据时,default可用于写阻塞时做相应操作
15 select可以与定时器绑定,即当大家都在阻塞时,定时器到时后即可执行  case <-time.After(time.Secend *57)
time.After()
tick:=time.Tock(time.Secend)
16 通道队列依旧使用锁同步机制,单次获取更多数据(批处理)将改善频繁加锁造成的性能问题
17 如果通道一直处于阻塞状态,垃圾回收器不回收,会导致等待队列长久休眠形成资源泄露






发布了264 篇原创文章 · 获赞 23 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Edu_enth/article/details/104048740