Go语言学习记录(持续更新)今日更新“make()“ “append()”“copy()”以及其它一些切片操作

变量

变量声明

1.普通声明

var a string //声明一个字符串类型变量---空值类型:“”(空字符串)
var b int//声明一个整型变量---空值类型:0
var c bool//声明一个布尔类型变量---空值类型:false

2.批量声明
这样就不用每次声明变量都写一遍var关键字,十分便捷

var (
	a string
	b int
	c bool
)

注:Go语言中,非全局变量声明了必须被使用,否则会报错

变量初始化

1.单个赋值

var a string = "like"
var b int = 20

在Go中,我们甚至可以不具体写出变量类型,直接赋值,它会为我们自动确定类型

var a = "like"
var b = 20

2.同时多个赋值
我们可以一次性初始化多个变量

var a, b = "like", 20

3.简短变量声明

a := "like"

等同于:

var a = "like"

注:简短变量声明只能在函数中使用

4.匿名变量
例如一个函数的返回值有两个,而其中一个我们并不关心,可以用匿名变量去参与赋值(实际上就是将返回值舍弃),匿名变量不占用内存,不占用命名空间,可以重复使用。
使用匿名变量可以防止我们产生没有使用的空闲变量,从而避免报错。

func ret() (int, string) {
	return 20, "like"
}
func main() {
	b, _ := foo()
	_, a := foo()
	fmt.Println("a=", a)
	fmt.Println("y=", b)
}

常量

声明方式类似普通变量,也可以单个赋值同时多个赋值

const pi = 3.14
const e = 2.71

等同于:

const (
	pi = 3.14
	e = 2.71
)

特殊用法:

const (
    a = 20
    b
    c 
)

利用这种方式,上面一行常量赋了值,下面的赋值进行省略,默认下面常量赋值与上面一行赋了值的相同,此处a, b, c的值全是20

iota:
Go语言中有一种叫做iota的常量计数器,在const里面首次声明它时,默认值是0,在const中每次多一行常量声明,iota的值加1

const (
	a = iota //0
	b        //1
	c        //2
	d        //3
)

为了方便大家理解iota的用法,这里给出几个例子:

const (
	a = iota //0
	b        //1
	_        //2 (被舍弃了)
	d        //3
)
const (
	a = iota //0
	b = 20   //20
	c        //20
	d        //20
)
const (
	a = iota //0
	b = 20   //20
	c = iota //2
	d        //3
)

基本数据类型

整型
类型描述
uint8无符号 8位整型 (0 到 255)
uint16无符号 16位整型 (0 到 65535)
uint32无符号 32位整型 (0 到 4294967295)
uint64无符号 64位整型 (0 到 18446744073709551615)
int8有符号 8位整型 (-128 到 127)
int16有符号 16位整型 (-32768 到 32767)
int32有符号 32位整型 (-2147483648 到 2147483647)
int64有符号 64位整型 (-9223372036854775808 到 9223372036854775807)
特殊整型
类型描述
uint32位操作系统上就是uint32,64位操作系统上就是uint64
int32位操作系统上就是int32,64位操作系统上就是int64
uintptr无符号整型,用于存放一个指针
占位符
func main() {
	a := 20
	fmt.Printf("%T\n",a)//输出数据类型
	fmt.Printf("%v\n",a)//输出存储值(任何数据类型都可以)
	fmt.Printf("%b\n",a)//输出2进制数
	fmt.Printf("%o\n",a)//输出8进制数
	fmt.Printf("%x\n",a)//输出16进制数
	b := "like"
	fmt.Printf("%s\n",b)//输出字符串
	
}
字符与字符串

组成每个字符串的单独元素为字符,用''表示,字符串用""表示

a := "like"
b := 'H'

Go语言字符有两种:

  1. uint8类型,也可叫做Byte类型,可代表ASCII码中的一个字符,占 8bit == 1Byte
  2. int32类型,也可叫做rune类型,可代表UTF-8字符,占 32bit == 4 Byte

当我们处理汉字或其他复杂文字时就要用到int32类型,因为一个汉字可能占据3或4个字节, 推断法赋值字符的时候默认使用in32

多行字符串(利用反引号进行赋值):

func main() {
    s1 := `第一行
     第二行
第三行
`
fmt.Println(s1)
}

输出结果为:

第一行
     第二行
第三行

反引号会保留其区间内所有字符

修改字符串
Go语言无法直接修改字符串,要想进行修改,首先要将其转换成[]byte或者[]rune型,修改后再转换回string类型,但两种转换都会需要新分配的内存来复制字节数组。

func main() {
	a := "like"
	// 强制类型转换
	S1 := []byte(s1)
	S1[0] = 'd'
	fmt.Println(string(S1))//输出结果变成 dike

	b := "黑猫"
	S2 := []rune(b)
	S2[0] = '白'
	fmt.Println(string(S2))//输出结果变成 白猫
}
类型转换

Go语言只有强制类型转换,且布尔类型bool不可以进行类型转换

func main(){
 	var a = 20
	fmt.Printf("%T\n",a)//输出结果为 int
	b := float64(a)
	fmt.Printf("%T\n",b)//输出结果为 float64
}

流程控制

1.if else 分支结构

func main() {
	a := 60
	if a >= 80 {
		fmt.Println("A")
	} 
	else if score > 70 {
		fmt.Println("B")
	} 
	else {
		fmt.Println("C")
	}
}

if 条件判断特殊用法:
可以在条件判断前加入一个执行语句,先执行再判断(注意作用域,在 if 后声明的变量只在 if 中有效)

func main() {
	if a := 60; a >= 80 {
		fmt.Println("A")
	} 
	else if score > 70 {
		fmt.Println("B")
	} 
	else {
		fmt.Println("C")
	}
}

2.for 循环结构

func main() {
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}
}

当然,我们也可以把 i := 0 写在 for 之前,将 i++ 写在for循环中。
Go语言中没有while循环,只有 for 循环。

数组

数组定义
var a [10]int//创建一个长度为10的int类型数组

注意:数组长度也是数组类型的一部分,不同长度的数组,类型不一样
定义数组时,[]只能是常量,不能是变量。

func main(){
	var b [20]int
	var c [30]int
	fmt.Printf("%T\n",b)//输出结果为 [2o]int
	fmt.Printf("%T\n",c)//输出结果为 [30]int
}
数组初始化

1.普通赋值,提前确定好数组长度

func main() {
	var a [3]int                                //声明之后,数组会初始化成零值
	var b = [3]int{0, 1, 2}                     //利用确切值进行赋值
	var c = [3]string{"like", "apple", "pear"}  
	fmt.Println(a)                              //[0 0 0]
	fmt.Println(b)                              //[0 1 2]
	fmt.Println(c)                              //[like apple pear]
}

2.自动推断数组长度

func main() {
	var a [3]int
	var b = [...]int{1, 2}
	var c = [...]string{"like", "apple", "pear"}    //...会根据后面赋值情况自动推断数组长度
	fmt.Println(a)                                  //[0 0 0]
	fmt.Println(b)                                  //[1 2]
	fmt.Printf("%T\n", b)                           //[2]int
	fmt.Println(c)                                  //[like apple pear]
	fmt.Printf("%T\n", c)                           //[3]string
}

3.利用索引进行初始化

func main() {
	a := [...]int{1: 1, 3: 4}       //给a[1]赋值 1,给a[3]赋值 4
	fmt.Println(a)                  //[0 1 0 4]
	fmt.Printf("%T\n", a)           //[4]int
}
数组的遍历

1.普通for循环

func main() {
	var a = [...]string{"apple", "like", "pear"}
	for i := 0; i < len(a); i++ {
		fmt.Println(a[i])
	}
}

2. for range方法

func main() {
	var a [10]int
	for index, value := range a {//第一个变量存索引值,第二个存数组对应索引存储的值
		fmt.Println(index, value)
	}
}
多维数组
func main() {
	a := [3][2] int{
		{1, 2},
		{3, 4},
		{5, 6},    //如果括号在下一行,这里要加逗号,同一行则不用
	}
	fmt.Println(a) //[[1 2] [3 4] [5 6]]
	fmt.Println(a[2][1]) //6
}

多维数组只有第一个维度可以利用推断法推断长度:

func main() {
	b := [...][2]int{{7,8},{9,10},{10,11}}
	fmt.Println(b)
}

注:数组是一个值类型的变量,赋值和传参会复制整个数组, 支持“==“、”!=” 操作

切片

切片是一个存储相同类型元素长度可变的序列,它不是数组!!!
它是一个引用类型,可以理解为是一种指针。

声明
var a []int//声明一个叫 a 的int类型切片

切片初始化零值不管什么类型切片,都是nil(空),虽然输出结果有点像Java中的空链表,但它就是“空”

func main() {
	var a []string              
	var b []int             
	var c = []bool{false, true} 
	fmt.Println(a)              //[]
	fmt.Println(b)              //[]
	fmt.Println(c)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //true
	fmt.Println(c == nil)       //false
	// fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
}
初始化

1.直接赋值

func main() {
	a := []int{1,2,3,4}
	b := []string{"like","apple","pear"}
	c := [][]int{{1,2},{3,4},{5,6}}
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
}

2.由数组切割得到切片

func main() {
	a := [...]int{1,3,5,7,9,11}
	b := a[0:4]//通过数组切割得到切片,索引值左包含,右不包含
	fmt.Println(b)//[1 3 5 7]
}

注!!!:当我们创建一个新的切片并且直接赋值时,实际上是在底层创建了一个等长数组,并将等长数组从头切到尾得到一个切片,切片相当于只是一个框,它里面没有实际存储的值,存储的值都在底层数组里,而我们通过这个框(指针)索引出数组里的值。

几种特殊切割方式:

func main() {
	a := [...]int{1,3,5,7,9,11}
	b := a[:4]// = a[0:4]
	d := a[2:]// = a[2:len(a)]
	e := a[:]// = a[0:len(a)]
	fmt.Println(b)//[1 3 5 7]
	fmt.Println(d)//[5 7 9 11]
	fmt.Println(e)//[1 3 5 7 9 11]
}

3.从切片里切割得到切片

func main() {
	a := [...]int{1,3,5,7,9,11}
	b := a[0:5] //[1 3 5 7 9]
	c := b[1:3] //[3 5]
	fmt.Println(c)
}

注:如果切片底层数组的值发生变化,那么对应上层的切片返回值也会发生变化

切片长度和切片容量

我们可以使用len()函数求切片长度,用cap()函数求切片容量

func main() {
	a := [8]int{1,2,3,4,5,6,7,8}
	b := a[1:5] // [2 3 4 5]
	c := b[1:2] // [3]
	fmt.Println(cap(b))// 7
	fmt.Println(len(b))// 4
	fmt.Println(cap(c))// 6
	fmt.Println(len(c))// 1
}

这里我详细说一下切片的长度以及容量含义,解释一下为何上面的输出会是这些值。
就像我之前所说的那样,切片相当于一个框,它需要底层有数组做支撑才有意义:
长度:切片框住元素个数
容量:切片第一个指针索引的位置到底层数组最后一个位置容量大小
注:像 c 这种切片的切片,他的底层数组和被切切片一样,索引的时候在被切切片处索引,长度定义一样,容量大小依照原底层数组为准,看新切片索引位置在底层数组何处,从其开始到结尾容量大小。
在这里插入图片描述

利用Make()函数创建切片
a := make([]Type,len,cap)//创建一个类型为“Type”,长度为“len”,容量为“cap”的切片
b := make([]Type,c)
//假如[]Type后面只写了一个数“c”,表明长度和容量都为c

用make()方法创建出切片,会自动生成一个跟容量同长度的全是零值的底层数组。
make()方法内的“len”和“cap”可以用变量赋值,也可用常量赋值,和数组不同。

func main() {
	var a []int = make([]int,5)//生成一个长度和容量均为5的int类型切片
	var b [5]int//生成数组,[]中只能写常量,不能写变量
	c := make([]int,3,10)//生成一个长度为3,容量为10的int类型切片
	fmt.Printf("\nlen(a): %d\ncap(a): %d\n",len(a),cap(a))
	//len(a): 5    cap(a): 5
	fmt.Println(a)//[0 0 0 0 0]
	fmt.Println(b)//[0 0 0 0 0]
	fmt.Printf("\nlen(c): %d\ncap(c): %d\n",len(c),cap(c))
	//len(c): 3    cap(a): 10
	fmt.Println(c)//[0 0 0]
	
	d := 2
	e := make([]int,d)
	fmt.Println(e)// [0 0]
}
切片之间不可进行比较

不能使用==来比较两个切片元素是否完全相等,切片只可和nil比较
注:一个nil值的切片,长度和容量均为0,但是长度和容量均为0的切片不一定为nil

var a []int         //len(a)=0;cap(a)=0;a==nil
b := []int{}        //len(b)=0;cap(b)=0;b!=nil
c := make([]int, 0) //len(c)=0;cap(c)=0;c!=nil
var d [0]int        //长度为0的数组,没有存任何东西,但是也被分配了内存

nil的本质在于有无分配内存。
第一行创建了一个空切片,没有底层数组,没有分配内存,所以为空
第二行创建了一个空切片,但是有底层数组,底层数组跟“d”类似,不为空,被分配了内存
第三行创建了一个空切片,但是有底层数组,底层数组跟“d”类似,不为空,被分配了内存

但是对于三者而言,他们的长度和容量均为0。
因此要判断一个切片是否无值,要用len(a) == 0,不能用a == nil

切片值的修改

原切片的值被赋给新切片后,二者共用同一个底层数组,修改切片中的值,相当于更改底层数组的值,因此两个切片的值都会发生变化。

func main() {
	a := []int{1,2,3} 
	b := a           //将切片a的值直接赋值给b,a和b共用一个底层数组
	fmt.Println(a,b) //[1 2 3] [1 2 3]
	b[0] = 10
	fmt.Println(a,b) //[10 2 3] [10 2 3]
}
append() 方法扩容切片

append() 方法可以一次添加1个值或者多个值,也可将其他切片的值添加进去。
注:同时添加其他切片的值和确切数字,变量是不允许的
例如:
a = append(a, b…, 10) 这样会报错
a = append(a, b…, e)这样也会报错

但是我们可以同时添加变量和确切数字

func main(){
	var a []int
	a = append(a, 1)       //[1]
	a = append(a, 2, 3, 4) //[1 2 3 4]
	b := []int{5, 6, 7}  
	a = append(a, b...)    //[1 2 3 4 5 6 7]
	e := 11
	a = append(a, e, 12)   //[1 2 3 4 5 6 7 11 12]
}
func main() {
	a := []int{1,2,3,4,5}
	b := a[0:2]
	for i:=0;i<len(a);i++{
		b = append(b,9)
		fmt.Println("value of b:",b,"cap of b:",cap(b))
		fmt.Println("valie of a:",a,"cap of a:",cap(a))
		fmt.Println("---------------")
	}
}

运行结果:
在这里插入图片描述
刚开始我们知道切片ab共用一个底层数组。
之后利用append()方法为切片b扩容。
刚开始扩容时,切片b的索引没有超过底层数组的容量,因此,为其扩容赋值相当于改变切片b对于底层数组的索引范围并且改变底层数组中存储的值。
再继续扩容后,发现底层数组容量不够,新出现的索引会越界,这时append()方法会单独为切片b创建一个新的扩容后的底层数组,并把原来的值拷贝进去,根据图所示,发现cap(b)变成了10,这就是创建了一个新底层数组给切片b使用的结果,但是原来切片a的底层数组不会继续因为切片b的扩容而改变,因为切片b已经换了一个底层数组去操作。

copy()方法复制切片

copy(des,src) 将切片src的值,复制到 切片des中
实际上就是将src索引范围内底层数组的值,复制到des索引范围内底层数组。

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := make([]int, 5, 5)
	copy(b, a)     
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 99
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[99 2 3 4 5]
}

两个切片范围不一样也可以copy(),多余的部分舍弃

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := a[1:3]    //[2 3]
	d := []int{9,10,11}
	copy(b, d)   
	fmt.Println(b) //[9 10]  
	fmt.Println(a) //[1 9 10 4 5]
}
删除切片中的元素

Go语言没有专门删除切片元素的方法,但是这个可以用 append() 方法解决:

func main() {
	a := []int{1, 2, 3, 4, 5, 6, 7, 8}//我们想把4删除
	a = append(a[:3], a[4:]...)
	fmt.Println(a) //[1 2 3 5 6 7 8]
}

为了更好地理解是如何利用 append() 方法的,再举一个例子:

func main() {
	a := [...]int{1, 3, 5}
	b := a[:]
	fmt.Println(b)//[1 3 5]
	b  = append(a[:1],a[2:]...)
	fmt.Println(b)//[1 5]
	fmt.Println(a)//[1 5 5]
}

实质上就是创建一个基于底层数组的切片a[:1],索引范围就是“0”,然后在其基础上扩容,将底层数组从a[2]到结尾的值扩充进新创建的切片中,注意之前上面讲的append()方法扩充,当扩充不会导致索引越界时,只会发生覆写,因此这里会看到底层数组的值发生了改变,最后将新生成的切片返回给切片b,而切片b实际上就是底层数组改变值后的a[:2]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值