Go语言入门指南:基础语法和常用特性解析 | 青训营

78 阅读6分钟

1 什么是go语言

  • 高性能,高并发

    • 不需要通过其它经过高度优化的第三方库来实现高并发
    • 通过go语言的标准库或者基于标准库开发的第三方库即可实现高并发
    • 有和c,c++,java媲美的性能
  • 语法简单,学习曲线平缓,学习周期短,几行代码即可实现一个承载静态文件装载的该并发

    • 丰富的标准库
  • 完善的工具链

  • 静态链接

  • 快速编译,编译速度非常快

  • 跨平台(各种系统,各种奇奇怪怪的小东西)

  • 垃圾回收

2 入门

2.1.1 开发环境--安装Golang

打开go的官网,下载后安装提示进行安装即可

2.1.2 配置集成开发环境

可以选择VScode,GoLand

本人选择了vscode进行开发,通过左侧下载go的相关插件即可

2.2 基础语法

Hello World

package main

import (
	"fmt"
)

func main() {
	fmt.Print("hello world")
}

通过 go run demo.go即可运行 编译成二进制文件可以使用go build demo.go ,然后通过./demo运行exe文件

变量

package main

import (
	"fmt"
	"math"
)

func main() {
	var a = "initial"
	var b,c int = 1,2
	var d = true
	var e float64
	f := float32(e)
	g := a + "foo" // 可以通过==去比较字符串
	fmt.Println(a,b,c,d,e,f)
	fmt.Println(g)

	const s string = "constant" // 常量,没有确定的类型
	const h = 500000
	const i = 3e20 / h
	fmt.Println(s,h,math.Sin(h),math.Sin(i))
}

注意: 常量最开始没有确定类型,而是根据使用的时候来确定

if-esle

package main

import (
	"fmt"
)

func main() {
	if 7%2 == 0 {
		fmt.Println("7 is even")
	} else {
		fmt.Println("7 is not even")
	}

	if 8%4 == 0 {
		fmt.Println("8 is not even")
	}

	if num := 9; num < 0 {
		fmt.Println(num, "is negative")
	} else if num < 10 {
		fmt.Println(num, "has 1 digit")
	} else {
		fmt.Println(num, "has mutiple digits")
	}
}

对于if-else,不同于c++,java等,条件里面不需要() 这里可以显得更加的简洁

注意:if后面的内容必须重开一行,不能把if里面的东西写在同一行

循环

	for j := 7; j < 9; j++ {
		fmt.Println(j)
	}
	for n := 0; n < 5; n++ {
		if n%2 == 0 {
			continue
		}
		fmt.Println(n)
	}
	i := 1
	for i <= 3 {
		fmt.Println(i)
		i = i + 1
	}

	for {
		// 死循环
		fmt.Println("loop")
	}

在go里面,没有while和do while,通过一个for即可实现while和do while的功能

switch

	a := 2
	switch a {
	case 1:
		{
			fmt.Println("one")
		}
	case 4, 5:
		{
			fmt.Println("four of five")
		}
	default:
		{
			fmt.Println("others")
		}
	}

	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("before afternoon")
	default:
		fmt.Println("after afternon")
	}

case不需要break,不会进行贯穿,通过case不仅仅可以是数字,还可以是其它的东西,比如字符串,切片等,通过不同的类型使用,让switch的使用场景可以更加的广泛

数组和切片

    b := [5]int{1,2,3,4,5}
    var a [5]int
    a[4] = 100
    var twoD [2][3]int

    func main() {
        var t [2][3]int
        for i := 0; i < 2; i++ {
            for j := 0; j < 3; j++ {
                t[i][j] = i + j
            }
        }
        fmt.Println(t) // [[0 1 2] [1 2 3]]
    }

	s := make([]string, 3) // 切片
	s[0] = "a"
	s[1] = "b"
	s[2] = "c"
	fmt.Println("get", s[2])
	fmt.Println("len", len(s))

	s = append(s, "d")
	s = append(s, "e", "f")
	fmt.Println(s)

	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]

在go里面,其实数组很少使用,因为它长度固定了,通常都是使用切片,因为切片的长度是可以改变的。

切片是一个可变长度的数组,通过make创建一个切片

append必须把原数组的值赋值回去原数组

通常切片具有和python一样的切片操作 slice

  • slice预分配内存,尽可能在使用make()初始化切片时提供容量信息

    data := make([]int,0)
    data := make([]int,0,10)
    
  • 切片的底层实现就是可扩容的链表

  • 陷阱:大内存未释放

    1. 在已有的切片上创建切片,不会创建新的底层数组
    2. 原切片较大,代码在原切片的基础上新建小切片
    3. 原底层数组在内存中引用,得不到释放
  • 可使用copy替代re-slice

map(哈希,字典)

	m := make(map[string]int)
	m["one"] = 1
	m["two"] = 2
	fmt.Println(len(m))
	fmt.Println(m["one"])    // 1
	fmt.Println(m["unknow"]) // 0

	r, ok := m["unknow"] // ok表示这个map中是否含有这个键
	fmt.Println(r, ok) // 0 false

	m2 := map[string]int{
		"one": 1,
		"two": 2,
	}
	delete(m,"one")
	var m3 = map[string]int{
		"one": 1,
		"two": 2,
	}
	fmt.Println(m2, m3) // map[one:1 two:2] map[one:1 two:2]

就是其它语言的哈希,可以通过make进行创建

数据插入map后在map中是无序的

可以通过delete去删除K-V对

  • map预分配内存

    • 不断地向map中添加新元素地操作会触发map地扩容
    • 提前分配好空间可以减少内存拷贝和Rehash地消耗
    • 建议提前根据实际需求预估好需要的空间大小

range

	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)

	m := map[string]int{
		"one": 1,
		"two": 2,
	}
	for k, v := range m {
		fmt.Println(k, v)
	}
	for k := range m {
		fmt.Println("key", k)
	}

通过range我们可以对数组,切片,map等进行遍历

对于数组返回两个值,一个值是数组的索引,另一个是该索引下的值

函数

go里面变量类型是后知的,go的函数原生支持返回多个值,第一个值是真正的返回结果,第二个值是错误信息

import "fmt"

func add(a int, b int) (int, error) {
	return a + b, nil
}

func exists(m map[string]string, k string) (v string, ok bool) {
	v, ok = m[k]
	return v, ok
}

func main() {
	res, _ := add(1, 2)
	fmt.Println(res)
	v, ok := exists(map[string]string{"a": "A"}, "a")
	fmt.Println(v, ok)
}

指针

package main

import "fmt"

func add2(n int) {
	n += 2
}

func add2ptr(n *int) {
	*n += 2
}

func main() {
	n := 5
	add2(n)
	fmt.Println(n)
	add2ptr(&n)
	fmt.Println(n)
}

go语言里面的指针主要用途是对传入的参数进行修改

结构体

结构体是带类型和字段的集合,初始化时可以选择指定或者不指定结构体的内容,使用指针时可以在别的地方对值进行修改


type user struct {
	name     string
	password string
}

func main() {
	a := user{name: "wang", password: "123"}
	b := user{"wang", "123"}
	c := user{name: "wang"}
	c.password = "123"
	var d user
	d.name = "wang"
	d.password = "123"
	fmt.Println(a, b, c, d)
	fmt.Println(check2Password(&a, "haha"))
	fmt.Println(checkPassword(b, "123"))
}
func checkPassword(u user, password string) bool {
	return u.password == password
}
func check2Password(u *user, password string) bool {
	return u.password == password
}

当(u *user)提前后,该方法就从一个普通的方法变为一个结构体方法

func (u *user) resetPassword(password string){
	u.password = password
}

错误处理

在golang里面,通常使用一个带错误的返回值来对函数进行错误的处理

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", "123"}}, "li")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(u.name)
}

在调用方法的时候,通过判断err是否为空来判断是否发生了错误

字符串操作

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))
}

字符串格式化

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)
	fmt.Printf("n=%v\n", n)
	fmt.Printf("p=%v\n", p)
	fmt.Printf("p=%+v\n", p)
	fmt.Printf("p=%#v\n", p)
	f := 3.14159
	fmt.Println(f)
	fmt.Printf("%0.2f\n", f)
}

%v可以代替任意类型的变量,%+v可以详细打印,%#v可以更加详细的打印 对于字符串的处理,还可以进行一定的优化 strings.Buider进行字符串处理

  • 使用+拼接性能最差,strings.Buider,bytes.Buffer相近,strings。Buffer更快
  • 字符串在go里面时不可变类型,占用大小固定
  • 使用+每次都会重新分配内存
  • strings.Buider和bytes.Buffer底层都是使用[]byte数组