七叶笔记 » golang编程 » 大白话 golang 教程-03-变量和类型系统

大白话 golang 教程-03-变量和类型系统

具有一定功能的程序离不开对数据的操作,以前的章节中,通过给 fmt.Println 函数直接的传递 “hello world” 来实现打印的功能,”hello world” 就是这里的数据,为了更好的表达数据的行为和优化程序的执行,需要给数据加上类型定义。”hello world” 的类型就是字符串,go 语言不仅支持字符,还支持数字、字节、数组、结构、接口等各种派生类型,以后会一一的接触到。

对数据的操作需要把它放到变量里,go 语言可以根据数据的值自动推导变量的类型,比如:

 func printHello() {
  str := "hello" // 通过 "hello" 推导出 str 是一个字符串类型的数据
  fmt.Printf(str)
}  

怎么知道 str 就是一个字符串类型呢? 可以通过 fmt 包的格式化打印函数来查看,修改程序代码如下:

 func printHello() {
  str := "hello"
  fmt.Printf("%T\n", str) // %T 用来显示 str 的类型,结果显示 string (\n 表示打印换行符)
  fmt.Printf(str)
}  

我们也可以明确的去表明一个数据的类型:

 func printWorld() {
  var str string // 明确的定义 str 是一个字符串类型,具有一个默认的空值
  str = "world"  // 改变 str 这个数据的值为 "world" 
  // var str string = "world" // 可以申明变量同时赋初值
  fmt.Printf(str)
}  

字符串类型是字符的集合,字符类型使用两个单号 ” 表示,在 go 语言中字符类型是 uint8,也叫 byte 字节类型,用于表示 ASCII 码表里的字符,如下代码的作用是打印一个空白字符:

 func printSpace() {
  var space byte
  space = 32
  fmt.Printf("%c", space) // 打印 (一个空格,看不见)
  fmt.Printf("%T", space) // 打印 uint8
}  

因为在 ASCII 码表里空格的数值是 32,%c 表明把 space 变量当做字符来打印,问题是上面的类型打印为什么不是 byte 而是 uint8 呢? 我明明定义了 space 是 byte 字节类型啊? 在 byte 上点右键或者按 F12 使用 go to definition 文件查看:

 // byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8  

原来 go 语言使用关键字 type 把 uint8 这个 8 位的无符号整型定义成了 byte,byte 就是个昵称(别名),也就说 byte 只是写代码的时候叫 byte,到编译阶段其实都是 uint8 了,为什么要加个别名呢? 因为它的语义性更好,字节类型在程序里太过于常用了,使用 uint8 大家直观上都觉得它只是个数字,但是 byte 有表达字符的意思。byte 真的占用 1 个字节吗? 使用下面的代码验证一下:

 fmt.Printf("%d", unsafe.Sizeof(space)) // %d 表示期望打印整数,显示结果是 1  

如果用 byte 去申明一个中文可以吗? 如图编辑器提示了错误:

提示 20013 超出了 byte 的范围,20013 是什么? 因为 ASCII 码表使用 8 位最多表示 256 种字符,可是每个国家都有那么多的文字,于是有了 Unicode 编码集,对世界上大部分的文字进行了整理、编码,让每个符号都有独一无二的编码,让计算机能够处理这些非拉丁字母的文字,这样就不会出错了。由于 Unicode 实现了编码的唯一性和规范,但是在存储设计的并不好,后来就有了 UTF-8 编码的统一规则,对编码和存储都指定了简洁的规范,实现了互联网文字的普及。go 对 UTF-8 支持非常的好。上面文字 中 的UTF-8 编码就是 20013。

类型 uint8 的最大值是二进制 1111 1111 = 255(2 的 8 次方减 1),它不能表示 20013 这么大的数,直接就被编辑器检测出来了,所以报错。

你可能看到,在前面 type byte = uint8 字节别名定义下还有一个定义:

 // rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32  

rune 原意是有魔力的符号,int32 占用 4个字节,所以把 byte 换成 rune 就没有问题了。根据 rune 的定义,可以知道它占用了 4 个字节。

 func printChinese() {
  var ch1 rune = '中'
  var ch2 rune = '国'
  fmt.Printf("\n%c%c\n", ch1, ch2)
}  

除了 uint8,类似的还有 uint16、uint32、uint64,int8、int16、int32、int64 表示不同的位数,u 大头表示无符号整型数字,为了处理小数(浮点数)还有 float32、float64,处理虚数 complex64、complex128 等。

当使用 uint8 和 uint16 有什么区别呢?

第一: 他们表示的范围不同,如果超出了它们的范围,会造成溢出造成非期望的数值。

第二: 他们占用的内存空间不同,运行的时候编译器会为变量开辟内存空间来存放它们的值,位数越大需要的内存空间越多。

 func numberOverflow() {
  var num1 uint8 = 50 // 最大表是 256
  var num2 uint8 = 50 // 最大表是 256
  var num3 uint8

  num3 = num1 * num2
  fmt.Printf("%d", num3) // 期望是 2500,结果显示 196,改为 uint16 则正常
}  

因为 50 的二进制是 110010,110010 和 110010 的乘积(按位相乘再相加)是 100111000100 = 2500,但是 num3 只有 8 位,所以截取末尾的 8 位后结果是 11000100 = 196。所以要选择合适的类型,太大了可能造成浪费给内存分配和回收造成压力,太小了可能可能会造成 bug 产生意料之外的结果。

这些只是 go 的简单数据类型,简单类型还有一个 bool,表示真假,经常用到逻辑判断中。

 func printBool() {
  b := true
  fmt.Printf("%T", b) // 打印 bool 
}  

字符还有一些其他的表示形式,比如 ‘\x20’ 也是空格,\x 后跟一个 16 进制数,字符串也可以用 “ 反斜杠来保留格式,不过这些不用去记忆,遇到了再学习或者查询 go 语言的相关知识。

 func printInitalText() {
  str := `I hold keep
      myself,
            \r\n\t\b
can you?
  `
  fmt.Println()
  fmt.Printf(str)
}  

这段文本会按照编写的格式打印出来,带有转义的 \r 回车 \n 换行 \t 跳格 \b 回退 也会原样输出。

本章节的代码

相关文章