- 主函数:程序有且只有一个主函数
package main //导入主函数的包 func main() { }
- 打印的模块:
fmt
package main import "fmt" func main() { fmt.Println("Hello", "World") // Hello World }
变量
- 内存是一个连续的数据集合,每一块内存都有唯一的地址编号,称为内存地址;
- 内存地址编号是由一个无符号十六进制整型数据表示的;
- 变量名其实就是为内存地址所起的别名;
- 变量的声明:
var 变量名 数据类型
var a int //声明 a = 10 //赋值
- 变量的定义:
var 变量名 数据类型 = 值
var a int = 10
- 自动类型推导:
变量名 := 值
num := 10 //推导num的类型为整型
- 多重赋值:
变量1, 变量2, 变量3 := 值1, 值2, 值3
a, b, c, d := 1, 2, 3, 4
- 交换两个变量的值
a, b := 1, 2 a, b = b, a
- 匿名变量
_, b, _, d := 1, 2, 3, 4 fmt.Println(b, d) // 2 4 fmt.Println(_) //编译报错
格式输出
fmt.Println()、fmt.Print()、fmt.Printf()
:换行输出、不换行输出、格式化输出;
- 整型:
%d
a := 10 fmt.Printf("%d", a) // 10
- 指定占的位数:默认使用空格占位,正数表示左侧占位,负数表示右侧占位
fmt.Printf("==%4d==", a) // == 10== fmt.Printf("==%-4d==", a) // ==10 == fmt.Printf("==%04d==", a) // ==0010==
- 位数小于原数值,则原样输出;
a := 123456 fmt.Printf("%3d", a) // 123456
- 浮点型:
%f
,默认保留6
为小数,如果第7
位大于5
,则进1
a := 3.1415926 fmt.Printf("%f", a) // 3.141593
- 指定小数位数时,四舍五入
fmt.Printf("%.3f", a) // 3.142
-
\n
:转义字符,换行符 - 布尔型
%t
,字符串%s
,字符型%c
var c byte = 'a' fmt.Println(c) // 56,'a'对应的ASCII码值 fmt.Printf("%c", c) // a
-
%p
:输出一个数据对应的内存地址(十六进制),&
是取地址运算符var a int fmt.Printf("%p", &a) // 0xc000056080
-
%%
:输出一个%
-
%b
:把数值转为二进制输出,Go中没有表示二进制数据的方式!a := 159 //十进制数值 fmt.Printf("%b", a) // 10011111
-
%o
:一个八进制表示的数值,以0
开头,每个值的范围是0-7
a := 100 //十进制数值 fmt.Printf("%o", a) // 144 b := 0123 //八进制数值, fmt.Printf("%o", b) // 123
-
%x、%X
:十六进制表示的数值(0x开头
),区别是大小写字母a-f
、A-F
-
%T
:输出一个值的类型; -
%s 与 %q
:都是用于打印字符串,不同的是,后者会带上Go语言格式;str := "hello" fmt.Printf("%q", str) // 打印的是:"hello" fmt.Printf("%s", str) // 打印的是:hello
格式输入
-
fmt.Scan()
:取出键盘输入的数据;var a int fmt.Scan(&a) // 从 a 的内存地址中取出键盘输入的数据,123.456 fmt.Println(a) //123,a是整型数据,所以小数部分会被舍弃
var a string fmt.Scan(&a) // helloworld fmt.Println(a) // helloworld
- 遇到空格键,表示一次接收结束,但可以使用多个变量接收键盘输入;
var a string fmt.Scan(&a) // hello world fmt.Println(a) // hello
var a,b string fmt.Scan(&a, &b) // hello world fmt.Println(a, b) // hello world
-
fmt.Scanf()
:格式化接收键盘输入的数据;var a int var b string fmt.Scanf("%d%s", &a, &b) // 123world456 fmt.Println(a, b) // 123 world456 整数部分给了a,剩下的给了b
var a int var b string fmt.Scanf("%3d%s", &a, &b) // 123456world fmt.Println(a, b) // 123 456world 规定a只接收3个整数,剩下的给b
基本数据类型
-
bool
:布尔型,长度为1
,默认值false
-
byte
:字节型,长度为1
,默认值0
,其实是uint8
的别名c := 'x' fmt.Printf("%T", c) // int32 var c1 byte = 'x' fmt.Printf("%T", c1) // uint8
- 整型:默认值
0
-
int、uint
:有符号32
位或无符号64
位 -
int8、uint8
:长度为1
,取值范围-128 ~ 127
、0 ~ 255
-
int16、uint16
:长度为2
,取值范围-32768 ~ 32767
、0 ~ 65535
-
int32、uint32
:长度为4
-
int64、uint64
:长度为8
num := 12 fmt.Printf("%T", num) // int c := 'x' fmt.Printf("%T", c) // int32 var c1 byte = 'x' fmt.Printf("%T", c1) // uint8
- 一个
int
类型会根据操作系统的不同,占用4
或8
个字节,64x系统占用8
字节.
-
- 浮点型:默认值
0.000000
-
float32
:长度为4
,,小数位精确到7
位; -
float64
:长度为8
,小数位精确到15
位;
f := 3.14 fmt.Printf("%T", f) // float64
-
-
string
:字符串,utf-8
字符串,默认值是空字符串- 空字符串实际是一个空字符:
\0
- 一个字符串的结束就是以
\0
为标识,但它对开发者是不可见的!
- 空字符串实际是一个空字符:
keep moving
- 在Go中,一个汉字等于
3
个字符,为了和Linux同一处理;var a string = "go语言" num := len(str) //计算字符串的个数 fmt.Println(num) // 8
- 常量:
const
,一旦定义,则不允许修改;const A int = 10 const PI = 3.14
- 内存可以简单划分为代码区、数据区、栈区、堆区
- 普通的变量一般存放在栈区,常量则存放在数据区
- 系统为每一个应用程序分配
1M
的栈区空间存放变量,程序运行结束后,系统自动清空栈区; - 常量不能使用
&
取地址;
-
iota
枚举:常量生成器,用于生成一组以相似规则初始化的常量;const ( a = iota //0 b = iota //1 c = iota //2 ) fmt.Println(a, b, c) // 0 1 2 //通过修改value的值,控制不同的状态 value := a value := b value := c
- 默认情况下,第一个
iota
的值是0
,后面的值自动累加1
- 简写形式
const ( a = iota //0 b c ) fmt.Println(a, b, c) // 0 1 2
- 写在同一行的
iota
,其值相同
const ( a = iota //0 b,c = iota, iota d, e ) fmt.Println(a, b, c, d, e) // 0 1 1 2 2
- 可以手动赋初始值,但后面的
iota
仍保持默认的自增长方式;
const ( a = 22 //0 b,c = iota, iota d, e ) fmt.Println(a, b, c, d, e) // 22 1 1 2 2
- 默认情况下,第一个
- 算术和赋值运算符:
+、-、*、/、%、++、--、=、+=、*=、/=、%=
- 不同类型之间不能直接进行计算,需要进行类型转换;
a := 10 b := 3.14 c := float64(a) * b d := a * int(b)
- 浮点型转整型:保留整数部分,舍弃小数部分,不会四舍五入;
- 整型之间如果类型不同,也不能直接进行计算,而且通常都会把低类型转为高类型,尽量避免数据溢出!
var a int32 = 10 var b int64 = 20 c := int64(a) + b
- 关系和逻辑运算符:结果总是布尔类型,
==、!=、<、>、<=、>=、!、&&、||
,逻辑与&&
的优先级高于逻辑或||
-
&
:取内存地址运算符,如&b
表示获取变量b 的内存地址; -
*
:取值运算符,如*b
表示指针变量b 所指向内存的值;b := 10 p := &b fmt.Println(p) // 10
控制语句
-
if
score := 70 if score>100 { } else if score>80 { } else { }
-
switch
score := 70 switch score { case 100: fmt.Println(score) case 60: fmt.Println(score) default: fmt.Println(score) }
- 不支持浮点型数据,因为浮点数是一个约等于的数值;
- 支持布尔型,但表达式只能是单一区间
score := 70 switch score>60 { case true: fmt.Println(score) case false: fmt.Println(score) }
- 支持表达式,支持
case
的合并
score := 70 switch score/10 { case 1: fallthrough //执行下一个case case 2: fmt.Println(score) case 3: fmt.Println(score) default: fmt.Println(score) }
-
for
循环for 表达式1; 表达式2; 表达式3 { 循环体 } ``` ```go for i:=1; i<10; i++ { fmt.Println(i) }
- 三个表达式都可以省略
i:=1 for ; i<10; i++ { fmt.Println(i) } for ;; i++ { if i>10 { break } fmt.Println(i) } for ;; { if i>10 { break } fmt.Println(i) i++ }
-
break
只能跳出本次循环,对于循环嵌套的时候需要注意! -
continue
继续下次循环,下面的代码不再执行! - 空死循环
for { ; }
函数
- 定义
func 函数名(参数列表) { 函数体 }
func sum(a int, b int, c int) { s := a+b+c fmt.Println(s) } func main() { sum(1,2,3) // 6 }
- 如果多个连续的形参类型相同,可以省略前面形参的类型
func sum(a, b, c int) { }
- 函数本身存储在代码区,函数的调用存储在栈区;
- 不定长参数:
...数据类型
,被封装在一个slice(切片)
中,len(args)
可以计算切片的长度;func sum(a int, args ...int) { fmt.Println(args) } sum(1,2,3) // [2 3] sum(1,2,3,4,5) // [2 3 4 5]
- 所有的函数都是全局函数,可以被项目中的其他文件调用,所以项目中的函数名是唯一的;
- 返回值
func 函数名(参数列表) 返回值类型列表 { 函数体 return 返回值 }
func sum(a int, b int, c int) int { s := a+b+c return s } func main() { value := sum(1,2,3) }
- 预声明返回值变量
func sum(a int, b int, c int) (count int) { count = a+b+c return } func main() { value := sum(1,2,3) }
- Go支持多个返回值
func sum() (a int, b bool, c string) { a,b,c = 1,true,"hello" return } a,b,c := sum() //接收三个返回值 1,true,"hello" a,_,c := sum() //选择接收两个返回值 1,"hello"
- 函数类型
func sum(a int, b int, c int) { s := a+b+c fmt.Println(s) } fmt.Printf("%T", sum) // func(int, int, int)
- 为函数起别名
f := sum f(1, 2, 3) // 6 fmt.Printf("%T", f) // func(int, int, int)
var f func(int, int, int) = sum // var f = sum f(1, 2, 3) // 6 fmt.Printf("%T", f) // func(int, int, int)
-
type
:可以定义函数类型/格式,还可以为已存在的类型起别名;
func test1() { fmt.Println("test1") } type FUNCTEST func() //定义函数类型 FUNCTEST func main() { var f FUNCTEST f = test1 f() // test1 }
func test2(x int, y int) { fmt.Println(x, y) } type FUNCDEMO func(int, int) //定义函数类型 FUNCDEMO func main() { var f FUNCDEMO f = test2 f(1, 2) // 1 2 }
func test3(x int, y int) int { return x+y } type FUNCEXAM func(int, int)int //定义函数类型 FUNCEXAM func main() { var f FUNCEXAM = test3 f(1, 2) // 3 }
- 函数名本身就是一个指针类型的数据,指向函数体所在的内存地址
fmt.Println(f) // 0x491080 fmt.Println(test3) // 0x491080
- 作用域
- 函数内定义的变量都是局部变量,函数外部定义的变量是全局变量,且全局变量不能使用自动类型推导;
- 全局变量可以被项目中的所有文件引用;
- 局部变量存储在栈区,全局变量和常量都存在数据区;
-
&
可以取全局变量的地址,但不能取常量的地址;
- 匿名函数与闭包
- Go支持在函数中继续定义函数,它是一个匿名函数;
func main() { // 匿名自执行函数 func() { fmt.Println(2) }() // 2 //给匿名函数起别名 fx := func(x int, y int) { fmt.Println(x, y) } fx(1, 2) // 1 2 //带返回值的匿名自执行函数 fy := func(x int, y int)int { return x+y }(1, 2) // 3 }
- 闭包:函数返回值是一个匿名函数
func wrap() func() int { var a int return func() int { a++ //内部函数持有外部函数的变量 return a } } func main() { f := wrap() f() // 1 f() // 2 f() // 3 }
- 通过匿名函数和闭包,实现函数在栈区的本地化.
工程管理
- Go项目目录
-
src
:源代码,.go、.c、.h、.s
等文件; -
pkg
:由go install
命令构建安装的代码包,包含Go库源码文件,.a
归档文件; -
bin
:与pkg
目录类似,通过go install
命令完成安装后,保存由Go命令生成的可执行文件。
-
- 创建
src
目录,配置编译项为Directory
,并选择src
目录路径; - 同级别目录
- 源文件的包名必须是相同的,且主函数
main()
的包名总是package main
- 源文件中的全局变量和函数是共享的,可以相互调用。
- 源文件的包名必须是相同的,且主函数
- 不同级别的目录
- 调用其他目录下的函数和变量时,需要导入其包名,且函数和变量的命名必须以大写字母开头!
- 小写字母开头的函数和变量是私有的,不允许其他目录中的文件调用!
- 在一个项目中,包名是唯一的!
复合类型
复合类型主要包括:数组、指针、切片、结构体...
数组
- 定义:
var 变量名 [容量]数据类型
- 声明一个元素个数为10、元素类型为int 的数组
var arr [10]int
- 定义数组并初始化
var arr [5]int = [5]int{1,2,3,4,5} fmt.Println(arr[2]) // 3
- 定义数组并初始化部分元素
arr := [5]int{1,2,3} fmt.Println(arr[4]) // 0
- 定义数组时,指定初始化哪些元素
arr := [10]int{1,2,3, 7:10} //索引为7 的元素为10 fmt.Println(arr[7]) // 10
- 使用类型推导,可以根据元素个数创建数组
arr := [...]int{1,2,3,4,5,6}
- 数组的容量一旦声明,就不可更改,使用
len()
可以获取数据长度; -
range
:遍历集合信息arr := [...]int{1,2,3,4,5} for i,v:=range arr { fmt.Println(i, v) }
-
i
表示数组的角标,v
表示数组元素
-
- 数组是一块连续的存储空间,数组名指向首地址;
arr := [...]int{1,2,3,4,5} fmt.Printf("%p", &arr) // 0xc00006c060 fmt.Printf("%p", &arr[0]) // 0xc00006c060 fmt.Printf("%p", &arr[2]) // 0xc00006c070
- 在Go中,数组作为参数的传递也是值传递!
func test(arr [5]int) { arr[2] = 100 //修改数组元素的值 fmt.Printf("%p", &arr) // 0xc00005c0a0 } func main() { arr := [5]int{1,2,3,4,5} fmt.Printf("%p", &arr) // 0xc00005c050 test(arr) b := arr b[2] = 200 fmt.Printf("%p", &b) // 0xc00006c060 fmt.Println(b) // b:[1 2 200 4 5] fmt.Println(arr) // arr:[1 2 3 4 5] }
- 对于值传递,如果数组中的元素支持
==、!=
,那么该数组也支持==、!=
arr := [5]int{1,2,3,4,5} b := [5]int{1,2,3,4,5} fmt.Println(b == arr) // true
- 把数组作为返回值,返回一个新的数组
func test(arr [10]int) [10]int { arr[2] = 100 //修改数组元素的值 return arr } func main() { arr := [10]int{1,2,3,4,5} arr = test(arr) }
- 对于值传递,如果数组中的元素支持
- 随机数:导入
math/rand
包- 时间包:
time
time.Now() // 当前时间 2019-09-05 18:24:53.5059375 +0800 CST m=+0.002991601 time.Now().Unix() // 当前时间的毫秒值 1567679093 time.Now().UnixNano() // 当前时间的纳秒值 1567679093519899400 time.Now().UnixNano() // 当前时间的纳秒值 1567679093519899400 //当前时间 time.Now().YearDay() // 今天是今年第几天 time.Now().Year()、Month()、Day()、Date() //年月日、日期 time.Now().Hour()、Minute()、Second()、Nanosecond() //时分秒、纳秒
- 默认情况下,随机数是根据一个固定时间
1970.1.1 0:0:0
计算出来的,所以每次生成的随机数都一样; - 加入当前时间作为种子,进行混淆,从而生成随机数!
rand.Send(time.Now().UnixNano()) r := rand.Intn(100) //生成 [0, 100) 的随机数
- 时间包:
- 二维数组
- 定义:
var 数组名 [行个数][列个数] 数组类型
var arr[3][4]int arr[1][2] = 11 arr[2][1] = 33
var arr = [3][4]int{{1,2,3,4}, {2,3,4,5}, {3,4,5,6}} fmt.Println(arr[2][3]) // 6
-
len()
获取行和列
var arr[3][4]int fmt.Println(len(arr)) // 3 fmt.Println(len(arr[1])) // 4
- 定义:
切片
与数组相比,切片的长度是不固定的,可以追加元素,虽然它看起来像"动态数组",但它并不是数组!
- 定义:
var 切片名 []数据类型
var s [] int fmt.Println(s) // [] var s2 []int = []int{1, 2, 3, 4, 5} fmt.Println(s2) // [1 2 3 4 5]
-
make([]数据类型, 初始长度)
自动类型推导创建切片s := make([]int, 5) s[0] = 1 fmt.Println(s) // [1 0 0 0 0]
- 通过索引的方式操作切片时,也不允许超过切片长度;
s := make([]int, 5) s[5] = 1 //error: index out of range fmt.Println(100) //error: index out of range
-
append(切片, 元素1, 元素2, ...)
:向切片中添加元素,并返回一个新的切片,但不会影响原切片!s := make([]int, 5) b := append(s, 11, 12) fmt.Println(s) // [0 0 0 0 0] fmt.Println(b) // [0 0 0 0 0 11 12]
-
len()
获取切片的长度,cap()
获取切片的总容量;-
append()
在向切片中添加元素时,切片的长度会自动扩展; - 当切片数据小于1024字节时,每次为切片扩展上一次的倍数;超过1024字节时,每次扩展上一次的1/4
s := make([]int, 5) s = append(s, 10, 11) len(s) // 7 cap(s) // 10
-
- 遍历切片:
for i,v:=range slice { }
,for i:=0; i<len(slice); i++ { }
- 截取:
slice[起始下标:结束下标:容量]
- 省略起始下标,默认从
0
开始;省略结束下标,默认为len(slice)-1
- 截取之后返回一个新的切片,指定的容量不能大于原切片的容量,但不能小于新切片的元素个数!
- 新切片只是引用原切片的一部分,使用的是同一块内存地址,所以通过索引修改值时,它们会同步修改!
src := []int{1, 2, 3, 4, 5, 6} dst := src[0:2] // [1 2] src[0] = 2001 fmt.Println(dst) // [2001 2] fmt.Printf("%p\n", src) // 0xc0000160f0 切片名就是地址,无需使用 & fmt.Printf("%p\n", dst) //0xc0000160f0
- 省略起始下标,默认从
- 数组存储在栈区,而切片存储在堆区
- 堆区的容量远远大于其他内存区域,切片的容量是不固定的,如果储存在栈区,会受容量限制!
- 在切片扩展过程中,地址会发生变化,因为切片也是存储在一块连续的内存空间,如果空间不够,系统会重新为其分配内存;
-
copy(切片1, 切片2)
:拷贝切片2到切片1中,切片1一定要有足够的容量;src := []int{1, 2, 3} dst := make([]int, 5) copy(dst, src) fmt.Println(dst) // 1 2 3 0 0
- 不管目标切片中是否有元素,拷贝总是从索引
0
开始,如果目标切片中已经有了元素,则被覆盖! - 源切片和目标切片处在不同的内存空间,互不影响!
- 不管目标切片中是否有元素,拷贝总是从索引
- 切片是引用传递,不支持
==、!=
- 切片作为实参时,如果函数内对切片使用了
append()
,且导致切片扩容,那么形参指向的切片地址会发生变化,最终与原切片脱离关系; - 对于这种情况,应该把修改后的且作为返回值,重新赋给原切片;
func test(s []int) []int { s = append(s, 10, 11, 12) return s } func main() { src := []int{1, 2, 3} src = test(src) }
- 切片作为实参时,如果函数内对切片使用了
Map
Map
:key-value
结构,其中 key
是唯一的,且必须是支持 ==、!=
特性的数据类型,如整型、字符串;
- 定义
- 声明一个
Map
,默认值为nil
,并不能直接使用;
var m map[int] string //声明 key为int型,value为string型 m[1] = "Machel" // 报错:nil map
- 声明并初始化
Map
m := map[int]string{1:"A", 2:"B", 3:"C"}
- 声明一个
-
make()
创建Map
m := make(map[int] string, 1) //key为int型,value为string型,初始容量为1 m[1] = "Machel" m[2] = "Jack" //自动扩容
-
Map
的容量自动扩容,链式存储,无序,所以使用for k,v:=range map {}
遍历 -
len()
可以计算Map
的元素个数; - 取值时,可以判断值是否存在
m := map[int]string{1:"A", 2:"B", 3:"C"} v, ok := m[1] fmt.Println(v, ok) // A true
-
if
语句支持多条语句,以;
分割
if _, ok := m[100]; ok { // ok 为 true 时,则进入此分支 }
-
- 删除元素:
delete(map, key)
,根据key
删除,即使不存在,也不会报错; -
Map
是地址(引用)传递。