当前位置: 首页 > news >正文

golang 基础案例_01

1.make 和 new 的区别

new和make都是在堆上分配内存‌。
堆(Heap)‌:用于动态内存分配,如使用new或make函数分配的内存。堆中的内存手动分配和释放,因此存在内存泄漏的风险。
 ‌栈(Stack)‌:用于存储局部变量、函数调用的上下文等。栈中的内存由编译器自动管理,不需要手动干预。
new和make的使用场景和区别
new‌:用于为结构体、数组、指针等类型分配内存。它返回一个指向新分配内存的指针。例如,new(int)会分配一个整数大小的内存,并返回一个指向该内存的指针。
make‌:专门用于创建切片(slice)、映射(map)、通道(channel)等内置数据结构。它返回一个对数据结构的引用,而不是指针。例如,make([]int, 0, 100)创建一个长度为0、容量为100的整数切片。

func MakeAndNew() {// 使用 new 创建一个整数指针numPtr := new(int)fmt.Println("numPtr:", *numPtr) // 输出:numPtr: 0,因为 int 类型的零值是 0// 使用 new 创建一个长度为 5 的整数数组指针arrPtr := new([5]int)fmt.Println("arrPtr:", arrPtr) // 输出:arrPtr: &[0 0 0 0 0]// 使用 make 创建一个切片slice := make([]int, 3, 5)fmt.Println("slice:", slice) // 输出:slice: [0 0 0],因为 int 类型的零值是 0// 使用 make 创建一个mapm := make(map[string]int)fmt.Println("m:", m) // 输出:m: map[]// 使用 make 创建一个信道ch := make(chan int)fmt.Println("ch:", ch) // 输出:ch: 0xc000016090,信道的地址
}

2.数组与切片

        长度固定 vs 动态长度:数组的长度在声明时就确定了,无法改变;而切片的长度可以动态增长或缩小。
内存分配方式:数组在声明时会分配固定大小的连续内存空间;而切片则是引用一个数组,通过指针指向底层数组,并记录切片的长度和容量。
传递方式:数组在函数传递时会进行值拷贝,即传递的是数组的副本;而切片在函数传递时是通过引用传递,传递的是指向底层数组的指针。
长度信息:数组的长度是固定的,通过len()函数获取;而切片有两个长度信息:长度(len())和容量(cap()),分别表示当前切片的实际长度和底层数组的容量。
灵活性:切片可以动态增长或缩小,方便进行数据操作和处理;而数组的长度固定,无法动态改变。
总的来说,数组适合存储固定长度的数据,而切片适合存储不固定长度的数据,并且在实际开发中更常用。

type slice struct {array unsafe.Pointer // 元素指针len   int            // 长度cap   int            // 容量
}func ArrayAndSlice() {// 声明一个整数数组arr := [3]int{}fmt.Println("arr:", arr)var a1 [3]intfmt.Println("a1:", a1)a2 := [...]int{}fmt.Println("a2:", a2)// 声明一个整数切片slice := []int{1, 2, 3}fmt.Println("slice:", slice)// 修改数组的元素arr[0] = 100fmt.Println("修改后的 arr:", arr) // 输出:修改后的 arr: [100 0 0]// 修改切片的元素slice[0] = 100fmt.Println("修改后的 slice:", slice) // 输出:修改后的 slice: [100 2 3]
}

3.defer

多个 defer 的顺序,defer 在什么时机会修改返回值?
defer延迟函数,释放资源,收尾工作;如释放锁,关闭文件,关闭链接;捕获panic;
defer函数紧跟在资源打开后面,否则defer可能得不到执行,导致内存泄露;
多个 defer 调用顺序是 LIFO(后入先出),defer后的操作可以理解为压入栈中;
defer,return,return value(函数返回值) 执行顺序:首先return,其次return value,最后defer。defer可以修改函数最终返回值,修改时机:有名返回值或者函数返回指针。

func Defer1() {//defer 先进后出方式执行var whatever [5]struct{}defer fmt.Println(222)for i := range whatever {defer fmt.Println(i)}/** 输出:43210222*/
}
func Defer2() {unres := unnamedReturn()fmt.Println("unres:", unres) // 输出:res: 10nres := namedReturn()fmt.Println("nres:", nres) // 输出:res: 15
}func unnamedReturn() int {result := 10ret := &resultdefer func() {*ret += 5fmt.Printf("ret:%v \n", *ret) //15}()return result
}func namedReturn() (result int) {defer func() {result += 5}()result = 10return
}

4.uint

uint8-uint64 32位操作系统:uint32 64位操作系统:uint64


func Uint() {var a uint8 // 0~255(0~2^8-1)a = 255var b uint8 = 1b = 1fmt.Printf("a=%v,b=%v,a+b=%v \n", a, b, a+b)
}

5.rune

相当int32
golang中的字符串底层实现是通过byte数组的,中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8;
byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符
rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.


func Rune() {// 使用 rune 类型表示单个字符r := 'A'fmt.Printf("r: %c, type: %T \n", r, r) // 输出:r: A, type: int32// 使用 rune 类型处理字符串中的字符s := "Hello, 世界"runes := []rune(s)fmt.Println("runes: \n", runes) // 输出:runes: [72 101 108 108 111 44 32 19498 30028]// 遍历字符串中的字符for _, r := range s {fmt.Printf("%c ", r)}// 输出:H e l l o ,   世 界
}

6.goroutine 协程

goruntime.GOMAXPROCS(1) 设置最大并发数为1

waitGroup.Wait() 等待所有协程执行完毕

var wg sync.WaitGroupfunc Goroutine() {wg.Add(5)collectStart := time.Now()collectStartMills := collectStart.UnixNano() / 1e6ctx, cancel := context.WithCancel(context.Background())ctx = context.WithValue(ctx, "StartTime", collectStartMills)for i := 0; i < 5; i++ {go func() {sayHello(ctx, i)}()}go func() {time.Sleep(time.Second * 2)cancel()fmt.Printf("Context cancel\n")}()wg.Wait()fmt.Println("is finished...\n", time.Since(collectStart))
}
func sayHello(ctx context.Context, i int) {defer func() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r)}}()go func() {select {case <-ctx.Done():fmt.Println("ctx cancel:", ctx.Err())wg.Done()return}}()if i == 4 {time.Sleep(time.Second * 10)}startime := ctx.Value("StartTime").(int64)fmt.Printf("hello, world %d,time:%v \n", i, startime)return
}

7.reflect

        TypeOf(nil) 返回的是 reflect.Type 类型,可以通过该类型获取到具体的类型信息,
IsNil()报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。
IsValid()返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。
IsNil()常被用于判断指针是否为空;IsValid()常被用于判定返回值是否有效。

func ReflectGetValue() {a := 100atp := reflect.TypeOf(a)avu := reflect.ValueOf(a)fmt.Println(atp, avu)fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())// nil值fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())// 实例化一个匿名结构体b := struct{}{}// 尝试从结构体中查找"abc"字段fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())// 尝试从结构体中查找"abc"方法fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())// mapc := map[string]int{}// 尝试从map中查找一个不存在的键fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("mm")).IsValid())
}

8.reflect.TypeOf和reflect.ValueOf

        在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。
在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,并且reflect包提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的Value和Type。

func ReflectSetValue() {a := 100if reflect.TypeOf(a).Kind() == reflect.Int {/**ca := reflect.ValueOf(a)ca.SetInt(1000)fmt.Println(ca)  //修改的是副本,reflect包会引发panic*/reflect.ValueOf(&a).Elem().SetInt(1000)}fmt.Println(a)
}type Student struct {Name    string `json:"name"`Age     int    `json:"age"`Sex     string `json:"sex"`Address string `json:"address"`
}

9.eflect.TypeOf()

        任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,
可以通过反射值对象(reflect.Type)的NumField()和Field()方法获得结构体成员的详细信息。

/*Field(i int) StructField    根据索引,返回索引对应的结构体字段的信息。
NumField() int  返回结构体成员字段数量。
FieldByName(name string) (StructField, bool)    根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField   多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool) 根据传入的匹配函数匹配需要的字段。
NumMethod() int 返回该类型的方法集中方法的数目
Method(int) Method  返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool)  根据方法名返回该类型方法集中的方法
type StructField struct {// Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。// 参见http://golang.org/ref/spec#Uniqueness_of_identifiersName    stringPkgPath stringType      Type      // 字段的类型Tag       StructTag // 字段的标签Offset    uintptr   // 字段在结构体中的字节偏移量Index     []int     // 用于Type.FieldByIndex时的索引切片Anonymous bool      // 是否匿名字段
}*/func ReflectStruct() {st1 := Student{Name:    "zhangsan",Age:     20,Sex:     "man",Address: "beijing",}st2 := &Student{Name:    "lisi",Age:     21,Sex:     "man",Address: "wuhan",}/*st1 是一个 Student 类型的值,存储的是结构体的副本。st2 是一个指向 Student 类型的指针,存储的是结构体的内存地址。在实际使用中,如果需要修改原始结构体的内容或者关心性能(例如,避免复制大型结构体),通常会选择使用指针。如果结构体较小且不需要修改原始内容,可以直接使用值类型。*/rt1 := reflect.TypeOf(st1)fmt.Printf("name:%v,kind:%v \n", rt1.Name(), rt1.Kind())rt2 := reflect.TypeOf(st2)fmt.Printf("name:%v,kind:%v \n", rt2.Name(), rt2.Kind())for i := 0; i < rt1.NumField(); i++ {field := rt1.Field(i)fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))}/*for j := 0; j < rt2.NumField(); j++ {field := rt2.Field(j)fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))}报错panic: reflect: NumField of non-struct type *basic.Student这个错误信息表明在使用反射(reflect)包时,尝试获取一个非结构体类型的字段数量。具体来说,reflect: NumField of non-struct type *basic.Student 表示你试图对一个 *basic.Student 类型的值调用 NumField 方法,但 *basic.Student 不是一个结构体类型。在 Go 语言中,reflect 包提供了对结构体字段的反射操作。要正确使用 reflect 包,你需要确保你操作的对象确实是一个结构体类型*/
}

10.反射的不足

        反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
大量使用反射的代码通常难以理解。
反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

func ReflectGetMethod() {GetMethod(Student{})/*reflect.DeepEqualreflect.DeepEqual 是 Go 语言中 reflect 包提供的一个函数,用于比较两个值是否深度相等。它会递归地比较两个值的所有字段和元素,包括结构体、数组、切片、映射等复杂类型。如果两个值的类型和内容都相同,则返回 true,否则返回 false。需要注意的是:reflect.DeepEqual 在比较某些类型的值时可能会产生意外的结果。例如,它将 nil 切片和空切片视为不相等,将 nil 映射和空映射视为不相等。此外,它还会比较函数指针的值,这可能导致不正确的结果。因此,在使用 reflect.DeepEqual 时,请确保了解其行为,并在必要时进行适当的自定义比较。如果你只需要比较基本类型(如整数、浮点数、字符串等)的值,可以使用 == 运算符,而无需使用 reflect.DeepEqual。对于复杂类型,可以考虑使用第三方库,如 github.com/google/go-cmp,它提供了更强大且灵活的比较功能。*/p1 := Person{Name: "Alice", Age: 30}p2 := Person{Name: "Alice", Age: 30}p3 := Person{Name: "Bob", Age: 25}fmt.Println(reflect.DeepEqual(p1, p2)) // 输出:truefmt.Println(reflect.DeepEqual(p1, p3)) // 输出:false
}type Person struct {Name stringAge  int
}func GetMethod(i interface{}) {t := reflect.TypeOf(i)v := reflect.ValueOf(i)for j := 0; j < v.NumMethod(); j++ {methodName := t.Method(j).NamemethodType := v.Method(j).Type()fmt.Printf("method name:%s,method type:%v \n", methodName, methodType)// 通过反射调用方法传递的参数必须是 []reflect.Value 类型var args = []reflect.Value{}v.Method(j).Call(args)}
}// 给student添加两个方法 Study和Sleep(注意首字母大写)
func (s Student) Study() string {msg := "好好学习,天天向上。"fmt.Println(msg)return msg
}func (s Student) Sleep() string {msg := "好好睡觉,快快长大。"fmt.Println(msg)return msg
}

11.select

        golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。
在执行select语句的时候,运行时系统会自上而下地判断每个case中的发送或接收操作是否可以被立即执行【立即执行:意思是当前Goroutine不会因此操作而被阻塞,还需要依据通道的具体特性(缓存或非缓存)】。
每个case语句里必须是一个IO操作;
所有channel表达式都会被求值、所有被发送的表达式都会被求值;
如果任意某个case可以进行,它就执行(其他被忽略);
如果有多个case都可以运行,Select会随机公平地选出一个执行(其他不会执行);
如果有default子句,case不满足条件时执行该语句;
如果没有default字句,select将阻塞,直到某个case可以运行;Go不会重新对channel或值进行求值。
可处理一个或多个 channel 的发送/接收操作。
如果多个 case 同时满足,select 会随机选择一个执行。
对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出。

func ChannelSelect() {chan1 := make(chan int, 4)chan1 <- 11select {case a := <-chan1:fmt.Printf("a:%v \n", a)default:fmt.Println("222")}//有缓冲区与无缓冲区ch2 := make(chan int) //无缓冲通道,需要现有一个goroutine来接收数据,不然向里边发数据会panicvar aaa intgo func() {aaa = <-ch2}()ch2 <- 1fmt.Println(aaa)ch3 := make(chan int, 2) //有缓冲通道ch3 <- 1ab := <-ch3fmt.Println(ab)
}
func Channel() {var wg sync.WaitGroupch := make(chan int)wg.Add(10) // 要与循环里边的wg.Add(1)区别for i := 0; i < 10; i++ {go func() {time.Sleep(time.Second * 2)ch <- i//wg.Add(1)fmt.Printf("ch <- %v \n", i)}()}go func() {for i := 0; i < 10; i++ {v := <-chwg.Done()fmt.Printf("%v <- ch  \n", v)}}()close(ch)wg.Wait()fmt.Println("main end")
}

12.断言

        在运行时检查接口类型的机制,通常用于确定接口的具体类型,并将其转换为该类型以便进行操作;
有两种方式:t := 变量.(T)  如果断言失败,会直接panic; t,ok := 变量.(T)

func CaseType() {stu := &Student{Name: "zhangsan",Age:  18,}detectType(stu)}func detectType(req interface{}) {student, ok := req.(*Student) //断言if ok {fmt.Printf("student.Name:%v \n", student.Name)}
}

http://www.lryc.cn/news/617563.html

相关文章:

  • Go选手如何快速上手第三方库
  • Springboot-vue 地图展现
  • JDK21虚拟线程和 Golang1.24协程的比较
  • 《姜妮与Veda的最后一次传输》
  • 李宏毅2025《机器学习》-第十讲:AI“思想钢印”:深入解析大模型的知识编辑技术
  • 秒懂边缘云|1分钟了解边缘安全加速 ESA
  • 机器学习——K-means聚类
  • 第9节 大模型分布式推理核心挑战与解决方案
  • 数据备份与进程管理
  • 机器学习:基于OpenCV和Python的智能图像处理 实战
  • 芯片设计流程
  • C# 异步编程(计时器)
  • 【C++】封装哈希表模拟实现unordered_set和unordered_map
  • Android 16 的用户和用户组定义
  • 基于倾斜摄影三维模型影像提取水面
  • Spring源码解析 - SpringApplication run流程-prepareContext源码分析
  • 了解不同电磁仿真类型中的电容报告
  • 某地渣库边坡自动化监测服务项目
  • GDB调试 core dump 文件与栈溢出分析
  • 农业气象站的应用场景拓展
  • 学习观察和行动:机器人操作中任务-觉察的视图规划
  • 2025年渗透测试面试题总结-13(题目+回答)
  • Python训练营打卡 DAY 33 MLP神经网络的训练
  • 首涂模板第45套主题2.0修正版苹果CMS模板奇艺主题二开源码
  • 【AxureMost落葵网】CRM客户关系管理原型系统-免费
  • MD5:理解MD5 / MD5核心特性 / MD5 在前端开发中的常见用途 / 在线生成MD5 / js-md5
  • 【Kafka系列】第三篇| 在哪些场景下会选择使用 Kafka?
  • 【车联网kafka】Kafka核心架构与实战经验(第三篇)
  • 钓鱼鱼饵制作的方式
  • Go 多进程编程-同步