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()方法获取的随机数是从下界到上界,但是不包括上界的随机数。
有疑问加站长微信联系(非本文作者))