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

news2024/10/12 3:15:11

文章目录

  • 环境安装
  • 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA 字符串 trim() 方法的正确使用

JAVA类里面 trim() 方法大家都比较熟悉&#xff0c;就是用来清除掉字符串首尾的空白字符。但在一次程序运行崩溃后&#xff0c;查找具体原因时&#xff0c;发现是由字符串末尾的一个回车符号 "\r" 所导致的。于是有机会仔细读了下该方法的 java 文档说明。其中一段内…

Docker理念

1.为什么会出现Docker Docker 的出现并非偶然&#xff0c;而是由一系列技术发展趋势和实际需求所推动的一项技术创新。 随着软件行业的快速发展&#xff0c;开发团队的规模不断扩大&#xff0c;成员可能分布在不同的地理位置&#xff0c;使用不同的操作系统和开发工具。这就导致…

CSD(computational storage devices)架构介绍

CSD&#xff08;computational storage devices&#xff09;架构介绍 前言一、CSD与传统SSD的架构对比二、为什么要采用FPGA三、FPGA缺点四、个人总结reference 前言 虽然一直有接触CSD&#xff0c;但一直对其原理和架构知之甚少&#xff0c;半知不解。今天&#xff0c;趁着我还…

element-ui点击文字查看图片预览功能

今天做一个点击文字查看图片的功能&#xff0c;大体页面长这样子&#xff0c;点击查看显示对应的图片 引入el-image-viewer&#xff0c;点击的文字时候设置图片预览组件显示并传入图片的地址 关键代码 <el-link v-if"scope.row.fileList.length > 0" type&…

模型预测控制工具包——ACADO:简介、安装与测试

模型预测控制工具包——ACADO&#xff1a;简介、安装与测试 ACADO 工具包简介ubuntu20.04 安装 ACADO工具包安装依赖安装ACADOtoolkit 测试 ACADO 工具包简介 ACADO Toolkit 是一个用 C 编写的用于自动控制和动态优化的软件环境和算法集合。 它提供了一个通用框架&#xff0c;…

三菱FX3UPLC定位控制程序举例

测试程序的编写 1.输入输出的分配输入输出的分配如下表所示。 2、相关软元件的设定 相关软元件也有所不同。更改定位指令的脉冲输出端时&#xff0c;根因设定为定位指令的脉冲输出端的软元件不同&#xff0c;据更改的内容&#xff0c;需要变更设定的相关软元件。 3.程…

【大模型新书】掌握大语言模型:高级技术、应用、尖端方法和顶尖LLMs

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/93e5a5c008474f72a0335083ef9c4893.png)我已将 这本大模型书籍免费分享 出来&#xff0c;需要的小伙伴可以扫取。 主要特性 探索自然语言处理&#xff08;NLP&#xff09;基础和大语言模型基本原理&#xff0c;包括…

若依前端后打成一个JAR包部署

客户需要将项目前后端作为一个整体打包成jar&#xff0c;不使用nginx方式转发。使用框架是若依前后端分离&#xff0c;后端springboot&#xff0c;前端vue&#xff0c;目的就是把vue打入jar。 一、前端修改 ruoyi-ui/src/router/index.js文件 &#xff0c;将 mode: ‘history’…

一键生成二维码的源码系统 电脑+手机版自适应代码 带完整的安装代码包以及搭建部署教程

系统概述 一键生成二维码的源码系统是一款集二维码生成、管理和应用于一体的综合性平台。它采用先进的技术和算法&#xff0c;能够快速、准确地生成各种类型的二维码&#xff0c;包括文本、链接、图片等。同时&#xff0c;该系统还具备高度的灵活性和可扩展性&#xff0c;能够…

如何使用bpmn-js实现可视化流程管理

介绍 BPMN-JS是一个流行的开源库&#xff0c;用于在Web应用程序中可视化、创建、编辑和分析BPMN&#xff08;Business Process Model and Notation&#xff0c;业务流程建模与表示法&#xff09;2.0 图。BPMN是一种国际标准的图形化语言&#xff0c;用于描述企业中的业务流程&a…

mongodb 连接, 去重,索引

mongodb 去重,索引 MongoDB Community Server 下载: https://www.mongodb.com/try/download/community GUI: The Ultimate Client, IDE and GUI for MongoDB | Studio 3T 连接 设置允许远程(局域网)连接 (windows) 在打开文件 "<你的安装目录>\MongoDB\Server\…

k3s安装指定版本以及离线安装(docker)

首先下载你所需要版本的k3s安装包&#xff0c;目录结构如下所示&#xff0c;我这里是v1.19.15k3s2。 1.首先赋予可执行权限后进行安装。 # k3s 需要赋予可执行权限 sudo chmod x k3s sudo chmod x k3s-install.sh2.然后将k3s的二进制文件复制到/usr/local/bin/ cp k3s /us…

✨机器学习笔记(七)—— 交叉验证、偏差和方差、学习曲线、数据增强、迁移学习、精确率和召回率

机器学习笔记&#xff08;七&#xff09; 1️⃣评估模型&#x1f397;️使用测试集评估模型&#x1f397;️交叉验证集&#xff08;cross validation&#xff09; 2️⃣偏差和方差&#xff08;Bias / Variance&#xff09;3️⃣学习曲线&#xff08;Learning curves&#xff09…

自动化分析背后,一站式数据分析平台!

自动化分析背后&#xff0c;一站式数据分析平台&#xff01; 前言一站式数据分析平台 前言 在如今的企业管理中&#xff0c;数据已经不再是简单的存储和备份&#xff0c;而是成为了决策的核心驱动力。尤其是在面对海量数据的情况下&#xff0c;企业急需一个能够高效处理、分析…

学习笔记之指针进阶(10.11)

a[0]就相当于数组名arr&#xff0c;a[0]1就相当于arr1&#xff0c;arr1是数组中下一个元素的地址&#xff0c;所以a[0]1就是数组中下一个元素的地址&#xff0c;&#xff08;把二维数组中的每一个数组看作一个元素&#xff09; 以上解释是错误的&#xff0c;a[0]不是整个数组的…

Leetcode——数组:移除元素—27.移除元素

知识点 双指针&#xff0c;在设置时&#xff0c;设置两个指针&#xff0c;一个用来寻找目标值&#xff0c;一个用来表示新数组的下标。 当找到不是目标值的元素时&#xff0c;将其添加到新数组中&#xff0c;如果是目标值&#xff0c;直接掠过 寻找目标值的指针会遍历整个数…

从commit校验失效问题探究husky原理

一、背景 之前创建的项目&#xff0c;发现代码 commit 提交的时候没有了任何校验&#xff0c;具体表现&#xff1a; 一是 feat fix 等主题格式校验没有了二是代码 lint 不通过也能提交 尝试解决这个问题&#xff0c;并深入了解husky的实现原理&#xff0c;将相关的一些知识点…

Excalidraw:在线手绘风格白板、图表绘制工具

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 Excalidraw 是一个开源的虚拟白板&#xff0c;用于绘制手绘风格的图表、线框图等。它支持多人实时协作&#xff0c;并使用端到端加密&#xff0c;确保…

钢铁行业3大改造方向 智能仪器亦起到重要作用!

钢铁企业新的改造方向主要包括超低排放改造、能效改造和数字化转型。‌这些政策旨在提升钢铁行业的环保水平、能效和智能化水平。其中智能仪器的加入&#xff0c;为钢铁企业数字化智能化自动化改造带来新的活力。 具体来说&#xff0c;到2027年&#xff0c;钢铁行业将实现以下目…

《Linux从小白到高手》综合应用篇:详解Linux系统调优之深入理解Huge Pages和Transparent Huge Pages

本篇深入介绍Linux Huge Pages和Transparent Huge Pages. 我在前一篇博文&#xff08;《Linux从小白到高手》综合应用篇&#xff1a;详解Linux系统调优之内存优化&#xff09;中说过&#xff0c;很多人可能对大页内存&#xff08;Huge Pages&#xff09;和透明大页&#xff08;…