Go最新【golang基础教程(持续更新ing)】_golang教程,2024年最新Golang教程

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

标识符

Go中对各种变量、函数等命名时使用的字符序列称为标识符。
(一)标识符的命名规则

  • 由26个英文字母大小写、0-9数字、_ 组成
  • 数字不能开头
  • 严格区分大小写(a和A是两个不同的变量)
  • 标识符不能包含空格
  • _ 在Go中是一个特殊的标识符,仅能作为占位符使用而不能作为标识符使用,比如返回值使用_进行忽略
  • 不能以系统关键字作为标识符使用

(二)注意事项

  • 包名 尽量保持package与目录的名称一致,采用简短、有意义、不和标准库重名的包名
  • 变量名、函数名、常量名(大写下划线分割) 、结构体、接口 采用驼峰法命名
  • 如果变量名、函数名常量名首字母大写是公开的,可以被其它包访问,如果首字母小写则是私有的,只能被本包访问

关键字

Go中有保留关键字25个,详情如下表:

break 	    default    	func	    interface	  select
case	    defer	        go	        map	          struct
chan	    else	        goto	    package	      switch
const	    fallthrough	    if	        range     	  type
continue	for	            import	    return	      var

预定义标识符就是事先定义好的有特殊意义的词,与关键字类似。

append	bool	byte	cap	      close	    complex	complex64	complex128	uint16
copy	false	float32	float64	  imag	    int	    int8	    int16	    uint32
int32	int64	iota	len	      make	    new	    nil	        panic	    uint64
print	println	real	recover	  string	true	uint	    uint8	    uintptr

go语言数据类型

在这里插入图片描述
这部分先介绍基本数据类型,下面会详细介绍每个复杂数据类型

变量

变量相当于内存中数据存储空间的表示,通过变量名访问到变量值,直接上代码

package main

import (
	"fmt"
	"reflect"
)

func main() {
	/\*
 变量 程序运行间 可以改变的量
 \*/
	// 1.声名格式 var 变量名 类型 变量声明了 必须使用
	// 2.只是声明没有初始化的变量 默认值为0
	// 3.在同一作用域里 声明的变量名是唯一的
	var a int
	fmt.Println(a)

	// 4.可以同时声明多个变量
	// var b, c string

	// 5.变量赋值
	a = 10
	fmt.Println(a)

	// 6.声明变量&初始化
	var b int = 100
	b = 3
	fmt.Println(b)

	// 另外一种方法 := 自动推导类型
	c := "aaa"
	fmt.Println(c)
	fmt.Println("c is ", reflect.TypeOf(c))

	// 多重赋值
	i, j := "ac", 100

	// 匿名变量 丢弃数据
	m, \_ := j, i
	fmt.Println(m)
}


常量

package main

import (
	"fmt"
)

const (
	PI    = 2.22
	SUPER = "dd"
)

func main() {
	// 常量 运行期间 不可改变的量 常量声明需要使用const
	const a int = 1000
	fmt.Println(a)
	
	// 
	// iota 常量自动生成器
	// 给常量赋值使用
	// iota遇到const 重置为0
	// 可以只写一个iota
	// 如果是同一行 值都一样
}


布尔类型

package main

import "fmt"
func main() {
	// 布尔类型 条件判断 循环 go中0/1不能用作bool
	b := true
	fmt.Printf("b: %v\n", b)
}


数字类型

package main

import (
	"bytes"
	"fmt"
	"math"
	"strings"
	"unsafe"
)

func main() {
	/\*
 数字类型
 int uint (8,16,32,64)
 float32 float64默认
 整形格式化输出 %d
 进制格式化输出 %b %o %x
 浮点格式化输出 %f
 \*/
	var i8 int8
	fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(i8), math.MaxInt8, math.MinInt8)
	var f32 float32
	fmt.Printf("%T %dB %v~%v\n", f32, unsafe.Sizeof(f32), math.MaxInt8, math.MinInt8)

}


字符串类型

package main

import (
	"bytes"
	"fmt"
	"strings"
	"unsafe"
)

func main() {
	/\*
 字符串类型
 字面量使用""或者反引号``创建
 格式化输出 %s
 \*/
	var name string = "James"
	fmt.Printf("name: %v\n", name)
	//字符串连接
	s1 := "200"
	s2 := "success"
	msg := s1 + s2
	fmt.Printf("msg: %v\n", msg)
	msg = fmt.Sprintf("%s%s", s1, s2)
	fmt.Printf("msg: %v\n", msg)
	msg = strings.Join([]string{s1, s2}, " ")
	fmt.Printf("msg: %v\n", msg)
	var buffer bytes.Buffer
	buffer.WriteString(s1)
	buffer.WriteString(s2)
	fmt.Printf("buffer.String(): %v\n", buffer.String())

	//转义字符
	// \n \t

	//索引切片 同python
	s := "I LOVE GOLANG"
	fmt.Printf("%v\n", s[1:9])

	// 其他常用方法
	fmt.Printf("%v\n", len(s))
	fmt.Printf("%v\n", strings.Split(s, " "))
	fmt.Printf("%v\n", strings.Contains(s, "LOVE"))
	fmt.Printf("%v\n", strings.ToUpper(s))
	fmt.Printf("%v\n", strings.ToLower(s))
	fmt.Printf("%v\n", strings.HasPrefix(s, " "))
	fmt.Printf("%v\n", strings.HasSuffix(s, "GO"))
	fmt.Printf("%v\n", strings.Index(s, "G"))

}

type关键字

类型定义
语法 type NewType Type

package main
import "fmt"

func main(){
	type MyInt int
	var i MyInt
	i = 10
	fmt.Println(i)
}


类型别名
语法 type TypeAlias = Type
类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区
别有哪些呢?下面通过一段代码来理解。

package main
import (
    "fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
    // 将a声明为NewInt类型
    var a NewInt
    // 查看a的类型名
    fmt.Printf("a type: %T\n", a)
    // 将a2声明为IntAlias类型
    var a2 IntAlias
    // 查看a2的类型名
    fmt.Printf("a2 type: %T\n", a2)
}

区别

  • 类型定义相当于定义了一个全新的类型,与之前的类型不同,而类型别名并没有定义全新的类型,而是使用别名替换了之前的类型
  • 类型别名只会在代码中存在,在编程完成之后不会存在该别名
  • 因为类型别名和原来的类型是一致的,所以原来类型的所有方法,类型别名都可以调用,但如果是重新定义的一个类型,那么不可以调用原来的任何方法。

go语言格式化输出

package main

import "fmt"

type WebSite struct {
	Name string
}

func main() {
	/\*
 %v var 任何变量值
 %#v
 %T 类型
 %b %o %x 进制
 %c 字符对应的Unicode值
 %s 字符串
 %p 指针

 \*/
	webSite := WebSite{Name: "baidu"}
	fmt.Printf("webSite: %v\n", webSite)
	fmt.Printf("webSite: %#v\n", webSite)
	fmt.Printf("webSite: %T\n", webSite)
	b := false
	fmt.Printf("b: %v\n", b)

}


go语言运算符

运算符介绍 运算符是一种特殊的符号,用以表示数据的运算,赋值和比较等

算数运算符

在这里插入图片描述
注意:

  • 自增( ++ )和自减( – )在Go语言中是单独的语句, 并不是运算符, 也不是表达式.
  • 不允许不同类型进行相加。

关系运算符

在这里插入图片描述
关系运算符中结果是布尔类型的,只有两个值true和false

逻辑运算符

在这里插入图片描述

赋值运算符

在这里插入图片描述

位运算符

在这里插入图片描述

其他运算符

在这里插入图片描述

运算符的优先级

从上到下优先级由高到低
在这里插入图片描述

键盘输入语句

导包 fmt 调用fmt包的函数 Scanln 或者Scanf
在这里插入图片描述
在这里插入图片描述

进制

https://studygolang.com/search?q=golang进制

位运算

在这里插入图片描述

go语言控制语句

分支结构

三种写法格式:if if……else if……else……if 话不多说上代码

package main

import "fmt"

func main() {
	//if
	flag := true
	// 表达式一定是布尔值
	if flag {
		fmt.Println("a")
	} else {
		fmt.Println("b")
	}
	// 初始化变量放在表达式中 注意作用域
	if age := 20; age > 18 {
		fmt.Println("成年")
	}
	// 不能使用0/1表示真假

	// if ……else
	a, b := 1, 2
	if a > b {
		fmt.Println("a")
	} else {
		fmt.Println("b")
	}

	// if ……else if
	score := 80
	if score >= 60 && score < 70 {
		fmt.Printf("score: %v\n", "C")
	} else if score >= 70 && score < 90 {
		fmt.Printf("score: %v\n", "B")
	} else {
		fmt.Printf("score: %v\n", "A")
	}
}

特殊写法: if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:

if err := Connect(); err != nil {
    fmt.Println(err)
    return
}

Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。err != nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。
提示
在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。

循环结构

与多数语言不同的是,Go语言中的循环语句只支持 for 关键字,而不支持 while 和 do-while 结构,关键字 for 的基本使用方法与C语言和 C++ 中非常接近,主要有以下这几种循环方法

package main

import "fmt"

func main() {
	// for循环
	s := "1234"
	for i := 0; i < len(s); i++ {
		fmt.Printf("i: %v\n", i)
	}

	i := 1
	for ; i < 5; i++ {
		fmt.Println(i)
	}

	j := 1
	for j < 5 {
		fmt.Println(j)
		j++
	}

	// for range
	var k = [...]int{1, 2, 3, 4, 5}
	for i, v := range k {
		fmt.Printf("i:%v v:%v\n", i, v)
	}

}

switch

Go语言的 switch 要比C语言的更加通用,表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true 进行匹配,因此,可以将 if else-if else 改写成一个 switch。

package main

import "fmt"

func main() {
	// switch
	// 条件匹配 表达式
	grade := 1
	fmt.Println(grade)
	switch grade {
	case 'A':
		fmt.Println("字母")
		// 跨越 case 的 fallthrough——兼容C语言的 case 设计
		fallthrough
	case 1, 2, 3:
		fmt.Println("数字")
	default:
		fmt.Println("默认")
	}
}

在Go语言中 case 是一个独立的代码块,执行完毕后不会像C语言那样紧接着执行下一个 case,但是为了兼容一些移植代码,依然加入了 fallthrough 关键字来实现这一功能.

goto&break&continue

package main

import "fmt"

func f1() {
OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                break OuterLoop
            case 3:
                fmt.Println(i, j)
                break OuterLoop
            }
        }
    }
	fmt.Println("end...")
}

func f2() {
	for x := 0; x < 10; x++ {
        for y := 0; y < 10; y++ {
            if y == 2 {
                // 跳转到标签
                goto breakHere
            }
        }
    }
    // 手动返回, 避免执行进入标签
    return
    // 标签
breakHere:
    fmt.Println("done")
}

func f3() {
OuterLoop:
    for i := 0; i < 2; i++ {
        for j := 0; j < 5; j++ {
            switch j {
            case 2:
                fmt.Println(i, j)
                continue OuterLoop
            }
        }
    }
}
func main() {
	//流程控制关键字
	/\*break
 结束 for、switch 和 select 的代码块
 另外 break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,
 标签要求必须定义在对应的 for、switch 和 select 的代码块上。
 \*/
	f1()
	/\*goto
 Go语言中 goto 语句通过标签进行代码间的无条件跳转,
 同时 goto 语句在快速跳出循环、避免重复退 出上也有一定的帮助,
 使用 goto 语句能简化一些代码的实现过程。
 \*/
    f2()
	/\*continue
 Go语言中 continue 语句可以结束当前循环,
 开始下一次的循环迭代过程,仅限在 for 循环内使用,
 在 continue 语句后添加标签时,表示开始标签对应的循环,
 \*/ 
	f3()
}

go语言容器

数组

数组是一个由固定长度特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。

package main

import "fmt"

func test() {
	// 数字 字符串数组定义
	var arr1 [2]int
	fmt.Printf("arr1: %v\n", arr1)
	fmt.Printf("arr1: %T\n", arr1)

	var arr2 [2]string
	fmt.Printf("arr2: %v\n", arr2)
	fmt.Printf("arr2: %T\n", arr2)

	// 初始化定义 名称 长度 类型
	arr1 = [2]int{1, 3}
	fmt.Printf("arr1: %v\n", arr1)

	// 忽略数组长度
	var arr3 = [...]string{"1", "32"}
	fmt.Printf("arr3: %v\n", len(arr3))
}

// 数组的遍历
func getArrElement() {
	var a1 = [...]int{1, 3, 4}
	fmt.Printf("a1: %v\n", a1)
	for \_, v := range a1 {
		fmt.Printf("v: %v\n", v)
	}
}

func main() {
	test()
	getArrElement()
}


切片

和数组对应的类型是 Slice(切片),Slice 是可以增长和收缩的动态序列,功能也更灵活。

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。

Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示。
在这里插入图片描述

package main

import "fmt"

func main() {
	var arr1 = [...]int{1, 2, 3, 4}
	fmt.Printf("arr1: %v\n", arr1)

	// 切片 声明 定义
	var s1 []int
	fmt.Printf("slice1: %v\n", s1)
	var s2 = make([]int, 2)
	fmt.Printf("s2: %v\n", s2)

	// 长度 容量
	s1 = []int{1, 3, 4, 5}
	fmt.Printf("s1: %v\n", s1)
	fmt.Printf("len(s1): %v\n", len(s1))
	fmt.Printf("cap(s1): %v\n", cap(s1))

	//初始化
	// 使用数组初始化
	s3 := arr1[:]
	fmt.Printf("s3: %v\n", s3)
	//使用数组的部分元素初始化 (切片表达式)
	s4 := arr1[1:4]
	fmt.Printf("s4: %v\n", s4)

	// 遍历
	for i := 0; i < len(s1); i++ {
		fmt.Printf("s1[i]: %v\n", s1[i])
	}
	for \_, v := range s1 {
		fmt.Printf("v: %v\n", v)
	}

	// 添加
	s1 = append(s1, 100)
	fmt.Printf("s1: %v\n", s1)
	// 删除
	s1 = append(s1[:3], s1[4:]...)
	fmt.Printf("s1: %v\n", s1)
	// copy
	s1Copy := make([]int, len(s1))
	copy(s1Copy, s1)
	fmt.Printf("s1Copy: %v\n", s1Copy)
}

用法
● 第一个用法主要是用于函数有多个不定参数的情况,表示为可变参数,可以接受任意个数但相同类型的参数。
● 第二个用法是slice可以被打散进行传递。

map

Go语言中 map 是一种特殊的数据结构,一种元素对(pair)的无序集合,pair 对应一个 key(索引)和一个 value(值),所以这个结构也称为关联数组或字典,这是一种能够快速寻找值的理想结构,给定 key,就可以迅速找到对应的 value。

package main

import "fmt"

func main() {
	// 声明 名称 key的类型 value的类型
	var m1 map[int]string
	fmt.Printf("m1: %v\n", m1)
	fmt.Printf("m1: %T\n", m1)
	m2 := make(map[int]string)
	fmt.Printf("m2: %v\n", m2)

	// 初始化
	var m3 = map[int]string{1: "TOM", 2: "lib"}
	fmt.Printf("m1: %v\n", m3)
	m4 := make(map[string]string)
	m4["a"] = "1"
	fmt.Printf("m3: %v\n", m4)
	fmt.Printf("m4[\"a\"]: %v\n", m4["a"])

	// 遍历
	for k, v := range m3 {
		fmt.Printf("k: %v\n", k)
		fmt.Printf("v: %v\n", v)
	}

}


go语言函数

特性

  • 3种函数:普通函数 匿名函数 方法(定义在结构体上)
  • 不允许重载
  • 不能嵌套函数 但可以嵌套匿名函数
  • 函数是一个值 可以将函数赋值给一个变量
  • 函数可以作为参数传递给另外一个函数
  • 函数的返回值可以是一个函数
  • 函数调用的时候,如果有参数传递给函数 则先拷贝参数的副本 再将副本传递给函数
  • 函数的参数可以没有名称

函数

package main

import "fmt"

func sum(a int, b int) int {
	return a + b
}

func test1() {
	fmt.Println("没有参数和返回值的函数")
}

func test2() string {
	return "123"
}

func test3() (name string, age int) {
	name = "job"
	age = 23
	return name, age
}

// 形参
func test4(x int) string {
	x = 200
	return "111"
	// return x
}

func test5(s []int) {
	s[0] = 1000
}

func test6(args ...int) {
	for \_, v := range args {
		fmt.Printf("v: %v\n", v)
	}
}

func sayHello(name string) {
	fmt.Printf("hello, %s\n", name)
}
func test7(name string, f func(string)) {
	f(name)
}
func main() {
	// 返回值
	// 没有参数和返回值
	test1()
	// 有参数一个返回值
	r := test2()
	fmt.Printf("r: %v\n", r)
	// 有多个返回值
	n, \_ := test3()
	fmt.Printf("n: %v\n", n)

	// 参数
	ret := sum(1, 2)
	fmt.Printf("ret: %v\n", ret)
	// 实参
	/\*
 x的值没有改变 说明参数传递是拷贝一个副本
 有些数据类型是就是指针类型 所以拷贝传值也就是拷贝的指针
 拷贝后的参数任然指向底层数据结构 可能会改变原来的数据结构的值
 如
 \*/
	x := 100
	new_x := test4(x)
	fmt.Printf("new\_x: %v\n", new_x)
	fmt.Printf("x: %v\n", x)
	s := []int{1, 2, 4}
	test5(s)
	fmt.Printf("s: %v\n", s)

	// 可变参数
	test6(1, 2, 43, 5)
	
    // 高阶函数 函数作为参数
	test7("liusan", sayHello)
	
	// 函数作为返回值
	f := cal("-")
	ff := f(1, 2)
	fmt.Printf("ff: %v\n", ff)
}

函数类型实现接口

package main
import (
    "fmt"
)
// 调用器接口
type Invoker interface {
    // 需要实现一个Call方法
    Call(interface{})
}
// 结构体类型
type Struct struct {
}
// 实现Invoker的Call
func (s \*Struct) Call(p interface{}) {
    fmt.Println("from struct", p)
}
// 函数定义为类型
type FuncCaller func(interface{})
// 实现Invoker的Call
func (f FuncCaller) Call(p interface{}) {
    // 调用f函数本体
    f(p)
}
func main() {
    // 声明接口变量
    var invoker Invoker
    // 实例化结构体
    s := new(Struct)
    // 将实例化的结构体赋值到接口
    invoker = s
    // 使用接口调用实例化结构体的方法Struct.Call
    invoker.Call("hello")
    // 将匿名函数转为FuncCaller类型,再赋值给接口
    invoker = FuncCaller(func(v interface{}) {
        fmt.Println("from function", v)
    })
    // 使用接口调用FuncCaller.Call,内部会调用函数本体
    invoker.Call("hello")
}

匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成,下面来具体介绍一下匿名函数的定义及使用。
定义一个匿名函数

// 匿名函数
nm := func(a int, b int) int {
	return a + b
}
fmt.Printf("nm: %v\n", nm)

匿名函数用作回调函数

package main
import (
    "fmt"
)
// 遍历切片的每个元素, 通过给定函数进行元素访问
func visit(list []int, f func(int)) {
    for \_, v := range list {
        f(v)
    }
}
func main() {
    // 使用匿名函数打印切片内容
    visit([]int{1, 2, 3, 4}, func(v int) {
        fmt.Println(v)
    })
}

使用匿名函数实现操作封装

package main
import (
    "flag"
    "fmt"
)
var skillParam = flag.String("skill", "", "skill to perform")
func main() {
    flag.Parse()
    var skill = map[string]func(){
        "fire": func() {
            fmt.Println("chicken fire")
        },
        "run": func() {
            fmt.Println("soldier run")
        },
        "fly": func() {
            fmt.Println("angel fly")
        },
    }
    if f, ok := skill[\*skillParam]; ok {
        f()
    } else {
        fmt.Println("skill not found")
    }
}

闭包

Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:
函数 + 引用环境 = 闭包

同一个函数与不同引用环境组合,可以形成不同的实例,如下图所示。
在这里插入图片描述
一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,函数是编译期静态的概念,而闭包是运行期动态的概念

在闭包内部修改引用的变量

// 准备一个字符串
str := "hello world"
// 创建一个匿名函数
foo := func() {
   
    // 匿名函数中访问str
    str = "hello dude"
}
// 调用匿名函数
foo()

闭包的记忆效应
被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。

package main

import (
    "fmt"
)

// 提供一个值, 每次调用函数会指定对值进行累加
func Accumulate(value int) func() int {

    // 返回一个闭包
    return func() int {

        // 累加
        value++

        // 返回一个累加值
        return value
    }
}

func main() {

    // 创建一个累加器, 初始值为1
    accumulator := Accumulate(1)

    // 累加1并打印
    fmt.Println(accumulator())

    fmt.Println(accumulator())

    // 打印累加器的函数地址
    fmt.Printf("%p\n", &accumulator)

    // 创建一个累加器, 初始值为1
    accumulator2 := Accumulate(10)

    // 累加1并打印
    fmt.Println(accumulator2())

    // 打印累加器的函数地址
    fmt.Printf("%p\n", &accumulator2)
}

递归函数

几个例子说明
斐波那契数列

package main
import "fmt"
func main() {
    result := 0
    for i := 1; i <= 10; i++ {
        result = fibonacci(i)
        fmt.Printf("fibonacci(%d) is: %d\n", i, result)
    }
}
func fibonacci(n int) (res int) {
    if n <= 2 {
        res = 1
    } else {
        res = fibonacci(n-1) + fibonacci(n-2)
    }
    return
}

阶乘

package main
import "fmt"
func Factorial(n uint64) (result uint64) {
    if n > 0 {
        result = n \* Factorial(n-1)
        return result
    }
    return 1
}
func main() {
    var i int = 10
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

defer

用于注册延迟调用
直到函数return之前执行
多个defer语句 按照先进后出执行
defer语句中的变量 在声明时定义

package main

import "fmt"

func s1() {
	defer fmt.Println("后面执行1")
	fmt.Println("1111")
	defer fmt.Println("后面执行")
	fmt.Println("2222")
}

func main() {
	s1()
}

init函数

先于main函数自动执行 不能被调用
没有输入参数 返回值
每个包有多个init函数
包的每个源文件可以有多个init函数
同一包的init函数执行顺序 没有明确定义 编程时要注意程序不要依赖这个执行顺序
不同的init函数按照包导入的依赖关系决定执行顺序

go语言指针

与 Java 和 .NET 等编程语言不同,Go语言为程序员提供了控制数据结构指针的能力,但是,并不能进行指针运算。Go语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式,这对于构建运行良好的系统是非常重要的。指针对于性能的影响不言而喻,如果你想要做系统编程、操作系统或者网络应用,指针更是不可或缺的一部分。

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。

指针地址和指针类型
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:

ptr := &v    // v 的类型为 T

其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为*T,称做 T 的指针类型,*代表指针。
指针语法:var var_name *var_type

package main
import (


![img](https://img-blog.csdnimg.cn/img_convert/a20db72cc194bba181de83a3103c48ff.png)
![img](https://img-blog.csdnimg.cn/img_convert/89466cde93013c38dff7bd110991c01c.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

要注意程序不要依赖这个执行顺序  
>  不同的init函数按照包导入的依赖关系决定执行顺序
> 
> 
> 


## go语言指针


与 Java 和 .NET 等编程语言不同,Go语言为程序员提供了控制数据结构指针的能力,但是,并不能进行指针运算。Go语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式,这对于构建运行良好的系统是非常重要的。指针对于性能的影响不言而喻,如果你想要做系统编程、操作系统或者网络应用,指针更是不可或缺的一部分。


指针(pointer)在Go语言中可以被拆分为两个核心概念:


* 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
* 切片,由指向起始元素的原始指针、元素数量和容量组成。


受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。


切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。


**指针地址和指针类型**  
 一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。


每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:



ptr := &v // v 的类型为 T


其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为\*T,称做 T 的指针类型,\*代表指针。  
 `指针语法:var var_name *var_type`



package main
import (

[外链图片转存中…(img-Ia3NRvoP-1715519226046)]
[外链图片转存中…(img-oKdpNRn7-1715519226046)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值