教程 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语言中,主函数所在的包一定是mainpackage main//导入系统包,标准输入输出包import \"fmt\"// func 函数格式// main 函数名,main是主函数,程序有且只有一个主函数,无论程序中有多少函数,都会从main进入// () 函数参数列表// {} 函数体,也可以称作代码体或程序体func main() {// fmt包,Println 打印并且换行 \"\" 引起来的称为字符串fmt.Println(\"hello world!\")}
二、变量
2.1 变量定义和使用
- 在Golang中,定义的变量必须使用
package mainimport (\"fmt\"\"math\")func main01() {// 定义格式:var 变量名 数据类型 = 值// int 表示整型数据var sum int = 100// 变量在程序运行过程中,值可以发生改变sum = sum + 50fmt.Println(sum)}func main02() {// 变量的声明,如果没有赋值,默认值为0var sum int// 为变量赋值sum = 50fmt.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 mainimport \"fmt\"func main() {// 传统的变量定义方式// 不同的数据类型在内存中开辟的空间不同// var a int = 10// var b float64 = 123.456// 第一种自动推导类型// var a = 10// var b = 123.456// 第二种自动推导类型(推荐)a := 10b := 123.456// 不同的数据类型不能计算// c := a + b // errfmt.Println(a)fmt.Println(b)}
2.3 交换变量
package mainimport \"fmt\"func main() {//交换两个变量的值a := 10b := 20// 第一种使用第三变量进行交换// c := a// a = b// b = c// 第二种通过运算进行交换// a = a + b// b = a - b// a = a - b// 第三种通过多重赋值进行交换(推荐)a, b = b, afmt.Println(a)fmt.Println(b)}
2.4 多重赋值和匿名变量
package mainimport \"fmt\"func main01() {// 多重赋值// 变量个数和值的个数要一一对应a, b, c := 10, 123.456, \"golang\"fmt.Println(a)fmt.Println(b)fmt.Println(c)}func main02() {var a int = 10var b int = 20// 在一个作用域范围内变量名不能重复// var a = 100// 使用多重赋值修改变量//a, b = 100, 200// 多重赋值时如果有新定义的变量,可以使用自动推导类型,加个冒号a, b, c, d := 100, 200, \"hello\", \"golang\"// 没有定义新变量时不能使用自动推导类型//a, b := 100, 200 // errfmt.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 mainimport \"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 mainimport \"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 intvar b string// 带format格式化的,在接收字符串时只能用空格作为分割fmt.Scanf(\"%d%s\", &a, &b)fmt.Printf(\"%d\", a)fmt.Printf(\"%s\", b)}func main() {//通过键盘输入学生三门成绩计算总成绩和平均成绩var c, m, e intfmt.Scan(&c, &m, &e)sum := c + m + efmt.Println(\"总成绩:\", sum)// 两个整型数据相除 得到的结果也是整型fmt.Println(\"平均成绩:\", sum/3)}
2.7 变量命名规范
- 允许使用字母、数字、下划线
- 不允许使用go系统关键字
- 不允许使用数字开头
- 区分大小写
- 见名知义
三、基础数据类型
3.1 数据类型汇总
- 引用类型目前只知道有3个,切片、字典、通道。
类型 | 名称 | 长度(字节) | 默认值 | 范围 | 说明 |
---|---|---|---|---|---|
bool | 布尔类型 | 1 | false | 其值不为真即为假,不可以用数字代表true或false | |
byte | 字节类型 | 1 | 0 | 0 ~ 255 | uint8的别名 |
rune | 字符类型 | 4 | 0 | -2147483648 ~ 2147483647 | int32的一个别名,主要用于表示utf-8编码时的字符类型 |
uintptr | 无符号整型 | 4或8 | 无符号整型,用于存放一个指针 | ||
uint | 无符号整型 | 4或8 | 0 | 32位系统等于int32,64位系统等于int64 | |
uint8 | 无符号整型 | 1 | 0 | 0 ~ 255 | |
uint16 | 无符号整型 | 2 | 0 | 0 ~ 65535 | |
uint32 | 无符号整数类型 | 4 | 0 | 0 ~ 4294967295 | 小数位精确到7位 |
uint64 | 无符号整型 | 8 | 0 | 0 ~ 18446744073709551615 | 小数位精确到15位 |
int | 整型 | 4或8 | 0 | 32位系统等于int32,64位系统等于int64 | |
int8 | 整型 | 1 | 0 | -128 ~ 127 | |
int16 | 整型 | 2 | 0 | -32768 ~ 32767 | |
int32 | 整型 | 4 | 0 | -2147483648 ~ 2147483647 | |
int64 | 整型 | 8 | 0 | -9223372036854775808 ~ 9223372036854775807 | |
float32 | 单精度浮点型 | 4 | 0.0 | ||
float64 | 双精度浮点型 | 8 | 0.0 | ||
complex64 | 浮点类型 | 8 | 32 位实数和虚数 | ||
complex128 | 浮点类型 | 16 | 64 位实数和虚数 | ||
array | 数组值类型 | ||||
struct | 结构体值类型 | ||||
string | 字符串值类型 | “” | UTF-8 字符串 | ||
slice | 切片 | nil | 引用类型 | ||
map | 字典 | nil | 引用类型 | ||
channel | 通道 | nil | 引用类型 | ||
interface | 接口 | nil | |||
function | 函数 | nil |
3.2 bool 布尔类型
- 默认值为false
- bool类型一般用于条件判断
package mainimport \"fmt\"func main() {var a boolfmt.Println(a) // 默认值为falseb := truefmt.Printf(\"%T\",b) // 输出一个变量对应的数据类型}
3.3 int 整数类型
- int类型会根据操作系统位数不同在内存中占的字节也不同64位系统就是int64
- 32位系统就是int32
- 64位系统中的int不能直接与int64计算,需要类型装换才可计算
- 等于 -2^63 ~ 2^63-1
- 等于 -2^64+1 ~ 0
3.4 float 浮点数类型
- float32 仅小数点后5位是精准的
- float64 仅小数点后13位是精准的
- 自动推导类型创建的浮点型变量,默认类型为float64
package mainimport \"fmt\"func main() {var a float32 = 123.456fmt.Printf(\"%.5f\",a) // 小数点后5位之后不再精准var b float64 = 123.456fmt.Printf(\"\\n%.13f\",b) // 小数点后13位之后不再精准c := 3.14fmt.Printf(\"\\n%T\", c) // 自动推导类型是float64(双精度)}
3.5 byte 字符类型
- 类型 byte 是 uint8 的别名
- 需要背诵的ASCII编码值空格
space
对应值为
32
- 字符
\'0\'
对应值为
48
- 字符
\'A\'
对应值为
65
- 字符
\'a\'
对应值为
97
package mainimport \"fmt\"func main() {var abc byte = \'a\' // 定义变量abc的值为字符afmt.Println(abc) // 直接打印是97fmt.Printf(\"%c\\n\", abc) // 必须使用占位符 %c 输出一个字符// 其实类型 byte 是 uint8 的别名fmt.Printf(\"%T\\n\", abc) // 输出结果为 uint8// 根据ASCII编码表,将小写字符 a 转换成大写字符 Afmt.Printf(\"%c\", abc-32) // 输出结果为 A}
3.6 string 字符串类型
3.6.1 字符串定义
- 双引号引起来的称为字符串
- 在go语言中一个汉字占3个字符,为了和linux进行统一处理
package mainimport \"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 | 计算字符串
substr 在 s 中的非重叠个数。如果 substr 为空串则返回 s 中的字符(非字节)个数 +1 。 |
02 | 重复 | Repeat(s string, count int) string | 将
count 个字符串 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) string | 将
s 中满足 mapping(rune) 的字符替换为 mapping(rune) 的返回值。如果 mapping(rune) 返回负数,则相应的字符将被删除。 |
26 | 切割 | Split(s, sep string) []string | 以
sep 为分隔符,将 s 切分成多个子切片,结果中不包含 sep 本身。如果 sep 为空,则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为[]string的第一个元素返回。 |
27 | 切割 | SplitN(s, sep string, n int) []string | 以
sep 为分隔符,将 s 切分成多个子串,结果中不包含 sep 本身。如果 sep 为空则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为 []string 的第一个元素返回。参数 n 表示最多切分出几个子串,超出的部分将不再切分,最后一个 n 包含了所有剩下的不切分。如果 n 为 0 ,则返回 nil ;如果 n 小于 0 ,则不限制切分个数,全部切分。 |
28 | 切割 | SplitAfter(s, sep string) []string | 以
sep 为分隔符,将 s 切分成多个子切片,结果中包含 sep 本身。如果 sep 为空,则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为 []string 的第一个元素返回。 |
29 | 切割 | SplitAfterN(s, sep string, n int) []string | 以
str 为分隔符,将 s 切分成多个子串,结果中包含 sep 本身。如果 sep 为空,则将 s 切分成 Unicode 字符列表。如果 s 中没有 sep 子串,则将整个 s 作为 []string 的第一个元素返回。参数 n 表示最多切分出几个子串,超出的部分将不再切分。如果 n 为 0 ,则返回 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) string | 将
a 中的子串连接成一个单独的字符串,子串之间用 sep 分隔。 |
33 | 转换 | ToUpper(s string) string | 将
s 中的所有字符修改为其大写格式。对于非 ASCII 字符,它的大写格式需要查表转换。 |
34 | 转换 | ToLower(s string) string | 将
s 中的所有字符修改为其小写格式。对于非 ASCII 字符,它的小写格式需要查表转换。 |
35 | 转换 | ToTitle(s string) string | 将
s 中的所有字符修改为其 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 mainimport \"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 mainimport (\"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 mainimport (\"fmt\"\"strconv\")func main() {// string 转为 inti, _ := 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后使用FormatIntfmt.Println(s2)}
3.6.3.3 string 与 float 互转
3.7 类型转换
- 类型转换格式
数据类型(变量)数据类型(表达式)
- 在类型转换时建议低类型转成高类型 保证数据精度
- 建议整型转成浮点型
- 高类型转成低类型,可能会丢失精度,或者数据溢出,符号发生变化
- 将浮点型转成整型,保留数据整数部分,丢弃小数部分
package mainimport \"fmt\"func main01() {a, b, c := 10, 20, 40sum := 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 mainfunc main() {// 常量的定义,一般定义常量使用大写字母const MAX int = 10//go语言常量的地址,不允许访问// fmt.Println(&MAX) // err}
4.2 字面常量
- 所谓字面常量(literal),是指程序中硬编码的常量,如:
// 这里面 1 和 5 都是字面常量fmt.Println(1+5)
4.3 iota 枚举
package mainimport \"fmt\"func main01() {// iota枚举格式如果写在一行中值相等,如果换行值在上一行加1const (a = iotab, c = iota, iota)fmt.Println(a) // 0fmt.Println(b) // 1fmt.Println(c) // 1}func main02() {// 只需要对第一个进行iota赋值,后面会依次增长const (a = iotabcd)fmt.Println(a) // 0fmt.Println(b) // 1fmt.Println(c) // 2fmt.Println(d) // 3}func main() {// 在定义iota枚举时可以自定义赋值const (a = iotab = 10c = 20def = iotag)fmt.Println(a) // 0fmt.Println(b) // 10fmt.Println(c) // 20fmt.Println(d) // 20fmt.Println(e) // 20fmt.Println(f) // 5fmt.Println(g) // 6}
五、运算符
5.1 算数运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
+ | 加 | 10 + 5 | 15 |
– | 减 | 10 – 5 | 5 |
* | 乘 | 10 * 5 | 50 |
/ | 除 | 10 / 5 | 2 |
% | 取模(取余) | 10 % 3 | 1 |
++ | 后自增,没有前自增 | a=0; a++ | a=1 |
– | 后自减,没有前自减 | a=2; a– | a=1 |
package mainimport \"fmt\"func main01() {a := 10b := 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 := 10b := 0.5a++// 可以对浮点型进行自增自减运算b++fmt.Println(a) // 11fmt.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 mainimport \"fmt\"func main() {var c int = 10// 将表达式右侧进行结果计算在进行赋值运算符c %= 2 + 3//c = c % 5 // ok//c = c % 2 + 3 //errfmt.Println(c)}
取余经典练习题
- 107653秒,等于几天几小时几分几秒
func main() {//107653秒,等于几天几小时几分几秒s := 107653fmt.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 == 1 | false |
!= | 不等于 | 2 != 1 | true |
< | 小于 | 2 < 1 | false |
> | 大于 | 2 > 1 | true |
<= | 小于等于 | 2 <= 1 | false |
>= | 大于等于 | 2 >= 1 | true |
5.4 逻辑运算符
- 逻辑与优先级高于逻辑或
运算符 | 术语 | 示例 | 结果 | 八字口诀 |
---|---|---|---|---|
&& | 与 | a && b | 如果a和b都为真,则结果为真,否则为假。 | 同真为真,其余为假 |
|| | 或 | a || b | 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。 | 同假为假,其余为真 |
! | 非 | !a | 如果a为假,则!a为真;如果a为真,则!a为假。 | 非真为假,非假为真 |
5.5 其他运算符
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
& | 取地址运算符 | &a | 变量a的地址 |
* | 取值运算符 | *a | 指针变量a所指向内存的值 |
package mainimport \"fmt\"func main() {a := 10// & 取地址运算符fmt.Println(&a) // 0xc00000a0b8// p 定义为指针变量,所以 p 携带的是地址信息p := &afmt.Println(p) // 0xc00000a0b8// 获取指针变量 p, 内存地址所对应的值fmt.Println(*p) // 10// 既然通过*p可以得到指针变量的值,那就可以间接修改变量的值*p = 123fmt.Println(*p) // 123}
5.6 运算符优先级
大类 | 小类 | 优先级 | 运算符 | 说明 |
---|---|---|---|---|
最高 | () | 小括号 | ||
二级 | [] | 数组切片下标 | ||
三级 | . | 结构体.成员;包.函数;对象.方法 | ||
单目运算符 | 逻辑运算符 | 四级 | ! | 逻辑非 |
单目运算符 | 算数运算符 | 四级 | ++ | 自增 |
单目运算符 | 算数运算符 | 四级 | – | 自减 |
单目运算符 | 四级 | & | 取地址(引用) | |
单目运算符 | 四级 | * | 取值(指针) | |
双目运算符 | 算数运算符 | 五级 | * | 乘法 |
双目运算符 | 算数运算符 | 五级 | / | 除法 |
双目运算符 | 算数运算符 | 五级 | % | 取余 |
双目运算符 | 算数运算符 | 六级 | + | 加法 |
双目运算符 | 算数运算符 | 六级 | – | 减法 |
双目运算符 | 比较运算符 | 七级 | > | 大于 |
双目运算符 | 比较运算符 | 七级 | < | 小于 |
双目运算符 | 比较运算符 | 七级 | >= | 大于等于 |
双目运算符 | 比较运算符 | 七级 | <= | 小于等于 |
双目运算符 | 比较运算符 | 七级 | == | 相等于 |
双目运算符 | 比较运算符 | 七级 | != | 不等于 |
双目运算符 | 逻辑运算符 | 八级 | && | 逻辑与 |
双目运算符 | 逻辑运算符 | 九级 | || | 逻辑或 |
双目运算符 | 赋值运算符 | 最低 | = | 普通赋值 |
双目运算符 | 赋值运算符 | 最低 | += | 相加后再赋值 |
双目运算符 | 赋值运算符 | 最低 | -= | 相减后再赋值 |
双目运算符 | 赋值运算符 | 最低 | *= | 相乘后再赋值 |
双目运算符 | 赋值运算符 | 最低 | /= | 相除后再赋值 |
双目运算符 | 赋值运算符 | 最低 | %= | 求余后再赋值 |
六、流程控制
6.1 选择结构
6.1.1 if 结构
- 语法格式
if 条件判断 {代码体}
- 条件判断如果为真(true),那么就执行大括号中的语句,如果为假(false),就不执行大括号中的语句
package mainimport \"fmt\"func main() {var age intfmt.Print(\"请输入你年龄:\")fmt.Scan(&age)if age >= 18 {fmt.Println(\"恭喜成年了哦!\")}}
- Golang独有 if 的另外一种语法格式
package mainimport \"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 mainimport \"fmt\"func main() {var age intfmt.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 mainimport \"fmt\"func main() {var age, money intfmt.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 mainimport \"fmt\"// 根据输入的年份月份,计算这个月有多少天func main() {var y, m intfmt.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 执行效率低
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 mainimport \"fmt\"func main() {// 计算1-100的和sum1 := 0for i := 1; i <= 100; i++ {sum1 += i}fmt.Println(sum1)// 计算1-100偶数的和,在for语句中嵌套if条件判断sum2 := 0for i := 1; i <= 100; i++ {if i%2 == 0 {sum2 += i}}fmt.Println(sum2)// 计算1-100偶数的和,减少循环次数提高效率sum3 := 0for i := 0; i <= 100; i += 2 {sum3 += i}fmt.Println(sum3)}
6.2.2 喝酒敲七游戏
package mainimport \"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 % 10if 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 控制每行有几个左空格,行数越大空格越少,所以减 ifor j := 0; j < line-i-1; j++ {fmt.Print(\" \")}// k 控制每行有几个星星,i*2+1可以得到1 3 5 7 9for 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 mainimport \"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 跳出语句
// breakfunc main0901() {i := 0// 没有参数的for是死循环for {// 有些程序循环中,不知道程序执行次数,只有条件满足时程序停止if i == 5 {// 跳出语句跳出当前循环break}i++}}// continuefunc main0902() {for i := 0; i <= 5; i++ {if i == 3 {// 结束本次循环,继续下次循环// 如果在程序中入到continue后剩余代码不会执行,会回到循环的位置continue}fmt.Println(i)}}// goto 尽量少用func main() {fmt.Println(1)// 如果在代码中入到goto,会跳到所定义的标志位// 可以在一个循环中跳到另外一个循环中,可以在一个函数中跳到另外一个函数中goto FLAG1fmt.Println(2)fmt.Println(3)fmt.Println(4)fmt.Println(5)FLAG1:fmt.Println(\"标志位下方不能也是标注位,所以打了这句话输出!\")//死循环FLAG2:fmt.Println(6)goto FLAG2fmt.Println(7)}
七、函数
7.1 函数定义
- 函数只能定义一次, 在整个项目中函数名是唯一的,不能重名
- 在函数调用时参数为实际参数(实参)有具体的值 用来给形式参数(形参)传递数据
func 函数名(参数列表)(返回值列表){代码体}
7.2 不定参函数
-
...
不定参,在函数调用时可以传递不定量(0-n)的参数
- 不定参使用数据格式为切片
package mainimport \"fmt\"func main() {fmt.Println(sum1(1, 2, 3))fmt.Println(sum2(1, 2, 3))}// 一个求和的函数func sum1(array ...int) int {// 下标是从0开始的,为了减少循环次数,设置默认值是下标0sum := 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 mainimport (\"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 mainimport \"fmt\"// 最常规的返回值,只有一个 intfunc test1(a, b int) int {return a + b}// 推荐使用,提前定义好返回值变量,注意此变量只能在函数内部使用func test2(a, b int) (sum int) {sum = a + breturn}// 推荐使用,多返回值func test3(a, b int) (sum, sub int) {sum = a + bsub = a - breturn}func main() {fmt.Println(test1(10, 20))fmt.Println(test2(10, 20))fmt.Println(test3(10, 20))}
7.5 函数类型
- 函数也有它的类型,称之为函数类型
- 同样函数类型也可以使用
type
关键字为其取别名
package mainimport \"fmt\"// 此函数类型是 func(int, int)func demo1(a, b int) {fmt.Println(a + b)}// 此函数类型是 func(int, int) intfunc demo2(a, b int) int {return a + b}// 此函数类型是 func(int, int) (int, int)func demo3(a, b int) (sum, sub int) {sum = a + bsub = a - breturn}func main() {// 打印函数类型fmt.Printf(\"%T\\n\", demo1) // func(int, int)fmt.Printf(\"%T\\n\", demo2) // func(int, int) intfmt.Printf(\"%T\\n\", demo3) // func(int, int) (int, int)// 函数的名字表示一个地址,函数的地址在代码区fmt.Println(demo1) // 0x492d10// 可以用一个变量来表示函数,此变量的类型,就是函数类型 func(int, int)f := demo2fmt.Println(f(10,20)) // 30// 定义函数类型,为已存在的数据类型起别名,func(int, int) int 类型,系统早已内置type FUNCDEMO func(int, int) int// 定义 f2 变量,使用类型别名var f2 FUNCDEMOf2 = demo2fmt.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 mainimport \"fmt\"// 计算阶乘 n! = 1 * 2 * 3 * ... * n// 带返回值的递归函数,比较复杂但(推荐使用)func factorial(n int) (product int) {if n == 1 {// 1的阶乘等于1,所以要返回1,不然product默认为0return 1}// 阶乘要一直乘,所以这里再乘与自己本身里的乘积,传进去的参数要减1return n * factorial(n - 1)}// 不带返回值的递归函数,需要借用外部全局变量var s int = 1func factorialDemo(n int) {if n == 1 {return}s *= n //5*4*3*2factorialDemo(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] = 123arr[1] = 111arr[2] = 234
- 没有赋值的数组会存在默认值
// 如果不赋值,直接输出,结果默认全部是 0var a [10]int// 如果不赋值,直接输出,结果默认全部是 0var a [10]float64// 如果不赋值,直接输出,结果默认全部是 空字符var a[10]string// 如果不赋值,直接输出,结果默认全部是 falsevar a [10]bool
- 通过for循环完成数组的赋值与输出
package mainimport \"fmt\"func main() {// 对有规律的数据普通赋值方法var a [5]inta[0] = 1a[1] = 2a[2] = 3a[3] = 4a[4] = 5// 对有规律的数据for循环赋值,推荐使用var b [5]intfor 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)// 部分初始化,没有初始化的元素,自动赋值为0c := [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 = 5var arr [i]intfmt.Println(arr)
- 数组下标越界,最大值下标:len(数组)-1
var arr [5]int//arr[5] = 50 // err
- 赋值类型错误,数组名只能赋值数据类型一致的数组
var arr [5]int//arr = 123 // err// 两个数组如果类型和元素个数相同可以赋值arr1 := arrfmt.Println(arr1)
8.1.5 数组的内存地址
- 数组名表示整个数组,数组名对应的地址就是数组第一个元素的地址
arr := [5]int{1, 2, 3,4,5}// 数组名对应的地址就是数组第一个元素的地址fmt.Printf(\"%p\\n\",&arr) // 0xc000092030fmt.Printf(\"%p\\n\",&arr[0]) // 0xc000092030// 十六进制,数组每个元素的地址位置相差 8fmt.Printf(\"%p\\n\",&arr[1]) // 0xc000092038fmt.Printf(\"%p\\n\",&arr[2]) // 0xc000092040fmt.Printf(\"%p\\n\",&arr[3]) // 0xc000092048fmt.Printf(\"%p\\n\",&arr[4]) // 0xc000092050
8.1.6 数组案例
8.1.6.1 求数组中的最大值
package mainimport \"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 mainimport \"fmt\"func main() {var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}// 预定义最小下标和最大下标min := 0max := len(arr) - 1for min < max {if min >= max {break}arr[min], arr[max] = arr[max], arr[min]min++// 重点:最大下标要 --max--}fmt.Println(arr)}
8.1.6.3 冒泡排序
package mainimport \"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 mainimport (\"fmt\"\"math/rand\"\"time\")func main() {// 创建随机数种子,这里使用纳秒级别的时间戳rand.Seed(time.Now().UnixNano())for i := 0; i < 10; i++ {// 随机生成10个 1~10 的随机数,所以+1fmt.Println(rand.Intn(10) + 1)}}
8.1.6.5 数组去重
package mainimport (\"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// 重点的坑,再次随机生成的数还是要检查是否重复,因此重置计数器,循环执行到上面是进行++操作后值为0j = -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 mainimport \"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 mainimport \"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 mainimport \"fmt\"func main() {// 此方法定义的切片默认没有长度可存放值,必须使用append()追加数据var slice1 []intfmt.Println(slice1)// 使用 make(类型,长度,容量) 定于切片,容量可以不填,此方法定义的切片可以在长度范围内直接赋值slice2 := make([]int,3,10)fmt.Println(slice2)}
8.2.2 切片赋值
func main() {// 定义一个切片,此切片地址是 0x0var slice1 []int// 常规定义只能使用append追加数据,并且会因为容量不足而修改到新的地址slice1 = append(slice1,1,2,3,4,5)fmt.Printf(\"%p\\n\",slice1) // 0xc00009a030// 使用make定义一个切片,长度为5slice2 := 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 mainimport \"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]// 从起始位置到下标5s3:=slice[:5]fmt.Println(s3) // [1 2 3 4 5]// 截取全部,等同于 s4:=slices4:=slice[:]fmt.Println(s4) // [1 2 3 4 5 6 7 8 9 10]// 切片名[low:high:max],容量=max-low// 截取左闭右开,3-6下标的数据,并指定容量为10-3s5:=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 mainimport \"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 mainimport \"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 mainimport \"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]}