寫在開(kāi)頭
非原創(chuàng)唐断,知識(shí)搬運(yùn)工捞魁,本節(jié)介紹了基本數(shù)據(jù)類型及長(zhǎng)度赠群,字符串、byte和rune之間的區(qū)別浑吟,如何比較字符串
demo代碼地址
目錄
帶著問(wèn)題去閱讀
- len函數(shù)和unsafe.Sizeof的使用場(chǎng)景區(qū)別
- 整型溢出會(huì)發(fā)生什么現(xiàn)象笙纤?
- 無(wú)符號(hào)和有符號(hào)的區(qū)別,以及int64和uint64的取值范圍多少
- 類型別名如何定義
- 分別說(shuō)明GO中單引號(hào)组力、雙引號(hào)以及尖引號(hào)代表的含義
- 碼點(diǎn)的含義和與rune類型有什么關(guān)系
- rune類型和byte的區(qū)別
- "go語(yǔ)言"這個(gè)字符串在len/unsafe.Sizeof函數(shù)中輸出的結(jié)果是多少省容?[]rune("go語(yǔ)言")和[]byte("go語(yǔ)言")的成員數(shù)量又有多少
- 字符串的比較是根據(jù)什么
知識(shí)點(diǎn)前瞻
前置知識(shí)(字、字長(zhǎng)燎字、字節(jié)腥椒、位)
- bit字和位都表示一個(gè)二進(jìn)制
- byte字節(jié),一個(gè)字節(jié)由8個(gè)字組成候衍,8位
- word字 ,多個(gè)字節(jié)為一個(gè)字笼蛛,不同計(jì)算機(jī)的字大小不同,32位1字=32位=4字節(jié)蛉鹿,64位1字=64位=8字節(jié)
-
字長(zhǎng)伐弹,字的位數(shù)叫字長(zhǎng),即字的長(zhǎng)度就是字長(zhǎng),長(zhǎng)度用位數(shù)表示(不固定惨好,有可變和固定兩種)一般地煌茴,大型計(jì)算機(jī)的字長(zhǎng)為32―64位,小型計(jì)算機(jī)為16―32位日川,而微型計(jì)算機(jī)為4一16位蔓腐。
計(jì)算機(jī)的字長(zhǎng)決定了其CPU一次操作處理實(shí)際位數(shù)的多少,由此可見(jiàn)計(jì)算機(jī)的字長(zhǎng)越大龄句,其性能越優(yōu)越回论。
我們常說(shuō)的32位64位一般是指CPU數(shù)據(jù)總線的長(zhǎng)度,即數(shù)據(jù)大小為2^位數(shù)分歇,字大小與位數(shù)有關(guān)
1.數(shù)字類型(Numeric types)
我們使用unsafe.Sizeof來(lái)獲取變量占用的字節(jié)大小
1.1整型-平臺(tái)無(wú)關(guān)整型
在任何CPU架構(gòu)下或操作系統(tǒng)中傀蓉,長(zhǎng)度都是固定不變的(int8/16/32/64,uint8/16/32/64),類型長(zhǎng)度大小與數(shù)字有關(guān)职抡,即int8為8個(gè)字葬燎,一個(gè)字節(jié)
1.1.1無(wú)符號(hào)和有符號(hào)的區(qū)別
同樣字節(jié)長(zhǎng)度下,最高位(從右到左缚甩,表示低到高)比特為符號(hào)位
源碼:
01111111
取反:
10000000
+1得到整型編碼
10000001
不能簡(jiǎn)單的通過(guò)printf(%b)得到
摘取自官方文檔
The value of an n-bit integer is n bits wide and represented using two's complement arithmetic
1.2整型-平臺(tái)相關(guān)整型
先來(lái)看看res怎么描述的
uint either 32 or 64 bits
int same size as uint
uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value
uintptr可以看作是一個(gè)超級(jí)大的無(wú)符號(hào)整型(大到可以容納任何值)
這是其底層c++中的定義,是一個(gè)無(wú)符號(hào)的長(zhǎng)長(zhǎng)整型
typedef unsigned long long int uint64;
typedef uint64 uintptr;
這三類是跟平臺(tái)相關(guān)的厉熟,根據(jù)電腦位數(shù)大小為4字節(jié)或8字節(jié)
var a uint8 = 1
var b uint16 = 1
var c int = 1
var d int8 = 1
var e int16 = 1
var f = 1
fmt.Println(unsafe.Sizeof(a)) //1
fmt.Println(unsafe.Sizeof(b)) //2
fmt.Println(unsafe.Sizeof(c)) //8
fmt.Println(unsafe.Sizeof(d)) //1
fmt.Println(unsafe.Sizeof(e)) // 2
fmt.Println(unsafe.Sizeof(f)) //8
整型后面的數(shù)字表示占用多少個(gè)字bit导盅,當(dāng)不顯示聲明類型時(shí),類型推斷為int庆猫,這里因?yàn)槲业南到y(tǒng)是64位的,即int類型占用8字節(jié)Byte64字bit绅络,取值范圍為0-(2^63)-1月培,63是因?yàn)樽罡呶粸榉?hào),-1是因?yàn)闆](méi)有-0這個(gè)數(shù)值存在
fmt.Println(math.MaxInt) //9223372036854775807 即2^63-1
因此我們要注意移植不同系統(tǒng)時(shí)恩急,這類變長(zhǎng)類型的長(zhǎng)度
1.3不同進(jìn)制的格式化輸出
打開(kāi)電腦計(jì)算器發(fā)現(xiàn)杉畜,我們用B(BIN表示二進(jìn)制)、O(OCT表示8進(jìn)制)衷恭、D(DEC表示10進(jìn)制)此叠,H(HEX表示16進(jìn)制)
編程語(yǔ)言中表示類似(唯一注意的是16進(jìn)制)
- 默認(rèn)10進(jìn)制 %d
- 0b或0B 二進(jìn)制 %b
- 0o或0O 八進(jìn)制 %o
- 0x 16進(jìn)制 %x
func TestInt() {
var num01 int = 0b1100
var num02 int = 0o14
var num03 int = 0xC
fmt.Printf("2進(jìn)制數(shù) %b 表示的是: %d \n", num01, num01)
fmt.Printf("8進(jìn)制數(shù) %o 表示的是: %d \n", num02, num02)
fmt.Printf("16進(jìn)制數(shù) %X 表示的是: %d \n", num03, num03)
}
2進(jìn)制數(shù) 1100 表示的是: 12
8進(jìn)制數(shù) 14 表示的是: 12
16進(jìn)制數(shù) C 表示的是: 12
同時(shí)fmt的Printf格式化輸出為下
%b 表示為二進(jìn)制
%c 該值對(duì)應(yīng)的unicode碼值
%d 表示為十進(jìn)制
%o 表示為八進(jìn)制
%q 該值對(duì)應(yīng)的單引號(hào)括起來(lái)的go語(yǔ)法字符字面值,必要時(shí)會(huì)采用安全的轉(zhuǎn)義表示
%x 表示為十六進(jìn)制随珠,使用a-f
%X 表示為十六進(jìn)制灭袁,使用A-F
%U 表示為Unicode格式:U+1234猬错,等價(jià)于"U+%04X"
%E 用科學(xué)計(jì)數(shù)法表示
%f 用浮點(diǎn)數(shù)表示
1.4整型溢出問(wèn)題
看一個(gè)demo
func TestIntOverflow(t *testing.T){
var a int8 = 127
a+=1
fmt.Println(a)
}
=== RUN TestIntOverflow
-128
--- PASS: TestIntOverflow (0.00s)
天哪,該demo結(jié)果居然是-127茸歧,即邊界是127倦炒,+1后不是128,超出邊界后是繞了一圈成了-128(同理uint8软瞎,255+1 = 0 )
1.4浮點(diǎn)型
float只提供了float32/64兩種浮點(diǎn)類型逢唤,即4字節(jié)和8字節(jié)
func TestFloat(t *testing.T){
var a float32
var b float64
t.Log(unsafe.Sizeof(a),unsafe.Sizeof(b))
}
=== RUN TestFloat
int_test.go:43: 4 8
--- PASS: TestFloat (0.00s)
兩者都是IEEE-754標(biāo)準(zhǔn),32位是單精度涤浇,1位表示符號(hào)S鳖藕,8位用來(lái)指數(shù)E,剩下23位表示尾數(shù)M只锭。
64位雙精度著恩,1位符號(hào),11位指數(shù)纹烹,52位尾數(shù)
對(duì)于 float32(單精度)來(lái)說(shuō)页滚,表示尾數(shù)的為23位,除去全部為0的情況以外铺呵,最小為2-23裹驰,約等于1.19*10-7,所以float小數(shù)部分只能精確到后面6位片挂,加上小數(shù)點(diǎn)前的一位幻林,即有效數(shù)字為7位。同理 float64(單精度)的尾數(shù)部分為 52位音念,最小為2-52沪饺,約為2.22*10-16,所以精確到小數(shù)點(diǎn)后15位闷愤,加上小數(shù)點(diǎn)前的一位整葡,有效位數(shù)為16位。尾數(shù)越多讥脐,精確到小數(shù)位數(shù)越多
math.MaxFloat32
math.MaxFloat64
有了符號(hào)S遭居、指數(shù)E和尾數(shù)M,那么我們表示一個(gè)小數(shù)就可以用公式
看S我們可以知道S=0時(shí)-1^0 = 1小數(shù)>=0.0
offset為階碼偏移值是經(jīng)過(guò)換算的指數(shù)
IEEE-754轉(zhuǎn)換過(guò)程太復(fù)雜旬渠,不了解了擺爛
我們只需注意浮點(diǎn)型的比較俱萍,本質(zhì)是二進(jìn)制比較,但是轉(zhuǎn)為二進(jìn)制有精度丟失告丢,因此不準(zhǔn)確
1.5復(fù)數(shù)類型
查閱了好多資料枪蘑,發(fā)現(xiàn)跟數(shù)學(xué)有關(guān),寄,跳過(guò)了(論學(xué)好高數(shù)的重要性)
1.6類型別名和比較
我們可以利用type來(lái)給一個(gè)類型取別名
但是當(dāng)我們將別名和原類型做比較時(shí)岳颇,發(fā)現(xiàn)報(bào)錯(cuò)照捡,提示類型不匹配
demo
type myint int
var a int =1
var b myint =2
t.Log(a==b) //error
go語(yǔ)言對(duì)類型安全有嚴(yán)格的要求,即便底層類型相同赦役,但仍是不同類型的數(shù)據(jù)麻敌,不能被混在表達(dá)式中
2.字符串類型(string type)
與C不同的是GO沒(méi)有字符型(char 關(guān)鍵字),但仍可以通過(guò)引號(hào)的區(qū)別來(lái)區(qū)分
在GO中掂摔,通過(guò)string類型統(tǒng)一了對(duì)“字符串”的抽象术羔,無(wú)論是字符串常量、變量都被統(tǒng)一設(shè)置位string乙漓,是GO語(yǔ)言原生支持的(如C語(yǔ)言沒(méi)有原生的字符串级历,通過(guò)字符數(shù)組以'\0'結(jié)尾表示字符串)
問(wèn)題來(lái)了,為什么GO要原生支持字符串:
- 不是原生類型叭披,編譯器不會(huì)進(jìn)行類型校驗(yàn)寥殖,類型安全差
- c中字符串操作要時(shí)刻考慮“\0”,防止緩沖區(qū)溢出
- c以字符串?dāng)?shù)組形式定義數(shù)組涩蜘,值可變嚼贡,并發(fā)場(chǎng)景需要考慮
- 不是原生獲取其長(zhǎng)度代價(jià)大,比如c中使用strlen函數(shù)同诫,其原理是需要遍歷整個(gè)字符串直到"\0"O(n)
- c語(yǔ)言沒(méi)有內(nèi)置對(duì)非ASCII字符的支持(如中文字符粤策,輸出可能變?yōu)閬y碼)
因此相比于C我們需要注意:
- string類型的數(shù)據(jù)是不可變的
demo
var s string = "zjb"
s[1] = g // error
s = "www"
雖然仍可以通過(guò)下標(biāo)訪問(wèn)的方式s[1]獲取數(shù)據(jù),但是我們卻不能簡(jiǎn)單的通過(guò)下標(biāo)的方式來(lái)改變部分字符串误窖,即所在程序的內(nèi)存位置不能只修改部分叮盘,其實(shí)它底層不是一個(gè)數(shù)組,是一個(gè)stringstruct結(jié)構(gòu)體
runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
這里的len是小寫霹俺,因此我們?cè)L問(wèn)不到柔吼,只能通過(guò)len函數(shù)來(lái)訪問(wèn)
反射類的包中也有描述
reflect/value.go
type StringHeader struct {
Data uintptr
len int
}
stringHeader是一個(gè)string的運(yùn)行時(shí)表示
str才是虛擬內(nèi)存地址的指針,因?yàn)閘en的存在我們獲取產(chǎn)長(zhǎng)度的時(shí)間復(fù)雜度為O(1)丙唧,
同時(shí)愈魏,這也意味著我們直接將string類型傳參入函數(shù)也不會(huì)有太大開(kāi)銷(GO傳參是值傳遞,在調(diào)用函數(shù)前想际,會(huì)先在椗嗦空間開(kāi)辟一塊內(nèi)存拷貝參數(shù))
Strings are immutable: once created, it is impossible to change the contents of a string. The predeclared string type is
string
; it is a defined type.
代碼驗(yàn)證一下
var a string = "yes"
var b string = "我愛(ài)你"
fmt.Println(len(a)) //3
fmt.Println(len(b)) //9
fmt.Println(unsafe.Sizeof(a)) //16
fmt.Println(unsafe.Sizeof(b)) //16
fmt.Println(unsafe.Sizeof("我")) //16
我們可以看到 sizeof輸出的結(jié)果都是16即兩個(gè) int(8個(gè)字節(jié))的大小,這確實(shí)是一個(gè)指針
2.1字符串的編碼
A string value is a (possibly empty) sequence of bytes. The number of bytes is called the length of the string and is never negative.
從ref描述來(lái)看沼琉,字符串本質(zhì)是一個(gè)可以為空的字符序列byte組成北苟,len的長(zhǎng)度實(shí)際是這個(gè)byte的長(zhǎng)度桩匪,因此這也是為什么在GO中中文字符串的長(zhǎng)度為什么很奇怪的原因打瘪。
在GO中,字符使用Unicode編碼(通常用16進(jìn)制表示),unicode每個(gè)字符都分配了統(tǒng)一且唯一的字符編號(hào)(類似ASCII碼‘A’的65)闺骚,Unicode碼點(diǎn)是字符在編碼中的位置彩扔,一個(gè)碼點(diǎn)對(duì)應(yīng)一個(gè)字符
demo
func TestStr(t *testing.T){
var a string = "中國(guó)"
t.Log(len(a))
for _,c := range a {
fmt.Printf("%x\n",c)
}
}
=== RUN TestStr
6
4e2d
56fd
--- PASS: TestStr (0.00s)
“中”在unicode中的表示是0x4e2d,即0x4e2d是“中”在Unicode字符集表中的碼點(diǎn)
for range遍歷的是一個(gè)碼點(diǎn)
題外話--UTF8/16的誕生
ASCII碼是一個(gè)字節(jié)大衅(8位)虫碉,取值范圍是0-255,但實(shí)際ASCII碼一共定義了128個(gè)字符胸梆,對(duì)于英語(yǔ)來(lái)說(shuō)是夠用的敦捧,但亞洲語(yǔ)言的字符數(shù)量不止255個(gè),一個(gè)字節(jié)已經(jīng)完全不夠用碰镜,出于此目的誕生unicode兢卵,它實(shí)際上是一本字典,為每個(gè)字符規(guī)定一個(gè)用來(lái)表示該字符的數(shù)字绪颖。但是Unicode沒(méi)有規(guī)定字符對(duì)于的二進(jìn)制如何存儲(chǔ)秽荤,如“漢”,0x6c49,110110001001001d,二進(jìn)制15位柠横,至少需要2個(gè)字節(jié)窃款,那在“漢”之后出現(xiàn)在該本字典中的字符有沒(méi)有可能需要3到4個(gè)存儲(chǔ)?計(jì)算機(jī)又怎么知道到底是2個(gè)字節(jié)表示一個(gè)字符還是3個(gè)4個(gè)牍氛?晨继,因此誕生UTF-8/16,其原理是根據(jù)碼點(diǎn)進(jìn)行編碼
2.2 rune類型
A rune literal represents a rune constant, an integer value identifying a Unicode code point.
rune類型表示一個(gè)碼點(diǎn)糜俗,它實(shí)際上是int32別名踱稍,一個(gè)rune實(shí)例就是一個(gè)Unicod字符,一個(gè)GO字符就是rune實(shí)例的合集
3.字符類型(byte)
go語(yǔ)言把字符分為rune和byte悠抹,rune是int32別名用于存放多字節(jié)字符珠月,unicode碼點(diǎn),byte是uint8別名楔敌,用于存放1字節(jié)的ASCII字符
demo
func TestComplieByteWithRune(t *testing.T){
var a string = "go語(yǔ)言"
fmt.Println(len(a))
fmt.Println([]rune(a))
fmt.Println([]byte(a))
}
=== RUN TestComplieByteWithRune
8
[103 111 35821 35328]
[103 111 232 175 173 232 168 128]
--- PASS: TestComplieByteWithRune (0.00s)
byte存不了多字節(jié)所以分成多個(gè)字節(jié)啤挎,len函數(shù)計(jì)算的是byte長(zhǎng)度(g和o分別占一個(gè)字節(jié),中國(guó)兩字占6字節(jié)卵凑,因此該切片一共8個(gè)成員)
還有就是雙引號(hào)是表示字符串
單引號(hào)是表示byte或rune類型庆聘,對(duì)應(yīng)uint8和int32類型,默認(rèn)是rune類型
t.Log(unsafe.Sizeof('y')) //4
t.Log(unsafe.Sizeof((byte)('y'))) //1
補(bǔ)充一點(diǎn)反引號(hào)是字符串字面量勺卢,就是字面意思伙判,不支持任何轉(zhuǎn)義,你輸入啥樣就啥樣
4 字符串比較問(wèn)題
String values are comparable and ordered, lexically byte-wise
這是GO語(yǔ)言規(guī)范對(duì)于字符串比較的描述:
- 字符串是可比較的
- 字符串是有序的
- 是逐字節(jié)比較的
用幾個(gè)例子展開(kāi)說(shuō)明一下
demo
func TestCompareStr1(t *testing.T) {
s1 := "12345"
s2 := "2"
t.Log(s1 > s2)
t.Log([]byte(s1), []byte(s2))
}
false
[49 50 51 52 53] [50]
實(shí)際是逐字節(jié)ASCII瑪比較黑忱, 50>49宴抚,到這里已經(jīng)比出大小勒魔,后續(xù)不再對(duì)比,結(jié)果為false
當(dāng)s2="12"時(shí)
true
[49 50 51 52 53] [49 50]
前面都相同菇曲,后續(xù)51沒(méi)人比了冠绢,s1更大,結(jié)果為true
復(fù)雜點(diǎn)的中文例子
func TestCompareStr2(t *testing.T) {
s1 := "零"
s2 := "一"
s3 := "二"
t.Log(s1 > s2, []byte(s1), []byte(s2))
t.Log(s3 > s2, []byte(s3), []byte(s2))
t.Log(s3 > s1, []byte(s3), []byte(s1))
}
true [233 155 182] [228 184 128]
true [228 186 140] [228 184 128]
false [228 186 140] [233 155 182]
常見(jiàn)的字符串比較方法還有:
- strings.Compare
- strings.EqualFold
留疑
- 字符串連接的方式和那種方式性能最好
- 字符串與rune類型的切片轉(zhuǎn)換問(wèn)題
參考
1.tonyBaiGO語(yǔ)言第一課
2.計(jì)算機(jī)基礎(chǔ)概念字常潮、位弟胀、字節(jié)
3.go ref
4.整型和浮點(diǎn)型
5.Unicode\utf8\utf16終于懂了
6.詳細(xì)講解go中的rune