1、Go程序的一般结构:
- 只有package为main的包可以包含main函数,package要放在非注释的第一行。
- 一个可执行程序有且只有一个main包。
- 在函数体外可以使用var来进行全局变量的声明。
- 使用type进行struct或interface的声明。
- func来进行函数的声明。
Go语言结构一般由以下结构来组织:
- package、放在非注释代码的第一行
- import 一系列包
- 常量的定义
- 全局变量的声明与赋值
- 一般类型声明
- 结构声明
- 接口的声明
- 函数声明
2、Go的基本数据类型
- 布尔:bool: 1字节,不能与数字进行隐式转换。
- 整型:int(根据运行平台可能64bits或32bits)、int8/16/32/64强行指定位数, unit8/16/32/64无符号数。
- 字节型:byte(unit8别名)。
- 浮点型:float32小数点后7位。float64小数点后15位。
- 其它值类型:array、struct、string。
- 引用类型:slice、map、chan(通道)。
- 复数类型:complex64/complex128。
- 足够保存指针的32/64位整数型:uintptr。
- 接口类型:interface
- 函数类型:func
类型零值:变量被声明为某种类型之后的默认值,string为空字符串,bool为false。
3、变量的声明与赋值
全局变量不能够省略var关键字,*“:”*只有在方法体内声明变量的时候有用。
可以使用''_'对变量进行忽略。
var a int // 声明a为int,默认值为0
var a int = 10 // int可以省略
b := 1 // 声明并且自动推断类型
// 在方法体内进行多变量的声明
a, b := 2, 3
var a, b float32 = 2, 3
- Go语言不存在隐式转换,只存在显式转换且只能发生在两者兼容的情况下。
1、int 和 bool不能相互转换。
2、string与int的相互转换需要用到strconv.Itoa()与strconv.Atoi()
var a float32 = 1.1
b := int(a)
4、常量与运算符
- 常量的声明
// b和c将会沿用a的值。
const(
A = 1
B
C
)
// 在常量表达式中只能够出现常量和常量表达式,不能使用一些运行时候才能出现的变量。
// 以下代码无法编译通过
var c = "1234"
const(
A = len(c)
)
// c、d分别与a、b相等。而且声明时要对齐
const(
A, B = 1, "1"
C, D
)
// iota:组中每定义一个常量,其值+1
// iota:当每重新声明一次const、iota会被重制为0
const(
A = 'A'
B = 2
C = iota // c = 2
D = iota // d = 3
)
// 枚举实现计算机存储单位的枚举
const(
_ = iota
KB = 1 << (iota * 10)
MB
GB
TB
)
5、控制语句
- if 语句
var a = 3
a ++
var b int* = &a
// go的if语句可以由声明变量b,并且if语句块内的b都是用的该声明语句中的b、外部的b会被暂时屏蔽
if b := &a; *b == 4 {
fmt.Println("point test!")
}
- for 语句
for {
// do nothing
}
for a < 3{
// do nothing
}
for i:=0; i<3; i++ {
// do nothing
}
- switch
// a的作用范围只在switch语句块中。
switch a := 1; {
case a > 0:
fmt.Println("a > 0")
// fallthrough可以不break,继续测试下一个
fallthrough
case a > 1:
fmt.Println("a > 1")
default:
fmt.Println("a <= 0")
}
跳转语句:
// break跳出标签标注的循环,可以直接break与LABLE1同级的循环。
// continue也可以continue到某个标签同级的for循环。并且continue会保留for循环原用变量
// goto可以goto到某个标签同级的for循环。但goto不会保留for循环原用变量
LABLE1:
for {
for i := 0; i<10; i++{
if i > 3 {
break LABLE1
}
}
}
6、数组
// var声明数组
var arr1 [10]int
var arr2 [9]int
arr2 = arr1 // 无法进行赋值
// 数组声明1
a := [2]int{1, 2}
// 声明数组2,索引为19的元素赋值为1,其余使用默认赋值。
a := [20]int{19: 1}
// 使用...不指定长度,让编译器自己计算。
a := [...]int{1:2, 2:3} // 数组长度为3
// 指向数组的指针,*在前面
a := [...]int{99:20}
var p *[100]int = &a
// 使用new关键字返回的是指向数组的指针,new只能够获得初始化的数组
b := new([100]int)
// 指针数组,[]在前面
x, y := 1, 2
c := [...]*int {&x, &y}
// 多维数组
a := [2][3]int{
{1, 1, 2},
{2, 3, 4}
}
7、切片Slice:变长数组
// 声明一个slice,如果[]里面有...或者N,则声明的是一个数组不是一个slice
var a []int
// 使用make新建一个slice
// 第一个参数是类型,第二个参数是slice的当前大小,第三个参数是slice的容量
s1 := make([]int, 3, 5)
fmt.Println(len(s1), cap(s1))
一般使用make来获得slice,使用new会获得一个指向数组指针的指针。
slice与底层数组的关系:slice的索引以当前slice为基准,其cap是直到底层数组的最后一个元素。 索引不足会导致越界错误。
append会直接在当前索引下追加slice,如果slice容量不够则会自动进行扩容。扩容后该slice将会指向一个全新的地址,和原来的slice不再共用同一个底层数组。
copy函数在拷贝过程中会以较短的那个slice的元素个数为准来拷贝。超过元素个数的值不拷贝。
8、字典Map:
以key-value形式存储数据,Key必须是支持==和!=运算的类型,不可以是map、slice或函数。
Map的查找比线性搜索快很多,但比使用索引访问数据的类型慢100倍。
删除某一个key-value对:
// make(map[key-type]string-type, cap) 创建map
dict := make(map[string]int, 20)
dict["yanghong"] = 1
// delete(dict, key-value) 删除map中的某个key-value对
delete(dict, "yanghong")
// 多层map嵌套,每一层map都需要make
dict := make(map[string]map[string]int, 20)
a, ok := dict["yanghong"]["test"]
if !ok {
dict["yanghong"] = make(map[string]int)
}
dict["yanghong"]["test"] = 1
fmt.Println(a, dict["yanghong"]["test"])
for循环遍历map和slice:
map是无序的,不以插入的顺序来存储。如果需要map保证有序,则可以对key进行排序,根据key来便利value。
for k, v := range dict{
fmt.Println(k, ":", v)
}
9、函数
- Go函数不支持嵌套、重载和默认参数。
- Go支持变长参数,接收后封装成slice,但是不定长变参需要放在参数列表末尾。
- Go可以默认return函数内与返回参数同名的参数。
- Go函数作为一个类型,可以通过参数进行传递。
- 和java相同,有传递值类型和引用类型的区别。
- Go函数支持匿名函数和闭包。闭包的外层函数需要命名。
- Go defer:类似于析构函数,在函数执行完成后执行。
-
- 即便函数发生严重错误也会执行。
- 常用来清理文件,解锁以及记录时间等操作。
- 支持匿名函数。
- 通过与匿名函数配合可以在return之后修改函数计算结果。
- Go使用panic与recover进行异常处理。
// 可以省略返回值,接收变长参数封装成slice
func A(param ...int) (a, b, c int){
a, b, c := 1, 2, 3
// 函数默认返回a,b,c
return
}
// 匿名函数
a := func() {
fmt.Println("匿名函数")
}
// 闭包,返回值是一个函数,可以通过外层函数向内层函数传输参数。闭包可以用来实现策略模式,工厂模式。
func closure(x int) func(y int) int{
return func(y int) int{
return x + y;
}
}
f := closure(10)
fmt.Println(f(1)) // 打印出11
// defer匿名参数,defer使用闭包时,需要将参数先拷贝一份即拷贝到匿名函数中,否则defer会引用参数的地址
for i := 0; i < 3; i++ {
defer func(i) {
fmt.Println(i)
}()
} // 输出全是3
for i := 0; i < 3; i++ {
defer func(x int) {
fmt.Println(x)
}(i)
} // 输出0,1,2,将i通过x传入
// panic 配合defer recover(),这样就不会执行panic而是使用recover
defer func(){
if err := recover(); err != nil{
// do something recover
}
panic("panic B")
}()
10、struct
type person struct{
Name string
Age int
Contact struct{
Phone, City string
}
}
func main(){
// 初始化直接对结构进行取地址
p := &person{
Name: "杨鸿",
Age: 16,
}
// go语言不用*a.Name来访问数据,可以直接a.Name
p.Age = 19
fmt.Println(p)
A(p)
fmt.Println(p)
// 匿名结构体与字段
a := &struct{
Name string
Age int
}{
Name "joe",
Age 20
}
}
func A(p *person){
p.Name = "ljh"
}
Go语言没有继承机制,存在组合:
结构体的嵌入:
type human struct {
Sex int
}
// 结构体内如果是匿名字段,编译器会自动将类型名称当作字段名称使用。
type person struct {
human
Name string
Age int
}
p := &person{
human{Sex: 0},
Name: "杨鸿",
Age: 16,
}
// 可以嵌入访问Sex字段
p.sex = 1
3、方法method
- 使用Receiver来判断方法属于哪一个结构。包括int等底层类型也可以实现方法的绑定。
// 只是得到A的拷贝
func (a A) Print1(){
a.Name = "yanghong"
}
// 得到的是A的指针
func (a *A) Print2(){
a.Name = "yanghong"
}
// Method两种使用方法
type TZ int
var a TZ
a.Print()
(*TZ).Print2(&a)