go语言入门
go源码文件
名称以.go为后缀,内容以go语言代码组织的文件
多个go源码文件是需要用代码包组织起来的
分类
命令源码文件
声明自己属于main代码包、包含无参数声明和结果声明的main函数
被安装后,相应的可执行文件会被存放到gobin指向的目录或者<当前工作区目录>/bin下
命令源码文件是go程序的入口,但不建议把程序都写在一个文件中
注意:同一个代码包中强烈不建议直接包含多个命令源码文件
库源码文件
不具备命令源码文件的那两个特征的源码文件
被安装后,相应的归档文件会被存放到<当前工作区目录>/pkg/<平台相关目录>下
测试源码文件
不具备命令源码文件的那两个特征的源码文件
名称以_test.go为后缀
*其中至少有一个函数的名称以Test或Banchmark为前缀并且,该函数接受一个类型为 *testing.T或 testing.B的参数
func TestFind(t *testing.T){}
func Benchmark(b *testing.B){}
代码包
作用
编译和归档go程序的最基本单位
代码划分、集结和依赖的有效组织形式,也是权限控制的辅助手段
规则
一个代码包实际上就是一个由导入路径代表的目录
导入路径即<工作区目录>/src 或 <工作区目录>/pkg/<平台相关目录>之下的某段子路径
声明
有这么一个路径:/Users/bytedance/Documents/workspace/test/djcao.com/pkgtool
工作区目录是:/Users/bytedance/Documents/workspace/test
一级代码包:djcao.cn
二级代码包:pkgtool
代码包声明语句中的包名称应该是代码包的导入路径的最右子路径
package pkgtool
导入
代码包导入语句中使用的包名称应该与其导入路径一致,例如:
flag import(
fmt ====》 “flag”
strings “fmt”
“strings”
)
带别名的导入
import str "strings"
str.HasPrefix(“abc”,“a”)
本地化的导入
import . "strings" HasPrefix("abc","a")
仅仅初始化
import _ "strings"
仅执行代码包中的初始化函数
初始化
代码包初始化函数即:无参数声明和结果声明的init函数
init函数可以被声明在任何文件中
init函数的执行时机
在代码包被导入后
- 对所有全局变量进行求值
- 执行所有init函数
不同代码包之间的执行时机
场景:A导入B B导入C
init函数的执行顺序 C-》B-》 A
场景: A导入B A导入C
B和C的init执行顺序不确定 但是早于A
注意
代码包的init函数会先于入口main函数的执行,并且只会被执行一次
命令基础
go run
用于运行命令源码文件
只能接受一个命令源码文件以及若干个库源码文件作为文件参数
内部操作步骤
命令源码文件会形成可执行文件,库源码文件会形成归档文件
常用参数
-a:强制编译相关代码,不伦它们的编译结果是否已是最新的
-n:打印编译过程中所需要运行的命令,但不真正执行它们
-p n:并行编译,其中n为并行的数量
-work:显示编译时创建的临时工作目录的路径,并且不删除它
-x:打印编译过程中所需运行的命令
-v:列出被编译的代码包的名称
go build
用于编译源码文件或代码包
编译非命令源码文件不会产生任何结果文件
编译命令源码文件会在该命令的执行目录中生成一个可执行文件
go install
- 执行命令没有参数,把当前目录作为代码包并安装
- 以代码包的导入路径作为参数,该代码包及其依赖会被安装
- 以命令源码文件及相关库源码文件作为参数时,只有这些文件会被编译并安装
go get
用于从远程代码仓库上下载并安装代码包
受支持的代码版本控制系统有:git 、mercurial、svn、bazaar
指定的代码包会被下载到$GOPATH中包含的第一个工作区的src目录中
参数
-d:只执行下载动作,而不执行安装动作
-fix:先执行修正动作,再进行编译和安装
-u:更新已存在的代码包
程序实体与关键字
在go语言中,变量、常量、函数、结构体和接口被统称为“程序实体”,而它们的名字被统称为“标识符”
程序实体不能以数字和下划线开头
访问权限控制
名字首字母大写的程序实体可以被任何代码包中的代码访问到。而名字首字母中为消协的程序实体只能被同一个代码包中的代码所访问
关键字
var
声明一个变量
var可以先声明再赋值
const
只能声明一个常量,值只能是基本类型且需要声明的时候就赋值
基本数据类型
byte和rune
都是别名类型。
byte是uint8的别名类型
rune是int32的别名类型 相当于java中的char。 var chars rune = ‘d’
int
按占用内容(比特)大小分为
int有 int、int8、int16、int32、int64等四种
按有无符合分为
int和uint
unit是无符号的正整数
int可以是负数,两者所能表示的数字范围不一样
float
按占用内存大小分为float64和float32
可以是用科学计数法表示浮点数,需要用十进制,不能使用8进制。
var num = 5.89E-2 //0.0589
string
字符串类型
与java中相似,字符串内需要用\ 处理转义。
但是不能使用+ 进行字符串与int的拼接。 需要使用output := fmt.Sprintf("%s%d","hello go,",kk)
数组类型
需要指定数组的长度或者使用省略符号,自动计算长度
len是关键字,可以计算数组、字典、字符串、切片或通道类型的值的长度
type MyArr [3]int
var arr = [3]int{1,2,3}
var arr2 = [...]int{1,2,3,4,5,6,7}
var length = len(arr2)
切片类型
与数组相似,底层是数组的结构。估计是使用了cap变量控制了能访问的数组访问
声明一个切片
四个关键字的作用
len:获取切片的长度
cap:获取切片的容量
append:忘切片后面添加一个或多个值
copy:把第二个参数中的元素复制到第一个参数中的相应位置上
举例如下:
var slice4 = []int{0, 0, 0, 0, 0, 0, 0}
copy(slice4, slice1)
通过上述复制操作,slice4会变为`[]int{2, 3, 4, 6, 7, 0, 0}`。
var a = []int
var b = []string
type MySlice []int
var numbers3 = [...]int{1,2,3,4,5}
var slice1 = arr[1:4]
var slice2 = arr[1:3]
var cappp = cap(slice2) // 3
map
字典类型的零值为nil
java中的map,类似
注意
对于获取不存在的键值时,获取到的是值的“零值”
使用两个参数接收获取的值时,第二个为bool值,bool值为true表示获取到了值
delete函数可以删除map中的键值对,无论是否存在键,都不会报错。
mm := map[int]string{1: "a", 2: "b", 3: "c"}
delete(mm,3)
vv,ok = map[4] // ok ==false
chan
通道类型
这个类型是线程安全的
类比到java中有点像linkedblockqueue,提供了更加简便的操作
通道是双向的,能发也能收。可以使用type 声明单行通道类型
package main
import fmt
func main(){
ch2 := make(chan int,5)
go func(){
ch2 <- "来了老弟"
fmt.Println("发送成功")
}()
var value = "好嘞"
var value = value + (<- ch2)
close(ch2)
fmt.Println(value)
}
函数
函数也是一种类型
那么就可以定义自己的函数类型
举个例子
type MyFunc func(s string, s2 string)(result string)
func myFunc (s string, s2 string)(result string){
result = s + s2
return // 结果中声明了变量名,则return时不需要写。 结果中声明变量名就是声明了一个没有赋值的变量
}
var splice = myFunc
splice("a","b")
var splice2 = func(s string, s2 string)(result string){
result = s + s2
return // 结果中声明了变量名,则return时不需要写。 结果中声明变量名就是声明了一个没有赋值的变量
}
splice2("a","b")
var result = func(s string, s2 string) string {
return s + s2
}("a","b")
//result收到的是结果 “ab”
结构体和方法
结构体与java中的类相似
结构体有全参构造函数和无参构造函数
匿名结构体是不可能拥有方法的
匿名字段是 字段名与类型名称相同
结构体类型属于值类型。它的零值并不是nil,而是其中字段的值均为相应类型的零值的值。举个例子,结构体类型Person的零值若用字面量来表示的话则为Person{}
type Person struct{
Name string
Age int
Id int
Address string
int // 匿名字段 字段名与类型名称相同
}
var ppp = Person{Name: "Robert", Gender: "Male", Age: 33}
var pppp = Person{"Robert", "Male", 33}
// 匿名结构体
p := struct {
Name string
Gender string
Age uint8
}{"Robert", "Male", 33}
这是类声明方法的方式
第一种是指针方法
第二种是值方法
func (person *Person) Grow() {
person.Age++
}
func (person Person) Grow() {
person.Age++
}
p := Person{"Robert", "Male", 33}
p.Grow()
接口
- 一个接口类型的声明通常会包含关键字type、类型名称、关键字interface以及由花括号包裹的若干方法声明
- 无需在一个数据类型中声明它实现了哪个接口。只要满足了“方法集合为其超集”的条件,就建立了“实现”关系。这是典型的无侵入式的接口实现方法
type Animal interface {
Grow()
Move(new string) (old string)
}
type Person struct{
Name string
Age int
Id int
Address string
}
func(person *Person)Grow{
person.Age++
}
func(person *Person)Move(new string) (old string){
old = person.Address
person.Address = new
return
}
不能在一个非接口类型的值上应用类型断言来判定它是否属于某一个接口类型的。我们必须先把前者转换成空接口类型的值。这又涉及到了Go语言的类型转换
p := Person{"Robert", "Male", 33, "Beijing"}
v := interface{}(&p)
h, ok := v.(Animal) // 进行类型断言,获取类型转换后的值,以及是否转换成功的bool标识
指针
如果一个数据类型所拥有的方法集合中包含了某个接口类型中的所有方法声明的实现,那么就可以说这个数据类型实现了那个接口类型。
在上面提到的数据类型有两种
- 基底类型Person
- 指针类型*Person
一条法则
一个指针类型拥有以它以及以它基底类型为接收者类型的所有方法,而基底类型仅拥有以它本身为接收者类型的方法
注意
我们在基底类型的值上仍然可以调用它的指针方法
若我们有一个Person类型的变量bp,则调用表达式bp.Grow()是合法的。这是因为,如果Go语言发现我们调用的Grow方法是bp的指针方法,那么它会把该调用表达式视为(&bp).Grow()。实际上,这时的bp.Grow()是(&bp).Grow()的速记法