go学习笔记

1. 变量

声明单个变量

func main()  {
   
   
    // 1. 指定变量类型,若不赋值,则默认值。(int => 0)
    var a int
    a = 10
    fmt.Println("a =",a)
	// var a int = 10   声明变量并初始化
    
    // 2. 类型推导,系统自动推导变量类型
    var b = 10
    fmt.Println("b =",b)

    // 3. 省略var, 用 : 替代
    c := 10
    fmt.Println("c =",c)

    var n1 float32 = -123.0000901
    var n2 float64 = -123.0000901
    fmt.Println("n1=",n1,"n2=",n2)
}

声明多个变量

func main()  {
   
   
    var width, height int = 100, 50
    fmt.Println("width = ", width, "height = ", height)

    // 在一个语句中声明不同类型的变量
    var (
        name = "golang"
        age = 10
        sex = "man"
    )
    fmt.Println(name, age, sex)

    // 简短声明
    width, h := 500, 100   //错误,要求 := 操作符的左边至少有一个变量是尚未声明的
    fmt.Println(width, h)

    // 变量也可以在运行时进行赋值
    a, b := 145.8, 543.8
    c := math.Min(a, b)
    fmt.Println("minimum value is ", c)
}

2. 类型

Go支持的基本类型:

  • bool
  • 数字类型
    • 有符号整型 : int8, int16, int32, int64, int
    • 无符号整型 : uint8, uint16, unint32, unint64, uint
    • 浮点型 : float32, float64
    • 复数 : complex64, complex128
    • int8的别名 : byte
    • int32的别名 : rune
  • string

类型转换

把 v 转换为 T 类型的语法是 T(v)

func main()  {
   
   
    i := 55
    j := 67.8

    //sum = i + j 错误
    sum := i + int(j)

    fmt.Println(sum)  // 122 
}

3. 常量

定义

var a int = 50  
var b string = "I love Go"

在上面的代码中,变量 ab 分别被赋值为常量 50I love GO

关键字 const 被用于表示常量,比如 50I love Go

即使在上面的代码中我们没有明确的使用关键字 const,但是在 Go 的内部,它们是常量。

package main

func main() {
   
     
    const a = 55 // 允许
    a = 89       // 不允许重新赋值
}

常量的值会在编译的时候确定。

因为函数调用发生在运行时,所以不能将函数的返回值赋值给常量

package main

import (  
    "fmt"
    "math"
)

func main() {
   
     
    fmt.Println("Hello, playground")
    var a = math.Sqrt(4)   // 允许
    const b = math.Sqrt(4) // 不允许
}

查看常量类型

func main() {
   
   
    var name = "Sam"
    fmt.Printf("type %T value %v", name, name)
    // type string value Sam
}

4. 函数

函数声明

func functionname(parameter1 type, parameter2 type) returntype {
   
   
    // 函数体
}

[可选] 参数,返回值

例如:计算 商品总价 = 商品单价 * 商品数量

func totalPrice(price int, number int) int {
   
   
    var res = price * number
    return res
}

func main()  {
   
   
    price, number := 2, 3
    ans := totalprice(price, number)
    fmt.Println(ans)
}

如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。 例如,price int, no int 可以简写为 price, no int

多个返回值

Go 语言支持一个函数可以有多个返回值。

func rectProps(length, width float64) (float64, float64) {
   
   
    var area = length * width
    var perimeter = (length + width) * 2

    return area, perimeter
}

func main()  {
   
   
    area, perimeter := rectProps(10.8, 5.6)
    fmt.Printf("Area %f Perimeter %f", area, perimeter)
}

命名返回值

从函数中可以返回一个命名值。

一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。

上面的 rectProps 函数也可用这个方式写成:

func rectProps(length, width float64)(area, perimeter float64) {
   
     
    area = length * width
    perimeter = (length + width) * 2
    return // 不需要明确指定返回值,默认返回 area, perimeter 的值
}

请注意, 函数中的 return 语句没有显式返回任何值。由于 areaperimeter 在函数声明中指定为返回值, 因此当遇到 return 语句时, 它们将自动从函数返回。

空白符

_ 在 Go 中被用作空白符,可以用作表示任何类型的任何值。

我们继续以 rectProps 函数为例,该函数计算的是面积和周长。假使我们只需要计算面积,而并不关心周长的计算结果,该怎么调用这个函数呢?这时,空白符 _ 就上场了。

下面的程序我们只用到了函数 rectProps 的一个返回值 area

func rectProps(length, width float64) (float64, float64) {
   
   
    var area = length * width
    var perimeter = (length + width) * 2

    return area, perimeter
}

func main()  {
   
   
    area, _ := rectProps(10.8, 5.6)
    fmt.Printf("Area %f", area)
}

5. 包

所有可执行的 Go 程序都必须包含一个 main 函数。

这个函数是程序运行的入口。

main 函数应该放置于 main 包中。

创建自定义的包

package rectangle

import "math"

// 计算矩形面积
func Area(len, wid float64) float64 {
   
   
    area := len * wid
    return area
}

// 计算矩形对角线的长度
func Diagonal(len, wid float64) float64 {
   
   
    diagonal := math.Sqrt((len * len) + (wid * wid))  // 平方根
    return diagonal
}

在 Go 中,任何以大写字母开头的变量或者函数都是被导出的名字。

其它包只能访问被导出的函数和变量。

因此,Area 和 Diagonal 函数的首字母大写。

导入自定义包

package main  // 指定该文件属于main包

import "fmt"  // 导入其他包
import "5包/geometry/rectangle"

func main()  {
   
   
    var rectLen, rectWidth float64 = 6, 7
    fmt.Println("几何参数")
    fmt.Printf("area %.2f \n", rectangle.Area(rectLen, rectWidth))
    fmt.Printf("rectangle %.2f", rectangle.Diagonal(rectLen, rectWidth))
}

init 函数

init 函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性。

包的初始化顺序如下:

  1. 首先初始化包级别(Package Level)的变量
  2. 紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。
package main  // 指定该文件属于main包

// 导入其他包
import (
    "5包/geometry/rectangle"
    "fmt"
    "log"
)

// 包级别变量
var rectLen, rectWidth float64 = 6, 7

// init 函数
func init()  {
   
   
    println("main package initialized")
    if rectLen < 0{
   
   
        log.Fatal("length is less than zero")
    }
    if rectWidth < 0{
   
   
        log.Fatal("width is less than zero")
    }
}


func main()  {
   
   
    fmt.Println("几何参数")
    fmt.Printf("area %.2f \n", rectangle.Area(rectLen, rectWidth))
    fmt.Printf("rectangle %.2f", rectangle.Diagonal(rectLen, rectWidth))
}

main 包的初始化顺序为:

  1. 首先初始化被导入的包。因此,首先初始化了 rectangle 包。
  2. 接着初始化了包级别的变量 rectLenrectWidth
  3. 调用 init 函数。
  4. 最后调用 main 函数。

空白标识符

导入了包,却不在代码中使用它,这在 Go 中是非法的。

然而,在程序开发的活跃阶段,又常常会先导入包,而暂不使用它。遇到这种情况就可以使用空白标识符 _

package main

import (  
    "geometry/rectangle"
    _ "fmt"                  // 错误屏蔽器
)

var _ = rectangle.Area      // 错误屏蔽器

func main() {
   
   

}

6. if else

语法

if condition {
   
     
    // 代码
} else if condition {
   
   
    // 代码
} else {
   
                 // }和else 必须在同一行,避免插入分号
    // 代码
}

在 Go 语言规则中,它指定在 } 之后插入一个分号,如果这是该行的最终标记。因此,在if语句后面的 } 会自动插入一个分号。

实际上我们的程序变成了

if num%2 == 0 {
   
     
      fmt.Println("the number is even") 
};  //semicolon inserted by Go
else {
   
     
      fmt.Println("the number is odd")
}

分号插入之后。从上面代码片段可以看出第三行插入了分号。因此会报错

举例:

package main

import "fmt"

func main() {
   
   
    num := 99
    if num <= 50 {
   
   
        fmt.Println("number is less than or equal to 50")
    } else if num >= 51 && num <= 100 {
   
   
        fmt.Println("number is between 51 and 100")
    } else {
   
   
        fmt.Println("number is greater than 100")
    }
}

7. 循环

for 是 Go 语言唯一的循环语句。

Go 语言中并没有其他语言比如 C 语言中的 whiledo while 循环。

for 循环语法

for 循环的三部分,初始化语句、条件语句、post 语句都是可选的。

for initialisation; condition; post {
   
     
}

eg.

func main() {
   
     
    for i := 1; i <= 10; i++ {
   
   
        fmt.Printf(" %d",i)
    }
}

or.

func main() {
   
     
    i := 0
    for i <= 10 {
   
    
        fmt.Printf("%d ", i)
        i += 2
    }
}

多个变量

for 循环中可以声明和操作多个变量。

func main()  {
   
   
    for no, i := 10, 1; i <= 10 && no <= 19; i, no = i+1, no+1{
   
   
        fmt.Printf("%d * %d = %d \n", no, i ,no*i)
    }
}

break

break 语句用于在完成正常执行之前突然终止 for 循环,之后程序将会在 for 循环下一行代码开始执行。

continue

continue 语句用来跳出 for 循环中当前循环。在 continue 语句后的所有的 for 循环语句都不会在本次循环中执行。循环体会在一下次循环中继续执行。

无限循环

无限循环的语法是:

for {
   
     
}

8. switch

switch是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。

func main(){
   
   
    num := 3
    switch num {
   
   
    case 1:
        fmt.Println(1)
    case 2:
        fmt.Println(2)
    case 3:
        fmt.Println(3)
    default:
        fmt.Println("5")
    }
}

当匹配不成功时,执行default.

多表达式判断

通过用逗号分隔,可以在一个 case 中包含多个表达式。

func main() {
   
   
    letter := "i"
    switch letter {
   
   
    case "a", "e", "i", "o", "u": // 一个选项多个表达式
        fmt.Println("vowel")
    default:
        fmt.Println("not a vowel")
    }
}

无表达式

在 switch 语句中,表达式是可选的,可以被省略。

如果省略表达式,则表示这个 switch 语句等同于 switch true,并且每个 case 表达式都被认定为有效,相应的代码块也会被执行。

func main() {
   
   
    num := 75
    switch {
   
    // 表达式被省略了
    case num >= 0 && num <= 50:
        fmt.Println("num is greater than 0 and less than 50")
    case num >= 51 && num <= 100:
        fmt.Println("num is greater than 51 and less than 100")
    case num >= 101:
        fmt.Println("num is greater than 100")
    }
}

Fallthrough 语句

在 Go 中,每执行完一个 case 后,会从 switch 语句中跳出来,不再做后续 case 的判断和执行。

使用 fallthrough 语句可以在已经执行完成的 case 之后,把控制权转移到下一个 case 的执行代码中。

func main() {
   
   

    switch num := number(); {
   
    // num is not a constant
    case num < 50:
        fmt.Printf("%d is lesser than 50\n", num)
        fallthrough
    case num < 100:
        fmt.Printf("%d is lesser than 100\n", num)
        fallthrough
    case num < 200:
        fmt.Printf("%d is lesser than 200", num)
    }
}

fallthrough 语句应该是 case 子句的最后一个语句。

9. 数组和切片

数组

数组是同一类型元素的集合。Go 语言中不允许混合不同类型的元素。

如果是 interface{} 类型数组,可以包含任意类型

数组的声明

一个数组的表示形式为 [n]Tn 表示数组中元素的数量,T 代表每个元素的类型。

func main()  {
   
   
    var a [3]int
    fmt.Println(a)     // [0 0 0]
    a[0] = 1
    a[1] = 2
    a[2] = 3
    fmt.Println(a)     // [1 2 3]
}

简略声明:

func main()  {
   
   
    a := [3]int{
   
   1,2,3}
    fmt.Println(a)       // [1 2 3]

    b := [3]int{
   
   1}
    fmt.Println(b)       // [1 0 0]
}

省略数组长度,用 ... 代替:

func main()  {
    a := [...]int{1,2,3}
    fmt.Println(a)       // [1 2 3]
}

数组的大小是类型的一部分。因此 [5]int[25]int 是不同类型。数组不能调整大小。

Go 中的数组是值类型而不是引用类型。这意味着当数组赋值给一个新的变量时,该变量会得到一个原始数组的副本。如果对新变量进行更改,则不会影响原始数组。

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

同样,当数组作为参数传递给函数时,它们是按值传递,而原始数组保持不变。

func changeLocal(num [5]int) {
   
   
    num[0] = 55
    fmt.Println("inside function ", num)    // [55 6 7 8 8]
}
func main() {
   
   
    num := [...]int{
   
   5, 6, 7, 8, 8}
    fmt.Println("before passing to function ", num)    // [5 6 7 8 8]
    changeLocal(num)
    fmt.Println("after passing to function ", num)    // [5 6 7 8 8]
}
数组的长度

通过将数组作为参数传递给 len() 函数, len(Array)可以得到数组的长度。

range

通过使用 for 循环的 range 方法来遍历数组,返回索引和该索引处的值

func main()  {
   
   
    a := [...]float64{
   
   12.3, 23.4, 34.5, 45.6, 56.7}
    sum := float64(0)
    for i,v := range a{
   
   
        fmt.Printf("数组a中第%d位元素是%.2f \n", i, v)
        sum += v
    }
    fmt.Println("数组a的元素和为:", sum)
}

如果只需要值并希望忽略索引,则可以通过用 _ 空白标识符替换索引来执行。

for _, v := range a {
   
    // ignores index  
}
多维数组
func main() {
   
   
    a := [3][2]string{
   
   
        {
   
   "lion", "tiger"},
        {
   
   "cat", "dog"},
        {
   
   "pigeon", "peacock"},  //逗号必须
    }
}

切片

切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用。

创建一个切片

带有 T 类型元素的切片由 []T 表示。

使用语法 a[start:end] 创建一个从 a 数组索引 start 开始到 end - 1 结束的切片:

func main()  {
   
   
    a := [5]int{
   
   1,2,3,4,5}
    var b []int = a[1:4]
    fmt.Println(b)
}

or

创建一个有 3 个整型元素的数组,并返回一个存储在 c 中的切片引用:

func main() {
   
     
    c := []int{
   
   6, 7, 8} 
    fmt.Println(c)
}
切片的修改

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。

func main() {
   
   
    darr := [...]int{
   
   57, 89, 90, 82, 100, 78, 67, 69, 59}
    dslice := darr[2:5]
    fmt.Println("array before", darr)   // [57 89 90 82 100 78 67 69 59]  
    for i := range dslice {
   
   
        dslice[i]++
    }
    fmt.Println("array after", darr)    // [57 89 91 83 101 78 67 69 59]
}

当多个切片共享同一个数组时,每个切片所做的修改都会反映在数组中。

切片的长度和容量

切片的长度是切片中的元素数。切片的容量是从创建切片索引开始的底层数组中元素数。

func main() {
   
   
    fruitarray := [...]string{
   
   "apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
    fruitslice := fruitarray[1:3]
    fmt.Println(len(fruitslice))    // 2
    fmt.Println(cap(fruitslice))    // 6
}
使用 make 创建一个切片

make 函数创建一个数组,并返回引用该数组的切片:

func make([]类型,长度,容量)

容量是可选参数, 默认值为切片长度。

func main() {
   
   
    i := make([]int, 5, 5)
    fmt.Println(i)    // [0 0 0 0 0]
}
追加切片元素

数组的长度是固定的,不能增加。

切片是动态的,使用 append 可以将新元素追加到切片上。append 函数的定义是

func append(s[]T,x ... T)

如果切片由数组支持,并且数组本身的长度是固定的,那么切片如何具有动态长度。以及内部发生了什么?

当新的元素被添加到切片时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量是旧切片的两倍(不同版本扩容方式不同)。

func main() {
   
   
    cars := []string{
   
   "Ferrari", "Honda", "Ford"}
    fmt.Println("长度是:", len(cars), ", 容量是:", cap(cars))    // 长度是: 3 , 容量是: 3
    cars = append(cars, "Toyota")
    fmt.Println("长度是:", len(cars), ", 容量是:", cap(cars))    // 长度是: 4 , 容量是: 6
}

切片类型的零值为 nil。一个 nil 切片的长度和容量为 0。

也可以使用 ... 运算符将一个切片添加到另一个切片。

func main() {
    veggies := []string{"potatoes", "tomatoes", "brinjal"}
    fruits := []string{"oranges", "apples"}
    food := append(veggies, fruits...)    //将fruits添加到veggies中
    fmt.Println("food:",food)   // food: [potatoes tomatoes brinjal oranges apples]
切片的函数传递

我们可以认为,切片在内部可由一个结构体类型表示。这是它的表现形式,

type slice struct {
   
     
    Length        int
    Capacity      int
    ZerothElement *byte
}

切片包含长度、容量和指向数组第零个元素的指针。

当切片传递给函数时,即使它通过值传递,指针变量也将引用相同的底层数组。

因此,当切片作为参数传递给函数时,函数内所做的更改也会在函数外可见,区别于数组

func subtactOne(numbers []int) {
   
   
    for i := range numbers {
   
   
        numbers[i] -= 2
    }
}
func main() {
   
   
    nos := []int{
   
   8, 7, 6}
    fmt.Println("slice before function call", nos)  // [8 7 6]
    subtactOne(nos)
    fmt.Println("slice after function call", nos)   // [6 5 4]
}
多维切片

类似于数组,切片可以有多个维度。

func main() {
   
     
     pls := [][]string {
   
   
            {
   
   "C", "C++"},
            {
   
   "JavaScript"},
            {
   
   "Go", "Rust"},
            }
    for _, v1 := range pls {
   
   
        for _, v2 := range v1 {
   
   
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}
内存优化

切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收。

假设有一个非常大的数组,我们只想处理它的一小部分。由这个数组创建一个切片,并开始处理切片,在切片引用时数组仍然存在内存中。

一种解决方法是使用 copy函数 func copy(dst,src[]T)int 来生成一个切片的副本。这样我们可以使用新的切片,原始数组可以被垃圾回收。

func countries() []string {
   
   
    countries := []string{
   
   "USA", "Singapore", "Germany", "India", "Australia"}
    neededCountries := countries[:len(countries)-2]
    countriesCpy := make([]string, len(neededCountries))
    copy(countriesCpy, neededCountries) // 将 neededCountries 复制到 countriesCpy
    return countriesCpy
}
func main() {
   
   
    countriesNeeded := countries()
    fmt.Println(countriesNeeded)
}

现在 countries 数组可以被垃圾回收, 因为 neededCountries 不再被引用。

10. 可变参数函数

可变参数函数是一种参数个数可变的函数。

语法

如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最后一个参数。

请注意只有函数的最后一个参数才允许是可变的。

append函数举例:

func append(slice []Type, elems ...Type)

上面是 append 函数的定义。在定义中 elems 是可变参数。这样 append 函数可以接受可变化的参数。

可变参数函数的工作原理是把可变参数转换为一个新的切片。

func find(num int, nums ...int) {
   
   
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
   
   
        if v == num {
   
   
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
   
   
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {
   
   
    find(89, 89, 90, 95)
    find(45, 56, 67, 45, 90, 109)
    find(78, 38, 56, 98)
    find(87)
}

以上面程序中为例,find 函数中的可变参数是 89,90,95 。 find 函数接受一个 int 类型的可变参数。因此这三个参数被编译器转换为一个 int 类型切片 int []int{89, 90, 95} 然后被传入 find函数。

给可变参数函数传入切片

在切片后加上 ... 后缀,切片将直接传入函数,不再创建新的切片

func find(num int, nums ...int) {
   
   
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
   
   
        if v == num {
   
   
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
   
   
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {
   
   
    nums := []int{
   
   89, 90, 95}
    find(89, nums...)
}

11. map

map 是在 Go 中将值(value)与键(key)关联的内置类型。通过相应的键可以获取到值。

创建map

语法: make(map[type of key]type of value)

personSalary := make(map[string]int)

上面的代码创建了一个名为 personSalary 的 map,其中键是 string 类型,值是 int 类型。

map 必须使用 make 函数初始化,未初始化的 map 的值是 nil。

给 map 添加元素

func main() {
   
   
    personSalary := make(map[string]int)
    personSalary["steve"] = 12000
    personSalary["jamie"] = 15000
    personSalary["mike"] = 9000
    fmt.Println(personSalary)	//map[jamie:15000 mike:9000 steve:12000]
}

or:

func main() {
   
   
    personSalary := map[string]int {
   
   
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    fmt.Println(personSalary)   // map[jamie:15000 mike:9000 steve:12000]
}

获取 map 中的元素

获取值

获取 map 元素的语法是 map[key]

func main() {
    personSalary := map[string]int{
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    employee := "jamie"
    fmt.Println(employee, "is", personSalary[employee])    // jamie is 15000
    fmt.Println("joe is", personSalary["joe"])    //joe is 0
}

如果我们获取一个不存在的元素,会返回 int 类型的零值 0

map 中 key 是否存在?

如果想知道 map 中到底是不是存在这个 key,该怎么做 ?

value, ok := map[key]

上面就是获取 map 中某个 key 是否存在的语法。如果 ok 是 true,表示 key 存在,key 对应的值就是 value ,反之表示 key 不存在。

func main() {
   
   
    personSalary := map[string]int{
   
   
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    newEmp := "joe"
    value, ok := personSalary[newEmp]
    if ok == true {
   
   
        fmt.Println("Salary of", newEmp, "is", value)
    } else 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值