cat
Shioho

# 下载与安装

大家可以在 Go 语言官网下载 Windows 系统下的 Go 语言开发包。
下载 Windows 版本的 Go 语言开发包时尽量选择 MSI 格式,因为它可以直接安装到系统,不需要额外的操作。

# Hello World

# 脚本

package main
import "fmt"
func main() {
	fmt.Println("hello,world!")
}

package 类似于 c# 中的 namespace
import 类似于 c# 中的 using

# 编译与运行

  • 方法一:先生成.exe 再运行
go build xxx.go
  • 方法二:直接运行脚本
go run xxx.go

# 基础语法

# 数据类型

类型描述
布尔型布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
数字类型整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
字符串类型字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
派生类型(a) 指针类型(Pointer)
(b) 数组类型
(c) 结构化类型 (struct)
(d) Channel 类型
(e) 函数类型
(f) 切片类型
(g) 接口类型(interface)
(h) Map 类型

# 数据声明

# 变量声明

类型在变量名之后

var x int = 0; //int 类型可以忽略,声明后可强制类型绑定

注意:函数内声明的变量如果没使用的话会导致编译报错
如果暂时用不到的话可以写 _=x 避免报错

# 函数声明

  • 函数可以没有参数或接受多个参数,类型也在变量名之后,返回值也放在最后
func add(x int, y int) int {
	return x + y
}
  • 当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略
    x int, y int 可以缩写为 x, y int

  • 函数可以有多个返回值

func swap(x, y string) (string, string) {
	return y, x
}
a, b := swap("hello", "world")

简洁赋值语句 := 用于类型推导,等价于 var , 但只能在函数内使用

  • Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}
  • 函数作为实参
    可以声明一个变量为函数类型,作用于回调,与 js 类似
// 声明一个函数类型
type cb func(int) int
func main() {
    testCallBack(1, callBack)
    testCallBack(2, func(x int) int {
        fmt.Printf("我是回调,x:%d\n", x)
        return x
    })
}
func testCallBack(x int, f cb) {
    f(x)
}
func callBack(x int) int {
    fmt.Printf("我是回调,x:%d\n", x)
    return x
}
  • 闭包
    闭包是指有权访问另一个函数作用域中变量的函数,并一直保存在闭包函数的内存中,与 js 的闭包一致
func add() func() int{
    i:=0
    return func() int{
        i++;
        return i
    }
}
func main(){
	addNum1 := add()
	fmt.Println(addNum1())  //1
	fmt.Println(addNum1())  //2
	addNum2 := add()
	fmt.Println(addNum2())  //1
	fmt.Println(addNum2())  //2
}
  • defer 关键字
    defer 语句会将函数推迟到外层函数返回之后执行。
func main() {
	defer fmt.Println("world")
	fmt.Println("hello")
}
// 输出结果为
//hello
//world

defer 栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用

func main() {
	fmt.Println("counting")
	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}
	fmt.Println("done")
}
// 输出结果为 9->0

# 结构体声明

type Books struct {
   title string
   author string
   subject string
   book_id int
}
// 创建
Books{"aaa", "bbb", "ccc", 1}
Books{title: "aaa", author: "bbb", subject: "ccc", book_id: 1}
Books{title: "aaa", author: "bbb"}
// 使用
var Book1 Books  
Book1.title = "aaa"
Book1.author = "bbb"
Book1.subject = "ccc"
Book1.book_id = 1
  • 为结构体添加一个方法
// 该 method 属于 Books 类型对象中的方法
func (c Books) getName() string {
  return c.title
}
func main() {
  var c1 Books
  fmt.Println(c1.getName())
}

# 枚举声明

  • 枚举值从 0 开始累加
const (
    Unknown = iota
    Female
    Male
)
  • iota 的用法
    每当 iota 在新的一行被使用时,它的值都会自动加 1
const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   // 独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7, 恢复计数
            i          //8
    )

常量值会继承上一个常量值,如 e=d,g=f

有趣的的 iota 实例

const (
    i=1<<iota  //1<<0       1
    j=3<<iota  //3<<1       6
    k          //3<<2       12
    l          //3<<3       24
)

# 条件语句

  • if 语句
if xxx{
        ...
    }else if xxx{
        ...
    }else{
        ...
    }
  • switch 语句
switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

go 的 case 结尾不需要加 break 也能跳出,与 c# 不同

fallthrough 关键字
使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

a := 1
	switch a {
	case 1, 2, 3:
		fmt.Println("true")
		fallthrough
	case 4:
		fmt.Println("true")
	default:
	}
    // 会输出 2 次 true

# 循环语句

  • for 循环
sum := 0
    for i := 0; i <= 10; i++ {
        sum += i
    }
  • 死循环
for true  {
        if xxx
            break
    }
  • for 循环的遍历,range 格式可以对 slice、map、数组、字符串等进行迭代循环
    看这

# 数组

  • 声明数组
var balance [10]float32
  • 初始化数组
// 定长
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 不定长,自动根据元素个数赋值
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 指定下表赋值
balance := [5]float32{1:2.0,3:7.0}
  • 访问数组元素
num:=balance[0]
  • 多维数组
// 创建一个二维数组
values := [][]int{}
// 添加元素
row1 := []int{1, 2, 3} // 切片方式创建
row2 := []int{4, 5, 6}
values = append(values, row1)
values = append(values, row2)
// 访问
fmt.Println(values[0])
fmt.Println(values[1])
fmt.Println(values[0][0])

# 切片 (Slice)

切片是对数组的抽象,使数组长度可以动态变化,类似于 c# 中的 List

  • 定义切片
slice1 := make([]type, len, capacity)

len : 数组长度, capacity : 总容量,为可选参数

  • 切片初始化
// 方法 1 
s :=[] int {1,2,3 } 
// 方法 2
s := make([]int,3,5)
// 方法 3
var numbers []int // 空切片
  • 切片截取
numbers := []int{0,1,2,3,4,5,6,7,8}  
// 截取索引 1-4
fmt.Println(numbers[1:4])
// 截取索引 0-3
fmt.Println(numbers[:3])
// 截取索引 2-len
fmt.Println(numbers[2:])
  • len () 和 cap () 函数

用于获取切片的 len 和 capacity

  • append () 和 copy () 函数
numbers := []int{0,1,2,3,4,5,6,7,8}  
//append 向切片末端添加一个元素
numbers = append(numbers, 1)
// 创建新切片
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
// 拷贝 numbers 的内容到 numbers1 
copy(numbers1,numbers)

# 集合 (map)

  • 声明
// 方法 1
var map1 map[string]string
// 方法 2
var map1 = make(map[string]string)
  • 赋值
map1["xxx"] ="xxx"
  • 查看元素是否存在
v,ok:=map1["xxx"]
//ok=true 存在,false 不存在
  • delete () 函数
    delete () 函数用于删除集合的元素,参数为 map 和其对应的 key
delete(map1,"xxx")

# 接口 (interface)

用于定义共性方法,具体实现交由不同结构体实现,类似于 c# 中的接口,但 go 中不存在 class,都交由 struct 实现

type Person interface{
    getSex() int
}
type Man struct{
}
type Woman struct{
    
}
func (man Man) getSex() int{
    return 1
}
func (woman Woman) getSex() int{
    return 0
}
func main(){
    var person Person
    // 创建一个男人
    person = new(Man)
    person.getSex()
    // 创建一个女人
    person = new(Woman)
    person.getSex()
}

# 范围 (range)

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

# map 遍历

for key, value := range oldMap {
    newMap[key] = value
}
// 省略 value
for key := range oldMap{
}
// 省略 key
for _, value := range oldMap{
}

# slice 遍历

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
//i 为索引,v 为值,若不想要 i,可用 '_' 替代
for i, v := range pow {
    fmt.Printf(i, v)
}

# string 遍历

str:="abc"
//i 为字符索引,c 为字符 Unicode 的值
for i, c := range str {
    fmt.Println(i, c)
}

# channel 遍历

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}
func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    //range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
    // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
    // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
    // 会结束,从而在接收第 11 个数据的时候就阻塞了。
    for i := range c {
        fmt.Println(i)
    }
}

# 指针

基本使用与 C++ 一致 (不想写了)

var ptr *int  // 空指针 nil
a:=20
ptr = &a // 取 a 的地址赋值
fmt.Printf(*ptr) // 解引用输出 20

# 类型转换

表达式 T (v) 将值 v 转换为类型 T。

i := 42
f := float64(i)
u := uint(f)

# 并发

# goroutine

goroutine 是轻量级线程 (协程与线程的处理方式,详情自行百度 GMP), 由 Golang 运行时进行管理调度。

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
func main() {
    go say("a")
    say("b")
}
// 输出的 a 和 b 是无序的,因为由不同线程处理哒

# channel

通道(channel)是用来传递数据的一个数据结构,可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送 (c <-chan) 或接收 (c chan<-)。如果未指定方向,则为双向通道 (在作为函数形参时用来确保既能发送又能接收)

  • 声明
//100 为通道缓冲区大小,可以不设置
ch := make(chan int,100)
  • 示例
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // 把 sum 发送到通道 c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c) // 看通道是否设置缓冲区,如果设置了则异步发送消息,没设置则会阻塞至接收方接收了消息
    go sum(s[len(s)/2:], c) 
    x, y := <-c, <-c // 从通道 c 中接收数据,会阻塞至 channel 收到值
    fmt.Println(x, y, x+y)
    // 输出结果为 -5 17 12 或者 17 -5 12
}
  • 通道缓冲区
    缓存区数量的意义是通道接收到数据的数量,带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

v, ok := <-ch 这个 ok 值判断通道是否关闭

  • 遍历通道与关闭通道
    看这
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

汘帆 微信

微信

汘帆 支付宝

支付宝