Go语法快速预览

Thursday, September 5, 2024

快速的对各种结构做个总结~

循环

for 循环至少需要条件表达式,其余是可选的。

if初始化语句

switch

当需要根据表达式的值执行几个操作时,可能会导致if语句和else子句的混乱,switch语句是更有效的方式:写下 switch 关键字,然后是条件表达式,再添加几个case表达式,每个case表达式都有一个条件表达式可能有的值,选择其值与条件表达式匹配的第一个case,并运行其代码,其他case表达式则被忽略。还可以提供一个default语句,如果没有匹配的case,那么运行该语句。

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func awardPrize() {
	switch rand.Intn(3) + 1 { //条件表达式
	case 1: //如果结果是1
		fmt.Println("you win a cruise!") //打印这条消息
	case 2:
		fmt.Println("you win a car!")
	case 3:
		fmt.Println("you win a goat!")
	default: //如果结果不是以上任何一个
		panic("invalid door number") //那么产生panic
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	awardPrize()
}

声明函数参数

函数返回值

函数声明多个返回值

package main

import "fmt"

func manyReturns() (int, bool, string) {   //返回一个整数,一个布尔值,一个字符串
	return 1, true, "hello"
}
func main() {
	myInt, myBool, myString := manyReturns()  //将每个返回值存储在一个变量中
	fmt.Println(myInt, myBool, myString)
}

若要使返回值目的更清楚,可以为每个返回值提供名称,类似于参数名称。

package main

import (
	"fmt"
	"math"
)

func floatParts(number float64) (integerPart int, fractionalPart float64) {
	wholeNumber := math.Floor(number)
	return int(wholeNumber), number - wholeNumber
}
func main() {
	cans, remainder := floatParts(1.26)
	fmt.Println(cans, remainder)
}

数组

数组中包含的值称为它的元素

数组保存特定数量的元素,不能增长或收缩,要声明保存数组的变量,需要在方括号[]中指定它所保存的元素数量,后跟数组所保存的元素类型。

要设置数组元素的值或稍后检索值,需要一种方法来指定哪个元素,数组中的元素从0开始编号,一个元素的编号称为其索引

例如,创建一个由7个字符串组成的数组:

var notes [7]string
notes[0] = "do"    //给第一个元素赋值
notes[1] = "re"
notes[2] = "mi"
fmt.Println(notes[0])   //打印第一个元素
fmt.Println(notes[1])   //打印第二个元素

与变量一样,当创建一个数组时,其所包含的所有值都被初始化为该数组所保存类型的零值。默认情况下,一个 int 值数组用0填充。

var primes [5]int
primes[0] = 2
fmt.Println(primes[0])   //打印显示赋值的元素
fmt.Println(primes[2])   //打印未显式赋值的元素
fmt.Println(primes[4])   //打印未显式赋值的元素

输出:

2
0
0

若事先知道数组应该保存哪些值,可以使用数组字面量来初始化数组。数组字面量的开头与数组类型类似,其元素的数量将放在方括号中,后跟元素的类型,再后面跟大括号,里面是每个元素应该具有的初始值列表,元素值用逗号分隔。

var notes [3]string = [3]string{"do", "re", "mi"}

数组字面量允许使用短变量声明:

notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"}
primes := [5]int{2, 3, 5, 7, 11}

写成多行:

text := [3]string{
       "this is a series of long strings",
       "which would be awkward to place",
       "together on a single line",    //末尾的逗号是必须的。
}

循环访问数组

notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"}
for i := 0; i < len(notes); i++ {
       fmt.Println(i, notes[i])
}

for..range 遍历数组

notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"}
for index, note := range notes {
       fmt.Println(index, note)
}

切片

声明一个保存切片的变量,使用一对空方括号,后面跟着这个切片所保存的元素类型 :

var mySlice []string

除了不指定大小,与声明一个数组变量的语法完全相同。

声明切片调用内建的make函数:

var notes []string   //声明一个切片变量
notes = make([]string, 7)  //创建7个字符串的切片
primes := make([]int, 5)
primes[0] = 2
primes[1] = 3
fmt.Println(primes[0])

	notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"}
	fmt.Println(notes[3], notes[6], notes[0])
	primes := []int{
		2,
		3,
		5,
	}
	fmt.Println(primes[0], primes[1], primes[2])

每个切片都构建在一个底层的数组之上,实际上是底层的数组存储了切片的数据;切片仅仅是数组中的一部分(或者所有)元素的视图。

当使用 make 函数或者切片字面量创建一个切片的时候,底层的数组会自动创建出来(只有通过切片,才能访问它),但也可以创建一个数组,然后再基于数组通过切片运算符创建一个切片。

mySlice := myArray[1:3]

切片运算符有两个索引,其中一个标识切片开始的位置,另一个标识切片在此位置之前结束。

underlyingArray := [5]string{"a", "b", "c", "d", "e"}
slice4 := underlyingArray[:3]
fmt.Println(slice4)
underlyingArray := [5]string{"a", "b", "c", "d", "e"}
slice5 := underlyingArray[1:]
fmt.Println(slice5)

注意:通常情况下使用make切片字面量来创建切片,而不是创建一个数组,这样就不用关心底层数组的问题。

调用append函数,惯例是将函数的返回值赋给传入的那个切片变量,若只保存一个切片,就无需考虑多个切片是否共享同一个底层数组的问题。

通常并不需要担心切片是 nil 还是空的,可以同样对待它们。

可变长参数函数

可变长参数函数的最后一个参数接收一个切片类型的变长参数,这个切片可以被函数当作普通切片来处理。

func myFunc(param1 int, param2 ...string) {
       //function code here
}

函数定义中最后一个参数是可变长参数,不能放到其他必须参数前。

调用可变长参数函数,在传入的切片变量后面加省略号...:

func severalInts(numbers ...int) {
       fmt. Println(numbers)
}
func mix(num int, flag bool, strings ...string) {
       fmt.Println(num, flag, strings)
}
func main() {
       intSlice := []int{1, 2, 3}
       severalInts(intSlice...)   //使用int切片代替可变参数
       stringsSlice := []string{"a", "b", "c", "d"}
       mix(1, true, stringsSlice...)   //使用string切片代替可变参数
}

映射

var ranks map[string]int    //声明一个映射变量
ranks = make(map[string]int) //真正创建一个映射
ranks := make(map[string]int)   //创建一个映射并声明一个用于保存它的变量

多行映射字面量:

elements := map[string]string{
        "H": "Hydrogen",
        "Li": "Lithium",
}   //多行映射字面量

声明映射变量不是创建映射,需要 make 或使用映射字面量创建映射后才能赋值,当然未赋值的映射变量零值是 nil,意味着没有映射存在来增加键或者值。

golang 中,map 的设计:访问键,会返回两个值,第二值为 bool 类型;第二个 bool 值是可选的,如果要忽略,可以不用显示_

package main

import "fmt"

func main() {
	counters := map[string]int{"a": 3, "b": 0}
	value, ok := counters["a"]
	fmt.Println(value, ok)
	value, _ = counters["b"]
	fmt.Println(value)
	value = counters["b"]
	fmt.Println(value)
	value, ok = counters["c"]
	fmt.Println(value, ok)
	_, ok = counters["d"]
	fmt.Println(ok)
}

输出:

3 true
0
0
0 false   //没有“c”这个键,为其值(这里map 的值为 int 类型)的零值
false

if 语句后的条件必须为 true,因为 if 是用来判断某个条件是否成立的。条件为 true 时,执行相应代码;为 false 时,不执行代码。

对映射进行循环

其中一个变量保存键,另一个变量保存值,自动遍历映射中每一个条目。

for…range 循环是一个非有序的 键/值 对集合,若需要按照特定顺序,需要自己写代码实现,比如:

package main

import (
	"fmt"
	"sort"
)

func main() {
	grades := map[string]float64{"Alma": 74.2, "Rohit": 86.5, "Carl": 59.7}
	var names []string
	for name := range grades {
		names = append(names, name)
	}
	sort.Strings(names)
	fmt.Println(names)
	for _, name := range names {
		fmt.Printf("%s has a grade of %0.1f%%\n", name, grades[name])
	}
}

输出:

[Alma Carl Rohit]
Alma has a grade of 74.2%
Carl has a grade of 59.7%
Rohit has a grade of 86.5%

结构体

package main

import "fmt"

type car struct { //定义一个名为car的类型,car的基础类型是有下面这些字段的struct
	name     string
	topSpeed float64
}

func main() {
	var porsche car                //定义一个car类型的变量
	porsche.name = "Porsche 911 R" //访问struct的字段
	porsche.topSpeed = 323
	fmt.Println("Name:", porsche.name)
	fmt.Println("Top speed:", porsche.topSpeed)
}

不要使用一个已经存在的类型名称作为变量名!

struct 字面量

代码定义一个 struct 且挨个给其赋值很讨厌,像切片与映射一样,Go 提供了 struct 字面量来让你创建一个 struct 并同时给其字段赋值。

方法

只能获取保存在变量中的指针!!

接口

一个接口是特定值预期具有的一组方法。

任何拥有接口定义的所有方法的类型被称作'满足那个接口'。

一个满足接口的类型可以用在任何需要接口的地方。

一个类型可以满足多个接口,一个接口可以有多个类型满足它。

接口类型的变量可以保存任何实现了该接口的具体类型的值,接口在 Go 中被设计为一组方法的集合,任何实现了这些方法的类型都被视为实现了该接口。

类型断言

当将一个具体类型的值赋给一个接口类型的变量时,类型断言能够取回具体类型的值。

var robot Robot = noiseMaker.(Robot) : 从接口变量 noiseMaker 中提取其持有的具体类型为 Robot 的值。

当类型断言失败时避免程序异常

package main

import (
    "fmt"
    "reflect"
)

type Robot string

func (r Robot) MakeSound() {
    fmt.Println("Beep Boop")
}

func (r Robot) Walk() {
    fmt.Println("Powering legs")
}

type NoiseMaker interface {
    MakeSound()
}

func main() {
    var noiseMaker NoiseMaker = Robot("Botco Ambler")
    noiseMaker.MakeSound()

    // 获取具体类型信息
    fmt.Println("Type of noiseMaker:", reflect.TypeOf(noiseMaker))

    // 类型断言,提取具体类型的值
    robot, ok := noiseMaker.(Robot)
    if ok {
        robot.Walk()
    } else {
        fmt.Println("noiseMaker 并非 Robot 类型")
    }
}

goroutine

channel

实际创建channel需要调用内置的make函数:

var myChannel chan float64
myChannel = make(chan float64)

短变量声明:

myChannel := make(chan float64)  // 创建一个channel并立即声明一个变量

使用channel发送和接收值

发送值:myChannel <- 3.14
接收值: <-myChannel

有缓冲的channel

一级函数

package main

import (
	"fmt"
)

func sayHi() {     // 正常声明一个函数
	fmt.Println("Hi")
}

func main() {
	var myFunction func()   //声明一个类型为"func()" 的变量,这个变量可以保存一个函数
	myFunction = sayHi     // 将sayHi函数赋值给变量
	//myFunction := sayHi
	myFunction()   //调用存储在变量中的函数
}

将 sayHi 函数本身赋值给 myFunction ,这里没写成 sayHi() ,因为这样会调用sayHi函数,只输入函数名,不加括号。
在下一行中,在myFunction变量名后面包含了括号,这将调用存储在myFunction变量中的函数。

将函数传递给其他函数

将函数作为参数传递给其他函数。

package main

import (
	"fmt"
)

func sayHi() {
	fmt.Println("Hi")
}

func sayBye() {
	fmt.Println("Bye")
}

func twice(theFunction func()) {  //"twice"函数接收另一个函数作为参数
	theFunction()
	theFunction()
}

func main() {
	twice(sayHi)
	twice(sayBye)
}

函数作为类型

函数的参数和返回值是其类型的一部分,保存函数的变量需要指定函数应该具有哪些参数和返回值。
该变量只能保存参数的数量和类型以及返回值与指定类型匹配的函数。

Golang打怪升级

二分查找

Go匿名结构体提高搬砖效率

comments powered by Disqus