Go语言笔记(三)

函数

为什么要用函数

  • 提高代码的复用性,减少代码冗余,提高代码维护性

函数的定义

  • 为完成某一功能的程序指令(语句)的集合,称为函数
  • 对特定的功能进行提取,形成一个代码片段。

函数的说明

  • 函数和函数是并列的关系,所以定义的函数不能写到main函数中

基本语法

func 函数名(形参列表) (返回值类型列表){
    执行语句
    return + 返回值列表
}

函数名

  • 遵循标识符命名规范:见名知意,驼峰命名
  • 首字母不能是数字
  • 首字母大写该函数可以被本包文件和其他包文件使用(类型public)
  • 首字母小写只能被本包文件使用,其他包文件不能使用(类似private)

形参列表

  • 形参列表:个数:可以是一个参数,可以是n个参数,可以是0个参数
  • 形式参列表:作用:接受外来的数据
  • 实际参数:实际传入的数据

返回值类型列表

  • 函数的返回值对应的类型应该返回在返回值列表中
  • 返回0个:返回值不写
  • 返回1个:返回值中的()可以省略不写
  • 返回多个:返回值中的()不可省略
  • 如果有返回值不想接收。可使用_可忽略

注意事项

  • 函数名相同,形参列表不同叫重载,但Golang中不支持重载
  • Golang中支持可变参数(如果你希望函数带有可变数量的参数)
//定义一个函数,函数的参数为:可变参数 ... 参数的数量可变
// args...int 可以传入任意多个数量的int类型的数据,传入0个,1个。。。。n个
func test(args...int){
    //函数内部处理可变参数的时候。将可变参数当做切片来出来
    for i:=0;i <len(args);i++ {
        fmt.Println(args[i])
    }
}
  • 基本数据类型和数组默认都是值传递的。即进行值拷贝,在函数内修改不会影响原来的值
  • 以值传递方式的数据类型,如果希望在函数内的数量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量,以效果来看类似引用传递
//参数的类型为指针
func test(num *int){
    //对地址对应的变量进行改变数值
    *num = 30
}
func main(){
    var num int = 10
    fmt.Println(&num)
    test(&num)
    fmt.Println(num)
}
  • 函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
func test(num int) {
	fmt.Println(num)
}

func main() {
	// 函数也是一种数据类型,可以赋值给一个变量
	a := test	//变量就是一个函数类型的变量
	fmt.Printf("a的类型是:%T,test函数的类型是:%T\n",a, test)
	// 通过变量可以对函数调用
	a(10) // 等价于 test(10)
}
  • 函数既然是一种数据类型,因此在Go中也可以作为形参,并且调用(把函数本身当做一种数据类型)
func test02(num1 int,num2 float32,testFunc func(int)) {
	fmt.Println("------test02")

}
func main() {
	// 函数也是一种数据类型,可以赋值给一个变量
	a := test	//变量就是一个函数类型的变量
	fmt.Printf("a的类型是:%T,test函数的类型是:%T\n",a, test)
	// 通过变量可以对函数调用
	a(10) // 等价于 test(10)

	//调用test02函数
	test02(10,3.19, test)
	test02(10,3.19, a)

}
  • 为了简化数据类型定义,GO支持自定义数据类型
基本语法:type 自定义数据类型 数据类型
可以理解为起了一个别名
type myFunc func(int)
func test03(num1 int,num2 float32,testFunc myFunc){
	fmt.Println("------test02")
}
func main(){
	var num1 myInt = 30
	fmt.Println("num1为", num1)
	var num2 int = 30
	num2 = int(num1)  // 虽然是别名,但在go中编译识别时候还是认为myInt和int不是同一个数据类型
	fmt.Println("num1为", num2)

	test03(10,9.8, a)
}
  • GO语言里支持对返回值进行定义
    • 传统写法要求:返回值和返回值的类型对应,顺序不能差
// 定义一个函数:求两个数的和、差
func test04(num1 int, num2 int)(int,int) {
	result01 := num1 + num2
	result02 := num1 - num2
	return result01, result02
}
* 返回值定义的则无需关心顺序
// 定义一个函数:求两个数的和、差
func test05(num1 int, num2 int)(sum int,sub int) {
	sum = num1 + num2
	sub = num1 - num2
	return
}

调用案例

func cal(num1 int, num2 int) (int,int) {
	var sum int = 0
	sum += num1
	sum += num2
	var result int = num1 - num2
	return sum,result
}

func cal(num1 int, num2 int) int { //如果返回值类型就一个,那括号可省略不写。
	var sum int = 0
	sum += num1
	sum += num2
	return sum
}

func main() {
	sum, result := cal(20,10)
	fmt.Println(sum)
}

内存分析

栈、堆、代码区

栈:通常会放基本数据类型

  • 为每个函数开辟栈帧(开辟空间)
  • 在函数执行后,程序会销毁函数对应开辟的栈空间(栈帧)

堆:通常会放复杂数据类型

代码区:通常会放代码本身

包的引入

使用包的原因

  1. 我们不可能把所有函数放到同一个源文件中,可以分门别类的把函数放在不同的分类中
  2. 解决同名问题:两个人都想定义一个同名的函数,在同一个文件中是不可以定义相同名字的函数,但可以用包来区分。
package main  //1.package进行包的声明,建议:包的声明和这个包所在的文件夹同名
// 2.main包是程序的入口包,一般main函数会放在这个包下
//import "fmt"
// 3. 包名是从$GOPATH/src后开始计算的。使用/进行路径分隔。
//import "testproject01/dbutils"
// 5.如果有多个包。可以一次性导入
import (
	"fmt"
	"testproject01/dbutils"
)
func main() {
	fmt.Println("你好,这是main函数的这行")
	dbutils.GetConn()  //4.在函数调用的时候前面要定位到所在的包
}

包的细节

  1. package进行包的声明,建议:包的声明这个包和所在的文件夹同名
  2. main包是程序的入口包,一般main函数会放在这个包下。
    • main函数已经要在main包下,否则不能编译执行
  3. 打包语法:package 包名形式
  4. 引入包的语法:import 包的路径
    • 包名是从$GOPATH/src后开始计算的。使用/进行路径分隔。
  5. 如果有多个包建议一次性导入,格式如下
  import(
      "fmt"
      "testproject01/dbutils"
  )
  1. 在函数调用的时候前面要定位到所在的包
  2. 函数名、变量名首字母大写,才可被其他包访问
  3. 一个目录下不能有重复的函数,即使是不同文件下,但他们在同一个目录下。如果存在相同的函数名。则会报错。
  4. 包名和文件夹的名字,可以不一样。import引入的是包所在的文件夹的路径,函数前的定位用的是包名
  5. 一个目录下的同级文件归属于一个包
    同级别的源文件的包的声明必须一致
  6. 包到底是什么
    1. 在程序层面,所有使用相同package 包名的源文件组成的代码模块
    2. 在源文件层面就是一个文件夹
  7. 可以给包取别名,取别名后,原来的包名就不能使用了

init函数

  • init函数:初始化函数,可以用来进行一些初始化的操作
    • 每一个源文件都可以包含一个init函数,该函数会在main函数前,被GO运行框架调用
  • 全局变量定义、init函数、main函数都存在时是怎样的执行过程
    • 全局变量定义-->init函数-->main函数
  • 多个源文件都有init函数时,执行过程
    • 先执行main函数中import的包,包中如果有init函数先执行
    • 以此类推,如果import的包中还有import的包则继续执行
    • 最后才是当前main函数下的init函数

匿名函数

  • Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数
  • 匿名函数使用方式:
    • 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)
    import "fmt"
    
    func main() {
    	//定义匿名函数
    	result := func (num1 int,num2 int)(int){
    		return num1 + num2
    	}(10, 20)
    	fmt.Println(result)
    }
    
    • 将匿名函数赋值给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数,可重复调(用的比较少)
    func main() {
    	//定义匿名函数
    	result := func (num1 int,num2 int)(int){
    		return num1 + num2
    	}(10, 20)
    	fmt.Println(result)
    
    	// 将匿名函数赋给变量,这个变量实际就是函数类型为int
    	sub := func (num1 int,num2 int)(int){
    		return num1 - num2
    	}
    
    	result01 :=sub(30, 70)
    	fmt.Println(result01)
    }
    
    • 如何让一个匿名函数,可以将匿名函数给一个全局变量就可以了。

闭包

什么是闭包

  • 闭包就是一个函数和其相关的引用环境组合的一个整体
  • 案例
// 函数功能:求和
// 函数的名字: getSum 参数为空
// getSum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getSum() func (int) int{
	var sum int = 0
	return func(num int) int {
		sum += num
		return sum
	}
}
//闭包:返回的匿名函数+匿名函数以外的变量num
func main() {
	f := getSum()
	fmt.Println(f(1))//1
	fmt.Println(f(2))//3
	fmt.Println(f(3))//6
	fmt.Println(f(4))//10
}

总结:匿名函数中引用的变量会一直保存在内存中,可以一直使用

闭包的本质

  • 闭包的本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数即:
    匿名函数+引用的变量/参数=闭包

闭包的特点:

  • 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。
  • 闭包中使用的变量/参数会一直保存在内存中,所以会一直使用--> 意味着闭包不可滥用(对内存消耗大)
  • 不使用闭包时候:想保留的值,不可以反复使用
  • 闭包应用场景:闭包可以保留上次引用的某个值,传一次即可反复使用

defer关键字

  • 在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时释放资源。Go的设计者提供defer关键字
  • 案例
func main() {
	fmt.Println(add(30,60))
}

func add(num1 int,num2 int) int {
	//在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,继续执行函数后面的语句。
	defer fmt.Println("num1=", num1)
	defer fmt.Println("num2=", num1)
	// 栈的特点是:先进后出
	// 在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
	var sum int = num1 + num2
	fmt.Println("sum=", sum)
	return sum
}
  • 发现defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝到栈中,不会随着函数后面的变化而变化
  • 案例
func main() {
	fmt.Println(add(30,60))
}

func add(num1 int,num2 int) int {
	//在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,继续执行函数后面的语句。
	defer fmt.Println("num1=", num1)
	defer fmt.Println("num2=", num2)
	num1 += 90 //num1:120
	num2 += 50 //num2:110
	// 栈的特点是:先进后出
	// 在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
	var sum int = num1 + num2
	fmt.Println("sum=", sum)
	return sum
}

defer应用场景

如:关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句,所以用完立即关闭,比较省心,省事

系统函数

字符串相关函数

  • 内置函数不用导包,直接用即可。
  • 案例
import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	//1.统计字符串长度
	str := "你好Golang" //在golang中,汉字是utf-8字符集,一个汉字3个字节
	fmt.Println(len(str))

	//2.对字符串遍历
	r:=[]rune(str) //转成切片
	for i := 0; i < len(r); i++ {
		fmt.Printf("%c\n",r[i])
	}

	//3.字符串转整数
	num1, _ := strconv.Atoi("666")
	fmt.Println(num1)

	//4. 整数转字符串
	str1 := strconv.Itoa(88)
	fmt.Println(str1)

	//5.查找子串是否在指定的字符串中
	strings.Contains("javaandgolang", "go")

	//6.统计一个字符串有几个指定的字符串
	count := strings.Count("golangandjava", "ga")
	fmt.Println(count)

	//7.不区分大小写的字符串比较
	flag := strings.EqualFold("hello", "HELLO")
	fmt.Println(flag)
	//区分大小写的字符串比较
	fmt.Println("hello"=="Hello")

	//8.返回子串在字符串中第一次出现的索引值,存在返回索引值,不存在返回-1
	index := strings.Index("golangandjavaga","ga")
	fmt.Println(index)

	//9.字符串的替换
	str2 := strings.Replace("goandjavagogo","go","golang",-1)
	str3 := strings.Replace("goandjavagogo","go","golang",2)
	fmt.Println(str2)
	fmt.Println(str3)
	//n 可以指定要替换的次数

	//10.按照指定字符进行切割,返回一个字符串数组
	arr := strings.Split("go-python-java","-")
	fmt.Println(arr)

	//11.将字符串字母进行大小写
	fmt.Println(strings.ToLower("Go"))
	fmt.Println(strings.ToUpper("Go"))

	//12.字符串左右两边空格去除
	fmt.Println(strings.TrimSpace("     go and java     "))

	//13.将字符串左右指定的字符去除
	fmt.Println(strings.Trim("~golang~","~"))

	//14.将字符串左边的字符去掉
	fmt.Println(strings.TrimLeft("~golang~","~"))

	//15.将字符串右边的字符去掉
	fmt.Println(strings.TrimRight("~golang~","~"))

	//16.判断字符串是否以指定的字符串开头
	fmt.Println(strings.HasPrefix("http://localhost", "http"))

	//16.判断字符串是否以指定的字符串结尾
	fmt.Println(strings.HasSuffix("test.png", "jpg"))

}

日期和时间相关函数

  • 时间相关函数可以使用time
  • 案例
import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	//Now()返回的值是一个结构体,类型是:time.Time
	fmt.Printf("%v ~~~~类型为%T\n",now,now)

	//调用结构体中的方法:
	fmt.Printf("年:%v\n",now.Year())
	fmt.Printf("月:%v\n",int(now.Month())) //月就变成数字,默认是
	fmt.Printf("日:%v\n",now.Day())
	fmt.Printf("时:%v\n",now.Hour())
	fmt.Printf("分:%v\n",now.Minute())
	fmt.Printf("秒:%v\n",now.Second())

	fmt.Println("--------------------1-----------------------------")
	//将字符串直接输出
	fmt.Printf("当前年月日:%d-%d-%d 时分秒:%d-%d-%\n",now.Year(),now.Month(),now.Day(),now.Hour(),
		now.Minute(),now.Second())
	//Sprintf可以得到这个字符串以便后续使用。
	dateStr := fmt.Sprintf("当前年月日:%d-%d-%d 时分秒:%d-%d-%d",now.Year(),now.Month(),now.Day(),now.Hour(),
		now.Minute(),now.Second())
	fmt.Println(dateStr)
	fmt.Println("----------------------2---------------------------")
	//这个参数字符串的各个数字必须是固定的,必须这样写
	dateStr2 := now.Format("2006/01.02 15/04/05")
	fmt.Println(dateStr2)
	//选择任意的组合都是可以的,根据需求自己选择
	dateStr3 := now.Format("2006 01.02")
	fmt.Println(dateStr3)
}

内置函数

什么是内置函数

  • Golang设计者为了编程方便,提供了一些函数,这些函数不用导包可以直接使用,我们称为Go的内置函数/内建函数

内置函数存放位置

  • builtin包下,直接用即可,不用引包

常用函数

  • len函数:统计字符串长度
  • new函数:用于分配内存,主要用来分配值类型(int系列、float系列、bool、string、数组和结构体struct)
func main() {
	//new分配内存,new函数的实参是一个类型而不是具体是数值,返回值是对应类型的指针num: *int
	num := new(int)
	fmt.Printf("num的类型是:%T,num的值是:%v,num地址是:%v,num指针指向的值是:%v", num,num,&num,*num)
}
  • make函数:用于分配内存,主要用来分配引用类型(指针、slice切片、map、管道chan、interface等)
# Go入门  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×