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 布尔类型 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 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) 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 包含了所有剩下的不切分。如果 n0,则返回 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 表示最多切分出几个子串,超出的部分将不再切分。如果 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) 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)
}

取余经典练习题

  • 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 == 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("恭喜成年了哦!")
	}
}

  • 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; 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]
}

你可能感兴趣的:(golang)