前言
学习Go半年之后,我决定重新开始阅读《The Go Programing Language》,对书中涉及重点进行全面讲解,这是Go语言知识查漏补缺系列的文章第二篇,前一篇文章则对应书中一二两章。
我也开源了一个Go语言的学习仓库,有需要的同学可以关注,其中将整理往期精彩文章、以及Go相关电子书等资料。
仓库地址:https://github.com/BaiZe1998/go-learning
第三章、基本数据类型
3.1 整数
负数的%运算
&^(位运算符:and not),x &^ y = z,y中1的位,则z中对应为0,否则z中对应为x中的位
00100010 &^ 00000110 = 00100000
无符号整数通常不会用于只为了存放非负整数变量,只有当涉及到位运算、特殊的算数运算、hash等需要利用无符号特性的场景下才会去选择使用
比如数组下标i用int存放,而不是uint,因为i--使得i == -1时作为判断遍历结束的标志,如果是uint,则0减1则等于2^64-1,而不是-1,无法结束遍历
注意:int的范围随着当前机器决定是32位还是64位
var x int32 = 1
var y int16 = 2
var z int = x + y // complie error
var z int = int(x) + int(y) // ok
// 大多数数值型的类型转换不会改变值的内容,只会改变其类型(编译器解释这个变量的方式),但是当整数和浮点数以及大范围类型与小范围类型转换时,可能会丢失精度,或者出现意外的结果
3.2 浮点数
math.MaxFloat32
math.MinFloat32
const x = 6.2222334e30 // 科学计数法
// math包中有很多的使用浮点数的函数,并且fmt包有很多适用于浮点数的格式化输出,包括保留小数点的具体精度等
float32精度大概6位
float64精度大概15位(更常用,因为单精度计算损失太快)
// 直接用浮点数为返回值结果,再二次用于其他的比较判断返回结果是否有效,有时会有误差导致错误,推荐额外增加一个bool参数
func compute() (value float64, ok bool) {
if failed {
return 0, false
}
return result, true
}
3.3 复数
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // -5+10i
fmt.Println(real(x*y)) // -5
fmt.Println(imag(x*y)) // 10
// 两个复数相等当且仅当实部和虚部相当
fmt.Println(cmplx.Sqrt(-1)) // 0+1i
3.4 布尔量
bool是if或者for的判断条件
s != "" && s[0] == 'x' // 当逻辑运算符左侧表达式可以决定操作结果则将放弃执行右侧表达式
// &&的优先级高于||
3.5 字符串
string在GO语言中是不可变的量
len获取的是字符串的字节数目,而不是码点(UTF-8 Unicode code point)
字符串第i个字节,并不一定是字符串的第i个字符,因为UTF-8编码对于非ASCII的code point需要2个或更多字节
str := "hello, world"
fmt.Println(s[:5]) // hello
fmt.Println(s[7:]) // world
fmt.Println(s[:]) // hello world
s := "left"
t := s
s += " right" // 此时s指向新创建的string ”left right“,而t指向之前的s表示的“left”
a := "x"
b := "x"
a == b // true,且string可以按照字典序比较大小
string是不可变的,意味着同一个string的拷贝可以共享底层的内存,使得拷贝变得很轻量,比如s和s[:7]可以安全的共享相同的数据,因此substring操作也很轻量。没有新的内存被分配。
反引号中的字符串表示其原生的意思,内容可以多行定义,不支持转义字符
func main() {
a := `hello
world
lalala`
fmt.Println(a)
}
Unicode
UTF-8使用码点描述字符(Unicode code point),在Go中对应术语:rune(GO中使用int32存储)
可以使用一个int32的序列,来代表rune序列,固定长度带来了额外的开销(因为大多数常用字符可以使用16bits描述)
UTF-8
可变长编码,使用1-4 bytes来代表一个rune,1byte存储 ASCII ,2 or 3 bytes 存储大多数常用字符 rune,并且采用高位固定的方式来区分范围(前缀编码,无二义性,编码更紧凑)
s := "Hello, 世界"
fmt.Println(len(s)) // 13
fmt.Println(utf8.RuneCountInString(s)) // 9
字符串和数组切片
字符串包含了一连串的字节(byte),创建后不可变。而[]byte内容是可变的
s := "abc"
b := []byte(s) // 分配新的字节数组内存
s2 : string(b) // 发生内存拷贝
为了避免没有必要的转换和内存分配,bytes包中提供了很多与string包中相同功能的方法,更推荐使用(共享内存)
bytes.Buffer用于字符(字符串)的累加构造字符串操作很方便,高效
// 一些操作Buffer的api
var buf bytes.Buffer
fmt.Fprintf(&buf, "%d", x)
buf.WriteString("abc")
buf.WriteByte('x')
buf.WriteRune(码点值)
字符串和数值类型的转换
// 整数转string
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv,Itoa(x)) // "123" "123"
// %b %d %u %x 用于进制转换
s := fmt.Sprintf("x=%b", x) // "x=1111011"
// string转整数
x, err := strconv.Atoi("123")
y, err := strconv.ParseInt("123", 10, 64)// base 10, up to 64 bits,第三个参数表示转换的整型的范围为int64
// fmt.Scanf()可以用于读取混合数据(整型、字符等)
3.6 常量
所有常量的底层都是由:boolean、string、number组成(在编译时确定,不可变,常量的运算结果依旧是常量)
const a = 2
const b = 2*a // b 在编译时完成
大多数常量的声明没有指定类型,但是也可以指定,没有类型的常量Go中称为无类型常量(untyped constant),具体的类型到使用到的时候确定
untyped constant
const a = 10
fmt.Printf("%T\n", a) // int(隐式类型)
var b float64 = 4*a // 在需要的时候,a转变成了float64
fmt.Printf("%T\n", b) // float64
在默认情况下,untyped constant 不是没有具体类型,而是隐式转换成了如下类型,因此上述a的类型可以打印为int
并且untyped constant拥有更高的精度,可以认为至少有 256bit 的运算精度
- untyped boolean
- untyped integer (隐式转换成 int)
- untyped rune (隐式转换成 int32)
- untyped floaing-point (隐式转换成 float64)
- untyped complex (隐匿转换成 complex128)
- untyped string
常量生成器
可以参与计算且拥有增长属性
type Flags uint
const (
a = 1 << iota // 1
b // 2
c // 4
d // 8
)
const (
_ = 1 << (10 * iota)
KiB // 2^10
MiB // 2^20
GiB // 2^30
TiB //
PiB //
EiB //
ZiB // 2^70
...
)