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 创建一个map
    m := 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]int
    fmt.Println("a1:", a1)
    a2 := [...]int{}
    fmt.Println("a2:", a2)
    // 声明一个整数切片
    slice := []int{1, 2, 3}
    fmt.Println("slice:", slice)
    // 修改数组的元素
    arr[0] = 100
    fmt.Println("修改后的 arr:", arr) // 输出:修改后的 arr: [100 0 0]
    // 修改切片的元素
    slice[0] = 100
    fmt.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)
    }
    /** 输出:
    4
    3
    2
    1
    0
    222
    */
}
func Defer2() {
    unres := unnamedReturn()
    fmt.Println("unres:", unres) // 输出:res: 10
    nres := namedReturn()
    fmt.Println("nres:", nres) // 输出:res: 15
}
 
func unnamedReturn() int {
    result := 10
    ret := &result
    defer func() {
       *ret += 5
       fmt.Printf("ret:%v \n", *ret) //15
    }()
 
    return result
}
 
func namedReturn() (result int) {
    defer func() {
       result += 5
    }()
 
    result = 10
    return
}

4.uint

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


func Uint() {
    var a uint8 // 0~255(0~2^8-1)
    a = 255
    var b uint8 = 1
    b = 1
    fmt.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.WaitGroup
 
func Goroutine() {
	wg.Add(5)
 
	collectStart := time.Now()
	collectStartMills := collectStart.UnixNano() / 1e6
	ctx, 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 := 100
    atp := 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())
    // map
    c := 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 := 100
    if 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_identifiers
    Name    string
    PkgPath string
    Type      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.DeepEqual
             reflect.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)) // 输出:true
    fmt.Println(reflect.DeepEqual(p1, p3)) // 输出:false
}
 
type Person struct {
    Name string
    Age  int
}
 
func GetMethod(i interface{}) {
    t := reflect.TypeOf(i)
    v := reflect.ValueOf(i)
 
    for j := 0; j < v.NumMethod(); j++ {
       methodName := t.Method(j).Name
       methodType := 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 <- 11
    select {
    case a := <-chan1:
       fmt.Printf("a:%v \n", a)
    default:
       fmt.Println("222")
    }
    //有缓冲区与无缓冲区
    ch2 := make(chan int) //无缓冲通道,需要现有一个goroutine来接收数据,不然向里边发数据会panic
    var aaa int
    go func() {
       aaa = <-ch2
    }()
    ch2 <- 1
    fmt.Println(aaa)
 
    ch3 := make(chan int, 2) //有缓冲通道
    ch3 <- 1
    ab := <-ch3
    fmt.Println(ab)
}
func Channel() {
    var wg sync.WaitGroup
    ch := 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 := <-ch
          wg.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)
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值