demo已上传git:https://gitee.com/yayako/go-learning.git
数据类型
基本数据类型
可以使用 unsafe 包下的 sizeof 函数来查看变量被分配的内存大小,单位为字节。
整形 int
就像java中根据整形位数分为short、int、long一样,go中也进行了相应的分类,只不过命名比较直观—— int8、int16、int32、int64。
默认的 int 则根据操作系统字长来决定。比如我使用的是64位的os,那默认的 int 就是指代 int64 了。
零值为0。
var a0 int8var a1 int16var a2 intfmt.Println(unsafe.Sizeof( /*int8(0)*/ a0)) // 1fmt.Println(unsafe.Sizeof( /*int16(0)*/ a1)) // 2fmt.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中并没有专门的字符类型,但是可以通过 byte 、rune 两种方式来表示字符,前者通常用于表示单个字符(一个字节的长度范围内),后组合则通常用来表达字符串底层(占用四个字节)。
- 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中有获取长度的 len(v type) 函数,但是该函数实际上是获取参数在内存中占用的字节数,而前面提到过:
- 在go中strings的底层实现实际上是一个byte数组,因此使用len函数获取到的实际上是这个byte数组的长度(类似在java中,
str.length()实际上获取到的就是内置char数组的长度 - 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)
- FormatInt
- 通过
/* 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),接口等,后续再详细看。
