这是我参与「第五届青训营」伴学笔记创作活动的第 1 天
Go的语法与C语言高度相似,本文中将以代码为中心,需要注意的内容将写在注释中
简介
什么是Go语言
-
高性能、高并发
Go使用标准库或基于标准库的第三方库来实现高并发
-
语法简单、学习曲线平缓
package main import "net/http" func main() { //将标准库的http包里,将“/”路由指向静态文件夹“.” http.Handle("/", http.FileServer(http.Dir("."))) //监听端口并启动服务 http.ListenAndServe(":8080", nil) }
标准库实现一个Http服务器
-
丰富的标准库
可以保障稳定性、兼容性
-
完善的工具链
编译、代码格式化、错误检查、帮助文档、包管理等……
-
静态链接
-
快速编译
Go语言有着静态语言里最快的编译速度
-
跨平台
支持交叉编译
-
垃圾回收
无需考虑内存分配释放
应用领域:云计算、微服务、大数据、区块链、物联网等
Docker、Kubernetes、Istio、etcd、prometheus 几乎所有的云原生组件全是用Go实现的。
基础语法
变量
声明
- 未使用不能声明
- 不可以重复声明
package main
import "fmt"
func main(){
//小驼峰userName
// 大驼峰UserName
var a int
// 未使用不能声明
fmt.Println(a)
}
初始化
package main
import "fmt"
func main(){
//变量初始化
var b int = 100
fmt.Println(b)
}
赋值
package main
import "fmt"
func main(){
//赋值
var c float64 = 123.321
c = 321.123
fmt.Println(c)
}
可以同时给多个变量赋值
package main
import "fmt"
func main(){
var a,b,c=100,200,300;
}
自动推导
自动推导是让变量根据声明时候初始化的值去自动判断变量的类型
package main
import "fmt"
func main(){
// 自动推导
var sum = 789.654
fmt.Printf("%T\n",sum)//%T是打印输出类型的格式控制符
// 常用的自动推导,相当于声明+初始化
// a := 100 //会报错,因为a已经声明过了
pi := 3.1415926
fmt.Println(pi)
}
这里的变量sum被自动推导出了为float64类型
字符串
package main
import (
"fmt"
)
func main() {
var str string
str = "Hello World"
fmt.Println(str)
//字符串长度
fmt.Println(len(str))
//一个汉字3字符
ch := "中"
fmt.Println(len(ch))
//字符串拼接
str1 := "Hello"
str2 := " World"
str3 := str1 + str2
fmt.Println(str3)
//字符串取单个字符
strr := "Hello World"
fmt.Println(str[0], strr[1]) //对应的ASCII码
fmt.Printf("%c,%c", strr[0], strr[1]) //输出字符
}
判断结构
if-else
package main
import "fmt"
func main() {
//if支持一个初始化语句,只在当前判断生效
if a := 700; a == 700 {
fmt.Printf("%d\n", a)
}
if a := 500; a > 700 {
fmt.Println("a>700")
} else if a == 700 {
fmt.Println("a==700")
} else {
fmt.Println("a<700")
}
}
switch
Go的Switch与C的switch不一样
C的switch如果不遇到break,会跑完所有的case
而Go的switch只会跑完符合条件的case
switch a := 120; a {
case 80:
fmt.Println("80")
case 90:
fmt.Println("90")
case 100, 110, 120:
fmt.Println("100,110,120") //可以有多个值匹配
fallthrough //相当于continue
default:
fmt.Println("default")
}
Go的switch支持任意的数据类型,字符串,结构体等
Go的switch判断变量可以留空,在case里添加条件,起到代替if-else的作用
//判断字符留空
switch sc := 100; {
case sc >= 100:
fmt.Println("A")
case sc <= 100:
fmt.Println("B")
default:
fmt.Println("C")
}
数组
package main
import "fmt"
func main() {
//一维数组
var a [5]int
a[4] = 100
fmt.Println(a[4], len(a))
//初始化
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
//二维数组
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println(twoD)
}
切片
- 切片是可变长度的数组,可以任意时刻去更改长度
- Golang里Slice的结构是长度+容量+指向数组的指针,当容量不够时,会进行扩容,并返回新的Slice,因此需要赋值回去
- Golang不支持负索引,需要借助len()进行运算
package main
import "fmt"
func main() {
//用make创建切片
s := make([]string, 3)
s[0], s[1], s[2] = "a", "b", "c"
fmt.Println(s[2])
fmt.Println(len(s))
//使用append追加元素
//append必须将结果赋值给原数组
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s, len(s))
//make指定长度
c := make([]string, len(s))
//拷贝数据
copy(c, s)
fmt.Println(c)
//切片取值
fmt.Println(s[2:5]) //左闭右开
fmt.Println(s[:5])
fmt.Println(s[2:])
}
map
其他语言又被称为哈希、字典
package main
import "fmt"
func main() {
//make创建空map
//key的类型为string,value的类型为int
m := make(map[string]int)
//读取、写入map
m["one"] = 1
m["two"] = 2
fmt.Println(m)
fmt.Println(len(m))
fmt.Println(m["one"])
fmt.Println(m["unknow"])
//可以通过ok来检查指定的key是否存在
r, ok := m["unknow"]
fmt.Println(r, ok)
r, ok = m["one"]
fmt.Println(r, ok)
//删除k-v对
delete(m, "one")
//map的kv对是完全无序的,遍历时候不会以任何顺序输出
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
range
对于一个slice或者map可以用range进行遍历
package main
import "fmt"
func main() {
//遍历数组
nums := []int{2, 3, 4}
sum := 0
//第一个参数是索引,第二个参数是索引对应的值
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num)
}
}
fmt.Println(sum)
//遍历map
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v)
}
//遍历输出map的k
for k := range m {
fmt.Println("k=", k)
}
}
函数
- 返回值类型后置
- 可以返回多个值
- 通常第一个值返回真正的结果,第二个值返回错误信息
package main
import "fmt"
//返回值类型后置
func add(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
}
func main() {
fmt.Println(add(1, 2))
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok)
}
指针
package main
import "fmt"
func Add(n int) {
//这里的n只是一个拷贝,不会改变原变量的值(内存地址不同)
n += 2
}
func Addptr(n *int) {
*n += 2
}
func main() {
n := 5
Add(n)
fmt.Println(n)
Addptr(&n)
fmt.Println(n)
}
结构体
-
结构体的三种初始化赋值方式
- 完整初始化各字段(有关键字)
- 完整初始化各字段(无关键字)
- 只初始化其中一部分字段(未初始化的字段默认为空值)
- 只初始化结构体变量,后给字段赋值
-
结构体也可以作为函数参数
- 指针式
- 非指针式
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
//第一种
a := user{name: "XiaoZhi", password: "123456"}
//第二种
b := user{"XiaoZhi", "123456"}
//第三种
//可以只初始化一个字段,但必须指明初始化哪个字段,没有初始化的字段默认为空
c := user{name: "XiaoZhi"}
fmt.Println(c.password)
c.password = "123456"
fmt.Println(c.password)
//第四种
var d user
d.name = "XiaoZhi"
d.password = "123456"
fmt.Println(a, b, c, d)
}
//结构体作为函数参数
func CheckPassword(u user, password string) bool {
return u.password == password
}
//指针式
func CheckPassword2(u *user, password string) bool {
return u.password == password
}
使用指针可以实现对结构体变量的修改, 同时避免大结构变量产生拷贝的开销
结构体函数
类似于类成员函数,
package main
import "fmt"
type user struct {
name string
password string
}
func (u user) CheckPassword(password string) bool {
return u.password == password
}
func (u *user) ResetPassword(password string) {
u.password = password
}
func main() {
u := user{"XiaoZhi", "admin"}
fmt.Println(u.CheckPassword("admin"))
u.ResetPassword("adminadmin")
fmt.Println(u.password)
}
错误处理
在Golang里,符合语言错误处理的习惯是,使用一个单独的返回值来传递错误信息
区别于Java中的异常处理,Go中可以清晰地知道哪个函数返回了错误,并可以用简单的if-else去处理错误
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() {
v, err := findUser([]User{{"Yang", "1024"}}, "Yang")
if err != nil {
fmt.Println(err)
return
}
//查找成功,继续执行,打印用户姓名
fmt.Println(v.name)
//查找失败,输出错误信息
if u, err := findUser([]User{{"wang", "1234"}}, "li"); err != nil {
fmt.Println(err)
return
} else {
fmt.Println(u.name)
}
}
字符串操作
package main
import (
"fmt"
"strings"
)
func main() {
a := "Hello"
fmt.Println(strings.Contains(a, "ll")) //查询字串是否存在
fmt.Println(strings.Count(a, "l")) //字串出现次数计数
fmt.Println(strings.HasPrefix(a, "he")) //前缀判断
fmt.Println(strings.HasSuffix(a, "llo")) //后缀判断
fmt.Println(strings.Index(a, "ll")) //字串位置
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) //字符串拼接,自定义分隔符
fmt.Println(strings.Repeat(a, 2)) //重复多次字符串
fmt.Println(strings.Replace(a, "e", "E", -1)) //替换字符
fmt.Println(strings.Split("a-b-c", "-")) //分割字符串为数组
fmt.Println(strings.ToUpper("abc")) //转大写
fmt.Println(strings.ToLower("abc")) //转小写
fmt.Println(len(a)) //获取字符串长度
fmt.Println(len("你好")) //一个中文3个字符长度
}
字符串格式化
package main
import "fmt"
type Point struct {
x, y int
}
func main() {
s := "hello"
n := "123"
p := Point{1, 2}
fmt.Println(s, n)
fmt.Println(p)
//%v自动识别类型
fmt.Printf("s=%v\n", s)
fmt.Println("n=%v\n", n)
fmt.Println("p=%v\n", p)
//%+v得到更完整的结构
fmt.Printf("p=%+v\n", p)
//%#v更更进一步细化
fmt.Printf("p=%#v\n", p) //结构体的类型名称、字段名字及值
//设置小数点、精确度
b := 3.1415926535
fmt.Println(b)
fmt.Printf("%.2f\n", b)
}
JSON处理
- JSON首字母必须大写
- 序列出来的字符串是一个大写字母开头的,如果输出需要小写,则需要在结构体中添加tag,在输出时会小写
- 需要使用强制类型转换为字符串,否则为一个16进制字符串
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
//JSON首字母必须大写
Name string
Age int `json:"age"` //添加tag
Hobby []string
}
func main() {
a := userInfo{"XiaoZhi", 24, []string{"sing", "dance", "rap", "play basketball"}}
//序列化
//序列出来的字符串是一个大写字母开头的
//如果输出需要小写,则需要在结构体中添加tag,在输出时会小写
buf, err := json.Marshal(a)
if err != nil {
//运行时恐慌
//在panic被抛出之后,如果没有在程序里添加任何保护措施的话,程序就会在打印出panic的详情,终止运行
panic(err)
}
// 序列化为字符串
fmt.Println(buf) //十六进制字符串
// 强制类型转换
fmt.Println(string(buf))
//应用缩进来格式化输出
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)
}
时间处理
package main
import (
"fmt"
"time"
)
func main() {
//快速获取当前时间
now := time.Now()
fmt.Println(now)
//构造一个带时区的时间
t := time.Date(2023, 1, 15, 12, 0, 36, 6, time.UTC)
t2 := time.Date(2023, 1, 15, 12, 20, 36, 6, time.UTC)
fmt.Println(t)
//获取时间点信息
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute())
//格式化一个时间字符串
fmt.Println(t.Format("2006-01-02 15:04:05"))
//时间相减,获得时间段
diff := t2.Sub(t)
fmt.Println(diff)
//输出时间段内有多少分钟,多少秒
fmt.Println(diff.Minutes(), diff.Seconds())
//第一个参数是时间字符串格式,第二个为具体时间字符串
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)
//获取时间戳
fmt.Println(now.Unix())
}
数字解析
常用数字解析函数
package main
import (
"fmt"
"strconv" //string convert缩写
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f)
//第二个参数表示进制(8、10、16,如果传0为自动推测)
//第三个参数为精度
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n)
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n)
//字符串快速转数字
n2, _ := strconv.Atoi("123")
fmt.Println(n2)
//数字转字符串
n3 := strconv.Itoa(132)
fmt.Println(n3)
}
进程信息
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
//当执行当前程序 包含参数时,读取参数
fmt.Println(os.Args)
//打印环境变量
fmt.Println(os.Getenv("PATH"))
// 设置环境变量
fmt.Println(os.Setenv("AA", "BB"))
// 启动子进程,获取输入输出
buf, err := exec.Command("java", "--version").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf))
}