Go语言语法基础
1.Go项目构建及编译
一个Go工程中主要包含以下三个目录:
go
src:源代码文件
pkg:包文件
bin:相关bin文件
我们可以引用别人的包也可以发布自己的包,但是为了防止不同包的项目名冲突,我们通常使用顶级域名来作为包名的前缀,这样就不担心项目名冲突的问题了
因为不是每个个人开发者都拥有自己的顶级域名,所以目前流行的方式是使用个人的github用户名来区分不同的包:
举个例子:张三和李四都有一个名叫studygo的项目,那么这两个包的路径就会是:
go
import "github.com/zhangsan/studygo"
和:
go
import "github.com/lisi/studygo"
2.下划线的用处
++1、下划线在import中++
当导入一个包时,该包下的文件里所有init()
函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()
函数而已。这个时候就可以使用 import 引用该包。即使用import _ 包路径
只是引用该包,仅仅是为了调用init()
函数,无法通过包名来调用包中的其他函数
++2、下划线在代码中++
下划线意思是忽略这个变量,意思是那个位置本应赋给某个值,但是咱们不需要这个值,这样编译器可以更好的优化,任何类型的单个值都可以丢给下划线,例如:
go
package main
import (
"os"
)
func main() {
buf := make([]byte, 1024)
f, _ := os.Open("/Users/***/Desktop/text.txt")
defer f.Close()
for {
n, _ := f.Read(buf)
if n == 0 {
break
}
os.Stdout.Write(buf[:n])
}
}
3.Go语言变量与常量
变量命名规范
1、Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线
2、大写字母和小写字母是不同的:heapSort
和Heapsort
是两个不同的名字
3、如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。
名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的,那么它将是导出的,也就是说可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母
4、通常来说,如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义
5、Go语言程序员推荐使用 驼峰式 命名,当名字由几个单词组成时优先使用大小写分隔,而不是优先用下划线分隔。因此,在标准库有QuoteRuneToASCII
和parseRequestLine
这样的函数命名
变量声明与初始化
Go语言主要有四种类型的声明语句:var
、const
、type
和func
,分别对应变量、常量、类型和函数实体对象的声明
go语言中还支持批量变量声明,例如:
go
var (
a string
b int
c bool
d float32
)
var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。语法如下:
plain
var 变量名字 类型 = 表达式
其中类型
或= 表达式
两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量,例如:
go
func main() {
var s string
var d = 12
fmt.Println(s)
fmt.Println(d)
}
在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部变量。它以"名字 := 表达式
"形式声明变量,变量的类型根据表达式来自动推导,例如:
go
func main() {
i := 100
fmt.Println(i)
}
因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化
这里有一个比较微妙的地方:简短变量声明左边的变量可能并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了,那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了
例如:
在下面的代码中,第一个语句声明了in和err两个变量。在第二个语句只声明了out一个变量,然后对已经声明的err进行了赋值操作
go
in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)
常量
一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在运行期被意外或恶意的修改
所有常量的运算都可以在编译期完成,这样可以减少运行时的工作,也方便其他编译优化。当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等
常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof
iota 常量生成器
iota是go语言的常量计数器,只能在常量的表达式中使用。 iota在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次
例如:周日将对应0,周一为1,如此等等
go
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
我们也可以在复杂的常量表达式中使用iota,下面是一个更复杂的例子,每个常量都是1024的幂:
go
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)
4.Go语言基本数据类型
整型
Go语言同时提供了有符号和无符号类型的整数运算。有int8、int16、int32和int64
四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64
四种无符号整数类型
还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int
和uint
;其中int是应用最广泛的数值类型
无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合
最后,还有一种无符号的整数类型uintptr
,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方
算术运算符+、-、*和/
可以适用于整数、浮点数和复数,但是取模运算符%
仅用于整数间的运算
对于每种类型T,如果转换允许的话,类型转换操作T(x)
将x转换为T类型,例如:
go
func main() {
f := 3.141 // a float64
i := int(f)
fmt.Println(f, i) // "3.141 3"
f = 1.99
fmt.Println(int(f)) // "1"
}
任何大小的整数字面值都可以用以0
开始的八进制格式书写,例如0666
;或用以0x
或0X
开头的十六进制格式书写,例如0xdeadbeef
。十六进制数字可以用大写或小写字母。如今八进制数据通常用于POSIX操作系统上的文件访问权限标志,十六进制数字则更强调数字值的bit位模式
当使用fmt包打印一个数值时,我们可以用%d、%o或%x
参数控制输出的进制格式,就像下面的例子:
go
func main() {
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
}
请注意fmt的两个使用技巧:
1、通常Printf格式化字符串包含多个%
参数时将会包含对应相同数量的额外操作数,但是%
之后的[1]
副词告诉Printf函数再次使用第一个操作数
2、第二,%
后的#
副词告诉Printf在用%o、%x或%X
输出时生成0、0x或0X
前缀
浮点型
Go语言提供了两种精度的浮点数,float32和float64
一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度;通常应该优先使用float64类型
小数点前面或后面的数字都可能被省略(例如.707或1.)。很小或很大的数最好用科学计数法书写,通过e或E来指定指数部分:
go
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34 // 普朗克常数
例子:打印e的幂,打印精度是小数点后三个小数精度和8个字符宽度:
go
for x := 0; x < 8; x++ {
fmt.Printf("x = %d e^x = %8.3f\n", x, math.Exp(float64(x)))
}
复数
Go语言提供了两种精度的复数类型:complex64
和complex128
,分别对应float32
和float64
两种浮点数精度。内置的complex
函数用于构建复数,内建的real
和imag
函数分别返回复数的实部和虚部:
go
func main() {
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x * y) // "(-5+10i)"
fmt.Println(real(x * y)) // "-5"
fmt.Println(imag(x * y)) // "10"
}
上面x和y的声明语句还可以简化:
go
x := 1 + 2i
y := 3 + 4i
如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0:
go
fmt.Println(1i * 1i) // "(-1+0i)"
另外我,复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的
布尔型
一个布尔类型的值只有两种:true和false
在Go语言中,布尔值并不会隐式转换为数字值0或1
指针
一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。通过指针,我们可以直接读或更新对应变量的值,而不需要知道该变量的名字
如果用"var x int
"声明语句声明一个x变量,那么&x
表达式(取x变量的内存地址)将产生一个指向该整数变量的指针,指针对应的数据类型是*int
,指针被称之为"指向int类型的指针
"
go
func main() {
x := 1
p := &x
fmt.Println(*p) // "1"
*p = 2
fmt.Println(x) // "2"
}
任何类型的指针的零值都是nil
。如果p指向某个有效变量,那么p != nil
测试为真。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等
go
func main() {
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
}
字符串
窥探字符串类型
因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的,例如:
go
s[0] = 'L'
在一个双引号包含的字符串中,可以用以反斜杠\
开头的转义序列插入任意的数据。下面的换行、回车和制表符等是常见的ASCII控制代码的转义方式:
plain
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表符
\v 垂直制表符
\' 单引号
\" 双引号
\\ 反斜杠
使用反引号代替双引号,就可以定义一个多行字符串:
go
func main() {
const GoUsage = `Go is a tool for managing Go source code.
Usage:
go command [arguments]
...`
fmt.Println(GoUsage)
}
字符串内建函数
例如内置的len
函数可以返回一个字符串中的字节数目:
go
func main() {
s := "hello, world"
fmt.Println(len(s)) // "12"
d := "你好,世界"
fmt.Println(len(d)) // "15"
}
常用操作:
方法 | 介绍 |
---|---|
len(str) | 求长度 |
+或fmt.Sprintf | 拼接字符串 |
strings.Split | 分割 |
strings.Contains | 判断是否包含 |
strings.HasPrefix,strings.HasSuffix | 前缀/后缀判断 |
strings.Index(),strings.LastIndex() | 子串出现的位置 |
strings.Join(a[]string, sep string) | join操作 |
字符串和数字的转换
将一个整数转为字符串,一种方法是用fmt.Sprintf
返回一个格式化的字符串;另一个方法是用strconv.Itoa
:
go
func main() {
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"
}
如果要将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数,还有用于解析无符号整数的ParseUint函数:
go
func main() {
x, _ := strconv.Atoi("123")
y, _ := strconv.ParseInt("123", 10, 64)
fmt.Println(x)
fmt.Println(y)
}