GO语言常见数据类型

GO语言常见数据类型

  • GO语言常见数据类型
    • 基础类型
      • 布尔类型 bool
      • 整型 int8byteint16intuintuintptr等
        • 类型表示
        • 数值运算
        • 比较运算
        • 位运算
      • 浮点类型 float32float64
        • 浮点数表示
        • 浮点数比较
      • 复数类型 complex64complex128
        • 复数的表示
        • 实部与虚部
      • 字符串 string
        • 字符串操作
        • 字符串遍历
      • 字符类型 rune
      • 错误类型 error
    • 复合类型
      • 指针pointer
      • 数组array
        • 元素的访问
        • 值类型
      • 切片slice
        • 创建数组切片
        • 元素遍历
        • 动态增减元素
        • 基于数组切片创建数组切片
        • 内容复制
      • 字典map
        • 变量声明
        • 创建
        • 元素赋值
        • 元素删除
        • 元素查找
      • 通道channel
        • 基本语法
        • select
        • 缓冲机制
        • 超时机制
        • channel的传递
        • 单向channel
        • 关闭channel
      • 结构体struct
      • 接口interface

基础类型

布尔类型 bool

Go语言中的布尔类型与其他语言基本一致,关键字也为bool,可赋值为预定义的true和false,示例代码如下:

var v1 bool //声明后默认为false
v1 = true 
v2 := (1 == 2) // v2也会被推导为bool类型 

布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。以下的示例是一些错误的用法,会导致编译错误:

var b bool 
b = 1 // 编译错误 
b = bool(1) // 编译错误 
以下的用法才是正确的: 
var b bool 
b = (1!=0) // 编译正确 
fmt.Println("Result:", b) // 打印结果为Result: true

布尔可以做3种逻辑运算,&&(逻辑且),||(逻辑或),!(逻辑非)。
比较操作符:<,>, ==,!=, <=, >=。

整型 int8/byte/int16/int/uint/uintptr等

种类
有符号(负号)
int8 int16 int32 int64
无符号(无符号)
uint8 uint16 uint32 uint64
架构特定(取决于系统位数)
int uint
类型别名
Unicode字符rune类型等价int32
byte等价uint8
特殊类型
uintptr,无符号整型,
由系统决定占用位大小,足够存放指针即可,和C库或者系统接口交互

取值范围

具体类型 取值范围
int8 -128到127
uint8 0到255
int16 -32768到32767
uint16 0到65535
int32 -2147483648到2147483647
uint32 0到4294967295
int64 -9223372036854775808到9223372036854775807
uint64 0到18446744073709551615

1.类型表示

需要注意的是,int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你做自动类型转换,比如以下的例子:

var value2 int32 //声明后默认为0
value1 := 64    //value1将会被自动推导为int类型
value2 = value1 //编译错误

使用强制类型转换可以解决这个编译错误:

value2 = int32(value1) //编译通过

2.数值运算

Go语言支持以下的常规整数运算:+、-、*、/、%。%是求余运算。

5 % 3 //结果为:2

3.比较运算

Go语言支持以下几种比较运算符:>、<、==、>=、<=、!=。

4.位运算

Go语言支持以下所示的位运算

运算 含义 样例
x << y 左移 124 << 2 //结果为496
x >> y 右移 124 >> 2 //结果为31
x ^ y 异或 124 ^ 2 //结果为 126
x & y 124 & 2 //结果为 0
x | y 124 | 2 //结果为 126
^x 取反 ^ 124 //结果为-3

浮点类型 float32/float64

浮点型用于表示包含小数点的数据,Go语言中的浮点类型采用IEEE-754标准的表达式。
取值范围

类型 最大值 最小非负数
float32 3.402823466385288598117041834516925440e+38 1.401298464324817070923729583289916131280e-45
float64 1.797693134862315708145274237317043567981e+308 4.940656458412465441765687928682213723651e-324

1.浮点数表示

在Go语言中,定义一个浮点数变量的代码如下:

var fvalue1 float32 //声明后默认为0
fvalue2 := 12.0 //如果不加小数点,fvalue2会被推导为整型而不是浮点型

2.浮点数比较

因为浮点数不是一种精确的表达方式,所以像整型那样直接用==来判断两个浮点数是否相等是不可行的,这可能会导致不稳定的结果。下面是一种推荐的替代方案:

import "math"
func IsEqual(f1,f2,p float64) bool {
    return math.Abs(f1-f2) < p
}

复数类型 complex64/complex128

复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示虚部(imag)。

1.复数的表示

func main() {
    var v1 complex64 //声明后默认为(0+0i)
    v1 = 3.2 + 12i
    v2 := 3.2 + 12i
    v3 := complex(3.2, 12)
    fmt.Println(v1, v2, v3)//输出结果为:(3.2+12i) (3.2+12i) (3.2+12i)
}

2.实部与虚部

对于一个复数 z=complex(x,y),我们可以通过GO语言内置函数real(z)获取复数z的实部,imag(z)获取复数z的虚部。

func main() {
    var v1 complex64
    v1 = 3.2 + 12i
    fmt.Println(real(v1), imag(v1))//输出结果为:3.2 12
}

字符串 string

在所有编程语言中都涉及到大量的字符串操作,可见熟悉对字符串的操作是何等重要。 Go中的字符串和C#中的一样(java也是),字符串内容在初始化后不可修改。 需要注意的是在Go中字符串是有UTF-8编码的,请注意保存文件时将文件编码格式改成UTF-8(特别是在windows下)。

1.字符串操作

平时常用的字符串操作如表2-3所示。

运算 含义 样例
x+y 字符串连接 “Hello”+”123” //结果为Hello123
len(s) 字符串长度 len(“Hello”) //结果为5
s[i] 取字符 “hello”[1] //结果为’e’

更多的字符串操作,请参考标准库strings包。

2.字符串遍历

Go语言支持两种方式遍历字符串。一种是以字节数组的方式遍历:

var str string //声明后默认为"",注意Go语言中没有null的定义
str ="Hello,世界"
n := len(str)
for i := 0; i< n; i ++ {
    ch := str[i] //依据下标取字符串中的字符,类型为byte
    fmt.Println(i,ch)
}

输出结果为:

0 72
1 101
2 108
3 108
4 111
5 44
6 228
7 184
8 150
9 231
10 149
11 140

这个字符串的长度为12,因为每个中文字符在UTF-8中占3个字节。
另一种是以Unicode字符遍历:

str:="Hello,世界"
for i,ch := range str {
    fmt.Println(i,ch)//ch的类型为rune
}

输出结果为:

0 72
1 101
2 108
3 108
4 111
5 44
6 19990
9 30028

以Unicode字符方式遍历时,每个字符的类型是rune,而不是byte。

字符类型 rune

在Go语言中支持两个字符类型,要给是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。
关于rune相关操作,可查阅Go标准库的unicode包。另外unicode/utf8包也提供了UTF-8和Unicode之间的转换。

错误类型 error

error类型本身就是一个预定义好的接口,里面定义了一个method。

type error interface {
    Error() string
}

生成一个新的error并返回,一般有以下几种处理方式:

package main

import (
    "errors"
    "fmt"
)

type Customerror struct {
    infoa string
    infob string
    Err   error  //声明后默认为nil
}

func (cerr Customerror) Error() string {
    errorinfo := fmt.Sprintf("infoa : %s , infob : %s , original err info : %s ", cerr.infoa, cerr.infob, cerr.Err.Error())
    return errorinfo
}

func main() {
    //方法一:
    //采用errors包的New方法 返回一个err的类型
    var err error = errors.New("this is a new error")
    //由于已经实现了error接口的方法 因此可以直接调用对应的方法
    fmt.Println(err.Error())

    //方法二:
    //采用fmt.Errof 将string信息转化为error信息 并返回
    err = fmt.Errorf("%s", "the error test for fmt.Errorf")
    fmt.Println(err.Error())

    //方法三:
    //采用自定义的方式实现一个error的 一个duck 类型
    err = &Customerror{
        infoa: "err info a",
        infob: "err info b",
        Err:   errors.New("test custom err"),
    }

    fmt.Println(err.Error())

}

/*output:
this is a new error
the error test for fmt.Errorf
infoa : err info a , infob : err info b , original err info : test custom err 
*/

golang中的 error package 内容也比较简单,这个package中实现了error中所声明的method(Error)相当于是一个error接口的duck类型。

// Package errors implements functions to manipulate errors.
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

采用fmt.Errorf方法把string类型转化为error类型,在这个方法的内部,先调用fmt包中的Sprintf方法把格式化的输入转化为字符串,在使用 errors.New 方法返回error类型。
采用自定义的error类型可以先判断err的动态类型,再进行下一层的判断。
比如net.Error接口,是一个对原先error接口的再封装。在读取文件的时候判断读取器读取结束的时候的io.EOF。

//io.EOF
var EOF = errors.New("EOF")

//net.Error 
type Error interface {
    error
    Timeout() bool   // Is the error a timeout?
    Temporary() bool // Is the error temporary?
}

复合类型

指针(pointer)

Go中的指针是很容易学习,也是比较有趣的。一些Go编程任务使用指针更容易执行,并且不使用指针不能执行其他任务,例如通过引用调用。 如众所知,每个变量都是一个内存位置,每个内存位置都有其定义的地址,可以使用”和”号(&)运算符来访问它,这个运算符表示内存中的地址。参考下面的例子,它将打印定义的变量的地址:

package main
import "fmt"
func main() {
   var a int = 10 
   var b *int  //声明后默认为nil
   fmt.Printf("Address of a variable: %x\n", &a  )//输出结果:Address of a variable: c42000e2b0
}

如何使用指针?
有几个重要的操作,将非常频繁地使用指针来实现。

  • 定义一个指针变量
  • 将一个变量的地址赋值给一个指针
  • 最后访问指针变量中可用地址的值

这是通过使用一元运算符*来返回位于操作数指定的地址的变量的值。下面的例子使用这些操作:

package main
import "fmt"
func main() {
   var a int= 20   // actual variable declaration 
   var ip *int        // pointer variable declaration
   ip = &a  // store address of a in pointer variable
   fmt.Printf("Address of a variable: %x\n", &a)  // address stored in pointer variable 
   fmt.Printf("Address stored in ip variable: %x\n", ip )// access the value using the pointer 
   fmt.Printf("Value of *ip variable: %d\n", *ip )
}

当上面的代码编译和执行时,它产生结果如下:

Address of var variable: 10328000
Address stored in ip variable: 10328000
Value of *ip variable: 20

在Go语言中的nil指针
Go编译器为指针变量分配一个Nil值,以防指针没有确切的地址分配。这是在变量声明的时候完成的。指定为nil值的指针称为nil指针。
nil指针是在几个标准库中定义的值为零的常量。参考下面的程序:

package main
import "fmt"
func main() {
   var  ptr *int
   fmt.Printf("The value of ptr is : %x\n", ptr  )//输出结果为:The value of ptr is 0,但不能直接做比较
}

在大多数操作系统上,程序不允许访问地址0处的内存,因为该内存是由操作系统保留的。 然而,存储器地址0具有特殊意义; 它表示指针不打算指向可访问的存储器位置。但是按照惯例,如果指针包含nil(零)值,则假设它不指向任何东西。
要检查是否为nil指针,可以使用if语句,如下所示:

if(ptr != nil)     // succeeds if p is not nil
if(ptr == nil)    // succeeds if p is null

数组(array )

数组就是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素的个数被称为数组的长度。

1.元素的访问

可以使用数组下标来访问数组中的元素。下面示例遍历数组并逐个打印。

for i := 0; i< len(array); i++ {
    fmt.Println("Element",i,"of array is",array[i])
}

Go语言海提供了一个关键字range,用于便捷地遍历容器中的元素。上面的遍历可以简化如下:

for i,v := range array {
    fmt.Println("Array element[",i,"]=",v)//数组的range打印出来是有序的
}

2.值类型

需要特别注意的是,在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。

切片(slice)

数组的缺点有:数组的长度在定义之后无法再次修改且数组是值类型,每次传递都将产生一份副本。数组切片(slice)可以弥补数组的不足。初看起来,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针。数组切片的数据结构可以抽象为以下3个变量:

  • 一个指向原生数组的指针;
  • 数组切片中的元素个数;
  • 数组切片已分配的存储空间。

1.创建数组切片

基于数组

 mySlice1 := myArray[:]//基于myArray的所有元素创建数组切片
 mySlice2 := myArray[:5]//基于myArray的前5个元素创建数组切片
 mySlice2 := myArray[5:]//基于myArray第5个元素开始的所有元素创建数组切片

直接创建

mySlice1:= make([]int,5)//创建一个初始元素个数为5的数组切片,元素初始值为0
mySlice2:= make([]int,5,10)//创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间
mySlice3:= []int{1,2,3,4,5}//直接创建并初始化包含5个元素的数组切片

2.元素遍历

传统元素遍历方法如下:

for i := 0; i< len(slice); i++ {
    fmt.Println("Element",i,"of slice is",slice[i])
}

使用range关键字遍历:

for i,v := range slice {
    fmt.Println("slice[",i,"]=",v)//数组切片的range打印出来是有序的
}

3.动态增减元素

与数组相比,数组切片多了一个存储能力(capacity)的概率,即元素个数和分配的空间可以是两个不同的值。数组切片支持Go语言内置的cap()函数和len()函数分别返回数组切片分配的空间大小和存储的元素个数。例如:

mySlice := make([]int,5,10)//cap(mySlice)为10,len(mySlice)为5
mySlice = append(mySlice,1,2,3)//在原mySlice的尾部添加3个元素
mySlice2 := []int{8,9,10}
mySlice = append(mySlice,mySlice2...)

需要注意的是,在的哥参数mySlice2后面加了三个点,即一个省略号,如果没有这个省略号的话,会有编译错误,因为按appand()的语义,从第二个参数起的所有参数都是待附加的元素。mySlice中的元素类型为int,直接传递mySlice2是行不通的。加上省略号相当于把mySlice2包含的所有元素打散后传入。
数组切片会自动处理存储空间不足的问题。如果追加的内容长度超过当前已分配的存储空间,数组切片会自动分配一块足够大的内存。

4.基于数组切片创建数组切片

oldSlice := []int{1,2,3,4,5}
newSlice := oldSlice[:3] //基于oldSlice的前3个元素构建新数组切片

有意思的是,选择的oldSlice元素范围甚至可以超过所包含的元素个数,只要这个选择的范围不超过oldSlice存储能力,那么创建程序就是合法的。newSlice中超出oldSlice元素的部分都会填上0。

5.内容复制

slice1 := []int{1,2,3,4,5}
slice2 := []int{5,4,3}
copy(slice2,slice1)//只会复制slice1的前3个元素到slice2中
copy(slice1,slice2)//只会复制slice2的前3个元素到slice1的前3个位置

字典(map )

map是一堆键值对的未排序集合。

1.变量声明

var myMap map[string]PersonInfo

myMap是声明的变量名,string是键的类型,PersonInfo是值的类型。

2.创建

myMap = make(map[string]PersonInfo)
myMap = make(map[string]PersonInfo,100)//myMap初始存储能力100
myMap = map[string]PersonInfo{
"1234":PersonInfo{"1","Jack","Room 101,..."},
}//创建并初始map

3.元素赋值

myMap["1234"]=PersonInfo{"1","Jack","Room 101,..."}

4.元素删除

delete(myMap,"1234")//从myMap中删除键为"1234"的键值对。

如果”1234”这个键不存在,那么这个调用将什么都不发生,也不会有什么副作用。但如果传入的map变量的值是nil,该调用将导致程序抛出异常。

5.元素查找

value,ok := myMap["1234"]
if ok {//找到了
    //处理找到的value
}

或者

if value,ok := myMap["1234"];ok {//找到了
    //处理找到的value
}

通道(channel )

channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或 多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用 分布式系统的方法来解决,比如使用Socket或者HTTP等通信协议。Go语言对于网络方面也有非常完善的支持。 channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。如果对Unix管道有所了解的话,就不难理解channel,可以将其认为是一种类 型安全的管道。

1.基本语法

声明

//var chanName chan ElementType
var ch chan int
var m map[string] chan bool

定义

ch := make(chan int)

写入

ch <- value

读取

value := <- ch

读取和写入都会导致程序阻塞。

2.select

Go语言直接在语言级别支持select关键字,用于处理异步IO问题。

select {
    case <- chan1:
    //如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    //如果成功向chan2写入数据,则进行该case处理语句
    default:
    //如果上面都没有成功,则进入default处理流程
}

select类似switch语句,但不同在于,select后面不带判断条件,直接去查看case语句,且case语句里必须是一个channel操作。

3.缓冲机制

给channel带上缓冲,可达到消息队列的效果。

c := make(chan int,1024)

上面创建了一个大小为1024的int类型channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。
读取数据时候可以用range关键字实现简便的循环读取。

for i := range c {
    fmt.Println("Received:",i)
}

4.超时机制

//首先,我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool,1)
go func(){
    time.sleep(1e9)
    timeout <- true
}()

//然后我们把timeout这个channel利用起来
select {
    case <- ch:
    //从ch中读取数据
    case <- timeout:
    //一直没有从ch中读取到数据,但从timeout中读取到了数据
}

5.channel的传递

channel本身也是原生类型,与map之类的类型地位一样,也可以通过channel传递。

type PipeData struct {
    value int
    handler func(int) int
    next chan int
}

func handle(queue chan *PipeData){
    for data := range queue {
        data.next <- data.handler(data.value)
    }
}

流式处理

6.单向channel

var ch2 chan<- float64 //ch2是单向channel,只用于写float64数据
var ch3 <-chan int //ch3是单向channel,只用于读取int数据

channel支持类型转换

ch4 := make(chan int)
ch5 := <-chan int(ch4) //ch5就是一个单向的读取channel
ch6 := chan<- int(ch4) //ch6是一个单向的写入channel

基于ch4可以进行初始化,实现最小权限原则

7.关闭channel

close(ch)

如何判断channel是否已经关闭:

if x,ok := <- ch;!ok{
    //关闭处理程序
}

注意:以上判断如果ok为true,则会从ch中取出一个数据,可能会影响数据的连续性。

结构体(struct )

结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。

接口(interface)

接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。

你可能感兴趣的:(Go语言)