

the better C - 更好的 C 语言
前置知识
gofmt
Go 语言在代码格式上采取了很强硬的态度。gofmt 工具把代码格式化为标准格式(译注:这个格式化工具没有任何可以调整代码格式的参数,Go 语言就是这么任性),并且 go 工具中的 fmt 子命令会对指定包,否则默认为当前目录中所有 go 源文件应用 gofmt 命令
以法令方式规定标准的代码格式可以避免无尽的无意义的琐碎争执(译注:也导致了 Go 语言的 TIOBE 排名较低,因为缺少撕逼的话题)
基础
Hello World
一个标准的 golang 项目分为两种:程序和包
// main.go
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}执行代码:
go run main.go- 对代码进行编译,生成二进制可执行文件,
go build->./helloworld
包
引用包的顺序:
$GOROOT/src- 当前项目的
/vendor路径下 - 最后到
$GOPATH/src路径下寻找
// 别名引用
import (
pkgA "c/a"
)
func A(){
pkgA.Test()
}
// 匿名引用
import (
. "c/a"
)
func C(){
Test()
}包初始化
在每个包中都可以定义一个 init() 方法,该方法通常用于做一些初始化工作。对于包进行引用时,首先会执行引用包中的 init() 方法
如果只是需要执行引用包中的 init 方法,没有调用到其他内容时,可以使用关键字 _
package a
import (
__ "c/a"
)在引用包后必须调用到引用包中的内容 (使用了
_关键字除外),否则会编译出错
其他特性
- 可访问性:只有首字母大写的内容可以被别的包调用
- 包不能出现循环引用
包下载
一般情况下使用 go get 命令即可将包以及包所对应的依赖包都下载下来,而像是一些 golang.org/x/ 下的包则需要连接到外网才能下载到。
在没有翻墙手段的情况下,可以到 github 等代码仓库中找到对应的镜像包,如 golang.org/x/net 包在 github 中的镜像地址为 https://github.com/golang/net,使用 git 命令将包下载到 $GOPATH/src/golang.org/x/net 目录下即可。
例子:免翻墙下载 golang.org/x/net 包git clone git@github. com: golang/net. git $GOPATH/src/golang. org/x/net
也可以通过环境变量 GOPROXY 设置包下载代理服务地址
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct可选的代理地址如下:
变量
变量声明
golang 中使用关键字 var 声明变量,格式为:var {变量名称} {变量类型}
// 声明一个整型变量,默认值0
var v1 int
// 声明一个浮点型变量,默认值0
var v2 float32
// 声明一个数组变量,默认值nil
var v3 [10]int
// 声明一个切片变量,默认值nil
var v4 []float32
// 声明一个结构体变量,默认值{age:0}
var v5 struct {
age int
}
// 声明一个指针变量,默认值nil
var v6 *int
// 声明一个字典变量,默认值nil
var v7 map[string]string
// 声明一个方法变量,默认值nil
var v8 func(x int)int
// 声明一个接口变量,默认值nil
var v9 interface{}Make
使用 make 关键字声明切片、map 和 chan
func main() {
s := make(int[], 6)
m := make(map[string]int)
m["a"] = 1
c := make(chan int)
go func() {
ch <- 42
}
}
// struct 的声明方式
type Person struct {
Name string
Age int
}
p2 := Person{Name: "Bob", Age: 25}变量赋值
在声明变量时,可以为变量进行初始化赋值,有以下方式:
指定变量类型 var {变量名} {变量类型} = {变量值}
还可以进行匿名赋值,不指定变量类型,根据变量值的类型自动推断出变量类型var {变量名} = {变量值} 或 {变量名} := {变量值}
var v1 int = 30
var v2 = 50
v3 := 79常量
关键字 const,几个常量 true 、false 和 iota
类型

整型
int8 int16 int32 int64 int
uint8 uint16 uint32 uint64 uint uintptr(同uint)
var a1 int32 = 323
var a2 int64
a2 = int64(a1)位数长的类型转换为位数短的类型,或者无符号类型转为有符号类型,会丢失精度
浮点型
float32 float64在进行浮点数比较时,需指定精度
package main
import (
"fmt"
"math"
)
func main() {
var x,y float64 = 3.1, 3.0
fmt.Prinln(x-y == 0.1) // false
fmt.Pritln(IsEqual(x - 0.1, y, 0.1))
}
func IsEqual(x float64, y float64, precision float64) bool {
return math.Abs(x-y) < precision
}复数
complex64 complex128
布尔型
字符
| 类型 | 长度(byte) | 编码 |
| byte | 1(同 uint8) | UTF-8 |
| rune | 4(同 uint32) | Unicode |
字符串
字符串类型只能整体重新赋值,不能修改其中的某一位字符
- 字符串编辑:
string->[]byte或[]rune->string - 字符串和整型互转:
Atoi()、Itoa()
数组
var s2 [5]string = [5]string{0:"mike",3:"mary",2:"tony",1:"lucy",4:"gigi"}
虽然可以通过指针来传递数组参数(改变数组内部的值),但数组依然是僵化的类型,没法把 [16]byte 类型的数组指针赋给 [32]byte
切片
- 定义切片
- 切面创建(make 或者数组
[start: end]) - 读写
- 追加 (append
- 合并 (...
- 插入 (append 二次处理
- 删除 (append 覆盖
- 复制
- 函数中传参是引用传递
// 手写 appendInt
func appendInt(x []int, y int) []int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x) {
z = x[:zlen]
} else {
zcap := zlen
if zcap < 2 * len(x) { // 扩容两倍
zcap = 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x)
}
z[len(x)] = y
return z
}字典
- 初始化
- 读写
- 删除 (delete)
- 引用传递
map 的 range 遍历是随机的
通道 chan
指针 Pointer
*p有两种理解:
- 表达式:表示取 p 指针指向变量的值,在赋值语句右边使用
- 变量:表示 p 指向的变量
场景:
- 作为方法传参参数,指向占内存空间大的变量,以节约方法传参过程中的内存开销;
- 作为方法传参参数,指向需要在方法体内发生值变动后,在方法体外的值也同时需要发生变动的变量;
- 定义结构体指针方法,通过引用传递实现在方法体中修改源结构体中的字段值;
- 用于反射,获取所指向的元素类型。
与指针相关的关键字有两个 *和&
关键字 * 用于定义指针和获取指针的值
关键字 & 用于获取元素的指针地址
// 定义整型变量
var v1 int
// 定义一个指向整型的指针
var p1 *int
// 使用&获取整型变量地址并赋值给指针
p1 = &v1
// 定义一个指向浮点型的指针
var p2 *float32 = new(float32)func main() {
vals := []int{10, 20, 30, 40}
start := unsafe.Pointer(&vals[0])
size := unsafe.Sizeof(int(0))
for i := 0; i < len(vals); i++ {
item := *(*int)(unsafe.Pointer(unitptr(start) + size * uintptr(i)))
fmt.Println(item)
}
}方法 / 函数
函数内不能声明非匿名函数(继承 C 语言的特性)
遇到没有函数体的函数声明,这表示该函数不是以 Go 实现的。这样的声明定义了函数签名
pacakge math
fun Sin(x float64) float // 使用汇编语言实现与 JS/TS 不一样的地方
- 返回值可以提前定义名字,return 可缩写
- numbers.. int 張示多个 int 参数组成的数组
- 立即执行函数不需要 Hack
结构体
结构体方法 - 类型方法
&-取地址 , *-读地址
impont “fmt"
type Point struct{ X, Y int }
func modify(p *Point) { // 引用传递
p.X = 42 // (*p).X = 42 语法糖
}
func main() {
p1 := Point{1, 2}
modify(&p1)
fmt.Println (p1)
// A {42, 2}
// B {1, 2}
}与 JS 的不同之处:
- 结构体是值类型,不是引用类型,不能与 JS 的对象进行类比
- Go 只支持传值,不过可以把地址当做值 modify (&p1)
- 结构体支持 label,用于各种功能;JS 没有 label
匿名嵌入
var w Wheel
w.X = 8 // equivalent to w.Circle.Point.X = 8
w.Y = 8 // equivalent to w.Circle.Point.Y = 8
w.Radius = 5 // equivalent to w.Circle.Radius = 5
w.Spokes = 20接口
type Printable interface {
toString() string
}
func print (p Printable) {
fmt.Println (p.toString())
}
type User struct {
name string
}
//函数 function
func toString (u User) string {
return u.name
}
/ 方法 method
func (u User) toString() string {
return u. name
}
func main() {
u := User("frank"}
u.tostring()
}类型断言
func main() {
var i interface{} = "hello"
s, ok := i.(string)
fmt.Println(s, ok) // hello true
s, ok := i.(float64)
fmt.Println(f, ok) // false
}类型选择
switch v := i.(type) {
case T:
// v的类型为 T
case S:
// v的类型为 S
default:
// 没有匹配
}自定义类型
即类型别名
语法
判断
switch: 通过关键字 fallthrough 连带执行下一个 case 体中的内容,不管 case 条件是否成立。在必要时可以通过关键字 break 跳出 switch 语句
循环
if else
跟 JS 不一样的地方
- 推荐不加 ()
- if 接一个表达式等价于 JS 的 if (condition)
- if 接两个表达式没有等价的 JS 常见写法
for
for 循环也可以与关键字 range 结合,用于对数组、切片、字典和通道的遍历
package main
import (
"fmt"
)
func main() {
// 初始化切片
s1 := []string{"mike", "lili", "mary", "tony", "lucy"}
// 遍历切片,下标为index,值为value。value是复制出来的值,与原先切片中的元素不是同一个
for index, value := range s1 {
// 通过切片下标输出切片元素
fmt.Println(s1[index])
// 通过遍历获取到的元素值输出
fmt.Println(value)
}
}跟 JS 不一样的地方
- 不加(),加了会被删
- for 接 0 个表达式等价于 JS 的 while (true)
- for 接 1 个表达式等价于 JS 的 while (condition)
- for 接 3 个表达式等价于 JS 的 for (初始化, 判断,后续)
switch case
与 JS 不一样的地方
- 不推荐加 ()
- 不要加 break
- 一个 case 可以有多个值,用逗号隔开