golang基础汇总(一), 对变量、常量、操作符、分支控制、数组、切片,map、指针的使用详解。

基础

变量

变量是几乎所有编程语言中最基本的组成元素。从本质上说,变量相当于是对一块数据存储空间的命名,程序可以通过定义一个变量来申请一块数据存储空间,之后可以通过引用变量名来使用这块存储空间。

变量存储:

	等号 左边的变量,代表 变量所指向的内存空间。	  (写)

	等号 右边的变量,代表 变量内存空间存储的数据值。(读)

变量声明与作用域

  • 变量声明
1var a int = 10
2var a = 10   //编译器,类型推导出a的类型
3	a :=10   //函数内使用  ,等价于  var a int ; a=10  
  • 声明多个变量
1var (
2	v1 int
3	v2 string
4)
  • 变量赋值与多重赋值
1var  v1 int
2var  i int 
3var  j int 
4v1=10 //变量
5i,j=j,i //多重赋值	
  • 匿名变量
1	func getName() (firstName,lastname, nickName string) {
2		return "","nonfu", "crmao"
3	}
4	//_为逆名变量,忽略不接收的作用
5	_,_, nickName := getName()       
  • 变量作用域
 1package main
 2import "fmt"
 3var a string  //声明变量
 4var b int     //全局变量
 5var c bool
 6//省略多个var关键字,写成一个
 7var (
 8	d string = "ddd"
 9	e =3
10)
11// 简写  ,但是这种写法这能写在函数内
12func  variableShort(){
13	a,b,c :=1,true,"string_c"
14	fmt.Printf("%v,%v,%v\n",a,b,c)
15}
16//类型推导
17func variableTuiduan(){
18	var a = "abc"
19	fmt.Printf(" a的类型是%T\n",a)  // a的类型是string
20}
21
22func getName() (a, b string) {
23	return "nonfu", "crmao"
24}
25func main() {
26	fmt.Println("hello world")
27	fmt.Println(a,b,c)
28	fmt.Printf("%q,%d,%v\n",a,b,c)  //"",0,false 有初始值
29	variableTuiduan()
30	variableShort()
31	fmt.Println(d,e)
32	//_为逆名变量,忽略不接收的作用
33	_, nickName := getName()
34	fmt.Println(nickName)
35}

占位符的使用

 1package main
 2
 3import "fmt"
 4
 5func zhanwei() {
 6	a:=10//int
 7	fmt.Printf("%d\n",a)   //%d 整数
 8	//var b float64 =10
 9	b:=10.0//float64
10	fmt.Printf("%f\n",b)  //%f 浮点数
11	var c bool =true
12	fmt.Printf("%t\n",c)  //%t bool
13	var d byte ='A'
14	fmt.Printf("%c\n",d)  //%c 字符
15	var e string ="hello" 
16	fmt.Printf("%s\n",e) //%s 字符串
17	fmt.Printf("%p\n",&a)
18
19	//%T  打印变量对应的数据类型
20	fmt.Printf("%T\n",a) //%T 数据类型
21	fmt.Printf("%T\n",b)
22	fmt.Printf("%T\n",c)
23	fmt.Printf("%T\n",d)  //uint8==byte
24	fmt.Printf("%T\n",e)
25	//%% 会打印一个%
26	fmt.Printf("35%%")
27}
28func main(){
29	//计算机能够识别的进制  二进制 八进制 十进制 十六进制
30	a:=123//十进制数据
31	b:=0123//八进制数据 以0开头的数据是八进制
32	c:=0xabc//十六进制  以0x开头的数据是十六进制
33	//go语言中不能直接表示二进制数据
34
35	fmt.Println(a)
36	fmt.Println(b)
37	fmt.Println(c)
38	fmt.Println("------------------------------")
39	//%b 占位符 表示输出一个二进制数据
40	fmt.Printf("二进制值为:%b\n",a)
41	fmt.Printf("二进制值为:%b\n",b)
42	fmt.Printf("二进制值为:%b\n",c)
43	fmt.Println("------------------------------")
44	//%o 占位符 表示输出一个八进制数据
45	fmt.Printf("8进制表示:%o\n",a)
46	fmt.Printf("8进制表示:%o\n",b)
47	fmt.Printf("8进制表示:%o\n",c)
48	fmt.Println("------------------------------")
49	//%x %X 占位符 表示输出一个十六进制数据  %x小写 %X 大写
50	fmt.Printf("16进制大写表示:%X\n",a)
51	fmt.Printf("16进制大写表示:%X\n",b)
52	fmt.Printf("16进制大写表示:%X\n",c)
53	fmt.Println("------------------------------")
54	s:=' '
55	fmt.Printf("%T\n",s)  //int32
56	ceshi :='a'
57	fmt.Printf("%T\n",ceshi)  //int32
58	fmt.Println("----------------------------")
59	zhanwei()
60}

变量类型

  • 基础类型

    • 布尔类型: bool

      1true,false
      
    • 整形: uint8,uint16,uint32,uint64 ,int8,int16,int32,int64,int,uint,byte,rune

       1uint8  8位无符号整型(0 to 255)
       2uint16 16位无符号整型(0 to 65535)
       3uint32 32位无符号整型(0 to 4294967295)
       4uint64 64位无符号整型(0 to 2^64-1)
       5int8   8位有符号整型(-128 to 127)
       6int16  16位有符号整型
       7int32  32位有符号整型
       8int64  64位有符号整型
       9平台相关的类型 uint,int   (32或者是64位)
      10uintptr 一个足够表示指针的无符号整数 当不同类型进行混合运算的时候,需要进行明确的显示的类型转换
      11byte uint8的别名
      12rune uint32的别名
      
    • 浮点类型: float32,float64

      1float32 32位浮点类型
      2float64 64位浮点类型
      
    • 复数类型: complex64,complex128

      1complex64和complex128就是用来表示我们数学中的复数,复数实部和虚部,complex64的实部和虚部都是32位float,complex128的实部和虚部都是64位float 
      
  • 字符串类型: string

  • 字符类型 byte ,rune

    1  rune代表单个unicode字符
    2  byte代表 utf8字符串的单个字节的值 
    
  • 错误类型:error

  • 复合类型 (派生类型)

    • 指针(pointer)
    • 数组 (array)
    • 切片 (slice)
    • 结构体 (struct)
    • 字典 (map)
    • 接口(interface)
    • 通道(chan)
    • 函数 (func)​

类型表示

1var value2 int32
2value1 :=64 //value1自动被推导程int类型
3value2 = value1 //编译报错
4//类型转换
5value2 = int32(value1)

数值运算

1和c语言一样 +,-,*,/,%, ++,--

比较运算符

1> ,<,>=,<=,==,!=     和c语言完全一致

位运算符

1x << y   左移                 124<<2  // 结果为496
2x >> y   右移                 124>>2  // 结果为31
3x ^ y    异或                 
4x & y    与
5x | y    或
6^x       取反                 c语言 ~x表示,go语言用^x表示

浮点类型

1fvalue :=12.0   //自动推导为float64

复数

还没找到使用场景…

字符串

 1var str = "hello world"
 2
 3 ch :=str[0] //取第一个字符
 4
 5fmt.Prinf("%d",len(str))   //字节数
 6
 7str[0]='X'  //编译错误,不能重新赋值
 8var str1 = "append str"
 9
10str1=str + str1 

字符串遍历

 1func foreachstr(){
 2   var str="hello world 测试"
 3   for i:=0;i<len(str);i++{
 4      //fmt.Println(str[i])
 5      fmt.Printf("%c",str[i]) //中文乱码
 6   }
 7   
 8   var runestr =[]rune(str)
 9   for _,v :=range runestr {
10      fmt.Printf("%c",v)
11   }
12}

常量

在程序运行过程中其值不能发生改变的量 称为常量

字面常量

1字面常量是指程序中硬编码的常量,如:
2	-12     //可以赋给int unint int32 int64 float32 float64 complex64 complex128
3	3.14     //浮点类型的常量
4	3.2+12i //复数类型的常量
5	true   // 布尔类型的常量
6	"foo"  //字符串常量

常量定义

1const PI float64 = 3.1415926
2const zero = 0.00 
3const (
4	size int64 = 1024
5	eof = -1       //无类型整数常量
6)
7const u,v float32=0.3 //u=0.00, v=3.0 常量的多重赋值
8const a,b,c = 3, 4, "foo"  //无类型整形 和字符串常量

内存中的位置

  1. 内存分为栈区,堆区 ,数据区,代码区
  2. 常量的存储位置在数据区,在数据区中的常量区
  3. 常量的存储位置在数据区 不能通过& 取地址来访问
  4. 常量的值不允许修改

常用无法使用取地址符,常量不同与变量在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为数据指令使用

作用域

局部常量

全局常量

 1package main
 2import "fmt"
 3//全局常量
 4const h = 'h'
 5//一次定义多个常量
 6const (
 7	f = "float"
 8	g =3
 9)
10
11func cons() {
12   //常量定义和使用
13   //在程序运行过程中其值不能发生改变的量 成为常量
14   //常量的存储位置在数据区
15   //栈区 系统为每一个应用程序分配1M空间用来存储变量  在程序运行结束系统会自动释放
16   var s1 int =10
17   var s2 int =20
18   //常量的存储位置在数据区 不能通过& 取地址来访问
19   const a int = 10
20   fmt.Println(&s1)
21   fmt.Println(&s2)
22   //a=20//常量的值不允许修改
23   fmt.Println(a)
24}
25func main(){
26   //内存分为栈区,堆区 ,数据区,代码区
27   //常量在数据区中的常量区
28   //常量一般用大写字母表示
29   	fmt.Println(f,g,h) //float 3 104  //h对应的码值是104
30   	
31   const MAX int =10
32   b:=20
33   c:=MAX+b
34   fmt.Println(c)
35   fmt.Println(123)  //字面常量
36   fmt.Println("hello world") //字面常量
37   //硬常量 32
38   d:=c+32
39   e:="hello"
40   e=e+"world"
41   fmt.Println(d)
42   fmt.Println(e)
43   cons()
44}

预定义常量

go语言预定义了这些常量: true,false和iota

iota比较特殊,可以被认为 是一个可被编译器修改的常量,在每一个const关键词出现时被重置为0,然后在下一个const出现之前,每出现一个iota,其所代表的数字会自动增1

 1const (
 2	c0 = iota //c0 == 0
 3	c1 = iota //c2 == 1
 4	c2 = iota //c2 == 2
 5)
 6const (
 7	a = 1 << iota  // a == 1 (iota在每个const开头被重置为0)
 8	b = 1 << iota  // b == 2 
 9	c = 1 << iota  // c == 4 
10)
11const (
12	u = iota * 42         // u == 0
13	v float64 = iota * 42 // v == 42.0
14	w = iota * 42         // w == 84
15)
16
17const 如果两个const的赋值语句时一样的那么可以省略后i 个赋值表达式
18const (
19	c0 = iota  //c0==0
20	c1         //c1==1
21	c2         //c2==2
22	_          
23	c3         //c3==4
24)
25const (
26	a = 1 << iota     //a = 1
27	b                 //b == 2
28	c                 //c == 4
29)

枚举

枚举中包含了一系列相关的常量,比如下面关于一个星期中每天的定义。Go 语言并不支持其他语言用于表示枚举的 enum 关键字,而是通过在 const 后跟一对圆括号定义一组常量的方式来实现枚举

 1const (
 2    Sunday = iota 
 3    Monday 
 4    Tuesday 
 5    Wednesday 
 6    Thursday 
 7    Friday 
 8    Saturday 
 9    numberOfDays
10)

和函数体外声明的变量一样,以大写字母开头的常量在包外可见(类似于 public 修饰的类属性),比如上面介绍的 PiSunday 等,而以小写字母开头的常量只能在包内访问(类似于通过 protected 修饰的类属性),比如 zeronumberOfDays 等,后面在介绍包的可见性时还会详细介绍。函数体内声明的常量只能在函数体内生效

综合代码

 1package main
 2
 3import "fmt"
 4
 5func iotafunc() {
 6   const(
 7      a=iota //0   从0开始
 8      b=iota //1
 9      c=iota //2 
10      d=iota //3
11   )
12
13   fmt.Println(a)
14   fmt.Println(b)
15   fmt.Println(c)
16   fmt.Println(d)
17}
18
19func enums(){
20	const (
21		php = iota
22		java
23		c
24		python
25		_
26		javascript
27	)
28	const (
29		b = 1 << (10*iota)
30		kb
31		mb
32		gb
33		tb
34		pb
35	)
36	fmt.Println(php,java,c,python,javascript)  // 0 1 2 3 5
37	fmt.Println(b,kb,mb,gb,tb,pb) //1 1024 1048576
38	//1073741824 1099511627776 1125899906842624
39}
40func main(){
41   //如果定义枚举是常量写在同一行值相同 换一行值加一
42   //在定义枚举时可以为其赋初始值 但是换行后不会根据值增长
43   const(
44      a=10
45      b,c=iota,iota
46      d,e
47   )
48
49   fmt.Println(a) //10
50   fmt.Println(b) //1
51   fmt.Println(c) //1
52   fmt.Println(d) //2
53   fmt.Println(e) //2
54   iotafunc()
55   enums()
56}

数据类型本质:固定内存大小的别名

数据类型的作用:编译器预算对象(变量)分配的内存空间大小

if,switch,for,goto,for range

if

注意:

  1. 条件语句不需要()

  2. 花括号必须存在 ,并且与 if , else 同行

  3. 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔

 1package main
 2import (
 3    "fmt"
 4    "io/ioutil"
 5)
 6func main(){
 7    var filename = "abc.txt"
 8    if content,err :=ioutil.ReadFile(filename); err != nil {
 9         fmt.Println(err)
10    }else{
11        fmt.Printf("%s\n",content)
12    }
13    // fmt.Printf("%s\n",content) 报错, content作用域在if里面
14    a :="bcf1"
15    if a=="abc" {
16        fmt.Println("abc")
17    } else  if  a=="bcf" {
18        fmt.Println("bcf")
19    } else  {
20        fmt.Println("else")
21    }
22}

switch

  1. switch 会自动break, 除非使用fallthrough

  2. switch 可无需表达式,case 中成立即跳出

  3. fallthrough只穿透一层

如果case表达式中子表达式的结果值是无类型的常量,那么它的类型会被自动地转换为switch表达式的结果类型

1value2 := [...]int8{0, 1, 2, 3, 4, 5, 6}
2switch value2[4] {  //int8 
3case 0, 1: //自动转换成int8比较
4	fmt.Println("0 or 1")
5case 2, 3:
6	fmt.Println("2 or 3")
7case 4, 5, 6:
8	fmt.Println("4 or 5 or 6")
9}
 1package main
 2import (
 3    "fmt"
 4  
 5)
 6//switch 会自动break, 除非使用fallthrough
 7//switch 可无需表达式,case 中成立即跳出
 8func score(score int ) string {
 9    var  result = ""
10    switch {
11    case score < 60 :
12        result = "F"
13    case score < 70 :
14        result = "E"
15    case score < 80 :
16        result = "C"
17    case score < 90 :
18        result = "B"
19    case score <= 100 :
20        result = "A"
21    default :
22            panic(fmt.Sprintf("错误的分数%d",score))
23    }
24    return result
25}
26//根据运算符计算
27func cal(op string,a int,b int) int{
28    var result int
29     switch op {
30        case "+" :
31            result = a+b
32         case "-" :
33            result = a-b
34         case "*" :
35            result = a*b
36         case "%" :
37            result = a%b
38         case "/" :
39            result = a/b
40    }   
41    return result
42}
43// case 可以有多个值
44func manycase(a int) {
45        switch a {
46            case 1,2,3 :
47                fmt.Println(a)
48            case 4,5,6 :
49                fmt.Println(a)  
50        }
51}
52// fallthrough 用法
53func fallthroughswitch(a int){
54    switch a {
55        case 1,2,3 :
56            fmt.Println("1,2,3其中一个")
57                fallthrough   //只穿透一层
58        case 4,5,6 :
59            fmt.Println("4,,5,6其中一个")   
60        case 7 :
61            fmt.Println("7")
62    }
63}
64func main(){
65    var a = 4
66    var b = 2
67    fmt.Println(
68        score(50),
69        score(60),
70        score(70),
71        score(80),
72        score(90),
73        score(100),
74        // score(101),  //101 panic 
75    )
76    fmt.Println(
77        cal("+",a,b),
78        cal("-",a,b),
79        cal("*",a,b),
80        cal("%",a,b),
81        cal("/",a,b),
82    )
83    manycase(1)  //1 
84    fallthroughswitch(1) //1,2,3其中一个   4,5,6其中一个
85}

type switch

 1func typeswitch() {
 2	var a interface{}
 3	var b int = 10
 4	a = b
 5	switch c := a.(type) {    //  a.(type) 只能用来switch 内, 不然报错
 6		case int:
 7			fmt.Printf("x的类型市%T",c)
 8		case nil:
 9			fmt.Printf("x的类型市%T",c)
10	}
11}

for

一般 for 循环 为 for 单次表达式;条件表达式;末尾循环体 {中间循环体;}

go语言中 for循环没有()

go语言中只有for循环

可以只有条件表达式

也可以什么都没有表示无限循环

break 后 末尾循环体不执行

一个普通的for 循环

1func sum() int{
2    sum :=0
3    for i :=1 ;i <=100; i++ {
4        sum +=i
5    }
6    return sum
7}

无限循环

1    for {
2           
3    }
4    for ;;{
5        
6    }

while循环

直到i>200结束

1    var i=100
2    for {
3        if(i >200){
4            break
5        }
6        fmt.Println(i)
7        i++
8    }

do while演示

1    var i=100
2    for {
3     	    fmt.Println(i)
4            i++
5        if(i >200){
6            break
7        }
8    }

只要条件语句

1	var i=10
2	for i >1 && i<20 {
3		i++
4		fmt.Println(i)
5	}

综合代码

 1package main
 2import (
 3    "fmt"
 4    "strconv"
 5    "os"
 6    "bufio"
 7)
 8//一个普通的for 循环示例, 没有()
 9func sum() int{
10    sum :=0
11    for i :=1 ;i <=100; i++ {
12        sum +=i
13    }
14    return sum
15}
16//可以初始化,条件,递增 ... 显示100-200 ,到200退出
17func forever(){
18    var i=100
19    for {
20        if(i > 200){
21            break
22        }
23        fmt.Println(i)
24        i++
25    }
26}
27    /*
28            2   10
29            2    5    0
30            2    2    1
31            2    1    0 
32                 0    1
33                 */
34//可以没有初始值 ,整数转二进制
35func converttobin(a int) string{
36     result :=""
37    for ;a > 0 ; a=a/2 {
38        result = strconv.Itoa(a % 2) + result
39    }
40    return result
41}
42
43
44//一行一行读文件
45func printfile(){
46    file, err := os.Open("test.txt")
47    if err != nil {
48            panic(err)
49    }
50    scaner := bufio.NewScanner(file)
51    for scaner.Scan() {
52        fmt.Println(scaner.Text())  //一行一行读,读到结束false,跳出循环
53    }
54}
55func main(){
56    fmt.Println(sum())  //5050
57    //forever()
58    fmt.Println(converttobin(10))
59    printfile()
60}

goto

1 	var j=10
2	 if j<20 {
3	 	goto HERE
4	 } 
5	 fmt.Println("hello") // 不执行
6	 HERE:
7	 fmt.Println("GOTO")

for range

  1. range表达式只会在for语句开始执行时被求值一次,无论后边会有多少次迭代;

  2. range表达式的求值结果会被复制,也就是说,被迭代的对象是range表达式结果值的副本而不是原值。

 1numbers2 := [...]int{1, 2, 3, 4, 5, 6}
 2maxIndex2 := len(numbers2) - 1
 3for i, e := range numbers2 {   //e是副本 不会变了
 4	if i == maxIndex2 {
 5		numbers2[0] += e
 6	} else {
 7		numbers2[i+1] += e
 8	}
 9}
10fmt.Println(numbers2)
11
12//i=0 , numbers2[1]=3,numers[0]=1
13//i=1  numbers2[2]=3+2=5;  //e 是副本   1,3,5 4,5,6
14//i=2 numerbs[3]=4+3 ;// 7        1,3,5 7,5,6
15//i=3 numers[4] =5+4 //9        1,3,5,7,9,6
16//i=4 numers[5]=6+5  //11   1,3,5,7,9,11
17//i=5 numers[0]=             7,3,5,7,9,11

for range中的坑

https://studygolang.com/articles/9701

根本原因在于for-range会使用同一块内存去接收循环中的值

数组

数组是所有语言编程中最常用的数据结构之一,Go 语言也不例外,与 PHP、JavaScript 等弱类型动态语言不同,在 Go 语言中,数组是固定长度的、同一类型的数据集合。数组中包含的每个数据项被称为数组元素,一个数组包含的元素个数被称为数组的长度。

数组声明

1 1. var 数组名 [数组大小]数据类型
2  var arr [3]int = {1,2,3}
3  
4 2. 通过 := 对数组进行声明和初始化:
5      arr2 :=[3]int{1,3,5}
6 3.  省略长度 
7   arr3 :=[...]int{1,2,3,4}
8	

数组在初始化的时候,如果没有填满,则空位会通过对应的元素类型空值填充:

1a := [5]int{1, 2, 3}
2fmt.Println(a) //[1,2,3,0,0]

指定下标

a := [5]int{1: 3, 3: 7}
[0 3 0 7 0]
 1//数组的声明方式 
 2func declareArr(){
 3   //var 数组名 [数组大小]数据类型
 4   var arr [3]int
 5   arr2 :=[3]int{1,3,5}
 6   arr3 :=[...]int{1,2,3,4}
 7   fmt.Println(arr)
 8   fmt.Println(arr2)
 9   fmt.Println(arr3)
10   arr[0]=1
11   arr[1]=2
12   arr[2]=3
13// arr[3]=4 //越界,下标从0开始 
14   fmt.Println(arr)
15   arr4 :=[...]string{1:"string2",0:"string1",2:"string3",4:"string5"}
16   fmt.Println(arr4)  //有5个值 [string1 string2 string3  string5],下标是3的是""
17   fmt.Printf("%q\n",arr4)  //["string1" "string2" "string3" "" "string5"]
18   /*
19   [0 0 0]    //有默认值
20   [1 3 5]
21   [1 2 3 4]
22   [1 2 3]
23   */
24}

一维数组遍历

 1/*
 2   遍历数组
 3*/
 4func  foreachArr(){
 5   arr4 :=[...]string{1:"string2",0:"string1",2:"string3",4:"string5"}
 6   for _,value :=range arr4 {
 7      fmt.Printf("%q\n",value)  
 8   }
 9     /*
10		"string1"
11		"string2"
12		"string3"
13		""
14		"string5"
15   */

二维数组遍历

 1/*
 2   二维数组
 3*/
 4func foreachTwoArr(){
 5      var arr [3][4]int
 6      arr[0][0]=1
 7      arr[0][1]=1
 8      fmt.Println(arr) //  [[1 1 0 0] [0 0 0 0] [0 0 0 0]]
 9}
10
11   var    arr [3][4]int
12   arr[0][0]=1
13   arr[0][1]=2
14   arr[0][2]=3
15   arr[0][3]=4
16   arr[1][0]=1
17   arr[1][1]=2
18   arr[1][2]=3
19   arr[1][3]=4
20   arr[2][0]=1
21   arr[2][1]=2
22   arr[2][2]=3
23   arr[2][3]=4
24   //二维数组遍历
25   for key :=range arr {
26      for key1 :=range arr[key] {
27         fmt.Println(arr[key][key1])
28      }
29   }
30   fmt.Println(arr)
31}

数组内存分布

 1//数组内存分布
 2   /*
 3arr的地址是0xc420010180 和 第一个元素所在地址相同
 4arr[0]的地址是0xc420010180
 5arr[1]的地址是0xc420010188
 6arr[2]的地址是0xc420010190
 7数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8
 8int32->4...
 9   */
10func  innerAddress(){
11   var arr [3]int
12   arr[0]=1
13   arr[1]=2
14   arr[2]=3
15   fmt.Printf("arr的地址是%p\n",&arr)
16   fmt.Printf("arr[0]的地址是%p\n",&arr[0])
17   fmt.Printf("arr[1]的地址是%p\n",&arr[1])
18   fmt.Printf("arr[2]的地址是%p\n",&arr[2])
19}

作为函数参数

 1// 参数数组 len长度要一致
 2func  toArg(arr [2]int){
 3   fmt.Println(arr)
 4}
 5func main(){
 6      var  arr [2]int
 7      toArg(arr)  
 8      var arr1 [3]int
 9      toArg(arr1)  //编译报错
10}
11
12  

数组指针作为函数参数

 1//数组指针,可以不带* 底层编译器会帮我们加上*
 2func arrPointer(arr *[2]int){
 3   arr[0]=1
 4   (*arr)[1]=2  
 5}
 6func main() {
 7 var arr [2]int
 8   arrPointer(&arr)  //利用指针改变原来的值
 9   fmt.Println(arr) //[1 2]
10}

切片

  1. slice 是一个引用类型

  2. slice 从底层来说,其实就是一个数据结构(struct 结构体)

    1//path:Go SDK/src/runtime/slice.go
    2type slice struct {
    3  array unsafe.Pointer
    4  len   int
    5  cap   int
    6}
    
  3. slice可以扩展,只能向后扩展,截取的时候不能超过cap 见extendingslice()

  4. 切片 append 操作的底层原理分析:

    1. 切片 append 操作的本质就是对数组扩容

    2. go 底层会创建一下新的数组 newArr(安装扩容后大小) 将 slice 原来包含的元素拷贝到新的数组 newArr

    3. slice 重新引用到 newArr

    4. 注意 newArr 是在底层来维护的,程序员不可见

  5. slice的copy ,是相互独立的 见 copyslice()示例

  6. 切片作为函数是 (值传递,但是 这个值是 切片的值 是 地址)

  7. 切片名称 [ low : high : max ]

1   low: 起始下标位置
2   high:结束下标位置	len = high - low
3   容量:cap = max - low
4   截取数组,初始化 切片时,没有指定切片容量时, 切片容量跟随原数组(切片)。
5   s[:high:max]  从 0 开始,到 high结束。(不包含)
6   s[low:] 	从low 开始,到 末尾
7   s[:high]	从 0 开始,到 high结束(不包含)。容量跟随原先容量。

切片声明

1     var  s1 []int;
2     s1 = make([]int,3)    //make的时候才开辟内存空间
3     s1[0]=1
4     s1[1]=2
5     s1[2]=3
  1. 自动推导类型创建切片 make([]数据类型,5)
 1  s:=make([]int,5)
 2   s[0]=123
 3   s[1]=234
 4   s[2]=345
 5   s[3]=456
 6   s[4]=567
 7   //s[6]=678//err
 8      //通过append 添加切片信息
 9   s=append(s,678,789,8910)
10   fmt.Println(s)

3.字面量方式

1  var s4 []string=[]string{0:"maozhongyu1",1:"maozhongyu2",3:"maozhongyu3"}
2   fmt.Println(s4) //[maozhongyu1 maozhongyu2  maozhongyu3] 下标2没有是空

切片的使用

方式 1 :定义一个切片,然后让切片去引用一个已经创建好的数组

方式 2 通过 make 来创建切片.,底层也会创建数组,是由切片底层进行维护,程序员不可见 基本语法:var 切片名 []type = make([]type, len, [cap])

方式 3

定义一个切片,直接就指定具体数组,使用原理类似 make 的方式

切片遍历

 1func foreachSlice(){
 2   s:=make([]int,5)
 3   s[0]=123
 4   s[1]=234
 5   s[2]=345
 6   s[3]=456
 7   s[4]=567
 8   //遍历
 9    for i := 0; i<len(s);i++  {
10      fmt.Println(s[i])
11    }
12
13   for i,v:=range s{
14      fmt.Println(i,v)
15   }
16   fmt.Println(s)
17}

切片长度,容量,扩容

 1func sliceappend(){
 2   //不写元素个数叫切片 必须写元素个数的叫数组,
 3   var s []int=[]int{1,2,3,4,5}
 4   s=append(s,6,7,8,9)
 5   //容量大于等于长度
 6   //fmt.Println(s)
 7   fmt.Println("长度:",len(s))
 8   fmt.Println("容量:",cap(s))
 9   //容量每次扩展为上次的倍数
10   s=append(s,6,7,8,9)
11   //fmt.Println(s)
12   fmt.Println("长度:",len(s))
13   fmt.Println("容量:",cap(s))
14   s=append(s,6,7,8,9)
15   fmt.Println("长度:",len(s))
16   fmt.Println("容量:",cap(s))
17   //如果整体数据没有超过1024字节 每次扩展为上一次的倍数  超过1024 每次扩展上一次的1/4
18   s=append(s,6,7,8,9)
19   fmt.Println("长度:",len(s))
20   fmt.Println("容量:",cap(s))
21}

切片地址

切片名本身就是地址

 1	s:=[]int{1,2,3,4,5}
 2	////切片名本身就是地址
 3	////一个字节在内存中占8bit(位)
 4	fmt.Printf("%p\n",s)
 5	fmt.Printf("%p\n",&s[0])
 6	fmt.Printf("%p\n",&s[1])
 7	fmt.Printf("%p\n",&s[2])
 8	//fmt.Printf("%p\n",slice)
 9	//切片名[:]获取切片中所有元素
10	slice:=s[:]
11	fmt.Println(slice)
12	news:=make([]int,5)
13	copy(news,s)  //地址不同
14	fmt.Printf("%p\n",news)
15	fmt.Printf("%p\n",s)
16	

切片作为函数参数(扩容后会出现结果并非预期)

切片作为函数参数是 值传递 形参可以改变实参的值

 1//值传递  ,但是这个值 是指向对应 堆区空间的
 2func BubbleSort(s []int)  {
 3	for i := 0; i < len(s)-1; i++ {
 4		for j := 0; j < len(s)-1-i; j++ {
 5			if s[j]>s[j+1]{
 6				s[j],s[j+1]=s[j+1],s[j]
 7			}
 8		}
 9	}
10}
11func main(){
12	s:=[]int{9,1,5,6,7,3,10,2,4,8}
13	//切片作为函数参数是地址传递 形参可以改变实参的值
14	//在实际开发者 建议使用切片代替数组
15	BubbleSort(s)
16	fmt.Println(s)
17}

综合代码

  1package main
  2import "fmt"
  3var global_arr =[...]int{1,2,3,4,5,6,7}
  4//切片的几种声明方式
  5func first(){
  6   arr :=[...]int{0,1,2,3,4,5,6,7}
  7   s :=arr[2:6]    //下标2 到 下标6 不包含6
  8   fmt.Println(s) // [2 3 4 5]
  9      // [:] //切全部
 10      // [:2]//切下标0,1 2个元素
 11   var  s1 []int;
 12   s1 = make([]int,3)
 13   s1[0]=1
 14   s1[1]=2
 15   s1[2]=3
 16   fmt.Println(s1) //[1 2 3]
 17   s2 :=make([]string,3,6)
 18   s2[0]="maozhongyu1"
 19   s2[1]="maozhongyu2"
 20   s2[2]="maozhongyu3"
 21   fmt.Println(s2) //[maozhongyu1 maozhongyu2 maozhongyu3]
 22   var s3 []string=make([]string,3)
 23   s3[0]="maozhongyu1"
 24   s3[1]="maozhongyu2"
 25   s3[2]="maozhongyu3"
 26   fmt.Println(s3) //[maozhongyu1 maozhongyu2 maozhongyu3]
 27   var s4 []string=[]string{0:"maozhongyu1",1:"maozhongyu2",3:"maozhongyu3"}
 28   fmt.Println(s4) //[maozhongyu1 maozhongyu2  maozhongyu3] 下标2没有是空
 29}
 30// 切片在内存中的布局 ,数组下标1的内存地址和切片第一个元素的 内存地址是相同的
 31func second(){
 32   var arr [6]int= [6]int{1,2,3,4,5,6}
 33   fmt.Printf("%p\n",&arr[1])  //下标1的地址
 34   slice :=arr[1:3]
 35   fmt.Printf("%p\n",&slice[0])
 36}
 37//证明切片是对数组的引用,切片被改变了,数组相应的值也会被改变
 38func update(slice []int){
 39   slice[0]=100
 40}
 41//slice len 和cap容量
 42func third(slice []int){
 43   fmt.Printf("slice的长度%d\n",len(slice))
 44   fmt.Printf("slice的容量%d\n",cap(slice))
 45}
 46//slice声明长度后不能越界,但可以动态的增加的
 47func fourth(){
 48   var slice []int=make([]int,6,10)
 49   fmt.Println(slice)  //[0 0 0 0 0 0]
 50   slice[0]=100
 51   slice[1]=200
 52   slice[2]=200
 53   slice[3]=200
 54   slice[4]=200
 55   slice[5]=200
 56   //slice[6]=200 //越界了
 57   fmt.Println("slice的长度",len(slice))
 58   fmt.Println("slice的容量",cap(slice)) //10
 59   fmt.Println(slice)
 60}
 61//切片后还可以再切片
 62func reslice(){
 63   fmt.Println("reslice start")
 64   var slice []int=make([]int,6,10)
 65   fmt.Println(slice)  //[0 0 0 0 0 0]
 66   slice[0]=100
 67   slice[1]=200
 68   slice[2]=300
 69   slice[3]=400
 70   slice[4]=500
 71   slice[5]=600
 72   fmt.Println(slice)
 73   slice1 :=slice[1:]
 74   fmt.Println(slice1)
 75   slice1 =slice1[1:]
 76   fmt.Println(slice1)
 77   fmt.Println("reslice end")
 78}
 79//可扩展的slice
 80//slice可以向后扩展,不可以向前扩展
 81//向后扩展不可以超过cap(slice)
 82func extendingslice(){
 83   fmt.Println("extendingslice start")
 84   var arr =[...]int{0,1,2,3,4,5,6,7}
 85   slice1 :=arr[2:6]   //2,3,4,5
 86   slice2 :=slice1[3:5]  //向后扩展
 87   fmt.Println(arr)   //[0 1 2 3 4 5 6 7]
 88   fmt.Println(slice1) //[2 3 4 5]
 89   fmt.Println(slice2)  //[5 6]
 90   fmt.Printf("slice1=%v,len(slice1)=%d,cap(slice1)=%d\n",slice1,len(slice1),cap(slice1))
 91   fmt.Printf("slice2=%v,len(slice2)=%d,cap(slice2)=%d\n",slice2,len(slice2),cap(slice2))
 92   fmt.Println("extendingslice end")
 93}
 94//append 追加
 95/*
 96   切片 append 操作的底层原理分析:
 97   切片 append 操作的本质就是对数组扩容
 98   go 底层会创建一下新的数组 newArr(安装扩容后大小) 将 slice 原来包含的元素拷贝到新的数组 newArr
 99   slice 重新引用到 newArr
100   注意 newArr 是在底层来维护的,程序员不可见.
101*/
102func appendSlice(){
103   var slice1 =[]int{1,2,3,4,5,6}
104   slice2 :=append(slice1,7);
105   fmt.Println(slice2)   //[1 2 3 4 5 6 7]
106   slice3 :=[]int{8,9,10}
107   slice4 :=append(slice2,slice3...)
108   fmt.Println(slice4)   //[1 2 3 4 5 6 7 8 9 10]
109}
110//删除下标为3的元素
111func deleteslice(){
112   fmt.Println("删除slice下标为3的元素")
113   var slice1 =[]int{1,2,3,4,5,6}
114   slice2 :=slice1[:3]
115   slice3 :=slice1[4:]
116   slice1 =append(slice2, slice3...)
117   fmt.Println(slice1) // [1 2 3 5 6]
118}
119//slice 的copy
120func copySlice(){
121   fmt.Println("slice的copy")
122   var slice =make([]int,8,16)
123   slice1 :=[]int{1,2,3,4,5}
124   copy(slice,slice1)
125   fmt.Println(slice)  //[1 2 3 4 5 0 0 0]
126   slice1[0]=100
127   fmt.Println(slice) //[1 2 3 4 5 0 0 0]  slice1和slice 是独立的,不影响
128}
129func main(){
130      first()
131      second()
132      slice :=global_arr[1:]
133      fmt.Println("before update")
134      fmt.Printf("slice=%v\n",slice)
135      fmt.Printf("global_arr=%v\n",global_arr)
136      update(slice)
137      fmt.Println("after update")
138      fmt.Printf("slice=%v\n",slice)
139      fmt.Printf("global_arr=%v\n",global_arr)
140      third(slice)
141      fourth()
142      reslice()
143   extendingslice()
144   appendSlice()
145   deleteslice()
146   copySlice()
147}

打散符号 …

1S1 = append(s1,s2...)

map

var map 变量名 map[keytype]valuetype

key 可以是什么类型

  • golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只 包含前面几个类型的 接口, 结构体, 数组
  • 通常 key 为 int 、string
  • 注意: slice, map 还有 function 不可以,因为这几个没法用 == 来判断

map的长度是自动扩容的

map中的数据是无序存储的

map名本身就是一个地址

访问不存在的下标,获得对应的零值

声明方式

 1//1.
 2   var  a map[string]string
 3   a = make(map[string]string,10)
 4   fmt.Println(a)
 5   a["key1"]="value1"
 6   a["key2"]="value2"
 7   fmt.Println(a)  //map[key1:value1 key2:value2]
 8   
 9//2.   
10   b :=make(map[string]int)
11   b["key1"]=10
12   b["key2"]=11
13   fmt.Println(b)   //map[key1:10 key2:11]
14   //直接声明元素
15   
16//3.   
17   var c = map[string]string{"key1":"values1","key2":"value2"}  //map[key1:values1 key2:value2]
18   fmt.Println(c)

map删除

1   b :=make(map[string]int)
2   b["key1"]=10
3   b["key2"]=11
4   delete(b,"key1")
5   fmt.Println(b) //map[key2:11]

map是否有这个下标

 1   b :=make(map[string]int)
 2   b["key1"]=10
 3   b["key2"]=11
 4   a :=b["key1"]
 5   fmt.Println(a) //10
 6   if c,ok:=b["key3"];ok{
 7      fmt.Println(c)
 8   }else{
 9      fmt.Println("key3 not exists")
10   }

map遍历

1   b :=make(map[string]int)
2   b["key1"]=10
3   b["key2"]=11
4   for key,value :=range b {
5      fmt.Println(key,value)
6   }
7   for key := range b {
8       fmt.Println(key) //打印的是下标 
9   }

map作为函数参数

map作为函数参数是。 值传递 (map 本身是地址)

 1package main
 2
 3import "fmt"
 4
 5func demo(m map[int]string){
 6   //map在函数中添加数据 可以的  影响主调函数中实参的在
 7   m[102]="杨二郎"
 8   m[103]="唐老二"
 9   fmt.Println(len(m))
10   delete(m,101)
11   fmt.Println(len(m))
12}
13func main() {
14   m:=make(map[int]string,1)
15   m[101]="孙悟空"
16   fmt.Println(len(m))
17   //fmt.Println(cap(m))//err
18   //map作为函数参数是地址传递 (引用传递)
19   demo(m)
20   fmt.Println(m)
21}

map的value赋值问题

  • 值引用的特点是只读
 1package main
 2
 3import "fmt"
 4
 5type Student struct {
 6	Name string
 7}
 8
 9//key string, value Student struct
10//var list map[string]Student
11var list map[string]*Student
12
13/*
14func main() {
15
16	list = make(map[string]Student)
17
18	//定义了一个结构体
19	student := Student{"Aceld"}
20
21	//map添加了一条数据, key student, value Student{"Aceld"}
22	list["student"] = student //值拷贝 过程,  map中的value是新创建的一个结构体, 该结构体的属性是只读的
23	list["student"].Name = "LDB" //报错     	list["student"]是值引用,值引用的特点是只读
24
25	fmt.Println(list["student"])
26}
27*/
28
29/*
30	方法一:
31func main() {
32
33	list = make(map[string]Student)
34
35	//定义了一个结构体
36	student := Student{"Aceld"}
37
38	//map添加了一条数据, key student, value Student{"Aceld"}
39	list["student"] = student //值拷贝,  map中的value是新创建的一个结构体, 该结构体的属性是只读的
40
41	//创建一个临时的student结构体
42	tmpStudent := list["student"]
43	tmpStudent.Name = "LDB"
44	list["student"] = tmpStudent
45	//中间发生两次值拷贝,性能比较差
46
47	fmt.Println(list["student"])
48}
49*/
50
51/*方法二*/
52func main() {
53
54	list = make(map[string]*Student)
55
56	//定义了一个结构体
57	student := Student{"Aceld"}
58
59	//map添加了一条数据, key student, value Student{"Aceld"}
60	list["student"] = &student 
61
62  //每次修改的是指针指向的studeng 空间,指针本身是常指针,不能修改,只读,但是指向student 是可以随便修改的,
63  //而且这里不是指拷贝,而是指针的赋值
64	list["student"].Name = "LDB"
65
66	fmt.Println(list["student"])
67}

指针

从传统意义上说,指针是一个指向某个确切的内存地址的值。这个内存地址可以是任何数据或代码的起始地址,比如,某个变量、某个字段或某个函数。

在 Go 语言中还有其他几样东西可以代表“指针”。其中最贴近传统意义的当属uintptr类型了。该类型实际上是一个数值类型,也是 Go 语言内建的数据类型之一。

所有指针在32位系统下占4字节

所有指针在64位系统下占8字节

指针就是地址。 指针变量就是存储地址的变量。

*p : 解引用、间接引用。

指针可以操作其指向存储区的值

指针必须初始化,访问野指针和空指针对应的内存空间都会报错

声明了一个指针 默认值为nil(空指针值为0)指向了内存地址编号为0的空间

1	var p *int //空指针
2	//p=0xc042058080//野指针  指针变量指向了一个未知的空间
3	//访问野指针和空指针对应的内存空间都会报错
4	//*p=123 //err  在上面加一行 p=new(int) 即可
5	fmt.Println(p) //nil
6	p = new(int)
7	fmt.Println(*p) //0
 1package main
 2import "fmt"
 3
 4/*
 5结果:
 6p的地址是0xc42000c028
 7p本身是0xc420012068
 8p的指向的值是2
 9a现在的值是3
10*/
11func first(){
12   var  a = 2
13   var  p *int=&a
14   fmt.Printf("p的地址是%p\n",&p)
15   fmt.Printf("p本身是%v\n",p)
16   fmt.Printf("p的指向的值是%v\n",*p)
17   //*p 解引用,间接引用
18   *p = 3 
19   fmt.Printf("a现在的值是%v\n",a)
20}
21//利用指针交换值
22func swap (a,b *int){
23   *a,*b=*b,*a
24}
25func main() {
26   first()
27   //go的指针不能运算
28   a,b :=3,4
29   swap(&a,&b)
30   fmt.Println(a,b)  //4,3 
31}

new操作

new 函数会在heap上申请一片内存空间

 1package main
 2import "fmt"
 3func main() {
 4	var p *int//nil
 5	fmt.Println(p) //nil
 6	//为指针变量创建一块内存空间
 7	//在堆区创建空间
 8	p=new(int)//new 创建好的空间值为数据类型的默认值
 9	//打印p的值
10	fmt.Println(p)  //0xc000098008
11	//打印p指向空间的值
12	fmt.Println(*p) //0
13}

数组指针

直接使用指针[下标]操作数组元素,go语言内部维护

指针的值 == &arr == &arr[0]

可以使用 len(指针变量) 求数组元素个数

 1package main
 2
 3import "fmt"
 4
 5func test1() {
 6	var arr [5]int = [5]int{123,2,3,4,5}
 7	fmt.Printf("%p\n",&arr) //0xc000088000
 8	fmt.Printf("%p\n",&arr[0]) //0xc000088000
 9	//p:=&arr
10	//定义指针指向数组
11	var p *[5]int
12	//将指针变量和数组建立关系
13	p=&arr //5要和数组对硬上
14	fmt.Println(*p) //[123 2 3 4 5]
15	fmt.Println(arr) //[123 2 3 4 5]
16	fmt.Println((*p)[0]) //123
17	//直接使用指针[下标]操作数组元素
18	fmt.Println(p[0]) //go语言内部维护 123
19	fmt.Printf("%p\n",p) //0xc000088000
20	fmt.Printf("%T\n",p)//*[5]int
21	//可以通过指针间接操作数组
22	(*p)[0]=199
23	fmt.Println(arr) //[199 2 3 4 5]
24
25
26}
27func main(){
28	test1()
29	var arr [5]int = [5]int{123,2,3,4,5}
30	//指向数组的指针
31	p:=&arr //*[5]int
32	p1:=&arr[0]//*int
33	fmt.Printf("%T\n",p)  //*[5]int
34	fmt.Printf("%T\n",p1) //*int
35	//len(指针变量)元素个数
36	fmt.Println(len(p))  //5
37	for i := 0; i<len(p);i++  {
38		fmt.Println(p[i])
39	}
40	fmt.Printf("%p\n",p)     //0xc00008e000
41	fmt.Printf("%p\n",&arr) //0xc00008e000
42	fmt.Printf("%p\n",&arr[0]) //0xc00008e000
43}

切片指针

切片本身就是一个地址

切片的指针其实就是一个二级指针

切片的地址,append 后是不会改变的。 切片的值是可能改变的。 切片的值就是 指向堆区的,堆区里面的内容会改变。

切片指针和append后的示例图

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	var slice []int = []int{1, 2, 3, 4, 5}
 7	//p:=&slice   //*[]int
 8	var p *[]int //二级指针
 9	p = &slice
10	fmt.Printf("%T\n", p)      //*[]int
11	//切片名本身就是一个地址
12	fmt.Printf("%p\n", slice) // 0xc000070030
13	fmt.Printf("%p\n", p)     //	0xc00006c020
14	fmt.Printf("%p\n", &slice) //0xc00006c020
15	fmt.Printf("%p\n", *p)    //	0xc000070030
16	(*p)[1] = 200                    //ok
17	//p[1]=123 //err 不能通过指针访问切片中的元素
18	fmt.Println(slice)
19	fmt.Println(*p)
20
21	test01()
22}
23
24
25func test01() {
26	var slice []int = []int{1, 2, 3, 4, 5}
27	fmt.Printf("%p\n", slice) //0xc000086030
28	//p:=&slice//*[]int
29	var p *[]int //二级指针
30	p = &slice
31	fmt.Printf("%p\n", p)  //0xc000084040
32	fmt.Printf("%p\n", &slice)  //0xc000084040
33	fmt.Printf("%p\n", &p) //0xc00007e008
34	//左边 *p就是slice,
35	//在使用append添加数据是 切片的地址可能或发生变量 如果容量扩充导致输出存储溢出 切片会自动找寻新的空间存储数据
36	*p = append(*p, 6, 7, 8, 9, 10)
37	fmt.Printf("%p\n", slice)  // 0xc000094000
38	fmt.Printf("%p\n", &slice)  // 0xc000084040
39	fmt.Printf("%p\n", p)     //0xc000084040
40	fmt.Printf("%p\n", &p)     //0xc00007e008
41	fmt.Println(slice)
42}

切片作为函数参数

输出[1,2,3]

1func test(s []int) []int {
2		s = append(s,4,,5,6)
3		return s
4}
5func main() {
6	s :=[]int{1,2,3}
7	test(s)
8	fmt.println(s) 
9}

切片指针作为函数参数

输出[1,2,3,4,5,6]

1func test(s *[]int) {
2  *s = append(*s ,4,5,6)
3}
4func main() {
5  s :=[]int {1,2,3}
6  test(&s)
7  fmt.Println(s)
8}

new 创建切片指针

 1package main
 2import "fmt"
 3func main() {
 4   var p *[]int
 5   fmt.Printf("%p\n",p) //ox0
 6   p=new([]int)
 7   fmt.Printf("%p\n",p) //0xc0000b4000
 8   *p=append(*p,1,2,3)
 9   //for i := 0; i<len(*p);i++  {
10   // fmt.Println((*p)[i])
11   //}
12   for i,v:=range *p{
13      fmt.Println(i,v)
14   }
15   //fmt.Println(len(*p))
16}

指针数组

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7   //var arr [3]int ->int
 8   //指针数组
 9   var arr [3]*int  //->*int
10   a:=10
11   b:=20
12   c:=30
13
14   arr[0]=&a
15   arr[1]=&b
16   arr[2]=&c
17   fmt.Println(arr)
18   fmt.Printf("%p\n",&a)
19   fmt.Printf("%p\n",&b)
20   fmt.Printf("%p\n",&c)
21   //通过指针数组改变变量的值
22   *arr[1]=2  // arr[1] 优先级高
23   fmt.Println(b)
24   //变量指针数组对应的内存空间的值
25   for i := 0; i < len(arr); i++ {
26      fmt.Println(*arr[i])
27   }
28
29}

指针切片

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6   //指针切片
 7   var slice []*int
 8   a:=10
 9   b:=20
10   c:=30
11   d:=40
12   slice=append(slice,&a,&b,&c)
13   slice=append(slice,&d)
14   fmt.Println(slice)
15   for i,v:=range slice{
16      fmt.Printf("%T\n",v)
17      fmt.Println(i,*v)
18   }
19}

结构体指针

 1package main
 2
 3import "fmt"
 4
 5type Student struct {
 6   name string
 7   id int
 8   age int
 9   sex string
10}
11func test01() {
12   //定义结构体变量
13   //var 结构体名 结构体数据类型
14   var stu Student=Student{id:101,name:"多啦A梦",age:100,sex:"男"}
15   //结构体变量.成员=值
16
17   //定义结构体指针指向变量的地址
18   var p *Student
19   ////结构体指针指向结构体变量地址
20   p=&stu
21   p1:=&stu
22   p2:=&stu.name
23   fmt.Printf("%T\n",p1)//*main.Student
24   fmt.Printf("%T\n",p2)//*string
25   fmt.Printf("%T\n",p) //*main.Student
26   fmt.Printf("%p\n",&stu)  //0xc00009a090
27   fmt.Printf("%p\n",&stu.id) //0xc00009a0a0
28   ////结构体成员地址
29   fmt.Printf("%p\n",&stu.name)  //0xc00009a090  第一个元素地址和 结构体地址相同
30   fmt.Printf("%p\n",&stu.id) 
31   fmt.Printf("%p\n",&stu.age)
32   fmt.Printf("%p\n",&stu.sex)
33}
34
35func test02(){
36   var stu Student=Student{id:101,name:"多啦A梦",age:100,sex:"男"}
37   var p *Student
38   p=&stu
39   //通过结构体指针间接操作结构体成员
40   //(*p).name="大熊"
41   //通过指针可以直接操作结构体成员
42   p.name="静香"
43   p.age=18
44   p.sex="女"
45   fmt.Println(stu)
46}
47
48func main(){
49   //结构体切片
50   var stu []Student=make([]Student,3)
51   p:=&stu//*[]Student  //结构体切片指针
52   //stu[0]=Student{"小猪佩奇",1,10,"女"}
53   (*p)[0]=Student{"小猪佩奇",1,10,"女"}
54   (*p)[1]=Student{"野猪佩奇",2,15,"女"}
55   (*p)[2]=Student{"猪刚鬣",3,1000,"男"}
56   //append在切片长度后添加数据
57   *p=append(*p,Student{"小猪佩奇",1,10,"女"})
58   //fmt.Printf("%T\n",p)
59   //fmt.Println(stu)
60   for i := 0; i<len(*p);i++  {
61      fmt.Println((*p)[i])
62   }
63}

多级指针

 1package main
 2
 3import "fmt"
 4
 5var pp **int
 6func main() {
 7	a:=10
 8	p:=&a
 9	pp:=&p//二级指针  二级指针存储一级指针的地址
10	p3:=&pp//三级指针 存储二级指针的地址
11	fmt.Println(pp) //0xc000078010
12	fmt.Println(*p3)//0xc000078010
13	//变量a 的值
14	fmt.Println(**pp)
15	fmt.Println(*p)
16	fmt.Println(a)
17	fmt.Printf("%T\n",a) //int
18	fmt.Printf("%T\n",p) //*int
19	fmt.Printf("%T\n",pp) //**int
20	fmt.Printf("%T\n",p3) //***int
21}

内存四区

流程说明:

  • 操作系统把物理硬盘代码load 到内存
  • 操作系统把代码分成4个区
  • 操作系统找到main入口执行

栈区:空间较小、要求数据读写性能高、数据存放时间短暂。由编译器自动分配和释放,存放函数的参数、函数的调用流程方法地址、

局部变量等(局部变量如果产生了逃逸现象,可能会挂在堆区

堆区

空间充裕、数据存放时间较长。一般由开发者分配和释放(但是golang中会根据变量等逃逸现象来选择是否分配到栈上还是堆上)

启动golang的gc由gc清除机制自动回收

全局区-静态全局变量区

全局变量的开辟是在程序在main之前就已经放在内存中。

 尽量减少全局变量的设计

全部区-常量区

常量为 存放数值字面值 单位(在内存中符号表中存在,不在栈上,也不在堆上)。既不可修改。不可取地址,因为字面量符号 无地址可言

函数中切片名是在栈区,数据是在堆区

内存的基本单位是字节

堆区由垃圾回收机制控制

string 字符串是 底层是字节切片

栈帧内存布局

32位操作内存图为例, 3g-》4g 内核空间,其他是用户空间。

上图最右侧的请忽略

 1func test(m int)  {
 2    var b int = 1000
 3    b += m
 4}
 5
 6func main(){
 7    var a int =100
 8    var p *int
 9    p=new(int) //在堆区开辟内存空间
10    *p=1000
11    
12     test(10)
13}
空间 存放内容
stack(栈) 函数和函数内定义的引用(变量名)
heap(堆) 引用所对应的实体
.bss(未初始化数据区) 未初始化(赋初值)的全局变量
.data(数据区) 已初始化的全局变量
. rodata(只读数据区) 常量
.text(代码区)

栈在初始的时候存在两个指针,一个是栈基指针,一个是栈顶指针,他们刚开始都指向栈的基地址,当创建一个函数,栈顶指针会往下挪一段距离,此时两个指针指向地址中间的这段空间就是函数的栈帧,例如代码中的main()函数就有一块自己的栈帧,其中保存的是局部变量(a、p)、形参和内存地址描述值。

内存地址描述值:考虑在main()函数中我们又创建了一个test()函数,此时栈基指针会往下挪指向原先栈顶指针的位置,栈顶指针则往下挪若干距离,这段新产生的空间就是test()的栈帧,由于两个指针的移动,main栈帧的位置就无法被描述了,所以需要main栈帧自己记录原先的栈基地址和栈顶地址,称为内存地址描述值。

另外,局部变量和形参的地位是等同的,如test()中的b和m。

当函数执行完毕,栈帧空间会被释放。

栈空间非常小,只有1MB~8MB,所以以上创建栈帧回收栈帧操作都由操作系统完成

堆空间相对比较大,有至少1GB的空间,且go语言中也有垃圾回收机制,帮助回收不用的内存,但对于不再使用的空间,应将指针置为nil,以便提示系统回收。

栈帧: 用来给函数运行提供内存空间。 取内存于 stack(栈) 上。

当函数调用时,产生栈帧。函数调用结束,释放栈帧。

栈帧存储: 1. 局部变量。 2. 形参。 (形参与局部变量存储地位等同) 3. 内存字段描述值

stack 默认是1mb ,可以扩展,但是还是很小

指针的作为函数参数

指针的函数传参(传引用)。

	传地址(引用):将形参的地址值作为函数参数传递。
	传值(数据值):将实参的值 拷贝一份给形参。
	传引用:在A栈帧内部,修改B栈帧中的变量值。

内存的释放

堆区内存,用完后,可以让它等于nil ,gc会回收。

new和make

2者都是内存的分配(堆上),但是make 只用于slice,map以及channel 的初始化(非零值);

而new 用于类型的内存分配,并且内存置为零。 make 返回的是这3个引用类型的本身,而new 返回的是指向类型的指针

new: 一般是用来初始化值类型指针的,new([]int) 也可以哦。

make:是用来初始化slice, map,chan

go语言for range中的坑