Pointer

运算和指针

上章节我们了解了,变量和常量的定义与使用,本章节我们继续开车,目标如下:

  • Go 中运算符(算术运算、逻辑运算、比较运算)
  • Go 中指针以及使用
  • New 函数的简单使用
  • 数值型间的类型转换

运算符

比较运算符

我们先来看下 Go 中二元比较运算符(用于两数比较,结果是布尔类型):

var n1 int = 1
var n2 int = 2

以上面 n1 和 n2 为例,列出阐述比较运算符如下:

符号描述示例结果
==等于n1 == n2false
!=不等于n1 != n2true
<小于n1 < n2true
<=小于等于n1 <= n2true
>大于n1 > n2false
>=大于等于n1 >= n2false

事实上,与其他编程语言没有什么差别。

注意:不相同类型(布尔型、数字类型和字符串)的值不可以进行比较

示例:

var num1 int16 = 1
var num2 int16 = 2
var num3 int32 = 3

fmt.Println(num1 > num2) // false
fmt.Println(num1 > num3) // mismatched types int16 and int32

不同类型间不能进行比较,下面将介绍 数值类型转换,转换为同类型后可进行比较和运算。

算术运算符

以为 num1 和 num2 为例,演示如下算术运算符:

var num1 int = 4
var num2 int = 2
符号描述示例
+相加num1 + num2 = 6
-相减num1 - num2 = 4
*相乘num1 * num2 = 8
/相除num1 / num2 = 2
%取模num1 % num2 = 0
++自增num1 ++ 结果:5
自减num2 – 结果:1
点击预览完整代码
package main
import "fmt"
func main() {
  var num1 int = 4
  var num2 int = 2

  fmt.Println(num1 + num2) // 6
  fmt.Println(num1 - num2) // 2
  fmt.Println(num1 * num2) // 8
  fmt.Println(num1 / num2) // 2
  fmt.Println(num1 % num2) // 0
  num1++
  fmt.Println(num1) // 5
  num2--
  fmt.Println(num2) // 1
}

位运算符

继续我们看一下 Go 中位运算符,位运算顾名思义是对整数在内存中的二进制位 bit 进行的操作:

符号描述示例(一个 bit 位为例)总结
&位运算 AND0&0=0、0&1=0、1&1=1、1&0=0两数为 1,结果才为 1
位运算 OR0丨0=0、0丨1=1、1丨1=1、1丨0=1两数有一个为 1,结果为 1
^异或 XOR0^0=0、0^1=1、1^1=0、1^0=1相同为 0,不相同为 1(一元运算符时:按位取反)
&^位清空 AND NOT0&^0=0、 0&^1=0、 1&^0=1、1&^1=0将运算符左边数据相异的位保留,相同位清零
«左移0000 0001«2=0000 0100右边空出的位用 0 填补(若左移溢出,舍弃高位)
»右移1111 1111»2=0011 1111左边空出的位用 0 填补(负数用 1)(低位右移溢出,舍弃该位)
点击预览完整代码
package main

import "fmt"

// 位运算符
func main() {
  var x uint8 = 1
  var y uint8 = 255
  fmt.Printf("%08b %d\n", x, x)    // 00000001 1
  fmt.Printf("%08b %[1]d\n", y)    // 11111111 255
  fmt.Printf("%08b %[1]d\n", x&y)  // 00000001 1
  fmt.Printf("%08b %[1]d\n", x|y)  // 11111111 255
  fmt.Printf("%08b %[1]d\n", x^y)  // 11111110 254
  fmt.Printf("%08b %[1]d\n", x&^y) // 00000000 0
  fmt.Printf("%08b %[1]d\n", x<<2) // 00000100 4
  fmt.Printf("%08b %[1]d\n", y>>2) // 00111111 63
  // % 之后的[1] 意思是告诉 Printf 函数再次使用第一个数,省去了一个参数
}

逻辑运算符

定义 bool 类型,变量 b1 和 b2 为例:

var b1 bool = true
var b2 bool = false
符号描述示例
&&逻辑于b1 && b2 = false
丨丨逻辑或b1 丨丨 b2 = true
!逻辑非!b1 = false
点击预览完整代码
package main

import "fmt"

func main() {
  var b1 bool = true
  var b2 bool = false

  fmt.Println(b1 && b2) // true
  fmt.Println(b1 || b2) // false
  fmt.Println(!b1)      // false
}

运算符优先级

Go 中运算符(算术运算、逻辑运算、比较运算)按照由上至下代表优先级由高到低(二元运算符从左至右)如下:

优先级运算符
5(高)* / % « » & &^
4+ - 丨 ^
3== != < <= > >=
2&&
1(低)丨丨

小测一下,请问 a 和 b 值分别是多少:

a := 4 + 3*2 - 1
fmt.Println(a)
b := (4+3)*2 - 1
fmt.Println(b)
点击看答案
9
13

注意:可以通过 () 改变其运算优先级

值域范围

说完运算符,我们继续说一下数值类型的大小范围,如果在编码时不明白其类型的范围,那定义都是个问题:

Go 中提供了有符号和无符号类型的整数运算,分别有 int8、int16、int32、int64,分别对应 8、16、32、64bit大小的有符号整数,对应无符号类型的分别为 uint8、uint16、uint32、uint64。

  • 无符号 n-bit 的值范围,0 到 2^n,例如:uint8 值范围是 0 到 255
  • 有符号整数最高位 bit 位用作表示符号位,所以一个 n-bit 的有符号数的值域是从 -2^{n-1} 到 -1,例:int8 值范围是 -128 到 127

下面给出整数的取值范围表:

类型字节最小值最大值
int81-128127
uint810255
int162-3276832767
uint162065535
int324-21474836482147483647
uint32404294967295
int648-92233720368547758089223372036854775807
uint648018446744073709551615

如果计算结果溢出,超出的高位 bit 位部分将被丢弃,如果是有符号数值最左边为 1 那么溢出的结果可能是负的,如下:

var u uint8 = 255
fmt.Println(u, u+1, u*u) // 255 0 1
fmt.Printf("%08b %08b\n", 255, 0) // 11111111 00000000

var i int8 = 127
fmt.Println(i, i+1, i*i) // 127 -128 1
fmt.Printf("%08b %08b\n", 127, -128) // 01111111 -10000000

Printf 函数的 %b 参数打印二进制格式的数字,其中 %08b 中 08 表示打印至少 8 个字符宽度,不足的前缀用 0 填充。

注意:知道了各个数值类型大小范围,再编码中为了防止变量溢出,选择合适的类型相当重要。

指针

Go 中的指针比较简单,使用 & 符号获取某个变量的内存地址,如下:

var m int8 = 100
var n *int8 = &m

fmt.Printf("%v %v %v %v\n", m, &m, &n, *n) // 100 0xc00001c09a 0xc00000e028 100

声明变量变量 m 为 int8 类型,使用 & 符号获取 m 变量指针赋值给 n (*int8) 指针类型

为了方便理解,我画了如下图

指针内存图

变量前使用 * 符号,表示获取指针指向的底层的值,如下:修改 n 变量的值为 10

*n = 10
fmt.Println(m, *n, n)   // 10 10 0xc00001c09a

因为 n 为指向 m 的指针,固然 m 的值自然也被修改为 10

n 为指向 m 的指针,所以 n 打印值为 0xc00001c09a*n 意思是获取该指针对应的具体值。

New 函数

new 函数 Go 中语法糖,因为返回值是指针类型,这里我们以数值类型为例(其他复合类型的可以通过 new 声明)

例如:我们使用 New 声明一个 int 类型指针的变量 p ,并修改其值。代码如下:

p := new(int)
fmt.Printf("%T %v \n", p, p) // *int 0xc0000b4008
fmt.Println(*p) // 0

*p = 2
fmt.Println(*p) // 2

数值类型间转换

在强类型语言中,不同的数值类型之间是不能进行比较和运算的(比如:int 和 int8),如果我们希望可以进行运算,那么就需要类型转换了,这里我们暂且只讨论数值类型间的转换。

比如,计算 int32 类型与 int16 类型之和,下代码:

var apples int32 = 1
var oranges int16 = 2
var compote int = apples + oranges // (mismatched types int32 and int16)

如果想完成此运算,可以统一转为一个相同的数据类型,因为我们期待的 compote 是 int 类型,那么将统一转为 int 然后再计算其结果,代码如下:

var compote int = int(apples) + int(oranges)
fmt.Println(compote)    // 3

如果是浮点数转为整数,可能会丢失精度:

f := 1.56
i := int(f)
fmt.Println(f, i) // 1.56 1

f = 1.99
fmt.Println(int(f)) // 1