Skip to content
标签
note
字数
2657 字
阅读时间
12 分钟

the better C - 更好的 C 语言

前置知识

gofmt

Go 语言在代码格式上采取了很强硬的态度。gofmt 工具把代码格式化为标准格式(译注:这个格式化工具没有任何可以调整代码格式的参数,Go 语言就是这么任性),并且 go 工具中的 fmt 子命令会对指定包,否则默认为当前目录中所有 go 源文件应用 gofmt 命令

以法令方式规定标准的代码格式可以避免无尽的无意义的琐碎争执(译注:也导致了 Go 语言的 TIOBE 排名较低,因为缺少撕逼的话题)

基础

Hello World

一个标准的 golang 项目分为两种:程序和包

go
// main.go
package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

执行代码:

  1. go run main.go
  2. 对代码进行编译,生成二进制可执行文件,go build -> ./helloworld

引用包的顺序:

  1. $GOROOT/src
  2. 当前项目的 /vendor 路径下
  3. 最后到 $GOPATH/src 路径下寻找
go
// 别名引用
import (
	pkgA "c/a"
)
func A(){
	pkgA.Test()
}

// 匿名引用
import (
	. "c/a"
)
func C(){
	Test()
}

包初始化

在每个包中都可以定义一个 init() 方法,该方法通常用于做一些初始化工作。对于包进行引用时,首先会执行引用包中的 init() 方法

如果只是需要执行引用包中的 init 方法,没有调用到其他内容时,可以使用关键字 _

go
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
go env -w GO111MODULE=on

go env -w GOPROXY=https://goproxy.io,direct

可选的代理地址如下:

变量

变量声明

golang 中使用关键字 var 声明变量,格式为:var {变量名称} {变量类型}

go
// 声明一个整型变量,默认值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

go
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 {变量名} = {变量值}{变量名} := {变量值}

go
var v1 int = 30
var v2 = 50
v3 := 79

常量

关键字 const,几个常量 truefalseiota

类型

image.png

整型

go
int8 int16 int32 int64 int
uint8 uint16 uint32 uint64 uint uintptr(同uint)

var a1 int32 = 323
var a2 int64
a2 = int64(a1)

位数长的类型转换为位数短的类型,或者无符号类型转为有符号类型,会丢失精度

浮点型

go
float32 float64

在进行浮点数比较时,需指定精度

go
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)编码
byte1(同 uint8)UTF-8
rune4(同 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

切片

  1. 定义切片
  2. 切面创建(make 或者数组 [start: end]
  3. 读写
  4. 追加 (append
  5. 合并 (...
  6. 插入 (append 二次处理
  7. 删除 (append 覆盖
  8. 复制
  9. 函数中传参是引用传递
go
// 手写 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
}

字典

  1. 初始化
  2. 读写
  3. 删除 (delete)
  4. 引用传递

map 的 range 遍历是随机

通道 chan

指针 Pointer

*p 有两种理解:

  • 表达式:表示取 p 指针指向变量的值,在赋值语句右边使用
  • 变量:表示 p 指向的变量

场景:

  1. 作为方法传参参数,指向占内存空间大的变量,以节约方法传参过程中的内存开销;
  2. 作为方法传参参数,指向需要在方法体内发生值变动后,在方法体外的值也同时需要发生变动的变量;
  3. 定义结构体指针方法,通过引用传递实现在方法体中修改源结构体中的字段值;
  4. 用于反射,获取所指向的元素类型。

与指针相关的关键字有两个 *和&
关键字 * 用于定义指针和获取指针的值
关键字 & 用于获取元素的指针地址

go
// 定义整型变量
var v1 int
// 定义一个指向整型的指针
var p1 *int 
// 使用&获取整型变量地址并赋值给指针
p1 = &v1

// 定义一个指向浮点型的指针
var p2 *float32 = new(float32)
go
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 实现的。这样的声明定义了函数签名

go
pacakge math
fun Sin(x float64) float // 使用汇编语言实现

与 JS/TS 不一样的地方

  • 返回值可以提前定义名字,return 可缩写
  • numbers.. int 張示多个 int 参数组成的数组
  • 立即执行函数不需要 Hack

结构体

结构体方法 - 类型方法

&-取地址 *-读地址

go
impontfmt"
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
匿名嵌入
go
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

接口

go
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()
}
类型断言
go
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
}
类型选择
go
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 结合,用于对数组、切片、字典和通道的遍历

go
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 可以有多个值,用逗号隔开

参考

Go语言的主要特征 · Go语言中文文档

Hello,world! - Golang 学习手册

贡献者

The avatar of contributor named as jiechen jiechen

页面历史

撰写