Go语言基础|青训营笔记

86 阅读7分钟

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)