Go语言基础Part I | 青训营笔记

153 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记.

第一部分:基础语法与变量类型

1.1 hello world

package main // 一个package只有一个main方法

import "fmt" // 导入标准库

func main(){
     fmt.Println("Hello,world!")
 }

1.2 基础数据类型

var a = "initial"

var b, c int = 1, 2

var d = true

var e float64

f := float32(e)

g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g)                // initialfoo

const s string = "constant"
const h = 500000000
// 3e(+)20 表示3x10^20
const i = 3e20 / h
fmt.Println(3e+5)
// math.Sin 返回弧度参数的正弦值
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))

go中的基本数据类型主要有:整型,浮点型,布尔型,字符串

  • 整型有int,int8,int16,int32,int64之分,还有uint8,uint16,uint32,uint64等无符号整数以及还有uint8重定义的byte类型和int32重定义的rune类型;未写明类型的uint和int代表32位或64位
  • 浮点型包括float32和float64,分别表示32位和64位的浮点型数;以及complex64和complex128,分别表示32位和64位的实数和虚数
  • 布尔型,只能是true或false
  • 字符串,可以用+号进行简单拼接,可以用==进行比较,可以遍历,遍历得到的字节是rune类型。更详细的操作在strings包里进行讲解。

变量的命名有几种方式:

  1. 形如var a int = 2 ,其中类型int可以省略,go语言会根据后面的值自动判断类型;但如果需要int64,int32这种需要指定精度的,则务必要写明
  2. 短声明,形如a := 1,这种形式比较简单,但全局变量的声明不能用短变量声明

go是一门强类型的语言,变量一旦命名必须被使用;变量类型一旦确定,就不能更改,除非用转换的方式,比如下面的情况:

f1 := 1.234435
f2 := 1.9
a := int(f1)
b := int(f2)
fmt.Println(a) // 1
fmt.Println(b) // 1

可以看出,类型转换可能会有一定的精度损失,还有部分类型不能相互转换,比如string和int或float64是不能相互转换的;比较常用的转换是string和[]byte,也就是字节数组和字符串类型可以相互转换:

s := "bytedance"
b := []byte(s)
fmt.Println(b)  // [98 121 116 101 100 97 110 99 101] 
fmt.Println(string(b)) // bytedance

常量类型const更是不可更改的,不允许有任何改变; iota,特殊常量,可以认为是一个可以被编译器修改的常量。 iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。 iota 可以被用作枚举值

1.3 循环

i := 1
for {
   fmt.Println("loop") // loop
   break
}
for j := 7; j < 9; j++ {
   fmt.Println(j) // 7 8
}

for n := 0; n < 5; n++ {
   if n%2 == 0 {
      continue
   }
   fmt.Println(n) // 1 3
}
for i <= 3 {
   fmt.Println(i) // 1 2 3
   i = i + 1
}

go里面只有for循环,

  • for后面可以定义循环变量,循环条件等等;如果什么也不加就是无限循环;
  • 只加一个条件就类似其他语言里面的while循环
  • 循环控制语句有break,continue, goto等等

1.4 条件语句

if 7%2 == 0 {
   fmt.Println("7 is even")
} else {
   fmt.Println("7 is odd")
}

if 8%4 == 0 {
   fmt.Println("8 is divisible by 4")
}

if num := 9; num < 0 {
   fmt.Println(num, "is negative")
} else if num < 10 {
   fmt.Println(num, "has 1 digit")
} else {
   fmt.Println(num, "has multiple digits")
}

go里面的if/else语句,条件部分不用加括号,if后可以接变量命名,作用域只在if条件体内

a := 2
switch a {
case 1:
   fmt.Println("one")
case 2:
   fmt.Println("two")
case 3:
   fmt.Println("three")
case 4, 5:
   fmt.Println("four or five")
default:
   fmt.Println("other")
}

go中的switch语句,

  • 每个分支是相对独立的,不加break也能自动结束;
  • 可以对单个变量进行条件判断,也可以switch后面什么都不加,case分支用条件语句;
  • 如果想执行多个分支,可以用fallthrough(也可以用break中止)

switch还可以用于interface类型判断

    
   switch i := x.(type) {
      case nil: 
         fmt.Printf(" x 的类型 :%T",i)                
      case int:  
         fmt.Printf("x 是 int 型")                      
      case float64:
         fmt.Printf("x 是 float64 型")          
      case func(int) float64:
         fmt.Printf("x 是 func(int) 型")                      
      case bool, string:\
         fmt.Printf("x 是 bool 或 string 型" )      
      default:\
         fmt.Printf("未知型")    
   }

1.5 Array(数组)

var a [5]int
a[4] = 100
fmt.Println("get:", a[2]) // 0
fmt.Println("len:", len(a)) // 5

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 a [5]int也可以用短声明
  • 数组支持通过索引index获取元素,以及通过len获取数组长度
  • 如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

1.6 Slice(切片)

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) // 深拷贝 deep copy
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]

切片是一种元素个数不固定的数组,它一般有len长度和cap容量两个参数

  • 切片的声明方式除了数组的两种以外,多了一种make的方式:s := make([]string, 3, 5) 前一个是len,后一个是cap
  • 通过append函数向切片的末尾追加元素,而删除的话只能通过切片的方式
  • 切片的复制一般是浅拷贝,比如[:]的方式和直接赋值,内部指针其实指向同一个底层数组,修改其中一个,另一个也会随之改变
  • 如果要实现深拷贝,应该使用copy方法,copy前后的两个切片就不再指向同一个数组

1.7 map

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")
// it deletes the element with the specified key (m[key]) from the map

m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)

map是go语言中的哈希表,key和value的类型确定了之后就不能改变

  • map声明之后一定要初始化;如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。会报空指针的错
  • 可以校验某个key值是否存在,如r, ok := m["unknow"],这里ok是一个布尔值,true表示存在,反之则不存在
  • 可以通过delete函数删除map中的某个键值对

1.8 range

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 {
   // todo 为什么顺序不一致
   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 对。 在map中也可以只读取key:for key := range m;其他情况忽略另一个值可以用下划线_来代替