Go学习笔记一(变量与类型)

1.1 声明

Go语言中有四个主要的声明:变量(var)、常量(const)、类型(type)、函数(func)。Go程序存储在以.go为后缀的文件中,每一个文件以package声明开头,表明文件属于哪个包。package后面是import声明,然后是包级别的类型、变量、常量、函数的声明。以大写字母开头则为导出的,在包外可见可访问。小写字母开头意味仅在包内可见。

1.2 变量

var关键字声明创建一个具体类型的变量,通用形式如下:

var name type = expression

类型和表达式可以省略其一,类型省略,则变量类型由初始化表达式决定;表达式省略,变量初始化为该类型的零值,Go语言中不存在未初始化变量。

1.2.1 短变量声明

用来声明和初始化局部变量

name := expression

变量类型由expression的类型决定。
短声明不需要声明所有在左边的变量,但至少需要声明一个新变量。

f, err := os.Open(file)
f, err := os.Create(newFile)   //错误,没声明新变量
f, err = os.Create(newFile)   //正确,赋值语句
out, err := os.Create(newFile)   //正确,声明out为新变量

1.2.2 指针

指针的值是一个变量的地址,使用指针,可以在不知道变量名字的情况下读取或更新变量的值。
表达式&x获取指向x的指针,如果这个值叫做p,我们说p指向x,或者p包含x的地址,指向的变量写作*p。

x := 1
p := &x    //整形指针,指向x
*p = 2    //等价于x = 2

传递指针参数给函数,可以让函数间接更新原始变量值。

1.2.3 new函数

内置的new函数也是创建变量的一种方式。表达式new(T)创建了一个未命名的T类型变量并初始化为该类型的零值,返回其地址(*T)

p := new(int)         //p为*int类型

使用new创建的变量和用取地址的普通变量没什么不同,只是不需要创建一个命名的变量。

1.2.4 作用域

声明的作用域是声明在程序文本中出现的区域,是一个编译时属性。

1.3 类型

类型定义变量或表达式应有的特性,例如大小、如何表达及关联的方法等。

1.3.1 类型声明

type声明定义一个新的命名类型,它和某个已有类型使用相同的底层类型。Go中习惯if块中处理错误然后返回。

type name underlying-type

对于每个类型T,都有一个对应的类型转换操作T(x)将值x转换为类型T。如果两个类型有相同的底层类型或者二者都是指向相同底层类型变量的未命名指针类型,则二者可以相互转换。命名类型可以直接使用底层类型的操作和方法。

2.1 数据类型

Go的数据类型分为四大类:基础类型、聚合类型、引用类型、接口类型。

2.1.1 基础类型

基础类型包含数字、字符串和布尔型。

数字

  • 整数。包含有符号整数与无符号整数,有符号整数分为四种大小:8位、16位、32位、64位,用int8、int16、int32、int64表示,对应无符号整数是uint8、uint16、uint32、uint64。
  • 浮点数
  • 复数

布尔值

字符串

越界访问字符串会宕机。字符串不可改变。修改时可以利用字符串与字节slice相互转换:

s := "abc"
b := []byte(s)
s2 := string(b)

[]byte(s)转换会分配新的字节数组,将s含有的字节拷入并生成一个slice引用指向整个数组。

常量

常量需要保证在编译阶段就能计算出该表达式的值,本质上属于基本类型:布尔值、字符串或者数字。
常量声明可以使用常量生成器iota,声明中iota从0开始取值,逐项加1.常量可声明为无类型的。

2.1.2 复合数据类型

复合数据类型包含数组、slice、map和结构体。

数组

数组是具有固定长度且拥有零个或多个相同数据类型元素的序列。新数组元素的初始值为元素类型的零值,可以使用数组字面量来初始化一个数组:

var arr [3]int = [3]int{1, 2, 3}

数组字面量中,如果省略号出现在数组长度的位置,那么数组长度由初始化数组的元素个数决定。上面数组定义可以简化为:

arr := [...]intP{1, 2, 3}

数组长度是数组类型的一部分,[2]int与[3]int是不同的数组类型。数组长度必须是常量表达式,也就是说,该表达式的值在编译期间就能确定。
索引可以按照任意顺序出现,并且可省略,没有指定值的索引位置元素默认被赋值为该元素类型的零值。

数组是可比较的,如果数组的元素类型是可比较的,则数组可比较。若数组长度及元素完全相同,则两个数组相等。

Go数组的传递看成值传递。调用函数时,每个传入的参数会创建一个副本,而不是原始的参数。使用这种方式传递大的数组会很低效,并且函数内部对数组的修改仅影响副本,不影响原始数组,与其他很多语言的实现不同。当然,也可以传递数组指针给函数,这样对数组的修改都会反映到原始数组上面。但因为数组本身不可变,因此无法对其添加或删除元素。

slice

slice表示拥有相同类型元素的可变长度序列,slice通常写成[]T,其中元素类型都是T,看上去像没有长度的数组类型。

slice与数组紧密关联,slice可以用来访问数组的部分或全部的元素,这个数组成为slice的底层数组。slice有三个属性:指针、长度和容量。指针指向数组第一个可以从slice访问的元素。长度指slice中的元素个数,它不能超过slice1的容量。容量的大小是slice的起始元素到底层数组的最后一个元素间元素的个数。len和cap用来返回slice的长度和容量。

一个底层数组可以对应多个slice,可以引用数组的任何位置,彼此之间元素还可以重叠。s[i:j](0<=i<=j<=cap(s))创建一个新的slice,这个新slice引用了s中从i到j-1索引位置的所有元素,s既可以是数组或者数组指针,也可以是slice。

如果slice的引用超过了被引用对象的容量,即cap(s),会导致程序宕机;超过被引用对象的长度,即len(s),最终slice会比原slice长。

slice包含指向数组元素的指针,将slice传递给函数时可以在函数内部修改底层数组的元素。与数组不同,slice不可比较,仅可以和nil做比较。slice类型的零值是nil。值为nil的slice长度和容量都是零。内置函数make可以创建一个具有指定元素类型、长度和容量的slice。

make([]T, len)

map

map是一个拥有键值对元素的无序集合,其中键值是唯一的。map的类型是map[k]v,其中k和v分别是键和值对应的数据类型,键与值都分别拥有各自相同的数据类型,键的类型必须是可以通过==比较的数据类型。内置函数make可以用来创建一个map:

ages := make(map[string]int)

也可以使用字面量来新建一个map:

ages := map[string]int{
  "alice": 31,
  "bob": 24,
}

可以使用内置函数delete来删除一个元素:

delete(ages, "alice")

如果键不在map中,map查找元素返回值类型的零值。

ages["tom"] += 1   //合法,不存在的键取值为0

不能获取map元素的地址,因为随着map的变化会导致已有元素被重新散列,这样会使获取到的地址无效。

map迭代的顺序是不固定的。map类型的零值是nil。在零值map上执行操作是安全的,除了赋值操作,设置map元素之前必须初始化,不然会导致宕机错误。

map取值返回一个布尔值来表示键是否在map中

age, ok := ages["bob"]

map不可比较,仅能与nil比较。map的值本身可以是复合数据类型,例如map或slice。

结构体

结构体是将零个或者多个任意类型的命名变量结合在一起的聚合数据类型,每个变量叫做结构体的成员。下面定义一个结构体类型以及结构体变量:

type Employee struct {
  ID                 int
  Name           string
  Address       string
  Salary           int
  ManagerID   int  
}

var bob Employee
可以通过bob.Name来访问结构体成员,也可以通过指针来访问:

salary := &bob. Salary

点号同样可以用在结构体指针上

var tom * Employee = &bob
tom.Name = "tom"

命名的结构体类型s不可以定义一个拥有相同结构体类型s的成员,也就是说一个聚合类型不可以包含它自己,但是s中可以定义一个s的指针类型,即*s。这样我们可以创建一些递归的数据结构。

type tree struct {
  value           int
  left, right    *tree
}

结构体的零值由结构体成员的零值组成,没有任何成员变量的结构体称为空结构体,写作struct{},它没有长度也不携带任何信息。

结构体类型的值可以通过结构体字面量来设置,即通过设置结构体的成员变量来设置:

type Point struct{X, Y int}
p := Point{1, 2}
p := Print{X: 1}

结构体类型的值可以作为参数传递给参数或者作为函数的返回值,大型结构体通常使用结构体指针的方式进行传递,这种方式可以让函数修改结构体的内容。

如果结构体的所有成员都是可比较的,那么结构体可比较。

结构体可以嵌套,例如:

type Point struct {
  X, Y   int
}

type Circle struct {
  Center Point
  Radius int
}

type Wheel struct {
  Circle Circle
  Spokes int
}

Go允许定义不带名称的结构体成员,仅需指定其类型即可,称为结构体的匿名成员。该结构体成员的类型必须是一个命名类型或者是指向命名类型的指针。

type Circle struct {
  Point
  Radius int
}

type Wheel struct {
  Circle
  Spokes int
}

用结构体嵌套可以让我们直接访问到变量:

var w Wheel
w.X = 8      //等价于w. Circle. Point.X = 8
w.Radius = 1  //等价于w. Circle. Radius = 1

匿名成员拥有与类型同名的隐式名字,访问成员时可以省略中间所有的匿名成员。因为匿名成员拥有该隐式名字,因此不能在一个结构体内定义两个相同类型的匿名成员。由于匿名成员的名字是由类型决定的,其可导出性也是由类型决定的。外层结构体不仅可以获取匿名成员的内部变量,还有其所拥有的方法。

推荐阅读更多精彩内容

  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 70,026评论 26 501
  • Hello World这是开发的一个开发魔咒,几乎所有人都是从这开始的,今天我也从魔咒开始先打印一份Hello W...
    cocoaAhda阅读 659评论 0 2
  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 10,951评论 1 42
  • 草图一张照片,理论记得很多,可惜实践差乎了。知行合一,真不是乱盖的。只是知道知识,而不能践行,并不是真的知道——不...
    易木成华阅读 69评论 0 0
  • 《北京遇上西雅图之不二情书》票房不错。首映日当日过亿,截至 4 月 30 日下午,票房已经超过了 1.7 亿。接下...
    八卦直说阅读 143评论 0 0