Golang学习笔记:语言基础 | 豆包MarsCode AI刷题

30 阅读5分钟

学习自8小时转职Golang工程师

Golang简介

Golang语言拥有极简的部署方式:

  • 可直接编译成机器码
  • 不依赖其他库
  • 直接运行可部署

Golang的特点:

  • 静态类型语言,需要编译,编译过程可检查出代码问题。
  • 语言层面的原生并发,底层优化利用多核。
  • 强大的标准库。runtime系统调度机制、高效的GC垃圾回收、丰富的标准库。
  • 简单易学。25个关键字、语言简洁、面向对象特征、跨平台。
  • 逐步拥有较为丰富的生态。代表项目dockerkubernetes

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 *2iota *3  //iota = 3,g = iota * 2,h = iota * 3,g = 6,h = 9
    i, k    //iota = 4, i = iota * 2,k = iota * 2
)

const定义枚举类型,当需要重复定义自增常量时,可以使用iotaiota只能在const括号内使用。
iota在第一行默认为0,自增只与所在代码行数相关,并且可以使用公式计算,公式会被后续的变量继承,如果有新公式则会覆盖旧的计算公式。

函数

函数多返回值

// 正常单返回值
func foo1(a string,b int) int {
    C:=100
    return c
}

//返回多个返回值,匿名的
func foo2(a string,b int)(intint){
    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导包

执行文件时的顺序

  1. 当前main文件执行import语句
  2. 递归的执行import直到没有import可以导入
  3. 此时形成了一个import递归栈,执行最后入栈文件的const和var,然后执行init(),完成调用并返回上一级,直到回到main文件
  4. 重复步骤1-3完成所有的import语句
  5. 执行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{123}

//声明slice1是一个切片,但是井没有给slice分配空间
var slice2 []int
slice1=make([]int3)//开辟3个空间,默认值是0

//声明sLice1是一个切片,同时给slice分配空间,3个空间,初始化值是0
var slice3 []int = make([]int3)

//声明slice1是一个切片,同时给sLice分配空间,3个空间,初始化值是0,通过:=推导出sLice是一个切片
slice4 := make([]int3)

//通过判断长度len是否和nil相等来判断是否有空间
if slice1 == nil {
    fmt.Println("slice是空切片")
}

slice切片的追加与截取

varnumbers=make([]int35)
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([]int3)

动态数组有两个属性lencap表示长度容量。容量代表实际分配的物理内存,但是只有len范围的被初始化了,也只有len范围的可以合法访问。

可以通过append(array []type, element type)函数来在合法的lencap范围内追加元素,如果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不是动态的,所以传递的不是引用,和普通变量一样。