2024年【golang基础教程(持续更新ing)】_golang教程(1),2024年最新Golang面试题2024笔试

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

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语言运算符


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


### 算数运算符


![在这里插入图片描述](https://img-blog.csdnimg.cn/f8121a4226a34e7ab818ae4ccbf92419.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAbGl1d2FuZ2xlb29P,size_20,color_FFFFFF,t_70,g_se,x_16)  
 **注意:**


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


### 关系运算符


![在这里插入图片描述](https://img-blog.csdnimg.cn/3cc1f317196c48ab8cc523ae90097522.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAbGl1d2FuZ2xlb29P,size_20,color_FFFFFF,t_70,g_se,x_16)  
 **关系运算符中结果是布尔类型的,只有两个值true和false**


### 逻辑运算符


![在这里插入图片描述](https://img-blog.csdnimg.cn/77094b3fa4424414a389d4c2525722b5.png)


### 赋值运算符


![在这里插入图片描述](https://img-blog.csdnimg.cn/c67cae1e40714fa5a2b733795275d893.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAbGl1d2FuZ2xlb29P,size_20,color_FFFFFF,t_70,g_se,x_16)


### 位运算符


![在这里插入图片描述](https://img-blog.csdnimg.cn/c12849533bd8476c80484452921c4133.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAbGl1d2FuZ2xlb29P,size_20,color_FFFFFF,t_70,g_se,x_16)


### 其他运算符


![在这里插入图片描述](https://img-blog.csdnimg.cn/c8fbf4be2c1c4d0da8344aa8c658da61.png)


### 运算符的优先级


从上到下优先级由高到低  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3e528404036649638f1fda7d86f7ff31.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAbGl1d2FuZ2xlb29P,size_20,color_FFFFFF,t_70,g_se,x_16)


### 键盘输入语句


导包 `fmt` 调用fmt包的函数 Scanln 或者Scanf  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/a881f423f7dc4c73a900a5d949a4e7fc.png)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ead6840a653f447381c16c9fa1ccb1ca.png)


### 进制


<https://studygolang.com/search?q=golang%E8%BF%9B%E5%88%B6>


### 位运算


![在这里插入图片描述](https://img-blog.csdnimg.cn/ef1eec5fc18647c2a2c6003b2290d608.png)


## 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语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e21ea6af952149c6af4dfb9fc999e4d9.png)



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函数本体

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


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


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



// 准备一个字符串
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 (
“fmt”
)
func main() {
var cat int = 1
var str string = “banana”
fmt.Printf(“%p %p”, &cat, &str)
}


**从指针获取指针指向的值**  
 当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用\*操作符,也就是指针取值,代码如下。



package main

import (
“fmt”
)

func main() {

// 准备一个字符串类型
var house = "Malibu Point 10880, 90265"

// 对字符串取地址, ptr类型为\*string
ptr := &house

// 打印ptr的类型
fmt.Printf("ptr type: %T\n", ptr)

// 打印ptr的指针地址
fmt.Printf("address: %p\n", ptr)

// 对指针进行取值操作
value := \*ptr

// 取值后的类型
fmt.Printf("value type: %T\n", value)

// 指针取值后就是指向变量的值
fmt.Printf("value: %s\n", value)

}


变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:


* 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
* 指针变量的值是指针地址。
* 对指针变量进行取值操作使用\*操作符,可以获得指针变量指向的原变量的值。


**使用指针修改值**



package main

import “fmt”

// 交换函数
func swap(a, b *int) {

// 取a指针的值, 赋给临时变量t
t := \*a

// 取b指针的值, 赋给a指针指向的变量
\*a = \*b

// 将a指针的值赋给b指针指向的变量
\*b = t

}

func main() {

// 准备两个变量, 赋值1和2
x, y := 1, 2

// 交换变量值
swap(&x, &y)

// 输出变量值
fmt.Println(x, y)

}


**创建指针的另一种方法——new() 函数**  
 Go语言还提供了另外一种方法来创建指针变量,格式如下:  
 `new(类型)`


一般这样写:



str := new(string)
*str = “Go语言教程”
fmt.Println(*str)


new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。


## go语言结构体


Go 语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型。Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。  
 Go 语言中的类型可以被实例化,使用new或&构造的类型实例的类型是类型的指针。  
 结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:


* 字段拥有自己的类型和值。
* 字段名必须唯一。
* 字段的类型也可以是结构体,甚至是字段所在结构体的类型。


关于 Go 语言的类(class),Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。  
 Go 语言的结构体与“类”都是复合结构体,但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。  
 Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。


`话不多说,全在代码`



package main

import “fmt”

// 嵌套结构体
type Dog struct {
name, color string
age int
}

// 定义结构体
type Person struct {
id int
name string
age int
email string
dog Dog
}

type Customer struct {
name string
}

func (customer Customer) login(name string, pwd string) bool {
fmt.Printf(“customer: %p\n”, customer)
if name == customer.name && pwd == “123” {
return true
}
return false
}

func (customer *Customer) show(name string) {
fmt.Printf(“customer: %p\n”, customer)
fmt.Printf(“%v 欢迎登陆 %v\n”, customer.name, name)
}

func (person Person) eat(food string) {
fmt.Printf(“%v…eat…%v\n”, person.name, food)
}

func showPerson(person *Person) {

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

关于 Go 语言的类(class),Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。
Go 语言的结构体与“类”都是复合结构体,但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。
Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。

话不多说,全在代码

package main

import "fmt"

// 嵌套结构体
type Dog struct {
	name, color string
	age         int
}

// 定义结构体
type Person struct {
	id    int
	name  string
	age   int
	email string
	dog   Dog
}

type Customer struct {
	name string
}

func (customer Customer) login(name string, pwd string) bool {
	fmt.Printf("customer: %p\n", customer)
	if name == customer.name && pwd == "123" {
		return true
	}
	return false
}

func (customer \*Customer) show(name string) {
	fmt.Printf("customer: %p\n", customer)
	fmt.Printf("%v 欢迎登陆 %v\n", customer.name, name)
}

func (person Person) eat(food string) {
	fmt.Printf("%v...eat...%v\n", person.name, food)
}

func showPerson(person \*Person) {


[外链图片转存中...(img-V1sCHB8u-1715710353149)]
[外链图片转存中...(img-T8kaXPEg-1715710353150)]
[外链图片转存中...(img-IGk71XDu-1715710353151)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值