---Tony Bai · Go语言第一课
在编程语言中,为了方便操作内存特定位置的数据,我们用一个特定的名字与位于特定位
置的内存块绑定在一起,这个名字被称为变量。变量所绑定的内存区域是要有一个明确的边界的。
Go 是静态语言,所有变量在使用前必须先进行声明。声明的意义在于告诉编译器该变量可以操作的内存的边界信息,而这种边界通常又是由变量的类型信息提供的。
变量分类:
Go 语言的变量可以分为两类:
- 一类称为包级变量 (package varible),也就是在包级别可见的变量。如果是导出变量(大写字母开头),那么这个包级变量也可以被视为全局变量;(包级变量只能使用带有 var 关键字的变量声明形式,不能使用短变量声明形式.)
- 另一类则是局部变量 (local varible),也就是 Go 函数或方法体内声明的变量,仅在函数或方法体内可见。
代码块与作用域
Go 语言中的代码块是包裹在一对大括号内部的声明和语句序列,如果一对大括号内部没有任何声明或其他语句,我们就把它叫做空代码块。
一个标识符的作用域就是指这个标识符在被声明后可以被有效使用的源码区域.
按照 Go 语言定义,一个标识符要成为导出标识符需同时具备两个条件:一是这个标识符声明在包代码块中,或者它是一个字段名或方法名;二是它名字第一个字符是一个大写的Unicode 字符。
变量遮蔽
变量遮蔽问题的根本原因,就是内层代码块中声明了一个与外层代码块同名且同类型的变量,这样,内层代码块中的同名变量就会替代那个外层变量,参与此层代码块内的相关计算,我们也就说内层变量遮蔽了外层同名变量。
基本数据类型
Go 语言的类型大体可分为基本数据类型、复合数据类型和接口类型这三种.
平台相关整型,它们的长度会根据运行平台的改变而改变。Go语言原生提供了三个平台相关整型,它们是 int、uint 与 uintptr
由于这三个类型的长度是平台相关的,所以我们在编写有移植性要求的代码时,千万不要强依赖这些类型的长度.
整型的溢出问题
如果某个整型因为参与某个运算,导致结果超出了这个整型的值边界,我们就说发生了整型溢出的问题。学习整型时你要特别注意,每个整型都有自己的取值范围和表示边界,一旦超出边界,便会出现溢出问题。
一个浮点数被分为符号位、阶码与尾数三个部分。
浮点数在内存中的二进制表示分三个部分:符号位、阶码(即经过换算的指数),以及尾数。这样表示的一个浮点数,它的值等于:
我们首先来看单精度(float32)与双精度(float64)浮点数在阶码和尾数上的不同
我们来看看如何将一个十进制形式的浮点值 139.8125,转换为 IEEE 754 规
定中的那种单精度二进制表示。
步骤一:我们要把这个浮点数值的整数部分和小数 部分,分别转换为二进制形式(后缀 d
表示十进制数,后缀 b 表示二进制数):
这样,原浮点值 139.8125d 进行二进制转换后,就变成 10001011.1101b
步骤二:移动小数点,直到整数部分仅有一个 1,也就是 10001011.1101b =>
1.00010111101b。我们看到,为了整数部分仅保留一个 1,小数点向左移了 7 位,这样
指数就为 7,尾数为 00010111101b
步骤三:计算阶码
IEEE754 规定不能将小数点移动而得到的指数,一直填到阶码部分,指数到阶码还需要一
个转换过程。对于 float32 的单精度浮点数而言,阶码 = 指数 + 偏移值。偏移值的计算公
式为 2^(e-1)-1,其中 e 为阶码部分的 bit 位数,这里为 8,于是单精度浮点数的阶码偏移
值就为 2^(8-1)-1 = 127。这样在这个例子中,阶码 = 7 + 127 = 134d = 10000110b
float64 的双精度浮点数的阶码计算也是这样的。
步骤四:将符号位、阶码和尾数填到各自位置,得到最终浮点数的二进制表示。尾数位数
不足 23 位,可在后面补 0
这样,最终浮点数 139.8125d 的二进制表示就为
0b_0_10000110_00010111101_000000000000
但日常使用中我们尽量使用 float64,这样不容易出现浮点溢出的问题。
math.MaxFloat32表示float32能表示的最大数值,大约是 3.4e38;对应的math.MaxFloat64常量大约是1.8e308
Go 提供的类型定义语法,来创建自定义的数值类型,我们可以通过 type关键字基于原生数值类型来声明一个新类型。type MyInt int32
根据 Go 的类型安全规则,我们无法直接让它们相互赋值,或者是把它们放在同一个运算中直接计算,这样编译器就会报错。我们需要借助显式转型再进行运算
我们也可以通过 Go 提供的类型别名(Type Alias)语法来自定义数值类型。和上面使用
标准 type 语法的定义不同的是,通过类型别名语法定义的新类型与原类型别无二致,可以
完全相互替代。
type MyInt = int32
var n int32 = 6
var a MyInt = n // ok
通过类型别名定义的 MyInt 与 int32 完全等价,所以这个时候两种类型就是同一种类型,不再需要显式转型,就可以相互赋值。
字符串类型
Go 语言规定,字符串类型的值在它的生命周期内是不可改变的。
string 类型其实是一个“描述符”,它本身并不真正存储字符串数据,而仅是由一个指向底层存储的指针和字符串的长度字段组成的。
Go 提供的内置函数 len,我们只能获取字符串内容的长度(字节个数)。获取字符串中字符个数更专业的方法,是调用标准库 UTF-8 包中的RuneCountInString 函数。
Go 原生支持通过 +/+= 操作符进行字符串连接, 除此之外 Go 还提供了 strings.Builder、strings.Join、fmt.Sprintf 等函数来进行字符串连接操作.
**常规 for 迭代与 for range 迭代字符串所得到的结果不同,常规 for 迭代采用的是字节视
角;而 for range 迭代采用的是字符视角。**
常量:
Go 常量一旦声明并被初始化后,它的值在整个程序的生命周期内便保持不变。Go 语言引入 const 关键字来声明常量.
iota 是 Go 语言的一个预定义标识符,它表示的是 const 声明块(包括单行声明)中,每个常量所处位置在块中的偏移值(从零开始)。
从定长数组到变长切片
切片类型
type slice struct {
array unsafe.Pointer
len int
cap int
}
array: 是指向底层数组的指针;
len: 是切片的长度,即切片中当前元素的个数;
cap: 是底层数组的长度,也是切片的最大容量,cap 值永远大于等于 len 值。
如何创建切片:
方法一:通过 make 函数来创建切片,并指定底层数组的长度。
方法二:采用 array[low : high : max] 语法基于一个已存在的数组创建切片。这种方式被称为数组的切片化.
切片与数组最大的不同,就在于其长度的不定长,这种不定长需要 Go 运行时提供支持,这种支持就是切片的“动态扩容”。数组的长度是确定的,而切片,我们可以理解为一种“动态数组”,它的长度在运行时是可变的 .
Map 类型
map 类型对 value 的类型没有限制,但是对 key 的类型却有严格要求,因为 map 类型要保证 key 的唯一性。Go 语言中要求,key 的类型必须支持“==”和“!=”两种比较操作符。
map 类型,因为它内部实现的复杂性,无法“零值可用”。所以,如果我们对处于零值状态的 map 变量直接进行操作,就会导致运行时异常(panic),从而导致程序进程异常退出.
使用 make 为 map 类型变量进行显式初始化,map 类型的容量不会受限于它的初始容量值,当其中的键值对数量超过初始容量后,Go 运行时会自动增加 map 类型的容量,保证后续键值对的正常插入.
Go 语言的 map 类型支持通过用一种名为“comma ok”的惯用法,进行对某个 key 的查询。
m := make(map[string]int)
v, ok := m["key1"]
if !ok {
// "key1"不在map中
} /
/ "key1"在map中,v将被赋予"key1"键对应的value
在 Go 语言中,请使用“comma ok”惯用法对 map 进行键查找和键值读取操作.
在 Go 中,我们需要借助内置函数delete 来从 map 中删除数据。使用 delete 函数的情况下,传入的第一个参数是我们的 map 类型变量,第二个参数就是我们想要删除的键.
m := map[string]int {
"key1" : 1,
"key2" : 2,
} f
mt.Println(m) // map[key1:1 key2:2]
delete(m, "key2") // 删除"key2"
fmt.Println(m) // map[key1:1]
对同一 map 做多次遍历的时候,每次遍历元素的次序都不相同
- 不要依赖 map 的元素遍历顺序;
- map 不是线程安全的,不支持并发读写;
- 不要尝试获取 map 中元素(value)的地址。
map深度解析: https://www.qcrao.com/2019/05...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。