Go基础语法知识
- go入门
- go历史(简单了解)
- go优势 强大的编译能力 、媲美C的执行速度、 并发效率极高 、异步语言快速写同步程序、 严格的语法
- 下载及配置(已配置,带过)
- 开发工具,推荐goland,电脑没装,先用vs code
- 变量
- 声明格式 var和:=、驼峰标识、 值变量自动初始化赋对应零值 ,引用变量自动 赋nil 、类型缺省则自动推导、 :=只能用在函数内部
- :=左只要有一个变量未初始化,则可以对所有左侧变量初始化
- go特有 原地交换 ‘a,b=b,a’
- 匿名变量‘_’不可参与任何运算( 值被直接抛弃 ),但可以重复初始化
- 全局变量首字母大写可被外包调用,否则不可被外调
- 基本类型
- (u)int≠(u)int32(64),根据计算机位数改变;无符号整型相减会引起零下溢的问题,但在go中 不会报错
- golang所有变量(或常量)之间的比较必须 显式转换 为相同类型、如果值的类型是 接口 ,双方必须 实现相同的接口; 比较行为 存在短路
- byte和rune分别是uint8和int32的 别名 ;
- 狭义的Unicode表示一种 字符集 ,是ASCII码的超集、utf-8是这种字符集的一种 编码格式 、广义的Unicode是一种 标准 ,是以上两种的 总和 ;
- golang底层支持utf-8即 变长存储 字符;反引号 `` 内部的字符串不会进行转义;len()只能取字符串的 字节数 ,而不是 字符数 ,因此len(“hello,码神之路”)的结果为18;byte和rune都是 字符类型 ,组合即为 字符串类型 string;Sprint的结果会以字符串形式返回;
- string类型不可变,如需改变则转为[]byte;
- 常量指针
- 所有常量的运算都可以在 编译期完成 ,且运算的结果也是常量;在一个 const 声明 语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加1;
- 指针分为类型指针和切片:类型指针 不能进行偏移和运算 、切片由指向起始元素的 原始指针 、 元素数量 和 容量 组成;切片在发生越界时,运行时会 报出宕机,并打出堆栈 ,而原始指针只会 崩溃 ;
- 编译器 自动选择 在栈或者堆上分配局部变量的存储空间,不论使用 var 还是 new 关键字声明变量都 不会 影响编译器的选择;若函数返回但局部变量仍在被使用,则局部变量 不会被销毁 ,且一定分配在 堆 上,用Go语言的术语说, 这个局部变量 x 从函数 f 中逃逸了 ,否则该变量既可在堆上创建也可在栈上创建;
- 类型创建和别名的区别:
- type newInt int
- var a newInt //newInt为类型创建,输出a的类型为main.newInt
- type newInt = int
- var a newInt //newInt为int的别名,输出a的类型为int,编译完成时,不会有newInt 类型
- 注释(带过)
- 保留关键字 不可以 使用;预定义标识符 不建议 使用(不报错)
- 运算符优先级(带过)
- 字符串转其他数据类型(带过)
- 数组切片
- 数组类型相同时可以比较,只有 所有元素相同 时相等。注意[ 3 ] int /[ 2 ]int/[3] float 为三种类型 无法比较(编译报错);
- 多维数组与C\JAVA等基本一致(带过)
- 每个切片值都会将数组作为其底层数据结构,我们把这样的数组称为切片的 底层数组 ;数组为 值 类型,切片为 引用 类型;切片 只能 与nil相等(编译报错);
- 切片复制为 浅 拷贝;
- map 是一种 无序 的键值对的集合; 切记不要使用new创建map,否则会得到一个空引用的指针 ;map 在 并发 情况下,只读是线程安全的,同时 读写 是 线程不安全 的; 线程安全 的map:sync.Map;
- nil 标识符 互相 是 不能 比较的;nil 不是 关键字或保留字;nil 没有 默认类型; 不同类型 nil 的指针是 一样的 ; 不同类型 的 nil 值占用的 内存大小 可能是 不一样 的;
- make 关键字的主要作用是创建 slice、map 和 Channel 等 内置的数据结构 ;new 可以分配 任意 类型的数据;make 分配空间后,会进行 初始化 ,new分配的空间被 清零 ;new 分配返回的是 指针 ,make 返回 引用 ;
- 流程控制
- 可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断
-
在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。
- golang没有while循环
- for range语句中的第二个变量val为值拷贝;对于字符串,for range的第一个变量返回 字节 数,第二个变量返回对应的 utf-8编码 ,两者一定是 对照 的(无论字符串中byte和rune如何组合)
- 通过 for range 遍历的返回值有一定的规律:
- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- channel只返回管道内的值。
- Go里面switch默认相当于每个case最后 带有break 、加上 fallthrough 时,执行完当前ase之后,进入下一个case;
-
加了fallthrough后,会直接运行【紧跟的后一个】case或default语句,不论条件是否满足都会执行
- break 语句还可以在语句后面添加标签,表示退出 某个标签对应 的代码块,标签要求必须 定义在对应的 for、switch 和 select 的 代码块 上;
- 在 continue 语句后添加标签时,表示开始 标签对应 的 循环
- 函数
- Go 语言的函数属于“ 一等公民 ”(first-class):
- 函数本身可以作为 值 进行 传递 。
- 支持 匿名函数 和 闭包 (closure)。
- 函数可以 满足接口 。
- 在将函数做为参数的时候,我们可以使用类型定义,将函数定义为类型,这样 便于阅读
-
我一点没觉得
- Go支持对返回值命名、 返回时未声明 的返回值直接返回对应 零值
- golang中 所有函数参数 都是 传值 的,其中map、slice、chan、指针、interface、函数传递的也是 拷贝的地址,称其为引用传递 ;函数参数支持不定参数传值,即以将多个相同类型的参数以切片展开传递
arg... //表示将arg展开,类似python中的*
- 回调函数是指 一个函数作为参数传递给另一个函数, 并且在 特定事件 发生或 条件满足 时被 调用执行的函数
- 闭包是指一个 函数 (或函数值)与其 引用的外部变量 (自由变量)的 绑定关系 。换句话说,闭包是一个 函数 和其 相关的引用环境 的 组合体 ;在闭包中,内部函数可以使用外部变量,甚至可以在外部函数执行 结束 后 依然保存相应变量 ,该特性经常帮助实现一些变量从外部函数中 逃逸
- defer 语句会将其后面跟随的语句进行 延迟处理 ;多个defer以 栈 形式处理;defer压栈后会 立即 存储当前函数的所有参数值,执行时以 前者时刻数值 处理;注意golang中 所有参数 都是 传值 的!所以任何非显然的值传递参数都会 优先计算为值 后再传递,这部分工作在defer 压栈时 完成,解决上述问题:使用defer fun(),因为拷贝的是 函数指针 ,函数 属于引用传递 ;
- //golang return语句执行顺序
- 返回值赋值
- 执行defer
- 执行RET指令
- Go语言中使用 panic 抛出错误,recover 捕获错误;程序抛出panic后仍然会正常执行 已压栈 的defer语句;利用recover处理panic指令,defer 必须放在 panic 之前 定义; recover 只有在 defer 调用的 函数 中才有效;延迟调用中引发的错误,可被后续延迟调用捕获,但 仅最后一个错误 可被捕获;如果需要保护代码段,可将代码块 重构成匿名函数 ,如此panic被recover后返回上一级函数则可以正常执行;除用 panic 引发 中断性错误 外,还可返回 error 类型错误对象来表示 函数调用状态 ;golang中的error类型和其他类型数据 无本质区别 ;
- Go 语言的函数属于“ 一等公民 ”(first-class):
- 结构体
- 结构体成员也可以称为“字段”
- 结构体的零值为其所有字段的零值;同一类型名的结构体 所有字段相同 时相等、不同类型的结构体 无法比较 (即使其全部字段一致),但是所有结构体与字段相同的匿名结构体属于 相同类型 (匿名结构体的 别名 等价匿名结构体);
- Go 方法是作用在 接收器 (receiver)上的一个 函数 ; 除了接口类型 和 指针类型 外,接收器 可以是任何一种类型 (包括函数);类型的代码和绑定在它上面的方法 等价于面向对象中的一个类 ,他们的代码必须在 同一个包下 ;接收器根据接收器的类型可以分为 指针 接收器、 非指针 接收器;
- 结构体可以包含一个或多个 匿名 (或 内嵌 )字段;Go语言中的继承是通过 内嵌或组合 来实现的,即某类型 可调用其内嵌类型的方法(请注意这里的内嵌类型必须是匿名字段) (另外结构体是可以内嵌多个匿名字段的,若多个匿名字段都实现了同名方法,则此时子结构体直接继承该方法 会报错! )
- 接口
- 接口类型声明中的方法必须具名且唯一;当接口类型和方法名 都 大写,此方法可以被包外代码访问
- 接口类型变量在 运行时存储 了右值的真实 类型信息 ; 接口的实现关系 在Go语言中是 隐式 的。两个类型之间的实现关系不需要在代码中显式地表示出来。
-
这个设计被称为非侵入式设计
- 一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中 嵌入其他类型或者结构体 来实现;接口与接口间可以通过嵌套创造出新的接口
- 空接口类型的变量可以 存储任意类型的变量 ;使用空接口实现可以 保存任意值 的字典(即同一个字典的value不限制类型);要判断空接口中的值这个时候就可以使用 类型断言:
- x.(T) // 返回两个值分别是对应类型值和是否断言成功
- 关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。
- io包接口和原始的操作以不同的实现包装了低级操作,客户 不应假定 它们对于 并行执行是安全的 ;
- io库比较常用的接口有三个,分别是 Reader , Writer 和 Closer ;
- 包
- 一个文件夹下的所有源码文件 只能属于同一个包 ,属于同一个包的源码文件 不能放在多个文件夹下 ;在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式:
import _ "fmt".
- 使用go module之前需要设置环境变量:
- GO111MODULE=on
- GOPROXY= open in new window
- GOPROXY=https://goproxy.cn,direct(国内的七牛云提供)
- GO111MODULE 有三个值: off , on 和 auto(默认值)
- 在 GOPATH 目录下新建一个目录,并使用go mod init初始化生成 go.mod 文件
- 执行go mod tidy, go mod 会自动查找依赖自动下载
- 使用 replace 替换无法直接获取的 package
- go install命令将项目打包安装为可执行文件,在安装在GOPATH的bin目录下,go install执行的项目 必须有main方法
- 一个文件夹下的所有源码文件 只能属于同一个包 ,属于同一个包的源码文件 不能放在多个文件夹下 ;在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式:
- 并发
- Go 从 语言层面 就支持并发。同时实现了 自动垃圾回收 机制
- 进程/线程
- 进程是程序在操作系统中的一次 执行过程 , 系统 进行 资源分配 和 调度 的一个 独立单位 。
- 线程是进程的一个 执行实体 ,是 CPU 调度 和 分派 的 基本单位 ,它是比进程 更小 的能 独立运行 的 基本单位 。
- 一个进程可以 创建和撤销 多个线程,同一个进程中的多个线程之间可以 并发 执行。
- 并发/并行
- 多线程 程序在 单核心 的 cpu 上运行,称为 并发 ;
- 多线程 程序在 多核心 的 cpu 上运行,称为 并行 。
- 并发与并行并不相同,并发主要由 切换时间片 来实现(准) “同时”运行 ,并行则是直接 利用多核 实现 多线程的运行 ,Go程序可以 设置使用核心数 ,以发挥多核计算机的能力。
- 协程/线程
- 协程: 独立 的 栈空间 , 共享堆空间 ,调度由 用户 自己 控制 ,本质上有点类似于 用户级线程 ,这些用户级线程的调度也是自己实现的。
- 线程: 一个线程 上可以跑 多个协程 , 协程是轻量级的线程 。
- Goroutine 一般将其翻译为Go协程,也就是说Go语言在 语言层面 就实现了 协程的支持
- Go程序会 智能 地将 goroutine 中的任务合理地 分配 给每个CPU
-
程序员只需要定义很多个任务,让系统去把这些任务分配到CPU上实现并发执行
- Go语言编程中不需要去自己写进程、线程、协程,当需要让某个任务并发执行的时候,只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了
- 主协程 退出了,子协程还会执行吗? 会 ,只要main函数没有结束,协程就会继续执行。
- 函数与函数间需要交换数据才能体现并发执行函数的意义;
- 共享内存在不同的goroutine中容易发生 竞态 问题;
- 为保证数据交换的 正确性 而使用 互斥量 对内存进行 加锁 会造成 性能 问题;
- Go语言的并发模型是 CSP (Communicating Sequential Processes),提倡 通过通信共享内存 而不是通过共享内存而实现通信
- channel就是并发的goroutine之间的 连接
- 通道像一个传送带或者队列,总是遵循 先入先出 (First In First Out)的规则
- 声明通道后需要使用 make函数初始化 之后才能使用
- 关闭后的通道有以下特点:
- 对一个关闭的通道再发送值就会导致panic。
- 对一个关闭的通道进行接收 会一直获取值 直到通道为空。
-
嗯?
- 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的 零值 。
- 关闭一个已经关闭的通道会导致panic。
- 使用无缓冲通道进行通信将导致发送和接收的goroutine 同步化 。因此, 无缓冲通道也被称为同步通道
- 如何判断一个通道是否被关闭?
i, ok := <-ch1 // 通道关闭后再取值ok=false
for i := range ch2 { // 通道关闭后会退出for range循环
- 在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的,但反过来是不可以的;
- select可以同时监听一个或多个channel,直到其中一个channel ready;
- Go语言中使用sync包的Mutex类型来实现互斥锁;使用sync包中的RWMutex类型来实现读写锁;
- 代码中的 加锁操作 因为 涉及内核态的上下文切换 会比较耗时、代价比较高;
- 针对 基本数据类型 我们还可以使用 原子操作 来保证并发安全,原子操作是Go语言提供的方法它在 用户态 就可以完成