GO自学笔记:数据类型

141 阅读8分钟

环境安装

go下载地址:golang.google.cn/dl/

goland下载地址:www.jetbrains.com.cn/go/

详细步骤是百度的,就不记录了。

开始学习

数据类型

基本数据类型

数值型

类型描述取值范围
int有符号,32位或64位整型(根据操作系统而定)32位: -2^31 ~ 2^31-1
64位:-2^63 ~ 2^63-1
int8有符号,8位整型-2^7 ~ 2^7-1
int16有符号,16位整型-2^15 ~ 2^15-1
int32有符号,32位整型-2^31 ~ 2^31-1
int64有符号,64位整型-2^63 ~ 2^63-1
uint无符号,32位或64位整型(根据操作系统而定)2位: 0 ~ 2^31-1
64位:0 ~ 2^63-1
uint8无符号,8位整型0 ~ 2^7-1
uint16无符号,16位整型0 ~ 2^15-1
uint32无符号,32位整型0 ~ 2^31-1
uint64无符号,64位整型0 ~ 2^63-1
float32有符号,单精度(转换为科学计数法后,精度大约为6位十进制数)-3.403E38 ~ 3.403E38
float64有符号,双精度(转换为科学计数法后,精度大约为15位十进制数)-1.798E308 ~ 1.798E308
complex6432位复数
complex12864位复数
byte类似uint8
rune类似int32
uintptr无符号整型,用于存放一个指针

字符型

  • 没有专门的字符型,一般用byte来保存单个字母字符
  • 字符串:string

布尔型

  • bool

复杂(派生)类型

指针

  • 声明指针:var name *type(name是指针变量名,type是指针类型,如int等)
  • 指针赋值:name = &a(&a就是获取变量a的内存地址,即指针)
  • 使用指针:fmt.Printf(*name)(*name访问name内存地址处的值)
  • 空指针:nil
  • 指针数组:var ptr [3]*int
  • 指向指针的指针:var ptr **int
var a int 
var ptr *int 
var pptr **int
a = 100 // 变量
ptr = &a // 指针
pptr = &ptr // 指向指针的指针
  • 指针作为函数参数:func test(x, y *int) {}

数组

  • 声明数组:var name [SIZE]type(name是变量名,SIZE是数组长度,type是数组类型)
  • 数组赋值:var a = [3]int{1, 2, 3}
  • 单个赋值:a[0] = 4
  • 不指定长度:var a = []int{1, 2, 3}(根据赋值的长度确定数组长度,此处为3)
  • 取值:a[0](a为变量名, 0是索引, 索引从0开始, 最多为数组长度 - 1)
  • 获取数组长度:len(a)
  • 多维数组:var name [SIZE1][SIZE2]...[SIZEN] type
   var a = [3][2]int{{1, 2}, {3, 4}, {5, 6}} // 3行2列的数组
   fmt.Printf("%d", a[0][0]) // 打印第一行第一列的值,1
  • 向函数传递数组:
func main() {
    var a = [5]int{1, 2, 3, 4, 5}
    var b = []int{6, 7, 8}
    foo(a)
    bar(b)
}
func foo(param [5]int){ // 指定参数数组长度
    fmt.Printf("%d\n", param[0])
}
func bar(param []int){ // 不指定参数数组长度
    fmt.Printf("%d\n", param[0])
}

结构体

  • 定义结构体:
type Test struct {
   name string
   age int
}
  • 访问成员:
var test Test
test.name = "小明"
test.age = 20
fmt.Printf("姓名: %s, 年龄: %d", test.name, test.age)
  • 结构体作为函数参数:
func testFunc(test Test){}
  • 结构体指针
var ptr *Test
ptr = &test
fmt.Printf("姓名: %s, 年龄: %d", ptr.name, ptr.age)

函数

  • 函数定义: func function_name( [parameter list] ) [return_types]{ 函数体 }
    • function_name: 函数名;
    • parameter list: 参数列表;
    • return_types: 返回值类型;
    • 函数体: 函数具体的实现
func add(a, b int) int {
    return a + b
}
  • 函数调用:
func main() {
    var a, b int = 1, 2
    var c = add(a, b)
    fmt.Printf("%d", c)
}
  • 函数返回多个值:
func swap(a, b int) (int, int) {
    return b, a
}
func main() {
    var a, b int = 1, 2
    fmt.Printf("before swap: a=%d, b=%d\n", a, b)
    a, b = swap(a, b)
    fmt.Printf("after swap: a=%d, b=%d\n", a, b)
}
  • 值传递和引用传递:go默认使用值传递(函数调用不会修改原来的值),如果需要使用引用传递,可以将参数设为指针
// 值传递
func swap(a, b int) {
    var temp = a
    a = b
    b = temp
}
// 引用传递
func swap1(a, b *int) {
    var temp = *a
    *a = *b
    *b = temp
}
func main() {
    var a, b int = 1, 2
    fmt.Printf("before swap: a=%d, b=%d\n", a, b)
    swap(a, b)
    // 此时打印的值跟上面是一样的
    fmt.Printf("after swap: a=%d, b=%d\n", a, b)
    swap1(&a, &b)
    // 此时打印的是交换后的值
    fmt.Printf("after swap: a=%d, b=%d\n", a, b)
}
  • 函数作为值:
func main(){
   i := 0
   // 函数
   increase := func() int {
      i++
      return i
   }

   /* 使用函数 */
   fmt.Println(increase())
   fmt.Println(increase())
   fmt.Println(increase())
   fmt.Println(increase())
   // 打印结果: 1 2 3 4
}
  • 闭包:匿名函数
// 返回一个匿名函数
func increase() (func() int) {
   i := 0
   return func() int {
       i++
       return i
   }
}
func main() {
   inc1 := increase()
   fmt.Println(inc1())
   fmt.Println(inc1())
   fmt.Println(inc1())
   // 打印结果 1 2 3
   inc2 := increase()
   fmt.Println(inc2())
   fmt.Println(inc2())
   // 打印结果 1 2
}

方法:Go 没有类。不过可以为结构体类型定义方法。方法就是带有接收者的函数。方法接收者在它自己的参数列表内,位于func关键字和方法名之间。

  • 方法声明及调用:
// 定义结构体
type Circle struct {
    radius float64
}
// 定义结构体的方法
func (c Circle) getArea() float64 {
    //c.radius 即为 Circle 类型对象中的属性
    return 3.14 * c.radius * c.radius
}
func main() {
    var c1 Circle 
    c1.radius = 10.00 
    // 方法调用 c1.getArea()
    fmt.Println("Area of Circle(c1) = ", c1.getArea())
}

切片

  • 切片是对数组的抽象,可以理解为具有动态长度的数组
  • 切片初始化:
// 直接初始化
s := []int{1,2,3}

// 引用数组初始化
// 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片,endIndex不能超过数组长度
s := arr[startIndex:endIndex]
// 从startIndex到len(arr) - 1
s := arr[startIndex:]
// 从0到endIndex
s := arr[:endIndex]
// 从0到len(arr) - 1
s := arr[:endIndex]

// 用make创建切片
// `make` 函数会分配一个元素为零值的数组并返回一个引用了它的切片,此切片未指定cap,即动态长度:len(a) = 5, cap(a) = 5, a = [0,0,0,0,0]
a := make([]int, 5)
// 要指定长度,可传入第三个参数:len(a) = 5, cap(a) = 10, a = [0,0,0,0,0]
// len是获取当前长度,cap是获取最大长度
a := make([]int, 510)
  • 空切片(nil):var numbers []int len(numbers) = 0, cap(numbers) = 0, numbers = []
  • 追加:func append(s []T, vs ...T) []T: 第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾,返回结果是一个包含原切片所有元素加上新添加元素的切片
  • 复制:func copy(dst, src []Type) int:第一个参数是新切片,第二个参数是原切片,代表将src的内容复制到dst,返回值是切片长度len(src)
  • 切片截取:numbers[startIndex:endIndex] 使用方法跟上面引用数组初始化一样

接口

  • 接口就是一个抽象方法集(没有具体实现),其他类型实现了这些方法就相当于实现了接口,无需显式定义
type Phone interface {
    call()
}

type NokiaPhone struct {
}

// 实现方法
func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}
// 实现方法
func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

Map映射

  • Map是用hash表实现的无序的键值对集合。
  • 定义Map
// 使用map关键字,map1是变量名,string是key的数据类型,int是value的数据类型
var map1 map[string]int
// 使用make内建函数,map2是变量名,string是key的数据类型,int是value的数据类型
var map2 make(map[string]int)
// 以上两种方式都是创建了空(nil)Map,其中map关键字创建的是未初始化的,里面没有键值,也不能添加键;
// make会自动初始化,里面没有键,但可以添加键
// 初始化创建,等号前的类型可以忽略,go会进行类型推导
var map3 map[string]int = map[string]int{
    "a": 1,
    "b": 2,
}
  • 元素操作
// 添加或修改
map[key] = value
// 删除
delete(map, key)
// 获取元素
var value = map[key]
// 判断是否存在:通过双赋值检测, 第二个参数ok为true则存在,否则不存在,默认是零值
// 零值: 数值类型为'0'; 布尔类型为'false'; 字符串类型为空字符串""
value, ok := map[key]
// 遍历:使用range关键字
for key := range map {
  fmt.Println("key=%v, value=%v", key, map[key])
}

管道(channel)

  • channel是go语言协程中数据通信的双向通道,类似于一个队列,遵循先进先出原则。管道涉及多线程的知识,这里先记录简单用法
  • 管道定义
// 直接声明:管道类型就是管道保存的值的类型
var 变量名 chan 管道类型
// make声明
管道实例 := make(chan 数据类型)
// 直接声明的管道未初始化,无法使用,需要配合make函数初始化后才能使用
  • 使用管道发送/接收数据: 一个协程发送数据,一个协程接收数据
c := make(chan int)
go func() {
    for {
        n := <- c //收数据
        fmt.Println(n)
    }
}()
c <- 1 //发数据
c <- 2

//防止数据还没有在协程里打印,main函数退出
time.Sleep(time.Millisecond)
  • 关闭管道(关闭并非销毁):close(c)
  • 判断管道是否关闭:
    • n, ok := <- c ok为true是未关闭,否则已关闭
    • for n := range c {} range在管道关闭时会自动退出循环

类型转换

表达式 T(v) 将值 v 转换为类型 T

i := 42
// 将i转换为float64类型
f := float64(i)
// 将f转换为uint类型
u := uint(f)

类型推导

在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。

// i为int
var i = 42
i := 42
// j 也为int
j := i
// k为float64
k := 3.14
// l为complex128
l := 0.867 + 0.5i // complex128
// 打印结果:i: int j: int k: int l: float64 m: complex128
fmt.Printf("i: %T\nj: %T\nk: %T\nl: %T\nm: %T", i, j, k, l, m)

常量声明

const关键字, 常量声明后无法再次修改其值 const a = 42