当前位置: 首页 > article >正文

【Go语言圣经3.1】

目标

  • 复习二进制基础:包括如何进行二进制、八进制、十六进制转换,理解计算机如何存储数据。
  • 了解两的补码:这是理解有符号数表示及溢出的关键。
  • 掌握类型系统与类型转换:明确不同类型间不能直接运算、需要显式转换的原因,以及可能引发的数据截断问题。
  • 多写一些简单的位运算代码:通过实践掌握位掩码、移位操作和按位逻辑运算在解决实际问题(如集合操作、数据压缩、加密等)中的应用。
  • 关注格式化输出:学会使用 fmt.Printf 的各种格式化动词,帮助你更直观地看到数据的二进制和其他进制表示形式。

概念

  1. 明确大小的整数类型

    • 有符号整数
      • int8:8 位,范围 -128 到 127
      • int16:16 位,范围 -32768 到 32767
      • int32:32 位,范围 -2³¹ 到 2³¹-1
      • int64:64 位,范围 -2⁶³ 到 2⁶³-1
    • 无符号整数
      • uint8:8 位,范围 0 到 255
      • uint16:16 位,范围 0 到 65535
      • uint32:32 位,范围 0 到 2³²-1
      • uint64:64 位,范围 0 到 2⁶⁴-1
  2. 平台相关的整数类型

    • int 与 uint
      这两种类型的大小由编译器根据目标平台确定,可能是 32 位或 64 位。由于其大小不固定,所以在跨平台代码中要特别小心,不应假设其具体大小。
  3. 特殊类型:rune、byte、uintptr

    • rune

      实际上是 int32 的别名,主要用于表示 Unicode 字符。
      使用 rune 可以清晰地表明变量的用途是存储一个字符,而非一个简单的数字。

      • Unicode 是一个国际标准,旨在为全球所有语言中的字符分配唯一的数字编号,从而实现跨语言、跨平台的文本处理。
      • 每个字符在 Unicode 标准中都有一个唯一的码点,通常用 U+ 后跟十六进制数字表示。例如,大写字母 A 的码点是 U+0041,中文字符“国”的码点是 U+56FD
      • UTF-32:每个码点固定使用 4 个字节,直接对应 Go 中的 rune 类型(即 int32)。这种方式虽然内存使用较多,但每个字符的长度一致,处理起来比较简单
    • byte

      uint8 的别名,用于强调该数值代表一个原始字节,而不是一个小整数。

    • uintptr

      是一种无符号整数类型,足够大到可以存放指针值,主要用于底层编程(如与 C 语言库、操作系统接口交互)。

  4. 数值的二进制表示:计算机内部一切以二进制存储,一个整数由若干 bit 组成。

  5. 两的补码表示

    • 有符号数

      在大多数编程语言中,包括 Go,负数采用两的补码表示。对于一个 n-bit 的数,其值域是:

      • 最小值:-2^(n-1)
      • 最大值:2^(n-1)-1
        例如,int8 的范围是 -128 到 127。
    • 无符号数

      所有位都用于表示数值,范围为 0 到 2^n - 1。例如 uint8 的范围为 0 到 255。

  6. Go 语言中提供的运算符包括:

    • 算术运算符+、、、/%
    • 逻辑运算符&&||
    • 比较运算符==!=<<=>>=
    • 位运算符&(与)、|(或)、^(异或,亦可用作一元按位取反)、&^(按位清零)、<<(左移)、>>(右移)
  7. 运算符结合赋值

    • 对于算术运算符,Go 还提供复合赋值形式,如 +=-= 等,这使得代码更加简洁。
  8. 整数除法与模运算

    • 整数除法

      当两个整数相除时,结果向零截断(丢弃小数部分),例如 5/4 得到 1

    • 模运算(%)

      仅适用于整数,且在 Go 中模运算的结果的符号与被模数相同,即使除数为负数,-5 % 3-5 % -3 均得 -2

  9. 基本位运算

    • &:按位与
    • |:按位或
    • ^:按位异或(作为一元运算符时表示取反)
    • &^:按位清零(如果 y 对应位为 1,则结果为 0,否则为 x 对应位的值)
  10. 移位操作

    • 左移(<<)
      相当于乘以 2 的 n 次方,左边的空位用 0 填充。
    • 右移(>>)
      对无符号数右移同样用 0 填充;而有符号数右移时会根据符号位来决定是否填充 0 或 1(这称为符号扩展)。
  11. 字符表示

    • 字符面值用单引号表示,例如 'a''国'
    • 字符可以使用 %c 进行打印,或者 %q 打印成带单引号的格式。

要点

溢出 wrap-around

如果运算结果超出变量能表示的范围,多出的高位会被截断。
例如,对于 uint8 类型

var u uint8 = 255
fmt.Println(u, u+1, u*u) // 输出 "255 0 1"

类型转换与类型安全

Go 语言强调类型安全,不同的整数类型不能直接进行运算,即使它们底层可能具有相同的位数。例如:

var apples int32 = 1
var oranges int16 = 2
// var compote int = apples + oranges // 编译错误,类型不匹配
var compote = int(apples) + int(oranges) // 显式转换后才能相加
  • 静态类型语言要求显式转换
    • 在 Go 语言中,转换语法为 T(x),其中 T 是目标类型,x 是要转换的值。
    • 隐式转换可能会在不经意间导致数据丢失或逻辑错误。例如,将一个较大范围的数据隐式转换成较小范围的类型,可能截断高位数据。显式转换要求开发者明确表明“我知道转换可能带来风险”,从而降低错误风险。
    • 静态类型语言(如 Go)的编译器在编译时就会检查类型一致性。如果允许隐式转换,编译器可能无法捕捉到所有潜在的类型错误。
  • 从大类型转换到小类型时,可能会丢失数据或精度。
  • 浮点数转换为整数时,会截断小数部分并向零截断,例如 int(3.141)3

数值的进制格式

使用 fmt.Printf 时,可以用不同格式化动词控制输出格式:

  • %d:十进制
  • %o:八进制
  • %x / %X:十六进制
  • %08b:二进制,至少 8 位,不足补 0

例如:


o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // 输出 "438 666 0666"

这里 %[1] 表示重复使用第一个操作数,# 前缀表示在八进制和十六进制输出时添加前缀(如 0 或 0x)。

语言特性

总结与补充

两的补码

你有一个数字钟表,比如钟表上标有数字0到11。当你往前走时,比如从10走2步,就会变成0。两的补码就利用了这种“绕圈”的特性,把数字“绕回来”来表示负数。

“两的补码”这个名字来源于它表示负数的方法:在二进制中,用一种方法把一个正数变成它的负数,而这个方法实际上就是用“2的幂”来做补数。因为2^n的基础是2,所以这种方法叫做两的补码

假设我们用8位二进制来表示数字,负数的表示其实就是“2^8减去正数”。比如-5此时是256-5

用反转加1的方法,实际上就是找出一个数,它和正数相加正好等于2^n,这个数我们就称为正数的“补数”。

设我们用 n 位二进制数表示一个整数 x,那么所有 n 位全为 1 的数就是 2ⁿ–1。在这种表示下,对 x 取反(即每一位 0 与 1 互换)得到的数,就是:

~x = (2ⁿ – 1) – x

接下来,我们对 ~x 加 1:

~x + 1 = (2ⁿ – 1 – x) + 1 = 2ⁿ – x

因此,有:

x + (~x + 1) = x + (2ⁿ – x) = 2ⁿ

想象你有一盒糖果,一共有256颗糖(这就像2^8)。如果你有5颗糖,那么剩下的糖数(256 - 5)就是你没有的糖。两的补码就像告诉你,“为了表示-5,我们实际上在表示‘从256里拿走5颗糖’的结果。

用二进制表示集合/构造集合(能够去重)

  • 集合与位的对应关系

    想象有一个小盒子,每个盒子中的位置(位)对应一个元素。如果某个位置为1,就表示集合中包含对应的元素;如果为0,则不包含。

    • 例如,数字 00100010 表示第1位和第5位为1,可以看作集合 {1, 5}。
  • 构造集合

    使用位运算“或”(|)可以将多个单一元素“合并”为一个集合:

    
    var x uint8 = 1<<1 | 1<<5  // 1<<1 产生 00000010,1<<5 产生 00100000,或运算后得 00100010
    

    同理,

    var y uint8 = 1<<1 | 1<<2  // 结果为 00000110,表示集合 {1, 2}
    

测试集合成员资格

  • 位与运算

    用“与”(&)运算符可以检测两个集合的交集,或者检测一个特定元素是否在集合中。

    • 在示例中,x & y 对应的运算是:
      • x00100010
      • y00000110
      • 逐位与运算后,只有第二位(从右数,位1)同时为1,所以结果为 00000010,代表集合交集 {1}。
  • 测试某个元素是否存在

    如果要检测集合 x 是否包含元素 n,可以这样写:

    
    if x & (1 << n) != 0 {
        // 表示元素 n 存在于集合 x 中
    }
    

    这里 (1 << n) 就构造了只有第 n 位为1的掩码,和 x 进行与运算后,如果结果不为0,则说明该位在 x 中也为1,即元素 n 存在。


http://www.kler.cn/a/590537.html

相关文章:

  • Python中的Collections库
  • pyqt 上传文件或者文件夹打包压缩文件并添加密码并将密码和目标文件信息保存在json文件
  • 【从零开始学习计算机科学】数据库系统(十一)云数据库、NoSQL 与 NewSQL
  • 面向机器人领域 | AKM Delta-Sigma数字输出无磁芯电流传感器
  • vue/H5的日历组件可简单定制
  • [网络][tcp协议]:tcp报头
  • UI设计公司:数据大屏设计提升用户体验的方法
  • 【软考-架构】5.2、传输介质-通信方式-IP地址-子网划分
  • OpenGL ES 入门指南:从基础到实战
  • golang-struct结构体
  • C# 使用Markdown2Pdf把md文件转换为pdf文件
  • centos 安装pip时报错 Cannot find a valid baseurl for repo: centos-sclo-rh/x86_64
  • SQLMesh系列教程:利用date_spine宏构建日期序列实践指南
  • hbuiderx的sass编译器报dart-sass等错误的解决方法
  • 美团AI面试总结
  • 密码学研究热点
  • obeaver 连接oracle 库 模式乱码
  • 基于 GEE 利用 Sentinel-1 双极化数据计算 SDWI 指数实现逐月提取水域面积
  • 漏洞预警 | Apache OFBiz 服务端模板注入漏洞(CVE-2025-26865)
  • ChatGPT and Claude国内使用站点