[TOC]
1 程序结构
Go 源文件以 package 声明开头,说明源文件所属的包,接着使用 import 导入依赖的包,其次为包级别的变量、常量、类型和函数的声明和赋值。函数中可定义局部的变量、常量
2 基本组成元素
2.1 标识符
标识符是编程所使用的名字,用于给变量、常量、函数、类型、接口、包名等进行命名,以建立名称和使用之间的关系,Go 语言标识符的命名规则:
必须满足:
- 只能由非空字母(Unicode)、数字、下划线
_
组成 - 只能以字母或下划线开头
- 不能使用 Go 关键字
- 避免使用 Go 语言预定义标识符
- 建议使用驼峰式
- 标识符区分大小写
Go 语言提供一些预先定义得标识符用来表示内置得常量、类型、函数,在自定义标识符时应该避免使用:
- 内置常量:
true、false、nil、iota
- 内置类型:
bool、byte、rune、int、int8、int16、int32、uint、uint8、uint16、uint32、uint64、uintptr、float32、float64、complex64、complex128、string、error
- 内置函数:
make、len、cap、new、append、copy、close、delete、complex、real、imag、panic、recover
- 空白标识符:
_
2.2 关键字
关键字用于特定的语法结构,Go 语言定义 25 个关键字:
- 用于声明:
import、package
- 实体声明和定义:
chan、const、func、interface、map、struct、type、var
- 流程控制:
break、case、continue、default、defer、else、fallthrough、for、go、goto、if、range、return、select、switch
2.3 字面量
字面量是值的表示方法,常用与对变量/常量进行初始化,主要分为:
- 标识基础数据类型值的字面量,例如:
0, 1.1, true,3 + 4i, 'a', "我爱中国"
- 构造自定义的复合数据类型的类型字面量,例如:
type Interval int
- 用于表示符合数据类型值的复合字面量,用来构造
array、slice、map、struct
的值,例如:{1,2,3]
2.4 操作算数运算符
-
算术运算符:
+、-、*、/、%、++、--
运算符 描述 + 相加 – 相减 * 相乘 / 相除 % 求余 示例:
package main import "fmt" func act() { var a int = 4 var b int = 8 var c int = a + b var d int = b - a var e int = a * b var f int = b / a var g int = b % a fmt.Println(c, d, e, f, g) } func main() { act() }
输出:
[15:05:57 root@go day1]#go run main.go 12 4 32 2 0
-
关系运算符:
运算符 描述 == 检查两个值是否相等,如果相等返回 True 否则返回 False != 检查两个值是否不相等,如果不相等返回 True 否则返回 False > 检查左边值是否大于右边值,如果是返回 True 否则返回 False >= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False < 检查左边值是否小于右边值,如果是返回 True 否则返回 False <= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False 示例:
package main import "fmt" func rel() { var a int = 8 var b int = 3 fmt.Println(a > b) fmt.Println(a < b) fmt.Println(a != b) fmt.Println(a >= b) fmt.Println(a <= b) } func main() { rel() }
输出:
[15:05:58 root@go day1]#go run main.go true false true true false
-
逻辑运算符
运算符 描述 && 逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False || 逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False ! 逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True 示例:
package main import "fmt" func rel() { var a int = 8 var b int = 3 var c int = 5 fmt.Println(a > b && c > b) // a 大于 b 并且 c 大于 b 这里为 true fmt.Println(a < b || b < c) // a 小于 b 为假但是这里为 || 所以 b < c 这里为 true fmt.Println(!(a > b)) // a 大于 b 为 true 取反之后为 false } func main() { rel() }
输出:
[15:07:58 root@go day1]#go run main.go true true false
-
位运算符:
&、|、^、<<、>>、&^
运算符 描述 & 参与运算的两数各对应的二进位相与(两位均为1才为1) | 参与运算的两数各对应的二进位相或(两位有一个为1就为1) ^ 参与运算的两数各对应的二进位相异或,当两对应的二进位不相同时,结果为1(两位不一样则为1)。作为一元运算符时表示按位取反,,符号位也跟着变 << 左移n位就是乘以2的n次方。a<<b是把a的各二进位全部左移b位,高位丢弃,低位补0 >> 右移n位就是除以2的n次方。a>>b是把a的各二进位全部右移b位 示例:
package main import "fmt" func bit_op() { fmt.Printf("%b\n", 5&6) // 101 & 110 == 100 fmt.Printf("%b\n", 5|6) // 101 | 110 == 111 fmt.Printf("%b\n", 5^6) // 101 ^ 110 == 011 fmt.Printf("%b\n", 5>>1) // 101 >> 1 = 10 可以看到 5 的二进制为 101 向右移 1 位,就变成了 10 fmt.Printf("%b\n", 5<<1) // 101 << 1 = 1010 可以看到 5 的二进制为 101 向右移 1 位,就变成了 1010 } func main() { bit_op() }
输出:
[15:27:52 root@go day1]#go run main.go 100 111 11 10 1010
-
赋值运算符:
=、+=、—=、/=、%=、&=、|=、”=、<<=、>>=
运算符 描述 = 简单的赋值运算符,将一个表达式的值赋给一个左值 += 相加后再赋值 -= 相减后再赋值 *= 相乘后再赋值 /= 相除后再赋值 %= 求余后再赋值 <<= 左移后赋值 >>= 右移后赋值 &= 按位与后赋值 |= 按位或后赋值 ^= 按位异或后赋值 示例:
// 这里我只演示一下几种赋值运算,其他的以此类推 package main import "fmt" func main() { a := 5 a += 1 // a += 1 = 5 + 1 = 6 b := 7 b *= a // b *= a = 7 * 6 = 42 fmt.Println(a, b) b ^= a // 42 ^ 6 = 101010 ^ 110 = 101100 = 44 fmt.Printf("二进制:%b\n十进制:%d\n", b, b) }
输出:
[17:29:01 root@go day1]#go run main.go 6 42 二进制:101100 十进制:44
-
其他运算符:
&(单目)、*(单目)、.(点)、-(单目)、... 、<-
2.5 分割符
小括号()
,中括号[]
,大括号{}
,分号;
,逗号,
2.6 保留字
常量: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
3 声明
声明语言用于定义程序的各种实体对象,主要有:
- 声明变量的
var
- 声明常量的
const
- 声明函数的
func
- 声明类型的
type
4 变量、常量、字面量
4.1 变量
类型 | go变量类型 | fmt输出 |
---|---|---|
整型 | int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 | %d |
浮点型 | float32 float64 | %f %e %g |
复数 | complex128 complex64 | %v |
布尔型 | bool | %t |
指针 | uintptr | %d |
引用 | map slice channel | %v |
字节 | byte | %d |
任意字符 | rune | %d |
字符串 | string | %s |
错误 | error | %v |
变量初始化:
如果变量声明后未赋予初始值那么就只有默认的初始化值,数值型初始化 0,字符串初始化为空字符串,布尔型初始化为 false,引用类型、函数、指针、接口初始化为nil
函数内部的变量(非全局变量)可以通过 :=
声明并初始化
a:=3
下划线表示匿名变量,匿名变量不占命名空间,不会分配内存,因此可以重复使用
_=2+4
变量常用语法:
变量是指对一块存储空间定义名称,通过名称对存储空间的内容进行访问或修改,使用 var 进行变量声明,常用的语法为:
-
var变量名变量类型=值
定义变量并进行初始化,例如:
var name string = "silence"
-
var变量名变量类型
定义变量使用零值进行初始化,例如:
var age int
-
var 变量名 = 值
定义变量,变量类型通过值类型进行推导,例如:
var isBoy = true
-
var变量名1,变量名2,…,变量名 n 变量类型
定义多个相同类型的变量并使用零值进行初始化
例如:
var prefix,suffix string
-
var变量名1,变量名2 ,…,变量名n变量类型 = 值1,值2,…,值n
定义多个变量并使用对应的值进行初始化,变量的类型使用值类型进行推导,类型可不相同,
例如:
var name,age = "silence",30
标准定义:
var name string
var age int
var isOk bool
// 声明变量同时并赋值
var a string="china"
var a,b int=3,7 // 给 a 赋值 3 b 赋值 7
var a="china" // 类型推导,a 默认为 string 类型
var a,b="china",7 // 类型推导给 a 赋值 china 并且为 string,给 b 赋值为 7 为 int 类型
批量定义:
var (
变量名1 变量类型1 = 值1
变量名2 变量类型2 = 值2
)
- 定义多个变量并进行初始化,批量赋值中变量类型可省略
例如:
var (
name string = "silenge"
age int = 30
)
初始化表达式可以使用字面量、任何表达式、函数
初始化表达式可以使用字面量、任何表达式、函数
范例代码:
package main
import (
"fmt"
)
// 我是注释
/*
我是块注释
*/
// 全局变量
// var 定义变量
// var 标识符(变量名称) 类型(变量得类型)
// "zz" 等于给 Name 赋值
var Name string = "zz"
func main() {
// 局部变量
// 10 等于给 Age 赋值
var Age int = 10
fmt.Println(Name, Age)
}
输出:
go 的局部变量声明了必须使用,不然会报:weight declared but not used
如下代码:
报错:
但是全局变量定义不使用就不会报错,如下图代码:
成功执行该代码:
当然我们还可以将类型忽略掉,由此又出了类型推导
范例代码:
package main
import (
"fmt"
)
// 全局变量不能使用类型推导
var Name string = "zz"
func main() {
// 类型推导也可以叫做短声明,为 int 类型
Age := 10
weight := 189
fmt.Println(weight, Age)
fmt.Printf("age type = %T\nweight type = %T", Age, weight)
}
通过输出我们会发现 age weight 类型推导为了 int 类型
![image-20210529231244575](1 程序结构.assets\image-20210529231244575.png)
4.1.1 同时定义多个变量
我们在定义变量的时候可以同时定义多个变量并且赋值
范例代码:
package main
import (
"fmt"
)
func main() {
// 这里同时定义了 age, name, height 三个不同的变量,并且同时赋值了三个不同的类型
age, name, height := 18, "zzz", 13.4
fmt.Printf("age type = %T\nweight type = %T\nheight = %T", age, name, height)
}
4.1.2 表达式定义范例
初始化表达式可以使用字面量、任何表达式、函数
范例代码:
package main
import (
"fmt"
)
func main() {
// 这种称为表达式
age := 12 + 1
fmt.Println(age)
}
4.1.3 简短声明
在函数内可以通过简短声明语句声明并初始化变量,可通过简短声明同时声明和初始化多个变量,需要注意操作符左侧的变量至少有一个未定义过
注意:
全局变量不能通过短声明来定义变量
4.1.4 赋值
赋值等于更改变量的值
范例代码:
package main
import (
"fmt"
)
func main() {
// 定义 age 变量并且赋值为 12
var age = 12
fmt.Println("第一次赋值", age)
// 重新对 age 赋值 20
age = 20
fmt.Println("第二次赋值", age)
// 最后对 age 赋值为 100
age = 100
fmt.Println("最终赋值", age)
}
最后输出的值是最后一次赋值的值
也可以一次同时赋值多个变量
4.1.5 变量总结
变量:
-
变量必须先定义再使用
-
变量都是有类型的指定,未指定通过值推导
-
定义变量的几种方式:
// 1 种 var name type // 2 种 var name type = value // 3 种 var name = value // 4 种:只能在函数内部使用,不能在全局变量使用 name := value
-
标识符在局部内只能定义一次,一个
{}
就为一个局部 -
变量可以在后期更改它的值的
4.1.6 常量
常量可以声明全局的也可以声明为局部的,常量定义以后就不能更改他的值
常量必须在定义的时候就需要赋值,因为后期不能更改该常量
而且常量这一块不能定义函数
常量用于定义不可被修改的的值,需要在编译过程中进行计算,只能为基础的数据类型布尔数值、字符串,使用const进行常量声明,
常用语法:
-
const 常量名 类型 = 值
定义常量并初始化,
例如:
const pi float64 = 3.14
-
const 常量名 = 值
定义常量,类型通过值类型进行推导
例如:
const e = 2.177
-
批量定义
const( 常量名1 类型1 = 值1 常量名2 类型2 = 值2 ) // 定义多个常量并进行初始化,批量赋值中类型可以省略,并且除了第一个常量值外其
-
常量可同时省略类型和值,表示使用前一个常量的初始化表达式
例如:
const ( name string = "silence" age int = 30 ) const ( name string = "silence" desc )
-
常量之间的运算,类型转换,以及对常量调用函数
len、cap、real、imag、complex、unsafe.Sizeof
得到的结果依然为常量
范例代码:
package main
import (
"fmt"
)
// 全局定义常量
const statusNew int = 1
const statusDelete int = 2
func main() {
// 局部定义常量
const Monday = "星期一"
const Tuesday = "星期二"
fmt.Println(statusNew, statusDelete, Monday, Tuesday)
}
常量定义后不能被赋值了,否则报错
输出:
报错无法给 Tuesday 常量进行分配
4.2 常量
常量在定义时必须赋值,且程序运行期间其值不能改变
伪代码 1 :
const PI float32=3.14
const(
PI=3.14
E=2.71
)
伪代码 2 :
const(
a=100
b //100,跟上一行的值相同
c //100,跟上一行的值相同
)
4.2.1 多常量定义
范例代码:
package main
import (
"fmt"
)
// 全局定义常量
const statusNew int = 1
const statusDelete int = 2
func main() {
// 多常量定义,局部定义常量
const (
Monday = "星期一"
Tuesday = "星期二"
)
fmt.Println(statusNew, statusDelete, Monday, Tuesday)
}
如果常量里面被小括号括起来了,除了第一个常量被初始化值了,后面的常量没有初始化值,会自动继承第一个常量的初始化值
范例代码:
package main
import (
"fmt"
)
// 全局定义常量
const (
statusNew int = 1
statusDelete
)
func main() {
// 局部定义常量
const (
Monday = "星期一"
Tuesday = "星期二"
yyds // 如果在同一个小括号内,若为赋值则使用最近的一个已赋值常量对应的值进行初始化
)
fmt.Println(statusNew, statusDelete, Monday, Tuesday, yyds)
}
会发现这里 yyds也被赋值为了 星期二,statusDelete 赋值为了 1
4.2.2 枚举(iota)
伪代码:
const(
a=iota //0
b //1
c //2
d //3
)
const(
a=iota //0
b //1
_ //2
d //3
)
const(
a=iota //0
b=30
c=iota //2
d //3
)
const(
_=iota // iota =0
KB=1<<(10* iota) // iota =1
MB=1<<(10* iota) // iota =2
GB=1<<(10* iota) // iota =3
TB=1<<(10* iota) // iota =4
)
const(
a,b = iota+1, iota+2 //iota = 0 , a = iota+1=0+1 , b = iota+2=0+2 , a,b = 1,2
c,d //iota = 1 , c = iota+1=1+1 , d = iota+2=1+2 , c,d = 2,3
e,f //iota = 2 , e = iota+1=2+1 , f = iota+2=2+2 , e,f = 3,4
)
枚举:
有一个值是有一个范围的,而且每一个值都对应一个类型,并且这些值都是固定的
范例代码:
package main
import (
"fmt"
)
func main() {
// 枚举值
// 这里就类似于 status 的枚举,并且对应的值是 1,2,3
// 分别对应的是 New Complete Delete
const (
// iota 在小括号内,初始化为 0 ,每次调用一次 + 1
// 如果在同一个小括号内,若为赋值则使用最近的一个已赋值常量对应的值进行初始化
// 所以这里都会调用 statusNew 的值 iota
statusNew = iota // 0
statusComplete // 1
statusDelete // 2
)
fmt.Println(statusNew, statusComplete, statusDelete)
}
4.2.3 枚举时赋值
枚举的同时也支持赋值
package main
import (
"fmt"
)
func main() {
// 枚举值
// 这里就类似于 status 的枚举,并且对应的值是 1,2,3
// 分别对应的是 New Complete Delete
const (
// iota 在小括号内,初始化为 0 ,每次调用一次 + 1
// 如果在同一个小括号内,若为赋值则使用最近的一个已赋值常量对应的值进行初始化
// 所以这里都会调用 statusNew 的值 iota
// 这里再定义枚举的同时 + 10
statusNew = iota + 10 // 10
statusComplete // 11
statusDelete // 12
)
fmt.Println(statusNew, statusComplete, statusDelete)
}
iota 乘法的用法范例
package main
import (
"fmt"
)
func main() {
const (
// 由于 iota 每次 + 1 所以这里乘以 10
statusNew = iota * 10 // 0
statusComplete // 10
statusDelete // 20
)
fmt.Println(statusNew, statusComplete, statusDelete)
}
4.3 字面量
字面量:没有出现变量名,直接出现了值。基础类型的字面量相当于是常量
fmt.Printf("%t\n", 04 == 4.00) //用到了整型字面量和浮点型字面量
fmt.Printf("%v\n", .4i) //虚数字面量 0.4i
fmt.Printf("%t\n", '\u4f17' == '众') //Unicode和rune字面量
fmt.Printf("Hello\nWorld\n!\n") //字符串字面量
5 作用域
作用域是限制变量的可以使用范围。go 语言使用大括号显示的标识作用域范围,大括号内包含一连串的语句,叫做语句块。语句块可以嵌套,语句块内定义的变量不能在语句块外使用
全局域中只能定义变量不能调用变量
常见隐式语句块:
- 全语句块
- 包语句块
- 文件语句块
- if、switch、for、select、case 语句块
作用域内定义变量只能被声明一次且变量必须使用,否则编译错误。在不同作用域可定义相同的变量,此时父部变量将被覆盖
作用域原理图:
我们在一个 package 作用域中定义了 Name ,然后再 main 函数作用域中也定义了 Name age 。然后又开启了一个
{var age int = 33 fmt.Println(Name, age)}
,这时候 age 就是 33 ,但是由于 Name 不在该{}
作用域中,所以就去找 main 作用域是否有 Name 变量,假如 main 也没有就会直接找到 Package 中是否有 Name
如果当前作用域没有对应的变量,就会找父级的作用域
范例代码:
package main
import (
"fmt"
)
// 该 Name 变量作用域在全局
var Name string = "zz"
func main() {
// 作用域用来说明标识符的适用范围 {}
// 该 Name 变量作用域在 main 函数中
var Name string = "gg"
var age int = 30
fmt.Println(Name, age)
}
输出发现只会将最后一次的 name 赋值 gg 输出
作用域细节:
当我们在局部作用域定义了一个和全局作用域相同的变量时,如果先执行的话就是先执行全局作用域的变量
package main
import (
"fmt"
)
// 该 Name 变量作用域在全局
var Name string = "zz"
func main() {
// 这里输出的时候是使用的全局变量 Name
fmt.Println(Name)
// 该 Name 变量作用域在 main 函数中
var Name string = "gg"
var age int = 30
fmt.Println(Name, age)
}
一个大括号就是一个作用域:
package main
import (
"fmt"
)
// 该 Name 变量作用域在全局
var Name string = "zz"
func main() {
// 这里输出的时候是使用的全局变量 Name
fmt.Println(Name)
// 作用域用来说明标识符的适用范围 {}
// 该 Name 变量作用域在 main 函数中
var Name string = "gg"
var age int = 30
// 一个大括号就是一个作用域,这里将 age 定义为了 33 ,并且输出的时候只会输出该作用域的结果
{
var age int = 33 // 这里是重新声明了 age
fmt.Println(age)
}
fmt.Println(Name, age)
}
通过输出,我们会发现在 {}
中定义的 age 并不会覆盖掉 main 函数中的 age 变量的值
在子作用域中赋值变量,会修改父作用域的该变量值
范例:
package main
import (
"fmt"
)
// 该 Name 变量作用域在全局
var Name string = "zz"
func main() {
fmt.Println(Name)
var Name string = "gg" // 被重新赋值为了 YY
var age int = 30
{
var age int = 33 // 这里是重新声明了 age
fmt.Println(age)
Name = "yy" // 将 Name 变量重新赋值为了 yy
}
fmt.Println(Name, age)
}
我们可以看到,输出的时候并没有输出 gg ,而是将父作用域的 Name 变量重新修改为了 yy。
总结:
作用域会先在同级的里面找,如果同级里面没有就会从父作用域去找,所以这里将 gg 修改为了 yy
7 注释
Go 支持两种注释方式,行注释和块注释:
行注释:以 //
开头,例如://我是行注释
块注释:以 /* 开头, */ 结尾
,例如:/*我是块注释*/
8 问题追踪和排查
最基本的问题跟踪方式为打印日志,我们可以fmt
包中提供的Println、Print、Printf
函数用于将信息打印到控制台,帮助我们进行问题调试
我们一般先是将代码分成一半来 fmt.Println()
来观察结果,判断代码前一半有问题还是没有问题,类似于二分查找,逐步缩小我们的代码范围。
9 输出&输入
输出的话通过 fmt 包中的 Println、Print、Printf
即可
输入范例:
package main
import (
"fmt"
)
func main() {
var name string
fmt.Println("请输入名字:")
fmt.Scan(&name) // 取 name 变量地址
fmt.Println("你输入的内容为:", name)
}
元哥又开始表演了 {{chan}}