Golang简介
Golang语言拥有极简的部署方式:
- 可直接编译成机器码
- 不依赖其他库
- 直接运行可部署
Golang的特点:
- 静态类型语言,需要编译,编译过程可检查出代码问题。
- 语言层面的原生并发,底层优化利用多核。
- 强大的标准库。runtime系统调度机制、高效的GC垃圾回收、丰富的标准库。
- 简单易学。25个关键字、语言简洁、面向对象特征、跨平台。
- 逐步拥有较为丰富的生态。代表项目docker、kubernetes。
Golang的不足:
- 包管理。大部分包在Gitbub上。
- 所有Excepiton都用Error来处理,比较有争议。
- 对C的降级处理,并非无缝,没有C降级到asm那么完美,存在序列化问题。
第一行代码
package main
import "fmt"
func main() {
/* 简单的程序 万能的hello world */
fmt.Println("Hello Go")
}
编译:$ go build hello.go
对源代码hello.go编译得到二进制文件hello。
运行:$ ./hello
执行当前目录下的二进制文件。
编译并运行:$ go run hello.go
该命令是编译和运行的结合,并且不会生成二进制文件,日常测试使用。
- 第一行代码package main定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
- 下一行import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
- 下一行func main()是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
- 注意:这里面go语言的语法,定义函数的时候,‘{’ 必须和函数名在同一行,不能另起一行,否则会报错,这是语言强制规定的代码规范。
变量的声明
所有类型的变量初始化以后都是对应数据类型的数值0
单变量声明
package main
import "fmt"
func main() {
//第一种 使用默认值
var a int
fmt.Printf("a = %d\n", a)
//第二种
var b int = 10
fmt.Printf("b = %d\n", b)
//第三种 省略后面的数据类型,自动匹配类型
var c = 20
fmt.Printf("c = %d\n", c)
//第四种 省略var关键字
d := 3.14
fmt.Printf("d = %f\n", d)
}
- 方法一:声明一个变量默认的值是0,
var a int - 方法二:声明一个变量,初始化一个值,
var b int = 100 - 方法三:在初始化的时候,可以省去数据类型,通过值自动匹配当前的变量的数据类型,
var c = 100 - 方法四:(常用的方法)省去var关键字,直接自动匹配,
e :=100
多变量声明
package main
import "fmt"
var x, y int
var ( //这种分解的写法,一般用于声明全局变量
a int
b bool
)
var c, d int = 1, 2
var e, f = 123, "liudanbing"
//这种不带声明格式的只能在函数体内声明
//g, h := 123, "需要在func函数体内实现"
func main() {
g, h := 123, "需要在func函数体内实现"
fmt.Println(x, y, a, b, c, d, e, f, g, h)
//不能对g变量再次做初始化声明
//g := 400
_, value := 7, 5 //实际上7的赋值被废弃,变量 _ 不具备读特性
//fmt.Println(_) //_变量的是读不出来的
fmt.Println(value) //5
}
常量const
//const来定义枚举类型
const(
//可以在const()添加一个关键字iota,每行的iota都会累加1,第一行的iota的默认值是0
BEIJING= 10*iota //iota=0
SHANGHAI //iota=1
SHENZHEN //iota=2
)
const(
a, b = iota+1, iota+2 // iota = 0,a = iota + 1, b = iota + 2, a = 1, b = 2
c,d //iota = 1,c = iota + 1,d = iota + 2,c = 2,d = 3
e, f //iota = 2,e = iota + 1,f = iota + 2,e = 3,f = 4
g, h = iota *2,iota *3 //iota = 3,g = iota * 2,h = iota * 3,g = 6,h = 9
i, k //iota = 4, i = iota * 2,k = iota * 2
)
const定义枚举类型,当需要重复定义自增常量时,可以使用iota,iota只能在const括号内使用。
iota在第一行默认为0,自增只与所在代码行数相关,并且可以使用公式计算,公式会被后续的变量继承,如果有新公式则会覆盖旧的计算公式。
函数
函数多返回值
// 正常单返回值
func foo1(a string,b int) int {
C:=100
return c
}
//返回多个返回值,匿名的
func foo2(a string,b int)(int,int){
return 666,777
}
//返回多个返回值,有形参名称的
func foo3(a string,b int)(r1 int,r2 int)
//r1、r2属于foo3的形参,初始化默认的值是0
//r1、r2作用域空间是foo3整个函数体的{}空间
//给有名称的返回值变量赋值
r1=1000
r2=2000
return
}
// 相同参数类型可省略
func foo4(a string,b int)(r1,r2 int){
//给有名称的返回值变量赋值
r1 =1000
r2 =2000
return
}
- 单返回值,在括号后加返回值类型
- 多返回值,多个类型加上括号,此处不能省略类型,一个类型代表一个返回值
- 返回值可添加形参名称,此时形参默认值为0,作用域在函数块内
- 返回值有名称就不需要在return处写出,但是要注意不要忘记赋值返回值的所有参数
init函数与import导包
执行文件时的顺序:
- 当前main文件执行import语句
- 递归的执行import直到没有import可以导入
- 此时形成了一个import递归栈,执行最后入栈文件的const和var,然后执行init(),完成调用并返回上一级,直到回到main文件
- 重复步骤1-3完成所有的import语句
- 执行main文件的const -> var -> init() -> main(),最后完成执行退出
注意事项:
- 如果需要在代码执行前导入配置或者加载数据库,可以将操作写在init()函数中在main()前执行。
- import导包需要写出在
$GOPATH下的完整路径。 - 在包中要提供对外访问的函数,函数名首字母大写,驼峰式,否则只能包内访问。
导包的3种方式:
- 别名。在包名前自定义名称。
_下划线。匿名导包,不使用包不会引起报错,无法调用,但是会执行包的init()函数。.点。导入所有包,导入的函数当作当前文件的函数可以直接调用。但是容易导入同名函数引起冲突,不推荐。
指针
func swap(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
指针的用法和C语言一致,使用&取地址,*读地址。
defer语句
- defer语句在函数的最后执行。可以有多个defer语句,语句以压栈的顺序执行,先进后出。
- defer在return之后执行调用。defer先被压栈,然后在代码块的生命周期结束以后,即“}”后面才被调用。
数组与动态数组
固定长度数组
//固定长度的数组
var myArray1[10]int
myArray2 :=[10]int{1,2,3,4}
myArray3:=[4]int{11,22,33,44}
fori:=0;i<len(myArray1);i++{
fmt.Println(myArray1[i])
}
//iterable元素可以直接使用
for range myArray2 {
fmt.Println("something to print")
}
//只取前一个值
for index := range myArray2 {
fmt.Println("index = ", index)
}
//只取后一个值
for _, value := range myArray2 {
fmt.Println("value = ", value)
}
//值都取
for index,value := range myArray2 {
fmt.Println("index = ", index, ", value = ", value)
}
不同长度的数组类型是不同的,例如[4]int和[10]int类型
动态数组
myArray = []int{1,2,3,4}
[]int类型是动态数组,也是切片类型,不同长度的类型是一致的。作为函数的传入参数时是引用传递,可以被修改内容值。
slice的4种声明方式
//声明slice1是一个切片,并且初始化,默认值是1,2,3。长度len是3
slice1 := []int{1,2,3}
//声明slice1是一个切片,但是井没有给slice分配空间
var slice2 []int
slice1=make([]int,3)//开辟3个空间,默认值是0
//声明sLice1是一个切片,同时给slice分配空间,3个空间,初始化值是0
var slice3 []int = make([]int,3)
//声明slice1是一个切片,同时给sLice分配空间,3个空间,初始化值是0,通过:=推导出sLice是一个切片
slice4 := make([]int,3)
//通过判断长度len是否和nil相等来判断是否有空间
if slice1 == nil {
fmt.Println("slice是空切片")
}
slice切片的追加与截取
varnumbers=make([]int,3,5)
fmt.Printf("len =%d,cap =%d,slice =%v\n",len(numbers),cap(numbers),numbers)
//向numbers切片追加-个元素1,numberslen=4,[0,0,0,1],cap=5
numbers =append(numbers,1)
fmt.Printf("len =%d,cap =%d,slice =%v\n",len(numbers),cap(numbers),numbers)
//向numbers切片追加-个元素2,numberslen=5,[0,0,0,1,2],cap=5
numbers =append(numbers,2)
fmt.Printf("len =%d,cap =%d,slice=%v\n",len(numbers),cap(numbers),numbers)
//向一个容量cap已经满的slice追加元素,
numbers=append(numbers,3)
fmt.Printf("len=%d,cap=%d,slice=%v\n",len(numbers),cap(numbers),numbers)
var numbers2=make([]int,3)
动态数组有两个属性len和cap表示长度和容量。容量代表实际分配的物理内存,但是只有len范围的被初始化了,也只有len范围的可以合法访问。
可以通过append(array []type, element type)函数来在合法的len到cap范围内追加元素,如果len == cap则数组扩容cap翻倍。
而slice切片都是共用一个物理内存的,只有len的长度不一样。切片的len可能小于实际已经有数值的长度,但是对于切片来说超过len的位置访问还是非法,需要使用append() 操作。
使用copy()函数深复制得到不同物理内存的数组,复制的元素是两个数组len长度的较小值。
map的3种声明方式
// ===>声明方式1:map后[]内的是key的类型,[]后的是value的类型
var myMap1 map[string]int
// 通过nil判断是否为空
if myMap1 == nil {}
// 在使用map前和数组一样要分配空间
myMap1 = make(map[string]int, 10)
// ===>声明方式2:分配空间,不需要确定长度len
myMap2 := make(map[string]int)
myMap2["a"] = 1
// ===>声明方式3:声明直接初始化,最后需要有逗号
myMap3 := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
map类型是没有cap容量的,在声明时可以不需要确定len长度来分配空间,赋值以后自动扩容,因为map占用的是零散的内存空间。可以在make()传入长度参数,但是实际长度跟分配确定的无关,传入是无效的,有多少元素才有多少长度。
map和数组一样,传入参数是引用传递,可以直接修改值。
map的删除操作使用delete(map map[keytype]valuetype, key keytype)函数。
struct的定义与使用
type Book struct {
title string
author string
}
var mybook1 Book
mybook1.title = "Golang"
mybook1.author = "name"
- type是一种关键字用来给类型定义别名。
- struct中不需要加 , 来分隔结构体元素。
- struct传入函数的是值,需要使用指针,本质上还是因为struct不是动态的,所以传递的不是引用,和普通变量一样。