万字总结!go语言速成教程(适用于有基础同学)


前言

这篇博客完全是因为对go突然产生了兴趣才开始的,无奈实习岗位太少,暑期还是准备去做java全栈了,虽然都说java卷(更吃学历了),但是go生态真的暂时落后太多,所以还是老老实实去做java了,go之后有缘再做吧,(大厂的话内部转go,尤其是java转go,是挺常见的,尤其是字节)。

也是考虑了很多,最后还是选了java。实习打开boss、实习僧,清一色的java,go岗位还是很有限的(其实也还好,不过java真的多)。问了一些学长学姐,大家本科出来也都是走的后端开发岗,身边也有部分走数据分析的(运维、测试就算了)。走嵌入式方向,硬件也没基础,据说今年嵌入式卷到飞起,还是老老实实开发岗比较合适。c++游戏开发,也是很可以的,还有一些时间,如果下学期找不到实习的话也会去做一两个游戏项目,也不能太all in java吧。

这篇博客前面部分还是自己学过来的,后面到了高级应用是一塌糊涂,拿了很多人家的博客内容,勉强拼凑完了,唉,还是开始java吧,不然开学实习就不好投了,希望之后还有精力分享java学习和实习面试内容。

go

1. go语言优势

  • 可直接编译成机器码,不依赖其他库。
  • 静态类型语言是有动态语言的感觉,静态类型的语言就是可以在编译的时候检查出来隐藏的大多数问题,动态语言的感觉就是有很多的包可以使用,写起来的效率很高。
  • 语言层面支持并发,这个就是Go最大的特色,天生的支持并发。Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。
  • 内置runtime,支持垃圾回收,这属于动态语言的特性之一吧,虽然目前来说GC(内存垃圾回收机制)不算完美,但是足以应付我们所能遇到的大多数情况,特别是Go1.1之后的GC。
  • 简单易学,Go语言的作者都有C的基因,那么Go自然而然就有了C的基因,那么Go关键字是25个,但是表达能力很强大,几乎支持大多数你在其他语言见过的特性:继承、重载、对象等。
  • 丰富的标准库,Go目前已经内置了大量的库,特别是网络库非常强大。
  • 内置强大的工具,Go语言里面内置了很多工具链,最好的应该是gofmt工具,自动化格式化代码,能够让团队review变得如此的简单,代码格式一模一样,想不一样都很困难。
  • 跨平台编译,如果你写的Go代码不包含cgo,那么就可以做到window系统编译linux的应用,如何做到的呢?Go引用了plan9的代码,这就是不依赖系统的信息。
  • 内嵌C支持,Go里面也可以直接包含C代码,利用现有的丰富的C库。

2. go语言不足

  • 泛型缺乏: 尽管在Go 1.18版本中引入了泛型,但其功能相对其他语言仍然有限,可能不够成熟,使用起来也不如C++或Java中的泛型灵活。
  • 错误处理: Go采用显式的错误返回机制,而不是异常处理。这种方法虽然简单明确,但在处理复杂的错误逻辑时,代码显得繁琐且冗长,容易导致大量的重复代码。
  • 依赖管理: 尽管Go模块系统(Go Modules)已经有所改进,但在处理依赖管理方面,尤其是版本控制和依赖冲突上,仍然存在一些问题。
  • 不支持动态加载: Go不支持像Java那样的动态类加载,这在某些需要动态扩展或插件化的应用场景中会显得不够灵活。
  • 生态系统较小: 相对于Java、Python等流行语言,Go的生态系统和第三方库相对较少,特别是在一些特定领域,这点相比java来说是很大的劣势。
  • GUI开发支持有限: Go在桌面应用程序的开发支持上较为有限,虽然有一些第三方库,如fyne和Qt bindings,但整体上不如Java或C#等语言的GUI开发工具成熟。

3. go适合用来做什么

  • 服务器编程,以前你如果使用C或者C++做的那些事情,用Go来做很合适,例如处理日志、数据打包、虚拟机处理、文件系统等。
  • 网络编程,这一块目前应用最广,包括Web应用、API应用、下载应用。
  • 内存数据库,如google开发的groupcache,couchbase的部分组建。
  • 云平台,目前国外很多云平台采用Go开发,CloudFoundy的部分组建,前VMare的技术总监自己出来搞的apcera云平台。
  • 分布式系统,数据库代理器等。

4. 环境搭建与编译器

这个可以看其他文章(点击访问网址),go的环境变量配置中,goroot是go的安装目录,相当于java中的jdk,gopath是go的项目工作目录,存放项目代码,个人比较推荐用vscode来编写go语言程序。


第一章 基础语法

1. Hello word!

go代码需要声明package包名,通过import引入我们的库(第三方库或者自定义库)。其中package main表示一个可独立执行的程序,每个 go 应用程序都包含一个名为 main 的包(并不是包名叫做main)。
func main()是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)

注意:这里面go语言的语法,定义函数的时候,‘{’ 必须和函数名在同一行,不能另起一行。

package main

import (
	"fmt"
)

func main() {
   //不能另起一行
	fmt.Println("Hello word!")
}

2. 变量声明

go支持匿名变量,用_空白标识符)来代替变量,比如:_, str := test(),test()返回两个值。

  • 指定变量类型var a int=0(没有赋值则为默认值)
  • 不指定变量类型var a=0(自动识别)
  • 省略var关键字a :=0(这种不可以用于声明全局变量)

多变量声明:

var ( //这种分解的写法,一般用于声明全局变量
        a int
        b float32
)
var aa,bb = 123, "go"

3. 常量(const和iota)

const在c++经常使用,在go语言中同样表示常量,即不可修改的变量。

  • 显式类型定义:const b string = "abc"(是否声明变量类型)
  • 隐式类型定义:const b = "abc"

常量可以计算表达式的值len(),cap(),unsafe.Sizeof()等,也就是说常量可以作为参数被传入到这些函数中。

const (
    a = "abc" //abc
    b = len(a) //3
    c = unsafe.Sizeof(a) //16
)
//unsafe.Sizeof(a)输出的结果是16
//字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,
//这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。

//组成可以理解成此结构体
type string struct 
{
   
    Data uintptr    // 指针占8个长度
    Len  int        // 长度64位系统占8个长度
}

注:常量表达式中,函数必须是内置函数。

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量。在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加1。有点像我们c++的迭代器,每次都会进行自增。

const (
    Unknown = 0
    Female = 1
    Male = 2
)//普通的const枚举

//采用iota进行枚举
const (
            a = iota   //0
            b          //b=1 ,iota = 1
            c          //c=2, iota = 2
            d = "ha"   //独立值,结果为ha,iota=3
            e          //"ha",   iota=4
            f = 100    //iota =5
            g          //100  iota =6
            h = iota   //7,恢复计数
            i          //8
            j = "nihao"//nihao   iota=9
            k = "shijie"//shijie  iota=10
            l = iota//11
            m		//12
    )

4. 函数

4.1 函数结构

在go中,函数名称首字母大写是公有方法,首字母小写是私有方法,变量也是。
不像c++和java通过public,private等关键字来对变量和函数进行修饰以控制作用域。

基本的结构如下:

func Name(/*参数列表*/) (a type1, b type2/*返回类型*/) {
   
    //函数体
    return v1, v2 //返回多个值
}

根据参数和返回值有无,简单举下面几个例子,这个也和其他语言类似:

func Test1(v1 int, v2 int) {
    //有参无返回
	//或者func Test02(v1, v2 int)
    fmt.Printf("v1 = %d, v2 = %d\n", v1, v2)
}

//形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
func Test2(args ...int) {
   //可变参数,传入若干个int型变量
    for _, n := range args {
    //忽略索引,遍历参数列表
        fmt.Println(n)
    }
}

多个返回值:

func Test01() (int, string) {
    //方式1
    return 1, "go"
}
 
func Test02() (a int, str string) {
    //方式2, 给返回值命名
    a = 1
    str = "go"
    return
}

init()函数,构造函数,和其它语言执行顺序一样,不多赘述。
main函数,只能在package main中。

注:一个包会被多个包同时导入,那么它只会被导入一次,也就是只执行一次init()。

4.2 函数传参

两种传参方式,值传递和引用传递,和c++的传参机制类似,go的传参还是很简单的。

  • 值传递是指在调用函数时将实际参数复制一份传递到函数中,不影响实际参数。
  • 引用传递(指针传递)

函数中引用传递用*
变量指向地址用&

4.3 匿名函数和闭包

这个很重要!
匿名函数是指没有名字的函数,可以在声明的同时定义和调用。匿名函数非常适合用于需要一次性使用的函数,或者将函数作为参数传递给其他函数的场景。

package main
import "fmt"

func main() {
   
    // 定义并调用一个匿名函数
    func(message string) {
   
        fmt.Println(message)
    }("Hello, World!")//()的作用是,此处直接调用此匿名函数
}

闭包是指函数可以捕获并记住其作用域外的变量。闭包使得这些变量的值在函数调用后仍然保持不变,或者在不同的函数调用间共享这些变量的状态。

或者理解为定义在一个函数内部的函数,是将函数内部和函数外部连接起来的媒介。

package main
import "fmt"

func main() {
   
    // 创建一个闭包
    counter := func() func() int {
   //函数名为func(),返回值为func() int,传参为null
        count := 0
        return func() int {
   
            count++
            return count
        }
    }()//()的作用是,此处直接调用此匿名函数
    // 调用闭包
    fmt.Println(counter()) // 输出: 1
    fmt.Println(counter()) // 输出: 2
    fmt.Println(counter()) // 输出: 3
}

count变量的生命周期却不会因为函数执行完而结束。这是因为count变量在内函数外部定义,被这个匿名函数所引用,所以它会在整个程序的运行期间保留下来。

5. 类型转换

go语言中不允许隐式转换,所有类型转换必须显式声明,而且转换只能发生在两种相互兼容的类型之间。

    var ch byte = 97
    //var a int = ch //err, cannot use ch (type byte) as type int in assignment
    var a int = int(ch)

类型别名

    type bigint int64 //int64类型改名为bigint
    var x bigint = 100
 
    type (
        myint int    //int改名为myint
        mystr string //string改名为mystr
    )

6. defer关键字

defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数。
什么意思呢?
c++的析构函数,是在函数生命周期结束后执行的函数。defer也类似,相当于延迟执行函数。
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行,包括导包的顺序,就类似于析构函数,defer的执行在return语句之后。

注意,defer语句只能出现在函数或方法的内部。
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。
通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。
释放资源的defer应该直接跟在请求资源的语句后。

func main() {
   
    a, b := 10, 20
    defer func(x int) {
    // a以值传递方式传给x
        fmt.Println("defer:", x, b) // b 闭包引用
    }(a) 
    a += 10
    b += 100
 
    fmt.Printf("a = %d, b = %d\n", a, b)
    /*
        运行结果:
        a = 20, b = 120
        defer: 10 120
    */
}

7. 数组与slice

go的数组定义var a[10] int {1, 2, 3},但是定长数组在函数传参时需要声明长度,a[10] int 和 a[4] int是不同的变量,而且这样只是浅拷贝(值拷贝),这些是传参时的区别。
还有数组遍历,可以用for也可以用range函数,这个前面有讲,返回索引(下标)和数组元素值,常使用匿名变量来丢掉索引值。

7.1 定义切片

slice(切片),可以认为是动态数组。slice定义比较简单,有点像c语言需要用malloc()动态分配内存。而且slice传参是引用传递,就像在c++中,数组本质上就是指针。

//1. 直接创建,只需要中括号内不声明元素个数就默认动态数组
var a []type

//2. 声明数组,使用make()函数来开辟空间,有点像c语言
var slice1 []type = make([]type, len)
//也可以简写为:
slice1 := make([]type, len)

//3.也可以指定容量,其中capacity为可选参数
make([]T, length, capacity)

切片扩容是以cap为单位进行扩容,超过cap容量时,会自动扩展成2*cap。

7.2 切片初始化

//1. 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s :=[] int {
   1,2,3 }
balance := [...]float32{
   1000.0, 2.0, 3.4, 7.0, 50.0}//也可以用...表示
balanced := [5]float32(1:2.0, 3:7.0)//声明部分元素

//2. 初始化切片s,是数组arr的引用
s:=arr[:]

//3. 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片,首位若缺省就是默认
s := arr[startIndex:endIndex]

//4. 通过切片s初始化切片s1
s1 := s[startIndex:endIndex]

//5. 通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片,cap是容量
s :=make([]int,len,cap)

7.3 切片常用操作

① len() :获取切片长度。

对切片进行截取时,可以认为是通过指针来实现的,不是浅拷贝。

② cap() :测量切片最长可以达到多少。

一个切片在未初始化之前默认为 nil(类似于null),长度为 0。

③ 切片截取:可以通过设置下限及上限来设置截取切片[lower-bound:upper-bound]。

package main
import "fmt"

func main() {
   
   /* 创建切片 */
   numbers := []int{
   0,1,2,3,4,5,6,7,8}   
   printSlice(numbers)


   /* 打印原始切片 */
   fmt.Println("numbers ==", numbers)


   /* 打印子切片从索引1(包含) 到索引4(不包含)*/
   fmt.Println("numbers[1:4] ==", numbers[1:4])


   /* 默认下限为 0*/
   fmt.Println("numbers[:3] ==", numbers[:3])


   /* 默认上限为 len(s)*/
   fmt.Println("numbers[4:] ==",
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值