前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >整型及相关运算符 【Go语言圣经笔记】

整型及相关运算符 【Go语言圣经笔记】

作者头像
Steve Wang
发布2021-12-06 16:16:27
1K0
发布2021-12-06 16:16:27
举报
文章被收录于专栏:从流域到海域从流域到海域

整型及相关运算符

Go语言的数值类型包括几种不同大小的整数浮点数复数。每种数值类型都决定了对应的大小范围和是否支持正负符号。让我们先从整数类型开始介绍。

Go语言同时提供了有符号和无符号类型的整数运算。Go中有int8int16int32int64四种截然不同大小的有符号整数类型,同时对应uint8unit16uint32uint64四种无符号整数类型。

Unicode字符rune类型是和int32等价的类型,通常表示一个Unicode的码点,这两个类型可以互换使用。byte类型也是uint8的等价类型,byte类型一般用于强调数值是一个原始的数据而不是小的整数。

最后,还有一种无符号的整数类型unitptr,没有指定具体的bit大小但是足以容纳指针。unitptr类型只有在底层编程时才需要,尤其是Go语言和C语言函数库或操作系统接口交互的地方。

不管具体的大小,intuintuintptr是不同类型的兄弟类型。intint32也是不同的类型,即使他们都是21bit,在需要将int当成int32类型的地方需要一个显式的类型转换操作,反之亦然。

有符号整数采用2的补码形式表示,也就是最高位bit位用来表示符号位,一个n-bit的有符号数的值域是从 -2^{n-1}

2^{n-1}-1 之间。无符号整数的所有bit位都用于表示非负数,值域是0到 2^{n-1} 。例如:int8类型整数的值域是-128~127,而uint8类型整数的值域是从0到255。

下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,按照优先级递减的顺序排列:

代码语言:javascript
复制
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||

&^: 将运算符左边数据相异的位保留,相同位清零。

代码语言:javascript
复制
fmt.Println(0&^0)  // 0
fmt.Println(1&^0)  // 1
fmt.Println(0&^1)  // 0
fmt.Println(1&^1)  // 0

特点:

  1. 如果右边位是0,则左边对应位保持不变
  2. 如果右边位是1,则左边位一定清零
  3. 功能和a&(^b)相同

二元运算符有五种优先级。在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序,使用括号也可以用于提升优先级,例如mask&(1<<28)

上表中前两行的运算符,例如+运算符还有一个与赋值相结合的对应运算符+=,可以简化赋值语句。

算术运算符+、-、*和/可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此-5%3和-5%-3结果都是-2。除法运算符/的行为则依赖于操作数是否全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。

一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。

如果原始的数值是有符号类型,而且最左边的bit位是1的话,那么最终结果可能是负的,例如int8的例子:

代码语言:javascript
复制
var u uint8 = 255
fmt.Println(u, u+1, u*u)  // 255 0 1

var i int8 = 127
fmt.Println(i, i+1, i*i)  // 127 -128 1

两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型(ture or false)。

事实上,布尔型、数字类型和字符串等基本类型都是可比较的,也就是说两个相同类型的值可以用==和!=进行比较。此外,整数、浮点数和字符串可以根据比较结果排序。许多其它类型的值可能是不可比较的,因此也就可能是不可排序的。对于我们遇到的每种类型,我们需要保证规则的一致性。

一元加法减法运算符:

代码语言:javascript
复制
+ // 一元加法 无实际效果
- // 负数 即负号 或者 符号取反

对与整数+x0+x的简写,-x0-x的简写;对于浮点数和负数,+x就是x-x则是x的负数。

位操作运算符,前4个操作运算符不区分有符号还是无符号:

代码语言:javascript
复制
&  // AND
|  // OR
^  // XOR
&^ // AND NOT
<< // 左移
>> // 右移

位操作运算符^作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;也就是说,它返回一个每个bit位都取反的数。位操作运算符&^用于按位置零(AND NOT):如果对应ybit位为1的话, 表达式z = x &^ y结果z的对应的bit位为0,否则z对应的bit位等于x相应的bit位的值。

下面的代码演示了如何使用位操作解释uint8类型值的8个独立的bit位。它使用了Printf函数的%b参数打印二进制格式的数字;其中%08b中08表示打印至少8个字符宽度,不足的前缀部分用0填充

代码语言:javascript
复制
var x uint8 = 1<<1 | 1<<5
var y uint8 = 1<<1 | 1<<2

fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
fmt.Printf("%08b\n", y) // "00000110", the set {1, 2}

fmt.Printf("%08b\n", x&y)  // "00000010", the intersection {1}
fmt.Printf("%08b\n", x|y)  // "00100110", the union {1, 2, 5}
fmt.Printf("%08b\n", x^y)  // "00100100", the symmetric difference {2, 5}
fmt.Printf("%08b\n", x&^y) // "00100000", the difference {5}

for i := uint(0); i < 8; i++ {
    if x&(1<<i) != 0 { // membership test
        fmt.Println(i) // "1", "5"
    }
}

fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6}
fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}

x<<nx>>n移位运算中,决定了移位操作的bit数部分必须是无符号数;被操作的x可以是有符号数或无符号数。算术运算上,一个x<<n左移运算等价于乘上 2^n ,一个x>>n右移运算等价于除以 2^n

左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。基于此,如果想使用移位最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。

尽管Go语言提供了无符号数的运算,但即使数值本身不可能出现负数,我们还是倾向于使用有符号的int类型,就像数组的长度那样,虽然使用uint无符号类型似乎是一个更合理的选择。事实上,内置的len函数返回一个有符号的int,我们可以像下面例子那样处理逆序循环(笔者觉得数组的长度使用int型是基于长度不可能是一个负值这样的自然逻辑)。

代码语言:javascript
复制
medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
    fmt.Println(medals[i]) // "bronze", "silver", "gold"
}

无符号数往往只有在位运算或其他特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或是哈希加密操作等,它们通常不用于仅仅是表达非负数量的场合

一般来说,需要一个显式的转换将一个值从一种类型转化为另一种类型,并且算术和逻辑运算的二元操作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题,而且也使得程序容易理解。

类型不匹配是一种常见的代码错误:

代码语言:javascript
复制
var apples int32 = 1
var oranges int16 = 2
var compote int = apples + oranges // compile error

// invalid operation: apples + oranges (mismatched types int32 and int16)

常见的修改方式即显式转换为同一类型:

代码语言:javascript
复制
var compote = int(apples) + int(oranges)

对于每种类型T,如果转换允许的话,类型转换操作T(x)x转换为T类型。许多整数之间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大的整数类型转为一个小的整数类型,或者是将一个浮点数(高精度)转为整数(低精度),可能会改变数值或丢失精度:

代码语言:javascript
复制
f := 3.141 // a float64
i := int(f)
fmt.Println(f, i) // "3.141 3"
f = 1.99
fmt.Println(int(f)) // "1"

浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。你应该避免对可能会超出目标类型表示范围的数值做类型转换,因为截断的行为可能依赖于具体的实现:

代码语言:javascript
复制
f := 1e100  // a float64
i := int(f) // 结果依赖于具体实现 (笔者注:int类型的实现方式会影响截断的方式,进而影响转换后f的值。)

任何大小的整数字面值都可以用以0开始的八进制格式书写,例如0666;或用以0x或0X开头的十六进制格式书写,例如0xdeadbeef。十六进制数字可以用大写或小写字母。如今八进制数据通常用于POSIX操作系统上的文件访问权限标志,十六进制数字则更强调数字值的bit位模式。

可以用%d、%o或%x参数控制输出的进制格式:

代码语言:javascript
复制
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF

请注意fmt的两个使用技巧:

  • 通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的[1]副词告诉Printf函数再次使用第一个操作数。
  • %后的#副词告诉Printf在用%o%x%X输出时生成00x0X前缀。

字符类型对应一个整型值,通过一个单引号直接包含对应字符。最简单的例子是ASCII中类似 `a` 这样的写法,可以通过转义的数值来表示任意的Unicode码点对应的字符,例子如下:

代码语言:javascript
复制
ascii := 'a'
unicode := '国'
newline := '\n'
fmt.Printf("%d %[1]c %[1]q\n", ascii)   // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
fmt.Printf("%d %[1]q\n", newline)       // "10 '\n'"

字符使用%c格式控制字符打印,也可以使用%q打印带单引号的字符。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-06-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 整型及相关运算符
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档