一份前端工程师快速接入GO语言语法直男指南(中)
long time no see, 又过了一周, 笔者这周为大家带来的是咱们这个指南的中篇(目测指南大概会存在: 上,中, 下, 进阶四篇), 而有了上篇的铺垫, 笔者相信你对go语言也有了一些基本的认识, 这一篇我们来继续讲讲go语言的其他特性:
- 指针
- 复合数据类型
- 函数
- go语言中一些常用的包
指针
如果你大学里上过C语言的话, 我相信你对指针这个东西不太陌生, 如果你没上过, 那笔者就用自己对指针的理解来跟同学聊聊指针
-
指针到底是什么?
跟int, string等类型一样, 指针也是一种类型, 用指针类型声明的变量叫做指针变量, 指针变量只能存储地址我们要知道, 即使是原始值, 他也是有地址的, 而一旦你操作的是地址, 则会产生跟引用值一样的效果, 所以指针的作用你应该能猜到了吧, 而在过去, C语言需要工程师自己做垃圾回收的时候, 指针也是必不可少的东西, 在现在, 很多语言都可以自动管理内存(GC机制), 所以我们对于指针的使用度会降低, 但是他在某些地方依旧可以帮我们大忙
str := "helloWorld" // 如你所见, 指针就是用来存储地址的, &叫做取地址符, 用来取一个变量的地址 var pointer *string = &str fmt.Println(pointer) // 0xc00008e1e0, 这一串就是str变量存储的值在底层的地址
如果我们想去读指针对应地址的值, 我们可以使用
*
来做读值处理·fmt.Println(*pointer) // 输出helloWorld
-
指针到底有什么用?
我们知道在go语言乃至绝大多数语言中, 都有引用类型和值类型之分, 值类型传递的是值的副本, 而引用类型传递的则是地址, 如果粗略的说的话, 就是有的时候我们也想传递值类型的地址, 而非值的副本str := "keep hungry, keep foolish" strPointer := &str secStr := str secStr = "if u think learning is expensive, u should estimating the const of ignorance" // 因为是值类型的副本传递, 所以str压根不会起到任何变化 fmt.Println(secStr) // if u think learning is expensive, u should estimating the const of ignorance fmt.Println(str) // keep hungry, keep foolish // 如果我们通过指针来修改值可就不一样了 *strPointer = "Strong minds discuss ideas, weak minds discuss people" fmt.Println(*strPointer) // Strong minds discuss ideas, weak minds discuss people fmt.Println(str) // Strong minds discuss ideas, weak minds discuss people
其实关于指针这个概念是绝对没有这么简单的, 如果一直往下面追溯, 是可以直接扯到寻址, 计算机底层存储原理的, 但是因为笔者对于计算机底层也不是很理解, 就不敢误人子弟了, 有兴趣的朋友可以自行去了解了解
在下面的一些模块中, 我也会将指针和其他的一些数据结构进行联合使用, 来增加大家对指针的理解
复合数据类型
除去根据引用类型和值类型太区分数据类型之外, 其实我们通常还会使用简单数据类型和复合数据类型来区分数据, 比如我们之前见过的string, int等都是简单数据类型, 而这里我们要带来一些常用的复合数据类型:
- 数组(Array)
- 切片(Slice)
- Map
- 结构体(Struct)
数组(Array)
数组我相信大家都不陌生了吧, 但凡你稍微搞点编程, 数组是一定清晰的, 这里笔者就不再介绍数组的概念了, 而是直接从js和go中数组的差异点来帮助你更好的接入go语言的数组
- 数组的定义
// 仅声明 var arr [2]int // var 数组名称 = [数组长度]数组中元素的类型{数组元素...} var intArr = [5]int{ 1, 2, 3, 4, 5} // 让系统自动推导长度 ...运算符表示让数组自动推导长度 var secIntArr = [...]int{ 1, 2, 3} // 简短声明 strArr := [3]string{ "break", "continue", "return"} // 通过索引指定初值 boolArr := [3]bool{ 2: true} // 代表数组索引为2的位置值为true
- 数组定长
不仅是在go语言中, 乃至大部分静态语言中, 数组都是定长的, 这就意味着, 一旦你给定了数组的长度, 则该数组的长度不能改变, 有些同学可能就会想了, 那我像js一样不给定长度不就行了, 不好意思, 还真不行, 数组的声明就必须指定数组长度, 而使用...
来推导长度的话则必须指定元素arr := [3]int{ 1, 2, 3} // 以下会直接报错 error: invalid array index 4 (out of bounds for 3-element array) // 告诉你数组越界 arr[4] = 10
可能有的朋友要问, 数组为啥要定长呢?
- 我们知道每个变量的内存都是分配好了的, 如果数组不定长, 因为数组的地址必须是连续的, 那么我们对数组进行长度增加的时候可能会冲突到其他已经分配好内存的变量
就好比你跟你邻居住在一起, 如果你家是可以自动伸缩大小的话, 那你房子变大了, 邻居的房子就被你挤压了, 如果你变小了, 变小的时候有人看到这里有块空地, 就建了个花园, 结果你又变回原来的大小, 是不是又把他这个花园压烂了, 可能有的朋友会说, 使用js的时候数组都是可以自动伸缩的呀, 这是为什么呢? 别急, 待会你就知道了
- 数组默认值
由于数组定长, 所以当我们某些情况下仅仅声明了数组却没有给数组的元素赋值的情况下, 数组的元素是存在默认值的, 跟我们之前说到的原始值默认值相同- int类型的数组默认值为0
- string类型的数组默认值为""
- bool类型的数组默认值为false
- 如果通过值类型和引用类型来划分数据的话, 数组算值类型, 因为当我们在传递数组的时候传递的是值的副本, 而非像js一样传递引用
如果我们想传递数组的引用的话, 可以通过指针来实现(因为指针的存在, 咱们进可攻退可守)fstArr := [...]int{ 1, 2, 3} secArr := fstArr secArr[0] = 100 fmt.Println(secArr) // [100 2 3] fmt.Println(fstArr) // [1 2 3]
fstArr := [...]int{ 1, 2, 3} secArr := &fstArr // 下面本身应该写成*secArr[0] = 100 // 但是在数组指针中有语法糖, 我们可以被允许省略*号来读值 secArr[0] = 100 fmt.Println(secArr) // [100 2 3] fmt.Println(fstArr) // [100 2 3]
- 数组的长度
我们知道在js中我们可以直接通过arr.length
来获取一个数组的长度, 而在go语言中, 我们必须通过len
函数来获取数组的长度arr := [2]int{ 1, 2} fmt.Println(len(arr)) // 2
- 数组的遍历
- for循环
var arr = [3]int{ 1, 2, 3} // 会依次输出1, 2, 3 <
- for循环