go程序设计语言第二章-- 程序结构
go程序设计语言第二章-- 程序结构
命名与关键字:
固定关键字25个
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
预定义关键字
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内建函数: make len cap new append copy close delete
complex real imag
panic recover
包内大写开头函数可导出,小写开头不可导出。
goto: 直接跳转到标签处,有时嵌套的for循环也可以在break时指定标签
fallthrough:switch和select的case语句,都是只执行符合条件的那个,自动break,如果希望多个case判断,就在case的逻辑处理后加fallthrough。
iota:在常量const声明多个常量时,iota的值动态变化,可以理解为iota值从0开始,每一行声明时其值加一(声明的第一行时其值为0)。
const (
k = 3 // 在此处,iota == 0
m float32 = iota + .5 // m float32 = 1 + .5
n // n float32 = 2 + .5
p = 9 // 在此处,iota == 3
q = iota * 2 // q = 4 * 2
_ // _ = 5 * 2
r // r = 6 * 2
s, t = iota, iota // s, t = 7, 7
u, v // u, v = 8, 8
_, w // _, w = 9, 9
)
make: 创建slice、map、chan
new: 创建新对象,返回对象的地址,一般为结构体。
len: 数组、slice、map的元素个数;string类型的字节数量;chan中未读的数据数量;
cap: 容量,对于数组,和len相同;对于slice,就是其底层数据的容量;对于chan,就是其容量。
append: 给sliece追加元素;容量足则不扩容,不足则扩容,产生新的底层数组。
copy: 从一个slice 全量copy元素到另一个slice。
close: 关闭一个chan。
delete: 从map中删除一个元素。
声明
四种类型声明:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。
(一个go程序由一个或多个以.go为文件后缀名的源文件构成。每个源文件以包的声明语句开始,说明该包属于哪个源文件。
之后是import语句导入依赖包,然后是包一级的类型、变量、常量、函数申明语句。
包一级声明的名字可以在包范围内的每个源文件中使用
)
变量
var 变量名 类型 = 表达式,如 var i int = 10
类型和表达式可省略,如var i = 10或者 var i
当没有=时,声明后变量自动初始化为默认值,因为go中不存在未初始化的变量(那常量呢?)
包级别声明的变量会在main函数执行前完成初始化。
- 指针
go语言函数可以返回局部变量的地址,因为go编译器会根据逃逸分析选择将变量创在堆还是栈上,之后自动内存回收。
- new函数
new(Type),返回指针类型
- 变量的生命周期和作用域(作用域下面再讲)
变量的声明周期,就是指变量何时被回收。
包级别变量的声明周期:整个程序的运行周期。
局部变量(函数的参数变量和返回值)的生命周期: 从创建一个新变量的声明语句开始,直到该变量不再被使用为止,
然后变量的存储空间被回收。
赋值
map查找、类型断言、通道接收,可以返回一个值或两个值。
v = m[key] // map查找,失败时返回零值
v = x.(T) // type断言,失败时panic异常
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败)
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
类型
类型分类:
- 基本类型(basic type):
string
bool
int8、uint8(byte)、int16、uint16、int32(rune)、uint32、int64、uint64、int、uint、uintptr
float32、float64
complex64、complex128
(其中,byte是uint8的别名,rune是int32的别名) - 组合类型(composite type):
pointer
struct
func
容器,包括array,slice,map
chan
interface{}
上面这些都是go的内置类型,这些类型都对应着一个叫做kind术语的种类,目前kind种类有26个。
概念: 类型定义(type define)
使用type关键字,来自定义新的类型,新类型和原类型属于不同的类型,如
type Myint int
概念: 类型别名(type alias)
使用type 和等号,来定义类型别名,别名类型和原类型属于相同的类型,如:
type name = string
概念: defined type 和 undefined type
一个defined type就是我们前面显示的用type语句定义的类型,比如type myString string, myString就是defined type;或者是一个基本类型,如int、string、bool、float、complext;
一个undefined type就是没有进行type定义的类型,比如一些组合类型的字面量表示形式,如[]int,或者是此字面量的别名,如 type C = []int, C也是一个undefined type。
概念: 底层类型(underlying type)
在go中,每个类型都有一个底层类型。
- 内置类型的底层类型为它自己
- 一个undefined type的底层类型为它自己,如一些组合类型的字面量形式
- 在一个type define中,新类型和原类型共享底层类型.
根据这三个规则,溯源一个类型的底层类型就很简单了:
type (
MyInt int
Age MyInt
)
// 下面这三个新声明的类型的底层类型各不相同。
type (
IntSlice []int // 底层类型为[]int
MyIntSlice []MyInt // 底层类型为[]MyInt
AgeSlice []Age // 底层类型为[]Age
)
// 类型[]Age、Ages和AgeSlice的底层类型均为[]Age。
type Ages AgeSlice
MyInt和int相同,
Age和MyInt相同,MyInt是一个有名字的类型,则继续溯源,->int.
IntSilce和[]int相同,[]int是一个内置类型,则不再溯源
MyIntSlice和[]Myint相同,[]Myint是一个字面量形式,则不再溯源
AgeSlice和[]Age相同,[]Age是一个字面量形式,则不在溯源
Ages和AgeSlice相同,AgeSlice是一个有名字的类型,则继续溯源,->[]Age。
概念: 类型转换
上面提到的底层类型对于类型转换很重要。
如果两个类型的底层类型相同,则可以显式进行转换。
自定义类型语法: ```type 自定义的类型名 底层类型```, 如 type name string,
自定义类型命名一般出现在包一级,因此大写时可以被导出.
两个不同的类型不能直接比较或混在一个表达式计算,即使他们所对应的底层类型相同.
任何类型T都有T(x)的方式来进行类型转换,用于将x转化为T类型。
- 只有当两个类型的底层基础类型相同时,才允许这种转换。
- 数值之间也允许这种转换,这类转换可能改变值的表现,如精度丢失
底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持
像==和<这样的比较运算符将一个有类型命名的变量,与另一个相同类型命名变量进行比较,或者与一个未类型命名的值比较。
但两个不同的已命名变量不能直接比较。
类型命名可以定义方法集,为该类型的值定义新的行为。
包和文件
- 概念:包和其他语言的库或模块概念相同,目的是为了支持模块化、封装、单独编译和代码重用。
- 包的构成:由一个或者多个以.go为后缀的文件构成,这些文件放在一个单独目录中,包名和目录名相同,文件名称不做要求。
每个源文件以包声明语句开始,包内大写变量和函数可导出。
- 包的导入:同一个包只导入一次,导入多个包名冲突的包时,可以将冲突的包名重命名。
- 包的初始化:包级别内变量按照声明出现的顺序一次初始化,复杂的初始化可以写成init()函数,每个包只会被初始化一次。
初始化工作是自下而上进行的,main包最后被初始化。
作用域
作用域,可以简单理解为能看到此变量的地方;而声明周期,是指此变量何时消亡。
不要将生命周期和作用域混淆,作用域是指源代码中的文本范围,为编译时属性;生命周期指运行时存在的有效时间段,是运行时改变。
语法块:由花括号包含的一系列语句。该块决定内部声明的名字作用范围,不能被块外访问。
词法块:将语法块的概念扩大化,某些未使用显式花括号的文本区域,称为词法块。
全局词法块:全局源代码;
对每个包,每个文件,每个for、if和switch语句,都有对应的词法块;每个select分支或switch也有独立词法块。
- 声明语句对应的词法域决定了作用范围的大小。对于内置的类型、函数和常量,如int、len()、true是在全局作用域。
- 在函数外(即包级语法域)声明的名字,在同一个包的任何源文件中访问。
- 对于导入的包,对应源文件的作用域,即在A中import B,则只能在A中访问B,和A同属同一个包的其他源文件无法访问B。
流程控制标签,如break、continue、goto,是函数级别的作用块。
程序可能包含多个同名的声明,只要它们不在相同的词法块就行。
编译器查找声明时,从内向外查找,如果内部有同外部同名的声明,则称内部遮蔽或隐藏了外部。
函数内部,词法块可能有多级嵌套,因此一个本地声明会遮蔽其他。大多数块由流程控制结构创造,如if 或for循环。
```
func main() {
x := "hello!" // 函数体内声明
for i := 0; i < len(x); i++ { // for语句创建两个词法块,一个条件部分,一个body部分。条件部分的作用范围包含了body范围
x := x[i] // 在body内又声明局部变量x, 赋值右边的x是外层的x
if x != '!' {
x := x + 'A' - 'a' // 在if的body中,又声明局部变量x, 赋值语句右边是外层的x
fmt.Printf("%c", x)
}
}
}
```
上面main函数声明了三个x,分别位于三个不同的词法域。
for循环创建两个词法域,循环体是显式部分,除此之外,还有隐式部分---循环的初始化部分。隐式部分包含了条件测试部分、
循环后迭代部分和显式的循环体部分。
```
func main() {
x := "hello"
for _, x := range x {
x := x + 'A' - 'a'
fmt.Printf("%c", x)
}
}
```
上面同样是三个不同的x变量,一个在函数体词法域,一个在for隐式的初始化部分,一个在for循环体词法域。
if和switch也会在条件部分创建隐式词法域,还有对应的执行体词法域。
```
if x := "xxx"; x == "zz" {
fmt.Println(x)
} else if y := x + "xx"; x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Println(x, y) // compile error: x and y are not visible here
```
第一个if之后的else if或else都嵌套在前面的语句中,因此他们可以使用前面定义的变量。
但if外的语句不能访问if内定义的变量。
switch语句也有类似的词法域规则:switch是一个词法域,之后是每个分支的此法域名。
(总结,if和for循环除了显示的词法域,还有一个隐式的词法域。隐式词法域的作用范围包含后续的else语句和显式部分,但他们都不能被if
或for之外的程序访问。)
在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。
```
var cwd string
func init() {
cwd, err := os.Getwd() // compile error: unused: cwd
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
```
上面os.Getwd()执行后,赋值给的是局部的cwd对象,并不是全局cwd对象。要赋值给全局对象,采用如下方式:
```
var cwd string
func main() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
```
不使用:=方式,让编译器寻找cwd的定义处,就会找到最外层也就是包级别的cwd对象。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术