demo已上传git:https://gitee.com/yayako/go-learning.git

数据类型

基本数据类型

可以使用 unsafe 包下的 sizeof 函数来查看变量被分配的内存大小,单位为字节。

整形 int

就像java中根据整形位数分为short、int、long一样,go中也进行了相应的分类,只不过命名比较直观—— int8int16int32int64

默认的 int 则根据操作系统字长来决定。比如我使用的是64位的os,那默认的 int 就是指代 int64 了。

零值为0。

  1. var a0 int8
  2. var a1 int16
  3. var a2 int
  4. fmt.Println(unsafe.Sizeof( /*int8(0)*/ a0)) // 1
  5. fmt.Println(unsafe.Sizeof( /*int16(0)*/ a1)) // 2
  6. fmt.Println(unsafe.Sizeof( /*int(0)*/ a2)) // 8

数字字面量语法,数字字面量语法,直接使用二进制、八进制或十六进制浮点数的格式定义数字

%d:10进制,%b:2进制,%o:8进制,%x:16进制

fmt.Printf("10进制 - %d\n", 17)    // 17
fmt.Printf("0进制 - %b\n", 17)    // 10001
fmt.Printf("8进制 - %o\n", 17)    // 21
fmt.Printf("16进制 - %x\n", 17)    // 11

浮点型 float

同样的,java中的double、float对应go中的float32、float64,默认的取决于os字长。

零值为0。

小数的格式化输出

var pi = 3.1415926
// b = 3.1415926 -- 3.141593,保留两位小数后3.14
fmt.Printf("b = %v -- %f,保留两位小数后%.2f", b, b, b)

精度丢失问题

精度丢失问题与语言无关,本就是采用二进制来表示小数所必须面临的问题。

var f1 float64 = 8.2
var f2 float64 = 3.8
fmt.Printf("8.2 - 3.8 = %v\n", f1-f2) // 4.3999999999999995

java中采用Decimal类来避免,而在go中,也是通过第三方的decimal包来解决。后续学习第三方包的引入后再补充。

布尔型

与java唯一的区别在于关键字为bool而不是boolean(

零值为false。

字符串

我们知道java中的String为引用类型,不属于基本数据类型,且其底层为char数组。而在go中,官方将string归属到基本数据类型(本质上还是一个结构体),底层是一个byte数组

在看字符串前,先来看看go中的“字符”。

字符

不如先看看编码方式(具体的字符编码方式之间的关系可以参考这篇博客:字符编码中ASCII、Unicode和UTF-8的区别

  • 在java中,字符串采用的是unicode的编码方式,用两个字节表示一个字符(char占用两个字节)
  • 而在go语言中,字符串采用utf-8(可变长编码)的编码方式,编码后的值为int32

而实际上,go中并没有专门的字符类型,但是可以通过 byterune 两种方式来表示字符,前者通常用于表示单个字符(一个字节的长度范围内),后组合则通常用来表达字符串底层(占用四个字节)。

  • byte - int8:代表一个ASKII字符
  • rune - int32:代表一个UTF-8字符

如果将汉字赋给byte类型,会造成溢出,相当于将int32赋给int8。

在go中,使用特殊的rune类型来处理unicode编码,可以让基于unicode的文本处理更为方便。

字符串

go的string都是由字节组成的。零值为空串 ""

底层结构

先看看底层实现(strings/strings.go

type stringStruct struct {
    str unsafe.Pointer    // 指向长度为len的byte数组
    len int
}

字符串的定义

普通的定义和上述几种数据类型没有什么差别。

不过在go中可以通过反引号 ```` 实现多行字符串的定义

var c = `line1
            line2`
fmt.Println(c)

// 打印结果:
// line1
//                  line2

字符串的操作

关于字符串的操作(比较、包含、判断前后、查找下标等等),都可以参考strings.go中的函数。

go的数据类型 - 图1

字符串的长度

go中有获取长度的 len(v type) 函数,但是该函数实际上是获取参数在内存中占用的字节数,而前面提到过:

  1. 在go中strings的底层实现实际上是一个byte数组,因此使用len函数获取到的实际上是这个byte数组的长度(类似在java中,str.length() 实际上获取到的就是内置char数组的长度
  2. go语言中的所有汉字都是使用utf-8进行编码的,编码后的值都为int,因此一个中文汉字占3个字节

所以,如果使用len函数来获取含有汉字的string的长度,将会造成错误。

e := "你好"
fmt.Println(len(e)) // 6

在go中,获取string长度有以下几种方式

  • 先将string转化为rune数组,再通过len()函数获取rune数组的长度(有关 rune 请查看字符模块
    runeE := []rune(e)
    fmt.Println(len(runeE)) // 2
    
  • 通过 bytes.Count(s, sep []byte) - 1 进行统计
    fmt.Println(bytes.Count([]byte(e), nil) - 1) // 2
    
  • 通过 strings.Count(s, substr string) - 1 进行统计
    fmt.Println(strings.Count(e, "") - 1) // 2
    
  • 通过 utf8.RuneCountInString(s string) - 1 进行统计
    fmt.Println(utf8.RuneCountInString(e)) // 2
    

字符串的修改

我们知道在java中,String是不可变的类(类被final修饰,value被final修饰),在go中也是一样,string是不可变的

若想修改string的值就只能通过重新赋值或者通过rune数组作中间变量实现(有点类似java中的 new String(char[] value) ),因此每次修改都会重新分配内存并复制字节数组。

str := "你好,golang"
fmt.Println(str)                // 你好,golang
runeStr := []rune(str)            // [20320 22909 65292 103 111 108 97 110 103]
fmt.Println(runeStr)
runeStr[0] = '您'
fmt.Println(string(runeStr))    // 您好,golang

字符串的遍历

上述已知,go中的string底层是一个byte数组,因此如果直接类似使用java中的fori循环,势必出现错误

str := "你好,golang"
//    228-ä 189-½ 160-  229-å 165-¥ 189-½ 239-ï 188-¼ 140-Œ 103-g 111-o 108-l 97-a 110-n 103-g
for i := 0; i < len(str); i++ {
    fmt.Printf("%v-%c\t", str[i], str[i])
}

同样的,在go中,只要牵扯到对string的操作,多需借助rune类型。

//     20320-你    22909-好    65292-,    103-g    111-o    108-l    97-a    110-n    103-g
for _, v := range str { // 若需要使用下标 => for i, v := range str
    fmt.Printf("%v-%c\t",  v, v)
}

关于转型

实际上,go中不存在隐式向上转型一说,必须手动显式向上转型(发出了我爱java的声音

var a0 int8
var a1 int16
// fmt.Println(a0 + a1)        // × invalid operation: a0 + a1 (mismatched types int8 and int16)
fmt.Println(int16(a0) + a1) // ✔

其他基本数据类型之间的转换只要通过强制转换(括号 + 目标数据类型),而在go中则是使用函数的写法,例如上例的 int16()

string与其他基本数据类型之间的转换

(更多的格式参考源码中注释)

  • 其他基本数据类型转string

    • 通过 strconv 包的 format 函数 ```go /*
      • FormatInt
        • 参数1:要转换的值
        • 参数2:参数1的进制类型 */ var str5 = strconv.FormatInt(int64(1), 10)

/* FormatFloat

  - 参数1:要转换的值
  - 参数2:格式化类型 'f' (-ddd.dddd, no exponent),
  - 参数3:保留的小数点 -1表示不对小数格式化
  - 参数4:格式化的类型 32 64

*/ var str6 = strconv.FormatFloat(1.1, ‘f’, 4, 64)



   - 
通过 `string()` 函数 —— 注意,该函数只能将字符转换为string
```go
// a - string
fmt.Printf("%v - %T\n", string(97), string(97))
fmt.Printf("%v - %T\n", string(rune(97)), string(rune(97)))
  • 通过 fmt.Sprintf() 函数
    str1 := fmt.Sprintf("%d", 1)
    fmt.Printf("值:%v,类型:%T\n", str1, str1) // 值:1,类型:string
    str2 := fmt.Sprintf("%.2f", 2.2)
    fmt.Printf("值:%v,类型:%T\n", str2, str2) // 值:2.20,类型:string
    str3 := fmt.Sprintf("%t", false)
    fmt.Printf("值:%v,类型:%T\n", str3, str3) // 值:false,类型:string
    str4 := fmt.Sprintf("%c", 'a')
    fmt.Printf("值:%v,类型:%T\n", str4, str4) // 值:a,类型:string
    
  • string转其他基本数据类型:通过 strconv 包的 parse 函数,返回结果为 (i int64, err error)
    int0, _ := strconv.ParseInt("1",10,64)
    fmt.Printf("值:%v,类型:%T\n", int0, int0)
    float0,_ := strconv.ParseFloat("1.1",64)
    fmt.Printf("值:%v,类型:%T\n", float0, float0)
    int1, err := strconv.ParseInt("-",10,64)
    fmt.Printf("值:%v,类型:%T\n", int1, int1)
    fmt.Printf("值:%v,类型:%T\n", err, err)
    

复合数据类型

数组,切片,结构体,函数,map,通道(channel),接口等,后续再详细看。