go程序设计语言第二章-- 程序结构

本文详细介绍了Go语言的基本语法,包括命名与关键字、预定义关键字、变量、类型、流程控制和包结构。重点讲解了Go的内置类型、类型转换、变量声明周期、作用域规则以及包的导入和初始化。还探讨了如iota常量、make和new函数、以及如何使用预定义函数如len、cap和append。此外,文章还阐述了Go中变量的生命周期和不同词法块的作用域规则。
摘要由CSDN通过智能技术生成

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对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值