GO基础学习笔记(1)基本数据类型

写在开头

非原创,知识搬运工,本节介绍了基本数据类型及长度,字符串、byte和rune之间的区别,如何比较字符串
demo代码地址

目录

链接地址

带着问题去阅读

  • len函数和unsafe.Sizeof的使用场景区别
  • 整型溢出会发生什么现象?
  • 无符号和有符号的区别,以及int64和uint64的取值范围多少
  • 类型别名如何定义
  • 分别说明GO中单引号、双引号以及尖引号代表的含义
  • 码点的含义和与rune类型有什么关系
  • rune类型和byte的区别
  • "go语言"这个字符串在len/unsafe.Sizeof函数中输出的结果是多少?[]rune("go语言")和[]byte("go语言")的成员数量又有多少
  • 字符串的比较是根据什么

知识点前瞻

思维脑图

前置知识(字、字长、字节、位)

  • bit字和位都表示一个二进制
  • byte字节,一个字节由8个字组成,8位
  • word字 ,多个字节为一个字,不同计算机的字大小不同,32位1字=32位=4字节,64位1字=64位=8字节
  • 字长,字的位数叫字长,即字的长度就是字长,长度用位数表示(不固定,有可变和固定两种)一般地,大型计算机的字长为32―64位,小型计算机为16―32位,而微型计算机为4一16位。
    计算机的字长决定了其CPU一次操作处理实际位数的多少,由此可见计算机的字长越大,其性能越优越。

我们常说的32位64位一般是指CPU数据总线的长度,即数据大小为2^位数,字大小与位数有关

1.数字类型(Numeric types)

我们使用unsafe.Sizeof来获取变量占用的字节大小

1.1整型-平台无关整型

在任何CPU架构下或操作系统中,长度都是固定不变的(int8/16/32/64,uint8/16/32/64),类型长度大小与数字有关,即int8为8个字,一个字节


golang ref

1.1.1无符号和有符号的区别

同样字节长度下,最高位(从右到左,表示低到高)比特为符号位

示意图
所以int8的寻址范围是7位,即27=127,数据大小范围位[-128,127],那么uint8(28=256),那为啥上图10000001的int8数据为啥表示的大小是-127,而不是-1呢,go采用2的补码表示整形的比特位编码方式,即源码取反后加1(有符号)

源码:
01111111
取反:
10000000
+1得到整型编码
10000001
不能简单的通过printf(%b)得到

摘取自官方文档

The value of an n-bit integer is n bits wide and represented using two's complement arithmetic

1.2整型-平台相关整型

先来看看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可以看作是一个超级大的无符号整型(大到可以容纳任何值)
这是其底层c++中的定义,是一个无符号的长长整型

typedef unsigned long long int  uint64;
typedef uint64          uintptr;

这三类是跟平台相关的,根据电脑位数大小为4字节或8字节

    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

整型后面的数字表示占用多少个字bit,当不显示声明类型时,类型推断为int,这里因为我的系统是64位的,即int类型占用8字节Byte64字bit,取值范围为0-(2^63)-1,63是因为最高位为符号,-1是因为没有-0这个数值存在

fmt.Println(math.MaxInt) //9223372036854775807 即2^63-1

因此我们要注意移植不同系统时,这类变长类型的长度

1.3不同进制的格式化输出

打开电脑计算器发现,我们用B(BIN表示二进制)、O(OCT表示8进制)、D(DEC表示10进制),H(HEX表示16进制)


计算器

编程语言中表示类似(唯一注意的是16进制)

  • 默认10进制 %d
  • 0b或0B 二进制 %b
  • 0o或0O 八进制 %o
  • 0x 16进制 %x
func TestInt() {
    var num01 int = 0b1100
    var num02 int = 0o14
    var num03 int = 0xC

    fmt.Printf("2进制数 %b 表示的是: %d \n", num01, num01)
    fmt.Printf("8进制数 %o 表示的是: %d \n", num02, num02)
    fmt.Printf("16进制数 %X 表示的是: %d \n", num03, num03)
}

2进制数 1100 表示的是: 12 
8进制数 14 表示的是: 12 
16进制数 C 表示的是: 12 

同时fmt的Printf格式化输出为下

%b    表示为二进制
%c    该值对应的unicode码值
%d    表示为十进制
%o    表示为八进制
%q    该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x    表示为十六进制,使用a-f
%X    表示为十六进制,使用A-F
%U    表示为Unicode格式:U+1234,等价于"U+%04X"
%E    用科学计数法表示
%f    用浮点数表示

1.4整型溢出问题

看一个demo

func TestIntOverflow(t *testing.T){                                                                              
        var a int8 = 127                                                                                         
        a+=1                                                                                                     
        fmt.Println(a)
}

=== RUN   TestIntOverflow
-128
--- PASS: TestIntOverflow (0.00s)

天哪,该demo结果居然是-127,即边界是127,+1后不是128,超出边界后是绕了一圈成了-128(同理uint8,255+1 = 0 )

1.4浮点型

float只提供了float32/64两种浮点类型,即4字节和8字节

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)

res

两者都是IEEE-754标准,32位是单精度,1位表示符号S,8位用来指数E,剩下23位表示尾数M。
64位双精度,1位符号,11位指数,52位尾数
对于 float32(单精度)来说,表示尾数的为23位,除去全部为0的情况以外,最小为2-23,约等于1.19*10-7,所以float小数部分只能精确到后面6位,加上小数点前的一位,即有效数字为7位。同理 float64(单精度)的尾数部分为 52位,最小为2-52,约为2.22*10-16,所以精确到小数点后15位,加上小数点前的一位,有效位数为16位。尾数越多,精确到小数位数越多

math.MaxFloat32
math.MaxFloat64

有了符号S、指数E和尾数M,那么我们表示一个小数就可以用公式


小数表示公式

看S我们可以知道S=0时-1^0 = 1小数>=0.0
offset为阶码偏移值是经过换算的指数
IEEE-754转换过程太复杂,不了解了摆烂
我们只需注意浮点型的比较,本质是二进制比较,但是转为二进制有精度丢失,因此不准确

1.5复数类型

complex

查阅了好多资料,发现跟数学有关,寄,跳过了(论学好高数的重要性)

1.6类型别名和比较

我们可以利用type来给一个类型取别名
但是当我们将别名和原类型做比较时,发现报错,提示类型不匹配
demo

type myint int
var a int =1
var b myint =2
t.Log(a==b) //error

go语言对类型安全有严格的要求,即便底层类型相同,但仍是不同类型的数据,不能被混在表达式中

2.字符串类型(string type)

与C不同的是GO没有字符型(char 关键字),但仍可以通过引号的区别来区分
在GO中,通过string类型统一了对“字符串”的抽象,无论是字符串常量、变量都被统一设置位string,是GO语言原生支持的(如C语言没有原生的字符串,通过字符数组以'\0'结尾表示字符串)
问题来了,为什么GO要原生支持字符串:

  • 不是原生类型,编译器不会进行类型校验,类型安全差
  • c中字符串操作要时刻考虑“\0”,防止缓冲区溢出
  • c以字符串数组形式定义数组,值可变,并发场景需要考虑
  • 不是原生获取其长度代价大,比如c中使用strlen函数,其原理是需要遍历整个字符串直到"\0"O(n)
  • c语言没有内置对非ASCII字符的支持(如中文字符,输出可能变为乱码)

因此相比于C我们需要注意:

  • string类型的数据是不可变的
    demo
var s string = "zjb"
s[1] = g  // error
s = "www"

虽然仍可以通过下标访问的方式s[1]获取数据,但是我们却不能简单的通过下标的方式来改变部分字符串,即所在程序的内存位置不能只修改部分,其实它底层不是一个数组,是一个stringstruct结构体

runtime/string.go
type stringStruct struct {
        str unsafe.Pointer
        len int
}

这里的len是小写,因此我们访问不到,只能通过len函数来访问

反射类的包中也有描述
reflect/value.go
type StringHeader struct {
  Data uintptr
  len int
}
stringHeader是一个string的运行时表示

str才是虚拟内存地址的指针,因为len的存在我们获取产长度的时间复杂度为O(1),
同时,这也意味着我们直接将string类型传参入函数也不会有太大开销(GO传参是值传递,在调用函数前,会先在栈空间开辟一块内存拷贝参数)

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.

代码验证一下

    var a string = "yes"
    var b string = "我爱你"

    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输出的结果都是16即两个 int(8个字节)的大小,这确实是一个指针

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描述来看,字符串本质是一个可以为空的字符序列byte组成,len的长度实际是这个byte的长度,因此这也是为什么在GO中中文字符串的长度为什么很奇怪的原因。
在GO中,字符使用Unicode编码(通常用16进制表示),unicode每个字符都分配了统一且唯一的字符编号(类似ASCII码‘A’的65),Unicode码点是字符在编码中的位置,一个码点对应一个字符
demo

func TestStr(t *testing.T){
        var a string = "中国"
        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字符集表中的码点
for range遍历的是一个码点

题外话--UTF8/16的诞生
ASCII码是一个字节大小(8位),取值范围是0-255,但实际ASCII码一共定义了128个字符,对于英语来说是够用的,但亚洲语言的字符数量不止255个,一个字节已经完全不够用,出于此目的诞生unicode,它实际上是一本字典,为每个字符规定一个用来表示该字符的数字。但是Unicode没有规定字符对于的二进制如何存储,如“汉”,0x6c49,110110001001001d,二进制15位,至少需要2个字节,那在“汉”之后出现在该本字典中的字符有没有可能需要3到4个存储?计算机又怎么知道到底是2个字节表示一个字符还是3个4个?,因此诞生UTF-8/16,其原理是根据码点进行编码

2.2 rune类型

A rune literal represents a rune constant, an integer value identifying a Unicode code point.

rune类型表示一个码点,它实际上是int32别名,一个rune实例就是一个Unicod字符,一个GO字符就是rune实例的合集


builtin.go

3.字符类型(byte)

go语言把字符分为rune和byte,rune是int32别名用于存放多字节字符,unicode码点,byte是uint8别名,用于存放1字节的ASCII字符
demo

func TestComplieByteWithRune(t *testing.T){
        var a string = "go语言"
        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存不了多字节所以分成多个字节,len函数计算的是byte长度(g和o分别占一个字节,中国两字占6字节,因此该切片一共8个成员)

还有就是双引号是表示字符串
单引号是表示byte或rune类型,对应uint8和int32类型,默认是rune类型

    t.Log(unsafe.Sizeof('y'))  //4
    t.Log(unsafe.Sizeof((byte)('y'))) //1

补充一点反引号是字符串字面量,就是字面意思,不支持任何转义,你输入啥样就啥样

4 字符串比较问题

String values are comparable and ordered, lexically byte-wise

这是GO语言规范对于字符串比较的描述:

  • 字符串是可比较的
  • 字符串是有序的
  • 是逐字节比较的

用几个例子展开说明一下
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]

实际是逐字节ASCII玛比较, 50>49,到这里已经比出大小,后续不再对比,结果为false
当s2="12"时

true
[49 50 51 52 53] [49 50]

前面都相同,后续51没人比了,s1更大,结果为true
复杂点的中文例子

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]

常见的字符串比较方法还有:

  • strings.Compare
  • strings.EqualFold

留疑

  • 字符串连接的方式和那种方式性能最好
  • 字符串与rune类型的切片转换问题

参考

1.tonyBaiGO语言第一课
2.计算机基础概念字、位、字节
3.go ref
4.整型和浮点型
5.Unicode\utf8\utf16终于懂了
6.详细讲解go中的rune

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容