Golang学习笔记

教程 day07-08 笔记 day06-05

一、初识Golang

1.1 环境安装

1.1.1 安装包下载

  • Go安装包下载网址:https://golang.org/dl/
  • 这里使用msi安装版,比较方便
  • 千万不要在安装路径中出现中文,一路Next
// 可以查看是否安装成功
go ven

1.1.2 Go 模块代理设置

  • 打开终端并执行
go env -w GOPROXY=https://goproxy.cn,direct
  • 还有一个代理地址 https://goproxy.io 也很好用

1.1.3 GoLand 模块代理设置

File - Setting - Go Modules(vgo) - Proxy 设置成 https://goproxy.io

1.2 注释

/*
块注释
可以注释多行
*/
 
// 这是单行注释

1.3 hello world

//文件所属的包,在go语言中,主函数所在的包一定是main
package main

//导入系统包,标准输入输出包
import "fmt"

// func 函数格式
// main 函数名,main是主函数,程序有且只有一个主函数,无论程序中有多少函数,都会从main进入
// () 函数参数列表
// {} 函数体,也可以称作代码体或程序体
func main()  {

	// fmt包,Println 打印并且换行 "" 引起来的称为字符串
	fmt.Println("hello world!")
}

二、变量

2.1 变量定义和使用

  • 在Golang中,定义的变量必须使用
package main

import (
	"fmt"
	"math"
)

func main01() {

	// 定义格式:var 变量名 数据类型 = 值
	// int 表示整型数据
	var sum int = 100

	// 变量在程序运行过程中,值可以发生改变
	sum = sum + 50
	fmt.Println(sum)
}

func main02() {

	// 变量的声明,如果没有赋值,默认值为0
	var sum int

	// 为变量赋值
	sum = 50
	fmt.Println(sum)
}

func main03() {
	// float64 浮点型数据
	var value float64 = 2

	// var sum3 float64 = value * value * value * value * value
	// 可以使用系统提供的包,计算数据的 n 次方
	// 需要导入math包,Pow函数
	var sum float64 = math.Pow(value, 5)
	fmt.Println(sum)
}

2.2 自动推导类型

package main

import "fmt"

func main() {
	// 传统的变量定义方式
	// 不同的数据类型在内存中开辟的空间不同
	// var a int = 10
	// var b float64 = 123.456

	// 第一种自动推导类型
	// var a = 10
	// var b = 123.456

	// 第二种自动推导类型(推荐)
	a := 10
	b := 123.456

	// 不同的数据类型不能计算
	// c := a + b  // err

	fmt.Println(a)
	fmt.Println(b)
}

2.3 交换变量

package main

import "fmt"

func main() {
	//交换两个变量的值
	a := 10
	b := 20

	// 第一种使用第三变量进行交换
	// c := a
	// a = b
	// b = c

	// 第二种通过运算进行交换
	// a = a + b
	// b = a - b
	// a = a - b

    // 第三种通过多重赋值进行交换(推荐)
	a, b = b, a
	
	fmt.Println(a)
	fmt.Println(b)
}

2.4 多重赋值和匿名变量

package main

import "fmt"

func main01() {
	// 多重赋值
	// 变量个数和值的个数要一一对应
	a, b, c := 10, 123.456, "golang"

	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
}

func main02() {
	var a int = 10
	var b int = 20

	// 在一个作用域范围内变量名不能重复
	// var a = 100

	// 使用多重赋值修改变量
	//a, b = 100, 200

	// 多重赋值时如果有新定义的变量,可以使用自动推导类型,加个冒号
	a, b, c, d := 100, 200, "hello", "golang"

	// 没有定义新变量时不能使用自动推导类型
	//a, b := 100, 200  // err

	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println(d)
}

func main() {
	// _表示匿名变量 不接收数据
	_,_,c,d := 100,200,"hello","golang"

	fmt.Println(c)
	fmt.Println(d)
}

2.5 格式化输出

  • 格式化输出打印目前需要使用 fmt.Printf() 配合占位符
  • fmt.Println() 收到非十进制数字都会自动转换成十进制
  • 十六进制数据是以0x开头,例如 0xABC
  • 八进制数据是以0开头,最大值为7,例如 0777
  • 二进制数据不能在go语言中直接表示
package main

import "fmt"

func main() {

	// 换行输出
	fmt.Println("hello golang")

	// 不换行输出
	fmt.Print("hello golang")

	a, b, c := 10, 123.456, "golang"

	// format,格式输出,\n表示一个转义字符换行
	// %d是一个占位符,表示输出一个整型数据
	fmt.Printf("\n%d", a)  // 10

	// %f是一个占位符,表示输出一个浮点型数据
	// %f默认保留六位小数,因为浮点型数据不是精准的数据,六位是有效的
	fmt.Printf("\n%f", b)  // 123.456000

	// %.2f保留小数位数为两位,会对第三位小数进行四舍五入
	fmt.Printf("\n%.2f", b)  // 123.46

	// %s是一个占位符,表示输出一个字符串类型
	fmt.Printf("\n%s", c)  // golang
}
格式含义
%%一个%字面量
%b一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d一个十进制数值(基数为10)
%f以标准记数法表示的浮点数或者复数值
%o一个以八进制表示的数字(基数为8)
%p以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%t以true或者false输出的布尔值
%T使用Go语法输出的值的类型
%x以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X以十六进制表示的整型值(基数为十六),数字A-F使用小写表示

2.6 获取键盘输入

package main

import "fmt"

func main01() {
	// 声明变量
	var a int

	// 通过键盘为变量赋值
	// & 是一个运算符,取地址运算符
	// 输入必须加 & 运算符,简单点理解是修改变量a在内存中储存的值
	fmt.Scan(&a)

	// fmt.Println(&a)  // 内存地址 0xc042058080 是一个十六进制整型数据

	fmt.Println(a)
}

func main02() {
	// 可以一次声明多个变量
	var a, b int

	// 空格或回车 表示一个输入接收结束
	fmt.Scan(&a, &b)

	fmt.Println(a)
	fmt.Println(b)
}

func main03() {
	var a int
	var b string

	// 带format格式化的,在接收字符串时只能用空格作为分割
	fmt.Scanf("%d%s", &a, &b)

	fmt.Printf("%d", a)
	fmt.Printf("%s", b)
}


func main() {
	//通过键盘输入学生三门成绩计算总成绩和平均成绩
	var c, m, e int

	fmt.Scan(&c, &m, &e)

	sum := c + m + e
	fmt.Println("总成绩:", sum)
	// 两个整型数据相除 得到的结果也是整型
	fmt.Println("平均成绩:", sum/3)
}

2.7 变量命名规范

  • 允许使用字母、数字、下划线
  • 不允许使用go系统关键字
  • 不允许使用数字开头
  • 区分大小写
  • 见名知义

三、基础数据类型

3.1 数据类型汇总

  • 引用类型目前只知道有3个,切片、字典、通道
类型名称长度(字节)默认值范围说明
bool布尔类型1false其值不为真即为假,不可以用数字代表true或false
byte字节类型100 ~ 255uint8的别名
rune字符类型40-2147483648 ~ 2147483647int32的一个别名,主要用于表示utf-8编码时的字符类型
uintptr无符号整型4或8无符号整型,用于存放一个指针
uint无符号整型4或8032位系统等于int32,64位系统等于int64
uint8无符号整型100 ~ 255
uint16无符号整型200 ~ 65535
uint32无符号整数类型400 ~ 4294967295小数位精确到7位
uint64无符号整型800 ~ 18446744073709551615小数位精确到15位
int整型4或8032位系统等于int32,64位系统等于int64
int8整型10-128 ~ 127
int16整型20-32768 ~ 32767
int32整型40-2147483648 ~ 2147483647
int64整型80-9223372036854775808 ~ 9223372036854775807
float32单精度浮点型40.0
float64双精度浮点型80.0
complex64浮点类型832 位实数和虚数
complex128浮点类型1664 位实数和虚数
array数组值类型
struct结构体值类型
string字符串值类型“”UTF-8 字符串
slice切片nil引用类型
map字典nil引用类型
channel通道nil引用类型
interface接口nil
function函数nil

3.2 bool 布尔类型

  • 默认值为false
  • bool类型一般用于条件判断
package main

import "fmt"

func main() {
	var a bool
	fmt.Println(a)  // 默认值为false

	b := true
	fmt.Printf("%T",b)  // 输出一个变量对应的数据类型
}

3.3 int 整数类型

  • int类型会根据操作系统位数不同在内存中占的字节也不同
    • 64位系统就是int64
    • 32位系统就是int32
    • 64位系统中的int不能直接与int64计算,需要类型装换才可计算
  • int64
    • 等于 -2^63 ~ 2^63-1
    • 因为0占了一位所以最大值要减1
    • 因为负号占了一字节所以只能是63次方
  • uint64
    • 等于 -2^64+1 ~ 0
    • 因为0占了一位所以最小值要加1
    • 因为符号统一所以是64次方

3.4 float 浮点数类型

  • float32 仅小数点后5位是精准的
  • float64 仅小数点后13位是精准的
  • 自动推导类型创建的浮点型变量,默认类型为float64
package main

import "fmt"

func main() {
	var a float32 = 123.456
	fmt.Printf("%.5f",a)  // 小数点后5位之后不再精准

	var b float64 = 123.456
	fmt.Printf("\n%.13f",b)  // 小数点后13位之后不再精准

	c := 3.14
	fmt.Printf("\n%T", c)  // 自动推导类型是float64(双精度)
}

3.5 byte 字符类型

  • 类型 byte 是 uint8 的别名
  • 需要背诵的ASCII编码值
    • 空格 space 对应值为 32
    • 字符 '0' 对应值为 48
    • 字符 'A' 对应值为 65
    • 字符 'a' 对应值为 97
package main

import "fmt"

func main() {
	var abc byte = 'a'  // 定义变量abc的值为字符a

	fmt.Println(abc)  // 直接打印是97
	fmt.Printf("%c\n", abc)  // 必须使用占位符 %c 输出一个字符

	// 其实类型 byte 是 uint8 的别名
	fmt.Printf("%T\n", abc)  // 输出结果为 uint8
	
	// 根据ASCII编码表,将小写字符 a 转换成大写字符 A
	fmt.Printf("%c", abc-32)  // 输出结果为 A
}

3.6 string 字符串类型

3.6.1 字符串定义

  • 双引号引起来的称为字符串
  • 在go语言中一个汉字占3个字符,为了和linux进行统一处理
package main

import "fmt"

func main() {
	str := "go语言"
    count := len(str)
	fmt.Println(count) 
}

// 输出结果
8

3.6.2 字符串处理函数

3.6.2.1 strings 包
序号分类函数说明
01计数Count(s, substr string) int计算字符串 substrs 中的非重叠个数。如果 substr 为空串则返回 s 中的字符(非字节)个数 +1
02重复Repeat(s string, count int) stringcount 个字符串 s 连接成一个新的字符串。
03删除Trim(s string, cutset string) string删除 s 首尾连续的包含在 cutset 中的字符。
04删除TrimSpace(s string) string删除 s 首尾连续的的空白字符。
05删除TrimPrefix(s, prefix string) string删除 s 头部的 prefix 字符串。如果 s 不是以 prefix 开头,则返回原始 s
06删除TrimSuffix(s, suffix string) string删除 s 尾部的 suffix 字符串。如果 s 不是以 suffix 结尾,则返回原始 s。(只去掉一次,注意和 TrimRight 区别)
07删除TrimLeft(s string, cutset string) string删除 s 头部连续的包含在 cutset 中的字符串。
08删除TrimRight(s string, cutset string) string删除 s 尾部连续的包含在 cutset 中的字符串。
09删除TrimFunc(s string, f func(rune) bool) string删除 s 首尾连续的满足 f(rune) 的字符。
10删除TrimLeftFunc(s string, f func(rune) bool) string删除 s 头部连续的满足 f(rune) 的字符。
11删除TrimRightFunc(s string, f func(rune) bool) string删除 s 尾部连续的满足 f(rune) 的字符。
12查找Contains(s, substr string) bool判断字符串 s 中是否包含子串 substr。包含或者 substr 为空则返回 true
13查找Index(s, substr string) int返回子串 substr 在字符串 s 中第一次出现的位置。如果找不到则返回 -1;如果 substr 为空,则返回 0
14查找LastIndex(s, substr string) int返回子串 substr 在字符串 s 中最后一次出现的位置。如果找不到则返回 -1;如果 substr 为空则返回字符串 s 的长度。
15查找HasPrefix(s, prefix string) bool判断字符串 s 是否以 prefix 开头。
16查找HasSuffix(s, suffix string) bool判断字符串 s 是否以 prefix 结尾。
17查找ContainsRune(s string, r rune) bool判断字符串 s 中是否包含字符 r
18查找IndexRune(s string, r rune) int返回字符 r 在字符串 s 中第一次出现的位置。如果找不到则返回 -1
19查找ContainsAny(s, chars string) bool判断字符串 s 中是否包含子串 chars 中的任何一个字符。包含则返回 true,如果 chars 为空则返回 false
20查找IndexAny(s, chars string) int返回字符串 chars 中的任何一个字符在字符串 s 中第一次出现的位置。如果找不到或 chars 为空则返回 -1
21查找LastIndexAny(s, chars string) int返回字符串 chars 中的任何一个字符在字符串 s 中最后一次出现的位置。如果找不到或chars 为空则返回 -1
22查找IndexFunc(s string, f func(rune) bool) int返回s中第一个满足 f(rune) 的字符的字节位置。如果没有满足 f(rune) 的字符,则返回 -1
23查找LastIndexFunc(s string, f func(rune) bool) int返回 s 中最后一个满足 f(rune) 的字符的字节位置。如果没有满足 f(rune) 的字符,则返回 -1
24替换Replace(s, old, new string, n int) string返回s的副本,并将副本中的 old 字符串替换为 new字符串,替换次数为n 次,如果 n-1,则全部替换;如果 old 为空,则在副本的每个字符之间都插入一个 new
25替换Map(mapping func(rune) rune, s string) strings 中满足 mapping(rune) 的字符替换为 mapping(rune) 的返回值。如果 mapping(rune) 返回负数,则相应的字符将被删除。
26切割Split(s, sep string) []stringsep 为分隔符,将 s 切分成多个子切片,结果中不包含 sep 本身。如果 sep 为空,则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为[]string的第一个元素返回。
27切割SplitN(s, sep string, n int) []stringsep 为分隔符,将 s 切分成多个子串,结果中不包含 sep 本身。如果 sep 为空则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为 []string 的第一个元素返回。参数 n 表示最多切分出几个子串,超出的部分将不再切分,最后一个 n 包含了所有剩下的不切分。如果 n0,则返回 nil;如果 n 小于 0,则不限制切分个数,全部切分。
28切割SplitAfter(s, sep string) []stringsep 为分隔符,将 s 切分成多个子切片,结果中包含sep 本身。如果 sep为空,则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为 []string 的第一个元素返回。
29切割SplitAfterN(s, sep string, n int) []stringstr 为分隔符,将 s 切分成多个子串,结果中包含sep 本身。如果 sep 为空,则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为 []string 的第一个元素返回。参数 n 表示最多切分出几个子串,超出的部分将不再切分。如果 n0,则返回 nil;如果 n 小于 0,则不限制切分个数,全部切分。
30切割Fields(s string) []string以连续的空白字符为分隔符,将 s 切分成多个子串,结果中不包含空白字符本身。空白字符有:\t, \n, \v, \f, \r, ’ ‘, U+0085 (NEL), U+00A0 (NBSP) 。如果 s 中只包含空白字符,则返回一个空列表。
31切割FieldsFunc(s string, f func(rune) bool) []string以一个或多个满足 f(rune) 的字符为分隔符,将 s 切分成多个子串,结果中不包含分隔符本身。如果 s 中没有满足 f(rune) 的字符,则返回一个空列表。
32连接Join(a []string, sep string) stringa 中的子串连接成一个单独的字符串,子串之间用 sep 分隔。
33转换ToUpper(s string) strings 中的所有字符修改为其大写格式。对于非 ASCII 字符,它的大写格式需要查表转换。
34转换ToLower(s string) strings 中的所有字符修改为其小写格式。对于非 ASCII 字符,它的小写格式需要查表转换。
35转换ToTitle(s string) strings 中的所有字符修改为其 Title 格式,大部分字符的 Title 格式就是 Upper 格式,只有少数字符的 Title 格式是特殊字符。这里的 ToTitle 主要给 Title 函数调用。
36比较EqualFold(s, t string) bool比较 UTF-8 编码在小写的条件下是否相等,不区分大小写,同时它还会对特殊字符进行转换。比如将 “ϕ” 转换为 “Φ” 、将 “DŽ” 转换为 “Dž” 等,然后再进行比较。
37比较“==“比较字符串是否相等,区分大小写,返回 bool
38比较Compare(a, b string) int比较字符串,区分大小写,比”==”速度快。相等为 0,不相等为 -1

3.6.3 字符串类型转换

3.6.3.1 string 与 []byte 互转
package main

import "fmt"

func main() {
	// 将字符串转成字符切片,强制类型转换
	slice := []byte("hello world")
	fmt.Println(slice)  // [104 101 108 108 111 32 119 111 114 108 100]

	// 将字符切片转成字符串,强制类型转换
	str := string(slice)
	fmt.Println(str)  // hello world
}
3.6.3.2 string 与 bool 互转
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// 将字符串转成布尔,可以查看官方函数里的具体实现
	b, _ := strconv.ParseBool("true")
	fmt.Println(b)

	// 将布尔转成字符串
	s := strconv.FormatBool(true)
	fmt.Println(s)
}
3.6.3.2 string 与 int 互转
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// string 转为 int
	i, _ := strconv.Atoi("123")
	fmt.Println(i)

	// int 转为 string,默认十进制便捷版
	s1 := strconv.Itoa(123)
	fmt.Println(s1)

	// int 转为 string,完整版,2-36进制均可
	s2 := strconv.FormatInt(int64(123), 10) // 强制转化为int64后使用FormatInt
	fmt.Println(s2)
}
3.6.3.3 string 与 float 互转

3.7 类型转换

  • 类型转换格式
数据类型(变量)  
数据类型(表达式)
  • 在类型转换时建议低类型转成高类型 保证数据精度
  • 建议整型转成浮点型
  • 高类型转成低类型,可能会丢失精度,或者数据溢出,符号发生变化
  • 将浮点型转成整型,保留数据整数部分,丢弃小数部分
package main

import "fmt"

func main01() {
	a, b, c := 10, 20, 40

	sum := a + b + c

	// 这里对sum使用类型转换,使其结果也为浮点数,这里的数字3是字面变量,会自动转换成浮点数
	fmt.Println("平均值是:", float64(sum)/3)
}

func main() {
	var a float32 = 1.99

	// 将浮点型转成整型,保留数据整数部分,丢弃小数部分
	b := int(a)
	fmt.Println(b) // 1
}

四、常量

4.1 const 常量定义与使用

  • 常量不允许左值赋值(常量不允许放在等号左边接收右边的值)
  • 常量的定义,一般定义常量使用大写字母
  • go语言常量的地址,不允许访问
  • 由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达式
package main

func main() {
	// 常量的定义,一般定义常量使用大写字母
	const MAX int = 10

	//go语言常量的地址,不允许访问
	// fmt.Println(&MAX)  // err
}

4.2 字面常量

  • 所谓字面常量(literal),是指程序中硬编码的常量,如:
// 这里面 1 和 5 都是字面常量
fmt.Println(1+5)

4.3 iota 枚举

package main

import "fmt"

func main01() {

	// iota枚举格式如果写在一行中值相等,如果换行值在上一行加1
	const (
		a    = iota
		b, c = iota, iota
	)

	fmt.Println(a) // 0
	fmt.Println(b) // 1
	fmt.Println(c) // 1
}

func main02() {
	// 只需要对第一个进行iota赋值,后面会依次增长
	const (
		a = iota
		b
		c
		d
	)

	fmt.Println(a) // 0
	fmt.Println(b) // 1
	fmt.Println(c) // 2
	fmt.Println(d) // 3
}

func main() {
	// 在定义iota枚举时可以自定义赋值
	const (
		a = iota
		b = 10
		c = 20
		d
		e
		f = iota
		g
	)

	fmt.Println(a) // 0
	fmt.Println(b) // 10
	fmt.Println(c) // 20
	fmt.Println(d) // 20
	fmt.Println(e) // 20
	fmt.Println(f) // 5
	fmt.Println(g) // 6
}

五、运算符

5.1 算数运算符

运算符术语示例结果
+10 + 515
-10 - 55
*10 * 550
/10 / 52
%取模(取余)10 % 31
++后自增,没有前自增a=0; a++a=1
后自减,没有前自减a=2; a–a=1
package main

import "fmt"

func main01() {
	a := 10
	b := 20

	//两个整数相除等到的结果也是整型
	fmt.Println(a / b) // 等于0

	// 在除法计算时,除数不能为 0
	// fmt.Println(a / 0) // err

	//取余运算符除数不能为 0
	// fmt.Println(a % 0)  // err

	// 取余运算符不能对浮点型使用
	// fmt.Println(a % 0.5) // err
}

func main() {
	a := 10
	b := 0.5

	a++

	// 可以对浮点型进行自增自减运算
	b++

	fmt.Println(a) // 11
	fmt.Println(b) // 1.5

	// 自增自减不能出现在表达式中
	// a := a++ + a--  // err

	// 二义性,在不同操作系统中运算方式不同,结果可能会产生偏差
	// a = a++ * a-- - a--  // err
	// b := a-- // err
}

5.2 赋值运算符

运算符说明示例
=普通赋值c = a + b 将 a + b 表达式结果赋值给 c
+=相加后再赋值c += a 等价于 c = c + a
-=相减后再赋值c -= a 等价于 c = c - a
*=相乘后再赋值c *= a 等价于 c = c * a
/=相除后再赋值c /= a 等价于 c = c / a
%=求余后再赋值c %= a 等价于 c = c % a
  • 算数运算符的优先级大于赋值运算符
package main

import "fmt"

func main() {
	var c int = 10
	// 将表达式右侧进行结果计算在进行赋值运算符
	c %= 2 + 3

	//c = c % 5 // ok
	//c = c % 2 + 3 //err

	fmt.Println(c)
}
取余经典练习题
  • 107653秒,等于几天几小时几分几秒
func main() {
	//107653秒,等于几天几小时几分几秒

	s := 107653
	fmt.Println(s/60/60/24%365, "天")
	fmt.Println(s/60/60%24, "时")
	fmt.Println(s/60%60, "分")
	fmt.Println(s%60, "秒")

	//1*24*60*60 + 5*60*60 + 54*60 + 13 =107653
}

5.3 比较运算符

  • 算数运算符优先级高于比较运算符
  • 比较运算符返回值类型为bool类型
运算符术语示例结果
==相等于2 == 1false
!=不等于2 != 1true
<小于2 < 1false
>大于2 > 1true
<=小于等于2 <= 1false
>=大于等于2 >= 1true

5.4 逻辑运算符

  • 逻辑与优先级高于逻辑或
运算符术语示例结果八字口诀
&&a && b如果a和b都为真,则结果为真,否则为假。同真为真,其余为假
||a || b如果a和b有一个为真,则结果为真,二者都为假时,结果为假。同假为假,其余为真
!!a如果a为假,则!a为真;如果a为真,则!a为假。非真为假,非假为真

5.5 其他运算符

运算符术语示例结果
&取地址运算符&a变量a的地址
*取值运算符*a指针变量a所指向内存的值
package main

import "fmt"

func main() {
	a := 10

	// & 取地址运算符
	fmt.Println(&a)  // 0xc00000a0b8

	// p 定义为指针变量,所以 p 携带的是地址信息
	p := &a
	fmt.Println(p)  // 0xc00000a0b8

	// 获取指针变量 p, 内存地址所对应的值
	fmt.Println(*p)  // 10

	// 既然通过*p可以得到指针变量的值,那就可以间接修改变量的值
	*p = 123
	fmt.Println(*p)  // 123
}

5.6 运算符优先级

大类小类优先级运算符说明
最高()小括号
二级[]数组切片下标
三级.结构体.成员;包.函数;对象.方法
单目运算符逻辑运算符四级!逻辑非
单目运算符算数运算符四级++自增
单目运算符算数运算符四级自减
单目运算符四级&取地址(引用)
单目运算符四级*取值(指针)
双目运算符算数运算符五级*乘法
双目运算符算数运算符五级/除法
双目运算符算数运算符五级%取余
双目运算符算数运算符六级+加法
双目运算符算数运算符六级-减法
双目运算符比较运算符七级>大于
双目运算符比较运算符七级<小于
双目运算符比较运算符七级>=大于等于
双目运算符比较运算符七级<=小于等于
双目运算符比较运算符七级==相等于
双目运算符比较运算符七级!=不等于
双目运算符逻辑运算符八级&&逻辑与
双目运算符逻辑运算符九级||逻辑或
双目运算符赋值运算符最低=普通赋值
双目运算符赋值运算符最低+=相加后再赋值
双目运算符赋值运算符最低-=相减后再赋值
双目运算符赋值运算符最低*=相乘后再赋值
双目运算符赋值运算符最低/=相除后再赋值
双目运算符赋值运算符最低%=求余后再赋值

六、流程控制

6.1 选择结构

6.1.1 if 结构

  • 语法格式
if 条件判断 {
    代码体
}
  • 条件判断如果为真(true),那么就执行大括号中的语句,如果为假(false),就不执行大括号中的语句
package main

import "fmt"

func main() {
	var age int

	fmt.Print("请输入你年龄:")
	fmt.Scan(&age)

	if age >= 18 {
		fmt.Println("恭喜成年了哦!")
	}
}

  • Golang独有 if 的另外一种语法格式
package main

import "fmt"

func main() {

	// if 支持一个初始化语句,初始化语句和判断语句用分号分开
	if age := 18; age >= 18 {
		fmt.Println("恭喜成年了哦!")
	}
}

6.1.2 if…else 结构

  • 语法格式如下:
if 条件判断 {
    代码语句1
} else {
    代码语句2
}
  • 如果条件为真,执行代码语句1,并表示整个 if…else 结构结束了(else后面的代码语句2不会执行)。
  • 如果条件为假,代码语句1不会被执行,这时会执行else后面的代码语句2,并表示整个 if…else 结构执行结束了。
package main

import "fmt"

func main() {
	var age int

	fmt.Print("请输入你的年龄:")
	fmt.Scan(&age)

	// if 支持一个初始化语句,初始化语句和判断语句用分号分开
	if  age >= 18 {
		fmt.Println("恭喜你成年了哦!")
	} else {
		fmt.Println("你还是未成年哦!")
	}
}

6.1.3 if 嵌套

  • 语法格式
if 条件判断1 {
	if 条件判断2 {
	    条件1 和 条件2 都成立
	} else {
	    仅条件1 成立
	}
} else {
    条件1 不成立
}
package main

import "fmt"

func main() {
	var age, money int

	fmt.Print("请输入你的年龄和零花钱:")
	fmt.Scan(&age, &money)

	// 只有同时满足两个条件才可以上网
	if  age >= 18 {
		fmt.Println("恭喜你成年了哦!")
		if money >= 5 {
			fmt.Println("大于5元可以上网了哦!")
		} else {
			fmt.Println("不足5元不可以上网哦!")
		}

	} else {
		fmt.Println("你还是未成年哦!")
	}
}

6.1.4 if…else if 结构

  • 语法格式
if 条件判断 {
	代码体1
} else if 条件判断1 {
	代码体2
} else if 条件判断2 {
	代码体3
} else if 条件判断n {
    ...
} else {
    以上都不满足
}

6.1.5 switch 结构

package main

import "fmt"

// 根据输入的年份月份,计算这个月有多少天
func main() {
	var y, m int

	fmt.Print("请输入年和月:")
	fmt.Scan(&y, &m)

	switch m {
	// 同样的结果可以写一起
	case 1, 3, 5, 7, 8, 10, 12:
		fmt.Printf("%d年%02d月是:31天", y, m)
	case 4, 6, 9, 11:
		fmt.Printf("%d年%02d月是:30天", y, m)
	case 2:
		// 判断是否是闰年:能被4整除,但不能被100整除;能被4整除,也能被400整除。
		if y%4 == 0 && y%100 != 0 || y%400 == 0 {
			fmt.Printf("%d年%02d月是:29天", y, m)
		}else {
			fmt.Printf("%d年%02d月是:28天", y, m)
		}
	default:
		fmt.Println("月份输入错误!")
	}
}

6.1.6 if…else if 与 switch 的比较

  • 各自优点
    • if…else if 可以进行区间判断,嵌套使用
    • switch 执行效率高,可以将多个满足相同条件的值放在一起
  • 各自缺点
    • if…else if 执行效率低
    • switch 不建议嵌套使用

6.2 循环结构

// 循环5次
for i := 0; i < 5; i++ {
	// 代码体,i可在此调用
}
// 遍历一个数组 i -> index 下标 v -> value 值
for i, v := range array {
    // 代码体,i是下标,v是值
}

6.2.1 简单的求和

package main

import "fmt"

func main() {

	// 计算1-100的和
	sum1 := 0
	for i := 1; i <= 100; i++ {
		sum1 += i
	}
	fmt.Println(sum1)

	// 计算1-100偶数的和,在for语句中嵌套if条件判断
	sum2 := 0
	for i := 1; i <= 100; i++ {
		if i%2 == 0 {
			sum2 += i
		}
	}
	fmt.Println(sum2)

	// 计算1-100偶数的和,减少循环次数提高效率
	sum3 := 0
	for i := 0; i <= 100; i += 2 {
		sum3 += i
	}
	fmt.Println(sum3)
}

6.2.2 喝酒敲七游戏

package main

import "fmt"

func main() {
	// 喝酒敲七游戏,何时敲:7的倍数,十位为7,个位为7,求100内哪些数字敲桌子
	for i := 1; i < 100; i++ {
		if i%7 == 0 || i/10 == 7 || i%10 == 7 {
			fmt.Println("敲桌子!")
		}else {
			fmt.Println(i)
		}
	}
}

6.2.3 水仙花数

func main() {
	// 水仙花数,一个三位数,各个位数的立方和等于这个数本身
	for i := 100; i <= 999; i++ {
		// 抽出百位
		a := i / 100
		// 抽出十位
		b := i / 10 % 10 // b:= i % 100 / 10
		// 抽出个位
		c := i % 10
		if a*a*a+b*b*b+c*c*c == i {
			fmt.Println(i)
		}
	}
}

6.2.4 九九乘法表

func main() {
	// 九九乘法表
	
	// i 控制有几行
	for i := 1; i <= 9; i++ {
		// j 控制每行有几个
		for j := 1; j <= i; j++ {
			fmt.Printf("%d * %d = %d\t", i, j, i*j)
		}
		fmt.Println()
	}
}

6.2.5 打印等腰三角形 ▲

func main() {
	// 打印等腰三角形
	line := 5

	// i 控制有几行
	for i := 0; i < line; i++ {
		// j 控制每行有几个左空格,行数越大空格越少,所以减 i
		for j := 0; j < line-i-1; j++ {
			fmt.Print(" ")
		}
		// k 控制每行有几个星星,i*2+1可以得到1 3 5 7 9
		for k := 0; k < i*2+1; k++ {
			fmt.Print("*")
		}
		// l 控制右边的空格,规则同 j ,其实可以不写
		for l := 0; l < line-i-1; l++ {
			fmt.Print(" ")
		}

		fmt.Println()
	}
}

6.2.6 百钱百鸡

package main

import "fmt"

/*
中国古代数学家张丘建在他的《算经》中提出了一个著名的“百钱百鸡问题”:
一只公鸡值五钱,一只母鸡值三钱,三只小鸡值一钱,
现在要用百钱买百鸡,请问公鸡、母鸡、小鸡各多少只?

cock 公鸡,hen 母鸡,chick 小鸡
*/

func main() {
	// 统计执行次数
	count := 0
	// 公鸡最多买20只
	for cock := 0; cock <= 20; cock++ {
		// 母鸡最多买33只
		for hen := 0; hen <= 33; hen++ {
			// 小鸡最多100只
			for chick := 0; chick <= 100; chick += 3 {
				// 不管有没有算出来都算执行一次
				count++
				// 百鸡 并且 百钱,因为小鸡每次都买三只所以除以三,代表几个三就是几钱
				if cock+hen+chick == 100 && cock*5+hen*3+chick/3 == 100 {
					fmt.Printf("公鸡:%d,母鸡:%d,小鸡:%d\n", cock, hen, chick)
				}
			}
		}
	}
	// 24276次
	fmt.Printf("执行次数:%d\n", count)
}

// 对于循环优化,最主要的优化就是减少循环次数
func main02() {
	// 统计执行次数
	count := 0
	// 公鸡最多买20只
	for cock := 0; cock <= 20; cock++ {
		// 母鸡最多买33只
		for hen := 0; hen <= 33; hen++ {
			// 小鸡的个数等于 100-公鸡-母鸡
			chick := 100 - cock - hen
			// 不管有没有算出来都算执行一次
			count++
			// 小鸡必须三的倍数,百鸡 并且 百钱,因为小鸡每次都买三只所以除以三,代表几个三就是几钱
			if chick%3 == 0 && cock+hen+chick == 100 && cock*5+hen*3+chick/3 == 100 {
				fmt.Printf("公鸡:%d,母鸡:%d,小鸡:%d\n", cock, hen, chick)
			}
		}
	}
	// 714次
	fmt.Printf("执行次数:%d\n", count)
}

6.2.7 跳出语句

// break
func main0901() {
	i := 0
	// 没有参数的for是死循环
	for {
		// 有些程序循环中,不知道程序执行次数,只有条件满足时程序停止
		if i == 5 {
			// 跳出语句跳出当前循环
			break
		}
		i++
	}
}

// continue
func main0902() {
	for i := 0; i <= 5; i++ {
		if i == 3 {
			// 结束本次循环,继续下次循环
			// 如果在程序中入到continue后剩余代码不会执行,会回到循环的位置
			continue
		}
		fmt.Println(i)
	}
}

// goto 尽量少用
func main() {
	fmt.Println(1)
	// 如果在代码中入到goto,会跳到所定义的标志位
	// 可以在一个循环中跳到另外一个循环中,可以在一个函数中跳到另外一个函数中
	goto FLAG1
	fmt.Println(2)
	fmt.Println(3)
	fmt.Println(4)
	fmt.Println(5)
FLAG1:
	fmt.Println("标志位下方不能也是标注位,所以打了这句话输出!")

	//死循环
FLAG2:
	fmt.Println(6)
	goto FLAG2
	fmt.Println(7)
}

七、函数

7.1 函数定义

  • 函数只能定义一次, 在整个项目中函数名是唯一的,不能重名
  • 在函数调用时参数为实际参数(实参)有具体的值 用来给形式参数(形参)传递数据
func 函数名(参数列表)(返回值列表){
    代码体
}

7.2 不定参函数

  • ...不定参,在函数调用时可以传递不定量(0-n)的参数
  • 不定参使用数据格式为切片
package main

import "fmt"

func main() {
	fmt.Println(sum1(1, 2, 3))
	fmt.Println(sum2(1, 2, 3))

}

// 一个求和的函数
func sum1(array ...int) int {
	// 下标是从0开始的,为了减少循环次数,设置默认值是下标0
	sum := array[0]
	// 通过array[下标]可以找到具体数据的值
	for i := 1; i < len(array); i++ {
		sum += array[i]
	}
	return sum
}

func sum2(array ...int) int {
	sum := 0
	// 通过for循环遍历集合中的数据
	//i -> index 下标 v -> value 值,如果数据的值不需要接收,可以通过匿名变量 _ 来接收数据
	for _, v := range array {
		sum += v
	}
	return sum
}

7.3 函数嵌套调用

  • 函数内部可以调用其他已定义好的函数
  • 函数调用时传递的参数为多个时,不定参...要写在其他参数后面
  • 不能将不定参的名称传递给另外一个不定参
package main

import (
	"fmt"
)

func main() {
	// test2在调用test1时,指明了传递4个数字,所以调用的时候要输入大于4个的参数列表
	test2(1,2,3,4,5)
}

// 底层函数
func test1(array ...int) {
	fmt.Println(array)
}

// 函数嵌套
func test2(array ...int)  {
	//传递指定个数的数据,4个数据
	test1(array[0:4]...)
}

7.4 函数命名返回值

  • 推荐使用函数命名返回值,可以减少生成的汇编代码量
  • return 表示函数的结束,如果函数有返回值 return,可以将返回值返回
  • 函数有多个返回值 要一一对应接收数据
package main

import "fmt"

// 最常规的返回值,只有一个 int
func test1(a, b int) int {
	return a + b
}

// 推荐使用,提前定义好返回值变量,注意此变量只能在函数内部使用
func test2(a, b int) (sum int) {
	sum = a + b
	return
}

// 推荐使用,多返回值
func test3(a, b int) (sum, sub int) {
	sum = a + b
	sub = a - b
	return
}

func main() {
	fmt.Println(test1(10, 20))
	fmt.Println(test2(10, 20))
	fmt.Println(test3(10, 20))
}

7.5 函数类型

  • 函数也有它的类型,称之为函数类型
  • 同样函数类型也可以使用 type 关键字为其取别名
package main

import "fmt"

// 此函数类型是 func(int, int)
func demo1(a, b int) {
	fmt.Println(a + b)
}

// 此函数类型是 func(int, int) int
func demo2(a, b int) int {
	return a + b
}

// 此函数类型是 func(int, int) (int, int)
func demo3(a, b int) (sum, sub int) {
	sum = a + b
	sub = a - b
	return
}

func main() {
	// 打印函数类型
	fmt.Printf("%T\n", demo1) // func(int, int)
	fmt.Printf("%T\n", demo2) // func(int, int) int
	fmt.Printf("%T\n", demo3) // func(int, int) (int, int)

	// 函数的名字表示一个地址,函数的地址在代码区
	fmt.Println(demo1) // 0x492d10

	// 可以用一个变量来表示函数,此变量的类型,就是函数类型 func(int, int)
	f := demo2
	fmt.Println(f(10,20))  // 30

	// 定义函数类型,为已存在的数据类型起别名,func(int, int) int 类型,系统早已内置
	type FUNCDEMO func(int, int) int
	// 定义 f2 变量,使用类型别名
	var f2 FUNCDEMO
	f2 = demo2
	fmt.Printf("%T\n", f2) // main.FUNCDEMO
}

7.6 函数的作用域

  • 首字母大写的函数为公有函数,可在包之外被调用
  • 首字母小写的函数为私有函数,仅可在包之内调用

7.6.1 局部变量

  • 定义在函数内部的为局部变量,只能在函数内部使用
  • 为了临时保存数据需要在函数中定义变量来进行存储,这就是它的作用

7.6.2 全局变量

  • 定义在函数外部的变量,称为全局变量,作用域在整个包之内使用
    • 除非首字母大写,不然只能在当前包内使用
  • 全局变量名可以和局部变量名重名
    • go语言中会采用就进原则,如果函数内部定义局部变量和全局变量名重名,会优先使用局部变量
  • 全局变量储存在内存的数据区
  • 如果全局变量定义时有值,存储在初始化数据区 没有值存储在未初始化数据区

7.7 匿名函数

  • 匿名函数的主要作用就是实现了闭包
  • 匿名函数一般定义在函数之内
// 匿名函数在 func 后面不加函数名,尾巴多出来的小括号表示调用此函数 
func(){
    函数体
}()


// 如果想让匿名函数也拥有函数名,如下
f := func(){
    函数体
}
// 调用函数
f()


// 注意如果尾巴加了小括号即代表调用
f := func(a, b int) int {
    函数体
}(a, b)
// 此时的 f 不代表函数名,代表的是返回值
var s int = f

7.8 递归函数

  • 自己调用自己的函数,就是递归函数
  • 注意死递归比死循环更严重
package main

import "fmt"

// 计算阶乘 n! = 1 * 2 * 3 * ... * n

// 带返回值的递归函数,比较复杂但(推荐使用)
func factorial(n int) (product int) {
	if n == 1 {
		// 1的阶乘等于1,所以要返回1,不然product默认为0
		return 1
	}
	// 阶乘要一直乘,所以这里再乘与自己本身里的乘积,传进去的参数要减1
	return n * factorial(n - 1)
}


// 不带返回值的递归函数,需要借用外部全局变量
var s int = 1
func factorialDemo(n int) {
	if n == 1 {
		return
	}
	s *= n //5*4*3*2
	factorialDemo(n - 1)
}

func main() {
	// 调用第一种
	fmt.Println(factorial(10))

	// 调用第二种
	factorialDemo(10)
	fmt.Println(s)
}

八、数据格式

8.1 数组

8.1.1 数组的定义

  • 数组是一系列相同数据类型在内存中有序存储的数据集合
// 伪代码
var 数组名 [元素个数]数据类型

// 定义了10个整型变量的数组
var arr [10]int

8.1.2 数组赋值

  • 通过下标找到具体元素,数组下标是从0开始的,到数组元素个数-1位数值最大下标
// 伪代码
数组名[下标]

// 对指定下标的元素赋值
arr[0] = 123
arr[1] = 111
arr[2] = 234
  • 没有赋值的数组会存在默认值
// 如果不赋值,直接输出,结果默认全部是 0
var a [10]int

// 如果不赋值,直接输出,结果默认全部是 0
var a [10]float64

// 如果不赋值,直接输出,结果默认全部是 空字符
var a[10]string

// 如果不赋值,直接输出,结果默认全部是 false
var a [10]bool
  • 通过for循环完成数组的赋值与输出
package main

import "fmt"

func main() {
	// 对有规律的数据普通赋值方法
	var a [5]int
	a[0] = 1
	a[1] = 2
	a[2] = 3
	a[3] = 4
	a[4] = 5

	// 对有规律的数据for循环赋值,推荐使用
	var b [5]int
	for i := 0; i < len(b); i++ {
		b[i] = i + 1
	}

	// 验证赋值结果
	for i, v := range b {
		fmt.Printf("下标:%d,元素值:%d\n", i, v)
	}
}

8.1.3 数组初始化

  • 前面都是先定义数组,然后再完成数组的赋值。其实,在定义数组时,也可以完成赋值,这种情况叫做数组的初始化。
// 全部初始化
var a [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Println(a)

b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)

// 部分初始化,没有初始化的元素,自动赋值为0
c := [5]int{1, 2, 3}
fmt.Println(c)

// 指定某个元素初始化
d := [5]int{1: 10, 3: 20} // 这里面的1和3是下标
fmt.Println(d)

8.1.4 数组常见问题

  • 数组元素个数定义必须一个是常量,或是常量表达式
const i int = 5
var arr [i]int
fmt.Println(arr)
  • 数组下标越界,最大值下标:len(数组)-1
var arr [5]int
//arr[5] = 50 // err
  • 赋值类型错误,数组名只能赋值数据类型一致的数组
var arr [5]int
//arr = 123 // err

// 两个数组如果类型和元素个数相同可以赋值
arr1 := arr
fmt.Println(arr1)

8.1.5 数组的内存地址

  • 数组名表示整个数组,数组名对应的地址就是数组第一个元素的地址
arr := [5]int{1, 2, 3,4,5}

// 数组名对应的地址就是数组第一个元素的地址
fmt.Printf("%p\n",&arr) // 0xc000092030
fmt.Printf("%p\n",&arr[0]) // 0xc000092030

// 十六进制,数组每个元素的地址位置相差 8
fmt.Printf("%p\n",&arr[1]) // 0xc000092038
fmt.Printf("%p\n",&arr[2]) // 0xc000092040
fmt.Printf("%p\n",&arr[3]) // 0xc000092048
fmt.Printf("%p\n",&arr[4]) // 0xc000092050

8.1.6 数组案例

8.1.6.1 求数组中的最大值
package main

import "fmt"

func main() {
	var arr = [5]int{1, 5, 9, 3, 2}

	max := arr[0]
	for i := 1; i < len(arr); i++ {
		if arr[i] > max {
			max = arr[i]
		}
	}
	fmt.Println(max)
}
8.1.6.2 数组逆置(反转)
package main

import "fmt"

func main() {
	var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	// 预定义最小下标和最大下标
	min := 0
	max := len(arr) - 1

	for min < max {
		if min >= max {
			break
		}
		arr[min], arr[max] = arr[max], arr[min]
		min++
		// 重点:最大下标要 -- 
		max--
	}
	fmt.Println(arr)
}
8.1.6.3 冒泡排序
package main

import "fmt"

func main() {
	var arr [10]int = [10]int{9, 1, 5, 6, 8, 2, 10, 7, 4, 3}

	// 外层执行一次内层执行一周,外层控制行
	// 这里 len-1 是因为,10个数只要比较9次即可
	for i := 0; i < len(arr)-1; i++ {
		// 这里len-i 是因为每行确定一个最大数,那么已确定的就不必再比较,再-1 是因为最后一行不需要再比较
		for j := 0; j < len(arr)-i-1; j++ {
			// 如何前面大于后面,(大于号是升序,小于号是降序)
			if arr[j] > arr[j+1] {
				// 交换数据
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
	fmt.Println(arr)
}
8.1.6.4 随机数
  • 先创建随机数种子
    • 如果没有创建随机数种子称为伪随机数
    • 伪随机数使用的是 1970-01-01 00:00:00 的时间戳
  • 再创建随机数
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	// 创建随机数种子,这里使用纳秒级别的时间戳
	rand.Seed(time.Now().UnixNano())

	for i := 0; i < 10; i++ {
		// 随机生成10个 1~10 的随机数,所以+1
		fmt.Println(rand.Intn(10) + 1)
	}
}
8.1.6.5 数组去重
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	// 随机双色球彩票
	// 红色 1-33,选择6个,不能重复;蓝球 1-16,选择1个,可以和红球重复

	// 创建随机数种子,使用纳秒基本的时间戳
	rand.Seed(time.Now().UnixNano())

	// 预定义一个存放红球的数组
	var red [6]int

	// 第一层循环给数组填满6个随机数
	for i := 0; i < 6; i++ {

		red[i] = rand.Intn(33) + 1

		// 第二层循环对比,所填的随机数不能重复
		for j := 0; j < i; j++ {
			if red[i] == red[j] {
				red[i] = rand.Intn(33) + 1

				// 重点的坑,再次随机生成的数还是要检查是否重复,因此重置计数器,循环执行到上面是进行++操作后值为0
				j = -1
			}
		}
	}
	fmt.Println("红球:", red, "蓝球:", rand.Intn(16)+1)
}

8.1.7 二维数组

8.1.7.1 二维数组的定义
// 一维数组
var arr [10]int 

// 定义一个拥有2行3列的二维数组
var arr [2][3]int
8.1.7.2 二维数组赋值
// 为下标为0行1列的二维数组赋值
arr[0][1] = 123

// 为下标为1行2列的二维数组赋值
arr[1][2] = 234
8.1.7.3 二维数组初始化
package main

import "fmt"

func main() {
	// 完全初始化,两版本效果一样
	var arr1 = [2][3]int{{1, 2, 3}, {4, 5, 6}}
	arr2 := [2][3]int{{1, 2, 3}, {4, 5, 6}}
	fmt.Println(arr1)  // [[1 2 3] [4 5 6]]
	fmt.Println(arr2)  // [[1 2 3] [4 5 6]]
	
	// 部分初始化
	arr3 := [2][3]int{{1, 2}, {6}}
	fmt.Println(arr3)  // [[1 2 0] [6 0 0]]
	
	// 指定下标初始化
	arr4 := [2][3]int{1:{1, 2}}
	fmt.Println(arr4)  // [[0 0 0] [1 2 0]]
}
8.1.7.4 二维数组长度
// 一个二维数组有几行
len(二维数组名)
fmt.Println(len(arr))

// 一个二维数组有几列
len(二维数组名[下标])
fmt.Println(len(arr[0]))

8.1.8 数组作为函数参数

  • 数组属于值传递,形参不能改变实参的值,形参和实参是不同的地址单元
package main

import "fmt"

// 传递一个数组进行冒泡排序
func bubbleSort(arr [10]int) [10]int {
	for i:=0;i< len(arr)-1;i++ {
		for j:=0;j< len(arr)-i-1;j++  {
			if arr[j] > arr[j+1] {
				arr[j],arr[j+1] = arr[j+1],arr[j]
			}
		}
	}
	return arr
}


func main() {
	// 测试用的混乱数组
	arr := [10]int{9, 1, 5, 6, 8, 4, 7, 10, 3, 2}

	// 数组是值传递,需要函数的返回值才能得到结果
	fmt.Println(bubbleSort(arr))  // [1 2 3 4 5 6 7 8 9 10]
	
	// 当前数组毫无变化
	fmt.Println(arr)  // [9 1 5 6 8 4 7 10 3 2]
}

8.2 切片

  • 切片是引用类型,如果不进行扩容那么地址就不会改变

8.2.1 切片的定义

package main

import "fmt"

func main() {
	// 此方法定义的切片默认没有长度可存放值,必须使用append()追加数据
	var slice1 []int
	fmt.Println(slice1)
	
	// 使用 make(类型,长度,容量) 定于切片,容量可以不填,此方法定义的切片可以在长度范围内直接赋值
	slice2 := make([]int,3,10)
	fmt.Println(slice2)
}

8.2.2 切片赋值

func main() {
	// 定义一个切片,此切片地址是 0x0
	var slice1 []int
	
	// 常规定义只能使用append追加数据,并且会因为容量不足而修改到新的地址
	slice1 = append(slice1,1,2,3,4,5)
	fmt.Printf("%p\n",slice1)  // 0xc00009a030

	
	
	// 使用make定义一个切片,长度为5
	slice2 := make([]int, 3)
	fmt.Printf("%p\n",slice2)  // 0xc000012380
	
	// 在长度范围内添加数据,不会改变地址,超出长度会出现切片下标越界错误
	slice2[0] = 1
	
	// 使用append属于追加数据,容量不足的情况下会改变地址
	slice2 = append(slice2,2,3)
	fmt.Println(slice2)  // [1 0 0 2 3]
	fmt.Printf("%p\n",slice2)  // 0xc00009a060
}

8.2.3 切片初始化

  • 初始化就得定义类型的同时进行赋值
func main() {
	// 普通方式定义切片
	var slice1 = []int{1,2,3}
	fmt.Println(slice1)
	
	// 通过自动推导类型创建切片
	slice2 :=[]int{1,2,3}
	fmt.Println(slice2)
}

8.2.4 切片的地址和容量

  • 创建的空切片,指向内存地址为 0x0
  • 切片使用append进行追加数据时,如果容量足够也不会改变地址
  • 切片自动扩容是以之前容量2倍扩容,当达到1024字节的时候以之前容量大约1.25倍扩容
len(slice)  计算切片的长度
cap(slice)  计算切片的容量

8.2.5 切片的截取

  • 截取后的切片还是原始切片中的一块内容,如果修改截取后的切片,影响原始切片的值
package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	// 切片的截取是左闭右开方式,因此就等于截取包括端点的下标3到下标6的内容
	s1 := slice[3:7]
	fmt.Println(s1) // [4 5 6 7]

	// 从下标2位置到结束
	s2:=slice[2:]
	fmt.Println(s2) // [3 4 5 6 7 8 9 10]

	// 从起始位置到下标5
	s3:=slice[:5]
	fmt.Println(s3) // [1 2 3 4 5]

	// 截取全部,等同于 s4:=slice
	s4:=slice[:]
	fmt.Println(s4) // [1 2 3 4 5 6 7 8 9 10]

	// 切片名[low:high:max],容量=max-low
	// 截取左闭右开,3-6下标的数据,并指定容量为10-3
	s5:=slice[3:7:10]
	fmt.Println(s5)  // [4 5 6 7]
	

	// 多出的容量可以为当前切片增加长度,这里所增加的长度使用原始切片的内存空间,因此也会修改原始切片的值
	s5 = append(s5,0)
	fmt.Println(s5)  // [4 5 6 7 0]
	// s5因为有多出的容量,所以append后原始数据被修改,如果超出容量将会重新分配新的内存地址不会修改原始切片的值
	fmt.Println(slice)  // [1 2 3 4 5 6 7 0 9 10]
}

8.2.6 切片的拷贝

  • 在进行拷贝时需要预定义一个切片用于存放拷贝的元素
  • 必须使用make创建长度足够的切片,即使长度不够也不会报错,只会末尾切除
package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5}

	// 在进行拷贝时需要预定义一个切片用于存放,必须使用make创建长度足够的切片,即使长度不够也不会报错,只会末尾切除
	s := make([]int, 4)
	copy(s, slice)

	fmt.Println(s)  // [1 2 3 4]
}

8.2.7 切片的排序

  • 切片的排序与数组一样,尽量使用切片少用数组
package main

import "fmt"

func main() {
	var slice = []int{9, 1, 5, 6, 8, 3, 7, 2, 10, 4}

	// 外层控制行
	for i := 0; i < len(slice)-1; i++ {
		// 内层控制列
		for j := 0; j < len(slice)-i-1; j++ {
			if slice[j] > slice[j+1] {
				slice[j], slice[j+1] = slice[j+1], slice[j]
			}
		}
	}
	fmt.Println(slice)  // [1 2 3 4 5 6 7 8 9 10]
}

8.2.8 切片作为函数参数

  • 切片名本身就是一个地址,地址传递,形参可以改变实参
package main

import "fmt"


// 切片的冒泡排序
func bubbleSort(slice []int) {
	for i:=0; i<len(slice)-1;i++  {
		for j:=0; j<len(slice)-i-1;j++  {
			if slice[j]>slice[j+1] {
				slice[j],slice[j+1] = slice[j+1],slice[j]
			}
		}
	}
}

func main() {
	//切片名本身就是一个地址
	slice := []int{9, 1, 5, 6, 8, 4, 7, 10, 3, 2}

	// 因为切片是地址传递,所以这里调用函数将会改变原始切片的值
	bubbleSort(slice)

	fmt.Println(slice)  // [1 2 3 4 5 6 7 8 9 10]
}
  • 如果是函数中使用append进行数据添加时,形参的地址改变就不会在指向实参的地址
func test(slice []int)  {
	slice = append(slice,6,7)
	fmt.Println(slice)
}

func main() {
	slice := []int{1, 2, 3, 4, 5}

	// 这个函数使用了 append 追加数据,由于容量不足而改变了内存地址
	test(slice)  // [1 2 3 4 5 6 7]
	
	// 因此形参不会改成实参
	fmt.Println(slice)  // [1 2 3 4 5]
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值