AI智能
改变未来

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语言中,主函数所在的包一定是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计算,需要类型装换才可计算
  • 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 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 执行效率低
    • 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 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]}
    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » Golang学习笔记