Go语言基础语法和常用特性解析 | 青训营

87 阅读11分钟

什么是GO语言

Go语言的好处

1、高性能、高并发 2、语法简单、学习曲线平缓 3、丰富的标准库 4、完善的工具链 5、静态编译 6、快速编译 7、跨平台 8、垃圾回收

go基础语法

第一个helloworld程序


package main

import "fmt"

func main() {
	fmt.Println("Hello World") // 使用fmt标准库下的打印函数进行语句的打印行为
}


变量

【go语言的变量类型】

  • int int32 int64 bool string
  • rune bool
  • float32 float64 complex32 complext64

代码示例


func main() {

	// 变量的声明规则 定义规则1 可以不声明变量数据类型的定义 或者是忽略var关键字 使用":="来进行声明 包内部变量不能使用":="
	// 如果变量只定义只声明的话 默认会赋值该类型的初始化 如果是结构体的话,属性成员会赋初始值
	var a = "initial"
	var b string = "hello world"
	c := "hello world"

	var d = true
	var e float64

	f := float32(e) // 进行强制类型砖混

	g := a + "top" // 字符串拼接
	fmt.Printf("%v %f, %s", d, f, g)

	fmt.Printf("%s, %s, %s", a, b, c)

	// 常量的定义 如果定义多个常量的话 那么就可以下述语法进行声明 常量的定义可以在赋值表达式进行简单的运算符运算 只能赋值一次
	const (
		s string = "constant"
		h        = 500000
		l        = 3020 / h
	)

	fmt.Println(s, " ", h, " ", l, " ")
}

if-else条件分支

  • 特点
    • 条件表达式不需要写括号
    • 条件表达式可以先赋值,再进行条件判断;如:if bool = getTrue(); bool {} 这样的语法是允许的
    • if-else-elseif必须带上一个大括号

代码示例 1、if-else不带赋值语句的


func main() {
	a := rand.Intn(1000) + 4
	if a > 500 {
		fmt.Println("a > 500 ")
	} else {
		fmt.Println("a <= 500")
	}
}

2、if-else带赋值语句


if a := rand.Intn(1000) + 4; a > 500 {
		fmt.Println("a > 500")
	}else {
		fmt.Println("a <= 500")
	}

循环

  • 特点

    • go语言只使用for循环 使用for循环做到关于while循环的事情
  • 遍历方式

    • for i := 1;i < 100;i ++ {}
    • for index, value := range array {}
    • for {} (这个是死循环)
    • for 条件表达式 {}

代码示例

死循环


// 死循环
func deadFor() {
	for {
		fmt.Println("dead for")
	}
}

变量循环


// 定义变量循环
func defineVariableFor() {
	start := 0
	for i := 1; i < 100; i++ {
		fmt.Println("start = ", start)
		start++
	}
}


容器遍历循环


// 容器遍历循环
func collectionForeach() {
	a := []int{1, 2, 3, 4, 5, 6}

	for index, value := range a {
		fmt.Println("index = ", index, ";value = ", value)
	}

	// 使用_下划线可以忽略部分值
	for _, value := range a {
		fmt.Println("index = ", "_ ignored;value = ", value)
	}
}

条件表达式遍历循环


// 条件表达式循环
func conditionForeach() {
	arr := []int{1, 2, 3, 4}
	for len(arr) > 0 {
		arr = arr[1:] // 切片处理
	}
}

switch

  • 特点
    • 可以不需要条件表达式,直接在case语句直接声明 (相比于直接使用条件表达式 直接将条件写在case会更加清晰易懂)
    • 不像其他语言;case会自动break;如果需要继续执行的话,那么就需要手动声明fallthrough
    • 如果有多个case条件的结果导向相同;那么就可以以逗号分隔表示不同的case

代码示例 1、带条件表达式的switch


// 带条件表达式的switch语句
func conditionExpressionSwitch() {
	a := 2
	switch a {
	case 1: 
		fmt.Println("a = 1")
	case 2 :
		fmt.Println("a = 2")
	default:
		fmt.Println("a = ", a)
	}
}

2、不带条件表达式的switch


// 不带条件表达式的switch
func nonConditionalExpressSwitch() {
	now := time.Now()

	switch {
	case now.Hour() < 12:
		fmt.Println("now's hour is less than 12")
	default:
		fmt.Println("now's hour is ge 12")
	}
}

数组

  • 特点

    • 数组的长度可以是不定长长度;如 a := [...]int
    • 数组的长度不能使用变量来作为长度 如:a := 2 arr := [a]int 这样是不被允许的
    • 当数组有长度且不被赋值的时候 默认数组的元素值取其元素类型的默认值
    • 添加元素时,建议使用“append()”方法进行添加
    • 数组建议搭配切面一起使用;(可以理解为切面是对数组的一个映射视图)
    • 数组是一个不可变对象 但切片不是 切片是一个可变长的对象
  • 定义方式

    • [length]type
    • [...]type
    • make([]type, len, [cap])
    • []type{}

代码示例 1、带有var关键字的定义方式


// 数组的定义赋值规则
func define1() {
	// 如果一开始初始化的时候不想要向数组添加任何的元素的话 建议使用第一种方式
	var arr [5]int
	fmt.Printf("%X\n", arr)

	// 如果是不确定长度或者是清除得到数组中元素的值 建议使用第二种方式
	var arr1 []int = []int{1, 2, 3, 4}
	fmt.Printf("%X\n", arr1)

	// 不定长的话使用"..."代替长度
	var arr2 = [...]int{1, 2, 3, 4}
	fmt.Printf("%X\n", arr2)
	
	var arr3 = make([]int, 16)
	fmt.Printf("%X\n", arr3)
}

2、不带有var关键字的定义方式


func define2() {
	arr := []int{1, 2, 3, 4, 5}
	arr1 := [...]int{1, 2, 3, 4, 5}
	arr2 := make([]int, 16) // 相当于是 var arr [16]int

	fmt.Println(arr, " ", arr1, " ", arr2)
}


切片

【 概述 】:切片是对数组的一个视图对象;可以通过切片来改变数组的值

  • 特点
    • 可以通过切片对数组的值进行修改
    • 对一个切片reslice的时候,如果此时cap != len的话,那么reslice的切片可能会出现前一个切片隐藏的值
    • 切片的start、end的区间规则是[start, end)
    • 注意,如果此时slice的最后一个元素不是在最后的话,那么切片可以在适当的位置下标越界
    • 切片的元素通过“append()”方法来实现;由于不清楚append()后的数组对象是否需要进行扩容 所以使用这种格式:arr = append(arr, element)
    • 如果需要数据的拷贝,可以调用"copy(dest, src)"来拷贝
    • 如果slice := arr[:]的话 表示的就是映射整一个数组; 如果slice := arr[0:],同样也是

代码示例

切片数据一致性


// 切片数据的一致性
func sliceDataConsistent() {
	// 数组为[1, 2, 3, 4, 5]
	arr := []int{1, 2, 3, 4, 5}
	fmt.Println("arr = ", arr) // arr = [1 2 3 4 5]

	// 切片取数组的第二个元素到第4个元素
	slice := arr[1:4]
	fmt.Println("slice = ", slice) // slice = [2 3 4]

	// 对数组元素进行修改 首先修改视图显示元素
	slice[0] = 3
	fmt.Println("now: arr = ", arr, "; slice = ", slice) // now: arr =  [1 3 3 4 6] ; slice =  [3 3 4]

	// 对隐藏元素进行修改 不能通过下述这种方式进行隐藏元素的修改 而是应该这样 通过append()进行修改
	// slice[4] = 6
	// fmt.Println("now: arr = ", arr, "; slice = ", slice) 注意 如果切片最后一个元素不是数组的最后一个元素,那么就是修改数值
	slice = append(slice, 6)
	fmt.Println("now: arr = ", arr, "; slice = ", slice) // now: arr =  [1 3 3 4 6] ; slice =  [3 3 4 6]

	// 可以观察到此时slice的值变了 但是原来映射的数组的值没有改变 原因在于go底层拷贝了原来的数组 之后数组扩容后添加了7 此时slice映射的不是原来数组 而是系统底层的数组
	slice = append(slice, 7)
	fmt.Println("now : arr = ", arr, "; slice = ", slice) // now: arr = [1 3 3 4 6] ; slice = [3 3 4 6 7]
}

reslice


// reslice 对切片进行二次切片 切片并不是重新拷贝一个数组进行截取 而是将不需要的元素值隐藏起来 由len、start、cap进行记录共同协作
// 如果对一个切片进行截取 发现end已经大于了start+len的话 那么就会判断len跟cap的关系 之后重新切片的slice可能会带有原来切片没有的数据
func reSlice() {
	arr := []int{1, 2, 3, 4, 5}
	slice1 := arr[1:4]
	slice2 := slice1[2:4]

	fmt.Println("slice1 = ", slice1, ";slice2 = ", slice2) // slice1 = [2 3 4] slice2 = [4 5]
}

wildcard(:)


func wildword() {
	arr := []int{1, 2, 3, 4, 5}
	slice := arr[0:]
	slice2 := arr[:]

	fmt.Println("slice = ", slice, "; slice2 = ", slice2) // slice =  [1 2 3 4 5] ; slice2 =  [1 2 3 4 5]

}

map

  • 定义方式:var a map[type]type
  • 特点
    • 可以调用delete()方法执行key、value的删除行为
    • 在执行map[key]会返回两个返回值,一个是获取到的value, 一个是map中是否包含这个key

代码示例

包括语法特点:定义严格类型的map集合、定义并进行map的赋值、在定义时将map注入键值对、宽松定义,类型为interface{}


func defineMap() {
	// 只定义不赋值的话 是一个nil map 在go语言中 nil是可以调用函数对象的
	var map1 = make(map[string]int)
	map1["zhangsan"] = 1
	map1["lisi"] = 2
	fmt.Println("map1 = ", map1) // map1 =  map[lisi:2 zhangsan:1]

	// 执行删除行为
	delete(map1, "lisi")

	// 再次获取lisi 看是否有值
	if value, ok := map1["lisi"]; ok {
		fmt.Println("value exists : value = ", value)
	} else {
		fmt.Println("value no exists, key = ", "lisi") // value no exists, key =  lisi
	}

	// 定义规则2
	map2 := map[string]int{
		"lisi":     1,
		"zhangsan": 2,
	}

	fmt.Println("map2 = ", map2) // anyMap =  map[lisi:1 zhangsan:wangwu zhaoliu:true]

	// map允许存储不同类型的键值对
	anyMap := map[string]interface{}{
		"lisi":     1,
		"zhangsan": "wangwu",
		"zhaoliu":  true,
	}

	fmt.Println("anyMap = ", anyMap) // anyMap =  map[lisi:1 zhangsan:wangwu zhaoliu:true]

}

range

概述:可以使用range快速遍历 操作对象:集合、数组、channel等

  • 注意
    • 1、遍历数组 返回的是index, value
    • 2、遍历Map 返回的是key, value
    • 3、遍历channel 返回的是value
    • 4、如果不想要接受某一个值的话 使用_替换值变量名

语法定义规则: for index, value := range [collection | array | channel]...

代码示例

rangeArray


func rangeArray() {
	arr := []int{1, 2, 3, 4, 5}
	/*
		index =  0 ; value =  1
		index =  1 ; value =  2
		index =  2 ; value =  3
		index =  3 ; value =  4
		index =  4 ; value =  5
	*/
	for index, value := range arr {
		fmt.Println("index = ", index, "; value = ", value)
	}
}

rangeMap


func rangeMap() {
	tempMap := map[string]int{
		"zhang": 1,
		"lisi":  2,
	}

	/*
		key =  zhang ; value =  1
		key =  lisi ; value =  2
	*/
	for key, value := range tempMap {
		fmt.Println("key = ", key, "; value = ", value)
	}
}

rangeChannel


func rangeChannel() {
	temp := make(chan int)

	go func() {
		i := 0
		for {
			temp <- i
			i++
		}
	}()

	<-time.After(1 * time.Second)

	for value := range temp {
		fmt.Println("value = ", value)
	}
}

函数

  • 特点
    • 函数是一级对象 可以作为参数、返回值、数据类型
    • go语言推荐使用函数式编程 即返回值或数据类型定义为函数对象
    • 定义成员函数的时候,分为值接收者或者是指针接收者;
      • 如果是值接收者的话,那么在方法调用时会对接收者进行复制
      • 如果是指针接收者的话,那么在方法调用时会引用原始接收者
    • go语言存在指针对象,函数的参数类型可以是指针
      • 如果参数是指针类型的话,那么会对参数状态进行修改
      • 如果参数是值的话,即进行拷贝后传入

代码示例

定义匿名内部类

匿名内部函数包括:”匿名内部类、最终执行函数“

语法格式:a := func(a, b int) {} 、 defer func(){} ()


func defineNestedFunction() {
	// 定义匿名函数
	a := func(a, b int) int {
		return a + b
	}
	result := a(1, 2)

	fmt.Println("result = ", result)

	// 定义最终处理函数
	defer func() {
		fmt.Println("默认是main协程执行最后会执行 通常处理的是函数执行完毕的资源释放行为")
	}()
}

定义函数


// 定义函数
func add(a, b int) int {
	return a + b
}

函数式编程


// 定义累加器 传回的类型是函数值
func adder(a, b int) func(a, b int) int {
	return func(a, b int) int {
		return a + b
	}
}

参数类型是指针

特点:如果执行的是修改逻辑的话 那么外部传入参数的状态会被修改


// 函数参数类型是指针 如果是指针的话 那么就会修改到外部数据
func updateArray(arr *[]int) {
	*arr = append(*arr, 2)
}

参数类型是值

特点:如果执行的是修改逻辑的话 那么外部传入参数的状态不会被修改 其会被拷贝一份


// 函数参数类型是值类型
func updateArray1(arr []int) []int {
	return append(arr, 2)
}

返回值类型是指针

特点:go语言允许将一个函数的局部变量的作用域升级 可以被外部引用所引用


// 函数返回值类型是指针类型 如果返回值类型是指针的话 返回的是地址
func updateArray3(arr []int) *[]int {
	arr = append(arr, 3)
	return &arr
}

返回值类型是值

特点:返回的是方法执行逻辑最后结果的一个拷贝副本


// 函数返回值类型是值类型 如果返回值类型是值类型的话 返回的是拷贝返回值对象
func updateArray4(arr []int) []int {
	return append(arr, 3)
}

成员方法

值接收者

特点:如果是值接收者的话 方法内部执行关于结构体对象状态的修改 调用者的状态不会被修改到


// 值接收者 通常情况下 如果一个结构体当中的成员方法有一个是指针接收者 建议其他也统一为指针接收者
func (a Adder) setB(value int) {
	a.b = value
}

指针接收者

特点:如果是指针接收者的话 方法内部执行关于结构体对象状态的修改 调用者的状态会被修改到


type Adder struct {
	a, b int
}

// 指针接收者 调用者就是原始类型
func (a *Adder) addAndGet() int {
	return a.a + a.b
}

func (a *Adder) setA(value int) {
	a.a = value
}

指针

  • 特点
    • go语言的指针对象其作用不像C++、C那么明显
    • 如果使用的是指针 引用的是原始地址 如果使用的值 引用的是拷贝副本的地址

代码示例


func main() {
	a := 2
	addN(a)
	fmt.Println("a = ", a) // a = 2
	addN1(&a)
	fmt.Println("a = ", a) // a = 4
}

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

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


结构体

  • 特点
    • 如果执行的是a := struct{}的话;那么属性未进行赋值的话 那么赋值的就是成员本身类型的默认值
    • 结构体的成员属性类型可以是struct、func等对象
    • 结构体的命名方式:结构体的名字不应该出现包名 因为在使用的时候是调用包名.结构体名
    • go语言的访问权限控制:使用的是camel的方式 首字母大写为开放 全小写为包私有 结构体属性、函数、结构体都是这样的命名规则

代码示例:定义一个struct{name, password string};执行一系列方法


func main() {
	a := User{"zhangsan", "123456"}  // 全部赋值
	a1 := User{username: "zhangsan"} // 部分赋值
	a2 := User{}                     // 只定义 不赋值
	fmt.Println("a = ", a, "; a1 = ", a1, "; a2 = ", a2) // a =  {zhangsan 123456} ; a1 =  {zhangsan } ; a2 =  { }

	a2.username = "zhangsan"
	fmt.Println("isValidate : password =>", validatePassword(a, "123456"))   // isValidate : password => true
	fmt.Println("isValidate : username =>", validateUsername(a, "zhangsan")) // isValidate : username => true

}

type User struct {
	username, password string
}

func validatePassword(u User, password string) bool {
	return u.password == password
}

func validateUsername(u User, username string) bool {
	return u.username == username
}


结构体方法

分为

值接收者 指针接收者

推荐:> 结构体的成员方法如果至少有一个是指针接收者或值接收者的话 那么建议结构体的所有成员方法都是指针接收者或者是值接收者

  • 特点
    • 值接收者 方法执行的调用者为拷贝副本
    • 指针接收者 方法执行的调用者为原始地址

代码示例

// 其内部方法逻辑修改 不会传播到外部调用者
func (u *User) setUsername(username string) {
	u.username = username
}

// 其内部方法逻辑修改 会传播到外部调用者
func (u User) setPassword(password string) {
	u.password = password
}


错误处理

  • 特点
    • 介于go语言函数的特殊性(可以传递两个参数);通常情况下 第一个返回的是方法正确执行结果 第二个返回的是错误类型或者是一些bool标识
    • 可以使用简单的if-else就可以进行错误处理
    • 可以使用”panic()“进行错误信息的报告,也可以使用函数式编程将错误处理进行包装,统一处理某一个底层业务方法的错误处理
    • 注意:在服务器应用程序下,panic()不会终止程序,当执行panic()的时候,程序会被保护 建议在自己的错误代码下定制化保护机制 defer func() { err := recover() }

代码示例

使用if-else错误处理


func readFile(filename string) {
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	} else {
		fmt.Printf("%s\n", bytes)
	}
}

使用if-else赋值错误处理


func readFile1(filename string) {
	if bytes, err := ioutil.ReadFile(filename); err != nil {
		panic(err)
	} else {
		fmt.Printf("%s\n", bytes)
	}
}

定制保护机制


func customizeRecover(filename string) {

	defer func() {
		r := recover()
		if r != nil {
			fmt.Printf("readFile方法出现了错误 触发了保护机制")
		}
	}()

	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	} else {
		fmt.Printf("%s\n", bytes)
	}
}

字符串操作

代码示例:展示了一些常用的字符串操作


func main() {

	s := "hello"
	fmt.Println("is Contains => ", strings.Contains(s, "ll")) // true
	fmt.Println("ll's count => ", strings.Count(s, "l")) // 2
	fmt.Println("s is start with 'll' => ", strings.HasPrefix(s, "ll")) // false
	fmt.Println("s is end with 'll' => ", strings.HasSuffix(s, "ll")) // false
	fmt.Println("s search with 'l' index => ", strings.Index(s, "l")) // 2
	fmt.Println("join result =>", strings.Join([]string{"he", "llo"}, "-")) // 拼接的时候顺便加上分隔符 he-llo
	fmt.Println("s repeat two => ", strings.Repeat(s, 2))                   // 进行拼接 重复拼接两次 hellohello
	fmt.Println(strings.Replace(s, "e", "E", -1))                           // -1 表示在s这个字符串找到第一个 进行替换 hEllo
	fmt.Println(strings.ToLower(s))                                         // 小写 hello
	fmt.Println(strings.ToUpper(s))                                         // 大写 HELLO
}

字符串格式化

语法规则 1、%v:打印值的数据类型相关的值 如果是int的话;打印的就是%d;如果是string的话;打印的就是%s 2、%d:打印数值 3、%s:打印字符串 或 将字节数组转换为字符串输出 4、%c:打印为rune 5、%+v:进一步打印 适用于struct 打印属性成员的值 6、%#v: 5后更进一步打印 适用于struct有属性成员仍然是struct 打印结构体所在的全路径类名{属性成员的值} 7、%.2f:取两位浮点数

代码示例


func main() {
	a := "hello world"
	fmt.Printf("%s", a) // hello world
	fmt.Printf("%v", a) // hello world
	fmt.Printf("%+v", struct {
		a, b int
	}{1, 2}) // {a : 1 b : 2}
	fmt.Printf("%#v", struct {
		a, b int
	}{1, 2}) // struct{ a int; b int}{a : 1, b : 2}
	fmt.Printf("%.2f", 5.0)  // 5.00
}

JSON处理

方法

  • Marshal:将结构体对象值转换为json数据

  • Unmarshal:将json数据转换为结构体对象

  • 特点

    • 介于json在网络传输的通用性和go语言的属性名的命名规则,建议在每一个属性名后面打上tag;如:“name string json:"name"” 输出的json为{"name": ""}
    • 执行Unmarshal的时候 需要先声明一个结构体对象 再传入进去 如:json.Unmarshal(content, &structObj)

代码示例

不打上tag


type UserInfo struct {
	Name  string   
	Age   int      
	Hobby []string 
}

func jsonMarshalNotTag() {
	a := UserInfo{
		Name:  "zhangsan",
		Age:   22,
		Hobby: []string{"basketball", "football"},
	}

	result, _ := json.MarshalIndent(a, "", "\t")

	/*
		Result = {
		        "Name": "zhangsan",
		        "Age": 22,
		        "Hobby": [
		                "basketball",
		                "football"
		        ]
		}
	*/
	fmt.Printf("Result = %s", result)
}


打上tag


func jsonMarshalTag() {
	a := UserInfo{
		Name:  "zhangsan",
		Age:   22,
		Hobby: []string{"basketball", "football"},
	}

	result, _ := json.MarshalIndent(a, "", "\t")

	/*
		Result = {
		        "name": "zhangsan",
		        "age": 22,
		        "hobbies": [
		                "basketball",
		                "football"
		        ]
		}
	*/
	fmt.Printf("Result = %s", result)
}

将json转化为对象


func jsonUnmarshal() {
	a := ` {
		        "name": "zhangsan",
		        "age": 22,
		        "hobbies": [
		                "basketball",
		                "football"
		        ]
		}`

	var result UserInfo // 需要自己创建一个对象后 再将其传入到Unmarshal()方法下
	err := json.Unmarshal([]byte(a), &result)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v", result) // {Name:zhangsan Age:22 Hobby:[basketball football]}

}

时间处理

常用的就是:time.Now()

代码示例


func main() {
	a := time.Now()
	fmt.Println(a)

	date := time.Date(2022, 3, 27, 25, 36, 0, 0, time.UTC) // 构建date对象
	date1 := time.Date(2022, 3, 27, 25, 36, 0, 0, time.UTC)

	fmt.Println("date = ", date, "\n date1 = ", date1)
	fmt.Println("date.year = ", date.Year(), "; date.month = ", date.Month(), "; date.day = ", date.Day(), "; date.hour = ", date.Hour(), "date.minute = ", date.Minute()) // 调用getter()方法 进行属性成员的获取行为
	fmt.Println(date.Format("2002-01-02 15:04:05")) // 判断是否匹配给定值

	diff := date1.Sub(date) // 计算时间差距
	fmt.Println(diff) 

	fmt.Println("diff.minutes = ", diff.Minutes(), "; diff.seconds = ", diff.Seconds())

	date3, err := time.Parse("2006-04-02 15:04:05", "2005-01-02 14:04:54") // 前一个为固定的解析格式 后一个为目标值
	if err != nil {
		panic(err)
	}
	fmt.Println(date3 == date)
	fmt.Println(a.Unix()) // 十六进制
}

数字解析

跟数字解析相关的包都在strconv包下

1、ParseFloat(string, type):将字符串转化为float32 | float64的浮点数类型 2、ParseInt(string, base, type):将字符串转化为什么进制表示的 int | int8 | int16 | int32 | int64的类型(字符串内容可以是不同进制的) 3、Atoi(string):将字符串转化为十进制数;返回两个参数(value, err)

代码示例


func main() {
	f, _ := strconv.ParseFloat("1234", 32) // 第二个参数传0的话 表示自动推测
	fmt.Println(f) // 1234

	number, _ := strconv.ParseInt("123", 10, 32)
	fmt.Println(number) // 123

	number1, _ := strconv.ParseInt("0x123", 10, 32)
	fmt.Println(number1) // 0

	number2, _ := strconv.Atoi("123")
	fmt.Println(number2) // 123

	number3, err := strconv.Atoi("aaa") // 字符出内容不是数值型 无法转化 返回错误
	if err != nil {
		panic(err)
	} else {
		fmt.Println(number3)
	}
}

获取进程信息


func main() {
	fmt.Println(os.Args) // 获取程序启动的一些启动参数

	fmt.Println(os.Getenv("PATH")) // 获取环境变量

	fmt.Println(os.Setenv("s1", "v1")) // 设置环境变量

	output, err := exec.Command("ping").CombinedOutput() // 执行命令并获得控制台输出信息
	if err != nil {
		panic(err)
	} else {
		fmt.Printf("%s", output)
	}
}