3、基础数据类型
- 基础数据类型:数字、字符串、布尔型
- 复合类型:数组、结构体
- 引用类型:指针、切片、字典、函数、通道
- 接口类型
3.1 整型
有符号、无符号 int8/int16/int32/int64 uint8/uint16/uint32/units 64
Unicode字符rune类型是和int32等价的类型,通常用来表示一个Unicode码点,这两个名称可以互换使用。
byte和unit8是等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。
一种无符号的整数类型unitptr,没有指定具体的bit大小但是足以容纳指针。只有在底层编程时才需要。
int 和int32也是不同的类型,即使int的大小也是32bit。需要显式类型转换。
二元运算符:算术运算、逻辑运算、比较运算(优先级顺序)
%取模运算符的符号和被取模数的符号总是一致的。仅用于整数间的运算。 -5%3=-2
数据溢出
^ //位运算XOR,作为二元运算符时是按位异或,用作一元运算符时表示按位取反
&^ //位清空,按位清零
z=x &^ y//y=1,z=0;y=0,z=x
倾向于使用有符号的int类型
八进制前缀0,十六进制前缀0x
o:=0666
fmt.Printf("%d %[1]o %#[1]o\n",o)//438 666 0666
[1]:再次使用第一个操作数
#:输出时生成前缀
字符使用%c参数打印,用%q参数打印带单引号的字符
3.2 浮点数
float32/float64
%g 打印浮点数
对应表格的数据,使用%e(带指数)或%f形式打印可能更合适
math.IsNaN用来测试一个数是否是非数NaN
math.NaN返回非数NaN对应的值
3.3 复数
complex64/complex128 real()实部 imag()虚部
math/cmplx包提供了复数处理的很多函数
3.4 布尔型
true/false
3.5 字符串
字符串是一个不可改变的字节序列。但可以给一个字符串变量分配一个新字符串值。
不变性意味着如果两个字符串共享相同的底层数据的话也是安全的。
文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列
s:="left foot"
t:=s
s+=", right foot"
s[0]='L'//编译错误
3.5.1 字符串面值
将一系列字节序列包含包含在双引号,“hello,world”
Go语言源文件总是用UTF8编码,并且Go语言的文本字符串也以UTF8编码的方式处理,因此我们可以将Unicode码点也写到字符串面值中。
在一个双引号包含的字符串面值中,可以用以反斜杠\开头的转义序列插入任意的数据。
\a | 响铃 |
---|---|
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 制表符 |
\v | 垂直制表符 |
’ | 单引号(只用在‘"形式的rune符号面值中) |
‘’ | 双引号(只用在“…”形式的字符串面值中) |
\ | 反斜杠 |
可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转移形式\xhh,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的o数字(0到7),但不能超过\377(对应一个字节的范围,十进制是255)。每一个单一的字节表达一个特定的值。 | |
一个原生的字符面值形式是’…',使用反引号代替双引号。没有转义操作,全部的内容都是字面的意思,包含退格和换行。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的。 | |
原生字符串面值用于编写正则表达式会很方便,因为正则表达式往往会包含很多反斜杠。 |
const GoUsage=’Go is a tool for managing Go source code.
Usage:
go command [arguments]
...’
3.5.2 Unicode
如何有效处理这些包含了各种语言的丰富多样的文本数据呢?
Unicode,它收集了这个世界上所有的符号系统,包括重音符号或其他变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型。(rune是int32等价类型)
我们可以将一个符文序列表示一个int32序列。编码方式:UTF-32或UCS-4,这种方式比较简单统一,但是它会浪费很多存储空间。
大数据计算机可读的文本是ASCII字符,本来每个ASCII字符只需要8bit或1字节就能表示。常用的字符也远少于65536,16bit编码方式就能表达常用字符。
3.5.3 UTF-8
UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。
每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。
如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。
如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头
变长的编码无法直接通过索引来访问第n个字符,但有优点:
- UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步
- 它可以通过向前回溯最多2个字节就能确定当前字符编码的开始字节的位置
- 前缀编码,当从左向右解码时不会有任何歧义也并不需要向前查看。
- 没有任何字符的编码是其他字符编码的子串,或是其他编码序列的字符,因此搜索一个字符时只要搜索它的字节编码序列即可。
- UTF8编码的顺序和Unicode码点的顺序一致,可以直接排序UTF8编码序列。
- 没有嵌入NUL(0)字节,可以很好兼容那些使用NUL作为字符串结尾的编程语言。
Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(区分字母和数组、字母的大写和小写转换),unicode/utf8包提供了用于rune字符序列的UTF8编码和解码的功能。
Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:\uhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数组,一般很少使用32bit的形式。每一个对应码点的UTF8编码。
下面的字母串面值都表示相同的值:
Unicode转义也可以使用在rune字符中。下面三个字符是等价的:
对于小于256码点值可以写在一个十六进制转义字节中,例如\x41对应字符’A’,但对于更大码点则必须使用\u或\U转义形式。
得益于UFT8编码优良的设计,诸多字符串操作都不需要解码操作。
不用解码直接测试一个字符串是否是另一个字符串的前缀:
func HasPrefix(s,prefix string) bool{
return len(s)>=len(prefix)&&s[:len(prefix)]==prefix
}
后缀测试:
func HasSuffix(s,suffix string) bool{
return len(s)>=len(suffix)&&s[len(s)-len(suffix):]==suffix
}
包含子串测试:
func Contains(s,substr string) bool{
for i:=0;i<len(s);i++{
if HasPrefix(s[i:],substr){
return true
}
}
return false
}
对于UTF8编码后文本的处理和原始的字节处理逻辑是一样的。
内存表示形式
import "unicode/utf8"
s:="Hello, 世界"
fmt.Println(len(s))//13字节
fmt.Println(utf8.RuneCountInString(s))//9个Unicode
为了处理这些真实的字符,我们需要一个UFT8解码器。
for i := 0; i < len(s); {
// 返回字符本身,字符采用UTF8编码后的编码字节数目
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\n", i, r)
i += size
}
Go语言的range循环在处理字符串时,会自动隐式解码UFT8字符串。
for i, r := range "Hello, 世界" {
fmt.Printf("%d\t%q\t%d\n", i, r, r)
}
统计字符串中字符的数目:
n := 0
for _, _ = range s {
n++
}
n := 0
for range s {
n++
}
utf8.DecodeRuneInString解码或在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成Unicode字符\uFFD
UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,rune大小一致,支持数组索引和方便切割。
将[]rune类型转换应用到UTF8编码的字符串,将返回字符串编码的Unicode码点序列:
// "program" in Japanese katakana
s := "プログラム"
//%x参数用于在每个十六进制数字前插入一个空格
fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0" r := []rune(s)
fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]"
//如果将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码
fmt.Println(string(r)) // "プログラム"
//将一个整数转型为字符串:生成以只包含对应Unicode码点字符的UTF8字符串
fmt.Println(string(65)) // "A", not "65"
fmt.Println(string(0x4eac)) // "京"
3.5.4 字符串和Byte切片
- strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能
- bytes包,针对[]byte类型提供很多类似功能的函数。字符串是只读的,逐步构建字符串会导致很多分配和复制。bytes.Buffer类型将会更有效
- strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换
- unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,用于字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。
basename函数
basename(s) 将看起来像是系统路径的前缀删除,同时将看似文件类型的后缀名部分删除
fmt.Println(basename("a/b/c.go")) // "c"
fmt.Println(basename("c.d.go")) // "c.d"
fmt.Println(basename("abc")) // "abc"
basename1.go
func basename(s string) string {
// Discard last '/' and everything before.
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' {
s = s[i+1:]
break
}
}
// Preserve everything before last '.'.
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '.' {
s = s[:i]
break
}
}
return s
}
basename2.go
func basename(s string) string {
slash := strings.LastIndex(s, "/") // -1 if "/" not found
s = s[slash+1:]
if dot := strings.LastIndex(s, "."); dot >= 0 {
s = s[:dot]
}
return s
}
一个字节slice的元 素则可以自由地修改
字符串和字节slice之间可以相互转换:
s := "abc"
//分配了一个新的字节数组用于保存字符串数据的拷贝,然后 引用这个底层的字节数组
b := []byte(s)
//构造一个字符串拷贝,以确保s2字符串是只读的
s2 := string(b)
为了避免转换中不必要的内存分配,bytes包和strings同时提供了许多实用函数。下面是 strings包中的六个函数:
func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) string
bytes包中也对应的六个函数:
func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte
它们之间唯一的区别是字符串类型参数被替换成了字节slice类型的参数。
bytes包还提供了Buffer类型用于字节slice的缓存。一个Buffer开始是空的,但是随着string、 byte或[]byte等类型数据的写入可以动态增长,一个bytes.Buffer变量并不需要初始化,因为零值也是有效的。
3.5.5 字符串和数字的转换
由strconv包提供字符串和数值之间的转换
将一个整数转为字符串,一种方法是用fmt.Sprintf返回一个格式化的字符串;另一个方法是用strconv.Itoa(“整数到ASCII”):
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"
FormatInt和FormatUint函数可以用不同的进制来格式化数字:
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
fmt.Printf函数的%b、%d、%o和%x等参数提供功能往往比strconv包的Format函数方便很 多,特别是在需要包含附加额外信息的时候:
s := fmt.Sprintf("x=%b", x) // "x=1111011"
如果要将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数,还有用于解析无 符号整数的ParseUint函数:
x, err := strconv.Atoi("123") // x is an int
//第三个参数是用于指定整型数的大小;例如16表示int16,0则表示int。
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits
fmt.Scanf来解析输入的字符串和数字,特别是当字符串和数字混合在一行的 时候,它可以灵活处理不完整或不规则的输入。
3.6 常量
常量表达式的值在编译器计算,而不是在运行期
const pi = 3.14159 // approximately; math.Pi is a better approximation
批量声明多个
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
常量可以是构成类型的一部分,例如用于指定数组类型的长度:
const IPv4Len = 4
// parseIPv4 parses an IPv4 address (d.d.d.d).
func parseIPv4(s string) IP {
var p [IPv4Len]byte
// ...
}
一个常量的声明也可以包含一个类型和一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。
const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay) // "time.Duration 0"
fmt.Printf("%T %[1]v\n", timeout) // "time.Duration 5m0s"
fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s"
如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的
const (
a=1
b
c=2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
3.6.1 iota常量生成器
常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不 用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行, iota将会被置为0,然后在每一个有常量声明的行加一。
type Weekday int
const (
Sunday Weekday = iota//0
Monday //1
Tuesday //2
Wednesday
Thursday
Friday
Saturday
)
例子:给一个无符号整数的最低5bit的每个bit指定一个名字:
type Flags uint
const (
FlagUp Flags = 1 << iota // is up
FlagBroadcast// supports broadcast access capability
FlagLoopback// is a loopback interface
FlagPointToPoint// belongs to a point-to-point link
FlagMulticast// supports multicast access capability
)
随着iota的递增,每个常量对应表达式1 << iota,是连续的2的幂,分别对应一个bit位置
iota局限性:它并不能用于产生1000的幂(KB、MB等),因 为Go语言并没有计算幂的运算符。
3.6.2 无类型常量
六种未明确类型的常量类型:无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串
通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接 用于更多的表达式而不需要显式的类型转换。
只有常量可以是无类型的。