• go中的方法 func-----数据类型


    本文是java学习者学go种产生的容易记混点的笔记,所以有其他编译语言的基础更好

    go的方法有点像js

    基础

    func main() {
    	fmt.Println("Starting")
    	var p *string = new(string)
    	*p = "hello world"
    	demo := "demo"
    	fmt.Println(*&demo) //这样既然也可以  &得到地址 也就是指针 在通过取值操作 得到数据
    	fmt.Println(*p)
    	for i := 0; i < 10; i++ {
    		fmt.Println(i)
    		if i == 5 {
    			return //结束程序
    		}
    	}
    	fmt.Println("Ending")
    
    }
    
    
    多参数 多返回值形式
    
    func add(a, b int) int {
        return a + b
    }
    func add(a, b int) int,int {
        return a + b,b
    }
    
    可变参数

    形参部分是slice 动态数组

    func add( b ...int) int {
        return 1
    }
    

    之所以可以不用写;就是因为底层go做了优化 所以定义方法时候 {必须跟在方法名字之后

    细节-方法的传值

    方法的参数传递分为值传递和引用传递(go种的引用传递是通过地址 /指针等方式)
    值传递:实际形式参数接收的是数据的拷贝
    引用传递:传递的是数据的引用,在函数内修改这个参数,原来的数据也会改变
    为了简单记得,只要记得下下面的类型都是引用传递,其他都是值传递既可

    • 切片
    • 指针/地址
    • 映射
    • 通道
    • 接口

    基本就是复合类型加上指针接口 (函数虽然也是存储地址(函数体)的数据类型,但是作为参数也是把这个函数体地址作为值传递拷贝)所以想到达引用传递的效果就需要传递函数的地址(觉得头晕可见含后文)

    在java种类传递的对象也是引用传递 但是在go中并不是,go种定义类是结构体的形式,然后创建实列对象有多种方法,只有使用new关键字会返回指针这个时候实列对象名字才是引用,其他方式实列的结构体的是数据本身

    /*
    *
    只有下面的类型是引用传递 传递数据 修改数据会影响到原来的数据 其他的只可以通过传递地址指针实现方法修改元数据
    理解引用传递
    */
    package main
    
    import "fmt"
    
    // 1. 指针(Pointers)
    func modifyValue(x *int) {
    	*x = 42
    }
    
    // 2. 切片(Slices)
    func modifySlice(s []int) {
    	s[0] = 42
    }
    
    // 3. 映射(Maps)
    func modifyMap(m map[string]int) {
    	m["key"] = 42
    }
    
    // 4. 通道(Channels)
    func send(ch chan int) {
    	ch <- 42
    }
    
    // 5. 函数(Functions)
    func callFunction(f func(int) int, x int) int {
    	return f(x)
    }
    
    func square(n int) int {
    	return n * n
    }
    
    // 6. 接口(Interfaces)
    type Modifier interface {
    	Modify()
    }
    
    type Data struct {
    	Value int
    }
    
    func (d *Data) Modify() {
    	d.Value = 42
    }
    
    func modifyInterface(m Modifier) {
    	m.Modify()
    }
    
    func main() {
    	// 测试指针
    	a := 10
    	fmt.Println("之前 (Pointer):", a)
    	modifyValue(&a)
    	fmt.Println("修改后 (Pointer):", a)
    
    	// 测试切片
    	b := []int{1, 2, 3}
    	fmt.Println("之前 (Slice):", b)
    	modifySlice(b)
    	fmt.Println("修改后 (Slice):", b)
    
    	// 测试映射
    	c := map[string]int{"key": 1}
    	fmt.Println("之前 (Map):", c)
    	modifyMap(c)
    	fmt.Println("修改后 (Map):", c)
    
    	// 测试通道
    	ch := make(chan int)
    	go send(ch)
    	fmt.Println("Received (Channel):", <-ch)
    
    
    
    	// 测试接口
    	d := &Data{Value: 10}
    	fmt.Println("之前 (Interface):", d.Value)
    	modifyInterface(d)
    	fmt.Println("修改后 (Interface):", d.Value)
    }
    
    

    函数式编程

    首先在go种,函数也可以作为参数以及返回值存在 为此就要介绍其他语言也有的匿名函数

    匿名函数

    func main() {
    	a := 2
    	//匿名函数的使用
    	func(a *int) {
    		container := []int{1, 2, 3, 4, 5}
    		for _, value := range container {
    			if value%2 == 0 {
    				fmt.Println(value)
    			}
    		}
    	}(&a) //即可调用
    	b := func() int {
    		return 1
    	}()
    	fmt.Println(b)
    	f := func() int {
    		fmt.Println("hello world")
    		return 1
    	}
    	f()
    	}
    	
    

    当然匿名参数存在的作用就是可以作为回调函数传参

    值得注意的函数类型作为传递时候 申明格式就和go正常申明一样,但是申明go作为参数传递时候,这个时候的参数和返回值只需要申明类型即可

    
    func main(){
    r := opation(2, 3, func(a int, b int) int {
    		return a*100 + b
    	})
    }
    /*
    *
    主要用途是函数式编程 作用函数的参数和返回值类型可以省略,可以直接使用匿名函数
    ·回调函数 作为参数只需要申明返回值和参数类型即可
    */
    func opation(a, b int, callback func(int, int) int) int {
    	return callback(a, b)
    }
    

    闭包函数

    首先解释闭包:

    闭包(Closure)函数是一种函数,它可以访问其词法作用域(定义时的作用域)之外的变量。换句话说,闭包可以在其函数体内引用定义在函数外部的变量。这些变量称为自由变量。

    /*
    bitwiseIncrementer 是一个闭包函数,它返回一个函数,该函数每次调用时都会返回一个自增后的整数。
    因为返回的函数引用了一个方法的变量 所以这个变量不会s被垃圾回收 所以这个函数可以一直使用
    */
    func increment() func() int {
    	a := 0
    	f := func() int {
    		a++
    		return a
    	}
    	return f
    }
    

    main.go

    	ff := increment()
    	fmt.Println(ff)
    	fmt.Println(&ff)
    	v1 := ff()
    	fmt.Println(v1)
    	v2 := ff()
    	fmt.Println(v2)
    	又调用了一个新的闭包函数 创建了新的内存空间 所以这个i也是新生成额变量
    	ff2 := increment()
    	fmt.Println(ff2())
    

    猜猜会输出什么
    在这里插入图片描述
    流程是 :第一个函数返回了一个函数,而这个函数是闭包函数,正常来说函数使用后就被gc垃圾回收,但是由于返回的这个函数引用了这个变量,所以不会被gc,所以多次调用,自增还是改变的这个数据,而有再次调用这个代码得到一个自增函数时候,这个代码块的内容又是重新建立在新内存空间的,其中引用的a 又是新开辟的内存 所以又是1

    那么输出这俩个自增函数

    fmt.Println(ff)
    	fmt.Println(ff2)
    

    发现这俩个变量 不一样
    在这里插入图片描述

    函数类型深入

    细心的可以发现,感觉函数和指针一样,装的数据是地址,指针装的是变量地址,而函数装的就是代码快的地址
    由此可以再次测试

    	// 定义函数变量
    	funcdemo1 := func() {
    		fmt.Println("funcdemo1")
    	}
    	funcdemo2 := func() {
    		fmt.Println("funcdemo2")
    	}
    
    	// 打印函数的指针地址
    	fmt.Printf("函数代码块的地址 %p\n", funcdemo1)
    	fmt.Printf("函数的地址 %p\n", &funcdemo1)
    
    

    输出
    在这里插入图片描述
    可以证实,函数的值就是代码块的地址,而也可以对这个函数取地址
    感觉和指针一样,指针也是装载地址的数据类型,也可以对指针取地址

    	funcdemo1 := func() {
    		fmt.Println("funcdemo1")
    	}
    	funcdemo2 := func() {
    		fmt.Println("funcdemo2")
    	}
    	a := 12
    	P := &a
    		fmt.Println("对地址取指针的取值操作", *&a)
    	fmt.Println("指针的数值", P)
    	fmt.Println("指针的地址", &P)
    
    	// 打印函数的指针地址
    	fmt.Printf("函数代码块的地址 %p\n", funcdemo1)
    	fmt.Printf("函数的地址 %p\n", &funcdemo1)
    

    在这里插入图片描述
    既然知道了这个底层,那么就可以实现俩个函数交换函数体

    func main() {
    	// 定义函数变量
    	funcdemo1 := func() {
    		fmt.Println("funcdemo1")
    	}
    	funcdemo2 := func() {
    		fmt.Println("funcdemo2")
    	}
    
    	swapfunc(&funcdemo1, &funcdemo2)
    
    	// 现在 funcdemo1 应该调用原来 funcdemo2 的内容
    	funcdemo1() // 现在会调用 funcdemo2
    }
    func swapfunc(a, b *func()) {
    	//调用传递的参数
    
    	(*a)() //先对指针取值得到函数体然后执行
    	fmt.Printf("之前的地址: a: %p, b: %p\n", *a, *b)
    	*a, *b = *b, *a // 交换引用
    	fmt.Printf("之后的地址: a: %p, b: %p\n", *a, *b)
    }
    
    

    确实是交换了函数体,这点和二重指针一样的,通过指针地址改变指针中装的地址,这里通过函数地址改变函数中装的函数体地址
    在这里插入图片描述
    而这样就不行

    func swapfunc(a, b func()) {
    	//调用传递的参数
    
    	(a)()
    	fmt.Printf("之前代码的地址: a: %p, b: %p\n", a, b)
    	a, b = b, a // 交换引用
    	fmt.Printf("之后的地址: a: %p, b: %p\n", a, b)
    }
    
    

    传递函数作为参数是,是值拷贝,传递的只是把形参作为副本,形参值是函数体的值,但是形参的地址不是传递的哪个函数地址,所以修改存储的函数体原来的函数不会改变,改变的只是形参
    同理如果想要改变指针保存的地址,那么也是需要参数传递指针的地址

    细节,如果是在最外部申明的函数,无法取地址
    这样就会报错

    func main() {
    	//函数本身就是引用类型 无需地址符号即可提取地址
    	fmt.Printf("函数的地址%T\n", funcdemo1)
    	fmt.Printf("函数的地址%p\n", &funcdemo1)
    	swapfunc(funcdemo1, funcdemo2)
    	funcdemo1()
    
    }
    func funcdemo1() {
    	fmt.Println("funcdemo1")
    }
    func funcdemo2() {
    	fmt.Println("funcdemo2")
    }
    
  • 相关阅读:
    部署Kubernetes 集群
    SpringBoot第十三篇:同时集成华为RC6.5.1安全版kafka和原生kafka,通过配置文件动态控制
    vue3渲染函数(h函数)的变化
    还在为产品的客户服务而烦恼?来搭建在线客服中心!
    Doris相关记录
    索引与事务
    B+tree数据结构区分主键索引、非聚集索引、覆盖索引
    火星探测器背后的人工智能:从原理到实战的强化学习
    自学Python 53 多线程开发(三)使用Condition 对象
    【.NET8】访问私有成员新姿势UnsafeAccessor(下)
  • 原文地址:https://blog.csdn.net/qq_55272229/article/details/139998858