1.Go语言简介
- 具有高性能、高并发、语法简单的特点
- 拥有丰富的标准库、完善的工具链
- 能够快速编译、支持跨平台
- Java一样不需要自己释放内存,拥有垃圾回收机制
2.开发环境搭建
3.语法基础
Hello Word
每个Go程序都是由包构成的,程序从main
包开始运行。Hello Word
程序导入了fmt
包用来进行格式化输出。fmt
包中格式化输出字符串的函数还有Printf
、Sprintf
、Fprintf
等,读取格式化字符串的函数有Scanf
、Sscanf
、Fscanf
等。
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World")
}
变量
变量声明有两种方式,一种是var
关键字声明,可以写在函数体外,声明并初始化基本语法如下:
package main
import "fmt"
var (
i, j int = 1, 2
k string = "yes"
)
func main() {
var a, b, c = true, false, "this"
fmt.Println(i, j, k, a, b, c)
}
另一种是通过简洁赋值语句:=
在类型明确的地方代替第一种声明方式,但不能在函数体外面使用,基本语法如下:
package main
import "fmt"
func main() {
k := float32(3)
fmt.Println(k)
}
另外,Go中不允许变量不被使用。
常量没有特定类型,将var
改成const
,不能用 :=
语法声明。也能够根据上下文确定类型。
if else
Go 的 if
语句,表达式外无需小括号 ( )
,而大括号 { }
则必须。if
语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if
之内。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
//在 main 的 fmt.Println`调用开始前,两次 pow 的调用执行并返回输出为
//27 >= 20
//9 20
}
循环
Go 中只有 for
关键字用于循环,与if
一样大括号 { }
必须。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
//初始条件和后置语句可选,去掉分号和while一样,什么都不写是死循环
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
fmt.Println(sum)
}
switch
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break
语句。 除非以 fallthrough
语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four, five")
default:
fmt.Println("other")
}
t := time.Now()
switch {//在switch中不加判断条件,而是在case中去写条件分支,这样可以处理多个条件
case t.Hour() < 12:
fmt.Println("before 12'")
default:
fmt.Println("after 12'")
}
}
}
数组
数组长度不能改变,切片弥补了这一缺陷,比数组更常用。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
//Hello World
//[Hello World]
//[2 3 5 7 11 13]
切片
类型 []T
表示一个元素类型为 T
的切片,例如:
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]
切片实际上存储了一个长度和容量以及指向底层数组的指针,容量不够会扩容返回新的切片。可以通过make([]type, len, cap)
函数来创建
是一个左闭右开的区间。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会更新。与python一样的切片操作,但不支持负数索引。
package main
import "fmt"
func main() {
//使用make创建切片
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[0])//a
fmt.Println("len:", len(s))// 3
//切片可以使用append追加元素
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)//[a b c d e f]
//使用make的时候也可以直接指定长度进行创建
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] 取出5之前的元素,0 1 2 3 4
fmt.Println(s[2:])//[c d e f] 取出第二个及以后的元素
good := []string{"1", "2", "3", "4"}
fmt.Println(good)//[1 2 3 4]
}
map
Go中的map
是完全无序的,遍历是按随机顺序。是一个映射,存储键值对,可以用make
函数创建。
package main
import "fmt"
func main() {
m := make(map[string]int)//空map,键字符串,值整数
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") //使用delete函数从map中删除键值对
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
println(m2, m3)
}
range
对于数组、切片和字符串,它将返回每个元素的索引和值。对于map
,它将返回每个键和值。对于通道,它将返回每个从通道中接收到的值。在遍历时如果不使用可以用 _
来忽略这个变量。
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)
//index: 0 num: 2
}
}
fmt.Println(sum)//9
m := map[string]string{"a": "A", "b": "B", "c": "C"}
for k, v := range m {//返回每个键和值
fmt.Println(k, v)//c C a A b B
}
for k := range m {
fmt.Println("key:", k)//key: b key: c key: a
}
}
函数
Go 中的函数可以有多个返回值,在实际业务中一般返回两个值,一个是真正的返回结果,第二个是错误信息。这些值可以通过在函数名称后面添加圆括号来返回。
package main
import "fmt"
func add(a int,b int) int {
return a+b
}
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//v就是结果,ok则是错误信息,是布尔类型
}
func main() {
res := add(1,2)
fmt.Println(res)//3
v,ok := exists(map[string]string{"a":"A"},"a")
fmt.Println(v,ok)//A true
}
指针
指针是指向一个变量的地址的变量。指针存储了变量的内存地址,通过指针来访问和更改变量的值。使用 &
符号,取地址运算符。
package main
import "fmt"
func add2(n int) {
//传值调用,也就是说,创建变量的副本,不是本身
n += 2//这是无效的
}
func add2ptr(n *int) {
//指针指向的是 main 中的 n 变量
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n)//5
add2ptr(&n)
fmt.Println(n)//7
}
结构体
结构体是带类型字段集合,使用关键字type
和struct
。
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "Tom", password: "2672"}
b := user{"Lily", "2342"}
c := user{name: "Marry"}
c.password = "3453"
var d user
d.name = "Dive"
d.password = "2357"
fmt.Println(a, b, c, d)
//{Tom 2672} {Lily 2342} {Marry 3453} {Dive 2357}
fmt.Println(checkPassword(a, "456"))//false
fmt.Println(checkPassword2(&a, "456"))//false
fmt.Println(a.name)
}
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() {
a := user{name: "Tom", password: "2672"}
fmt.Println(a)//{Tom 2672}
a.resetPassword("2345")
fmt.Println(a)//{Tom 2345}
fmt.Println(a.checkPassword("2345")) //true
}
错误处理
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{{"Tom", "2672"}}, "Tom")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name)
if u, err := findUser([]user{{"Tom", "2672"}}, "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
//接受一个字符串和一个前缀作为参数,并判断该字符串是否以该前缀开头。如果是,函数返回 true,否则返回 false
fmt.Println(strings.HasPrefix(a, "he")) //true
//接受一个字符串和一个后缀作为参数,并判断该字符串是否以该后缀结尾。如果是,函数返回 true,否则返回 false
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
b := "你好"
//获取字符串的长度,一个中文会对应多个字符
fmt.Println(len(b)) //6
}
字符串格式化
在Go语言中,占位符可以是%d
, %s
, %f
, %v
,%+v
和 %#v
是 fmt
包中使用的两个格式化动词。
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}
//%v可以打印任何变量,数字用:%d,字符串用:%s,获取更加详细的结果:%+v,在详细上进一步详细:%#v
fmt.Printf("s=%v\n", s) //s=hello
fmt.Printf("s=%v\n", n) //s=123
fmt.Printf("s=%v\n", p) //s={1 2}
fmt.Printf("s=%+v\n", p) //s={x:1 y:2}
fmt.Printf("s=%#v\n", p) //s=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) //3.141592653
fmt.Printf("%.2f\n", f) //3.14
}
JSON处理
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "Tom", Age: 17, 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":"Tom","age":17,"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:"Tom", Age:17, Hobby:[]string{"Golang","TypeScript"}}
}
时间处理
使用time
包,time.Now()
获取当前时间,sub
进行减法计算时间。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now)
t := time.Date(2023, 1, 15, 10, 0, 0, 0, time.UTC)
t2 := time.Date(2023, 1, 15, 23, 59, 59, 59, time.UTC)
fmt.Println(t) //2023-01-15 10:00:00 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) //2023 January 15 10 0
fmt.Println(t.Format("2006-01-02 15:04:05")) //2023-01-15 10:00:00
diff := t2.Sub(t)
fmt.Println(diff) //13h59m59.000000059s
fmt.Println(diff.Minutes(), diff.Seconds()) //839.9833333343166 50399.000000059
t3, err := time.Parse("2006-01-02 15:04:05", "2023-01-15 10:00:00")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) //true
fmt.Println(now.Unix()) //1673774280
}
数字解析
解决字符串与数字之间的转化。
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) //1.234
n, _ := strconv.ParseInt("111", 10, 64)//字符串 10进制(0则自动推测) 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
}
进程信息
在go,用os.argv
来得到程序执行的时候的指定的命令行参数。编译的一个二进制文件,command。后面接abcd来启动,输出就是os.argv会是一个长度为5的切片,第一个成员代表二进制自身的名字。用so.getenv
来读取环境变量。
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("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf))
}
4.实战示例
介绍了三个项目:猜谜游戏,在线词典,以及SOCKS5代理。
猜谜游戏侧重于随机数生成时使用种子才能生成不同数字。在线词典侧重于抓包以及解析。SOCKS5代理主要侧重于理论。