这是我参与「第五届青训营 」笔记创作活动的第1天
Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(静态编译)。
我们以经典的hello,world作为入门
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Go语言的代码通过包(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。每个源文件都以一条package声明语句开始,这个例子里就是package main,表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。
main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main里的main 函数 也很特殊,它是整个程序执行时的入口。main函数所做的事情就是程序做的。当然了,main函数一般调用其它包里的函数完成很多工作
必须告诉编译器源文件需要哪些包,这就是跟随在package声明后面的import声明扮演的角色。hello world例子只用到了一个包,大多数程序需要导入多个包。
一个函数的声明由func关键字、函数名、参数列表、返回值列表(这个例子里的main函数参数列表和返回值都是空的)以及包含在大括号里的函数体组成。
Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析
Go语言在代码格式上采取了很强硬的态度。gofmt工具把代码格式化为标准格式(并且这个格式化的工具没有任何可以调整代码格式的参数),并且go工具中的fmt子命令会对指定包,否则默认为当前目录中所有.go源文件应用gofmt命令。
程序结构
命名
Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。并且大小写敏感。
Go语言中类似if和switch的关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内建函数: make len cap new append copy close delete
complex real imag
panic recover
此外,Go语言程序员推荐使用驼峰式命名。
声明与变量
Go语言的声明有以下4种
var:变量const:常量type:类型func:函数实体对象的声明
使用var进行声明
变量可以这样进行声明
var 变量名 类型 = 表达式
其中类型或者表达式可以省略其一,因为不管是省略前者还是后者,Go语言都有他自己的方法。
省略类型信息
类型信息会根据其表达式对类型进行推导
省略表达式
如果是数值类型变量,那么将被声明为0(int => 0)
如果是布尔类型变量,那么将被声明为0(bool => false)
如果是字符串类型变量,那么将被声明为0(string => "")
如果是接口或引用类型变量,那么将被声明为0(slice,map,chan,指针,函数 => nil)
简短变量声明语句
在函数内部,还有一种声明
名字 := 表达式
因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。var形式的声明语句往往是用于需要显式指定变量类型的地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64 //0
f := float32(e) //0
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
//constant 500000000 6e+11 -0.28470407323754404 0.7591864109375384
}
在我看来,go更像是Python和C语言的结合,语法相似,基础语法理解起来并不难,性能强大,并且很多功能都类似,比如C/C++特色的指针,这里也有类似的体现
如果用“var x int”声明语句声明一个x变量,那么&x表达式(取x变量的内存地址)将产生一个指向该整数变量的指针,指针对应的数据类型是*int,指针被称之为“指向int类型的指针”。
如果指针名字为p,那么可以说“p指针指向变量x”,或者说“p指针保存了x变量的内存地址”。同时*p表达式对应p指针指向的变量的值。一般*p表达式读取指针指向的变量的值,这里为int类型的值,同时因为*p对应一个变量,所以该表达式也可以出现在赋值语句的左边,表示更新指针所指向的变量的值。
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "1"
*p = 2 // equivalent to x = 2
fmt.Println(x) // "2"
new函数
表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。
new函数类似一个语法糖,并且每次调用都会返回一个新的地址
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"
基础数据类型
类C,其中常见到的8,16,32,64指的是8,16,32,64bit大小的有符号整数
复合数据类型
数组
在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。
//初始化1
var q [3]int = [3]int{1, 2, 3}
//初始化2
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已,会进行自动扩容操作,每一次容量的变化都会导致重新分配内存和copy操作。
//初始化
var runes []rune
//使用range来遍历,对于数组会返回两个值 第一个是下标 第二个是数值 如果不需要索引可以用_
for _, r := range "Hello, 世界" {
//append(原数组,...) 可以追加多个数
runes = append(runes, r)
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
//内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。
s := make([]string, 3)
map
无序的key/value对的集合,其中所有的key都是不同的
//创建方式
m1 := make(map[string]int)
//赋值方式
m1["one"] = 1
m1["two"] = 2
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
//删除数据
delete(m1, "one")
fmt.Println(m) //map[two:2]
而且x += y和x++等简短赋值语法也可以用在map上
ages["bob"] += 1
ages["bob"]++
但是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作
结构体
//声明
type Employee struct {
ID int
Name string
DoB time.Time
Salary int
Position string
}
//声明了一个Employee类型的变量dilbert
var dilbert Employee
//dilbert结构体变量的成员可以通过点操作符访问和赋值
dilbert.Salary -= 5000
//或者是对成员取地址,然后通过指针访问
position := &dilbert.Position
*position = "Senior " + *position
JSON
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`//该步骤可以更改json中的名称
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) //16进制编码 [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
//反序列化
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
函数
//后置数据类型
func add2(a, b int) int {
return a + b
}
//习惯于第一个是真正的返回值,第二个返回值是错误
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
错误处理
使用一个单目的返回值来传递错误信息,go语言的错误处理(使用if处理)能够及时处理,并且能够很清晰的知道哪个函数出错了
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
字符串操作
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
字符串格式化
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
//+v 查看较为详细的结构
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
//#v 更加详细
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
时间处理
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
//截取时间
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
//比较的结果是值而不是地址
fmt.Println(t3 == t) // true
//获取时间戳
fmt.Println(now.Unix()) // 1648738080
}
数字解析
package main
import (
"fmt"
"strconv"
)
func main() {
//字符串 => 数字
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
//10进制,64位
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
//0代表自动推测进制
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
进程信息
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
//该过程需要像Args中传入参数
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
//分别来获取和写入环境变量
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
//启动子进程和获取输入输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}
以上内容参考了Go语言圣经(中文版) 以及后端专场 学习资料一】第五届字节跳动青训营中老师提供的代码