本篇介绍
本篇主要就是介绍下go中的基本类型,看看和我们平时就见得有什么不一样。具体就是基本类型中多了表示unicode的rune,复数的complex,然后string操作那块介绍了下unicode的来源。最后就是类似于枚举的常量产生器iota。这篇还好,不难理解。
整数类型
go提供的整数类型比较多,int8,int16,int32,int64,也有对应的无符号形式uint8,uint16.uint32,uint64.当然也有int,uint,但是int,uint究竟是int64,还是int32,这取决于编译器,这块我们不能自己做假设。当然也多了两个类型rune和uintptr,rune用来表示Unicode编码,比如var i rune = '我'. 类似于char,不过char只能表示ASCII码,rune对应的是Unicode码,范围更广。uintptr是用来表示指针值,上层开发应用不多。
运算符这块区别不大,不再介绍。在看打印这块发现有一个小技巧,是go中新增的。
package main
import (
"fmt"
)
func main() {
var i int = 17
fmt.Printf("%d, %#[1]o, %#[1]X\n",i)
}
fox@fox:~/Documents/Go/chapter3/day1$ ./inter
17, 021, 0X11
这样需要将一个变量以多种形式打印时,可以借助于索引。这样的话就不需要把一个变量写多次了。#的作用就是将格式也打印出来,8进制的0,16进制的0X。
浮点类型
go提供了两种浮点类型,float32和float64,打印的时候除了使用%f外,还支持了%g(全部打印小数点后尾数),%e(用科学计数法打印)。在go里面多了+Inf,-Inf,NaN,也就是正无穷,负无穷,不是一个数字。正无穷和负无穷比较好理解,不是一个数字就是指一些非法的计算,比如零除以零,求-1的平方根。NaN和任何对象比较都是false,包括他自己。
复数类型
go中支持复数,这样在go中就可以直接进行复数的操作了。在go中提供了两种精度的复数,可以猜出来,就是complex64,complex128,为什么?因为go中的浮点数就只有两种啊,float32,float64. 如何构造一个复数?有两种方法,我们通过下面的代码看一下:
package main
import (
"fmt"
)
func main() {
var a complex128 = complex(1,3)
b := 1 + 3i
fmt.Println(a, b, a==b, real(a), imag(a))
}
fox@fox:~/Documents/Go/chapter3/day1$ ./inter
(1+3i) (1+3i) true 1 3
也就是复数可以像其他基本类型一样进行比较,加减乘除等基本操作,内置的real,imag分别是获取它的实部和虚部。
bool
bool类型比较简单,需要知道的是bool的true和false不会隐式地和1,0互相转换,除非是在if,for等场景中。其余的按照正常用即可。
字符串
字符串本质上是不可修改的byte序列。在go中或者是在c++等其他高级语言中,对string直接赋值,追加,其实是新创建了一个字符串。尤其是在字符串比较大的时候,反复追加赋值代价是比较高的。回到go,go里面有一些基本操作,比如获取字符串长度len,字符串的切片追加也是可以的,来一个例子看下这些操作:
package main
import (
"fmt"
)
func main() {
var a string = "i am chinese"
fmt.Println(a, ":" ,len(a),":", a[0:4], ":", a[:], ":" , a + " man")
}
fox@fox:~/Documents/Go/chapter3/day1$ ./inter
i am chinese : 12 : i am : i am chinese : i am chinese man
在写字符串时,会有一些特殊字符,比如单双引号,转义符号'\'。在字符串书写时需要转义一下,这在写正则表达式时不怎么方便,可以使用键盘左上角的那个符号`。 比如`\\\`, 就可以正常打印出三个转义符号。
unicode
(这儿不需要新起一小节,所以没有加横线,unicode是字符串的基础。)
介绍一下unicode,世上本来是没有unicode的,计算机刚出现时,用到的字符并不多,数字+字母+标点符号+控制字符,用7个bit位128位足够表示了,这就是ASCII码。
随着计算机的普及,世界上到处的人都开始使用计算机,这时候就出现麻烦了,因为世界上有上百种语言文字,而这些语言文字居然在电脑里面没法表示,也就是不管是哪里的人,讲什么语言,使用计算机就都必须按照英语来,这样对于从来没学过英语的人民就麻烦了,用电脑前需要学英语?后来就有人设想,为什么不把世界上所有的字符进行编号?这样用编号就能表示世界上所有的字符,这样电脑就可以显示各种字符,这样多好?然后有人就将每个字符用4个字节表示,这样4个字节,2^32数量接的字符都可以表示出来。这就是unicode编码,也叫做UTF-32或UCS-4. 这时候可以再回顾一下go中的新增类型rune,rune本质上就是int32,每个字符的unicode编码就是rune。这样就一下子彻底理解rune了吧。
不过这时候故事还没有结束,因为我们平时主要使用的字符并不多,对于英文体系国家,ASCII码足够了,甚至对于所有的计算机用户,最常用的字符也不会超过65536,也就是2^16,这样如果还是每个字符32bit,这会浪费多少空间?所以有一批人就想能否对符号的编码做做修改,不要定长的,常用的字符编码短一点,用7个bit,一般的用2-3字节,最不常用的再用32bit,不过加上区分字节长度的控制字符,会超过32bit。于是著名的Ken Thompson和Rob Pike出来了,制定了一套编码规范,在每个编码前加上几个控制bit,用于表示控制信息。具体点就是编码最高位有多少个1,就表示有多少字节编码,除了首字节,其余字节全部以10开头。具体点就是:
0xxxxxxx 表示0-127,也就是ASCII码,这样就兼容了ASCII码
11xxxxxx 10xxxxxx 2字节
111xxxxx 10xxxxxx 10xxxxxx 3字节
就是这个规律,最多的情况就是32个x
111111xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx,也就是6个字节。
这个就是今天著名的UTF-8编码,这就是为啥我们用记事本打开一个文件,发现是乱码,鼠标点点,转成UTF8,瞬间就是友好的字符出现的原因了。这里面每个编码值也叫作rune,不过仍然是4字节的。
这时候我们可以再来讨论一下len这个函数。
package main
import (
"fmt"
)
func main() {
var i string = "Hello, 中国"
n := 0
for range i {
n++
}
fmt.Println(len(i), n)
}
fox@fox:~/Documents/Go/chapter3/day1$ ./string
13 9
看到了吧,len是计算字符个数,每个汉字转换成UTF8会是3个字节,所以一共13字节,而range这个函数会对字符串进行解码,也就是在遍历的时候,会将字符当做rune来遍历,一个汉字就是一个rune。
这时候再来一个要点,将整数传给字符串,go会把该整数作为一个rune,也就是unicode编码,看下例子:
package main
import (
"fmt"
)
func main() {
var me string = string(65)
fmt.Println(me, string(11111111))
}
fox@fox:~/Documents/Go/chapter3/day1$ ./string
A �
对于非法rune就是用一个黑砖石包起来的空问号样子。
现在介绍些字符串的操作吧。
有几个操作字符串的包,bytes,strings,strconv,unicode。strings里面包含了一些字符串基本操作的函数。bytes包的功能功能和strings差不多,区别是bytes的操作对象是[]byte, 还有一点,字符串的修改,截取,追加都会涉及到空间分配和拷贝操作,而bytes里面提供了一个bytes.Buffer,可以随意修改,而不会有分配和拷贝操作。unicode里面提供的是一些判断是否是字母,是否是数字等基础字符处理函数。看一个例子吧,包含上面的bytes包。
File Edit Options Buffers Tools Help
package main
import (
"fmt"
"bytes"
)
func main() {
var str1 string = "machine learning"
var bs []byte = ([]byte)(str1)
fmt.Println(str1, string(bs))
var str = covertIntToString([] int )
fmt.Println(str)
}
func covertIntToString(digits []int) string {
var buf bytes.Buffer
buf.WriteByte('[')
for i,v := range digits {
if i > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%d", v)
}
buf.WriteByte(']')
return buf.String()
}
fox@fox:~/Documents/Go/chapter3/day1$ ./string
machine learning machine learning
[1, 2, 3, 4, 23]
在字符串处理中,还有一种情况,就是数字和字符串的来回转换,现在介绍常见的两种:
package main
import (
"fmt"
"strconv"
)
func main() {
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(x,y)
int2str := strconv.Itoa(x)
str2int,_:= strconv.Atoi(int2str)
fmt.Println(int2str, str2int)
}
fox@fox:~/Documents/Go/chapter3/day1$ ./string2int
123 123
123 123
常量
常量就是编译期间值已经确定的变量,而且后续不可以更改。在go中常量就是用const修饰的变量。这点和c++中类似,介绍点不一样的,在go中,多个常量可以一块声明,而且可以忽略其中一些变量的赋值,第一个始终需要赋值。看一个例子:
package main
import "fmt"
const (
a = 1
b
c = 2
d
)
func main() {
fmt.Println(a,b,c,d)
fmt.Printf("%T, %T, %T, %T\n",a,b,c,d)
}
fox@fox:~/Documents/Go/chapter3/day1$ ./constExample
1 1 2 2
int, int, int, int
可以按照,如果常量安祖赋值的话,未给值的就直接按照前面一个来了。这儿了解下%T,这个可以打印变量类型。
这时候可能让我们想起了枚举,我们有时希望值可以一次增长,那有没有方法呢?有,就是iota。iota就是常量产生器,使用它可以达到和枚举一样的效果:
import (
"fmt"
)
type WEEKDAY int
const (
SUNDAY WEEKDAY = iota
MON
TUE
WEN
THU
FRI
SAT
)
func main() {
fmt.Println(SUNDAY, MON, TUE,WEN, THU, FRI, SAT)
}
fox@fox:~/Documents/Go/chapter3/day1$ ./enumExam
0 1 2 3 4 5 6
untyped constants
go中还支持了一种常量,比如下面所示:
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
这时候e,pi明显不是float64了,在go中,这叫做untyped floating-point,一种有6种,untyped boolean,untyped integer,untyped rune,untyped floating-point,untyped complex,untyped string,举一个例子就是我们经常直接用的字面的true,0,'\u0000',0/0,0i, "hello"。和基本类型是对应的。那和基本类型有啥区别呢?至少对于浮点,untyped 可以表示更大的精度。
总结
这篇主要就是介绍了下go中的基本类型,大体都是一样的,了解下新增的complex和rune即可。对于字符串操作那块,介绍了下从ASCII,到unicode UTF-32,到UTF-8,作为一个背景知识。常量那块介绍了下常量产生器iota,效果和枚举一样。其余就可以按正常理解了。
领取专属 10元无门槛券
私享最新 技术干货