前言

前面学习了Go数据类型中的数字、字符串,下面看看Go中是否有Python中的list、dict等复合数据类型?

需要注意的是在Go中数组是可变类型+值(拷贝)类型,而go中切片是引用类型

数组声明之后它里面的元素是可以被修改,如果把1个数组变量引用到1个新的变量,那么这个新的变量会重新拷贝一份旧数组的数组。

Python中的list是可变+引用数据类型

llist1 = ["A", "B", "C"]
llist2 = llist1
llist1[0] = "a"
llist2[2] = "b"
print(llist1, id(llist1))
print(llist2, id(llist2))
"""
['a', 'B', 'b'] 42775624
['a', 'B', 'b'] 42775624
"""
Python的list可变+引用

Golang中的arry是可变+拷贝数据类型

package main

import "fmt"

func main() {
    arry1 := [3]string{"A", "B", "C"} //注意声明arry变量时不要忘记[指定长度],否则声明的是slice变量
    arry2 := arry1
    arry1[0] = "a"
    arry2[1] = "b"
    //说明go中的arry不是引用类型,单属于可变数控类型可以原处修改
    fmt.Printf("%v %p\n", arry1, &arry1)
    fmt.Printf("%v %p\n", arry2, &arry2)

}

/*
[a B C] 0xc000062330
[A b C] 0xc000062360
*/
Golang的arry可变+拷贝

 

Python的list切片之后会开辟1开新的内存生产1个新的list数据类型

nameList=["",""]
firstName=nameList[0]
lastName=nameList[1]
print(firstName)
print(lastName)

#Python的list切片会开辟1块新的内存,创建1个新的列表。
nameSlice=nameList[0:2]
nameSlice[0]="Martin"
nameSlice[1]="Zhang"
print(nameList,nameSlice)
print(type(nameList),type(nameSlice))
print(id(nameList),id(nameSlice))
'''
张
根
['张', '根'] ['Martin', 'Zhang']
<class 'list'> <class 'list'>
42632648 42632712
'''
Python的list切片

Golang的arry切片之后会开辟1个新的内存生成1个slice数据类型

package main

import (
    "fmt"
    "reflect"
)

func main() {
    nameArry:=[2]string{"",""}
    //1.获取数组的长度
    fmt.Println(len(nameArry))
    //2.通过数组索引取值
    firstName:=nameArry[0]
    lastName:=nameArry[1]
    fmt.Println(firstName)
    fmt.Println(lastName)
    //3.数组组切片
    nameSlice:=nameArry[0:2]
    nameSlice[0]="Martin"
    nameSlice[1]="Zhange"
    fmt.Println(nameArry,nameSlice) //[Martin Zhange] [Martin Zhange]
    fmt.Println(reflect.TypeOf(nameArry),reflect.TypeOf(nameSlice)) //[2]string(数组类型)-------- []string(切片类型)
    fmt.Printf("切片之前的内存地址:%p,切片之后的slice内存地址:%p-- ",&nameArry,nameSlice)
}
Golang的Arry切片

 

package main

import "fmt"

func main() {
	//声明1个字符串变量
	var name string
	fmt.Println(name)
	//声明1个数组变量
	var a1 [3]int
	fmt.Println(a1)
	//声明1个切片类型的变量
	var s1 []int
	fmt.Println(s1 == nil)
	//声明二维数组
	var a3 [3][3]int
	//对二维数组进行初始化
	a3 = [3][3]int{
		[3]int{1, 2, 3},
		[3]int{4, 5, 6},
		[3]int{7, 8, 8},
	}
	fmt.Println(a3)
	//
	a4 := [3]int{1, 2, 3}
	a5 := modifyArry(a4)
	fmt.Println(a4)
	fmt.Println(a5)
	s2 := []string{"河北", "保定", "唐县"}
	s3 := modifySlice(s2)
	fmt.Println(s2)
	fmt.Println(s3)

}

//数组是值类型:修改数组时,会复制 1个新的数组让我们修改
func modifyArry(a1 [3]int) (ret [3]int) {
	a1[0] = 100
	return a1
}

//切片是引用类型:修改切片时,不会复制1个新的切片让我们修改(修改原来的)
func modifySlice(s2 []string) (ret2 []string) {
	s2[0] = "河北省"
	return s2
}

  

 

数组

数组就是存放同1种数据基本数据类型(数值,字符、字符串、布尔值)元素的容器。

Go中的数组有一下特点:

在声明arry变量时就要声明arry的长度、容器内存放元素的数据类型;

数组的长度是数据类型的1部分,所以2个长度不同arrr,即使他们都属于数组,但2者的数据类型完全不一致,所以2者无法相互比较和赋值。

数组如果没有初始化里面的元素默认为0值(bool=false,int or float=0, string=空字符串)

 

 

数组的声明和初始化

 

package main

import "fmt"

func main() {
	//数组:存放同1中基础数据类型的容器
	//声明1个长度为3的数组,里面存放bool数据类型
	var arry1 [3]bool
	var arry2 [4]bool
	var arry3 [3]bool
	fmt.Printf("arry1:%T arry2:%T arry3:%T\n", arry1, arry2, arry3)
	fmt.Println(arry1 == arry3)
	//初始化方式1:
	arry1 = [3]bool{true, true, true}
	fmt.Println(arry1) //[true true true]
	//初始化方式2:[...]自动推算数组的长度
	arry4 := [...]int{1, 2, 3, 4}
	fmt.Println(arry4)
	//初始化方式3:利用默认值
	arry5 := [5]int{12, 13}
	fmt.Println(arry5) //[12 13 0 0 0]
	//初始化方式4:根据索引指定arry中的元素
	arry6 := [5]int{0: 1, 4: 6}
	fmt.Println(arry6)//[1 0 0 0 6]

}

  

遍历数组

package main

import (
    "fmt"
    "reflect"
)

func main() {
    nameArry := [2]string{"", ""}
    //1.获取数组的长度
    fmt.Println(len(nameArry))
    //2.通过数组索引取值
    firstName := nameArry[0]
    lastName := nameArry[1]
    fmt.Println(firstName)
    fmt.Println(lastName)
    //3.数组组切片
    nameSlice := nameArry[0:2]
    nameSlice[0] = "Martin"
    nameSlice[1] = "Zhange"
    fmt.Println(nameArry, nameSlice)                                 //[Martin Zhange] [Martin Zhange]
    fmt.Println(reflect.TypeOf(nameArry), reflect.TypeOf(nameSlice)) //[2]string(数组类型)-------- []string(切片类型)
    fmt.Printf("切片之前的内存地址:%p,切片之后的slice内存地址:%p--\n", &nameArry, nameSlice)

    //2.循环数组
    fmt.Println(nameArry)
    for i := 0; i < len(nameArry); i++ {
        fmt.Println(i, nameArry[i])
    }
    for item := range nameArry {
        fmt.Println(item)
    }
    for index := range nameArry {
        fmt.Println(index)
    }
    for index, item := range nameArry {
        fmt.Println(index, item)
    }
}
遍历数组

 

 

  

多维数组

多维数组就是数组里面嵌套数组

package main

import (
	"fmt"
)

func main() {
	//定义多维数组
	a1 := [3][2]int{{1, 2}, {1, 3}, {1, 4}}
	fmt.Println(a1) //[[1 2] [1 3] [1 4]]
	//遍历多维数组
	for _, v := range a1 {
		for _, v1 := range v {
			fmt.Println(v1)
		}
	}

}

 

数组的内存管理机制

 

Golang字符串类型存储的机制

 

我们在Golang中声明的字符串类型变量,编译器不会直接存储该字符串的值。

而是先开辟1块内存把字符串的值存储起来,然后开辟1块内存把这个字符串的指针(8个字节)和长度(8个字节)存储起来赋予变量。

为什么要这样存储1个字符串呢?额外开辟1块存储指针和长度的内存空间。

我感觉这是是为了字符串被放到到Arry或Slice之后的查询效率而做的铺垫。

 

1.数组的内存地址是连续的

2.数组的内存地址=数组中第1个元素的内存地址

这种设计可以让我们快速的从1个元素获取到数组中最后1个元素,查询效率大大提升。

既然声明1个数组即开辟了1块连续的内存,对Arry中的item进行顺序存储,那么Arry的item含有字符串呢?

字符串可能是英文/汉字它们占用内存的规格可不一致啊!既然如此那我就同一一下规格!

3.声明1个字符串数组时,开启一段连续的内存之后,(并不会在这段连续的内存上直接存储字符串的值,而是存储字符串值的指针(占4个字节)和长度(占4字节)=16个字节。这也是Golang中字符串数据类型的存储机制,连续的内存+指针=加快查询效率...)

这样Arry中每1个元素对这段连续内存的使用都有了固定的规格,节约了Arry对内存空间占用。

随意字符串存储时增加了额外的空间去存储指针和长度,但是加速了查询效率。用空间换时间吧!

 

package main

import "fmt"

//1.数组的内存是连续的
//2.因为数组的内存是连续的,所以数组的内存地址就是数组里第1个元素的内存地址。
func main() {
    numbers := [3]int8{11, 22, 33}
    fmt.Printf("%p\n", &numbers)    //0xc00000a0a8
    fmt.Println(numbers)            //[11 22 33]
    fmt.Printf("%p\n", &numbers[0]) //0xc00000a0a8
    fmt.Printf("%p\n", &numbers[1]) //0xc00000a0a9
    fmt.Printf("%p\n", &numbers[2]) //0xc00000a0aa

    numbers32 := [3]int32{11, 22, 33}
    fmt.Printf("%p\n", &numbers32)    //0xc00011e0b4
    fmt.Println(numbers)              //[11 22 33]
    fmt.Printf("%p\n", &numbers32[0]) //0xc00011e0b4
    fmt.Printf("%p\n", &numbers32[1]) //0xc00011e0b8
    fmt.Printf("%p\n", &numbers32[2]) //0xc00011e0bc

    var names = [2]string{"张根", "Martin"}
    fmt.Printf("%p\n", &names)    //0xc0000044c0
    fmt.Println(names)            //[张根 Martin]
    fmt.Printf("%p\n", &names[0]) //0xc000114460
    fmt.Printf("%p\n", &names[1]) //0xc000114470
}
Arry内存管理机制

 

 

Go中多维数组是数值类型不是引用类型

 值类型:就是重新开辟新的内存,与引用类型相反

package main

import (
	"fmt"
)

func main() {
	//arry数值类型辩证
	a1 := [...]string{"河北", "河南", "山东"}
	a2 := a1
	a1[len(a1)-1] = "山西"
	fmt.Println(a1) //[河北 河南 山西]
	fmt.Println(a2) //[河北 河南 山东]

}

  

 练习题

 

package main

import (
	"fmt"
)

func main() {
	//1.求数组[1, 3, 5, 7, 8]所有元素的和
	arr1 := [...]int{1, 3, 5, 7, 8}
	var sum int
	for _, v := range arr1 {
		sum += v
	}
	fmt.Println(sum)
	/*找出数组中和为指定值的两个元素的下标,比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)和(1,2)*/
	for i := 0; i < len(arr1); i++ {
		for j := i + 1; j < len(arr1); j++ {
			if arr1[i]+arr1[j] == 8 {
				fmt.Printf("%d %d\n", i, j)
			}
		}
	}

}

 

 

package main

import "fmt"

/*
1.Go语言的int占多少个字节?
答:取决于你的操作系统 32位 4个字节,64位8个字节

2.整型中有符号和无符号是什么意思?
答:所谓有符号是使用1个位表示征服性质,因为使用第1位表示正负性质,所以有符号整型可表示的正数范围比较小,无符号整型表示的整数范围比较大!

3.整型可以表示的最大范围是
答:0-2**64,超出之后使用bigint包

4.什么是nil
答:nil就是空的数据

5.十进制是以整型的方式存在,其他其他进制以字符串的形式存在?如何实现进制之间的转换?
答:在golang中10进制是以整型存在,其他进制(二进制、八进制、十六进制)都是以字符串的形式存在
    bynaryNumber:=strconv.FormatInt(int64(29),2)//十进制转二进制
    octalNumber:=strconv.FormatInt(int64(29),8)//十进制转八进制
    hexaDecimal:=strconv.FormatInt(int64(29),16)//十进制转八进制
     fmt.Println(bynaryNumber,octalNumber,hexaDecimal)
    fmt.Println(strconv.ParseInt(hexaDecimal,16,32))

6.简述如下代码的意义:var v1 int|var v2 *int var v3=new(int)
    var v1 int  //开辟1块个内存+内存初始化值位0,v1指向该内存地址,打印显示0
    fmt.Println(v1)
    v2:=999    //开辟1块个内存+内存初始化值设置为999,v2指向该内存地址,打印显示0
    fmt.Println(v2)
    var v3 *int
    fmt.Printf("编译器开辟1块名为:%p的内存 存储了1个%p的空指针,空指针指向了nil(并没有初始化)\n",&v3,v3)
    v4:=new(int)
    fmt.Printf("编译器开辟1块名为:%p的内存 存储了1个指针%p,指针指向%p内存这块内存上存了值%d\n",&v4,v4,&*v4,*v4)

7.浮点型为什么无法精准表示小数?
答:这取决于 浮点型的小数部。小数部分 分转换成二进制时得不出1就这个浮点型就无法精确表达。


8.如何使用第3方包decimal?

答:
    var v1 =decimal.NewFromFloat(0.0000019)
    var v2 =decimal.NewFromFloat(0.298766)
    var v3 =v1.Add(v2)
    var v4 =v3.Sub(v2)
    var v5 =v4.Mul(v2)
    var v6 =v4.Div(v1)
    fmt.Println(v3)
    fmt.Println(v4)
    fmt.Println(v5)
    fmt.Println(v6)
    var price=decimal.NewFromFloat(3.6615926)
    fmt.Println(price.Round(1)) //保留小数点后1位自动四舍五入
    fmt.Println(price.Truncate(2))//保留小数点后2位不需要四舍五入



9.简述ascii、Unicode、utf-8的关系

答:
American Standard Code for Information Interchange
是存储了欧美国家在计算机中可以用到字符和对应二进制的字符集,这些二进制使用8位表示足够
Unicode:描述全球人使用字符和对应码位的字符集 使用32位表示码位范围。
utf-8是对unicode的码位进行画区间然后根据字符的码位区间进行模板选择完成Unicode码位和模板的填充

10.判断Go语言中的字符是utf-8编码的字节
name:="张根"
fmt.Println(strconv.FormatInt(int64(name[0]),2))
fmt.Println(strconv.FormatInt(int64(name[1]),2))
fmt.Println(strconv.FormatInt(int64(name[2]),2))
//11100101 10111100  10100000 去对应utf-8的模板一看便知

11.什么是rune?
rune表示英文字符或者汉字在Unicode字符中对应的码位
name:="张根"
EnglishName:="Martin"
fmt.Println([]rune(name))
fmt.Println([]rune(EnglishName))
[24352 26681]
[77 97 114 116 105 110]
12.判断:字符串是否可变?
答:不可变
13.列举你记得的字符串常见功能
答:Prefix() stufix()
14.字符串集合和rune集合如何实现相互转换
name:="Alex"
newRune:=[]rune(name)
newRune[0]='a'
fmt.Println(string(newRune))

15.如何实现字符串高效率拼接
var builder strings.Builder
builder.WriteString("我爱")
builder.WriteString("北")
builder.WriteString("京")
result11:=builder.String()
fmt.Println(result11)

16.简述数组的存储原理
答:一块连续的内存地址,第一个元素即该数组的内存地址。如果数组内包含字符串 存储字符串(长度+指针)进行统一规格!

17.names:=[3]string{"alex","超级无敌小JJ","傻儿子"}
a.根据索引获取“傻儿子”
b.根据索引获取“alex”
c.根据索引获取“小JJ”
d.请将names数组最后1个元素修改为“大烧瓶”
答:
names := [3]string{"alex", "超级无敌小JJ", "傻儿子"}
fmt.Println(names[0])
fmt.Println(names[1])
fmt.Println(names[2])
names[0]="大烧饼"
fmt.Println(names)



18.看代码输出结果
var nestdata [3][2]int
fmt.Println(nestdata)
[[0 0] [0 0] [0 0]]

19.请声明1个3个元素的数组,元素的类型是数组,并在数组中初始化值
var nestdata=[3][2]int{{1,2},{3,4},{5,6}}
fmt.Println(nestdata)


20循环下列数组并使用字符串格式化输出一下内容:
dataList:=[
["alex","qwe123"],
["eric","qw2"],
["tony","qpep23"],
]

最终实现:
我是Alex我的密码是qwe123
答:
var userList = [3][2]string{{"alex", "qwe123"}, {"eric", "qw2"}, {"tony", "qpep23"}}
fmt.Println(userList)
for _, item := range userList {
    fmt.Printf("我是%s我的秘密是%s\n", item[0], item[1])
}


21.实现用户登录
//userList中有3个用户,
dataList:=[
["alex","qwe123"],
["eric","qw2"],
["tony","qpep23"],
]
从dataList中获取用户名验证用户合法性


*/

func main() {
    var userList = [3][2]string{{"alex", "qwe123"}, {"eric", "qw2"}, {"tony", "qpep23"}}
    var userName string
    var passWord string
    fmt.Print("请输入用户名: ")
    fmt.Scan(&userName)
    fmt.Print("请输入秘密: ")
    fmt.Scan(&passWord)
    isAuthenticated := 0
    if len(userName) > 0 && len(passWord) > 0 {
        isAuthenticated = 197
        for _, item := range userList {
            if userName == item[0] {
                //当用户名和密码全部正确是也会走这个if分支!
                isAuthenticated = 198
            }
            if passWord == item[1] {
                //当用户名和密码全部正确是也会走 这个if分支!
                isAuthenticated = 199
            }

            if userName == item[0] && passWord == item[1] {
                isAuthenticated = 200
                break
            }
        }

    }
    switch isAuthenticated {
    case 200:
        fmt.Println("登录成功!")
    case 197:
        fmt.Println("用户和密码都不对!")
    case 198:
        fmt.Println("用户对了,密码不对!")
    case 199:
        fmt.Println("密码对了,用户名不对!")
    default:
        fmt.Println("用户和密码都没有输入!")

    }
}
Doing more practice

 

 

see also

 

posted on 2020-03-23 10:08  Martin8866  阅读(1419)  评论(0编辑  收藏  举报