第四节 基本运算符和变量,数组切片

下划线

“_”是特殊标识符,用来忽略结果。

下划线在import中

在Golang里,import的作用是导入其他package。
import 下划线(如:import hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。
举例:
import _ “github.com/mysql”
不直接使用该包,只是执行该包的init函数。

下划线在代码中

两种解释
第一种:忽略这个返回值的变量,有些函数返回值有两个,但是我们只需要用第一个或者是第二个,此时就可以用下划线来忽略不需要关注的变量。
第二种:表示了占位符的意思,本来这个下划线的位置要赋值给某个变量,但是我们不需要该变量,则用下划线进行占位。因为赋值了变量,但是不用该变量,则编译器会报错。

变量

定义

数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。

功能

功能是存储数据,不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。
Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

声明

Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且Go语言的变量声明后必须使用。
Go语言的变量声明格式为:

var 变量名 变量类型

批量声明

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

var (
    a string
    b int
    c bool
    d float32
)

变量的初始化

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

我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:

var 变量名 类型 = 表达式

一次初始化多个变量,如下:

var 变量名, 变量名 = 表达式, 表达式

变量的类型推导

我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。

var 变量名 = 表达式, 表达式

短变量声明(常用–重要)

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

package main

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

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

匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示。
注:
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
函数外的每个语句都必须以关键字开始(var、const、func等)
:=不能使用在函数外
_多用于占位,表示忽略值

常量

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。
常量单独声明如下:

    const a = 1
    const b = 2

多个常量一起声明如下:

const (
   a = 1
   b = 2
)

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同:

const (
    a = 1
    b     // b = 1
    c     // c = 1
)

iota

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

iota使用场景

  1. _跳过某些值
const (
        n1 = iota //0
        n2        //1
        _
        n4        //3
    )
  1. iota声明中间插队
const (
        n1 = iota //0
        n2 = 100  //100
        n3 = iota //2
        n4        //3
    )
const n5 = iota //0
  1. 定义数量级
    <<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8
const (
        _  = iota
        KB = 1 << (10 * iota)
        MB = 1 << (10 * iota)
        GB = 1 << (10 * iota)
        TB = 1 << (10 * iota)
        PB = 1 << (10 * iota)
    )
  1. 多个iota定义在一行
const (
        a, b = iota + 1, iota + 2 //1,2
        c, d                      //2,3
        e, f                      //3,4
    )

基本类型

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

整型

按长度分为:int8、int16、int32、int64
对应的无符号整型:uint8、uint16、uint32、uint64
注:
uint8就是我们熟知的byte型
int16对应C语言中的short型
int64对应C语言中的long型

浮点型

float32和float64
数据格式遵循IEEE 754标准:
float32 的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32
float64 的浮点数的最大范围约为1.8e308,可以使用常量定义:math.MaxFloat64

复数

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

布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值
注:

  1. 默认false
  2. 不能将整型强制转换为布尔型
  3. 布尔型无法参与数值运算,也无法与其他类型进行转换

字符串

  1. 字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样
  2. Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为 双引号 中的内容
  3. Go语言的源码中可以直接添加非ASCII码字符

字符串转义符

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等

转义含义
\r回车符(返回行首)
\n换行符(直接跳到下一行的同列位置)
\t制表符
单引号
"双引号
\反斜杠

多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符:

   s1 := `第一行
    第二行
    第三行
    `
    fmt.Println(s1)

反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出

字符串的常用操作(重要)

方法介绍
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字符

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

数组

  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. 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
  3. 支持 “==”、“!=” 操作符,因为内存总是被初始化过的。
  4. ** 指针数组 [n]*T,数组指针 *[n]T。 **

数组的初始化

全局:
    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 main() {

    var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

    for k1, v1 := range f {
        for k2, v2 := range v1 {
            fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
        }
        fmt.Println()
    }
}

输出结果:

    (0,0)=1 (0,1)=2 (0,2)=3 
    (1,0)=7 (1,1)=8 (1,2)=9

切片Slice

slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案
注:
1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
2. 切片的长度可以改变,因此,切片是一个可变的数组。
3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
5. 切片的定义:var 变量名 []类型,比如 var str []string ; var arr []int。
6. 如果 slice == nil,那么 len、cap 结果都等于 0。

创建切片的方式

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] //去掉切片的最后一个元素

解释:

  1. s[n]:切片中索引位置为n的项
  2. s[:]:从切片的索引位置0~len()-1处所得到的切片
  3. s[low:]:从切片的索引位置low到len()-1处所得到的切片
  4. s[:high]:从切片的索引位置0到high处所得到的切片
  5. s[low:high:max]:从切片的索引位置low到high处所得到的切片
    len = high - low
    cap = max - low
  6. len():切片的长度,小于等于cap()
  7. cap():切片的容量,大于等于len()

通过make来创建切片

var slice []type = make([]type, len)
slice  := make([]type, len)
slice  := make([]type, len, cap)

用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]

注:append :向 slice 尾部添加数据,返回新的 slice 对象。

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

通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

    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]
*/

应及时将所需数据 copy 到较小的 slice,以便释放超大号底层数组内存。

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("inde : %v , value : %v\n", index, value)
    }

}

切片resize(调整大小)

如果从一个数组进行切片,则得到一个slice。若对该切片继续进行切片,且切片长度要大于第一次切片的长度。会从原数组中的数据进行调整大小。保证最后一次切片的内容最少要包括原数组内容。

字符串和切片

字符串底层是byte的数组,可以进行切片
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!
*/

a[x:y:z] 切片内容 [x:y] 切片长度: y-x 切片容量:z-x

数组or切片转字符串

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值