整数类型 Link to heading

整数区分有符号和无符号,且有四种大小:

int8, 	int16, 	int32,	int64		// 有符号
uint8, 	uint16,	uint32,	uint64	// 无符号

其它类型说明:

  • intuint,它们的大小相同,32位或64位,与平台有关,不同的编译器即使在相同的硬件上得到的结果可能也不相同

  • rune类型是int32的同义词,表示该值是一个Unicode码点(code point)

  • byteuint8的同义词,表示该值是原始数据(raw data)

  • uintptr也是一个整型类型,但大小不确定,主要用于低级编程(如调用C语言库)。

int, uint, uintptr与带大小的类型如int32, uint64等都是不同的类型,即使底层的类型相同,同时使用时需要做类型转换。

r1, _ := utf8.DecodeRuneInString("世")
fmt.Printf("r1 = %c\n", r1)				// 世

比较运算符

== 	!=	<		<= 	>		>=

所有的基本类型,如布尔、数值、字符串都是可比较的,即同一类型的两个值,可以使用上述比较运算符直接比较。

fmt.Printf("%t\n", 10 < 5)					// false
fmt.Printf("%t\n", 10 < 20.1)				// true
fmt.Printf("%t\n", "abc" >= "ab")		// true

位运算符:

&		位AND,位的交集
|		位OR,位的并集
^		位XOR,用作二元运算符时表示异或,用作单目运算符时表示按位取反
&^	位清空(AND NOT), z=x &^ y, 如果y的位为1,则z对应的位为0,否则与x对应的位相同
<<	位左移
>>	位右移
// bit operation
const b1 uint8 = 1 << 1 | 1 << 5	// set 1 and 5 bit
const b2 uint8 = 1 << 1 | 1 << 2	// set 1 and 2 bit

fmt.Printf("%08b\n", b1)		// 00100010
fmt.Printf("%08b\n", b2)		// 00000110

fmt.Printf("%08b\n", b1 & b2)		// 00000010
fmt.Printf("%08b\n", b1 | b2)		// 00100110
fmt.Printf("%08b\n", b1 ^ b2)		// 00100100
fmt.Printf("%08b\n", b1 &^ b2)	// 00100100
  • %08b:将结果以8位展示,不足8位的用0补齐。
  • 对于无符号数,左移右移都是补0,对于有符号数,左移补0,右移补符号位。所以整数在做位运算时,要注意使用无符号。

在Go语言中,对于不能为负值的整数,一般使用有符号数来表示,比如数组的长度,len函数返回的是int,如果返回的是uint,在与0比较时永远为true:

data := []string{"hello", "world"}
for i := len(data) - 1; i >= 0; i-- {
  println(data[i])
}

类型转换

将一个值从一个类型转换成另一个类型,需要进行显示的类型转换。二元操作符的算术和逻辑运算(移位操作除外),要求两个操作数的类型完全一样。

T(x)表示将x转换成类型T。不同宽度类型的整数之间转换,或者整数与浮点数之间转换,可能会修改变量的值或丢失精度,比如浮点数转换成整数,小数部分会被丢弃。

var n1 int16 = 10
var n2 int32 = 20
var sum int = int(n1) + int(n2)
fmt.Println(sum)		// 30

f1 := 3.1415
i1 := int(f1)
fmt.Println(i1)		// 3

浮点数 Link to heading

浮点数只有两种类型:float32float64,其中float32的精度为6位,float64的精度为15位。因为float32类型的浮点数运算很容易出错,所以,在大多数情况下,应该优先使用float64类型进行浮点数运算。

+Inf-Inf分别表示正无穷、负无穷,即除以0的结果,NaN表示不是一个数值类型,通常为0/0或对*Sqrt(-1)*的结果,任何涉及NaN的比较结果都是false:

var z float64
fmt.Println(z, -z, 1/z, -1/z, z/z)	// 0 -0 +Inf -Inf NaN
fmt.Println(math.IsInf(-1/z, -1), math.IsInf(1/z, 1))	// true true
nan := math.NaN()
fmt.Println(nan == nan, nan < nan, nan > nan)		// false false	false

复数(complex) Link to heading

复数由实数和虚数构成,有两种类型的复数,complex64complex128,对应的组成部分(实数和虚数)的类型为float32float64

complex函数可以创建一个复数,realimag函数获取对应的实数和虚数部分,math/cmplx包提供了对复数操作的函数:

var x complex64 = complex(1, 2)
var y complex64 = complex(3, 4)
var z = x * y

fmt.Println(x, y, z)						// (1+2i) (3+4i) (-5+10i)
fmt.Println(real(z), imag(z))		// -5 10
fmt.Printf("%T %T\n", real(z), imag(z))		// float32 float32

var r = cmplx.Sqrt(-1)
fmt.Println(r)					// (0+1i)

布尔 Link to heading

布尔类型为bool,取值只能为truefalse,布尔变量或表达式可以使用&&||进行组合,并且支持短路

布尔变量不会隐式转换成0或1:

func main() {
	const b bool = true
	if (b) {
		fmt.Println(b)				// true
	}

	fmt.Println(btoi(b))		// 1
	fmt.Println(itob(1))		// true
}

func btoi(b bool) int {
	if b {
		return 1
	}
	return 0
}

func itob(i int) bool {
	return i != 0
}

字符串 Link to heading

字符串类型为string,表示一个不可变的字节序列。在string上的操作都是基于字节(byte)的,比如:

  • len()函数返回字节数
  • s[i:j]操作返回第i(包含)个字节到第j(不包含)个字节间的子串
  • 比较操作(如==<),逐个字节的比较

string的第i个字节并不一定等于第i个字符,因为非ASCII字符码点(rune)需要2个或多个字节。

s := "hello, world"
fmt.Println(len(s))										// 12
fmt.Println(s[0], s[7], s[0] == 'h', s[7] == 'w')		// 104 119 true true

fmt.Println(s[7:])			// world
fmt.Println(s[:5])			// hello
fmt.Println(s[:])				// hello, world

s2 := "hello"
fmt.Println(s > s2)		// true
fmt.Println(s2 == s[:5])		// true

原始(raw)字符串以反引号表示,即**``**,其中的所有字符都不会被转义,因此可以跨行。

const GoRunHelp = `
	usage: go run [build flags] [-exec xprog] package [arguments...]
	Run 'go help run' for details.
	`
fmt.Println(GoRunHelp)

Unicode码点(code point),在Go中叫rune。在UTF-8编码中,对于ASCII字符,一个字节表示一个rune,但其它语言中的有些字符,可能2~3个字节表示一个rune

string变量上使用range循环时,会自动进行UTF-8解码(decoding):

s3 := "hello, 世界"
fmt.Println(len(s3))												// 13
fmt.Println(utf8.RuneCountInString(s3))			// 9
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s3[i:])
fmt.Printf("%d\t%c\n", i, r)
i += size
}	
for i, r := range(s3) {
fmt.Printf("%d\t%c\n", i, r)
}
/*
0	h
1	e
2	l
3	l
4	o
5	,
6
7	世
10	界
*/

将整数转换成字符串时,会将整数解析成UTF-8表示的rune值:

fmt.Println(string(65))				// "A"
fmt.Println(fmt.Sprintf("%d", 65))		// "65"

在对字符串进行操作时,以下4个包很重要:

  • strings:字符串的搜索、替换、连接、包含等函数
  • bytes:对[]byte类型的操作
  • strconvboolintfloatstring之间的互相转换
  • unicode:对rune的操作,包括IsDigit, IsLetter, IsUpper, IsLower等函数

string类型是不可变的,所以不能进行修改,但是[]byte是可以任意修改的。同时bytes包提供了Buffer类型用于对[]byte的高效操作。

为了避免不必要的类型转换以及内存分配,bytes包提供了与strings包中功能相同的工具函数,只是函数参数是[]byte,而不是string,如contains, count()等。

func intsToString(values []int) string {
  var buf bytes.Buffer
  buf.WriteRune('[')
  for i, v := range values {
    if i > 0 {
      buf.WriteRune(',')
    }
    fmt.Fprintf(&buf, "%d", v)
  }
  buf.WriteRune(']')
  return buf.String()
}
fmt.Println(intsToString([]int{1, 2, 3, 4}))		// [1,2,3,4]

将整数转换成字符串有两种方式,使用fmt.Sprintf函数,或者使用strconv.Itoa函数(即integer to ASCII)。将字符串转成整数,可以使用strconv.Atoi函数或者strconv.ParseInt函数:

x := 456
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x))		// 456 456
fmt.Println(strconv.FormatInt(int64(x), 2))		// 111001000
fmt.Println(fmt.Sprintf("x = %b", x))					// x = 111001000

m := "789"
n, _ := strconv.Atoi(m)
p, _ := strconv.ParseInt(m, 10, 64)
fmt.Println(n, p)			// 789 789

常量 Link to heading

const关键字定义常量,即值在编译时即已知。

当定义一组常量时,后定义的常量的表达式可以省略,表示与前一个常量的类型和值相同(所以第一个常量的定义不可省略):

const (
  a = 1
  b
  c = 3
  d
)
fmt.Println(a, b, c, d)				// 1 1 3 3

const声明中,常量生成器iota的起始值为0,后续每一项的值递增:

type Weekday int
const (
  Sunday Weekday = iota
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)
fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)		// 0 1 2 3 4 5 6

参考:《The Go Programming Language》(Alan Donovan, Brian Kernighan) (英文版)