05.Go 基本数据类型

Go语言中,数据类型用于声明函数与变量、常量的数据的类型。

Go语言的基本数据类型包括整型、浮点型、字符与字符串、布尔型等,另外,还要了解数据类型之间的转换方法、指针与类型别名的使用等。

1、整型
var value1 int32   //全局声明

func main() {     
    value2 := 64  //函数内部声明
}

有符号整数类型

无符号整数类型

int8(8bit)

unit8

int16(16bit)

uint16

int32 (32bit)

uint32

int64 (64bit)

uint64

Go语言中,除了指定的整型之外,还有int(符号整数)和uint(无符号整数)两种整数类型。在实际开发中,由于编译器和计算机硬件的不同,所以,它们的字节长度也是有所差异的,int和uint所能表示的整数大小在32bit或64bit之间变化。

另外,还有一种无符号的整数类型uintptr,它没有指定具体的bit大小,但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

使用int和uint的情况如下:

  • 程序的逻辑对整型的范围没有特殊需求。例如,对象的长度使用内建len()函数返回,这个长度可以根据不同平台的字节长度进行变化。实际使用中,切片或map的元素数量等都可以用int来表示。
  • 在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不使用int和uint。

整型的位运算符:

05.Go 基本数据类型_第1张图片

2、浮点型

Go语言提供了两种精度的浮点数,即float32和float64:

  • float32的浮点数的最大范围约为3.4e38,常量定义为math.MaxFloat32。
  • float64的浮点数的最大范围约为1.8e308,常量定义为math.MaxFloat64。

在使用浮点型时需要注意以下几点:

  • 浮点数的字面量被自动类型推断为float64类型,例如:var a := 20.00
  • 计算机很难进行浮点数的精确表示和存储,因此两个浮点数之间不应该使用==或!=进行比较操作,如果需要高精度的科学计算,应该使用math标准库。
  • 浮点数在进行声明时,可以只写整数部分或者小数部分,例如:
const e = .71828  //0.71828
const f = 1.      //1

用Printf函数打印浮点数时可以使用“%f”来控制保留几位小数,例如:

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Printf("%f\n", math.Pi)   // 按照默认宽度和精度输出整型
	fmt.Printf("%.2f\n", math.Pi) //按照默认 宽度 、2位精度输出(小数点后的位数)
}

3、字符与字符串

字符串是不可改变的字节序列,字符串可以包含任意数据,但是通常包含可读的文本。字符串是UTF-8字符的一个序列(当字符为ASCII码表上的字符时则占用1B,其他字符根据需要占用2~4B)。

UTF-8是一种被广泛使用的编码格式,是文本文件的标准编码,其中包括XML和JSON在内也都使用该编码。由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用1~4B,这与其他编程语言如C++、Java或Python不同(Java始终使用2B)。Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其他语言那样需要对使用UTF-8字符集的文本进行编码和解码。

字符串中的每一个元素称为“字符”,在遍历或者单个获取字符串元素时可以获得字符。

Go语言的字符有以下两种:

  • uint8类型或byte型,代表ASCII码的一个字符。
  • rune类型,代表一个UTF-8字符,当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型等价于int32类型。
a. 字符串的转义字符

05.Go 基本数据类型_第2张图片

b. 定义多行字符串

在Go语言中,字符串的常见表达方式之一就是使用双引号书写字符串的方式,这种方式被称为字符串字面量(string literal),使用该方式时需要注意双引号字面量不能跨行。如果想要在源码中嵌入一个多行字符串,就必须使用“`”反引号,代码如下:

func main() {
	const str = `第一行
	第二行
		第三行
		\r\n
 	`
	fmt.Println(str)
}

  • 在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
  • 多行字符串一般用于内嵌源码和内嵌数据等
  • 在“`”间的所有代码均不会被编译器识别,而只是作为字符串的一部分。
c. 连接字符串

在Go语言中可以使用“+”连接字符串

func main() {
	s := "adcd 你"
	fmt.Println(s[4:] + "好")
	str := "哎一古," +
		"金社长"
	fmt.Println(str)
}

另外,字符串还可以使用“==”和“<”等符号进行比较,通过比较逐字节的编码获取结果

func compare() {
	a := "你"
	b := "好"
	if a < b {
		fmt.Println(a[0], b[0])
		fmt.Println(a[1], b[1])
		fmt.Println(a[2], b[2])
	}
	c := "a"
	d := "b"
	if c < d {
		fmt.Println(c[0], "小于", d[0])
	}
}

  • 在以上代码中,字符串的大小判断是根据第一字节来判定的,“你好”之所以比“你”“好”小,是因为“你”的第一字节是228,而“好”的第一字节是229。
d. 字符串的修改

在Go语言中,字符串的内容不能修改,这就意味着不能使用a[i]这种方式修改字符串中的UTF-8编码,如果确实要修改,那么可以将字符串的内容复制到另一个可写的变量中,然后进行修改。一般使用[]byte或[]rune类型。

如果要对字符串中的字节进行修改,则转换为[]byte格式,如果要对字符串中的字符进行修改,则转换为[]rune格式,转换过程会自动复制数据。

修改字符串中的字节(使用[]byte):

func mod_byte() {
	a := "Hello 世界!"
	b := []byte(a)        // 转换为[]byte,自动复制数据
	b[5] = ','            // 修改[]byte
	fmt.Printf("%s\n", a) // a 不能被修改,内容保持不变
	fmt.Printf("%s\n", b) // 修改后的数据
}

修改字符串中的字符(使用[]rune):

func mod_character() {
	a := "Hello 世界!"
	b := []rune(a)         // 转换为[]rune,自动复制数据
	b[6] = '中'             // 修改[]rune
	b[7] = '国'             // 修改[]rune
	fmt.Println(a)         // a不能被修改,内容保持不变
	fmt.Println(string(b)) //转换为字符串,右一次复制数据
}

e. 字符串格式化

05.Go 基本数据类型_第3张图片

在Go语言中,单个字符可以使用单引号(')来创建,字符串支持切片操作。但是需要注意的是,如果字符串都是由ASCII字符组成的,可以随便使用切片进行操作;如果字符串中包含其他非ASCII字符,直接使用切片获取想要的单个字符时应十分小心,因为对字符串直接使用切片时是通过字节进行索引的,而非ASCII字符在内存中可能不是由1B组成的。如果想对字符串中的字符依次访问,可以使用range操作符。2

4、布尔型

布尔型的常量和变量也可以通过逻辑运算符(非!、和&&、或||)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的Go语言语句。

逻辑值可以被用于条件结构中的条件语句,以便测试某个条件是否满足。另外,和(&&)、或(||)、相等(==)、不等(!=)属于二元运算符,而非(!)属于一元运算符。这里使用T代表条件符合的语句,用F代表条件不符合的语句。

a. 非运算符(!)

非运算符用于取得和布尔值相反的结果。

!T -> false
!F -> true
b. 和运算符(&&)

只有当两边的值都为true时,和运算符的结果才是true。

T && T-> true
T && F-> false
F && T-> false
F && F->false
c. 或运算符(&&)

只有当两边的值都为false时,或运算符的结果才是false,其中任意一边的值为true就能够使得该表达式的结果为true。

T || T -> true
T || F -> true
F || T -> true
F || F -> false

在Go语言中,&&和||是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值时(&&左边的值为false,||左边的值为true),运算符在右边的表达式将不会被执行。利用这个性质,如果有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧,以减少不必要的运算。

另外,利用括号可以提升某个表达式的运算优先级。在格式化输出时,可以使用%t来表示要输出的值为布尔型。

5、数据类型转换

在必要及可行的情况下,一个类型的值可以被转换成另一种类型的值。类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

Go语言类型转换的基本格式如下:type_name(expression)

a := 5.0
b := int(a)

类型转换只能在定义正确的情况下转换成功:

  • 从一个取值范围较小的类型转换到一个取值范围较大的类型(将int16转换为int32)。
  • 当从一个取值范围较大的类型转换到取值范围较小的类型时(将int32转换为int16或将float32转换为int),会发生精度丢失(截断)的情况。
  • 只有相同底层类型的变量之间可以进行相互转换(如将int16类型转换成int32类型),不同底层类型的变量相互转换时会引发编译错误(如将bool类型转换为int类型)。
func main() {
	// 输出个数值范围
	fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
	fmt.Println("int16 range:", math.MinInt16, math.MaxInt16)
	fmt.Println("int32 range:", math.MinInt32, math.MaxInt32)
	fmt.Println("int64 range:", math.MinInt64, math.MaxInt64)

	// 初始化一个32位整型值
	var a int32 = 1047483647
	// 输出变量的十六进制形式和十进制值
	fmt.Printf("int32: 0x%x %d\n", a, a)

	// 将a变量数值转换为16进制,发生数值阶段
	b := int16(a)
	// 输出变量的十六进制形式和十进制值
	fmt.Printf("int16: 0x%x %d\n", b, b)

	// 将常量保存为float32类型
	var c float32 = math.Pi
	// 转换为int类型,浮点发生精度丢失
	fmt.Println(int(c))
}

05.Go 基本数据类型_第4张图片

根据运行结果,16位有符号整型的范围为-32 768~32 767,而变量a的值1 047 483 647不在这个范围内。1 047 483 647对应的十六进制为0x3e6f54ff,转为int16类型后,长度缩短一半,也就是在十六进制上砍掉一半,变成0x54ff,对应的十进制值为21759。

6、类型别名

类型别名是Go 1.9版本中新增的功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。

在Go 1.9版本之前定义内建类型的代码书写方式如下:

type byte uint8
type rune int32

在Go 1.9版本之后定义内建类型的代码书写方式修改为如下:

type byte = uint8
type rune = int32
a. 类型别名与类型定义

定义类型别名的书写格式如下:type TypeAlias = Type

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。

type name = string  		//类型别名
type name string        //类型声明
  • type name string将name定义为一个新的类型,该类型拥有和string一样的特性,但是两者是不同的类型,不可用“+”进行拼接等运算。
  • type name = string将name定义为string的一个别名,使用name和string相同。二者可以当作同一种类型运算。别名只在源码中存在,编译完成后,不会有别名类型。

类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些:

// 将NewInt定义为int类型
type NewInt int

// 给int取一个别名 IntAlias
type IntAlias = int

func main() {
    // 将 a 声明为 NewInt 类型
    var a NewInt
    // 查看 a 的类型名
    fmt.Printf("a type: %T\n", a)

    // 将 a2 声明为 IntAlias 类型
    var a2 IntAlias
    // 查看 a2 的类型名
    fmt.Printf("a2 type: %T\n", a2)
}

  • a的类型是main.NewInt,表main包下定义的NewInt类型,
  • a2的类型是int,IntAlias类型只会在代码中存在,编译完成时,不会有IntAlias类型。
b. 非本地类型不能定义方法

能够随意为各种类型起名字,是否意味着可以在自己的包中为这些类型任意添加方法呢?

// 定义time.Duration的别名为MyDuration
type MyDuration = time.Duration

// 为MyDuration添加一个函数
func (m MyDuration) EasySet(a string) {
	
}

从以上结果中可以看出,编译出错。编译器提示:不能在一个非本地的类型time.Duration中定义新的方法,非本地类型指的就是time.Duration不是在main包中定义的,而是在time包中定义的,与main包不在同一个包中,因此不能为不在一个包中的类型定义方法。

解决这个问题有以下两种方法:

  • ① 将代码修改为type MyDuration time.Duration,也就是将MyDuration从别名改为类型。
  • ② 将MyDuration的别名定义放在time包中。
c. 在结构体成员嵌入时使用别名

当类型别名作为结构体嵌入的成员时会发生什么情况呢?

// 定义商标结构
type Brand struct {
}

// 为商标结构添加Show()方法
func (b Brand) Show() {

}

// 为Brand定义一个别名FakeBrand
type FakeBrand = Brand

// 定义车辆结构
type Vehicle struct {
	// 嵌入两个结构
	FakeBrand
	Brand
}

func main() {
	// 声明变量a为车辆类型
	var a Vehicle

	// 指定调用FakeBrand的Show
	a.FakeBrand.Show()
	// 取a的类型反射对象
	ta := reflect.TypeOf(a)
	// 遍历a的所有成员
	for i := 0; i < ta.NumField(); i++ {
		// a的成员信息
		f := ta.Field(i)
		// 打印成员的字段名和类型
		fmt.Printf("FieldName: %v, FieldType: %v\n", f.Name, f.Type.Name())
	}
}

  • 第25行,显式调用Vehicle中FakeBrand的Show()方法。
  • FakeBrand是Brand的一个别名,在Vehicle中嵌入FakeBrand和Brand并不意味着嵌入两个Brand,FakeBrand的类型会以名字的方式保留在Vehicle的成员中。

如果尝试将第25行改为:a.Show()编译器将发生错误

  • 在调用Show()方法时,因为两个类型都有Show()方法,所以会发生歧义,证明FakeBrand的本质确实是Brand类型。

你可能感兴趣的:(Go语言从入门到实践,golang)