Primitive Types in Go

Introduction

As a strong  type static language, Go has many primitive types we will care about. In first level, they can be divided into two groups: non-collection and collections(e.g. array, slice, map). In this article we will talk three different non-collection primitive types in Go.

1. Boolean

2. Numerical (integer, float and complex number)

3. Text (string and rune)

 

Boolean

1. Use true and false for boolean

This may sounds crystal forward, but in Go we can only use keywords true & false for boolean type. Not like in some other language we can use 3 or 11 or 19 for true, 0 for false.

For example:

func main() {
      r := 36
      for 3 {
          fmt.Printf("%v, %T\n", r, r)
      }
}
// output
// prog.go:9:2: non-bool 3 (type untyped number) used as for condition   

 

Numerical 

1. Integer

a) Interger can be sign interger or unsign interger

sign integers, there are 5 versions:
  int: has varying size, but mininal int32.
  int8: -128 ~ 127
  int16: -32,768 ~ 32,767
  int32: around 2 billion
  int64: vary large
  use big package from math library can set more larger number

unsign integer, there are four versions:
  uint, uint8, uint16, uint32, we don't have int64.

b) calculations

There are five kinds of calculation we can do in integer: add, substract, multiple, divise, remainder(remainder only exist in integer)

Integer calculation will result integer, the remainder will miss. We can use % operator to get remainder between integers.

func main() {
	a := 3
	b := 10
	fmt.Println(b / a)
	fmt.Println(b % a)
}
// output
// 3, remainder is missing
// 1, get remainder using % operator

Notice integers in same family but with different type can not do caculateion. e.g. int8 + int16 = error

c) bitwise calculations
To keep thing simple, we don't cover bitwise calculations for now.

2.Floating points

a) There are two version of floating points, float32 and float64.

We can use decimal(e.g. 3.14) or exponential(3.14e12, or 3.14E12).

func main() {
	a := 10.1
	b := 10.1E12
	fmt.Printf("%v, %T\n", a, a)
	fmt.Printf("%v, %T", b, b)
}
// output
// 10.1, float64
// 1.01e+13, float64

b) calculations

Except %(remainder operator), they are: add, substract, multiple, divise. And floating points don't have bitwise calculation.

3. complex number

There are not so much language design complex number as a build-in type. From this we can get a glimps of how powerful Go is in science.

a) two versions, complex64 and complex128.

In math, we use float + float i to represent a complex number. So complex number is float32 + float32 = complex64 or float64 + float64 = complex128

func main() {
	a := 1 + 2i
	b := complex(1, 1.2)
	fmt.Printf("%v, %T\n", a, a)
	fmt.Printf("%v, %T", b, b)
}
// output
// (1+2i), complex128
// (1+1.2i), complex128

b) build-in functions

complex() to build a complex number.

real() to get the real part of a complex number.

imag() to get the imaginary part of a complex number.

func main() {
	a := 1 + 2i
	b := complex(1, 1.2)
	fmt.Printf("%v, %T\n", a, a)
	fmt.Printf("%v, %T\n", b, b)
	fmt.Printf("%v, %T\n", real(b), real(b))
	fmt.Printf("%v, %T\n", imag(b), imag(b))
}
// output
// (1+2i), complex128
// (1+1.2i), complex128
// 1, float64
// 1.2, float64

c) calculations

They are: add, substract, multiple, divise. Notice, in math, multiplication and division of two complex number is defined as polynomial style.

 

Text

1. string

String in Go stands for any utf8 character. It's powerful but also makes string can not encode all character available(for that, we have rune).

a) string, collection of letters

String behaves sort of like an array. That is, a collection of letters. We can use [] operator to slice them.

func main() {
	s := "This is a string"
        fmt.Printf("%v, %T", s[2], s[2])
}
// output
// 105, uint8

This result is out of our expect. We are expecting a letter "i". Actually, when we slice a string, in other language we may can get letters, in Go we will get the number of utf8/ ascii code. 105 stands for "i" in uft8/ ascii. And uint8  is alias for byte in Go.

We can use string() to change it back to a string.

func main() {
	s := "This is a string"
        fmt.Printf("%v, %T", string(s[2]), string(s[2]))
}
// output
// i, string

b) string is immutable

Although a string can be slice like an array, but we can't change the value inside.

func main() {
        s := "This is a string"
        s[2] = "u"
        fmt.Printf("%v, %T", string(s[2]), s[2])
}
// two errors:
// first, can not assign a string "u" to  byte s[2]
// second, can not manipulate the value inside a string

c) concat two string with +

A most common task is to concat two string together, in Go we can use operator +.

func main() {
	s := "This is a string"
	s2 := "This is also a string"
        fmt.Println(s + s2)
}
// output
// This is a stringThis is also a string

d) convert string to collection of byte

We can use []byte() to conver a string to collection of bytes. This can make our application work more easily with other applications than we only use string.

func main() {
	s := "This is a string"
	b := []byte(s)
        fmt.Printf("%v, %T", b, b)
}
// output
// [84 104 105 115 32 105 115 32 97 32 115 116 114 105 110 103], []uint8

The result shows a collection of integer numbers, that is ascii/utf8 value for each letter and space in our string s. As we said before, uint8 is alias for byte, so this []uint8 means []byte.

We can exchange []byte and string back and forward as we need without any effort. In some situation, we may say string is collection of byte([]byte), and vice versa.

func main() {
	s := "This is a string"
	b := []byte(s)
        fmt.Printf("%v, %T\n", b, b)
	fmt.Printf("%v, %T", string(b), string(b))
}
// output
// [84 104 105 115 32 105 115 32 97 32 115 116 114 105 110 103], []uint8
// This is a string, string

2.rune

a) use single quote to declare a rune

In Go, we use double quote " to declare a string and use single quote ' to decalre a rune. 

A rune can only be one character. var r rune = 'aa' will return error.

func main() {
	var r rune = 'a'
	r2 := 'a'
	fmt.Printf("%v, %T\n", r, r)
	fmt.Printf("%v, %T", r2, r2)
}
// output
// 97, int32
// 97, int32

In the result we can see the type is int32, that means rune is alias for int32.

b) why we need rune

Slicing a string may result wrong when it is not an english string.

As we can see in the first for-loop, each s[i] is a uint8(byte). That is, out application recognize Chinese character(hanzi) as english letter. But each Chinese character is 3 bytes not 1 byte(english letter). So the second for-loop output completely wrong.

func main() {
	var s string = "劳动光荣"
	b := []byte(s)
	fmt.Printf("%v, %T\n", b, b)
	fmt.Printf("%v, %T\n", string(b), string(b))

	for i := 0; i < len(s); i++ {
		fmt.Printf("%v, %T\n", s[i], s[i])
	}
	
	for i := 0; i < len(s); i++ {
		fmt.Printf("%v, %T\n", string(s[i]), string(s[i]))
	}
}
// output
[229 138 179 229 138 168 229 133 137 232 141 163], []uint8
劳动光荣, string
229, uint8
138, uint8
179, uint8
229, uint8
138, uint8
168, uint8
229, uint8
133, uint8
137, uint8
232, uint8
141, uint8
163, uint8
å, string
Š, string
³, string
å, string
Š, string
¨, string
å, string
…, string
‰, string
è, string
, string
£, string

If we use rune, our application can recognize non-english character well. In for-loop, we have range function to get each rune of non-english character.

Each output of first for-loop is int32, which means rune.

func main() {
	var s string = "劳动光荣"
	for i, v := range s {
		fmt.Printf("%v, %v, %T\n", i, v, v)
	}

	for i, v := range s {
		fmt.Printf("%v, %v, %T\n", i, string(v), string(v))
	}
}
// output
0, 21171, int32
3, 21160, int32
6, 20809, int32
9, 33635, int32
0, 劳, string
3, 动, string
6, 光, string
9, 荣, string

  

Summary

To keep things simple, there are three notice from this article:

1. Can only use keyword true and false for boolen type.

2. Can not calculate different type number even they are in same family. That is int8 + int32 = error.

3. Care about non-english string, we might need help of rune.

上一篇:8.1.4. Serial Types


下一篇:通用类型约束在哪里工作?