go语言基础语法二 | 青训营笔记

119 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天


一、函数

package main

import "fmt"

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

func add2(a, b int) int {
    return a + b;
}

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

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

与其他语言不同的是,go语言可以有多个返回值。

两种传参方式:

  • 值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
  • 引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

函数的四种形式

  • 第一种方式:有参有返回值
func test1(a int, b int)(int) { 
   fmt.Println(a + b) return a+b 
}
func test2(a int, b int)(int,int) { 
   fmt.Println(a + b) return a+b,b*a 
} 
  • 第二种方式:有参无返回值
func test3(a int, b int){ 
   fmt.Println(a + b) 
} 
  • 第三种方式:无参有返回值
func test4()(int,int){ 
   a,b:=100,200 return a+b,a*b 
} 
  • 第四种方式:无参无返回值
func test5() {
   a, b := 100, 200
   fmt.Println(a + b)
}

二、数组

package main

import "fmt"

func main() {
    var a [5]int
    a[4] = 100
    fmt.Println(a[4], len(a))
    
    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(b)
    
    var twoD [2][3]int
    for i := 0; i < 2; i ++ {
        for j := 0; j < 3; j ++ {
            twoD[i][j] = i + j
        }
    }
    
    fmt.Println("2d: ", twoD)
}

数组初始化

  • 方式一:
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  • 方式二:
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  • 如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度,经测试 ... 也是可以省略的(类比C语言)
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  • 如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
//  将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。


三、指针

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

指针这块跟C语言的一样,如果你已经会C语言的指针,这部分可以跳过。

什么是指针

  • 一个指针变量指向了一个值的内存地址。

指针声明格式:

var var_name *var-type
//var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:

举例:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

Go空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil

nil 指针也称为空指针。

nil在概念上和其它语言的nullNonenilNULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr

空指针判断:

if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */

既然Go语言有指针,哪是不是也有C语言那些恐怖的东西,答案是肯定的,什么指针的指针、指针数组、数组指针等等一些东西,感兴趣的同学可以自行了解,这里不做介绍。


四、结构体

package main

import "fmt"

type user struct {
    name string
    password string
}

func main() {
    a := user{name: "wang", password: "1024"}
    b := user{"wang", "1024"}
    c := user{name: "wang"}
    c.password = "1024"
    var d user
    d.name = "wang"
    d.password = "1024"
    
    fmt.Println(a, b, c, d)
    fmt.Println(checkPassword(a, "haha"))
    fmt.Println(checkPassword2(&a, "haha"))
}

func checkPassword(u user, password string) bool {
    return u.password == password
}

func checkPassword2(u *user, password string) bool {
    return u.password == password
}

结构体的定义:

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:

  • Title :标题
  • Author : 作者
  • Subject:学科
  • ID:书籍ID

定义结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

声明结构体:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名

五、切片(Slice)

package main

import "fmt"

func main() {
    s := make([]string, 3)
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
    fmt.Println("get:", s[2])   //c
    fmt.Println("len:", len(s)) //3
    
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println(s) //[a b c d e f]
    
    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 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

切片的定义方式

  • 方式一:你可以声明一个未指定大小的数组来定义切片:
var identifier []type
  • 方式二:使用 make()  函数来创建切片:
var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)

其中,len 是数组的长度并且也是切片的初始长度。

切片初始化

  • 第一种初始化方式:直接初始化切片,[] 表示是切片类型, {1,2,3}  初始化值依次是 1,2,3,其 cap=len=3看着跟数组一模一样,因为切片本质就是变成数组。
s :=[] int {1,2,3 } 
  • 第二种初始化方式:初始化切片 s,是数组 arr 的引用。
s := arr[:] 
  • 第三种初始化方式:将 arr 中从下标 startIndexendIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:endIndex] 
  • 第四种初始化方式:默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[startIndex:] 
  • 第五种方式:默认 startIndex 时将表示从 arr 的第一个元素开始。
s := arr[:endIndex] 
  • 第六种:通过切片 s 初始化切片 s1
s1 := s[startIndex:endIndex] 
  • 第7种:通过内置函数 make()  初始化切片**s**, []int 标识为其元素类型为int 的切片。
s :=make([]int,len,cap) 

len()和cap()函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0

切片截取

可以通过设置下限及上限来设置截取切片  [lower-bound:upper-bound],区间为左闭右开。

append()和copy()函数

  • append() 函数的作用是在切片后面追加元素。
  • copy() 函数是用来拷贝切片。

六、范围(range)

package main

import "fmt"

func main() {
    nums := []int{2, 3, 4}
    sum := 0
    for i, num := range nums {
        sum += num
        if num == 2 {
            fmt.Println("index:", i, "num:", num) //index: 0 num: 2
        }
    }
    fmt.Println(sum) //9
    
    m := map[string]string{"a" : "A", "b" : "B"}
    for k, v := range m {
        fmt.Println(k, v) //b 8; a A
    }
    
    for k := range m {
        fmt.Println("key", k) //key a; key b
    }
}

Go 语言中 range 关键字用于 for 循环中迭代数组(array)切片(slice)通道(channel)集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对

  • for 循环的 range 格式可以对 slicemap数组字符串等进行迭代循环。 格式如下:
for key, value := range oldMap {
    newMap[key] = value
}

以上代码中的 keyvalue 是可以省略。

  • 如果只想读取 key,格式如下:
for key := range oldMap
或者这样:
for key, _ := range oldMap
  • 如果只想读取 value,格式如下:
for _, value := range oldMap

七、map

package main

import "fmt"

func main() {
    m := make(map[string]int)
    m["one"] = 1
    m["two"] = 2
    fmt.Println(m)  // map[one:1 two:2]
    fmt.Println(len(m))  // 2
    fmt.Println(m["one"]) // 1
    fmt.Println(m["unknow"]) // 0
    
    //查看元素在集合中是否存在
    r, ok := m["unknow"]
    fmt.Println(r, ok) // 0 false
    
    delete(m, "one")
    
    m2 := map[string]int{"one" : 1, "two" : 2}
    var m3 = map[string]int{"one" : 1, "two" : 2}
    fmt.Println(m2, m3)
}

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据。

定义map

  • 可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

如果不初始化 map,那么就会创建一个 nil mapnil map 不能用来存放键值对

delete()函数

  • delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key