1.匿名函数 go语言中的函数都是声明在函数之外的,并不存在函数内声明函数的问题 但是也会存在一些特殊情况,在这写情况中允许在函数内部去再次定义一个函数。 这种情况下,在函数内部定义的函数就必须遵守一些 go 语言定义的特殊规则。 而这些内部的函数,被统称为:匿名函数。 func main (){ func (..){..} } (1)对于go语言中的匿名函数而言,由于其不存在函数名,无法使用传统函数的调用功能 所以如何调用匿名函数就必须从两个角度出发来解决问题。 1)利用函数指针,完成匿名函数的"重命名",然后再次调用 func main(){ fpointer := func (num int)int{ return num+10; } fmt.Println(fpointer(100)); } 利用函数指针的情况时需要注意,由于函数指针是在函数内部被定义的 所以函数指针其实是一个局部变量,因此函数指针是只能够在当前所在的函数内使用的 一旦超出当前所在函数范围,即宣告函数指针失效。 2)利用AIIFE,即Anonymous-Immediately-Invoked-Function-Expression 匿名立即自动执行的函数表达式,来完成匿名函数的调用 func main(){ func (num int){ fmt.Println(num+100); }(35); } 这种方式虽然能够在“真正匿名”的情况下完成函数的调用,但是缺陷也相当明显。 那就是AIIFE只能在它声明的时候被立即调用一次,无法延迟调用,也无法多次重复调用。 (2)闭包 闭包是go语言中匿名函数的另外一种表现形式。 从概念上来说,闭包是一种: 使得函数外部可以间接访问函数内部定义的局部变量的手段,在go语言中经常表现为匿名函数的样子。 func createClousre() func()int{ var num int = 100; //在这里闭包就是 返回的这个匿名函数 return func ()int{ return num; } } 闭包在go语言中主要有两个作用: 1)能够使得在函数外部访问到函数内部的局部变量,打破了“局部视障” func main(){ clo := createClousre(); fmt.Println(clo());//输出局部变量num的值 100 } 原本在createClousre函数中定义的局部变量num,在main函数中是不可能直接访问到的 但是我们借助于闭包clo,完成了一次打破“局部视障”的操作 2)能够将局部变量的生命周期延长,具体延长时间视闭包存在的函数而定 func main(){ clo := createClousre();//createClousre函数在此执行结束 fmt.Println(clo()); } 原本createClousre函数在调用完成后就会将其占有的内存销毁, 即局部变量num也会跟随消失不见,再也无法访问。 但是由于闭包中持有了局部变量num,于是闭包就将局部变量num的声明周期延长到了clo上 所以变量clo只要还继续存在,那么局部变量num就得以继续被访问。 2.递归函数 go语言中的递归函数与传统 c语言 语法类似,只不过考虑到在go语言中很多运算符是不能直接与调用语句结合。 故而写法上可能存在一点点差异。 (1)递归函数: 递归函数是指在函数的内部,再次调用本身的函数。 eg: func f(){ ... f(); } 乍一看这样的写法似乎是一个错误的写法,会导致回调地狱的产生。 实际上递归只是结构上需要满足这样,而事实中递归函数的构成还需要三个要素。 (2)递归三要素: 递归变量赋初值、递归结束条件、递归变量向着递归结束的趋势发生变化 1)递归变量赋初值 指的是递归函数总是要有一个“标识”来提供作为判断递归结束的标准 并且这个“标识”是需要有一个初始值的。 eg: func f(num int){ ... f(num); } 此时形参num就可以充当递归函数的递归变量。 2)递归结束条件 指的是递归函数总是要有一个通过标识来进行的判断。 这个判断用于决定递归何时结束, 毕竟大家都不希望回调地狱的发生 eg: func f(num int){ if num <= 1{ return num; } f(num); } 此时针对递归变量的条件判断if就成为了递归结束条件, 只要一旦递归变量满足结束条件,那么递归函数就会立即结束。避免了回调地狱的发生。 3)递归变量向着递归结束的趋势发生变化 这一点非常关键,因为递归结束条件之所以能够触发, 是因为递归变量必须一直在发生变化,而变化就会有趋势 在递归函数中由于函数最终必须要执行结束,因此递归变量就必须向着递归结束的趋势发生变化 eg: func f(num int){ if num <= 1{ return num; } num --; f(num); } 这样一来,每次递归调用f()函数的时候,num都是在上一次调用的基础上减少了1 那么总有一个时刻num的值会满足递归结束条件。 (3)递归案例-阶乘问题 func getJieChengSum(num int) int{ if num<=1{ return num; } tempNum := num; num--; return tempNum*getJieChengSum(num); } func main() { sum := getJieChengSum(10); fmt.Println(sum); } 3.工程管理 工程管理指的是goland在编译过程中,一个模块化思想的体现。 主要变现为: 1)在一个文件中,可以通过“导入包”的操作后,访问其他文件中的函数。 2)整个工程分为三类文件夹:src(代码源文件)、pkg(编译生成文件)、bin(系统资源文件) (1)“包” 包,即package。是go语言中为文件分类,而后在编译文件的过程中对文件合并时的一个名词。 包中存放着不同模块,每个模块有着自己独立的功能。 eg: package user -userLogin.go -userRegist.go .. 此时如果加载了user package这个包,就相当于引入了包中所有模块的功能。 而后就可以通过user包名,来访问包中模块所提供的功能。 eg: user.Login();//假定Login是userLogin.go文件中声明的方法 user.Regist()//假定Regist是userRegist.go文件中声明的方法 (2)包与文件夹 包并不是文件夹,但是通常包名和包文件所在的文件夹设置为相同名字,以便于理解和查看。 一般上来讲包可以认为是编译完成后在pkg文件夹下的.a文件 (3)导包 导入包的目的其实就相当于JS中的link文件或者script引入文件。其目的都只有一个 那就是在导包后,能够使用包中所提供的不同功能,将工程模块化与组件化。 eg: import "包路径" 包名.包中提供的接口方法 eg: src -test//包名 -testFile.go//文件名 package test func TTest(){...}//方法名 main.go package main import "test"//导入时候,使用的是包名 func main(){ test.TTest();//调用的时候,也是使用的包名 } 案例中能够看到在导包结束后,调用包中提供的方法时,使用的是包名而不是文件名!! (4)注意事项(重点) 由于初学go语言,的确在导入自定义包的这个问题上碰了个大坑。 我使用的是goLang IDE。在导入系统包的时候并不会出现什么问题,主要集中在导入自定义包的时候! 问题: import 无法检索到自定义包,但是手打却会出现包中方法的系统提示。(虽然运行不了) 缘由: 1)核心到爆炸的问题: goLang这个坑货IDE在加载包的时候不会主动查找当前路径下的文件,即系统环境变量不能自动配置! 必须采用手动配置的方式,将$GOPATH配置完毕才可以! eg: file -> preferences for New Projects And Settings -> GO -> GOPATH -> + -> 工程目录 注意一点,不要添加src路径进去,而是直接添加目录路径即可! eg: /Users/xxx/Desktop/FuncAndArray 其中FuncAndArray就是我的工程文件夹名称,也就是说直接将工程路径丢在这就可以了!!!!! 2)次要问题 Go语言要求自定义的包中,所提供的模块方法首字母必须大写,否则检索不到! eg: -test -testFile.go package test func tTest(){...}//方法名小写了,导入包后也无法检测到 4.数组 go语言中的数组结构与传统c语言中的结构类似,但是和JS等弱类型语言却截然不同。 eg: var 数组名 [数组长度]数据类型 = [数组长度]数据类型{初始化的数据内容} eg: var arr [10]int = [10]int{10,11,12,13,14,15,16,17,18,19}; 对于go语言而言,数组并不是一个可以存放任意数据类型的复杂数据结构。 数组能存放的数据的类型在数组被定义的时候就已经被约定了, 存储约定数据意外的其他数据类型,会导致go语言给出错误提示。 (1)不同的数组初始化方式 var arr [长度]数据类型 = [长度]数据类型 {初始化数据内容}; arr := [长度]数据类型 {初始化数据内容} arr := [...]数据类型 {初始化数据内容} arr := []数据类型 {初始化数据内容} (2)数组不能够通过赋值的方式来进行“硬扩充” 对于弱类型语言中的数组来说,数组的长度是一个动态可变的值。比如在JavaScript中 var arr = [1,2,3];//数组长度最初只有3 arr[100] = 10;//此时数组的长度被扩展到了101 但是对于go语言来说这种方式则一定会报错 arr := [3]int{1,2,3}; arr[100] = 10;//报错,数组下标访问越界! (3)冒泡排序(我居然在这栽了个跟斗,简直瞎眼,事实证明老 程序员 也会在最基础的地方摔) arr := [10]int{9,8,7,6,5,4,3,2,1,0}; for i:=0; i<len(arr)-1; i++{ for j:=0; j<len(arr)-1-i; j++{ if(arr[j]>arr[j+1]){ temp := arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } fmt.Println(arr); (4)特别注意的是,在go语言中数组作为参数在函数中传入的时候,是值传递!!! func allAdd(arr [4]int)(resultArr [4]int){ for i,_ := range arr{ arr[i]++; } return arr; } func main(){ arr := [4]int{1,2,3,4}; newArr := allAdd(arr); fmt.Println(arr);//[1,2,3,4] 传入函数后,原数组不会受到任何影响!! fmt.Println(newArr);//[2,3,4,5] } 5.随机数 在计算机的编程语言中,随机数的概念其实是不存在的。真正的随机数的名称应当是:概率。 但是我们可以通过计算机时钟来模拟出随机数的样子,也就是伪随机数。 而go语言对于伪随机数的构建不像很多弱类型语言一样封装到脖子,让开发者直接使用 而是需要开发者手动对随机数种子进行时钟混淆,来保证每次获取的样本都不相同。 eg: rand.Seed(time.Now().UnixNano()); rand.Intn(10); 在go语言中想要使用随机数需要使用到两个系统库,【math/rand】和【time】. 其中time库提供时间函数,来作为随机数种子 而math/rand库则提供随机数函数,从随机数种子混淆后的范围中获取样本。 eg: rand.Seed(time.Now().UnixNano()); for i:=0;i<100;i++{ ranNum := rand.Intn(100); fmt.Println(ranNum); } 注意:rand.Intn()方法获取的随机数是从下界到上界,但是不包括上界的随机数。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。