导言
如果要学习一门新的语言,入门的话需要了解哪些知识?
- 有哪几种数据类型?, 如何定义?, 如何使用?
- 有哪些
流控制
?, 如何遍历可迭代的数据类型? - 如何定义一个函数?,如何使用?
- 如何定义一个类?,如何实现面向对象编程?(是否有继承,重载机制等)
- 最重要的就是—> 写好代码后要如何编译运行?
- 与其他语言的特别之处?
为什么学习Go
谷歌下Go语言的优点,比如并发性好,部署简单等等,由于没用过,所以这里对Go语言的评价,之后再补,现在学习Go的主要原因纯粹只是瞎打发时间…
Go 命令行使用
- go get “package name” — 获取包
- go run
- go build
数据类型
Go是一个静态语言, 与C/C++类似, 上面图片中大部分数据类型,与C/C++相同,下面就逐一了解比较特殊的一些数据类型. 可以注意到上图中很多数据类型指明大小,如uint8,int32
, 因此需要的是int
的大小取决于运行环境的cpu大小可能为64位或32位
.
byte 和 rune
byte
是uint8
的别名
(alias for uint8)rune
是int32
的别名(alias for int32)1
2
3
4
5
6
7// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32byte
用来表示raw data
, 用以与0~255
的int
类型区分开来,rune
用来表示Unicode字符
, 用于区分int32
的数字类型1
2
3
4
5
6
7
8
9
10package main
import "fmt"
const test rune = '好' // rune (unicode code point)
const test2 string = "好"
func main(){
// 注意字符和字符串的区别
fmt.Println(test) // 输出 22909
fmt.Println(test2) // 输出 好
}
复数类型
Go原生支持复数类型, complex64
类型的实部和虚部都为float32
, complex128
类型实部和虚部都为float64
1 | package main |
数组
数组创建初始化
1 | arr := [5]int{} // 大括号表示初始化内容,不写默认为0 |
引用类型
引用类型有slice
, map
, channel
如何创建
引用类型
?
引用类型
必须要通过关键字make
创建,以便完成内存分配
和相关属性的初始化
1 | // make 实现内存分配和初始化 |
slice 切片(动态数组)
slice 是个只读对象
1 | package main |
map
map 类似于Python中的dict
1 | package main |
Channel
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。
它的操作符(数据表示数据传送方向)是箭头 <-
1
2ch <- v // 计算机 发送值v到Channel ch中
v := <-ch // 计算机 从Channel ch中接收数据,并将数据赋值给v<-
总是优先和最左边的类型结合。(The <- operator associates with the leftmost chan possible)1
2
3
4chan<- chan int // 等价 chan<- (chan int)
chan<- <-chan int // 等价 chan<- (<-chan int)
<-chan <-chan int // 等价 <-chan (<-chan int)
chan (<-chan int)创建方法
1
2
3ch := make(chan int,100) // 创建双向传输int类型数据的 channel, 容量为100
ch1 := make(chan<- float64) // 创建只能用来发 float64类型数据的channel
ch2 := make(<-chan int) // 创建只能用来接收int类型数据的channel使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package main
import (
"time"
"fmt"
)
func main() {
go func() {
time.Sleep(1 * time.Hour)
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("Finished")
}
定义变量方式
利用 var
定义
- 利用
var
定义变量, 可以自动初始化为零值 - 具有类型推断功能, 无需定义类型(如果在定义变量后自行初始化, 编译器会自动推断变量类型)
1 | var t1 int = 123 // 正常 |
利用:=
进行变量定义
- 只能用于定义局部变量
- 具有类型推断, 不会自动初始化
1 | func test() { |
控制流语句
Go语言只有三种控制语句,if..else
,switch..case
,for
(注意没有while语句)
if..else
1 | if 表达式 { // 左大括号不可另起一行 |
switch..case
1 | switch 表达式 { |
for
1 | // for 可以用来代替while |
for .. range
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。
1 | package main |
函数
Go函数借鉴了动态语言中的一些特性
- 无需前置声明
不支持重载
- 不支持默认参数,不支持命名实参,(只能通过位置来确定参数)
- 不支持命名嵌套定义(nested) 即
不能嵌套函数
- 支持匿名函数和闭包
- 支持不定长参数
- 支持多返回值
- 支持命名返回值
1 | func function_name( [parameter list] ) [return_types] { // 左花括号不可另起一行 |
从函数中返回局部变量指针是安全的,编译器会通过”逃逸分析”(escape analysis
)来决定是否在堆上分配内存
不定长参数
不定长参数本质上是个slice
切片
1 | package main |
多返回值
1 | // 一般用于返回函数值和函数运行状态(异常或正常) |
匿名函数
1 | package main |
延迟调用 defer
defer 常用于资源释放,错误处理,接触锁定等,它会在所在函数结束前调用,即使函数出现异常提前退出
延迟调用,注册的是调用, 因此,如果后面跟得是一个函数,需要提供所需的参数,以便后续调用
实现过程大致为 : 注册 - - > 缓存调用参数等 - - > 调用
具有一定的开销
1 | package main |
1 | package main |
多个延迟按照 FILO(栈)顺序调用
return和panic语句都会终止当前函数,引发延迟调用
1 | package main |
可知 test函数执行流程为:
- 先 return 100(返回值i为100)
- 延迟调用匿名函数, i自加一
- 因此,最终的返回值为101
error
官方推荐通过函数返回的错误变量来判断函数是否正常运行
1 | func func_name ()(val string, err error){ // 返回错误变量 |
官方将error定义为接口类型
1 | type error struct{ |
1 | // 通过 errors.New(s string) 定义简单错误文本的error对象 |
panic (恐慌) 和 recover
1 | func panic(v interface()) // 传入参数空接口类型 |
- panic 抛出的错误对象, recover接收错误对象
- panic 会中断当前程序执行,执行延迟调用
1 | package main |
panic参数是空接口类型, 因此可以使用任何对象作为传入参数
结构(struct)
定义
1 | type Address struct { |
初始化
初始化有两种方式,一种按照参数名字,一种按照变量位置初始化,推荐使用第一种
1 | type Student struct { |
匿名字段
1 | type Address struct { |
定义方法
1 | package main |
对于方法来说如何选择实例的传入方式(拷贝指针/拷贝值)?
编译器会拷贝函数的传入参数(实参
),如果传入指针,则拷贝指针,传入实例,则整个拷贝这个实例(这可能会有一定的开销),传引用与传值本质上是拷贝指针还是拷贝实例的区别,
绝大部分情况,优先使用传入指针的方式,但这种方式有个特别需要注意的地方- -> 如果以传入指针方式, 则该指针所指向的实例可能会被修改,因此,如果想避免这种情况,则可以考虑传值.
引用类型,字符串,函数等指针包装对象, 可以直接传入值(因为该值就是指针)
特殊点
- 只有
i++
没有++i
- 没有
while
语句 - 不可重载