Go编程基础-学习1

1350368559 · · 605 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Go编程基础-学习

本内容为自己学习go知识记录的笔记,方便复习查看
笔记内容参考无闻老师的github:https://github.com/Unknwon/go-fundamental-programming
课程视频内容:百度网盘(提取码:mgom)
笔记内容参考:Go编程基础-课堂讲义

1. 什么是Go?

Go是一门 并发支持 、垃圾回收 的 编译型 系统编程语言,旨在创造一门具有在静态编译语言的 高性能 和动态语言的 高效开发 之间拥有良好平衡点的一门编程语言。

2. Go的主要特点有哪些?

类型安全 和 内存安全
以非常直观和极低代价的方案实现 高并发
高效的垃圾回收机制
快速编译(同时解决C语言中头文件太多的问题)
为多核计算机×××能提升的方案
UTF-8编码支持

3.Go存在的价值是什么?

Go在谷歌:以软件工程为目的的语言设计

4.Go是记事本编程吗?

包括VIM,IDEA,Sublime Text,Eclipse等众多知名IDE均已支持

5.Go目前有多少实际应用和资源?

全球最大视频网站 Youtube(谷歌)
七牛云存储以及旗下网盘服务(Q盘)
爱好者开发的Go论坛及博客
已用Go开发服务端的著名企业:谷歌、盛大、七牛、360
其它海量开源项目:go-wiki、Go Walker、Go Language Resources

6.Go发展成熟了吗?

作为一门2009年才正式发布的编程语言,Go是非常年轻的,因此不能称为一门成熟的编程语言,但开发社区每天都在不断更新其核心代码,给我们这些爱好者给予了很大的学习和开发动力。

7.Go的爱好者多吗?

以Google Group为主的邮件列表每天都会更新10至20帖,国内的Go爱好者QQ群和论坛每天也在进行大量的讨论,因此可以说目前Go爱好者群体是足够壮大。

8.安装Go语言

Go源码安装:参考链接
Go标准包安装:下载地址
第三方工具安装

9.Go环境变量与工作目录

根据约定,GOPATH下需要建立3个目录:
bin(存放编译后生成的可执行文件)
pkg(存放编译后生成的包文件)
src(存放项目源码)

Go编程基础-学习1

10.Go命令

在命令行或终端输入go即可查看所有支持的命令

11.Go常用命令简介

go get:获取远程包(需 提前安装 git或hg)
go run:直接运行程序
go build:测试编译,检查是否有编译错误
go fmt:格式化源码(部分IDE在保存时自动调用)
go install:编译包文件并编译整个程序
go test:运行测试文件
go doc:查看文档(CHM手册)

12.程序的整体结构

Go编程基础-学习1

13.Go开发工具安装及配置

本套教程主要使用 Sublime Text
其它IDE安装方案:参考链接
Sublime Text
下载Sublime Text:官方网站
安装gosublime(破解版可能无法安装):安装指令
Sublime Text 2 入门及技巧

14.Go语言版”Hello world!”

Go编程基础-学习1
输出:hello.go
Go编程基础-学习1

15.Go内置关键字(25个均为小写)

- break        default           func        interface        select
- case          defer              go           map               struct
- chan          else                goto       package        switch
- const         fallthrough    if             range             type
- continue   for                  import    return             var 

16.Go注释方法

//   单行注释
/* */  多行注释

17.Go程序的一般结构:basic_structure.go

Go程序是通过 package 来组织的(与python类似)
只有 package 名称为 main 的包可以包含 main 函数
一个可执行程序 有且仅有 一个 main 包
通过 import 关键字来导入其它非 main 包
通过 const 关键字来进行常量的定义
通过在函数体外部使用 var 关键字来进行全局变量的声明与赋值
通过 type 关键字来进行结构(struct)或接口(interface)的声明
通过 func 关键字来进行函数的声明

18.Go导入 package 的格式

Go编程基础-学习1 Go编程基础-学习1Go编程基础-学习1

  • 导入包之后,就可以使用格式<PackageName>.<FuncName>
    来对包中的函数进行调用
    如果导入包之后 未调用 其中的函数或者类型将会报出编译错误:
    Go编程基础-学习1

    19.package 别名

    当使用第三方包时,包名可能会非常接近或者相同,此时就可以使用
    别名来进行区别和调用
    Go编程基础-学习1 Go编程基础-学习1 Go编程基础-学习1

    20.省略调用

    不建议使用,易混淆
    不可以和别名同时使用
    Go编程基础-学习1

    21.可见性规则

    Go语言中,使用 大小写 来决定该 常量、变量、类型、接口、结构
    或函数 是否可以被外部包所调用:
    根据约定,函数名首字母 小写 即为private
    Go编程基础-学习1
    函数名首字母 大写 即为public
    Go编程基础-学习1

    22.既然导入多个包时可以进行简写,那么声明多个 常量、全局变量

    或一般类型(非接口、非结构)是否也可以用同样的方法呢?
    Go编程基础-学习1Go编程基础-学习1Go编程基础-学习1

    23.Go基本类型

    布尔型:bool

    • 长度:1字节
    • 取值范围:true, false
    • 注意事项:不可以用数字代表true或false
      整型:int/uint
    • 根据运行平台可能为32或64位
      8位整型:int8/uint8
    • 长度:1字节
    • 取值范围:-128~127/0~255
      字节型:
    • byte(uint8别名)
      16位整型:int16/uint16
    • 长度:2字节
    • 取值范围:-32768~32767/0~65535
      32位整型:int32(rune)/uint32
    • 长度:4字节
    • 取值范围:-2^32/2~2^32/2-1/0~2^32-1
      64位整型:int64/uint64
    • 长度:8字节
    • 取值范围:-2^64/2~2^64/2-1/0~2^64-1
      浮点型:float32/float64
    • 长度:4/8字节
    • 小数位:精确到7/15小数位
      复数:complex64/complex128
    • 长度:8/16字节
      足够保存指针的 32 位或 64 位整数型:uintptr
      其它值类型:
    • array、struct、string
      引用类型:
    • slice、map、chan
      接口类型:inteface
      函数类型:func
      类型零值
      零值并不等于空值,而是当变量被声明为某种类型后的默认值,通常情况下值类型的默认值为0,bool为false,string为空字符串

      24.类型自定义别名,定义string类型的自定义别名是:文本,定义b是 文本 类型,给b赋值,输出ok

      type (
      bype int8
      rune int32
      文本 string
      )

    func main() {
    var b 文本
    b = "中文类型名字"
    fmt.Println(b)
    }
    $ go run basic_struct.go
    中文类型名字

    25.单个变量的声明与赋值

    变量的声明格式:var <变量名称> <变量类型>
    变量的赋值格式:<变量名称> = <表达式>
    声明的同时赋值:var <变量名称> [变量类型] = <表达式>
    Go编程基础-学习1

    26.多个变量的声明与赋值,

    • 全局变量的声明可使用 var() 的方式进行简写
    • 全局变量的声明不可以省略 var,但可使用并行方式
    • 所有变量都可以使用类型推断
    • 局部变量不可以使用 var() 的方式简写,只能使用并行方式
    • 全局变量不可以使用":"符号 ":"只用于方法体内使用,":"是用来代替var的,所以全局变量var a int = 1不能使用:,否则两个var岂不重复报错?
      三种定义局部变量的方法:
      func main() {
      var a, b, c, d int = 1, 2, 3, 4
      var a, b, c, d = 1, 2, 3, 4
      a, b, c, d := 1, 2, 3, 4
      fmt.Println(a)
      fmt.Println(b)
      fmt.Println(c)
      fmt.Println(d)
      }
      用空白符号: 接受2的返回值
      func main() {
      a,
      , c, d := 1, 2, 3, 4
      fmt.Println(a)
      fmt.Println(c)
      fmt.Println(d)
      }
      Go编程基础-学习1Go编程基础-学习1

      27.变量的类型转换

      Go中不存在隐式转换,所有类型转换必须显式声明
      转换只能发生在两种相互兼容的类型之间
      类型转换的格式:

    • <ValueA> [:]= <TypeOfValueA>(<ValueB>)
      Go编程基础-学习1

      尝试执行以下代码,看结果输出什么?

      Go编程基础-学习1

package main
import (
    "fmt"
)
func main() {
    //fmt.Println("hello world")
    var a int = 65
    fmt.Println(a)
    b := string(a)
    fmt.Println(b)
}    
daixuandeMacBook-Pro:学习go daixuan$ go run hello.go
65
A
如果我就是想输出字符串65,怎么办?
package main
import (
    "fmt"
    "strconv"
)
func main() {
    var a int = 65
    b := strconv.Itoa(a)
    fmt.Println(b)
}
$ go run basic_struct.go
65

string() 表示将数据转换成文本格式,因为计算机中存储的任何东西
本质上都是数字,因此此函数自然地认为我们需要的是用数字65表示
的文本 A。

28.常量的定义

常量的值在编译时就已经确定
常量的定义格式与变量基本相同
等号右侧必须是常量或者常量表达式
常量表达式中的函数必须是内置函数
Go编程基础-学习1

29.常量的初始化规则

在定义常量组时,如果不提供初始值,则表示将使用上行的表达式
使用相同的表达式不代表具有相同的值
Go编程基础-学习1
Go编程基础-学习1

package main
import (
    "fmt"
)
const (
    a, b = 1, "2" 
    c, d //这里两个变量c,d使用初始化规则,值等于上一行表达式,注意每一行的常量个数相同
)
func main() {
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
}
$ go run basic_struct.go
1
2
1
2

30.枚举

iota是常量的计数器,从0开始,组中每定义1个常量自动递增1
通过初始化规则与iota可以达到枚举的效果
每遇到一个const关键字,iota就会重置为0

package main
import (
    "fmt"
)
const (
    a = "A"
    b           //b初始化规则b=a="A"
    c = iota    //已经有2个常量a,b,所以c=2
    d           //注意:这里d套用了c的常量表达式d=iota,已经有3个常量a,b,c,所以d=3
)
const (  
    e = iota  //每遇到一个const关键字,iota就会重置为0,所以e=iota=0
    f     //f套用了e的常量表达式f=iota,e=0,f=e+1=1
)
func main() {
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
    fmt.Println(e)
    fmt.Println(f)
}
go run basic_struct.go
A
A
2
3
0
1

31.运算符

Go中的运算符均是从左至右结合
优先级(从高到低)
^ ! (一元运算符)

  • / % << >> & &^
    • ^(异或,相同为0,不同为1) (二元运算符)
      == != < <= >= >
      <- (专门用于channel)
      &&
      package main
      import (
      "fmt"
      )
      /*
      6  :0110
      11:1011
      &  0010 = 2
      |  1111 =15
      ^  1101 =13   //异或,相同为0,不同为1,1^0=1,0^1=1,0^0=0,1^1=0
      &^ 0100 = 4 //表示如果B后边的为1,需要强制将A的改为0
      */
      func main() {
      fmt.Println(6 & 11)
      fmt.Println(6 | 11)
      fmt.Println(6 ^ 11)
      fmt.Println(6 &^ 11)
      }
      go run basic_struct.go
      2
      15
      13
      4

      32.请尝试结合常量的iota与<<运算符实现计算机储存单位的枚举

      Go编程基础-学习1
      Go编程基础-学习1

      33.指针

      Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不
      支持指针运算以及”->”运算符,而直接采用”.”选择符来操作指针
      目标对象的成员
      操作符”&”取变量地址,使用”*”通过指针间接访问目标对象
      默认值为 nil 而非 NULL
      递增递减语句

      func main() {
      a := 1
      var p *int = &a //p是指向a地址(0xc420016088)的int指针
      fmt.Println(p)
      fmt.Println(*p)    
      }
      $go run basic_struct.go  
      0xc420016088
      1
      *p取地址内的值1
      func main() {
      a := 1
      var p = &a
      fmt.Println(p)
      fmt.Println(*p)
      }
      $ go run basic_struct.go
      0xc420016088
      1

      在Go当中,++ 与 -- 是作为语句而并不是作为表达式
      简单理解为:

      /允许:
      a :=1
      a++
      //不允许,报错
      a :=1
      a  := a++

      34.判断语句if

      条件表达式没有括号
      支持一个初始化表达式(可以是并行方式)
      左大括号必须和条件语句或else在同一行
      支持单行模式
      初始化语句中的变量为block级别,同时隐藏外部同名变量1.0.3版本中的编译器BUG

      func main() {
      a := 10 
      if a := 3; a > 1 {  //可以在if中初始化a:=3,但是a=3仅仅在if中有效
      fmt.Println(a)
      }
      fmt.Println(a) //如果没有提前定义a :=10 ,这里会报错
      }
      go run basic_struct.go
      3
      10

      Go编程基础-学习1

      func main() {
      a := true
      if a, b, c := 1, 2, 3; a+b+c > 6 { //可以在if中初始化a:=1,但是a=1仅仅在if中有效,if外a=true
      fmt.Println("大于6")
      } else {
      fmt.Println("小于等于6")
      fmt.Println(a)
      }
      fmt.Println(a)
      }
      $ go run basic_struct.go
      小于等于6
      1
      true

      35.循环语句for,3种形式

      Go只有for一个循环语句关键字,但支持3种形式
      初始化和步进表达式可以是多个值
      条件语句每次循环都会被重新检查,因此不建议在条件语句中
      使用函数,尽量提前计算好条件并以变量或常量代替
      左大括号必须和条件语句在同一行
      (1)for+if

      func main() {
      a := 1
      for {
      a++
      if a > 3 {
          break
      }
      fmt.Println(a)
      }
      fmt.Println("Over")
      }
      $ go run basic_struct.go
      2
      3
      Over

      (2)for 自带判断条件

      func main() {
      a := 1
      for a <= 3 {
      a++
      fmt.Println(a)
      }
      fmt.Println("Over")
      }
      $ go run basic_struct.go
      2
      3
      4
      Over

      (3经典方式

      func main() {
      a := 1
      for i := 0; i < 3; i++ {
      a++
      fmt.Println(a)
      }
      fmt.Println("Over")
      }
      $ go run basic_struct.go
      2
      3
      4
      Over

      36.选择语句switch

      可以使用任何类型或表达式作为条件语句
      不需要写break,一旦条件符合自动终止
      如希望继续执行下一个case,需使用fallthrough语句
      支持一个初始化表达式(可以是并行方式),右侧需跟分号
      左大括号必须和条件语句在同一行

      func main() {
      a := 1
      switch a {
      case 0:
      fmt.Println("a=0")
      case 1:
      fmt.Println("a=1")
      default:
      fmt.Println("None")
      }
      }
      $ go run basic_struct.go
      a=1
      func main() {
      a := 1
      switch {
      case a >= 0:
      fmt.Println("a=0")  //a=0满足条件,打印a=0跳出
      case a >= 1:
      fmt.Println("a=1")
      default:
      fmt.Println("None")
      }
      }
      $ go run basic_struct.go
      a=0
      func main() {
      a := 1
      switch {
      case a >= 0:
      fmt.Println("a=0") //a=0满足条件,打印a=0,有fallthrough不跳出,检查下一个
      fallthrough
      case a >= 1:
      fmt.Println("a=1")
      default:
      fmt.Println("None")
      }
      }
      $ go run basic_struct.go
      a=0
      a=1
      func main() {
      switch a := 1; { //在switch中定义a,作用于switch内部,外部调用失败
      case a >= 0:
      fmt.Println("a=0")
      fallthrough
      case a >= 1:
      fmt.Println("a=1")
      default:
      fmt.Println("None")
      }
      }
      $ go run basic_struct.go
      a=0
      a=1

      37.跳转语句goto, break, continue

      三个语法都可以配合标签使用,实现跳出多层循环
      标签名区分大小写,若不使用会造成编译错误
      Break与continue配合标签可用于多层循环的跳出
      Goto是调整执行位置,与其它2个语句配合标签的结果并不相同

      func main() {
      LABEL1:
      for {
      for i := 0; i < 10; i++ {
          if i > 3 {
              break LABEL1
          }
      }
      }
      fmt.Println("ok")
      }
      $ go run basic_struct.go
      ok
      func main() {
      for {
      for i := 0; i < 10; i++ {
          if i > 3 {
              goto LABEL1
          }
      }
      }
      LABEL1:   //标签放在最外层循环的外侧,确保不死循环,可以跳出
      fmt.Println("ok")
      }
      $ go run basic_struct.go
      ok
      func main() {
      LABEL1:
      for i := 0; i < 10; i++ { //把有限循环放在最外面,那么continue最终会结束,不会死循环
      for {
          continue LABEL1
          fmt.Println(i)
      }
      }
      fmt.Println("ok")
      }
      $ go run basic_struct.go
      ok

      将下图中的continue替换成goto,程序运行的结果还一样吗?

      请尝试并思考为什么。
      Go编程基础-学习1

func main() {
LABEL1:
    for i := 0; i < 10; i++ {
        for {
            fmt.Println(i)
            continue LABEL1
        }
    }
    fmt.Println("ok")
}
$ go run basic_struct.go
0
1
2
3
4
5
6
7
8
9
ok
func main() {
LABEL1:
    for i := 0; i < 10; i++ {
        for {
            fmt.Println(i)
            goto LABEL1
        }
    }
    fmt.Println("ok")
}
$ go run basic_struct.go
0
0
0
......
因为一执行循环,输出0后,goto到label1,用重新开始循环,重新输出0之后,又调到label1,又进入循环,无线下去输出0,死循环

38.数组Array

定义数组的格式:var <varName> [n]<type>,n>=0
数组长度也是类型的一部分,因此具有不同长度的数组为不同类型

func main() {
    var a [2]int  //初始化int类型数组a,定义为2个元素,默认值为0
    var b [2]int
    b = a
    fmt.Println(b)
}
$ go run basic_struct.go
[0 0]
func main() {
    a := [2]int{1, 1} //初始化int类型数组a,定义为2个元素,初始值为1
    var b [2]int
    b = a
    fmt.Println(b)
}
$ go run basic_struct.go
[1 1]
func main() {
    a := [2]int{1} //初始化int类型数组a,定义为2个元素,初始值1、0
    var b [2]int
    b = a
    fmt.Println(b)
}
$ go run basic_struct.go
[1 0]
func main() {
    a := [20]int{18: 2, 19: 1} //定义数组第19个元素值为2,第20个元素为1
    fmt.Println(a)
}
$ go run basic_struct.go
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1]
func main() {
    a := [...]int{3, 2, 1, 19: 1} //使用...定义数组,第20个元素是1,则数组长度为20
    fmt.Println(a)
}
$ go run basic_struct.go
[3 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]

注意区分:
指向数组的指针[100]int -->&[0 0 0 0 0 0 0 0 0 1]
指针数组[100]
int -->[0xc420016088 0xc420016090]

func main() {
    a := [...]int{9: 1}
    var p *[10]int = &a //p是指向数组的指针
    fmt.Println(p)
}
$ go run basic_struct.go
&[0 0 0 0 0 0 0 0 0 1] //数组前面有一个&,p是指向数组的指针
func main() {
    a := [...]int{9: 1}
    var p = &a   //这样更简单,p是指向数组的指针
    fmt.Println(p)
}
$ go run basic_struct.go
&[0 0 0 0 0 0 0 0 0 1]
func main() {
    x, y := 1, 2
    a := [...]*int{&x, &y} //定义a是指针类型的数组,保存的数组元素是指向int型的指针:变量x和y的地址 
    fmt.Println(a)
}
$ go run basic_struct.go
[0xc420016088 0xc420016090]

数组在Go中为值类型,传递数组是整个拷贝的,其他语言为了节省内存,数组是引用类型。
数组之间可以使用==或!=进行比较,但不可以使用<或>

func main() {
    a := [2]int{1, 2}
    b := [2]int{1, 2}
    fmt.Println(a == b)
}
$ go run basic_struct.go
true
func main() {
    a := [2]int{1, 2}
    b := [2]int{1, 200}
    fmt.Println(a == b)
}
$ go run basic_struct.go
false
//注意:数组长度不同,无法直接比较,否则报错
func main() {
    a := [2]int{1, 2}
    b := [1]int{10}
    fmt.Println(a == b)
}
$ go run basic_struct.go
# command-line-arguments
./basic_struct.go:17:16: invalid operation: a == b (mismatched types [2]int and [1]int)

可以使用new来创建数组,此方法返回一个指向数组的指针

func main() {
    p := new([10]int)
    fmt.Println(p)
}
$ go run basic_struct.go
&[0 0 0 0 0 0 0 0 0 0]
//可以使用索引直接对数组元素操作
func main() {
    a := [10]int{}
    a[1] = 2      //使用索引直接对数组元素赋值
    fmt.Println(a)
    p := new([10]int)
    p[1] = 2   //使用索引直接对数组元素赋值
    fmt.Println(p)
    fmt.Println(*p)
}
$ go run basic_struct.go
[0 2 0 0 0 0 0 0 0 0]
&[0 2 0 0 0 0 0 0 0 0]
[0 2 0 0 0 0 0 0 0 0]

Go支持多维数组

func main() {
    a := [2][3]int{
        {1, 1, 1},
        {2, 2, 2}}
    fmt.Println(a)
}
$ go run basic_struct.go
[[1 1 1] [2 2 2]]
func main() {
    a := [2][3]int{
        {1: 1},   //给数组元素赋值
        {2: 2}}    //给数组元素赋值
    fmt.Println(a)
}
$ go run basic_struct.go
[[0 1 0] [0 0 2]]

Go语言版冒泡排序

func main() {
    a := []int{5, 2, 6, 3, 9}
    fmt.Println(a)
    num := len(a)
    for i := 0; i < num; i++ {
        for j := i + 1; j < num; j++ {
            if a[i] < a[j] {
                temp := a[i]
                a[i] = a[j]
                a[j] = temp
            }
        }
    }
    fmt.Println(a)
}
$ go run basic_struct.go
[5 2 6 3 9]
[9 6 5 3 2]

39.切片Slice

其本身并不是数组,它指向底层的数组
作为变长数组的替代方案,可以关联底层数组的局部或全部
为引用类型
可以直接创建或从底层数组获取生成
使用len()获取元素个数,cap()获取容量
一般使用make()创建
如果多个slice指向相同底层数组,其中一个的值改变会影响全部

func main() {
    a := [10]int{1,2,3,4,5,6,7,8,9,0}
    fmt.Println(a)
    s1:=a[5:10]//a[5:10]指的是:a[5,6,7,8,9],不包括a[10]
    fmt.Println(s1)
}
$ go run basic_struct.go
[1 2 3 4 5 6 7 8 9 0]
[6 7 8 9 0]

func main() {
    a := [10]int{1,2,3,4,5,6,7,8,9,0}
    fmt.Println(a)
    s1:=a[5:len(a)]//a[5,6,7,8,9]
    fmt.Println(s1)
}
$ go run basic_struct.go
[1 2 3 4 5 6 7 8 9 0]
[6 7 8 9 0]

func main() {
    a := [10]int{1,2,3,4,5,6,7,8,9,0}
    fmt.Println(a)
    s1:=a[5:]//a[5,6,7,8,9]
    fmt.Println(s1)
}
$ go run basic_struct.go
[1 2 3 4 5 6 7 8 9 0]
[6 7 8 9 0]

func main() {
    a := [10]int{1,2,3,4,5,6,7,8,9,0}
    fmt.Println(a)
    s1:=a[:5]//a[5,6,7,8,9]
    fmt.Println(s1)
}
$ go run basic_struct.go
[1 2 3 4 5 6 7 8 9 0]
[1 2 3 4 5]

make([]T, len, cap)
其中cap可以省略,则和len的值相同
len表示存数的元素个数,cap表示容量

func main() {
    s1:=make([]int,3,10)
    fmt.Println(s1)
}
$ go run basic_struct.go
[0 0 0]
func main() {
    s1:=make([]int,3,10)
    fmt.Println(s1)
    fmt.Println(len(s1),cap(s1))//打印元素个数3和容量10
}
$ go run basic_struct.go
[0 0 0]
3 10

40.Slice与底层数组的对应关系

func main() {
    a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
    sa := a[2:5]
    fmt.Println(string(sa))
}
$ go run basic_struct.go
cde
func main() {
    a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
    sb := a[3:5]
    fmt.Println(string(sb))
}
$ go run basic_struct.go
de

Go编程基础-学习1

41.Reslice 从slice中获取新的slice

Reslice时索引以被slice的切片为准
索引不可以超过被slice的切片的容量cap()值
索引越界不会导致底层数组的重新分配而是引发错误

func main() {
    a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
    sa := a[2:5]
    fmt.Println(len(sa),cap(sa))//sa元素3,容量是9
    sb := a[3:5]
    fmt.Println(len(sb),cap(sb))//sb元素2,容量是8,比sa少1
    fmt.Println(string(sa))
    fmt.Println(string(sb))
}
$ go run basic_struct.go
3 9
2 8 
cde
de
func main() {
    a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
    sa := a[2:5]
    fmt.Println(len(sa),cap(sa))//sa元素3,容量是9(cdefghijk)
    sb := sa[2:5]
    fmt.Println(len(sb),cap(sb))//sb元素3,容量是8(efghijk)
    fmt.Println(string(sa))
    fmt.Println(string(sb))
}
$ go run basic_struct.go
3 9
3 7
cde
efg

42.Append(重要)

可以在slice尾部追加元素
可以将一个slice追加在另一个slice尾部
如果最终长度未超过追加到slice的容量则返回原始slice
如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据(容量翻倍)

func main() {
    s1 := make([]int,3,6)
    fmt.Printf("%p\n",s1)
    s1 = append(s1,1,2,3)
    fmt.Printf("%v,%p\n",s1,s1)
}
$ go run basic_struct.go
0xc4200180c0              //容量没有变化,内存地址没有变化
[0 0 0 1 2 3] 0xc4200180c0
func main() {
    s1 := make([]int,3,6)
    fmt.Printf("%p\n",s1)
    s1 = append(s1,1,2,3)
    fmt.Printf("%v,%p\n",s1,s1)
    s1 = append(s1,1,2,3) //这里容量不够,重新分配容量(翻倍),拷贝原来元素再追加
    fmt.Printf("%v,%p\n",s1,s1)
}
$ go run basic_struct.go
0xc420092000
[0 0 0 1 2 3],0xc420092000
[0 0 0 1 2 3 1 2 3],0xc420072060
func main() {
    a := []int{1,2,3,4,5}
    s1 := a[2:5]
    s2 := a[1:3]
    fmt.Println(s1,s2)
    s1[0] = 9    //slice指向底层数组,多个slice指向同一个数组时候,其中一个改变,会影响数组,同时影响其他slice值
    fmt.Println(a)
    fmt.Println(s1,s2)
}
$ go run basic_struct.go
[3 4 5] [2 3]
[1 2 9 4 5]
[9 4 5] [2 9]
func main() {
    a := []int{1,2,3,4,5}
    s1 := a[2:5]
    s2 := a[1:3]
    fmt.Println(s1,s2)
    s2=append(s2,1,2,1,1,1,1,1,1,1)//apend元素个数超过slice容量,会指向新的内存地址的底层数组(从a拷贝过来的),此时改变a,不会影响s2的值
    s1[0] = 9
    fmt.Println(a)
    fmt.Println(s1)
    fmt.Println(s2)
}
$ go run basic_struct.go
[3 4 5] [2 3]
[1 2 9 4 5]
[9 4 5]
[2 3 1 2 1 1 1 1 1 1 1]

slice拷贝,以个数少的为准

//把s2拷贝到s1
func main() {
    s1:=[]int{1,2,3,4,5,6}
    s2:=[]int{7,8,9}
    copy(s1,s2) //s2的元素7,8,9会覆盖s1的前三个元素1,2,3
    fmt.Println(s1)
}
$ go run basic_struct.go
[7 8 9 4 5 6]
//把s1拷贝到s2
func main() {
    s1:=[]int{1,2,3,4,5,6}
    s2:=[]int{7,8,9}
    copy(s2,s1)//把s1的前三个元素1,2,3拷贝覆盖s2
    fmt.Println(s2,s1)
}
$ go run basic_struct.go
[1 2 3] [1 2 3 4 5 6]
func main() {
    s1:=[]int{1,2,3,4,5,6}
    s2:=[]int{7,8,9}
    copy(s2,s1[3:6]) //把s1的后三个元素拷贝到s2,覆盖s2元素
    fmt.Println(s2,s1)
}
$ go run basic_struct.go
[4 5 6] [1 2 3 4 5 6]
func main() {
    s1:=[]int{1,2,3,4,5,6}
    s2:=[]int{7,8,9}
    copy(s2[1:3],s1[4:6]) //把s1的后2个元素拷贝到s2的后两位,覆盖s2后2个元素,保留第一个元素7
    fmt.Println(s2,s1)
}
$ go run basic_struct.go
[7 5 6] [1 2 3 4 5 6]

43.map

类似其它语言中的哈希表或者字典,以key-value形式存储数据
Key必须是支持==或!=比较运算的类型,不可以是函数、map或slice
Map查找比线性搜索快很多,但比使用索引访问数据的类型慢100倍
Map使用make()创建,支持 := 这种简写方式

func main() {
    var m map[int]string //定义map,int是key类型,string是value类型
    m=map[int]string{}
    fmt.Println(m)
}
$ go run basic_struct.go
map[]

make([keyType]valueType, cap),cap表示容量,可省略
超出容量时会自动扩容,但尽量提供一个合理的初始值
使用len()获取元素个数

func main() {
    var m map[int]string=make(map[int]string)
    fmt.Println(m)
}
$ go run basic_struct.go
map[]

func main() {
    var m =make(map[int]string)
    fmt.Println(m)
}
$ go run basic_struct.go
map[]
func main() {
    m :=make(map[int]string)
    fmt.Println(m)
}
$ go run basic_struct.go
map[]
func main() {
    m :=make(map[int]string)
    m[1] = "ok"
    fmt.Println(m)
}
$ go run basic_struct.go
map[1:ok]
func main() {
    m :=make(map[int]string)
    m[1] = "ok"
    a:=m[1]
    fmt.Println(a)
}
ok
func main() {
    m :=make(map[int]string)
    //m[1] = "ok"
    a:=m[1]
    fmt.Println(a)
}
输出为空

键值对不存在时自动添加,使用delete()删除某键值对
使用 for range 对map和slice进行迭代操作

func main() {
    m :=make(map[int]string)
    m[1] = "ok"
    delete(m,1)
    a:=m[1]
    fmt.Println(a)
}
输出为空
func main() {
    m := make(map[int]map[int]string) //使用make创建初始化m,定义m的value是另一个map[int]string
    m[1]=make(map[int]string)//这里是用make初始化另一个map,座位value赋值给m[1] 
    m[1][1]= "ok"
    a:=m[1][1]
    fmt.Println(a)
}
ok
func main() {
    m := make(map[int]map[int]string)
    m[1]=make(map[int]string) //这里只对m[1]初始化了,没有对m[2]初始化,所以把ok赋值给nil报错
    m[2][1]= "ok"
    b:=m[2][1]
    fmt.Println(b)
}
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
    /Users/daixuan/go/hello.go:8 +0x168

map嵌套map需注意,每一级的map都得初始化,怎么知道map是否被初始化呢?
采用多返回值的方式,当有一个返回值的时候,返回value,当有多个返回值的时候,第二个返回bool类型的值,true或者false

package main
import "fmt"
func main() {
    m := make(map[int]map[int]string)
    m[1]=make(map[int]string)
    m[1][1]= "123"
    a, ok  :=m[1][1] //m[1]被初始化,a=123,不为空,所以ok=true
    b, nok :=m[2][1] //m[2]未被初始化,b=nil,为空,所以nok=false
    fmt.Println(a,ok)
    fmt.Println(b,nok)
}
123 true   
 false
 package main
import "fmt"
func main() {
    m := make(map[int]map[int]string)
    a, ok  :=m[2][1]
    fmt.Println(a,ok) //第一次没有初始化map,所以ok=false
    if !ok { //这里判断是否ok==false,那么去初始化map[2](第二级的map)
        m[2]=make(map[int]string)//初始化第二级别map
    }
    m[2][1]="good"//赋值给第二级map的key=1的value是'good'
    a, ok  =m[2][1]
    fmt.Println(a,ok) //a=good,所以ok=true
}
 false
good true

迭代操作(slice和map都可以迭代操作):

package main
import (
    "fmt"
)
func main()  {
    slice := []int{10,20,30,40}
    for index,value :=range slice { //i是slice的索引,相当于计数器,int型,0,1,2,3,4....,v是slice存储的值取出赋值给v,修改v的值不会影响slice本身
        if slice[index]==30{
            slice[index]=300 //使用slice[index]=300 可以直接修改slice原始的值为300
            value=400
            fmt.Println(index,value)
            fmt.Println(slice[index]) //使用slice[index]=300 可以直接修改slice原始的值为300
        }
    }
}
2 400
300
package main
import (
    "fmt"
)
func main()  {
    slice := []int{10,20,30,40}
    for _,value :=range slice { //用空白标识符下划线 _ 来忽略索引值
            fmt.Println(value)
        }
    }
10
20
30
40
package main
import (
    "fmt"
)
func main() {
    m := make(map[int]string)
    m[1]="ok"
    for key, value := range m {
        fmt.Println(key, value)
    }

    info := map[string]string{
        "name": "david",
        "address": "shanghai",
    }
    for k,v := range info{
        fmt.Printf("Key:%s,Value:%s\n",k,v)
    }
}
1 ok
Key:name,Value:david
Key:address,Value:shanghai
package main
import (
    "fmt"
)
func main() {
    sm := make([]map[int]string,5)//定义一个以map为原数类型的slace,定义map方法:m :=map[int]string,定义slace方法:slice := make([]string,5)
    for _, v := range sm { //对slice:sm进程迭代操作
        v = make(map[int]string) //对slice中的map初始化
        v[1] ="OK" //这里对v是个拷贝赋值,不会影响slice本身的值,所以v=map[1:OK],而sm是:[map[] map[] map[] map[] map[]]
        fmt.Println(v)
    }
    fmt.Println(sm)
}
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
[map[] map[] map[] map[] map[]]

如果想把slice的值修改掉,怎么办呢?

package main
import (
    "fmt"
)
func main() {
    sm := make([]map[int]string,5)//定义一个以map为原数类型的slace,定义map方法:m :=map[int]string,定义slace方法:slice := make([]string,5)
    for i := range sm { //对slice:sm进程迭代操作,i=0,1,2,3,4
        sm[i] = make(map[int]string) //对slice中的map初始化
        sm[i][1] ="OK" //这里对第i个切片sm[i]赋值key=1,value="OK",会影响slice本身的值,所以v=map[1:OK],而sm是:[map[1:OK] map[1:OK] map[1:OK] map[1:OK] map[1:OK]]
        fmt.Println(sm[i])
    }
    fmt.Println(sm)
}
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
[map[1:OK] map[1:OK] map[1:OK] map[1:OK] map[1:OK]]

map是无序的,不能直接排序,但是我们可以对其key进程间接排序,需要借助slice,实现根据key有序的取出map中的值,实现map的简介排序

package main
import (
    "fmt"
)
func main() {
    m := map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"} //定义map,map没有索引
    s := make([]int,len(m))//定义slice,slice以索引为固定的key
    i :=0
    for k,_ := range m{
        s[i] = k //把map中所有的key存在slice中,但是无序的
        i++
    }
    fmt.Println(s)
}
[5 1 2 3 4]

package main
import (
    "fmt"
    "sort"
)
func main() {
    m := map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"} //定义map,map没有索引
    s := make([]int,len(m))//定义slice,slice以索引为固定的key
    i :=0
    for k,_ := range m{
        s[i] = k //把map中所有的key存在slice中
        i++
    }
    sort.Ints(s)//使用sort进程排序,把map中的key排序
    fmt.Println(s)
}
[1 2 3 4 5]
package main
import (
    "fmt"
    "sort"
)
func main() {
    m := map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"} //定义map,map没有索引
    fmt.Println(len(m))
    s := make([]int,len(m))//定义slice,slice以索引为固定的key
    i :=0
    for k,_ := range m{
        s[i] = k //把map中所有的key存在slice中
        i++
    }
    sort.Ints(s)
    fmt.Println(s)
    for j := range s{
        fmt.Println(m[j+1])
    }
}
5
[1 2 3 4 5]
a
b
c
d
e

根据在 for range 部分讲解的知识,尝试将类型为map[int]string的键和值进行交换,变成类型map[string]int

package main
import (
    "fmt"
)
func main() {
    m1 := map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
    fmt.Println(m1)
    m2 := make(map[string]int)
    for k,v := range m1{
        m2[v] =k
    }
    fmt.Println(m2)
}
map[3:c 4:d 5:e 1:a 2:b]
map[e:5 a:1 b:2 c:3 d:4]

程序正确运行后应输出如下结果:
Go编程基础-学习1

44.函数function

Go 函数 不支持 嵌套、重载和默认参数
但支持以下特性:
无需声明原型、不定长度变参、多返回值、命名返回值参数
匿名函数、闭包
定义函数使用关键字 func,且左大括号不能另起一行
函数也可以作为一种类型使用
Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:
func:函数由 func 开始声明
function_name:函数名称,函数名和参数列表一起构成了函数签名。
parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
函数体:函数定义的代码集合。

定义参数列表是一个int输入,另一个string输出,无返回值
fun A(a int,b string){  
}
定义参数列表是一个int输入,另一个string输出,只有一个int类型返回值
fun A(a int,b string)int{   
}
定义参数列表是一个int输入,另一个string输入,返回类型是一个int+一个string+一个int
fun A(a int,b string)(int,string,int){  
}
定义参数列表是3个int输入,3个int型输出
fun A(a int,b int, c int)(a int,b int,c int){   
}
可以简写为:
fun A(a,b, c int)(a,b,c int){   
}

命令返回值参数和不命名返回值参数有什么区别呢?
如果你要返回多个返回值的话,而且使用(a,b,c int)简写形式的话,你就必须命名返回值,不然它就不知道了

func A() (a,b,c int){
    a,b,c = 1,2,3 //这里不是:=,因为已经在内存中给a,b,c分配过内存地址了
    return a,b,c  //这里也可以直接写return,因为已经定义了返回值变量和类型(a,b,c int),代码可读性要求返回值return后加上变量a,b,c
}

如果这样定义返回值的话 (int,int,int),就可以不用命名返回值(不定义返回值是a,b,c)

func A() (int,int,int){
    a,b,c := 1,2,3 //这里必须是:=,因为是首次初始化变量
    return a,b,c  //这里不可以只写return
}

如果A是一串int型的数字,n个,我要计算A长得最大值,怎么写呢?
使用go中的不定长变参

package main
import "fmt"
func main() {
    A(1,2,3,4,5,6,7)
}
func A(a ...int){  //...就是不定长变参,A就是一个slice,可以打印出来
    fmt.Println(a)
}
输出:[1 2 3 4 5 6 7]

package main
import "fmt"
func main() {
    A("a",1,2,3,4,5,6,7)
}
func A(b string,a ...int){  //如果A使用了不定长变参"...",不可以在...后边定义变量b,可以在a之前定义变量b
    fmt.Println(b,a)
}
输出:a [1 2 3 4 5 6 7]

slice的值拷贝和直接slice地址拷贝有什么区别呢?

package main
import "fmt"
func main() {
    a :=1
    A(a)
    fmt.Println(a)
}
func A(a int){  //如果A使用了不定长变参"..."定义slice A
    a=2  //值拷贝不会修改面main函数中a的值
    fmt.Println(a)
}
2
1
package main
import "fmt"
func main() {
    a,b :=1,2
    A(a,b)
    fmt.Println(a,b)
}
func A(s ...int){  //如果A使用了不定长变参"..."定义slice s
    s[0]=3//a=3,值拷贝不会影响main函数的内部a的值
    s[1]=4//b=4,值拷贝不会影响main函数的内部b的值
    fmt.Println(s)
}
[3 4]
1 2
package main
import "fmt"
func main() {
    s1 := []int{1,2,3,4}
    A(s1)
    fmt.Println(s1)
}
func A(s []int){  //如果A使用了不定长变参"..."定义slice A
    s[0]=5//在A()函数的内部修改影响到了main函数中的s1的值,这里拿到了slice的地址,拷贝修改了slice内存地址中的值,实际上就是对slice本身进行操作
    s[1]=6
    s[2]=7
    s[3]=8
    fmt.Println(s)
}
[5 6 7 8]
[5 6 7 8]

如果我想对这种int,string,也进行内存地址值得拷贝覆盖操作,怎么做?
采用指针地址值传递,先取出地址,再赋值

package main
import "fmt"
func main() {
    a :=1
    A(&a)//调用A()函数,由于A()要求输出是指针类型(一个地址值0xxxx),所有输入&a符合输入要求&a=0xxxx
    fmt.Println(a)//这里也打印2,说明内存中的*a的值已经被修改
}
func A(a *int){ //定义了指针类型的a,a可能的值是a=0xxxxx
    *a=2 //把内存地址为0xxxx的变量*a重新赋值为2
    fmt.Println(*a)//打印*a的变量值,
}
2
2

函数也是一种数据类型,给个例子

package main
import "fmt"
func main() {
    a :=A //这里a就是A的复制品
    a()
}
func A(){
    fmt.Println("Func A")
}
Func A

什么是匿名函数呢?

package main
import "fmt"
func main() {
    a := func() { //定义a是匿名函数,可以调用并且打印Func A
        fmt.Println("Func A")
    }
    a()
}
Func A

那么什么是闭包?

package main
import "fmt"
func main() {
    f := closure(10) //调用闭包函数closure(),返回一个匿名函数给f,赋值x=10
    /*
    此时f就是匿名函数:
    func(y int)int {
        fmt.Printf("%p\n",&x)  
        return x + y
    }
    */
    fmt.Println(f(1)) //第一次调用func(),x=10,y=1,return 11
    fmt.Println(f(2)) //第二次调用func(),x=10,y=2,return 12
}
func closure(x int) func(int) int { //闭包函数的作用是返回一个匿名函数
    fmt.Printf("%p\n",&x) //第一次调用闭包函数打印x变量地址0xc420012070
    return func(y int)int {
        fmt.Printf("%p\n",&x)  //第二、三次调用闭包函数打印x变量地址0xc420012070,三次相同,
        return x + y
    }
}
0xc420012070
0xc420012070
11
0xc420012070
12

实例
以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 和 num2,并返回这两个参数的最大值:

//函数返回两个数的最大值
func max(num1, num2 int) int {
// 声明局部变量 
var result int
if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

函数调用
当创建函数时,你定义了函数需要做什么,通过调用改函数来执行指定任务。

调用函数,向函数传递参数,并返回值,例如:

package main
import "fmt"
func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
   var ret int
   /* 调用函数并返回最大值 */
   ret = max(a, b)
   fmt.Printf( "最大值是 : %d\n", ret )
}
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 定义局部变量 */
   var result int
   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}
以上实例在 main() 函数中调用 max()函数,执行结果为:
最大值是 : 200

函数返回多个值
Go 函数可以返回多个值,例如:

package main
import "fmt"
func swap(x, y string) (string, string) {
   return y, x
}
func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}
以上实例执行结果为:
Kumar Mahesh
  • 函数参数
    函数如果使用参数,该变量可称为函数的形参。
    形参就像定义在函数体内的局部变量。
    调用函数,可以通过两种方式来传递参数:
传递类型 描述
值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

  • 函数用法
    函数用法
    描述
    函数作为值 函数定义后可作为值来使用
    闭包 闭包是匿名函数,可在动态编程中使用
    方法 方法就是一个包含了接受者的函数

    45.defer

    执行方式类似其它语言中的析构函数,在函数体执行结束后
    按照调用顺序的相反顺序逐个执行(先进后出,后进先出)
    即使函数发生严重错误也会执行
    支持匿名函数的调用
    常用于资源清理、文件关闭、解锁以及记录时间等操作
    通过与匿名函数配合可在return之后修改函数计算结果
    如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址
    Go 没有异常机制,但有 panic/recover 模式来处理错误
    Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效

    package main
    import "fmt"
    func main() {
    fmt.Println("a")
    defer fmt.Println("b")
    defer fmt.Println("c")//先调用打印c,在调用打印b
    }
    a
    c
    b
    package main
    import "fmt"
    func main() {
    for i :=0;i < 3 ;i ++{
        defer fmt.Println(i)
    }
    }
    2 //打印结果是2,1,0,而不是0,1,2
    1
    0
    package main
    import "fmt"
    func main() {
    for i :=0;i < 3 ;i ++{//这里循环结束的时候i=3,闭包中的匿名函数调用的i=3,所以三次打印出来的值都是3
        defer func(){
            fmt.Println(i) //引用局部变量i,在退出for循环体的时候,i=3,在main函数return的时候,开始执行defer语句,i=3,所以全部打印3
        }() //这个括号的意思是调用这个函数,可以理解为defer a()
    }
    }
    3
    3
    3
    package main
    import "fmt"
    func main() {
    A()
    B()
    C()
    }
    func A()  {
    fmt.Println("Func A")
    }
    func B() {
    defer func(){//定义好defer遇到panic后Recover,必须在出现panic之前就定义好defer处理函数
        if err := recover();err !=nil{
            fmt.Println("Recover in B")
        }
    }()
    panic("Panic in B ")//定义panic
    }
    func C(){
    fmt.Println("Func C")
    }
    Func A
    Recover in B
    Func C

    运行以下程序并分析输出结果

    package main
    import "fmt"
    func main() {
    var fs = [4]func(){}//定义fs是func类型的slice
    for i :=0;i<4;i++{
        defer fmt.Println("defer i = ",i)//i作为一个参数,值拷贝传递,正常输出0,1,2,3,但是使用了defer定义,所以出书3,2,1,0
        defer func(){
            fmt.Println("defer_closure i =",i)//闭包匿名函数,外层勋魂结束,i=4,所以打印defer_closure i = 4
            }()
        fs[i] = func() {
            fmt.Println("closure i =",i)//先将这些匿名函数存在func类型的slice中,这里i来自于外层的for循环,外层for循环结束之后引用地址内容值i=4,所以输出closure i = 4
        }
    }
    for _,f := range  fs{//调用fs,打印closure i = 4
        f()
    }
    }
    closure i = 4
    closure i = 4
    closure i = 4
    closure i = 4
    defer_closure i = 4
    defer i =  3
    defer_closure i = 4
    defer i =  2
    defer_closure i = 4
    defer i =  1
    defer_closure i = 4
    defer i =  0

本文来自:51CTO博客

感谢作者:1350368559

查看原文:Go编程基础-学习1

入群交流(和以上内容无关):Go中文网 QQ 交流群:729884609 或加微信入微信群:274768166 备注:入群;关注公众号:Go语言中文网

605 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传