Go学习【01】:初步学习需要的知识
阅读原文时间:2021年09月24日阅读:1

理解以下知识,初步写简单go项目就足够了

基本语法

基本组成

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
  • 其它(可忽略)
    • go没有像php、js那样需要在文件开始申明 <?php ... 或者 <script>
    • 注释:多行 /* * 单行 //
    • 字符串链接符+
    • 标记:可以是关键字,标识符,常量,字符串,符号。
      • 标识符:标识符用来命名变量、类型等程序实体 ;一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

数据类型

  • 布尔值:truefalse
  • 数字类型
  • 字符串类型
  • 派生类型:依据上面是三个基础类型,衍生出的新类型
    • 指针类型(Pointer)
    • 数组类型(数组)
    • 结构化体类型(结构体)
    • Channel类型
    • 函数类型
    • 切片类型
    • 接口类型
    • Map类型
布尔类型

布尔类型就只有真假,truefalse

数据类型

会根据系统架构(如32位、64位系统)变化的类型

  • int
  • uint
  • uintptr

明确范围的数字类型

  • 整型

    • 无符号型

      • uint8 范围(0 ~255) 2百
      • uint16 范围(0 ~ 65535) 6万
      • uint32 范围(0 ~ 4294967295) 42亿
      • uint64 范围(0 ~ 18446744073709551615)184京
    • 有符号型(范围最大值小减半)

      • int8
      • int16
      • int32
      • int64
  • 浮点型

  • 其它数字类型

    • byte =》 uint8
    • rune =》 int32
    • uint
    • int
    • uintptr 无符号型

变量

定义

var identifier type
var identifier1, identifier2 type

说明

变量生命关键字 var

变量名称:identifier

变量类型:type

常用的几种声明方式

第一种:标准定义

指定变量类型,如果没有初始化,则变量默认为零值。

//var identifier type
var a int
//a值为0
  • 数值类型(包括complex64/128)为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)
  • 以下几种类型为 nil
第二种:自适应

根据值自行判定变量类型。

//var identifier = value
var a = 100
//a值为100 类型  int
第三种: :=

不使用 var 声明,用 := 声明变量

//value_name := value
a := 100
//等同于
// var a int
// a = 100
//注意:所以这里的a如果被声明过,这里再次var声明就会报错

派生类型声明定义方式不同,后面会讲到

多个变量声明

//法一
var a,b,c int
//-----环境分割线---------------------
//法二
var a,b,c = 100,200,300
//-----环境分割线---------------------
//法三
a,b,c := 100,200,300
//法四
//下面这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

常量

定义

const identifier [type] = value
//如果不定义类型,会根据值自动确定类型

特殊常量 iota ,每次出现自增,但是每次CONST关键字出现时,将会被重置为0

表达式

变量使用

作用域

Go 语言中变量可以在三个地方声明:

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数

定义

字符串、数字、布尔
//字符串
var a = "abc"
//数字
var b = 1
//布尔
var c = true

总结:字面量的类型定义使用常规定义就行var identifier = value ,变种不过多阐述。

//注意:  字符串使用双引号;单引号一般使用单个字符,并且还不是字符串类型,而是数据类型(type untyped rune,值为ASCII 码对应值)。
数组

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型

//声明: var variable_name [SIZE] variable_type
var calss_member [60] string
//赋值: variable_name[0] = "Tom"

//初始化
var class_member_1 =[5]string{"Jim","Tom","Neo","Lucy","Mei"}
class_member_2 :=[5]string{"Jim","Tom","Neo","Lucy","Mei"}
//不确定长度
class_member_3 =[...]string{"Jim","Tom","Neo","Lucy","Mei"}

总结:

  • {}中元素的个数,不能大于[]中指定的长度

  • 不知道长度时,可以使用...代替

  • 访问数据使用下标索引,从0开始

    //多维数组
    var variable_name [SIZE1][SIZE2]…[SIZEN] variable_type

    //初始化 例子
    a := [3][4]int{
    {0, 1, 2, 3} , /* 第一行索引为 0 / {4, 5, 6, 7} , / 第二行索引为 1 / {8, 9, 10, 11}, / 第三行索引为 2 */
    }
    //那么[2][2][2][2][2]是什么样子呢?
    a := [2][2][2][2][2] int {
    {
    {
    {
    {1,2},
    {3,4},
    },
    {
    {5,6},
    {7,8},
    },
    },
    {
    {
    {9,10},
    {11,12},
    },
    {
    {13,14},
    {15,16},
    },
    },
    },
    {
    {
    {
    {17,18},
    {19,20},
    },
    {
    {21,22},
    {23,24},
    },
    },
    {
    {
    {25,26},
    {27,28},
    },
    {
    {29,30},
    {31,32},
    },
    },
    },
    }

指针

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

所以指针变量储存的是地址,同时操作的也是地址(上的值),因为地址无法修改。当一个指针被定义后没有分配到任何变量时,它的值为 nil。

//举例:jack的钱包里有100元
        var jack int
        var point_jack *int
        jack = 100
        point_jack = &jack //拿到钱包
        *point_jack = 50   //操作钱包中的钱,放50元
        fmt.Println(jack)  //jack只有五十了
// 50

使用

//定义
// var var_name *var-type
var point_jack *int
// & 取地址
var num = 100
point_jack = &num
// * 使用
fmt.Println(*point_jack)
// 操作(不能掉了符号*)
*point_jack = 50
结构体

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

/* 定义  */
type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}
// 例子,如一个学生有ID、名字、学号、自我介绍
type  student struct {
  id   int
  name string
    code string
  dis string
} 

/* 变量声明 */
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
//例子
Jack := student{1,"Jack","10001","一个叫jack的孩子"}
Neo := student{id:2,naem:"Neo",code:"10002",dis:"一个叫Neo的孩子"}

/* 使用 */
variable_name.key
//例子
Jack.dis

结构体在函数中的使用

func main()  {
    Jack := student{1,"Jack","10001","一个叫jack的孩子"}
    fmt.Println(Jack.dis)
    var jk *student
    var num *int
    code := 1231231;
    num = &code
    jk = &Jack
    show(jk,num)
}

func show(stu *student,id *int)  {
    fmt.Println(stu.dis)
    fmt.Println(*id)
}

/* 函数运行结果 */
//一个叫jack的孩子
//一个叫jack的孩子
//1231231

总结:

  • 结构体是一个指定的数据结构,访问时使用符号.访问
  • 指针结构体访问和普通结构体访问没区别,但是基本类型指针访问值需要加前缀符号*
切片(slice)

Go 语言切片是对数组的抽象。数组的长度是固定的,切片的长度是不固定的。一个切片在未初始化之前默认为 nil,长度为 0

/* 定义  */
var identifier []type
// 其它定义方式
var slice1 []type = make([]type, len)
slice1 := make([]type, len)
make([]T, length, capacity)

// 初始化(变量声明)
variable_name := [] variable_type {value1, value2...valuen}
variable_name := 数组[start_index:end_index]
//例子
name := []string{"Tom","Jim","Neo"}
arr := [5]int{1,2,3,4,5}
slice_arr :=arr[2:4]

/* 使用 */
variable_name[index]
//例子
fmt.Println(name);
fmt.Println(slice_arr);
fmt.Println(slice_arr[0]);

-------
[Tom Jim Neo]
[3 4]
3

从上面可以看到切片常用的操作

/*
截取 [start_index:end_index]  明显索引范围是半包围,即 start_index 到 end_index -1

// 常见的截取如下:
[start_index:end_index]
[start_index:]
[:end_index]

// len() 和 cap() 函数
var numbers = make([]int,3,5)
len(numbers) => 3
cap(numbers) => 5
fmt.Println(numbers) => [0,0,0]

//空(nil)切片 //nil 空值,未赋值前变量的值
   var numbers []int
   if(numbers == nil){
      fmt.Printf("切片是空的")
   }
*/

//append() 和 copy() 函数
slice_int1 := [] int  //空切片, []
slice_int1 = append(slice_int1,1)  //[1]
slice_int2 = []int{10,11,12}
copy(slice_int1,slice_int2)  // slice_int1 => [1,10,11,12]
范围(rang)

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

//有点类似 PHP语言的 as ,使用方法如下
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
  fmt.Println(num)
}
集合(map)

Map 是一种无序的键值对的集合。

/* 定义 */
// 声明变量;默认 map 是 nil 和切片,指针一样
var  variable map[key_type]value_type

// make 函数
variable := make(map[key_type]value_type)

/* 初始化(变量声明) */
var  name_CN map[string]string
name_CN['Jim'] = "张三"
name_CN['Lilei'] = "李雷"
name_CN['Hanmeimei'] = "韩梅梅"

num_EN := map[string]string{"one":1,"two":2}

/* 删除元素: delete() 函数 */
delete(map,index)

delete(name_CN,"Jim")

类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

type_name(expression)

/* 例如 */

var money = 199.99
var pay int

pay = int(money)
fmt.Println(pay)
//199

运算符

  • 算术运算符:(假定有变量 a 和 b ,a=100, b=200)

    • + a+b

    • - a-b

    • * a*b

    • / a/b

    • % a%b

    • ++ a++

    • -- a--

  • 关系运算符

    • ==
    • !=
    • >
    • <
    • >=
    • <=
  • 逻辑运算符

    • &&
    • ||
    • !
  • 位运算符

    • &
    • |
    • ^
    • <<
    • >>
  • 赋值运算符

    • =
    • 前缀= a+=b ;a*=b 等
  • 其他运算符

    • & 取地址
    • * 地址指向值

流程控制

if

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}

// 例如,a=100

if a > 50 {
  fmt.Printf("> 50")
}

if…else

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

// 例如,a=100

if a > 50 {
  fmt.Printf("> 50")
}else{
  fmt.Printf("< 50")
}

if 嵌套

if 布尔表达式 1 {
   /* 在布尔表达式 1 为 true 时执行 */
   if 布尔表达式 2 {
      /* 在布尔表达式 2 为 true 时执行 */
   }
}

// 例如,a=100
if a > 50 {
  fmt.Printf("> 50")
  if a > 80{
    fmt.Printf("> 80")
  }
}

switch

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}
// 例如,成绩有 S,A,B,C,D   zhangsan ='B'
switch zhangsan {
  case 'S':
      fmt.Printf("优")
  case 'A':
      fmt.Printf("良")
  case 'B':
      fmt.Printf("一般")
  case 'C','D':
      fmt.Printf("不及格")
  default:
      fmt.Printf("没有成绩")
}

//fallthrough 强制执行,继续执行后面的选项,并且下一个还是强制执行
package main

import "fmt"

func main()  {

    var a = 'B'

    switch a {
    case 'S':
        fmt.Println("S")
        fallthrough
    case 'A':
        fmt.Println("A")
    case 'B':
        fmt.Println("B")
        fallthrough
    case 'C':
        fmt.Println("C")
    case 'D':
        fmt.Println("D")
        fallthrough
    default:
        fmt.Println("other")
    }
}
//
B
C

select

后期体会

for循环

//for
for init; condition; post { }

for condition { }

for {  }

//搭配使用 break  continue  goto

//例子:
package main

import "fmt"

func main()  {

    var i int
    for  i = 0; i<10;i++ {
        fmt.Println(i)
    }
    for ; i<20;{
      i++
      fmt.Println(i)
    }
    for i<30 {
      i++
      if(i%2 == 0){
          continue
      }
      fmt.Println(i)
    }

    for {
      i++
      fmt.Println(i)
      if i>40 {
        break
      }
    }
    goto LOOP
    fmt.Println("你可能出不来")
    LOOP:fmt.Println("果然出不来")
}

函数

函数是基本的代码功能块,用来实现一个基本功能。

/* 定义 */
func function_name(param type [,param type,param type])[return_type]{

}
/* 例子 */
func double(x int)int {
  return x*2
}
func changePosition(x,y int)(int,int) {
  return y,x
}

接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现这些方法就是实现了这个接口

/* 定义 */
type interface_name interface {
  method_name1 [return_type]
  method_name2 [return_type]
  method_name3 [return_type]
}

type struct_name struct {

}
/* 实现 */
func (struct_varibale_name struct_name) method_name()[return_type]{

}

/* 示例 */

异常处理

内置的go错误处理接口

type error interface {
    Error() string
}

举例

package main

import "fmt"
import "errors"

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: error")
    }
    return f*f , errors.New("math: ok")
}

func main(){
  result, err:= Sqrt(22)
  if err != nil {
     fmt.Println(err)
  }
  fmt.Println(result)
}

//math: ok
//484

并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

/* 定义 */
go 函数名( 参数列表 )

/* 例子 */
package main
import "fmt"
import "time"

func message(message string){
  for i := 0; i < 5; i++ {
    time.Sleep(1 * time.Second)
    fmt.Println(message)
  }
}
func main()  {
  message("000")
  go message("111")
  message("222")
}

// 输出
000
000
000
000
000
111
222
222
111
111
222
111
222
222
111
//结论:1.主流程不受影响,还是顺序执行;2.新启动的流程和主流程同时在执行
channel

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。

操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

package main
import "fmt"
import "time"

func tmpCache(a int,b int ,cache chan int){
  num := 0 

  fmt.Println("node 2:")
  fmt.Println(time.Now())

  time.Sleep(3 * time.Second)

  num = (a+b)*(a-b)/2
  cache <- num
}
func main()  {
  message :=make(chan int)
  count := 0

  fmt.Println("node 1:")
  fmt.Println(time.Now())

  go tmpCache(1,100,message)

  fmt.Println("node 3:")
  fmt.Println(time.Now())

//  time.Sleep(1 * time.Second)

  count = <- message
  fmt.Println("node 4:")
  fmt.Println(time.Now())

  fmt.Println(count)

}
//结果
node 1:
2021-09-15 16:48:35.746247 +0800 CST m=+0.000101265
node 3:
2021-09-15 16:48:35.746418 +0800 CST m=+0.000271887
node 2:
2021-09-15 16:48:35.746425 +0800 CST m=+0.000278833
node 4:
2021-09-15 16:48:38.748364 +0800 CST m=+3.002355026
-4999
// 注意点:node 2 到 node 4 有3秒;说明阻塞了3秒


package main
import "fmt"

func tmpCache(a int,b int ,cache chan int){
  num := 0
 // time.Sleep(3 * time.Second)
  num = (a+b)*(a-b)/2
  cache <- num
  close(cache)
}
func main()  {
  message :=make(chan int)

  go tmpCache(0,5,message)
  go tmpCache(5,10,message)
  go tmpCache(10,15,message)
  go tmpCache(15,20,message)
  go tmpCache(20,25,message)
  //close(message)
  fmt.Println("===================")
  for i := range message{
    fmt.Println(i)
  }
}

 //结果1
===================
-12
-62
-37
-87
-112
 //结果2
 ===================
-12
panic: send on closed channel

goroutine 22 [running]:
main.tmpCache(0x0, 0x0, 0x0)
        /Users/liupeng/go/test/src/heelo/func_channel1.go:8 +0x45
created by main.main
        /Users/liupeng/go/test/src/heelo/func_channel1.go:19 +0x1cf
exit status 2
 //结果3
 ===================
-12
-37
-62

总结:

  • 通道消息是按队列进出的
  • rang使用到通道时,会有异常关闭情况,如结果3明显是没有跑完,进程就关闭了;所以开启的进程数是1时可以(在异步函数中)关闭进程