Go语言——复合类型 上

简介: Go语言——复合类型 上

1. 数组

a.【声明数组

var name [size] type
// 如果数组长度不确定,可以使用 ... 代替数组的长度,
// 编译器会根据元素个数自行推断数组的长度:
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

b.【初始化数组】

name := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 1. 方式一:完整写法
  var arr [3]int = [3]int{1, 2, 3}
// 2. 方式二:短变量方式(常用)
  arr2 := [3]int{1, 2, 3}
// 3. 方式三:长度大于初始值个数.长度为4,只给前三个元素赋值,其余元素为默认值
  arr3 := [4]int{1, 2, 3}
// 4. 方式四:赋值时不写长度,数组长度根据元素个数确定(常用)
  arr4 := [...]int{1, 2, 3}
// 还可以通过指定下标来初始化元素
name := [...]float32{0:1000.0, 2:2.0, 3.4, 7.0, 50.0}

c.【数组作为参数传递】

// 正常情况下 数组为值传递 即传递的是数组的值 在函数中对数组的操作 对于原数组无效。
func modify(array [5]int) {
  fmt.Println (array) // [1 2 3 4 5]
  array[0] = 10
  fmt.Println(array) // [10 2 3 4 5]
}
func main() {
  array := [5]int{1, 2, 3, 4, 5}
  modify(array)
  fmt.Println (array) // [1 2 3 4 5]
}
// 将数组结合指针作为参数传递 可以利用指针达到引用传递的效果
func modify(array *[5]int) {
  fmt.Println(*array) // [1 2 3 4 5]
  (*array)[0] = 10
  fmt.Println(*array) // [10 2 3 4 5]
}
func main() {
  array := [5]int{1, 2, 3, 4, 5}
  modify(&array)
  fmt.Println(array) // [10 2 3 4 5]
}

d.【冒泡排序】

/*
  数组练习:冒泡排序
  注意:
    数组与切片的区别
*/
func main() {
  arr := [6]int{2,1,0,4,8,6}
  fmt.Println(BubbleSort(arr))
}
func BubbleSort(arr[6]int) [6]int {
  for i := 0; i < len(arr); i++ {
    for i1 := 0; i1 < len(arr)-i-1; i1++ {
      if arr[i1]<arr[i1+1] {
        continue
      }else {
        arr[i1],arr[i1+1] = arr[i1+1],arr[i1]
      }
    }
  }
  return arr
}

2. 二维数组

二维数组表示一个数组变量中每个元素又是一个一维数组变量,跟java一样

声明二维数组:

var name [n] [m] 
// 使用和java一样  n为行 m为列

数组的声明与赋值:

// 1.方式一:完整写法
var arr [3][3]int = [3][3]int{{1,2,3},{4,5,6},{7,8,9},}
// 2.方式二:短变量方式(常用) 注意 这里每行后都要有个逗号
arr := [3][3]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

注意:

golang中的二维数组和java不一样 java不可以直接打印二维数组 但是go可以。

3. 指针

所谓的指针就是一个地址 而*指针 就相当于操作指针指向的那段地址的内存。

a.【变量地址】

变量本质就是内存中一块数据的标记,把值存储到变量中实质是把值存储到内存中

每次对变量重新赋值就是在修改对应变量地址中的内容

重新创建一个非引用型变量(即使是把已有变量直接赋值给新变量)也会新开辟内存地址

b.【指针变量】

指针变量指向一个值的内存地址

使用&+变量 返回值就是一个指针类型

声明指针不会开辟内存地址,只是准备指向内存某个空间,

而声明变量会开辟内存地址,准备存放内容.

所以指针类型变量都是把一个变量的地址赋值给指针变量。

使用 *+指针 能够获取 和 操作 内存地址中的值 所以 *+指针 == 直接使用变量。

应用指针可以实现多个地方操作同一个内存地址的值(在方法参数应用指针较多)

c.【指针的声明与赋值】

使用var变量名 *类型、 声明指针类型变量

【声明格式】

*var name type

*号用于指定变量是作为一个指针。

func main() {
    // 声明一个指针变量
  var a *int
  b:=1
    // 将指针指向变量b的内存地址
  a=&b
  *a = 2
  fmt.Println(b) // 2
}

使用new函数去创建指针,

使用new创建的指针是已经有指向的,所以可以直接通过 *指针 直接赋值;

而只声明的指针变量是不可以直接通过 *指针直接赋值的(野指针)。

new(type)会根据你给的type去开辟一个type大小空间 使指针指向这片空间,且不同于c语言 go语言的gc(垃圾回收)机制会自动释放这片空间。

【语法格式】

变量名 = new(type)

// 不同于直接声明指针,使用new(type) 声明的指针有一个默认的
var ptr01 *int
fmt.Printf("%p\n",ptr01) // 0x0
ptr02 := new(int)
fmt.Printf("%p\n",ptr02) // 0xc0000ac080

d.【空指针与野指针】

指针的默认值为 即:空指针

指针变量指向一个未知的空间 即为:野指针

// 空指针
var ptr01 *int
fmt.Println(ptr01) // nil
fmt.Printf("%p\n",ptr01) // 0x0
// 野指针: 指针变量指向一个未知的空间 
*ptr01 = 1 // 会报错

e.【指针与数组 切片 map 结构体】

通过指针间接操作(数组 切片 map 结构体)的方法

type Student struct {
  id int
}
func main() {
    // 与 数组 
    arr:=[3]int{1,2,3}
  s:=&arr
    // 方法一:(*变量名)[index]
  fmt.Println( (*s)[0] )
    // 方法二:变量名[index]
    fmt.Println( s[0] )
    // 与 结构体
  student := Student{1}
  i:=new(Student)
  i = &student
  i.id=2
  fmt.Println(student) // 2 
}

f.【指针作为参数传递】

指针作为参数传递为 引用传递

func main() {
  a := new(int)
  *a = 1
  test(a)
  fmt.Println(*a)
}
func test(a *int) {
  *a = 2
}

g.【多级指针】

  1. 指针本身也是值,且这个值和 12 一样,是不可以寻址的,这也是为什么 &(&a) 不行,但是 先b = &a,再&b 却可以。
func main() {
  s :=1
  p:= &s
  fmt.Println(a) // 变量的地址 0xc0000ac058
  fmt.Printf("%p",&p) // 一级指针的地址 0xc0000d8018
  pp:=&p
  fmt.Printf("%T\n",p) // *int 一级指针:指向变量的地址
  fmt.Printf("%T\n",pp) // **int 二级指针:指向一级指针的地址
}

通过二级指针 修改一级指针和变量的值

func main() {
  s := 1
  p := &s
  s1 := 2
  pp := &p
  *pp = &s1   // 相当于将一级指针的指向 从s 改成了 s1
  fmt.Println(*p) // 2
    **pp = 100   // 直接修改变量的值
  fmt.Println(s1) // 100
}

4. 切片

1.  切片的英文名称 slice
2.  切片:具有 可变长度 **相同类型**元素序列.
3.  由于长度是可变,可以解决数组长度在数据个数不确定情况下浪费内存的问题.
4.  切片和数组声明时语法最主要的区别就是长度
5.  切片只声明时为nil,没有开辟内存空间,不能直接操作切片,需要先初始化

注意:切片只能和nil进行判断是否相等

a.【切片是引用类型】

引用类型在变量之间赋值时传递的是地址.引用类型变量就是这个类型的指针.切片就是引用类型。

值类型在变量之间赋值时传递的是值的副本(就是复制了值 但是内存地址是重新开辟的)

b.【切片的声明与赋值】

    var s1 []int //声明切片和声明array一样,只是少了长度,此为空(nil)切片
    s2 := []int{}

make 函数

Go语言中可以使用make函数创建 slice、map、 channel、 interface

使用make函数定义 无内容,但不是nil 的切片,意味着切片已经申请了内存空间

语法格式:

make(类型,初始长度[,初始容量])

初始容量可以省略,默认和长度相等

长度 表示切片中元素的实际个数, 容量 表示切片占用空间大小, 且切片成倍增加.当增加到1024后按照一定百分比增加。

可以使用len() cap() 查看长度和容量

注意 长度和容量的不同

s := make([]int,1,3)
fmt.Printf("%p",&s) // 0xc000004078
fmt.Println(len(s))  // 1
fmt.Println(cap(s))  // 3
//  长度和容量的不同 
s := make([]int,0,3) // 此时有三个空位
s := make([]int,3,3) // 此时三个位置都是默认值 0

append 函数

func append(slice []Type, elems …Type) []Type

slice —>

slice = append(slice, elem1, elem2) 或 slice = append(slice, anotherSlice…)

可以向切片中添加一一个或多个值,添加后 必须使用切片接收append()函数返回值

如果添加一次 添加多个值,且添加后的长度大于扩容一次的大小,容量和长度相等.等到下次添加内容时如果不超出扩容大小(就是原容量*2), 在现在的基础上进行翻倍。

如果向切片中添加的也是个切片 格式如下:(注意要加…)

newSlice = append(oldSlice,array/slice [n:]…)

s1 := make([]int, 0)
fmt.Println(s1)  // []
fmt.Println(len(s1),cap(s1))    // 0 0
// 一次 添加多个值,且添加后的长度大于扩容一次的大小,会导致容量和长度相等.
s1 = append(s1,2,3,4)
fmt.Println(s1) // [2,3,4]
fmt.Println(len(s1),cap(s1))  //3 3
// 等到下次添加内容时如果不超出扩容大小(就是原容量*2), 在现在的基础上进行翻倍。
s1 = append(s1,5)
fmt.Println(s1)  //[2 3 4 5]
fmt.Println(len(s1),cap(s1))  //4 6
// 向切片中添加的也是个切片
a:=make([]string,0)
b:=make([]string,1,3)
b = append(b, "伤病","大傻逼")
a = append(b,b[1:]...)
fmt.Println(a) // [ 伤病 大傻逼 伤病 大傻逼]

通过数组产生切片

定义数组后,取出数组中一个片段,这个片段就是切片类型。

切片是指针,指向数组元素地址,修改切片的内容数组的内容会跟随变化。

当切片内容在增加时

如果增加后切片的长度没有超出数组,修改切片也是在修改数组(即和原数组指向同一个地址)

如果增加后切片的长度超出数组,会重新开辟一块空间放切片的内容

slice := [] int {1,23,4,5}
// 1.如果增加后切片的长度没有超出数组,修改切片也是在修改数组(即和原数组指向同一个地址)
sb := [...]int{1,2,3,4,5}
s :=sb[0:]
s[0]=3
fmt.Println(sb) // [3 2 3 4 5]
// 2. 如果增加后切片的长度超出数组,会重新开辟一块空间放切片的内容
sb := [...]int{1,2,3,4,5}
s :=sb[0:]
fmt.Printf("%p\n",&sb) //0xc00000c2a0
s = append(s,1,3,4)
fmt.Println(sb) // [1 2 3 4 5]
fmt.Println(s) // [1 2 3 4 5 1 3 4]
fmt.Printf("%p\n",&s) // 0xc000004078

c.【切片元素的删除】

Go语言标准库中没有提供删除的函数

切片也可以取其中的一段形成子切片,利用这个特性可以实现删除效果(会导致原来的内容也随之改变 所以删除的话就不要使用内容了)

slice := [5]int {1,2,3,4,5}
n:= 2
newSlice := slice[0:n]
newSlice = append(newSlice, slice[n+1:]...)
fmt.Println(newSlice) // [1 2 4 5]
newSlice[0]=8888
// 会导致原来的内容也随之改变 所以删除的话就不要使用内容了
fmt.Println(slice) // [8888 2 4 5 5]

d.【copy函数】

通过copy函数可以把一个切片内容复制到另一个切片中

 func copy(dst, src []Type) int

Go语言标准库源码定义如下

第一个参数是 目标切片 接收第二个参数内容

第二个参数是源切片,把内容拷贝到第一个参数中

copy时严格按照角标进行

使用cope函数去实现删除功能(这个方法可以保证原切片内容不变)

g:=[]int{1,2,3,4,5,6}
n := 2 //要删除元素的索引
newSlice := make([]int,n)
copy(newSlice,g[0:n])
newSlice = append(newSlice,g[n+1:]...)
fmt.Println(g) // [1 2 3 4 5 6]
fmt.Println(newSlice) // [1 2 4 5 6]

e.【排序】

// 1. 正常冒泡排序
func main() {
  arr:=[]int{1,234,5,1,52,2,4}
  BubbleSort(arr)
}
func BubbleSort(arr[]int)  {
  for i := 0; i < len(arr); i++ {
    for i1 := 0; i1 < len(arr)-i-1; i1++ {
      if arr[i1]<arr[i1+1] {
        continue
      }else {
        arr[i1],arr[i1+1] = arr[i1+1],arr[i1]
      }
    }
  }
  fmt.Println(arr) // [1 1 2 4 5 52 234]
}

f.【切片作为参数传递】

// 切片作为参数传递为引用传递,函数对切片的操作,同样也作用与原切片。
// 切片本身就是个引用变量 是一个指向某片内存的地址
func modify(array []int) {
  fmt.Println(array) // [1 2 3 4 5]
  array[0] = 10
  fmt.Println(array) // [10 2 3 4 5]
}
func main() {
  array := []int{1, 2, 3, 4, 5}
  modify(array)
  fmt.Println(array) // [10 2 3 4 5]
}

注意:使用append添加元素,容量足够,则在原基础之上添加数据,地址不会发生改变,容量如果不够,就会重新开辟一片空间。

a:=[]int{1,3,4}
s:=a
s = append(s, 1,2,3,4,5,6)
fmt.Printf("%p\n",a) // 0xc000012168
fmt.Println(a) // [1 3 4]
fmt.Printf("%p\n",s) // 0xc0000160f0
fmt.Println(s) // [1 3 4 1 2 3 4 5 6]
//-------------------------------------------
b:=make([]int,0,3)
b = append(b, 1,2,3)
s:=b
s[0]=888
fmt.Printf("%p\n",b) // 0xc000012168
fmt.Println(b) // [888 2 3]
fmt.Printf("%p\n",s) // 0xc000012168
fmt.Println(s) // [888 2 3]


相关文章
|
3天前
|
Go 索引
Go 语言切片(Slice)
Go 语言切片(Slice)
9 1
|
3天前
|
存储 Go Python
Go 语言结构体
Go 语言结构体
6 0
|
3天前
|
存储 Go
Go 语言指针
Go 语言指针
8 0
|
3天前
|
JSON Java Go
使用go语言中的内置库调试性能
【5月更文挑战第21天】本文介绍Go 语言提供了内置的 expvar 模块来输出度量数据,帮助定位性能瓶颈。与 pprof 不同,expvar 专注于应用的宏观状态,通过 HTTP 接口 `/debug/vars` 提供标准的 JSON 格式数据,包括自定义度量和内存统计等。通过 expvar,开发者可以轻松监控应用状态,如消息处理速率、内存使用等,而无需像 C++ 或 Java 那样手动实现。
20 0
使用go语言中的内置库调试性能
|
4天前
|
编译器 Go 索引
Go 语言数组
Go 语言数组
9 1
|
4天前
|
Go CDN
Go 语言变量作用域
Go 语言变量作用域
14 4
|
4天前
|
编译器 Go
Go 语言函数
Go 语言函数
14 7
|
4天前
|
自然语言处理 算法 关系型数据库
再谈go语言中字符转换效率问题
【5月更文挑战第20天】本文讨论了Go语言中类型转换的效率,特别是`byte`、`rune`和`string`之间的转换。`性能测试显示,从`[]byte`到`string`的转换,新版与旧版性能相当;但从`string`到`[]byte`,旧版快于新版两倍。此外,文章提到了Unicode校对算法(UCA)的版本差异可能带来的排序和大小写转换不一致问题,这在多语言环境中需要注意。
19 1
再谈go语言中字符转换效率问题
|
4天前
|
编译器 Go 索引
浅谈go语言中的符文字符处理工具
【5月更文挑战第20天】本文简述了Go 1.20之后的rune符文处理工具和函数,`unsafe`包新增了SliceData、String和StringData函数,支持直接将slice转换为array,明确了数组和结构体比较顺序。
19 1
浅谈go语言中的符文字符处理工具
|
5天前
|
Go
Go 语言循环语句
Go 语言循环语句
12 0