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

84 阅读3分钟

1. Hello World

//1.定义包名。每个Go程序都是由包构成的。main包表示程序的入口包,程序从main包开始运行。
package main

//2.导包。这里导入的是标准库中的fmt包,主要是用来输入输出和格式化字符串
import(
    "fmt"
)
//3.main函数。里面调用了fmt.Printin输出helloworld
func main(){
    fmt.Println("Hello World")
}

2. 变量

2.1. 变量类型

  • bool 布尔型
  • string 字符串
  • 整型
    • uint8 无符号 8 位整型
    • uint16 无符号 16 位整型
    • uint32 无符号 32 位整型
    • uint64 无符号 64 位整型
    • int8 有符号 8 位整型
    • int16 有符号 16 位整型
    • int32 有符号 32 位整型
    • int64 有符号 64 位整型
  • 浮点型
    • float32 32位浮点型数
    • float64 64位浮点型数
    • complex64 32 位实数和虚数
    • complex128 64 位实数和虚数
  • 其他类型
    • byte类似uint8
    • rune类似 int32
    • uint32或64位
    • int与uint一样大小
    • uintptr无符号整型,用于存放一个指针

2.2. 变量声明和初始化

  • 使用var语句进行变量声明,var name type
  • 变量声明可以包含初始值,每个变量对应一个。如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
  • 短变量声明:在函数中,可以使用:=在类型明确的地方代替var声明(例如k := 1
  • 没有明确初始值的变量声明会被赋予零值(数值型0;布尔型flase;字符串"")

2.3. 常量

  • 常量的声明与变量类似,只不过是使用 const 关键字。常量可以是字符串、布尔值或数值。
  • 常量不能用:=声明。

3. 流程控制语句

3.1. if else

go中的if else写法与C/C++写法类似,不同点在于:if后面的小括号()不需要写;大括号{}必须写,不能省略

3.2. for循环

  • Go 只有一种循环结构:for 循环。
  • 基本的for循环由初始化语句、条件表达式、后置语句三部分组成,用分号隔开,初始化语句和后置语句是可选的。
  • 初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。
  • 同样,小括号()不需要写;大括号{}必须写
  • 死循环:for后面什么都不写
  • 可以用break、continue跳出或继续循环

3.3. switch

  • go语言里面的switch分支结构,和C/C++也比较类似。同样的在switch后面的那个变量名,不需要括号
  • 不同点:在C++里面,switch case如果不显式添加break,会继续往下跑完所有的case,在go语言中是不需要加break的。
  • go语言里面的switch功能更强大,可以使用任意的变量类型,甚至可以用来取代任意的if-else语句。可以在switch后面不加任何的变量,然后在case里面写条件分支。代码逻辑会更为清晰。
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

4. 数组

  • 定义格式:var 变量名 [数组长]类型(例如:var a [10]int,会将变量 a 声明为拥有 10 个整数的数组。)
  • 数组可以很方便地取特定索引的值或者往特定系引取行储值,也能够直接去打印一个数组
  • 数组的长度是其类型的一部分,因此数组不能改变大小。不过,Go 提供了更加便利的方式来使用数组。
package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1]) //Hello World
	fmt.Println(a) //[Hello World]

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes) //[2 3 5 7 11 13]
}

5. 切片

  • 切片可以任意更改长度
  • 切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:a[low:high],它会选择一个半开区间,包括第一个元素,但排除最后一个元素。切片下界的默认值为 0,上界则是该切片的长度
  • 切片可以用函数make创建,make 函数会分配一个元素为零值的数组并返回一个引用了它的切片
  • 为切片追加新的元素则使用append,append的结果是一个包含原切片所有元素加上新添加元素的切片。当底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组,返回的切片会指向这个新分配的数组。
  • len()切片的长度就是它所包含的元素个数。
  • cap()切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
package main

import "fmt"

func main() {
	a := make([]int, 2)
	a[0] = 0
	a[1] = 1
	fmt.Println(len(a)) //2
	
	a = append(a, 2, 3, 4)
	fmt.Println(a)      //[0 1 2 3 4]
	fmt.Println(len(a)) //5
	
	fmt.Println(a[2:4]) //[2 3]
	fmt.Println(a[:4])  //[0 1 2 3]
	fmt.Println(a[2:])  //[2 3 4]
}

6. map(映射)

  • 声明:var mapname map[keytype]valuetype
  • 在映射a中插入或修改元素:m[key] = elem
  • 获取元素:elem = m[key]
  • 删除元素:delete(m, key)
  • 通过双赋值检测某个键是否存在:elem, ok = m[key]
    • 若 key 在 m 中,ok 为 true ;否则,ok 为 false。
    • 若 key 不在映射中,那么 elem 是该映射元素类型的零值。
    • 同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
    • 注 :若 elem 或 ok 还未声明,你可以使用短变量声明:elem, ok := m[key]
package main

import "fmt"

func main() {
	a := make(map[string]int)
	a["one"] = 1
	a["two"] = 2
	fmt.Println(a)           //map[one:1 two:2]
	fmt.Println(a["one"])    //1
	fmt.Println(a["unknow"]) //0

	delete(a, "one")
	fmt.Println(a["one"]) //0

	r, ok := a["two"]
	fmt.Println(r, ok) //2 true
}

7. range

对于一个sice或者一个map,可以用range来快速遍历,这样代码能够更加简洁。range遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。如果不需要索引的话,可以用下划线来忽略

package main

import "fmt"

func main() {
	nums := []int{1, 2, 3, 4, 5}
	for i, num := range nums {
		fmt.Println(i, "+", num)
		//0 + 1
		//1 + 2
		//2 + 3
		//3 + 4
		//4 + 5
	}
	for i, _ := range nums {
		fmt.Print(i) //01234
	}
	for _, value := range nums {
		fmt.Print(value) //12345
	}
}

8. 函数

Go语言函数原生支持返回多个值,在实际业务逻辑代码里面几乎所有函数都是返回两个值,第一个是真正的返回结果,第二个值是错误信息

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

9. 指针

Go 拥有指针,指针保存了值的内存地址。

  • *T:是指向T类型值的指针。
  • & 操作符会生成一个指向其操作数的指针。
  • *操作符表示指针指向的底层值。
package main

import "fmt"

func main() {
	i, j := 42, 2700

	p := &i         // 指向i
	fmt.Println(*p) // 通过指针读取i的值 42
	*p = 21         // 通过指针设置i的值
	fmt.Println(i)  // 查看i的值 21

	p = &j         // 指向j
	*p = *p / 30   // 通过指针对j进行除法运算
	fmt.Println(j) // 查看j的值 90
}

10. 结构体

  • 构造时需要传入每个字段的初始值,也可以使用键值对的方式指定初始值
  • 结构体字段可以通过结构体指针来访问。
  • 如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段X。Go语言也允许使用隐式间接引用,直接写 p.X 就可以。
  • 可以为结构体定义方法,有一点类似其他语言里面的类成员函数。
  • 在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。区别就是如果带指针,那么就可以对这个结构体去做修改。如果不带指针,实际上操作的是一个拷贝,无法对结构体进行修改
package main

import "fmt"

type user struct {
	name string
	num  string
}

// 普通函数
func checkNum(u user, num string) bool {
	return u.num == num
}

// 结构体方法
func (u user) checkNum2(num string) bool {
	return u.num == num
}

func (u *user) changeNum(num string) {
	u.num = num
}

func main() {
	a := user{name: "zhangsan", num: "1"}
	b := user{"lisi", "2"}
	c := user{name: "wangwu"}
	c.num = "3"
	var d user
	d.name = "zhaoliu"
	d.num = "4"

	fmt.Println(a, b, c, d)       //{zhangsan 1} {lisi 2} {wangwu 3} {zhaoliu 4}
	fmt.Println(a.name)           //zhangsan
	fmt.Println(checkNum(a, "2")) //false
	fmt.Println(a.checkNum2("2")) //false
	a.changeNum("5")
	fmt.Println(a.num) //5
}

11. 错误处理

  • Go程序使用 error 值来表示错误状态。
  • 通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。
  • error为nil时表示成功;非nil表示失败。

以两数相除举例:

package main

import "fmt"

// 自定义错误信息结构
type DivideError struct {
	errortype int // 错误类型
	v1        int // 记录下出错时的除数、被除数
	v2        int
}

// 实现接口方法 error.Error()
func (divideError DivideError) Error() string {
	if 0 == divideError.errortype {
		return "除零错误"
	} else {
		return "其他未知错误"
	}
}

// 除法
func div(a int, b int) (int, *DivideError) {
	if b == 0 {
		// 返回错误信息
		return 0, &DivideError{0, a, b}
	} else {
		// 返回正确的商
		return a / b, nil
	}
}
func main() {
	// 正确调用
	v, err := div(100, 2)
	if nil != err {
		fmt.Println("(1)fail:", err)
	} else {
		fmt.Println("(1)succeed:", v)
	}
	// 错误调用
	v, err = div(100, 0)
	if nil != err {
		fmt.Println("(2)fail:", err)
	} else {
		fmt.Println("(2)succeed:", v)
	}
}
//运行结果:
//(1)succeed: 50
//(2)fail: 除零错误

12. 字符串操作

标准库strings包中有很多常用字符串工具函数

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
}

13. 字符串格式化

fmt包中包含多种字符串格式相关的方法

例如printf,写法类似C语言,不过在go中,可以使用%v打印任意类型的变量,也可以用%+v打印详细结果,%#v则更详细

14. JSON处理

对于一个已有的结构体,只要保证每个字段的第一个字母大写,那么结构体就能用JSON.marshal序列化变成一个JSON的字符串。序列化之后的字符串也能够用JSON.unmarshal反序列化到一个空变量里。

15. 时间戳

  • 获取当前时间 time.now()
  • 构造带时区的时间 time.date()
  • 获取某个时间点的年月日小时分钟秒 Year() Month() Day() Hour() Minute() Second()
  • 把时间格式化成字符串 Format()
  • 把日期字符串转化为时间 Parse()
  • 获取时间戳 Unix()

16. 数字解析

  • go语言关于字符串和数字类型的转换在strconv包中
  • ParseInt() 将字符串转换为数字
    • 三个参数:参数1 数字的字符串形式
    • 参数2 数字字符串的进制 比如二进制 八进制 十进制 十六进制
    • 参数3 返回结果的bit大小 也就是int8 int16 int32 int64
  • ParseFloat() 将字符串转换为数字
    • 两个参数:参数1 数字的字符串形式
    • 参数2 返回结果的bit大小
  • Atoi() 字符串转成整型数字
  • Itoa() 整型数字转成字符串
  • 如果输入不合法,这些函数都会返回error

17. 进程信息

  • os.Args 程序执行时指定的命令行
  • os.Getenv 获取环境变量
  • os.Setenv 设置环境变量
  • exec.Command 执行命令行