go 语言基础之变量和常量#
变量和常量是编程中必不可少的部分,也是很好理解的一部分。
标识符与关键字#
标识符#
在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名,常量名,函数名等等。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
此外,Go 语言中还有 37 个保留字。
Constants: true false iota nil
Types: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
Functions: make len cap new append copy close delete
complex real imag
panici recover
变量#
变量的来历#
程序运行过程中的数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。
变量的类型#
变量的功能是存储数据。不同的变量保存的数据类型可能会不一样。
Go 语言中的每一个变量都有自己的类型,并且变量必须经过生命才能开始使用。
变量的声明#
Go 语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且 Go 语言的变量声明后必须使用。
标准声明#
Go 语言的变量声明格式为:
var 变量名 变量类型
变量声明以关键字 var
开头,变量类型放在变量的后面,行尾无需分号。
类型推导#
有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。
var name = "Q1mi"
var ag=18
短变量声明#
在函数内部,可以使用更简略的 :=
方式声明并初始化变量
package main
import (
"fmt"
)
//全局变量m
var m = 10
func main() {
n := 10
m := 200 // 此处声明局部变量m
fmt.Println(m, n)
}
匿名变量#
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量。匿名变量使用一个下划线_表示.
func foo()(int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.PrintLn("y=", y)
}
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
注意事项:
-
函数外的每个语句都必须以关键字开始 (var, const, func 等)
-
:= 不能使用在函数外
-
_
多用于占位,表示忽略值。常量#
相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。常量的声明和变量声明非常类似,只是把
var
换成了const
, 常量在定义的时候必须赋值。并且在整个程序运行期间,他们的值都不能再发生变化了。iota#
iota
是 go 语言的常数计数器,只能在常量的表达式中使用。
iota
在 const 关键字出现时将被重置为 0。Const 中每新增一行常量声明将使iota
计数一次(iota
可理解为 const 语句块中的行索引)。使用 iota 能简化定义,在定义枚举时很有用。逻辑运算符#
运算符 | 描述 |
---|---|
&& | 逻辑 AND 运算符。如果两边的操作数都是 True,则为 True |
|| | 逻辑 OR 运算符 |
! | 逻辑非运算符 |
变量的作用域#
全局变量,大写字母开头,所有地方都可以访问,跨 package 访问是需要带 package 名称。全局变量,小写字母开头,本 package 内都可以访问。局部变量,仅本函数内可以访问。内部声明的变量可以跟外部声明的变量有冲突,以内部的为准。仅 {} 圈定的作用域内可以访问,可以跟外部的变量有冲突。
变量的生命周期#
变量的生命周期指的是在程序运行期间闭麦昂有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,在局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,知道该变量不再被引用为止,然后变量的存储空间可能会被回收。
注意:
- 由于一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。
go 语言基础之基本数据类型与注释#
Go 语言中有丰富的数据类型,除了基本的整型,浮点型,布尔型,字符串之外,还有数组,切片,结构体,函数,map,通道等。Go 语言的基本类型和其他语言大同小异。
基本数据类型#
整型#
整型分为以下两大类:按长度分为:int 8, int 16, int 32, int 64 对应的无符号整型:uint 8, uint 16, uint 32, uint 64 其中,uint 8 就是我们熟知的 byte 类型。int 16 对应的 c 语言中的 short 型,Int 64 对应 c 语言中的 long 型。
类型 | 描述 |
---|---|
uint 8 | 无符号 8 位整型 (0-255) |
uint 16 | 无符号 16 位整型 (0-65535) |
uint 32 | 无符号 32 位整型 (0-4294967295) |
uint 64 | 无符号 64 位 (0-18446744073709551615) |
int 8 | 有符号 8 位整型 (-128-127) |
int 16 | 有符号 16 位整数 (-32768 到 32767)) |
int 32 | 有符号 32 位整数 () |
布尔值#
Go 语言中以 bol
类型进行声明布尔型数据,布尔型数据只有 true
和 false
两个值
注意:
- 布尔类型变量的默认值为 false.
- Go 语言中不允许被整型强制转换为布尔型
- 布尔型无法参与数值运算,也无法与其他类型进行转换。
字符串#
Go 语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型 (int, bool, float 32, float 64 等) 一样。Go 语言里的字符串的内部实现使用 UTF-8
编码。字符串的值为 双引号(")
中的内容,可以在 Go 语言的与阿马忠直接添加非 ASII 码字符,例如:
s1 := "hello"
字符串的常用操作#
方法 | 介绍 |
---|---|
len (str) | 求长度 |
+ 或 fmt.Sprintf | 拼接字符串 |
strings. Split | 分割 |
strings. Contains | 判断是否包含 |
strings. HasPrefix ,strings. HasSuffix | 前缀 / 后缀判断 |
strings. Index (), strings. LastIndex() | 子串出现的位置 |
strings. Join (a[] string, sep String) | join 操作 |
Byte 和 rune 类型#
组成每个字符串的元素叫做 “字符”,可以通过遍历或者单个获取字符串元素获得字符。字符用单引号(‘)包裹起来,如:
var a := '中'
var b ::= 'x'
// byte 等价与uint8 ,rune 等价于 int32
// rune 可以表示任意的unicode字符
复合数据类型#
类型 | 默认值 | 说明 |
---|---|---|
array | 值类型 | |
struct | 值类型 | |
string | "" | UTF-8 字符串 |
slice | nil | 引用类型 |
map | nil | 引用类型 |
channel | nil | 引用类型 |
interface | nil | 接口 |
function | nil | 函数 |
数组#
数组是块连续的内存空间,在声明的时候必须指定长度,且长度不能改变。所以数组在声明的时候就可以把内存空间分配好,并赋上默认值,即可完成初始化。
初始化数组#
var arr [5]int = [5]{} // 数组必须指定长度和类型,且长度和类型指定后不可改变
var arr2 = [5]int{}
var arr3 = [5]int{3,2} // 给前两个元素赋值
var arr4 = [5]int{2:15, 4:30} // 指定index复制
var arr5 = [...]int{3,2,6,5,4} // 根据{}里元素的个数推断出数组的长度
var arr6 = [...]struct{
name string
age int
}{{"Tome", 18}, {"Jim", 20}} // 数组的元素类型是由匿名结构体给定
访问数组里的元素#
-
通过 index 访问
- 首元素 arr [0]
- 末元素 arr [len (arr)-1]
-
访问二维数组里的元素
-
位于第三行第 4 列的元素 arr [2][3]
// 遍历数组里的元素 for i, ele := range arr { fmt.Printf("index=%d, element=%d\n", i, ele) } // 遍历二维数组 for row, array := range arr { for col, ele := range array { fmt.Printf("arr[%d][%d]=%d\n", row, col, ele) } }
cap 和 len#
-
-
Cap 代表 capacity 容量
-
Len 代表 length 长度
-
len 代表目前数组里的几个元素,cap 代表给数组分配的内存空间可以容纳多少个元素
-
由于数组初始化后不会改变,不需要给它预留内存空间,所以 len (arr) = cap (arr)
数组传参#
-
数组的长度和类型都是数组类型的一部分,函数传递数组类型时,这两部分都必须吻合
-
Go 语言没有按引用传参,全部都是按值传参,即传递数组数组实际上上传的是数组的拷贝,当数组的长度很大时,仅传参开销都很大。
-
如果想修改函数外部的数组,就把它的指针(数组在内存里的地址)传进来
切片#
type slice struct {
array unsafe.Pointer
len int
cap int
}
初始化切片#
var s []int // 切片声明,len=cap=0
s = []int{} //初始化, len=cap=0
s = make([]int, 3) //初始化, len=cap=3
s = make([]int, 3, 5) //初始化,len=3, cap=5
s = []int{11,2,3,4,5} //初始化, le=cap=5
s2d := [][]int {
{1}, {2,3}, // 二维数组各行列数相等,但二维切片各行的len可以不相等
}
Append#
-
切片相对于数组最大的特点就是可以追加元素,可以自动扩容
-
追加的元素放到预留的内存空间里,同时 len 加 1
-
如果预留的空间已用完,则会重新申请一块更大的内存空间,capacity 变成之前的 2 倍 (cap<1024) 或 1.25 倍 (cap>1024)。把原内困空间数据拷贝过来,在新内存空间上执行 append 操作
截取子切片#
-
s := make([]int, 3,5)
// len=3, cap=5 -
sub_slice = s[1:3]
// len = 2, cap=4 -
刚开始,子切片和母切片共享底层的内存空间,修改子切片会反映到母切片上,在子切片上执行 append 会把新元素放到母切片预留的内存空间上
-
当子切片不断执行 append,耗完了母切片预留的内存空间,子切片跟目前片就会发生内存分离,伺候两个切片没有任何关系。
切片传参#
-
go 语言参数传参,传的都是值,即传切片会把切片的
{arrayPointer, len, cap}
这 3 个字段拷贝一份 -
由于传的是底层数组的指针,所以可以直接修改底层数组里的元素。
map#
Map 是一个无序的 key/value 对的集合,其中所有的 key 都是不同的,然后通过给定的 key 可以在常数时间复杂度内检索,更新或删除对应的 value。
指针#
定义#
自定义类型和字符串#
-
类性别名
- Type byte = uint 8
- Type rune = int 32
-
自定义类型
-
type signal uint 8
-
Type ms map[string]string
-
Type add func (a, b int) int
-
Type user struct {name string; age int}
强制类型转换#
var i int = 9 var by byte = byte(i) // int转为byte i = int(by) // byte转为Int fmt.Printf("i=%d\n", i)
-
-
Byte 和 int 可以相互转换
-
Float 和 int 可以相互转换,小数位丢失
-
Bool 和 int 不能相互转换
-
不同长度的 int 或 float 可以相互转换
-
String 可以转换为 [] byte 或 [] rune 类型,byte 或 rune 可以转换为 string
-
低精度向高精度转换没问题,高精度向低精度转换会丢失位数
-
无符号向有符号转换,最高位是符号位
注释#
-
单行注释。以 // 大头
-
多行注释。连续多行以 // 大头,或者在段前使用 /*, 段尾使用 */
-
多行注释之间不能出现空行
-
NOTE: 因人注意,TODO:将来需要优化,Deprecated: 变量或函数强烈建议不要在使用
-
注释行前加缩颈即可写 go 代码
-
包注释。在 package xxx 的上方。一个包只需要在一个地方写包注释,通常会专门写一个 doc. Go,里面只有一行 package xxx 和关于包的注释
-
结构体注释。在 type xxx struct 上方
-
函数注释。在 func xxx () 上方
-
行注释。在行上方或右侧
Godoc#
-
Go doc 是 Go 自带的命令
-
Go doc entrance class/util
-
Go doc 可以为项目代码导出网页版的注释文档
-
需要先安装 go get golang. Org/x/tools/cmd/godoc
-
启动 http: godoc -http:6060
包和工具#
包#
包简介#
-
所有导入的包必须在每个文件的开头显式声明。
-
禁止包的环状以来,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,并且很有可能被并发编译
-
编译后包的目标文件不仅仅记录包本生的导出信息,目标文件同时还记录了包的依赖关系
导入路径#
每个包是由一个全局唯一的字符串所标识的导入路径定位。出现在 import 语句中的导入路径也是字符串。
go 语言的规范并没有指明包的导入路径字符串的具体含义,导入路径的具体含义是由构建工具来解释的。包声明#
在每个 go 语言源文件的开头都必须有包声明语句。包声明语句的主要目的是确定当前包被其它包导入时默认的标识符(也成为包名)。
包和命名#
-
尽量避免包名使用可能被经常用于局部变量的名字,这样可能导致用户重命名导入包。
-
包名一般采用单数的形式。标准库的 bytes, errors 和 strings 使用了复数形式,这是为了避免和预定义的类型冲突,同样还有 go/types 是为了避免和 type 关键字冲突
工具#
Go 语言的工具箱集合了一系列的功能的命令集。它可以看作是一个包管理器,用于包的查询,计算包的依赖关系,从远程版本控制系统和下载它们等任务。他也是一个构建系统,计算文件的以来关系,然后调用编译器,汇编器和连接器构建程序,也是一个单元测试和基准测试的驱动程序。
下载包#
使用 go 语言工具箱的 go 命令,不仅可以根据包导入路径找到本地工作区的包,甚至可以从互联网上找到和更新包。
使用命令 go get
可以下载一个单一的包或者用 ...
下载整个子目录里面的每个包。Go 语言工具箱的 go 命令同时计算并下载所依赖的每个包。
一旦 go get
命令下载了包,然后就是安装包或包对应的可执行的程序。
构建包#
go build
命令编译命令行参数指定的每个包。如果包是一个库,则忽略输出结果;这可以用于检测包的是否可以正确编译。如果包的名字是 mian, go build
将调用连接器在当前目录创建一个可执行程序;以导入路径的最后一段作为可执行程序的名字。
因为每个目录只包含一个包,因此每个对应可执行程序或者叫 unix 术语中的包,会要求放到一个独立的目录中。
接口#
接口类型是对其他类型行为的抽象和概括;因为接口类型不回和特定的实现细节绑定在一起,通过这种抽象的方式,可以让我们的函数更加灵活和更具有适应能力。
Go 语言中接口类型的独特之处在于它是满足隐式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足已经存在的具体类型确不回去改变这些类型的定义。