这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天。今天学习了Golang的基础语法和实际案例,今天将笔记整理发出。
1. 本章学习内容
-
Go语言代码的基本结构
-
Go语言的基本语法
2. Go语言基本语法内容
- 基本语法结构
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
Go语言的代码结构如上所示,主要由三部分构成,package-包,import-引入库文件,fun main()-主函数,也是程序运行的出口。
- Go的变量类型
Go语言是强类型语言,每种变量都有对应的类型,常见的类型包括:字符串、整数、浮点型、布尔型。其中字符串可以直接通过“+”进行拼接,也可以使用“=”比较两个字符串。
- 变量的声明
Go语言变量的声明有以下几种方式:
- 可以省略类型,Go会自动推导变量的类型,如第2行代码。
- 显示声明变量的类型,但是类型需要在变量名后面。
- 可以使用 := 来声明变量
func main() {
var str = "Hello World"
var i1,i2 int = 12, 23
var flag = true
var f1 float65
f2 := float32(f1)
}
- 常量的类型
Go语言中,常量使用const 来声明,常量没有具体的类型,是根据使用时的上下文确定常量的类型。
func main() {
const s string = "constant"
const i = 20000
}
- if-else 分支控制语句
Go中的控制语句如下所示,if 判断语句后面不需要():
func main() {
const i = 7
if i % 2 == 0 {
fmt.Println(i,"is oushu")
else {
fmt.Println(i,"is jishu")
}
if num := 9; num < 0 {
fmt.Println("case 01")
else if num < 10 {
fmt.Println("case 02")
} else {
fmt.Println("case 03")
}
}
- 循环
Go中只有for循环,没有while循环和do-while循环,使用for循环可以充当无限循环(死循环)和有限循环。
func main() {
i := 1
for {
fmt.Println("loop")
}
for j:=7;j<9;j++ {
fmt.Println("j=",j)
}
}
- switch-case分支控制语句
Golang中的switch分支控制结构和Java类似,不同的地方在于其省略了(),并且不需要写break语句,这就意味着,Golang只会符合switch中的一个case,执行完该case中的语句后,则会跳出switch语句。另外可以使用switch语句取代if-else 语句
func main() {
a := 2
switch a {
case 1:
fmt.Println("one") //执行完该条语句会直接退出switch语句
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t:=time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
defaule:
fmt.Println("It's after noon")
}
}
- 数组
数组是具有编号且长度固定的序列。在Golang中,数组的声明方式如下:
func main() {
var a [5]int
a[4] = 100
fmt.Println(a[4],len(a))
b:=[5]int{1,2,3,4,5}
var twoD [2][3]int
}
- 切片
切片可以视为一个可变长度的数组,其声明方式如下所示,通过make关键字进行声明,并且切片可以通过append()方法追加内容,但是需要注意的是必须将追加后的切片赋值给原来的那个切片。
func main(){
s := make([]string,3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
//可以使用append()方法追加内容,但是必须赋回原值
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
//copy()方法可以将一个切片赋值给另一个切片
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
- 集合map
Golang中的map集合是通过make关键字来创建的。其赋值方式为:map["one"] = 1,其中 [] 中的内容为 key , = 右边的内容为value。Golang中的map是完全无序的。不会按照插入顺序也不会按照字母顺序进行输出。
func main() {
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
- 使用range遍历
对于数组或者map集合,可以使用range来进行快速遍历。使用range遍历数组会返回索引和对应索引的值。遍历map,则第一个值是key,第二个值是value。当然在map中也可以只遍历key。
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) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
}
- 函数
Golang中的函数结构如下所示,可以看到使用func 开头,然后紧接函数名和参数列表,最后才是函数的返回值,和Java有着较大区别。
func add(a int, b int) int {
return a + b
}
- 指针
Golang支持指针,其主要目的是可以针对传入的值进行修改,传入指针类型的参数变量需要使用*+类型,例如*int,*string,而在实际使用时需要使用&+变量。如下面第13行代码。
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
- 结构体
结构体是带类型的字段的集合,其声明方式如下所示,可以使用结构体名称来初始化一个结构体变量。
type user struct {
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
}
//普通方法
func checkPassword(u user, password string) bool {
return u.password == password
}
- 结构体方法
Golang中的结构体方法类似于Java中的成员方法,其结构如下所示,结构体方法和普通方法相比,在func 后面增加了结构体类型。即代码中的(u user) 和(u *user),同时结构体类型也有指针和非指针类型。
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() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
- 错误处理
Golang中错误处理的习惯是使用单独的返回值来传递错误信息,这样就可以使用if-else语句来传递错误信息。可以在函数的返回值中加入一个error类型的错误,当出现错误时,创建一个新的错误信息即可。代码如下所示:
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")
}
- 字符串操作
Golang中的对字符串的操作在标准库 “strings"中,常见的操作如下:
- Contains()-是否包含某个字符串,返回布尔值
- Count()-统计字符串中某个字符出现的次数
- HasPrefix()-字符串是否以某个字符串为前缀
- HashSuffix()-字符串是否以某个字符串为后缀
- Index()-某字符串在目标字符串中出现的索引
- Join()-连接多个字符串
- Repeat()-重复多个字符串
- Split()-分隔多个字符串
- ToLower()-将字符串转换为小写
- ToUpper()-将字符串转换为大写
- len()-输出字符串的长度(内置函数)
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
}
- 字符串的格式化
在fmt标准库中,有很多字符串的格式化输出的方法,使用%v可以打印任意类型的变量。
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}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
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
}
- 结构体转化为Json
在Golang中结构体转化为Json特别简单,只需要保证结构体中的属性名首字母为大写时,使用json.Marshal()就可以进行序列化,然后打印时需要强制转换为字符串类型,否则会打印十六进制的数字。另外,如果要输出为首字母为小写的json时,则需要在结构体的字段中显式声明 json :age。详情见如下的代码:
type userInfo struct {
Name string
Age int `json:"age"`
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) // [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"}}
}
- 时间处理
Golang中的格式化用的是一个特定的时间,即“2006-01-02 15:04:05”。
- time.Now()-获取当前时间
- Format()-格式化时间
- Sub()-某个时间减去另外一个时间
func main() {
//time是一个时间库
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
}
- 类型转换
字符串和数值之间的转换都在“strconv”包中。常见的方法有:
- ParseFloat()-解析字符串为浮点型
- ParseInt()-解析字符串为整型。
- Atoi()-快速将字符串转换为数字
- ItoA()-快速将数字转换为字符串
func main() {
//64表示精度
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
//10表示进制,不传是自动推断
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
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
}
- 获取进程相关的信息
Golang中可以通过os标准库来获取Go程序运行时进程相关的信息。
- Args()-获取参数李彪
- Getenv()、Setenv()-获取/写入环境变量
import (
"fmt"
"os"
"os/exec"
)
func main() {
// 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
}
可以看到Golang相比Java的一些不同地方:
- 不需要写“ ; ”
- if-else,switch语句不需要写“ () ”
- switch语句中不需要写 break 语句
- 可以使用switch语句代替if-else语句
- Golang中的库函数以 大写字母 开头,内置函数和自定义函数使用小写字母开头
- fmt.Println 输出语句中使用 “,”分隔并连接(语法分隔,输出连接)
- 变量的声明和java不同,其中更是分为变量和常量。
- 函数的结构和Java不同,Golang的返回值在后,Java的返回值在前。
- Golang支持指针,Java中称为引用类型。
- Java中一切皆为对象,而Golang中则为结构体,并且结构体方法对应成员方法。但是两者书写规则相差较远。
- Golang中是使用自定义错误信息的处理的方式,而Java中则是使用异常的处理方式。
- 使用:=符号可以在变量不声明的情况下直接被赋值使用。