教程 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 变量定义和使用
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 数据类型汇总
类型 |
名称 |
长度(字节) |
默认值 |
范围 |
说明 |
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 布尔类型
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 |
计算字符串 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 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 + 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 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)
}
取余经典练习题
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 == 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 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("恭喜成年了哦!")
}
}
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
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)
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; islice[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]
}