基本类型

Written by with ♥ on in Go

int

// 数字字面量默认为 int 类型
i := 1
fmt.Printf("%T %d %064b \n", i, i, i) // int 1 0000000000000000000000000000000000000000000000000000000000000001

// 最大有符号整数 2^63 - 1
i := 1 << 63 - 1
fmt.Printf("%T %d %064b \n", i, i, i) // int 9223372036854775807 0111111111111111111111111111111111111111111111111111111111111111

// 最小 64 位整数 int(1 << 63) -0x8000000000000000
-1000000000000000000000000000000000000000000000000000000000000000 -9223372036854775808

// 最大 64 位整数 int(1 << 63 - 1) 0x7FFFFFFFFFFFFFFF
0111111111111111111111111111111111111111111111111111111111111111 9223372036854775807

// 最大 64 位无符号整数 uint 0xFFFFFFFFFFFFFFFF
1111111111111111111111111111111111111111111111111111111111111111 18446744073709551615


1 << 5 // int 100000
1 << 5 - 1 // 11111

16 进制表示法

var x int
var y uint

// 直接求出字面量对应的无符号整数后赋值,赋值给有符号整数可能会产生 overflow 错误
x = 0x7FFFFFFFFFFFFFFF
y = 0xFFFFFFFFFFFFFFFF

负数如何表示

负数以原码的补码形式表示。

最高位表示符号:0 为正,1 为负

原码

数字按绝对值转换成二进制,负数在最高位补 1。

也就是用符号位表示带符号数。

反码

正数的反码与原码相同,负数的反码为除符号位以外全部取反

补码

补码表示法:

正数的补码与原码相同,负数的补码为原码的反码然后加 1。

例如 -5:

原码:10000000 00000000 00000000 00000101

反码:11111111 11111111 11111111 11111010

加 1 得到最终的编码:11111111 11111111 11111111 11111011

-128

负数最大值是特殊情况,转换成原码时最高位的 1 有两层含义,符号位和数值位。

对其取反码会将符号位也一起取反,得到反码 01111111,加一得到补码 10000000

参考资料:

float

浮点数的核心概念是精度:

func main() {
	var n float64 = 0
	for i := 0; i < 1000; i++ {
		n += 0.01
	}
	fmt.Println(n)
}

// 9.999999999999831

1000 个 0.01 相加不等于 10。

IEEE754

IEEE754 国际标准定义 32 位和 64 位浮点数的二进制存储方式,两张方式提供不同的计算精度。

  • float32:小数点后 6 位精度
  • float64:小数点后 15 位精度

Example

Equal

浮点数比较不建议使用 ==,可以使用函数判断精度。

import "math"

// p 为自定义精度
func IsEqual(f1, f2, p float64) bool {
    return math.Fdim(f1, f2) < p
}

bool

Bool 类型不接受其他类型的值,不支持强制类型转换,其他类型不能当布尔值使用。

byte

先看源码中的解释:

byte is an alias for uint8 and is equivalent to uint8 in all ways. It is used, by convention, to distinguish byte values from 8-bit unsigned integer values.

type byte = uint8

byte 其实只不过是 uint8 的一个别名,两者之间任何时候都是互相等价的,逻辑上代表一个字节。

string

string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.

根据源码中的解释:

  • string 本身就是一堆字节(8-bit bytes)集合(slice);
  • 默认使用 UTF-8 编码文本,但不一定是 UTF-8
  • 可以为空,但不能为 nil,零值为空字符串 ""
  • 值不可变。函数调用均返回新的字符串,创建 string 会创建新的底层 bytes 也就是 []byte,频繁追加场景可以使用 stirngs.Builder,频繁修改场景可以使用 bytes.Buffer
  • 使用索引号访问的是对应位置的字节(byte) s[i],不是 Unicode 字符;
  • 不能获取对应位置字节元素指针 &s[i]
  • 不可变类型,无法修改字节数组(只能复制一份 []byte 修改后转回来);
  • 字节数组尾部不包含 NULL
type byte = uint8

type rune = int32

// 底层结构
struct String {
    byte* str; // 指向字节数组的指针
    intgo len; // 长度
}

在底层,一个 string 值的内容会被存储到一块连续的内存空间中。同时,这块内存容纳的字节数量也会被记录下来,并用于表示该 string 值的长度。

可以把这块内存的内容看成一个字节数组,而相应的string值则包含了指向字节数组头部的指针值。如此一来,我们在一个 string 值上应用切片表达式,就相当于在对其底层的字节数组做切片。

另外,我们在进行字符串拼接的时候,Go 语言会把所有被拼接的字符串依次拷贝到一个崭新且足够大的连续内存空间中,并把持有相应指针值的 string 值作为结果返回。

互相转换

string -> []byte

[]byte(string)

[]byte -> string

string([]byte)

// OR

func bytesToString(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

特性

s := "test"

// 双引号和单引号字节的区别
// 单引号 int32 类型,即四字节 32 位整数
fmt.Printf("双引号类型:%T 单引号类型:%T\n", "0", '0') // string int32

// 字节转数字
// '1' = 49 (uint8)
fmt.Println('1' - '0') // 1

// string 转 []byte
// 可以看出在新的内存地址复制了一份新的
b := []byte(s)
fmt.Printf("%p\n", b) // 0xc420016080
fmt.Printf("%p\n", &s) // 0xc42000e1d0

// stirng 子串(切片)
// 指向原字符串,仅修改指针和⻓度属性
s[1:] // "est"

// 返回 byte
s[1]

遍历

// 遍历字符串
s := "test 中文"

// rune range 循环
// Unicode 标准使用术语`代码点`来用单个值表示一个字符
// 比如 16 进制值 \x2318 的代码点,U+2318 表示符号⌘
// rune 就是 Unicode 的代码点
// range 循环在每次迭代时,解码一个 UTF-8 编码的符文
// range 得到 rune 对应的 字符开始的字节位置(非0,1,2 递增)和 Unicode 字符
for index, runeValue := range s {
    // value 为单个字节 int32 类型
    fmt.Printf("%d%T:%c%T\n", index, index, runeValue, runeValue)
}

修改

修改字符串需要先将其转换成 []rune[]byte,完成修改后再转换成 string

⽆论哪种转换,都会重新分配内存,并复制字节数组。

func main() {
    s := "abcd"
    bs := []byte(s)
    bs[1] = 'B'
    println(string(bs)) // aBcd
    
    u := "电脑"
    us := []rune(u)
    us[1] = '话'
    println(string(us)) // 电话
}

单引号字符常量表⽰的 Unicode Code Point,如 \uFFFF、\U7FFFFFFF、\xFF 对应 rune 类型(int32 的别名,4 字节表示)。

func main() {
    fmt.Printf("%T\n", 'a') // int32 (rune 是 int32 的别名)
    
    var c1, c2 rune = '\u6211', '们'
    println(c1 == '我', string(c2) == "\xe4\xbb\xac") // true true
}

Example

字符串中的字符,值是 rune 类型,即一个 UTF-8 字符。

// Byte String
func StringDemo() {
	// 底层是 []byte 的只读字符串,\x是特殊的字节表示方法
	const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"
	// 对 []byte 依旧适用
	//sample2 = []byte("\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98")

	fmt.Println("Println:")
	fmt.Println(sample)

	// 字节循环
	fmt.Println("Byte loop:")
	for i := 0; i < len(sample); i++ {
		fmt.Printf("%x ", sample[i])
	}
	fmt.Printf("\n")

	// %x 打印字节
	fmt.Println("Printf with %x:")
	fmt.Printf("%x\n", sample)

	// % x 打印字节时会有空格分开
	fmt.Println("Printf with % x:")
	fmt.Printf("% x\n", sample)

	// %q 打印出能识别的字符
	fmt.Println("Printf with %q:")
	fmt.Printf("%q\n", sample)

	// %+q 将能识别的字符打印成 Unicode 形式
	fmt.Println("Printf with %+q:")
	fmt.Printf("%+q\n", sample)

	// 字符串索引返回字节,一个字符串就是一堆字节
	// Go 代码中的所有字面量都是 UTF-8 编码的,在源代码编译时转换成字节形式

	const placeOfInterest = `⌘`

	fmt.Printf("plain string: ")
	fmt.Printf("%s", placeOfInterest)
	fmt.Printf("\n")

	fmt.Printf("quoted string: ")
	fmt.Printf("%+q", placeOfInterest)
	fmt.Printf("\n")

	fmt.Printf("hex bytes: ")
	for i := 0; i < len(placeOfInterest); i++ {
		fmt.Printf("%x ", placeOfInterest[i])
	}
	fmt.Printf("\n")
}