『The Go Programming Language』三、基本数据

四种基本类型:基础类型(basci type)、聚合类型(aggregate type)、引用类型(referemce type)、接口类型(interface type)。

基础类型:number、string、boolean。 聚合类型:arrary、struct。 引用类型:pointer、slice、map、function、channel。 接口类型:等待第七章

3.1 整数

有符号intint8int16int32 (rune)int64\
无符号uintuint8 (byte)uint16uint32uint64unitptr

后四对都指定了bit占用;int与uint在平台上的大小与原生的有符号整数/无符号整数相同,或等于该平台上的运算效率最高的值。

// Windows 11 23H2 x86-64
package main

import "unsafe"

func main() {
var a int = 1
println(unsafe.Sizeof(a)) // 8
var b int32 = 1
println(unsafe.Sizeof(b)) // 4
var c uint = 1
println(unsafe.Sizeof©) // 8
var d uint32 = 1
println(unsafe.Sizeof(d)) // 4
}

rune是int32的可替换使用同义词;rune常常用于指明值是以一个Unicode码点。 byte是uint8的可替换使用同义词;强调是原始数据而非量值。 uintptr大小不明确但可以存下整个指针。适合底层编程。 Unicode:32/8=4B,UCS-4或UTF-32标准。UCS-2标准是2B

表中的每一项都是单独一种类型,尽管int、uint、(以及uintptr)在某些平台下与int32/64等等是等长的,但是它们是不同类型,必须显示转换。

二元运算符:

  1. *、/、%、<<、>>、&、&^(位清空 AND NOT)
  2. +、-、|、^
  3. ==、!=、<、<=、>、>=
  4. &&
  5. ||

+-*/用于整、浮点、复数。%仅整数,结果与被除数一致。/的操作数如果都是整型则结果也是,否则不是。 ^作为二元运算符是亦或(XOR);作为一元运算符表示按位取反。 &^:z=x&^y -->> z=x&(^y) 在关系上是这样的。

位集(bitset):是一组布尔值,但是每个布尔值仅占用1位空间。

设有一个8位的位集var bset int8,从高到低编号为76543210,如果集合表示为{1,5}则有: bset = 1<<1 | 1<<5,这时底层表示为00100010,代表1、5号为ture。 为什么呢?因为如果bset=1就是赋值底层为00000001bset=1<<1是左移一位也就是00000010; 而或‘|’就是“链接”0000001000100000。 在此之后bset就成了位集(集合),可以进行逻辑运算(只是要按位运算)。

一般而言,无符号数只用于位运算符和特定算术运算符,如:实现bitset、解析俄日禁止二进制格式文件、散列或加密。而极少表示非负值。 例如:var i uint = 0如果这是循环的index就有可能出现i-1的情况,而现在的i-1变为最大值(2^n-1)-1,就会导致判断错误。 其他语言也这样(C++):

#include<iostream>
int main(){
    unsigned int a=0;
    std::cout<<a-1; // 4294967295
}
package main

func main() {
var a uint = 0
println(a - 1) // 18446744073709551615
}

# 这里缺少关于Printf()的谓词 #

3.2 浮点数

float32和float64,遵守IEEE 754标准。 Math包给出了浮点极限值:math.Maxfloat32调用。 十进制下float32和64的有效位大约是6位和15位。 关于Printf的谓词%g可以保证浮点的精度输出。同样保证精度的还有%e(有指数)、%f(无指数),常用于数据表。

IEEE 754也定义了特殊值±∞和NaN分别表示超出最大许可值的数以及除0的商和数学上无意义的运算(0/0或Sqrt(-1))。也可以通过api调用。 值得一提的是:在数字运算中我们通常倾向将NaN当作信号值(sentinel value)而非数字。可以通过math.IsNaN()判断;同时直接判断是否为NaN可能存在错误,因为与NaN比较总不成立。(除了!=,因为它总与==相反)。

nan = math.NaN()
fmt.Printl(nan == nan, nan < nan, nan > nan) // false, fales, fales

一个函数的返回值是浮点且有可能出错则最好单独报错:

func compute() (value float64, ok bool) {
    // ...
    if failed {
        return 0, false
    }
    return result, true
}

3.3 复数

complex64/128分别由float32/64构成。由内置的complex()函数创建;内置的real()和imag()函数分别提取实部和虚部。 在源码中,如果一个数字紧接一个i如:-1.32i就表示一个实部为0的虚数。

3.4 布尔值

ture或false。 这里的布尔运算是包括'逻辑短路'的。 ture和false不能用数字1、0等表示。

func btoi(b bool) int {
    if b {
        return 1
    }
    return 0
}

3.5 字符串

string被解读成按UTF8编码在Unicode码点。(utf8储存) UTF-8是变长编码,长度在1-4字节

func main() {
    var fit string = "你好,世界!"
    fmt.Println(fit)                    // 你好,世界!
    fmt.Println(fit[0], fit[1], fit[2]) // 228(E4) 189(BD) 160(A0)
    fmt.Println(fit[0:3])               // 你
    fmt.Println(len(fit))               // 14
}

可见,s[i]操作访问的是字节而不是字符,len(s)返回字节数而不是字符数。 如果i不在0\leqslant i\leqslant len(s)会触发宕机异常。

字符串可以追加、重复值,但是不可以改变字符串包含本身序列的值

func main() {
    var fit string = "Hello,world!"
    s := fit + "HELLO!!!"
    fmt.Println(s) // Hello,world!HELLO!!!
    fit[0] = 'E'   // cannot assign to fit[0] (neither addressable nor a map index expression)
}
不可变意味着两个字符串能安全地共用同一段底层内存,使得复制任何长度字符串的开销都低廉。类似的,字符串s及其子串(如s[:7])可以安全地共用数据,因此子串生成操作的开销低廉。这两种情况都没有重新分配内存。 底层分布

3.5.1 字符串字面量

字符串的值可以直接写成带引号的字符串字面量(string literal)形式。 由于go默认使用utf-8编码,所以我们可以轻易地插入转义字符,如:\a\r\n\"\等。 当然也可以用16或8进制表示(都表示单字节):

  • 16进制:\x开头,必须两位16进制数表示(大小不敏感)。 \xAc
  • 8进制:\o开头,必须三位8进制数表示,不可超过\377\120

原生的字符串字面量的书写形式是 ... ,使用反引号而不是双引号。 原生的字符串字面量内,转义序列不起作用;实质内容与字面写法严格一致,包括 \ \n 。 因此在程序源码中,原生的字符串字面量可以展开多行。唯一的特殊处理是回车符会被删除换行符会保留。这使得同一字符串所在所有平台上的值相同。包括习惯在文本文件存入换行符的系统。

正则表达式往往含有大量 \ ,可以方便地写成原生的字符串字面量,原生的字面量也适用于HTML模板、JSON字面量、命令行提示信息以及需要多行表达场景。

const GoUsage = `Go is a tool for managing Go source code.
Usage:
    go command [arguments]
...`

3.5.2 Unicode

从前,事情简单明晰,至少,狭隘的看,软件处理只需一个字符集:ASCII。

ASCII是用7bit(占用1B)表示的字符。但当今兼顾多语言与效率的是Unicode(unicode.org)。在go中使用rune (int32)表示。

但仍为ASCII‘当道’的环境下,Unicode会导致资源浪费。怎么改进呢?

神说要有 \'低浪费的编码\' ,于是便有了 \'UTF-8\' 。

3.5.3 UTF-8

一种Unicode标准,每个字符采用1~4B表示,变长低占用。(编码规则见参考链接)

0xxxxxxx                             runes 0-127    (ASCII)
110xxxxx 10xxxxxx                    128-2047       (values <128 unused)
1110xxxx 10xxxxxx 10xxxxxx           2048-65535     (values <2048 unused)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  65536-0x10ffff (other values unused)

UTF-8编码可自同步,最多追溯3字节,所以可以定位起始位置。同时作为前置编码可以左→右解码而不产生歧义;也无需超前阅读。

像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。 ————由GO语言圣经译者注

unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数字,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。

在go中可以使用转义来轻松使用Unicode码点(原本很难指明);分别使用\uhhhh\Uhhhhhhhh来表示,每一个h代表以为16进制数。(后者很少用) (编码规则见参考链接)

此外还有一些有意思的点见参考链接“字符串 - UTF-8 - Go语言圣经”。

由go的两位创始人Ken Thompson和Rob Pike发明。

3.5.4 字符串和字节slice

四个重要的包:

  • strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
  • bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效,稍后我们将展示。
  • strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
  • unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。
func main() {
	s := "abc"
	c1 := []byte(s)
	c2 := string(c1)
	c1[0] = 'f'
	c2[0] = 'f'
}

概念上,c1 := []byte(s)会生成一个新的字节数组,拷贝s含有的字节并返回指向整个数组的slice引用。少数情况下会优化成不复制,但为保证s不可变一般不会优化
反之,c2 := string(c1)也会生成副本,保证c2不可变。

3.5.5 字符串和数字的转换

strconv:提供了各种类型和对应字符串的相互转换。 如要将整数转string,其一选用fmt.Sprintf,另一种就是strconv.Itoa (a是ascii)

x := 123
fmt.Println(strconv.Itoa(x)) // 123

FormatInt和FormatUint函数可以用不同的进制来格式化数字:

fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
// FormatInt(i int64, base int) string
// FormatInt returns the string representation of i in the given base, for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' for digit values >= 10.
// 就是说第一个数是int,第二个是数制。

fmt.Printf函数的%b、%d、%o和%x等参数提供功能往往比strconv包的Format函数方便很多,特别是在需要包含有附加额外信息的时候。 如果要将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数,还有用于解析无符号整数的ParseUint函数:

x, err := strconv.Atoi("123")             // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits 64是数制

3.6 常量

常量是一种表达式,都是基本类型(boolean、string、数字),防止出现运行时意外(或恶意)修改。

const pi = 3.1415926
const (
	e   = 2.71818
	tes = 3.1571224 / 2.3
)
某些操作要在运行时才能检测到(÷0、下标越界等),但如果是常量则可以编译时报错。
常量相关的操作返回的也是常量,如:常量的数学、逻辑、比较运算,常量的类型转换和某些内置函数返回值(len、cap、complex等)。
常量声明会自行推断类型,如 const noDelay time.Duration = 0const timeout = 5 * time.Minute。由于time.Minute底层依赖于int64,所以两者都是time.Duration类型常量。
若同时声明多组变量,则除第一行外后面可省。后面复用第一行。

const (
	a = 1
	b
	c = 2
	d
)
fmt.Println(a, b, c, d) // 1 1 2 2

3.6.1 常量生成器iota

iota从0取值,每次加1。

const (
	sunday    Weekday = iota // 0
	monday                   // 1
	tuesday                  // 2
	wednesday                // 3
	thursday                 // 4
	friday                   // 5
	saturday                 // 6
)
也可以存在 const ( Flagup Flags = 1 << iota )形式
以及更复杂的形式
const (
	_ = 1 << (10 * iota)
	kib // 1024
)
但iota也存在局限:如因为不存在指数运算符不能生成更为人知的1000的幂。

3.6.2 无类型常量

一个常量可以有任意一个如int或float64或类似time.Duration这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。
六种未明确类型的常量类型:无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
无类型的常量:
  1. 可以通过延迟明确具体类型
  2. 可以提供更高的运算精度
  3. 可以不使用显式类型转换(前提是转换合理)
例如,例子中的ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围,但是它们依然是合法的常量,而且像下面的常量表达式依然有效(译注:YiB/ZiB是在编译期计算出来的,并且结果常量是1024,是Go语言int变量能有效表示的):
fmt.Println(YiB/ZiB) // "1024"
另一个例子,math.Pi无类型的浮点数常量,可以直接用于任意需要浮点数或复数的地方(对应3):
此时z已经成为comlex128
const z complex128 = math.Pi
const f float64 = float64(z)
对于常量面值,不同的写法可能会对应不同的类型(0和0.0,0i的区别)。同样,true和false也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。

前面说过除法运算符/会根据操作数的类型生成对应类型的结果。因此,不同写法的常量除法表达式可能对应不同的结果:
var f float64 = 212
fmt.Println((f - 32) * 5 / 9)     // "100"; (f - 32) * 5 is a float64
fmt.Println(5 / 9 * (f - 32))     // "0";   5/9 is an untyped integer, 0
fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float
如果转换合法的话,无类型的常量将会被隐式转换为对应的类型。无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值。对于浮点数和复数,可能会有舍入处理(这里或许是由无类型的高精度导致?):
const (
    deadbeef = 0xdeadbeef // untyped int with value 3735928559
    a = uint32(deadbeef)  // uint32 with value 3735928559
    b = float32(deadbeef) // float32 with value 3735928576 (rounded up)
    c = float64(deadbeef) // float64 with value 3735928559 (exact)
    d = int32(deadbeef)   // compile error: constant overflows int32
    e = float64(1e309)    // compile error: constant overflows float64
    f = uint(-1)          // compile error: constant underflows uint
)
对于一个没有显式类型的变量声明(包括简短变量声明),由常量决定默认类型。
注意有一点不同:无类型整数常量转换为int,它的内存大小是不确定的,但是无类型浮点数和复数常量则转换为内存大小明确的float64和complex128。如果不知道浮点数类型的内存大小是很难写出正确的数值算法的,因此Go语言不存在整型类似的不确定内存大小的浮点数和复数类型。

当尝试将这些无类型的常量转为一个接口值时(见第7章),这些默认类型将显得尤为重要,因为要靠它们明确接口对应的动态类型。
fmt.Printf("%T\n", 0)      // "int"
fmt.Printf("%T\n", 0.0)    // "float64"
fmt.Printf("%T\n", 0i)     // "complex128"
fmt.Printf("%T\n", '\000') // "int32" (rune)

参考链接:

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值