第三节 Go语言基础语法(二)

 

go的条件语句、循环、函数、指针、数组

与其说是教程,更像是总结

一起学习:smiley:


1. 条件语句

if

if条件语句与其他语言的区别在于条件语句没有括号

if条件中也可以定义变量

第一种语法

中规中矩的写法

//文件名
const fileName = "abc.txt"
//读取文件
//这里可能会好奇,咋有两个返回值,等下面介绍函数时会详细说明的
contents, err := ioutil.ReadFile(fileName)

//第一种if写法
if err != nil {//判断是否有误
    //如果err不等于nil(null),说明里面有东西,也就是错误信息
    fmt.Println(err)
} else {
    //如果err == nil,就没有错,正常数据文件中的内容
    fmt.Printf("%s\n", contents)
}

第二种写法

类似其他语言的for循环的语法,同时也要注意作用域

//第二种if写法
if contents, err := ioutil.ReadFile(fileName); err != nil {
	fmt.Println(err)
} else {
	fmt.Printf("%s\n", contents)
}

//出了if作用域就无法访问contents了,和其他语言for很象
fmt.Println(contents)//报错

switch

没有繁琐的break,默认是有break的

第一种写法

中规中矩

//定义一个函数(后面会详细说的)
func cal(a, b int, op string) int {
    //定义返回值
	var result int
    //switch语句
	switch op {
	case "+":
		result = a + b
	case "-":
		result = a - b
	case "*":
		result = a * b
	case "/":
		if b == 0 {
			panic(fmt.Sprintf(
				"%s\n", "被除数不能为0"))
		}
		result = a / b
	default:
		panic("unsupported operator:" + op)
	}
	return result
}

第二种写法

省略参数(上略了上个例子中的op)

func grade(score int) string {
	var g string

	switch {
	case score < 0 || score > 100:
		//定义错误
		panic(fmt.Sprintf(
			"Wrong score :%d", score))
	case score < 60:
		g = "F"
	case score < 80:
		g = "C"
	case score < 90:
		g = "B"
	case score <= 100:
		g = "A"
	}
	return g
}

goto

这个一般不常用,因为比较难控制

/**
	求素数
*/
func findPrime(num int) {
	var C, c int
	C = 1
LOOP:
	for C < num {
		C++
		for c = 2; c < C; c++ {
			if C%c == 0 {
				goto LOOP
			}
		}

		fmt.Printf("%d \t", C)
	}
}

2. 循环

就一个:for

Go中没有while,用for就够了

for,if后面的条件没有括号

第一种写法:完整for参数

和其他语言一样,标准的三段式

/**
	二进制转换函数
*/
func convertToBin(n int) string {
	result := ""
    //标准的三段式
	for ; n > 0; n /= 2 {
		lsb := n % 2
		//由于二进制求完需要后往前倒推,这里直接放在前面
		result = strconv.Itoa(lsb) + result
	}
	return result
}

第二种写法:省略for两个参数

/**
	输出文件内容
*/
func printFile(fileName string) {
    //打开文件
	file, err := os.Open(fileName)
    //判断错误
	if err != nil {
		panic(err)
	}
    //读取文件
	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
        //逐行扫描读取
		fmt.Println(scanner.Text())
	}
}

第三种写法:省略for全部参数

直接变成while(true)

/**
	死循环
*/
func forever() {
    //相当于while循环
	for {
		fmt.Println("abc")
	}
}

3. 函数

返回一个值

就是和其他语言一样普通的函数

//格式
func 函数名(参数) 返回值类型{
	return 返回值
}
//返回返回一个值
func eval(a, b int, op string) int {
	switch op {
	case "+":
		return a + b
	case "-":
		return a - b
	case "*":
		return a * b
	case "/":
		return a / b
	default:
		//中断程序运行
		panic("unsupported operation " + op)
	}
}

返回两个值

//格式
func 函数名(参数) 返回值类型,返回值类型{
	return 返回值,返回值
}
//返回两个值
func div(a, b int) (int, int) {
	return a / b, a % b
}

另一种写法-带参数(不建议)

写明了参数,return返回时就不用返回具有内容了

//返回两个值(带参数名称),不建议这么写
func divWithParameterName(a, b int) (q, r int) {
   q = a / b
   r = a % b
   return
}

返回错误

其实就是多返回了个error类型的值,发现上面写的错误判断来源了:poultry_leg:

//返回错误
func evalWithError(a, b int, op string) (int, error) {
   switch op {
   case "+":
      return a + b, nil
   case "-":
      return a - b, nil
   case "*":
      return a * b, nil
   case "/":
      return a / b, nil
   default:
      //定义错误
      return 0, fmt.Errorf("unsupported operation %s", op)
   }
}

函数式编程

函数作为参数

函数可作为参数传递,因为可以反射获取到函数的函数名和地址,就可以调用函数

案例1

/**
	函数式编程
*/
package main

import (
	"fmt"
	"math"
	"reflect"
	"runtime"
)

func pow(a, b int) int {
	return int(math.Pow(float64(a), float64(b)))
}

/**
	1. 【op func(int, int) int】函数的格式
	2. 【a, b int】 参数
	3. 【int】 返回值
*/
func apply(op func(int, int) int, a, b int) int {
	//反射获取函数指针
	p := reflect.ValueOf(op).Pointer()
	//获取函数名称
	opName := runtime.FuncForPC(p).Name()
	fmt.Printf("Calling function %s with args (%d , %d)\n", opName, a, b)
	fmt.Print("ans is ")
	fmt.Print(op(a, b))
	return op(a, b)
}

func main() {
	apply(pow, 1, 2)
}

结果输出:

PS F:\Desktop\test> go run main.go
Calling function main.pow with args (1 , 2)
ans is 1

案例2

package main

import (
	"fmt"
)

// 相当于取别名
type processFunc func(int) bool

func main() {
	//数组
	slice := []int{1, 2, 3, 24, 32423, 4, 24, 2, 34}
	//
	odd := filter(slice, isOdd)
	fmt.Println("奇数: ", odd)

	enve := filter(slice, isEven)
	fmt.Println("偶数: ", enve)
}

func filter(slice []int, f processFunc) []int {
	var result []int
	for _, value := range slice {
		// 调用函数
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}

//判断元素是否为奇数
func isOdd(n int) bool {
	return n&1 == 1
}

//判断元素是否为偶数
func isEven(n int) bool {
	return n&1 == 0
}

结果输出:

PS F:\Desktop\test> go run main.go
奇数:  [1 3 32423]
偶数:  [2 24 4 24 2 34]

回调函数

回调函数,函数有一个参数是函数类型,这个函数就是回调函数

package main

import (
	"fmt"
	"math"
)

func main() {
	arr := []float64{1, 9, 16, 25, 36, 49}

	visit(arr, func(v float64) {
		v = math.Pow(v, 2) //平方
		fmt.Printf("%.0f\t", v)
	})

	fmt.Println()

	visit(arr, func(v float64) {
		v = math.Sqrt(v) //平方根
		fmt.Printf("%.0f\t", v)
	})
}

func visit(list []float64, f func(float64)) {
	for _, value := range list {
		f(value)
	}
}

结果输出:

PS F:\Desktop\test> go run main.go
1       81      256     625     1296    2401
1       3       4       5       6       7

闭包(难点)

后面会有专题讲解,可以暂时简单理解为,堆的内存没被释放

package main

import (
	"fmt"
)

func main() {
	myfunc := Counter()
	fmt.Println("myfunc", myfunc)

	fmt.Println(myfunc())
	fmt.Println(myfunc())
	fmt.Println(myfunc())

	myfunc1 := Counter()
	fmt.Println("myfunc1", myfunc1)

	fmt.Println(myfunc1())
	fmt.Println(myfunc1())

	fmt.Println(myfunc())
}

func Counter() func() int {
	i := 0
	res := func() int {
		i += 1
		return i
	}

	fmt.Println("Counter中的内部函数: ", res)
	return res
}

可变参数函数

【…】后面会详细说明的

/**
	可变参数函数
*/
//不限制参数的传递
func sumArgs(numbers ...int) int {
   sum := 0
   // 经典for range循环
   for i := range numbers {
      sum += numbers[i]
   }
   return sum
}

主函数和匿名函数

主函数:main函数,也就是Go中所有程序的入口

匿名函数:更像是其他语言的Lambda

// 要用主函数,就要使用main包
package main

//”头文件“
import (
	"fmt"
	"math"
)

//主函数
func main() {
	//匿名函数
	fmt.Println((func(a, b int) int {
		return int(math.Pow(float64(a), float64(b)))
	})(3, 4))//(3, 4) 相当于立即调用并传入参数
}

4. 指针

go语言只有值传递,没有引用传递

go语言指针不能做运算

一般就是判断需要需要改变参数的值,考虑使不使用指针(当然也有其他操作)

/**
	交换值
*/
func swap_p(a, b *int) (int, int) {
	return *b, *a
}

func swap(a, b int) (int, int) {
	return b, a
}

func main() {
	a, b := 3, 4
	fmt.Println(swap_p(&a, &b))
	fmt.Println(swap(a, b))
}

值传递和引用传递

go只有值传递,传递的都是一个副本,有些能修改是因为参数是引用类型

但也有引用的数据类型

默认副本的有:int string bool array struct

引用类型的有:pointer slice map chan

5. 数组

一维和二维数组定义

//定义一维数组,var定义默认初始化为0
var arr1 [5]int
//需要给值
arr2 := [3]int{1, 2, 3}
//让编译器计算个数
arr3 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}

//定义二维数组
var grid [4][5]int

输出数组

fmt.Println(arr1, arr2, arr3, grid)

//数组的遍历1
//Go语言没有++i的操作
for i := 0; i < len(arr3); i++ {
	fmt.Println(arr3[i])
}

//遍历2
for i, v := range arr3 {
	fmt.Println(i, v)//下标、值
}

for _, v := range arr3 {
	fmt.Println(v)//只要值
}

for i := range arr3 {
	fmt.Println(arr3[i])//只要下标
}

值传递

Go语言中数组作为参数是拷贝值传递的

package main

import "fmt"

func main() {
	arr := [5]int{1, 4, 5, 6, 7}
	changeArray(arr)
	for i, v := range arr {
		fmt.Println(i, v)
	}
	printChangeArray(&arr)
	for i, v := range arr {
		fmt.Println(i, v)
	}
}

//[5]int->值类型数组 []int->切片(后面会补充)
//传的是值类型,会拷贝数组
func changeArray(arr [5]int) {
	arr[0] = 100 //不被修改
}

//传指针就可以修改了
func printChangeArray(arr *[5]int) {
	arr[0] = 100 //会被修改
}

小案例

生成不重复真随机数

//生成count个[start,end)结束的不重复的随机数
func generateRandomNumber(start int, end int, count int) []uint64 {
	//范围检查
	if end < start || (end-start) < count {
		return nil
	}
	//存放结果的slice
	nums := make([]uint64, 0)
	for len(nums) < count {
		//生成真随机数
		num, _ := rand.Int(rand.Reader, big.NewInt(9))
		a := num.Uint64()
		//查重
		exist := false
		for _, v := range nums {
			if v == a {
				exist = true
				break
			}
		}
		if !exist {
			nums = append(nums, a)
		}
	}
	return nums
}