数组
数组是具有相同类型的一组长度固定的数据项序列。
- 数组是长度固定的数据类型
- 数据元素的类型相同
定义方式
//数组的定义方式1
var arrayVariables [10]int
arrayVariables[0] = 100
arrayVariables[3] = 200
//arrayVariables[10] = 100
fmt.Println(arrayVariables)
//数组的定义方式2
var arrayVariables2 [5]int = [5]int{1,2,3,4,5}
//在这种情况左边类型可简写
var arrayVariables3 = [5]int{1,2,3,4,5}
数组的遍历
//遍历数组1
var arrayVariables2 [5]int = [5]int{1,2,3,4,5}
var length = len(arrayVariables2)
for i :=0;i<length;i++ {
fmt.Printf("arrayVariables2[%d]=%d,地址=%p\n",i,arrayVariables2[i],&arrayVariables2[i])
}
//遍历数组2
for key,value := range arrayVariables6 {
fmt.Printf("arrayVariables6[%d]=%v,地址=%p\n",key,value,&arrayVariables6[key])
}
切片
介绍
- 一种数据结构,便于使用与管理我们的数据集合。
- 按需自动增长,动态数组(通过append来完成)
- 底层指向的是数组
- 内存当中是连续的存储空间,可以有效的提升cpu的执行效率。
- 引用类型
组成方式 - 指向底层数组的指针
- 切片元素的长度,通过len()获取
- 容量,通过cap()获取
//切片的定义
var sliceVariables []int
//定义一个数组
arrayVariables := [...]int{12,21,23,55,98,2}
for i :=0;i<len(arrayVariables);i++ {
fmt.Printf("arrayVariables[%d]=%d,地址=%p\n",i,arrayVariables[i],&arrayVariables[i])
}
//切片去引用数组
sliceVariables = arrayVariables[:]
for i:=0;i<len(sliceVariables);i++ {
fmt.Printf("sliceVariables[%d]=%d,地址=%p\n",i, sliceVariables[i],&sliceVariables[i])
}
var sliceVariables2 []int
sliceVariables2 = arrayVariables[1:3]
//12,21,23,55,98,2
//[21 23]
fmt.Println(sliceVariables2)
//切片指向的是底层的数组
for i :=0;i<len(sliceVariables2);i++ {
fmt.Printf("sliceVariables2[%d]=%d,地址=%p\n",i, sliceVariables2[i],&sliceVariables2[i])
}
sliceVariables2[0] = 100
fmt.Println(arrayVariables)
fmt.Println(sliceVariables2)
遍历和数组一样
切片的追加append
切片注意
- 切片是引用类型
- 切片做为参数传递给函数的意义重大,同数组,当传递较大的数组切片时可以有效的提升cpu执行效率
- new 用于各种类型的内存分配 返回的是指针,var variables = new(T)
返回的是T,指针类型,即返回的是一个地址,默认是当前类型的零值,它返回了一个指针,指向新分配的类型 T 的零值。也就是说new 返回指针。 - make 用来分配内存主要用来分配引用类型,比如channel ,map ,slice,返回一个有初始值 (非零) 的 T 类型,不是T类型
函数
函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func 函数名(形式参数列表)(返回值列表){
函数体
}
可变参数
func actionVariables3(args ...interface{}) {
for _,value := range args {
switch value.(type) {
case int:
fmt.Println(value,"int")
case string:
fmt.Println(value,"string")
case float64:
fmt.Println(value,"float64")
case bool:
fmt.Println(value,"bool")
default:
fmt.Println(value,"unknow")
}
}
}
匿名函数
//1
sumVariables := func(var1,var2 int) int {
return var1 + var2
}(1,2)
//2
func1 := func(var1,var2 int) int{
return var1 - var2
}
- //将一个匿名函数赋值给一个变量,这个变量存储的是这个匿名的地址
func1 := func(var1,var2 int) int{
return var1 - var2
}
函数参数传递方式
- 值传递,内存通常在栈上保存
- 引用传递 ,内存通常在堆上, 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的 数据大小,数据越大,效率越低。
值类型和引用类型
**值类型:**基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
变量作用域
- 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
- 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用 域在整个程序有效
- 如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
变量符合就近原则
闭包
所谓的闭包是指有权访问另一个函数作用域中的变量的函数,就是在一个函数内部创建另一个函数
示例1.
package main
import (
"fmt"
"strings"
)
//import "fmt"
func main() {
function := makeSuffix(".jpg")
name := function("hello.png")
fmt.Println(name)
}
//实现加后缀
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name,suffix){
return name + suffix
}
return name
}
}
示例二 累计器
func counter() func() int {
i := 0
res := func () int{
i += 1
return i
}
return res
}
defer关键字
- 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈 中 然后继续执行函数下一个语句。
- 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),
所以同学们看到前面案例输出的顺序。 - 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。
- defer 在return之后
defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。 - 在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是 锁资源), 可以执行 defer file.Close() defer connect.Close()
- 在 defer 后,可以继续使用创建资源.
- 当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.
defer示例一
func main() {
a:=10
b := 20
defer func() {
fmt.Println("匿名函数a",a)
fmt.Println("匿名函数b",b)
}()
a = 100
b = 200
fmt.Println("main函数a",a)
fmt.Println("main函数b",b)
/**
main函数a 100
main函数b 200
匿名函数a 100
匿名函数b 200
由于defer调用了函数不回立即执行,等待执行完毕之后在执行,那时a和b已经变成100,200了
*/
}
示例二
func main() {
a:=10
b := 20
defer func(a,b int) {
fmt.Println("匿名函数a",a)
fmt.Println("匿名函数b",b)
}(a,b)
a = 100
b = 200
fmt.Println("main函数a",a)
fmt.Println("main函数b",b)
/**
main函数a 100
main函数b 200
匿名函数a 10
匿名函数b 20
由于defer调用了函数不回立即执行,但是已经完成参数的传递
*/
}
示例三
func main() {
fmt.Println("defer begin")
// 将defer放入延迟调用栈
defer fmt.Println(1)
defer fmt.Println(2)
// 最后一个放入, 位于栈顶, 最先调用
defer fmt.Println(3)
fmt.Println("defer end")
/**
defer begin
defer end
3
2
1
*/
}
装饰器模式
装饰器模式(Decorator)是一种软件设计模式,其应用场景是为某个已经存在的功能模块(类或者函数)添加一些「装饰」功能,而又不会侵入
和修改原有的功能模块。
java等语言实现装饰器模式可以直接用注解实现,但是GO没有提供「注解」之类的语法糖,在函数式编程中,要实现装饰器模式,可以借助高阶函数来实现.
示例
//计算乘法的函数
func multiply(a,b int) int {
return a*b
}
func main() {
a:=2
b:=4
c:= multiply(a,b)
fmt.Println(a," X " , b ," = " , c )
}
若要实现乘法计算的时捡,则需要对其实行装饰器
type Multiply func(a,b int) int
func multiply(a,b int) int {
return a*b
}
//装饰器,传入函数,并返回结果
func execTime(f Multiply) Multiply {
return func(a, b int) int {
start := time.Now()
c := f(a,b)
end := time.Since(start)
fmt.Println("执行耗时",end)
return c
}
}
func main() {
a:=2
b:=4
decorator := execTime(multiply)
c := decorator(a,b)
fmt.Println(a," X " , b ," = " , c )
}
示例二
//比较位运算和乘法云算时间
type Multiply func(a,b int) int
func multiply(a,b int) int {
return a*b
}
func multiply2(a,b int) int {
return a << b
}
func execTime(f Multiply) Multiply {
return func(a, b int) int {
start := time.Now()
for i := 0; i < 1000000; i++ {
f(a,b)
}
c:=f(a,b)
end := time.Since(start)
fmt.Println("执行耗时",end)
return c
}
}
func main() {
a:=2
b:=4
decorator := execTime(multiply)
c := decorator(a,b)
fmt.Println(a," X " , b ," = " , c )
decorator2 := execTime(multiply2)
c2 := decorator2(a,b)
fmt.Println(a," << " , b ," = " , c2 )
}
Go语言计算函数执行时间
Since() 函数返回从 t 到现在经过的时间,等价于time.Now().Sub(t)。
Go语言递归函数
构成递归需要具备以下条件:
- 一个问题可以被拆分成多个子问题;
- 拆分前的原问题与拆分后的子问题除了数据规模不同,但处理问题的思路是一样的;
- 不能无限制的调用本身,子问题需要有退出递归状态的条件。
- 注意:编写递归函数时,一定要有终止条件,否则就会无限调用下去,直到内存溢出。
示例一
//实现函数嘉和
func main() {
c := run(100)
fmt.Println(c)
}
func run(num int) int {
if num == 0 {
return 0
}else{
return run(num-1) +num
}
}
实现斐波那契数列
func main() {
res := 0
for i := 1; i < 10; i++ {
res = fibonacci(i)
fmt.Println("fibonacci(i)=",res)
}
}
func fibonacci(n int) (res int) {
if n <= 2 {
res = 1
}else{
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}
func main() {
c := f1(3)
fmt.Println(c)
}
func f1(n int) int {
if n == 1 {
return 3
}else{
return 2*f1(n-1)+1
}
}
func main() {
c := peach(1)
fmt.Println(c)
}
func peach(n int) int {
if n > 10 || n < 1 {
return 0
}
if n == 10 {
return 1
}else {
return (peach(n+1) + 1) * 2
}
}
综合联系
func main() {
var n int
fmt.Println("请输入打印金字塔层数")
fmt.Scanln(&n)
printPyramind(n)
}
func printPyramind(n int) {
for i := 1; i <= n ; i++ {
for k := 0; k < n - i; k++ {
fmt.Print(" ")
}
for j := 0; j < 2 * i -1; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
总结
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
- 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传 入变量的地址&,函数内以指针的方式操作变量
- Go 函数不支持函数重载
- 使用 _ 标识符,忽略返回值
- 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。