语法基础
注释
// 单行注释
/* xxxx */ 编译器忽略该区间,其间都被认为是注释内容。虽然Go支持,但很少使用,一般都用多行//
//包注释
package main
import "fmt"
/*
x int
y int
*/
// x int
// y int 多行注释
func main() {
fmt.Println("zzzz") //打印
//TODO: 将来完成,一把是这个任务没写完,加入这个注释,方便下次继续完成这个任务
}
// TODO: 将来完成,推荐
// NOTE: 请注意
// Deprecated: 告知已经过期,建议不要使用。未来某个版本可能移除
- 函数、结构体等习惯把注释写在上面
- 包注释会写在package之上
命名规范
-
标识符采用CamelCase驼峰命名法
如果只在包内可用,就采用小驼峰命名
如果要在包外可见,就采用大驼峰命名userName 小驼峰命名 首字母小写中间大写 UserName 大驼峰 首字母大写 中间大写 区别在与包外可见 user_name snake 蛇形命名
-
简单循环变量可以使用i、j、k、v等
一般用简单的英文单词
-
条件变量、循环变量可以是单个字母或单个单词,Go倾向于使用单个字母。Go建议使用更短小
-
常量驼峰命名即可
在其他语言中,常量多使用全大写加下划线的命名方式,Go语言没有这个要求
对约定俗成的全大写,例如PI -
函数/方法的参数、返回值应是单个单词或单个字母
-
函数可以是多个单词命名
-
类型可以是多个单词命名
-
方法由于调用时会绑定类型,所以可以考虑使用单个单词
-
包以小写单个单词命名,包名应该和导入路径的最后一段路径保持一致
-
接口优先采用单个单词命名,一般加er后缀。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
标识符
-
一个名字,本质上是个字符串,用来指代一个值
-
只能是大小写字母、数字、下划线,也可以是Unicode字符
Unicode字符也可以是中文 但是不推荐标识符采用中文的字符
-
不能以数字开头
-
不能是Go语言的关键字
不能和关键字冲突
-
尽量不要使用“预定义标识符”,否则后果难料
-
大小写敏感
字面常量
它是值,不是标识符,但本身就是常量,不能被修改。
Go语言中,boolean、rune、integer、float、complex、string都是字面常量。其中,rune、
integer、float、complex常量被称为数值常量。
100
0x6162 0x61_62_63
3.14
3.14e2
3.14E-2
'测' //字符 ''单引号中 有且仅有一个
'\u6d4b'
'\x31'
'1'
'\n'
"abc" "\x61b\x63" // 字符串 ""双引号中,可以有一个也可以多个
"测试" "\u6d4b试"
"\n"
true
false
iot
‘1’ "1"的区别
‘1’ 这个代表有且只有一个数字就是1 代表的是字符
“1” 这个代表和这个字符串里有1 代表的是字符串
常量
常量:使用const定义一个标识符,它所对应的值,不允许被修改。
对常量并不要求全大写加下划线的命名规则。
-
常量必须在定义是赋值,并且后面不能再变化
-
数组不能赋值给常量
const 常量名 = 初值 常量必须在定义是直接赋初值 package main import "fmt" func main() { const a int = 100 // 指定类型定义常量并赋值 const b = "abc" // 定义常量,等式左边未给出类型,将进行类型推导 const c = 12.3 const d = 'T' fmt.Printf("%T\n", a) fmt.Printf("%T\n", b) fmt.Printf("%T\n", c) fmt.Printf("%T\n", d) } int %T 输出值对应的类型 string float64 int32
批量定义
const ( c = "abc" // 类型推导 String类型 d = 100 // 字面常量 无类型常量100 关联, 默认推导int类型 e uint8 = 100 // 无类型常量100 ,和左边有类型常量uint8类型 e // int 8bits => 1bytes -128~127 0~255 f float32 = 1.5 //flost32类型 g = true //bool类型 )
变量
- 变量:赋值后,可以改变值的标识符。
- 建议采用驼峰命名法。
package main
import "fmt"
func main() {
var a // 错误,无法推测类型
var b int // 正确,只声明,会自动赋为该类型的零值
var c, d int // 正确,声明连续的同类型变量,可以一并声明,会自动赋为该类型的零值
var b = 200 // 错误,b多次声明,第二行已经声明过
b = 200 //正确
var f, e int = 11, 22 // 正确
}
// 用var声明,立即赋值,或之后赋值
var b int // 正确,只声明,会自动赋为该类型的零值
b = 200
b = 300
b = "4" // 错误,类型错误
var a = 100 // 右边无类型常量,缺省类型是int,左边a没有指定类型,用到类型推导,所以a为int类型 var a int = 100
var a // 错误的,因为没有右边,go是强类型语言,此时不能利用右边来推导,左边又没有指定类型,所以错误
var a int // 正确,指定了类型,但是没有赋值。go中正确的原因是 零值可用。int的零值是0
var a int32 = 200 // 正确,指定了类型,也赋值。不会采用类型推导
// 批量赋值
var a int, b string // 错误,批量不能这么写
var ( // 正确
a int
b string
)
var ( // 错误,变量必须有类型,但没有给类型,也不能使用值来推导类型
a
b
)
var a int, b string = 111, "abc" // 错误,多种类型不能这么写,语法不对
var (
a int = 111
b string = "abc"
) // 正确,建议批量常量、变量都这么写
var (
a = 111
b = "abc"
) // 正确,类型推导
变量:标识符的指向的值建立后,可以改变
- 类型也可以变,往往动态语言JS、Python,举例 a = 100, a=“abc”, a=[1,2,3]
- 同类型可以变
- 批量定义 必须给定类型或者赋值,但是常量可以不用定义类型,因为常量在定义的时候必须给定一个值,因此可以进行类型推到
短格式
a := 100 // 赋值语句,短格式变量定义语句,定义变量。定义了变量标识符a,右边可以用来推导a的类型为int
相当于 var a = 100 同样的 没有定义类型 通过字面量来推导a的类型
不可以重复定义类型
var a int //零值0
a := 100 //错误的 因为前面已经定义类型了 短格式在定义类型是不允许的
有特例:
var a int
a,b := 123,"abcd" //竟然可以?在这里a不能重新定义为新类型,a被检测到了,go语言上只能迁就你了,但是要求a同类型,b是新定义的
短格式要求:
a, b := 123, "abcd"
a, b, c := 123, "abcd", 12.5 // 短格式批量同一行定义,因为短格式不能指定类型,所以采用类型推导,左右要求个数一致
短格式
使用 := 定义变量并立即初始化
只能用在函数中,不能用来定义全局变量
不能提供数据类型,由编译器来推断
交换
go的数据交换是特殊的 具体的特殊就是在定义和赋值后,想相当于对变量名进行了快照的处理,在交换数据时,是直接调用快照前,变量指向的数值
package main
import "fmt"
func main() {
a := 100
b := 200
a, b = b, a //按照别的语言当a=b时,a=200了,后面的b=a肯定是调用的是a=200,但是go不是,go依旧调用的是前面a=100,相当于是快照了
fmt.Println(a, b)
}
[Running] go run "f:\goprpject\test2\m.go"
200 100
iota
//一定是批量定义const这种,如果是单行 iota一直都是0
// iota 定义成批常量,iota从0,从成批的第一行
package main
import (
"fmt"
)
func main() {
const (
a = iota //第一行为0
b // 1
c // 2
)
fmt.Println(a, b, c)
}
0 1 2
package main
import (
"fmt"
)
func main() {
const (
a = iota + 1 // 1
b // 2 批量定义的时候,下面一行可以继承上面的表达式 b = iota +1
c // c = iota + 1 iota=2 c=3
_ // 空白标识符,黑洞 _=iota+1 iota =4
d
t = 100
_
_
e // ? e=100 //批量定义的时候,下面一行可以继承上面的表达式
)
fmt.Println(a, b, c, d, t, e)
}
1 2 3 5 100 100
const (
m = 100 // iota:0
n // n : 100, iota : 1
a = iota * 2 // 2 * 2
b = iota * 2 // 3 * 2
c
d
)
fmt.Println(a, b, c, d) // 4 6 8 10
总结:iota只能用于常量,是在成批定义和序列有关的常量定义,iota在成批定义中,可以认为是行索引,可以应用为日期和月份
索引和序号的区别:序号一般是从1开始的,行索引一般从0开始
零值
变量已经被声明,但是未被显式初始化,这是变量将会被设置为零值。其它语言中,只声明未初始化的变量误用非常危险,但是,Go语言却坚持“零值可用”理念。在Go语言中合理利用零值确实带来不小的便利
- int为0
- float为0.0
- bool为false
- string为空串""(注意是双引号)
- 指针类型为nil
- 其它类型数据零值,学到再说
标识符本质
*每一个标识符对应一个具有数据结构的值,但是这个值不方便直接访问,程序员就可以通过其对应的标识符来访问数据,标识符就是一个指代。一句话,标识符是给程序员编程使用的。*标识符就是给人来看的,经过机器编译后,就是以内存地址的形式存在。
标识符写源代码时候,用来指代某个值的。编译后还有变量、常量标识符吗? 没有了,因为数据在内存中,内存访问靠什么?地址,标识符编译后就没有了就换成了地址了。
源代码本质是文本文件
编译 ,源代码编程成二进制可执行文件。运行这个磁盘上的二进制可执行文件,运行在当前OS上,变成进程,变量、常量、值在这块内存中放着
变量可见性
包级标识符
在Go语言中,在.go文件中的顶层代码中,定义的标识符称为包级标识符。如果首字母大写,可在包外可见。如果首字母小写,则包内可见。
在a.go的文件中
package main
// 无类型常量定义
var a = 20 // int
var b = 3.14 // float64
在m.go的文件中
package main
import "fmt"
func main() {
fmt.Println(a, b)
}
使用建议
- 顶层代码中定义包级标识符首字母大写作为包导出标识符,首字母小写作为包内可见标识符
- const定义包级常量,必须在声明时初始化
- var定义包级变量
可以指定类型,也可以使用无类型常量定义
延迟赋值必须指定类型,不然没法确定零值 - 有相关关系的,可以批量定义在一起
- 一般声明时,还是考虑“就近原则”,尽量靠近第一次使用的地方声明
- 不能使用短格式定义
局部标识符
定义在函数中,包括main函数,这些标识符就是局部标识符。
使用建议
-
在函数中定义的标识符
-
const定义局部常量
-
var定义局部变量
可以指定类型,也可以使用无类型常量定义
延迟赋值必须指定类型,不然没法确定零值 -
有相关关系的,可以批量定义在一起
-
在函数内,直接赋值的变量多采用短格式定义
布尔型
类型bool,定义了2个预定义常量,分别是true、false。
bool在go中不是int类型,也不是其他整数类型。在go中,bool就是布尔型,和整型没有关系,就是完全不同的类型。
数值型
整型
-
长度不同:int8、int16(C语言short)、int32、int64(C语言long)
-
长度不同无符号:uint8、unit16、uint32、uint64
- byte类型,它是uint8的别名
-
自动匹配平台:int、uint
- int类型它至少占用32位,但一定注意它不等同于int32,不是int32的别名。要看CPU,32位就是4字节,64位就是8字节。但是,也不是说int是8字节64位,就等同于int64,它们依然是不同类型!
以上类型均不相同,但是独立的类型
package main import "fmt" func main() { var a = 20 b := 30 var c int = 40 fmt.Printf("%T, %T, %T, %d\n", a, b, c, a+b+c) } int, int, int, 90 看区别 package main import "fmt" func main() { var a = 20 b := 30 var c int = 40 fmt.Printf("%T, %T, %T, %d\n", a, b, c, a+b+c) var d int64 = 50 fmt.Printf("%T, %T\n", d, a+d) // a是int类型 d是int64 不是一个类型 必须同类型才能相加 } package main import "fmt" func main() { var a = 20 b := 30 var c int = 40 fmt.Printf("%T, %T, %T, %d\n", a, b, c, a+b+c) var d int64 = 50 fmt.Printf("%T, %T\n", d, a+int(d)) } int64, int //d是int64 将强制类型转换为int类型
%d 数值形式,往往用于整数
%T 表示type,取值的类型
强制类型转换
把一个值从一个类型强制转换到另一种类型,有可能转换失败。
package main
import "fmt"
func main() {
var d int64 = 50
fmt.Printf("%T, %d\n", d, d)
fmt.Printf("%T, %s; %T, %d; %T, %f\n", string(d), string(d), rune(d),
rune(d), float32(d), float32(d))
}
输出如下
int64, 50
string, 2; int32, 50; float32, 50.000000
%T 表示type,取值的类型
%d digital 数值形式,往往用于整数
%s 用string类型的值
%q 带字符串类型引号的%s,quote引号
%c character,字符输出
%f float浮点数输出,默认精度为6
字符和整数
字符表达,必须使用单引号引住一个字符。
单引号留给了表示字符,字面量表达,本质上是int32(rune)或byte(uint8)
双引号和反引号用来表示字符串字面量。
type rune = int32 // rune是int32的别名,4个字节,可以是Unicode字符
type byte = uint8 // byte是uint8的别名,1个字节
package main
import "fmt"
func main() {
var c rune = '中' // 字符用单引号
fmt.Printf("%T, %[1]c, %[1]d\n", c) // int32, 中, 20013
}
[1] 代表的是指定后面的序号
c = 'a'
fmt.Printf("%T, %c, %d\n", c, c, c) // int32, a, 97
var d byte = '中' // 错误,超出byte范围 byte是1个字节 但是中是四个字节的 转换出来超出byte的范围
浮点数
float32:最大范围约为3.4e38,通过math.MaxFloat32查看
float64:最大范围约为1.8e308,通过math.MaxFloat64查看
打印格式化符常用%f
// fmt的格式化,参考包帮助 https://pkg.go.dev/fmt
f := 12.15
fmt.Printf("%T, %f\n", f, f) // 默认精度6
fmt.Printf("%.3f\n", f) // 小数点后3位
fmt.Printf("[%3.2f]\n", f) // 宽度撑爆了,中括号加上没有特殊含义,只是为了看清楚占的
打印宽度
fmt.Printf("[%6.2f]\n", f) // 宽度为6,保留两位小数
fmt.Printf("[%-6.2f]\n", f) // 左对齐