【GO基础学习】环境安装到基础语法(1)

文章目录

  • 环境安装
  • GoLand 安装
  • GO基础
    • GO特点
    • 类型和函数
    • Init函数和main函数
    • GO命令
    • 下划线
    • 变量和常量
    • 数组
    • 切片Slice
  • 引用


环境安装

下载地址:https://www.golangroadmap.com/

安装目录文件说明:
在这里插入图片描述

api:每个版本的 api 变更差异。
bin:go 源码包编译出的编译器(go)、文档工具(godoc)、格式化工具(gofmt)。
doc:英文版的 Go 文档。
lib:引用的一些库文件。
misc 杂项用途的文件,例如 Android 平台的编译、git 的提交钩子等。
pkg:Windows 平台编译好的中间文件。
src:标准库的源码。
test:测试用例。

检测是否安装成功:cmd输入命令go env
在这里插入图片描述
安装成功!

配置GOPATH环境:
高级系统设置=》环境变量=》新建系统变量
在这里插入图片描述
同时在path里面添加go的安装目录和GOPATH目录

在这里插入图片描述
在GOPATH目录下新建3个文件夹
在这里插入图片描述

  • bin:用来存放编译后生成的可执行文件
  • pkg:用来存放编译后生成的归档文件
  • src:用来存放源码文件

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


GoLand 安装

IDE为GoLand JetBrains IDEs
下载地址:https://www.jetbrains.com/go/download

new project=》Edit Configuration=》Add New Configuration=》Go Build=》OK(无需填写其他)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Name:为本条配置信息的名称,可以自定义,也可以使用系统默认的值;
Output directory:用来设置编译后生成的可执行文件的存放目录,可以为空,为空时默认不生成可执行文件;
Working directory:用来设置程序的运行目录,不能为空。

测试运行GoLand默认生成的main.go

// 声明 main 包,表明当前是一个可执行程序
package main

import (
	"fmt" // 导入一个系统包fmt用来输出的
)
// 创建函数 main函数  func 函数  main 函数的名字 () 没有参数
func main() {
	s := "gopher"
	fmt.Printf("Hello and welcome, %s!\n", s)

	for i := 1; i <= 5; i++ {
		fmt.Println("i =", 100/i)
	}
}

Hello and welcome, gopher!
i = 100
i = 50
i = 33
i = 25
i = 20

Process finished with the exit code 0

运行成功,同时需要注意:在 Go 语言中,fmt.Println 是用于打印文本和变量的函数,但它并不支持像某些其他语言那样的格式化字符串。你使用的 %s 是 Go 语言的格式化占位符,但它只能在 fmt.Printf 或类似函数中使用。

在命令行执行,go代码跑起来的命令就是 go run后面跟go代码:go run \XXX\XXX.go


GO基础

GO特点

GO优点:

  • 自带gc。
  • 静态编译,编译好后,扔服务器直接运行。
  • 简单的思想,没有继承,多态,类等。
  • 丰富的库和详细的开发文档。
  • 语法层支持并发,和拥有同步并发的channel类型,使并发开发变得非常方便。
  • 简洁的语法,提高开发效率,同时提高代码的阅读性和可维护性。
  • 超级简单的交叉编译,仅需更改环境变量。
  • Go 语言是谷歌 2009 年首次推出并在 2012 年正式发布的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去10多年间软件开发的难度令人沮丧。Google 对 Go 寄予厚望,其设计是让软件充分发挥多核心处理器同步多工的优点,并可解决面向对象程序设计的麻烦。它具有现代的程序语言特色,如垃圾回收,帮助开发者处理琐碎但重要的内存管理问题。Go 的速度也非常快,几乎和 C 或 C++ 程序一样快,且能够快速开发应用程序。

Go语言的主要特征:
1.自动立即回收。
2.更丰富的内置类型。
3.函数多返回值。
4.错误处理。
5.匿名函数和闭包。
6.类型和接口。
7.并发编程。
8.反射。
9.语言交互性。

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

Go还有37个保留字:

    Constants:    true  false  iota  nil

    Types:    int  int8  int16  int32  int64  
              uint  uint8  uint16  uint32  uint64  uintptr
              float32  float64  complex128  complex64
              bool  byte  rune  string  error

    Functions:   make  len  cap  new  append  copy  close  delete
                 complex  real  imag
                 panic  recover

可见性:
1)声明在函数内部,是函数的本地值,类似private
2)声明在函数外部,是对当前包可见(包内所有.go文件都可见)的全局值,类似protect
3)声明在函数外部且首字母大写是所有包可见的全局值,类似public

有四种主要声明方式:
var(声明变量), const(声明常量), type(声明类型) ,func(声明函数)

一个Go工程中主要包含以下三个目录:

  • src:源代码文件
  • pkg:包文件
  • bin:相关bin文件

编译问题
1.系统编译时 go install abc_name时,系统会到GOPATH的src目录中寻找abc_name目录,然后编译其下的go文件;
2.同一个目录中所有的go文件的package声明必须相同,所以main方法要单独放一个文件,否则在eclipse和liteide中都会报错;
编译报错如下:(假设test目录中有个main.go 和mymath.go,其中main.go声明package为main,mymath.go声明packag 为test);can't load package: package test: found packages main (main.go) and test (mymath.go) in /home/wanjm/go/src/test
3.对于main方法,只能在bin目录下运行 go build path_tomain.go; 可以用-o参数指出输出文件名;
4.可以添加参数 go build -gcflags “-N -l” ****,可以更好的便于gdb;详细参见 http://golang.org/doc/gdb
5.gdb全局变量主一点。 如有全局变量 a;则应写为 p ‘main.a’;注意但引号不可少;

类型和函数

  1. 值类型:
    bool
    int(32 or 64), int8, int16, int32, int64
    uint(32 or 64), uint8(byte), uint16, uint32, uint64
    float32, float64
    string
    complex64, complex128
    array    -- 固定长度的数组
  1. 引用类型:(指针类型)
    slice   -- 序列数组(最常用)
    map     -- 映射
    chan    -- 管道
类型长度(字节)默认值说明
bool1false
byte10uint8
rune40Unicode Code Point, int32
int, uint4或8032 或 64 位
int8, uint810-128 ~ 127, 0 ~ 255,byte是uint8 的别名
int16, uint1620-32768 ~ 32767, 0 ~ 65535
int32, uint3240-21亿~ 21亿, 0 ~ 42亿,rune是int32 的别名
int64, uint6480
float3240.0
float6480.0
complex648
complex12816
uintptr4或8以存储指针的 uint32 或 uint64 整数
array值类型
struct值类型
string“”UTF-8 字符串
slicenil引用类型
mapnil引用类型
channelnil引用类型
interfacenil接口
functionnil函数

支持八进制、 六进制,以及科学记数法。标准库 math 定义了各数字类型取值范围。

    a, b, c, d := 071, 0x1F, 1e9, math.MinInt16

空指针值 nil,而非C/C++ NULL。

关于复数:

complex64和complex128

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

关于字符串的常见操作:

方法介绍
len(str)求长度
+或fmt.Sprintf拼接字符串
strings.Split分割
strings.Contains判断是否包含
strings.HasPrefix,strings.HasSuffix前缀/后缀判断
strings.Index(),strings.LastIndex()子串出现的位置
strings.Join(a[]string, sep string)join操作

byte和rune类型

uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
rune类型,代表一个 UTF-8字符。

第一个循环以字节为单位遍历字符串,而第二个循环以 Unicode 字符为单位遍历字符串。(UTF-8 编码中的每个中文字符通常占用 3 个字节,以字节为单位遍历,中文肯定会乱码)

// 遍历字符串
func traversalString() {
	s := "hello world!你好"
	for i := 0; i < len(s); i++ { //byte
		fmt.Printf("%v(%c) ", s[i], s[i])
	}
	fmt.Println()
	for _, r := range s { //rune
		fmt.Printf("%v(%c) ", r, r)
	}
	fmt.Println()
}

输出:
当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。
Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾

104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 119(w) 111(o) 114(r) 108(l) 100(d) 33(!) 228(ä) 189(½) 160( ) 229(å) 165(¥) 189(½) 
104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 119(w) 111(o) 114(r) 108(l) 100(d) 33(!) 20320() 22909() 

因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。

修改字符串:
要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

func changeString() {
	s1 := "hello"
	// 强制类型转换
	byteS1 := []byte(s1)
	byteS1[0] = 'H'
	fmt.Println(string(byteS1))

	s2 := "博客"
	runeS2 := []rune(s2)
	runeS2[0] = '狗'
	fmt.Println(string(runeS2))
}

类型转换
Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。
强制类型转换的基本语法如下:T(表达式) 其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.

    func sqrtDemo() {
        var a, b = 3, 4
        var c int
        // math.Sqrt()接收的参数是float64类型,需要强制转换
        c = int(math.Sqrt(float64(a*a + b*b)))
        fmt.Println(c)
    }
  1. 内置函数

Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。

    append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
    close           -- 主要用来关闭channel
    delete            --map中删除key对应的value
    panic            -- 停止常规的goroutine  (panicrecover:用来做错误处理)
    recover         -- 允许程序定义goroutine的panic动作
    real            -- 返回complex的实部   (complexreal imag:用于创建和操作复数)
    imag            -- 返回complex的虚部
    make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
    new                -- 用来分配内存,主要用来分配值类型,比如intstruct。返回指向Type的指针
    cap                -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 mapcopy            -- 用于复制和连接slice,返回复制的数目
    len                -- 来求长度,比如string、array、slice、map、channel ,返回长度
    printprintln     -- 底层打印函数,在部署环境中建议使用 fmt 包
  1. 内置接口error
    type error interface { //只要实现了Error()函数,返回值为String的都实现了err接口

            Error()    String

    }

Init函数和main函数

  1. init函数

go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。

有下面的特征:
1 init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
2 每个包可以拥有多个init函数
3 包的每个源文件也可以拥有多个init函数
4 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
5 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
6 init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

  1. main函数
Go语言程序的默认入口函数(主函数)func main()
函数体用{}一对括号包裹。

func main(){
    //函数体
}
  1. init函数和main函数的异同

相同点:
两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
不同点:
init可以应用于任意包中,且可以重复定义多个。
main函数只能用于main包中,且只能定义一个

两个函数的执行顺序:

对同一个go文件的init()调用顺序是从上到下的。
对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数
对于不同的package,如果不相互依赖的话,按照main包中”先import的后调用”的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。
如果init函数中使用了println()或者print()你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。

GO命令

$ go
Go is a tool for managing Go source code.

Usage:

    go command [arguments]

The commands are:

    build       compile packages and dependencies
    clean       remove object files
    doc         show documentation for package or symbol
    env         print Go environment information
    bug         start a bug report
    fix         run go tool fix on packages
    fmt         run gofmt on package sources
    generate    generate Go files by processing source
    get         download and install packages and dependencies
    install     compile and install packages and dependencies
    list        list packages
    run         compile and run Go program
    test        test packages
    tool        run specified go tool
    version     print Go version
    vet         run go tool vet on packages

Use "go help [command]" for more information about a command.

Additional help topics:

    c           calling between Go and C
    buildmode   description of build modes
    filetype    file types
    gopath      GOPATH environment variable
    environment environment variables
    importpath  import path syntax
    packages    description of package lists
    testflag    description of testing flags
    testfunc    description of testing functions

Use "go help [topic]" for more information about that topic.

go env用于打印Go语言的环境信息。

go run命令可以编译并运行命令源码文件。

go get可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。

go build命令用于编译我们指定的源码文件或代码包以及它们的依赖包。

go install用于编译并安装指定的代码包及它们的依赖包。

go clean命令会删除掉执行其它命令时产生的一些文件和目录。

go doc命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。

go test命令用于对Go语言编写的程序进行测试。

go list命令的作用是列出指定的代码包的信息。

go fix会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。

go vet是一个用于检查Go语言源码中静态错误的简单工具。

go tool pprof命令来交互式的访问概要文件的内容。

下划线

import 下划线(如:import _ hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。
示例:
对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数
在这里插入图片描述
demo.go

package foundation

import (
	"fmt"
)

func init() {
	fmt.Println("Init Function demo==1==")
}

main.go

package main

import (
	_ "GoStudy/foundation"
	"fmt"
)

func init() {
	fmt.Println("Main init()")
}

func main() {
	fmt.Println("hello world")
}


输出:

Init Function demo==1==
Init Function demo==2==
Init Function demo==3==
Main init()
hello world

Process finished with the exit code 0

变量和常量

  1. 变量声明:var 变量名 变量类型
    var name string
    var age int
    var isOk bool
    // 批量声明
    var (
        a string
        b int
        c bool
        d float32
    )
  1. 变量初始化

(1)Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil
(2)声明变量,指定初始值var 变量名 类型 = 表达式

    var name string = "hetongxue"
    var sex int = 1
    // 批量
    var name, sex = "hetongxue", 1

(3)类型推导

    // 编译器会根据等号右边的值来推导变量的类型完成初始化
    var name = "hetongxue"
    var sex = 1
  1. 短变量声明

在函数内部,可以使用更简略的 := 方式声明并初始化变量。

package main

import (
    "fmt"
)
// 全局变量m
var m = 100

func main() {
    n := 10
    m := 200 // 此处声明局部变量m
    fmt.Println(m, n)
}

函数外的每个语句都必须以关键字开始(var、const、func等)
:=不能使用在函数外。
_多用于占位,表示忽略值。

  1. 常量

var换成了const,常量在定义的时候必须赋值

    const pi = 3.1415
    const e = 2.7182
    // 一起声明
    const (
        pi = 3.1415
        e = 2.7182
    )
    // const同时声明多个常量时,如果省略了值则表示和上面一行的值相同
    const (
        n1 = 100
        n2
        n3
    )

声明了pi和e这两个常量之后,在整个程序运行期间它们的值都不能再发生变化了。

  1. iota

iota是go语言的常量计数器,只能在常量的表达式中使用。
iotaconst关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

    const (
            n1 = iota //0
            n2        //1
            n3        //2
            n4        //3
        )
    // 跳过某个值
    const (
            n1 = iota //0
            n2        //1
            _
            n4        //3
        )
    // 插队
    const (
            n1 = iota //0
            n2 = 100  //100
            n3 = iota //2
            n4        //3
        )
    const n5 = iota //0

数组

  1. 数组:是同一种数据类型的固定长度的序列。
  2. 数组定义:var a [len]int,比如:var a [5]int数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变
  3. 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型
  4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
  1. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
  2. 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。【JAVA数组是引用类型,如果函数内部修改了数组的元素,这些修改会影响到原始数组。】
  3. 支持 “==”、“!=” 操作符,因为内存总是被初始化过的。
  4. 指针数组 [n]*T,数组指针 *[n]T

参考代码理解:

package main

import "fmt"

func changeArray(x []int) {
	//遍历数组
	for i := 0; i < len(x); i++ {
		fmt.Println(x[i])
	}
	// 遍历数组
	for index, value := range x {
		fmt.Println(index, value)
	}

	// 修改数组的值
	for index, value := range x {
		x[index] = value + 1
	}
}

func main() {
	var x = [5]int{1, 2, 3, 5, 7}
	changeArray(x[:])
	fmt.Println(x) // [2 3 4 6 8]
}

需要注意的点:

  1. changeArray函数传入的切片,(切片是引用类型,函数修改时会影响原本的值的),这里相当于重新定义了一个切片x[:]传到了该函数,原本数组的值发生了改变。
  2. 其次注意全局变量和局部变量的声明方式。局部变量声明:正确的写法是直接使用 :=,不需要 var 关键字
    全局:
    var arr0 [5]int = [5]int{1, 2, 3}
    var arr1 = [5]int{1, 2, 3, 4, 5}
    var arr2 = [...]int{1, 2, 3, 4, 5, 6}
    var str = [5]string{3: "hello world", 4: "tom"}
    局部:
    a := [3]int{1, 2}           // 未初始化元素值为 0。
    b := [...]int{1, 2, 3, 4}   // 通过初始化值确定数组长度。
    c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
    d := [...]struct {
        name string
        age  uint8
    }{
        {"user1", 10}, // 可省略元素类型。
        {"user2", 20}, // 别忘了最后一行的逗号。
    }
    多维
    全局
    var arr0 [5][3]int
    var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
    局部:
    a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。

传入数组指针,传入的是引用,地址,会影响原本的值。

package main

import "fmt"

func printArr(arr *[5]int) {
	arr[0] = 10
	for i, v := range arr {
		fmt.Println(i, v)
	}
}

func main() {
	var arr1 [5]int
	printArr(&arr1)
	fmt.Println(arr1)
	arr2 := [...]int{2, 4, 6, 8, 10}
	printArr(&arr2)
	fmt.Println(arr2)
}
0 10
1 0
2 0
3 0
4 0
[10 0 0 0 0]
0 10
1 4
2 6
3 8
4 10
[10 4 6 8 10]

补充:
求数组所有元素之和

package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 求元素和
func sumArr(a [10]int) int {
	var sum int = 0
	for i := 0; i < len(a); i++ {
		sum += a[i]
	}
	return sum
}

func main() {
	// 若想做一个真正的随机数,要种子
	// seed()种子默认是1
	//rand.Seed(1)v 已弃用
	//rand.Seed(time.Now().Unix())
	rand.New(rand.NewSource(time.Now().Unix()))

	var b [10]int
	for i := 0; i < len(b); i++ {
		// 产生一个0到1000随机数
		b[i] = rand.Intn(1000)
	}
	fmt.Println(b)
	sum := sumArr(b)
	fmt.Printf("sum=%d\n", sum)
}

找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],找出两个元素之和等于8的下标分别是(0,4)和(1,2)

package main

import "fmt"

// 找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],找出两个元素之和等于8的下标分别是(0,4)和(1,2)
func sumTarget(x []int, target int) {
	for i := 0; i < len(x); i++ {
		for j := i + 1; j < len(x); j++ {
			if x[i]+x[j] == target {
				fmt.Printf("(%d, %d)\n", i, j)
			}
		}
	}

}

func main() {
	b := [5]int{1, 3, 5, 8, 7}
	sumTarget(b[:], 8)
}

切片Slice

值拷贝行为会造成性能问题,通常会建议使用 slice,或数组指针。

切片的创建:

package main

import "fmt"

func main() {
   //1.声明切片
   var s1 []int
   if s1 == nil {
      fmt.Println("是空")
   } else {
      fmt.Println("不是空")
   }
   // 2.:=
   s2 := []int{}
   // 3.make()
   var s3 []int = make([]int, 0)
   fmt.Println(s1, s2, s3)
   // 4.初始化赋值
   var s4 []int = make([]int, 0, 0)
   fmt.Println(s4)
   s5 := []int{1, 2, 3}
   fmt.Println(s5)
   // 5.从数组切片
   arr := [5]int{1, 2, 3, 4, 5}
   var s6 []int
   // 前包后不包
   s6 = arr[1:4]
   fmt.Println(s6)
}
全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end] 
var slice1 []int = arr[:end]        
var slice2 []int = arr[start:]        
var slice3 []int = arr[:] 
var slice4 = arr[:len(arr)-1]      //去掉切片的最后一个元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]        
slice7 := arr[start:]     
slice8 := arr[:]  
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素

在这里插入图片描述注意
在函数内部,无论是使用 var 还是 := 声明的变量都是局部变量,仅在该函数体内有效。如果需要定义全局变量,应该在包级别进行定义,而不是在函数内部。使用 var:= 的主要区别在于声明方式和类型推断,而可见性和作用域是相同的。

在 Go 语言中,切片(slice)的内存布局包含三个主要字段:指针(ptr)、长度(len)和容量(cap)。

  • 指针(ptr):ptr 是指向切片底层数组的指针。切片本质上是一个对数组的引用,指针指向数组中第一个元素的地址。
  • 长度(len):len 表示切片当前包含的元素数量,也就是切片的实际长度。这个值是动态的,可以随着切片的变化而变化。通过 len(slice) 函数可以获取切片的长度。
  • 容量(cap):cap 表示切片的容量,即底层数组中从切片的开始位置到数组末尾的元素数量。这是切片可以使用的最大元素数量,不会随切片的大小变化而变化,直到底层数组的大小不够为止。通过 cap(slice) 函数可以获取切片的容量。
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 切片包含 arr[1], arr[2], arr[3]

此时,切片的内存布局如下:

  • ptr 指向 arr 中的元素 2(即 arr[1])
  • len 为 3(切片当前包含的元素为 2, 3, 4)
  • cap 为 4(从 arr[1] 到 arr[4] 的元素数量)
  • 切片的容量是由其起始点到原数组末尾的距离决定的,而不是由切片当前包含的元素数量决定的。

lencap 的区别
len

  • 表示切片当前包含的元素数量。
  • 当切片进行追加(append)操作时,如果切片的长度小于容量,可以直接增加长度;如果切片的长度等于容量,底层数组需要扩展,新的底层数组将被分配,len 会增加,但 cap 也可能会增加。

cap:

  • 表示切片的最大容量,是切片可以容纳的元素数量,不会随着切片长度的增加而自动改变,直到底层数组不够大。
  • 可以通过切片操作(如切片扩展、追加元素)改变,但只在底层数组重新分配时。
package main

import (
	"fmt"
)

func main() {
	arr := [5]int{1, 2, 3, 4, 5}
	slice := arr[1:4] // slice = [2, 3, 4]

	fmt.Println("slice:", slice)           // 输出 [2 3 4]
	fmt.Println("len(slice):", len(slice)) // 输出 3
	fmt.Println("cap(slice):", cap(slice)) // 输出 4

	slice = append(slice, 6)               // 尝试添加元素
	fmt.Println("after append:", slice)    // 输出 [2 3 4 6]
	fmt.Println("len(slice):", len(slice)) // 输出 4
	fmt.Println("cap(slice):", cap(slice)) // cap 可能增加 == 4
}

通过make来创建切片 var slice []type = make([]type, len)slice := make([]type, len)slice := make([]type, len, cap)

var slice0 []int = make([]int, 10) // [0 0 0 0 0 0 0 0 0 0]
var slice1 = make([]int, 10) // [0 0 0 0 0 0 0 0 0 0]
var slice2 = make([]int, 10, 10) // [0 0 0 0 0 0 0 0 0 0]

切片和数组

package main

import "fmt"

func main() {
	s1 := []int{0, 1, 2, 3, 8: 100}
	arr := [...]int{0, 1, 2, 3, 8: 100}

	fmt.Println("s1:", s1)
	fmt.Println("Type of s1:", fmt.Sprintf("%T", s1))

	fmt.Println("arr:", arr)
	fmt.Println("Type of arr:", fmt.Sprintf("%T", arr))
}

数组指针:

package main

import "fmt"

func main() {
	s := []int{0, 1, 2, 3}
	p := &s[2] // *int, 获取底层数组元素指针。
	*p += 100
	fmt.Println(s) // [0 1 102 3]
}

改变切片影响原数组的值:

package main

import (
	"fmt"
)

func main() {
	data := [...]int{0, 1, 2, 3, 4, 5}

	s := data[2:4]
	s[0] += 100
	s[1] += 200

	fmt.Println(s) //[102 203]
	fmt.Println(data) // [0 1 102 203 4 5]
}

用append内置函数操作切片(切片追加)

package main

import (
	"fmt"
)

func main() {

	var a = []int{1, 2, 3}
	fmt.Printf("slice a : %v\n", a)
	var b = []int{4, 5, 6}
	fmt.Printf("slice b : %v\n", b)
	c := append(a, b...)
	fmt.Printf("slice c : %v\n", c)
	d := append(c, 7)
	fmt.Printf("slice d : %v\n", d)
	e := append(d, 8, 9, 10)
	fmt.Printf("slice e : %v\n", e)

}
/*
    slice a : [1 2 3]
    slice b : [4 5 6]
    slice c : [1 2 3 4 5 6]
    slice d : [1 2 3 4 5 6 7]
    slice e : [1 2 3 4 5 6 7 8 9 10]
*/

超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。

package main

import (
	"fmt"
)

func main() {

	data := [...]int{0, 1, 2, 3, 4, 10: 0}
	s := data[:2:3] // slice[start:end:cap]

	fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。===>相同

	s[0] = 78
	fmt.Println(s, data) // 没有重新分配底层数组,与原数组相关。
	
	s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。

	s[0] = 67

	fmt.Println(s, data)         // 重新分配底层数组,与原数组无关。
	fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。

}
/*
0xc0000760c0 0xc0000760c0
[78 1] [78 1 2 3 4 0 0 0 0 0 0]
[67 1 100 200] [78 1 2 3 4 0 0 0 0 0 0]
0xc00000e2d0 0xc0000760c0
*/

从输出结果可以看出,append 后的 s 重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。
通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

slice中cap重新分配规律:

package main

import (
	"fmt"
)

func main() {

	s := make([]int, 0, 1)
	c := cap(s)

	for i := 0; i < 50; i++ {
		s = append(s, i)
		if n := cap(s); n > c {
			fmt.Printf("cap: %d -> %d\n", c, n)
			c = n
		}
	}

}
/*
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
*/

切片拷贝

package main

import (
	"fmt"
)

func main() {

	s1 := []int{1, 2, 3, 4, 5}
	fmt.Printf("slice s1 : %v\n", s1)
	s2 := make([]int, 10)
	fmt.Printf("slice s2 : %v\n", s2)
	copy(s2, s1)
	fmt.Printf("copied slice s1 : %v\n", s1)
	fmt.Printf("copied slice s2 : %v\n", s2)
	s3 := []int{1, 2, 3}
	fmt.Printf("slice s3 : %v\n", s3)
	s3 = append(s3, s2...)
	fmt.Printf("appended slice s3 : %v\n", s3)
	s3 = append(s3, 4, 5, 6)
	fmt.Printf("last slice s3 : %v\n", s3)

}
/*
slice s1 : [1 2 3 4 5]
slice s2 : [0 0 0 0 0 0 0 0 0 0]
copied slice s1 : [1 2 3 4 5]
copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
slice s3 : [1 2 3]
appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
*/

copy :函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。

package main

import (
	"fmt"
)

func main() {

	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println("array data : ", data)
	s1 := data[8:]
	s2 := data[:5]
	fmt.Printf("slice s1 : %v\n", s1)
	fmt.Printf("slice s2 : %v\n", s2)
	copy(s2, s1)
	fmt.Printf("copied slice s1 : %v\n", s1)
	fmt.Printf("copied slice s2 : %v\n", s2)
	fmt.Println("last array data : ", data)

}
/*
array data :  [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data :  [8 9 2 3 4 5 6 7 8 9]
*/

slice遍历:

package main

import (
	"fmt"
)

func main() {

	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	slice := data[:]
	for index, value := range slice {
		fmt.Printf("index : %v , value : %v\n", index, value)
	}

}
/*
index : 0 , value : 0
index : 1 , value : 1
index : 2 , value : 2
index : 3 , value : 3
index : 4 , value : 4
index : 5 , value : 5
index : 6 , value : 6
index : 7 , value : 7
index : 8 , value : 8
index : 9 , value : 9
*/

切片resize(调整大小)

package main

import (
	"fmt"
)

func main() {
	var a = []int{1, 3, 4, 5}
	fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a)) // slice a : [1 3 4 5] , len(a) : 4

	b := a[1:2]
	b[0] = 100
	fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b)) // slice b : [100] , len(b) : 1
	fmt.Println(a)                                        // [1 100 4 5]

	c := b[0:3]
	c[0] = 200
	fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c)) // slice c : [200 4 5] , len(c) : 3
	fmt.Println(a)                                        // [1 200 4 5]
	fmt.Println(b)                                        // [200]

	fmt.Printf("cap === a : %v ,b : %v , c : %v\n", cap(a), cap(b), cap(c)) // cap === a : 4 ,b : 3 , c : 3
	
}

切片共用一个底层的指针数组ptr

字符串和切片(string and slice):
string底层就是一个byte的数组,因此,也可以进行切片操作。

package main

import (
	"fmt"
)

func main() {
	str := "hello world"
	s1 := str[0:5]
	fmt.Println(s1) // hello

	s2 := str[6:]
	fmt.Println(s2) // world
}

string本身是不可变的,因此要改变string中字符。需要如下操作:

package main

import (
	"fmt"
)

func main() {
	str := "Hello world"
	s := []byte(str) //中文字符需要用[]rune(str)
	s[6] = 'G'
	s = s[:8]
	s = append(s, '!')
	str = string(s)
	fmt.Println(str) // Hello Go!
}

数组or切片转字符串:

strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)

引用

  1. https://www.runoob.com/go/go-tutorial.html
  2. https://www.topgoer.cn/
  3. https://gopl-zh.github.io

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/889691.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot+Vue的船舶监造系统(带1w+文档)

基于SpringBootVue的船舶监造系统(带1w文档) 基于SpringBootVue的船舶监造系统(带1w文档) 大概在20世纪90年代&#xff0c;我国才开始研发船舶监造系统&#xff0c;与一些发达国家相比&#xff0c;系统研发起步比较晚。当时的计算机技术刚开始发展起来&#xff0c;国家经济力量…

Map的实现类:HashMap

在API获取HsahMap类的全部信息 实例代码&#xff1a;创建一个Student类和Demo02 package com.map;public class Student {private String name;private int stuNo;public Student(String name, int stuNo) {this.name name;this.stuNo stuNo;}public String getName() {retu…

从零开始构建:Python自定义脚本自动化你的日常任务

从零开始构建&#xff1a;Python自定义脚本自动化你的日常任务 Python 作为一种简洁且功能强大的编程语言&#xff0c;被广泛应用于各种自动化任务中。通过编写 Python 脚本&#xff0c;你可以轻松地将日常重复性工作自动化&#xff0c;例如文件操作、数据处理、网络爬虫、系统…

C++ | Leetcode C++题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution { public:bool circularArrayLoop(vector<int>& nums) {int n nums.size();auto next [&](int cur) {return ((cur nums[cur]) % n n) % n; // 保证返回值在 [0,n) 中};for (int i 0; i < n; i) {if …

STM32 407 RS485通信实现数据收发【我的创作纪念日】

1. 前言 本例中的485驱动&#xff0c;基于标准库编写&#xff0c;不是HAL库&#xff0c;请大家注意。 最近搞嵌入式程序&#xff0c;踩了不少坑&#xff0c;这里统一记录一下。 2. 收获 1.串口通信&#xff0c;数据是一个字节一个字节的发送&#xff0c;对方收到的数据是放在…

github学生认证(Github Copilot)

今天想配置一下Github Copilot&#xff0c;认证学生可以免费使用一年&#xff0c;认证过程中因为各种原因折腾了好久&#xff0c;记录一下解决方法供大家参考。 p.s.本文章只针对Github学生认证部分遇到的问题及解决方法&#xff0c;不包括配置copilot的全部流程~ 1、准备工作…

无图化加速!MemFusionMap提出时序重叠热图策略,在线建图mAP暴涨5.4%!

导读&#xff1a; HDMap对于自动驾驶系统至关重要&#xff0c;因为它可以为规划提供了精细的道路信息。尽管现有的单帧输入方法在在线矢量化高精地图构建方面取得了不错的成绩&#xff0c;但在处理复杂场景和遮挡时仍然存在挑战。为了解决这些问题&#xff0c;作者提出了 MemFu…

AWR1642+DCA1000采集ADC数据并解析

文章同步发布在CSDN和公众号(雷达原理与系统),后续文章中出现的资料,参考文档等都会放在GitHub仓库,欢迎fork和star。 0. 序言 为什么要先将采集ADC数据呢?因为ADC数据是信号处理的输入,是后续理解信号处理手段的基础。当然这里也可以采用仿真信号,但我的想法是单独出…

SQL第13课——创建高级联结

本课讲另外一些联结&#xff08;含义和使用方法&#xff09;&#xff0c;如何使用表别名&#xff0c;如何对被联结的表使用聚集函数。 13.1 使用表别名 第7课中使用别名引用被检索的表列&#xff0c;给列起别名的语法如下&#xff1a; SQL除了可以对列名和计算字段使用别名&a…

聚类分析 | IPOA优化FCM模糊C均值聚类优化算法

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 (多图聚类)IPOA优化FCM模糊C均值聚类优化算法&#xff0c;matlab代码&#xff0c;超多图 基于改进的鹈鹕优化算法&#xff08;IPOA&#xff09;优化FCM模糊C均值聚类优化&#xff0c;matlab代码&#xff0c;直接运行…

HTB:Preignition[WriteUP]

连接至HTB服务器并启动靶机 靶机IP&#xff1a;10.129.157.49 分配IP&#xff1a;10.10.16.12 1.Directory Brute-forcing is a technique used to check a lot of paths on a web server to find hidden pages. Which is another name for this? (i) Local File Inclusion, (…

窗口售票系统1.0版本

本窗口售票系统实现了三个售票窗口的随机售票&#xff0c;实现随机到某一个窗口买票&#xff0c;总票余量都会减少&#xff0c;即三个窗口共享同一个票余量。若票余量小于一次性购票量&#xff0c;则提示报错&#xff1b;若车票售罄&#xff0c;则代码结束运行。 代码实现&…

用户和组管理

用户管理 用户管理包括创建用户、修改用户属性、删除用户等操作。 创建用户 使用 useradd 命令可以创建新用户。 格式&#xff1a;useradd [选项] username 步骤1&#xff1a;创建新用户 useradd tom 步骤 2: 设置用户密码 新用户创建后&#xff0c;需要设置一个密码才能…

需求8——通过一个小需求来体会AI如何帮助改bug

这篇文章&#xff0c;我们通过一个简单的例子来说明&#xff0c;平时在写需求的时候&#xff0c;我们可以在什么时候用AI来帮助我们写代码。 首先来看一下这个需求&#xff1a;系统中某个用户使用的时候出现了bug&#xff0c;通过手机建立临时任务报错&#xff0c;没有办法新增…

ElasticSearch备考 -- Update by query Reindex

一、题目 有个索引task&#xff0c;里面的文档长这样 现在需要添加一个字段all&#xff0c;这个字段的值是以下 a、b、c、d字段的值连在一起 二、思考 需要把四个字段拼接到一起&#xff0c;组成一个新的字段&#xff0c;这个就需要脚本&#xff0c; 这里有两种方案&#xff…

ES(Elasticsearch)SSL集群部署

8.x后ES不在需要自行准备JDK环境&#xff0c;部署的服务包含ES、Kibana、Logstash&#xff0c;使用二进制方式部署&#xff0c;为了提高安全性&#xff0c;加密logstash、kibana及其他客户端到ES间的通信。 1、准备工作 1.1、 es无法使用root用户启动 useradd -m -s /bin/bas…

论文阅读:Split-Aperture 2-in-1 Computational Cameras (二)

Split-Aperture 2-in-1 Computational Cameras (一) Coded Optics for High Dynamic Range Imaging 接下来&#xff0c;文章介绍了二合一相机在几种场景下的应用&#xff0c;首先是高动态范围成像&#xff0c;现有的快照高动态范围&#xff08;HDR&#xff09;成像工作已经证…

Kubernetes-Kind篇-01-kind搭建测试集群

1、Kind 介绍 官方文档地址&#xff1a;https://kind.sigs.k8s.io/ github仓库地址&#xff1a;https://github.com/kubernetes-sigs/kind 国内镜像仓库地址&#xff1a;https://gitcode.com/gh_mirrors/ki/kind/overview kind 是一种使用 Docker 容器 nodes 运行本地 Kubern…

HI6338 (DIP-8内置75W方案)

Hi6338 combines a dedicated current mode PWM controller with integrated high voltage power MOSFET.Vcc low startup current and low operating current contribute to a reliable power on startup design with Hi6338. the IC operates in Extended ‘burst mode’ to …

Nginx请求头丢失,引发出来的问题

1.问题 新增的几个 header 参数是这样的&#xff1a; api_key_idapi_key_value 我配置有2层nginx转发&#xff0c;从机器A到机器B再到目标服务&#xff0c;遇到一个接口请求需要在header中传递api_key_id和api_key_value这2个参数&#xff0c;但是在EC2机器上直接curl目标服…