#第三篇:字符编码、数据类型

##3.1字符编码介绍

一、什么是字符编码

计算机要想工作必须通电,即用‘电’驱使计算机干活,也就是说‘电’的特性决定了计算机的特性。电的特性即高低电平(人类从逻辑上将二进制数1对应高电平,二进制数0对应低电平),关于磁盘的磁特性也是同样的道理。结论:计算机只认识数字,也只能存储数字
很明显,我们平时在使用计算机时,用的都是人类能读懂的字符(用高级语言编程的结果也无非是在文件内写了一堆字符),那如何才能让计算机识别并记录人类的字符呢?

这就必须经历一个过程:

字符------->(翻译过程)------->数字

这个过程就需要一个字符与特定数字一一对应的标准,这个标准称之为字符编码。

二、字符编码的发展史与分类

计算机由美国人发明,最早的字符编码为ASCII,只规定了英文字母数字和一些特殊字符与数字的对应关系。最多只能用 8 位来表示(一个字节),即:2**8 = 256,所以,ASCII码最多只能表示 256 个符号

当然我们编程语言都用英文没问题,ASCII够用,但是在处理数据时,不同的国家有不同的语言,日本人会在自己的程序中加入日文,中国人会加入中文。

而要表示中文,单拿一个字节表表示一个汉子,是不可能表达完的(连小学生都认识两千多个汉字),解决方法只有一个,就是一个字节用>8位2进制代表,位数越多,代表的变化就多,这样,就可以尽可能多的表达出不通的汉字。

所以中国人规定了自己的标准gb2312编码,规定了包含中文在内的字符->数字的对应关系。日本人规定了自己的Shift_JIS编码韩国人规定了自己的Euc-kr编码(另外,韩国人说,计算机是他们发明的,要求世界统一用韩国编码,但世界人民没有搭理他们)。

这时候问题出现了,精通18国语言的小周同学谦虚的用8国语言写了一篇文档,那么这篇文档,按照哪国的标准,都会出现乱码(因为此刻的各种标准都只是规定了自己国家的文字在内的字符跟数字的对应关系,如果单纯采用一种国家的编码格式,那么其余国家语言的文字在解析时就会出现乱码)。

所以迫切需要一个世界的标准(能包含全世界的语言)于是unicode应运而生(韩国人表示不服,然后没有什么卵用)。

ascii用1个字节(8位二进制)代表一个字符
unicode常用2个字节(16位二进制)代表一个字符,生僻字需要用4个字节

例:

字母x,用ascii表示是十进制的120,二进制0111 1000

汉字中已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101。

字母x,用unicode表示二进制0000 0000 0111 1000,所以unicode兼容ascii,也兼容万国,是世界的标准

这时候乱码问题消失了,所有的文档我们都使用但是新问题出现了,如果我们的文档通篇都是英文,你用unicode会比ascii耗费多一倍的空间,在存储和传输上十分的低效

本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间:

从上面的表格还可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

**总结:**内存中统一采用unicode,浪费空间来换取可以转换成任意编码(不乱码),硬盘可以采用各种编码,如utf-8,保证存放于硬盘或者基于网络传输的数据量很小,提高传输效率与稳定性。

基于目前的现状,内存中的编码固定就是unicode,我们唯一可变的就是硬盘的上对应的字符编码。

此时你可能会觉得,那如果我们以后开发软时统一都用unicode编码,那么不就都统一了吗,关于统一这一点你的思路是没错的,但我们不可会使用unicode编码来编写程序的文件,因为在通篇都是英文的情况下,耗费的空间几乎会多出一倍,这样在软件读入内存或写入磁盘时,都会徒增IO次数,从而降低程序的执行效率。因而我们以后在编写程序的文件时应该统一使用一个更为精准的字符编码utf-8(用1Bytes存英文,3Bytes存中文),再次强调,内存中的编码固定使用unicode。

1、在存入磁盘时,需要将unicode转成一种更为精准的格式,utf-8:全称Unicode Transformation Format,将数据量控制到最精简

2、在读入内存时,需要将utf-8转成unicode。内存中用unicode是为了兼容万国软件,即便是硬盘中有各国编码编写的软件,unicode也有相对应的映射关系,但在现在的开发中,程序员普遍使用utf-8编码了

##3.2基本数据类型及其操作


###3.2.1整型
Go语言里支持有符号和无符号两种整型。这里的符号就是正负号。

分类:

有符号的整型:int , int8 , int16 , int32 , int64

无符号的整型:uint,uint8,uint16,uint32,uint64
其它:uintptr(指针小节单独介绍)

说明:

1、8,16,32,64表示的是二进制数,即整型大小,1个字节(字节叫byte)= 8bits(1bit就是一位二进制)

2、int/uint比较特殊,它会根据不同的系统进行匹配,比如64位系统他就会存成64位的整型,32位系统它就会存成32位的整型,但是需要强调的是:int类型和int64或者int32不是同类型,不能直接进行运算。

范围:

####3.2.1.1整型的操作
整型的操作就不多说了,什么算术运算、比较运算、位运算,很简单。
但需要特别强调的是:只有相同类型之间才能进行运算和比较

###3.2.2浮点型
分类:

float32 ,float64

说明:

float32,可以显示8位,精确到7位(含整数位),如下:

var a float32 = 123456.785
fmt.Println(a)   ---------> 结果:123456.78var a float32 = 1.23456785
fmt.Println(a)   ---------> 结果:1.2345679

float64,可以显示17位,精确到16位(含整数位)

####3.2.2.1浮点型的操作
浮点型的操作也不多说了,算术运算、比较运算都行,就是不能直接进行位运算。

###3.2.3复数类型
我们把形如a+bi(a叫做复数的实部,b叫做复数的虚部,a、b的类型皆为浮点型,i是虚数单位)的数称为复数。

分类:

complex64(complex64里,a,b皆是float32)
complex128(complex64里,a,b皆是float64)

定义:

var v1 complex64       ---->初始值为(0+0i)
v2 := 3.2 + 12i        ---->不指定类型的,默认是complex128
v3 := complex(3.2,12)  ---->complex128

####3.2.3.1复数的操作
复数支持常规的数学运算,这里也不做过多说明

###3.2.4bool型
bool类型很简单,就两个值,true、false。支持逻辑运算:

true || false  ----> true
true && true   ----> true
!true          ----> false

###3.2.5字符型

分类:

string类型有两种表示方法,一种就用双引号引起来(”“),一种是用反引号引起来(``)

说明:

虽然 “”,``都可以表示字符串,但两者之间也有区别
先说“”,这样的字符串叫做解释型字符串,字符串里可以使用转义符号“\”,常用转义字符如下:

\n     换行
\r     回到行首
\"     双引号
\\     反斜杠

再说``,这样的字符串叫做非解释型字符串,非解释型字符串不支持转义符号,会原样输出引号内的内容,包括换行、空格等等。
####3.2.5.1字符串的操作

一、字符串逻辑比较

支持“!=,==,>,< ”

“!=”和“==”就不说了,字符串一毛一样才相等。

我们说说“>”和“<”。字符串比较大小,会从头到尾逐一比较,一旦分出结果就结束比较

var a string = "abc"
var b string = "bzz"
fmt.Println(b>a)           ------>  true

二、字符串拼接

用“+”,“+=”进行字符串拼接,如下:

var a = "abc" + "def"
fmt.Println(a)             ------>"abcdef"

三、字符串输出格式化

通过使用fmt.Printf("")来实现字符串输出时的格式化,实现如下:

var a int = 520
fmt.Printf("xxx,%d",a)   ----->结果:"xxx,520"

其中%d用来占位,a用来传值给%d
其它常用指令符如下:

四、获取字符串字节长度

使用len(字符串)获取:

fmt.Println(len("wo我"))   ---->中文占3个字节,所以结果是5

五、按索引访问字符串字节

字符串“wo我”,长度为5,即5个字节,“w“”o“分别占一个字节,”我“占3个字节。字符串会按照字节,从前到后依次用索引标记,第一个字节为0,第二个字节为1,以此类推。

我们可以通过索引访问到指定字节。如下:(得到的是utf-8的十进制表示)

var a = "wo我"fmt.Println(a[0])     ---->结果是119,119是“w”的utf-8编码的十进制表示
fmt.Println(a[2])     ---->结果是230,“我”占3个字节,230是我的第一个字节

六、字符串切片操作

形如:字符串[起始字节索引:结束字节索引]。省略起始索引,默认就从0开始,省略结束索引,默认就以最后一个索引结束。这样我们可以得到一个新的字符串。

强调:1、索引是按字节标记的;2、[起始索引:终止索引]是顾头不顾尾,或者说左闭右开。

var a = "我很帅"
var b = a[0:3]
fmt.Println(b)    ---->结果:"我",[0:2]刚好切出了表示"我"的三个字节
var c = a[3:]
fmt.Println(c)    ---->结果:"很帅",结束索引被省略,默认切到最后
var d = a[:3]
fmt.Println(d)    ---->结果:"我",起始索引被省略,默认从头开始切
var e = a[:]
fmt.Println(e)    ---->结果:"我很帅",都省略,默认从头切到最后

七、字符串的遍历

我们使用for i,v := range 字符串 {} 来遍历字符串

for i,v := range "我是haha" {fmt.Println(i,v)
}结果:0 25105    //从0开始计数,25105是unicode中“我”的序号(十进制)3 26159     //因为“我”占3个字节,所以,“是”的索引就是从3开始6 104       //“h”占一个字节7 978 1049 97

当然,学了printf格式化输出之后,unicode码点我们就可以用%c处理:

for i,v := range "我是haha"{fmt.Printf("%d,%c\n",i,v)
}结果:0,我3,是6,h7,a8,h9,a

八、strings包

Go语言为了简化字符串的复杂操作,在标准库中提供了一个名为strings的包,里面集成了很多功能,我们直接调用就好,不需要再自己编写。

下面我们来学习strings包中的几个常用的功能:

①、是否以…开头:

strings.HasPrefix(str, “…”),使用如下:

var str1 = "我很帅"
fmt.Println(strings.HasPrefix(str1, "我"))    ------> true

②、是否以…结尾:

strings.HasSuffix(str, “…”),使用如下:

var str1 = "我很帅"
fmt.Println(strings.HasSuffix(str1, "很帅"))   ------> true

③、是否包含…

strings.Contains(str, “…”),使用如下:

var str1 = "我真的很帅"
fmt.Println(strings.Contains(str1, "真的"))    ------> true

特别的:

var str1 = "我真的很帅"
fmt.Println(strings.Contains(str1, ""))        ------> true

④、是否包含…中任意字符

strings.ContainsAny(str, “…”),使用如下:

var str1 = "我真的很帅"
fmt.Println(strings.ContainsAny(str1, "真帅"))    ------> true

特别的:

var str1 = "我真的很帅"
fmt.Println(strings.ContainsAny(str1, ""))      ------> false

⑤、查找…第一次出现的索引

strings.Index(str, “…”)

若存在就返回指定字符串第一个字符的索引,若不存在,就返回-1,使用如下:

var str1 = "我真的真的很帅"
fmt.Println(strings.Index(str1, "真的"))   -----> 3var str1 = "我真的真的很帅"
fmt.Println(strings.Index(str1, "很丑"))   -----> -1

⑥、查找…最后一次出现的索引

strings.LastIndex(str, “…”)

若存在就返回指定字符串第一个字符的索引,若不存在,就返回-1,使用如下:

var str1 = "我真的真的很帅"
fmt.Println(strings.LastIndex(str1, "真的"))     -----> 9

⑦、字符串替换

strings.Replace(str, oldstr, newstr, n)

oldstr表示需要被替换的字符串,newstr表示替换内容,n表示匹配个数,n为-1表示匹配所有,使用如下:

var str1 = "我真的真的真的很帅"
fmt.Println(strings.Replace(str1, "真的", "非常", 2))    ---> 我非常非常真的很帅var str1 = "我真的真的真的很帅"
fmt.Println(strings.Replace(str1, "真的", "非常", -1))    ---> 我非常非常非常很帅

⑧、频率统计

strings.Count(str, target)

target表示需要统计的字符串,返回总个数,使用如下:

var str1 = "我真的真的真的很帅"
fmt.Println(strings.Count(str1,"真的"))     -----> 3

小技巧(统计字符数量):

var str1 = "我真的真的真的很帅"
var lenStr1 = strings.Count(str1,"") - 1

⑨、大小写转换

strings.ToLower(str),全部转成小写

strings.ToUpper(str),全部转成大写

var str = "老王是dSB"
fmt.Println(strings.ToLower(str))     ---->  老王是dsb
fmt.Println(strings.ToUpper(str))     ---->  老王是DSB

⑩、修剪

strings.Trim(str,target)

strings.TrimLeft(str,target)

strings.TrimRight(str,target)

target是被修剪的字符串,trim用来修剪首尾,TrimLeft用来修剪首部,TrimRight用来修剪尾部。

strings.TrimSpace(str),用来修剪首尾的空格换行

var str = "!!!golang!!!"
fmt.Println(strings.Trim(str,"!"))         ---->golang
fmt.Println(strings.TrimLef(str,"!"))      ---->golang!!!
fmt.Println(strings.TrimRight(str,"!"))    ---->!!!golangvar str = "\n\tgolang\n\t"
fmt.Println(strings.TrimSpace(str))         ---->golang

⑪、分割

strings.Split(str, target)

target是用来分割字符串的字符串,结果是以target为分隔点得到的一个slice切片

var str = "a,b,c,d"
fmt.Println(strings.Split(str, ","))   --->[a b c d]var str = "我很帅"
fmt.Println(strings.Split(str, ""))    --->[我 很 帅]

⑫、插入

strings.Join(strslice, target)

strslice是一个切片类型的数据,如⑪中分割所得到的,target为需要插入的字符串,得到的结果是每一个slice切片中的元素拼上插入字符串得到的新字符串

var str = "a,b,c,d"
var strslice = strings.Split(str, ",")      --->[a b c d]
fmt.Println(strings.Join(strslice, ","))    --->a,b,c,d

九、strconv包

这个包主要用于字符串与其它类型的转换,conv是convert的缩写,变换的意思

①、将bool值转成字符串

strconv.FormatBool(bool)

var strbool = strconv.FormatBool(true)      --->类型:string,值:"true"

②、把字符串转成bool值

strconv.ParseBool(str),

str为"1",“t”,“T”,“TRUE”,“true”,“True”,时,返回值为bool类型的true

str为"0",“f”,“F”,“FALSE”,“false”,“False”,时,返回值为bool类型的false

str为其它值时,会转换错误,返回错误值。

因为转换可能出错,所以strconv.ParseBool(str)有两个返回值,一个接受转换值,一个接受错误

var istrue,err = strconv.ParseBool("1")
if err == nil {                    ---> nil表示空,即什么都没有的意思fmt.Println(istrue)            ---> err=nil,即没有错误,那就打印正确结果
}else{fmt.Println(err)               ---> 否则,打印错误信息
}

③、整型转成字符串

strconv.FormatInt(int64的整型,进制数)

这会把指定的int64的整型按照指定的进制数转成字符串
同理,还有strconv.FormatUint(uint64的整型,进制数)

var intstr1= strconv.FormatInt(0x123,8)     --->16进制转8进制以字符串显示
var intstr1= strconv.FormatInt(123,10)      --->10进制转10进制以字符串显示

④、字符串转整型

strconv.ParseInt(表示数字的字符串, 参数进制数, 参数大小)

表示数字的字符串:如16进制的“0x123”,8进制的“0123”,十进制“123”,不过,表示进制的“0x”“0”也不可以不写,由第二个参数指定

参数进制数:0,表示进制数由字符串决定,8,表示字符串是8进制,此时字符串中表示八进制的“0”不写

参数大小:这个值表示bit,用来描述字符串中数字的大小或者说范围,比如,int8的范围是-128127,那么当该参数设置为8的时候,字符串中的数字就不能超过-128127这个范围,不然就会报错。如果是0,那就是int。

得到的结果是两个值,一个是正确结果int64类型,一个是错误信息。

同理,还有strconv.ParseUint(表示数字的字符串, 参数进制数, 参数大小)

var v,err = strconv.ParseInt("0x123",0,0)     ---> v:int64类型的291,err:nil
var v,err = strconv.ParseInt("123",16,0)      ---> v:int64类型的291,err:nil
var v,err = strconv.ParseInt("123",0,0)       ---> v:int64类型的123,err:nil
var v,err = strconv.ParseInt("128",0,8)       ---> err:value out of range

⑤整型转10进制以字符串显示

strconv.Itoa(int类型整数),相当于strconv.FormatInt(int64的整型,10)的简写。

“itoa”里的i表示int,a表示algorism(十进制)

var a = strconv.Itoa(0x123)     ---> "291"
var b = strconv.Itoa(123)       ---> "123"var c int8 = 123
var d = strconv.Itoa(c)         ---> 报错

⑥、字符串表示的十进制转整型

strconv.Atoi(str),相当于strconv.ParseInt(str, 10, 0)

var v,err = strconv.Atoi("123")  ---> v:123 err:nil

⑦、float64转字符串

strconv.FormatFloat(float64的小数, 显示格式, 保留位数, 精度)

显示格式:‘e’/‘E’:用科学计数法表示,如1.23e+2、1.23E+2

​ ‘f’:正常显示,如3.14159265

​ ’g‘/‘G’:由保留位数确定是用科学计数表示还是正常表示

保留位数:该参数设置为-1时,保留位数由第四个参数(32/64)的精度确定。

​ 对于’e’/‘E’/‘f’,保留位数就是小数点后的保留位数,超过第四个参数(32/64)的精度限制之后就不准确了。

​ 对于‘g’/‘G’,保留位数是整数和小数部分总和,但一定不能超过精度

精度:32或者64,相当于float32和float64,来控制显示精度

var a = strconv.FormatFloat(12.345678910,'f',5,32)   ---->  12.34568
var b = strconv.FormatFloat(12.345678910,'g',1,32)   ---->  1e+01
var c = strconv.FormatFloat(12.345678910,'g',4,32)   ---->  12.35

⑧、字符串转float64

strconv.ParseFloat(小数字符串,类型)

类型:32/64 , 32表示按照float32的精度去处理字符串,64表示按照float64的精度去处理字符串。需要注意的是,它的结果是float64,按照float32的精度去处理后,会按照float64去显示,这会带来一些问题,我们都知道float32可以显示8位,精确到7位,但是在这里转换后,会按照float64去显示结果,这就造成第8位后出现一堆乱七八糟的数字,以至于我们无法用于判断。所以,正常情况,我们最好使用64

var v,err = strconv.ParseFloat("12345.67891234567891",64)
fmt.Println(v,err)                             ---> v:12345.678912345678   err:nil

##3.3复合数据类型及其操作
###3.3.1指针

什么是指针

内存的每一小块区域都会有内存地址标识,我们把内存地址用变量存起来,这样的变量就叫做指针

为什么要有指针

要回答为什么要有指针,就要说两个关于函数传参的概念,一个是引用传递,一个是值传递。值传递,就会把你传递过去的变量拷贝一个副本然后传进去,无论你对这个值怎么操作,改变的都是副本的值,原来变量的值是不会变的。引用传递就相反,传递过去的是内存地址,可以直接对变量的值进行修改。所以为什么要有指针呢,就是为了方便我们直接对内存里的数据进行操作。

怎么用指针

####3.3.1.1指针的类型

指针的类型就是在正常类型前加个"*",比如,*int,表示int的指针类型;*float64,表示float64的指针类型。
####3.3.1.2指针的定义

定义指针就是定义一个变量,只是这个变量的值是内存地址。
我们通过“&”来获得另一个变量的内存地址,如下:

var a int = 123
var b *int = &a           ----->结果如:0xc00004a080,每次运行分配的内存是不同的
//也可以用短声明,b := &a

####3.3.1.3指针的操作
1、获取指针指向的值

我们通过在指针前加“*”来获取指针指向的值,如下:

var a string = "dsb"
b := &a
fmt.Println(*b)      ----> "dsb"

2、修改指针指向的值

修改值,我们直接操作*x就行了,支持内存中该类型的任意操作,如下:

func change(x *string){*x+="dsb"
}func main(){var name = "老王"change(&name)fmt.Println(name)     -----> "老王dsb"
}

###3.3.2数组

什么是数组

字面意思就是一组数据,就是用来存放一推同类型数据的容器。它的底层是用一块连续的内存空间(内存地址连续)来存储数据的。

为什么要有数组

很多复杂的数据结构都是基于数组和链表实现的,数组作为最最基本的数据结构一定是存在的。它的优点在于根据索引取数据很快,那具体原因我这里不深入研究,在数据结构和算法课程里会另外详细讲解。

怎么用数组

####3.3.2.1 声明数组
我们声明一个数组同样是用var关键字或者是“:=”短声明。如下:

var myArry [5]int
//这里的5是告诉计算机我声明的数组长度为5,即有5个元素
//这里的int是告诉计算机,我声明的数组里存放的是int类型
//这里因为我没有给值,所以每个元素都是初始值,int的初始值为0,所以该数组就是[0 0 0 0 0]、myArry := [5]int{1,2,3,4,5}
//我们用花括号来给数组定义初始值,该数据的值为[1 2 3 4 5]
//元素个数超出指定的长度就会报错myArry := [...]int{1,2,3,4,5}
//我们也可以长度用...代替,让程序自动根据值来判断数组长度maArry := [5]int{2:10,4:11}
//我们也可以给元素标记索引,这里的2,4就是索引,10会存在索引为2的位置,11会存在索引4的位置
//其它值为默认值,所以数组为[0 0 10 0 11]

####3.3.2.2数组的操作

1、获取数组长度
这个很简单,用len()函数就行

arry := [5]int{1,2,3,4,5}
fmt.Println(len(arry))     ---> 5

2、按索引访问元素

数组索引从0开始,最大索引是数组长度-1。操作如下:

var myArry = [5]int{1,2,3,4,5}
fmt.Println(myArry[0])   ---> 1
fmt.Println(myArry[2])   ---> 3
fmt.Println(myArry[4])   ---> 5

3、修改元素

myArry := [3]{1,1,1}
myArry[0] = 10
myArry[1] = 11
myArry[2] = 12
fmt.Println(myArry)  ---> [10,11,12]

4、数组比较

数组支持==,!=,但前提得满足两个条件:一是数组的类型必须一致(长度,元素类型都得一致),二是元素支持 = =,!=

var a = [3]int{1,2,3}
var b = {3}string{"a","b","c"}
fmt.Println(a == b)     --->类型不同,报错:mismatched types [3]string and [3]intvar a = [3]int{1,2,3}
var b = [2]int{1,2}
fmt.Println(a == b)     --->类型不同,报错:mismatched types [3]int and [2]intvar a,b [3]map[string]int
fmt.Println(a == b)     --->元素不支持比较,[3]map[string]int cannot be compared
//这个map类型后面会讲,这里先提一下

5、遍历数组

我们用for…range…遍历数组,如下

var citiesOfChina = [3]string{"shanghai","beijing","riben"}for k,v := range citiesOfChina{fmt.Println(k,v)
}//结果:(k拿到元素索引,v拿到元素值)
//0 shanghai
//1 beijing
//2 riben

6、补充:

①、如果把数组当成参数传给一个函数,这个时候应该传递数组的指针。因为直接传递数组的话,是值传递,程序会拷贝一份数组再给函数使用,如果数组很大的话,这个拷贝的过程会比较耗时耗内存。而使用指针传递的话,只需要传一个指针过去就好了。

②、数组可以存任意类型的数据,其元素可以是float,int,bool,arry,map,struct…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A3ImRQay-1577153551908)(…/新配图/恶搞图/04-21-卖力.jpg)]

###3.3.3切片

什么是切片

在go语言里切片这个概念比较特殊,可以看成是数组切片和动态扩容数组的结合。我们先看数组切片,数组切片顾名思义,就是把数组里的数据根据需要切下一块,就像切糕,数据是整个切糕,切片是切下的那一块。再看动态扩容数组,数组我们学过,它的容量是固定的,规定好是几就是几,动态扩容数组顾名思义就是会自动增加容量的数组。

为什么要有切片

这个问题其实就是为什么要有数组切片和动态扩容数组。

为什么要有数组切片,把一堆数据切成我们要的那一块,方便我们针对性的操作数据。

为什么要有动态扩容数组,因为数组不支持扩容,如果我数据存满了,就得自己再创建一个更大的数组,然后把原来的数据一个个丢进去,很麻烦。所以就有了动态扩容数组。

怎么用切片

####3.3.3.1切片的定义:
1、由数组或者切片生成

数据或切片[开始位置:结束位置],顾头不顾尾,左闭右开区间,代码如下:

var myArry = [3]int{1,2,3}  ---> 这是一个数组
var mySlice = myArry[0:2]   ---> 顾头不顾尾,所以就是切的索引为0,1的数据
var mySlice2 = mySlice[0:1] fmt.Println(myArry[0],&myArry[0])       ---> 1 0xc0000480c0
fmt.Println(myArry[1],&myArry[1])       ---> 2 0xc0000480c8
fmt.Println(mySlice[0],&mySlice[0])     ---> 1 0xc0000480c0
fmt.Println(mySlice[1],&mySlice[1])     ---> 2 0xc0000480c8
fmt.Println(mySlice2[0],&mySlice2[0])   ---> 1 0xc0000480c0mySlisc[0] = 100
fmt.Println(myArry)                     --->[100,2,3]
fmt.Println(mySlice)                    --->[100,2]
fmt.Println(mySlice2)                   --->[100]

由数组和切片生成的切片,它的元素和生成它的数组或切片指向的是同一个内存地址。

如果省略开始位置,就默认从0索引开始,如果省略结束位置,就默认到最后一个索引为止

2、var关键字声明切片

var mySlice1 []int
var mySlice2 = []int{}
var myslice = []int{1,2,3}//需要注意的是:
//mySlice1 == nil ---> true
//mySlice2 == nil ---> false

3、make()函数构造切片

切片有两个属性,一个是长度,一个是容量,容量表示切片有多大可以存多少数据,长度表示已经存了多少数据。

我们用make( []T ,size ,cap )来创建一个切片。

T:表示切片元素类型

size:表示长度,就是创建的时候我要有几个元素

cap: 表示容量,即我需要申请多少内存空间(可以省略,如果省略cap就等于size)

var mySlice1 = make([]int,2)
var mySlice2 = make([]int,2,10)
fmt.Println(mySlice1,cap(mySlice1))   --->  [0 0] 2
fmt.Println(mySlice2,cap(mySlice2))   --->  [0 0] 10

####3.3.3.2切片的操作

1、获取切片长度

len()函数

var mySlice1 = make([]int,2,10)
fmt.Println(len(mySlice1))            --->  2

2、获取切片容量

cap()函数

var mySlice1 = make([]int,2,10)
fmt.Println(cap(mySlice1))            --->  10

3、遍历切片

遍历切片和遍历数组一样,用for…range…

4、增加元素

①使用append(切片,元素)来增加元素

var mySlice = make([]int,0,2)
fmt.Println(mySlice)             ---> []
mySlice = append(mySlice,1,2)
fmt.Println(mySlice)             ---> [1 2]

如果容量满了之后我继续append呢,会报错吗?当然不会,因为它是动态扩容数组,程序会另外申请一块新的内存空间,容量是原来的2倍,然后把原来的数据复制进去。怎么证明呢?看代码:

var mySlice = make([]int,0,2)
mySlice = append(mySlice,1,2)
fmt.Println(mySlice)             ---> [1 2]
fmt.Println(&mySlice[0])         ---> 注意此时的内存地址 0xc0000120b0
fmt.Println(cap(mySlice))        ---> 注意此时的容量 2
//现在切片满了,我继续append
mySlice = append(mySlice,1)
fmt.Println(&mySlice[0])         ---> 注意此时的内存地址 0xc0000102a0
fmt.Println(cap(mySlice))        ---> 注意此时的容量 4

当我继续append的时候,原来索引为0的元素,内存地址变了,说明这是一个新的内存空间,而这时候的容量也从2变成了4。

强调:

我们之前提到过,数组生成的切片,其元素和数组元素指向的同一个内存地址,如果我们继续append直到超出容量,切片就会扩容,申请新的内存空间,把元素拷贝进去,这个时候,切片元素和数组元素的内存地址就没有关联了。

②、使用append(切片1,切片2…)来增加元素

这会把切片2中的所有元素增加到切片1后面,看代码:

var a = []int{1,2}        ---> 切片:[1 2]
var b = make([]int,2,5)   ---> 切片:[0 0]
b = append(b,a...)        ---> 把切片a的元素增加到切片b里
fmt.Println(a)            ---> [1 2]
fmt.Println(b)            ---> [0 0 1 2]

5、复制切片

用copy(切片1,切片2)把切片2的数据拷贝到切片1里去。copy会根据索引替换掉切片1里已存在的数据。看代码:

var a = []int{1,2,3}          ---> 声明一个切片[1 2 3]
var b = make([]int,2,5)       ---> 声明一个容量为5,元素个数为2的切片[0 0]
copy(b,a)                     ---> 把切片a的数据copy到切片b里
//先来看一下a和b里的元素
fmt.Println(a)                ---> [1 2 3]
fmt.Println(b)                ---> [1 2]
//再来看一下a,b元素的内存地址
fmt.Println(&a[0])            ---> 0xc0000480c0
fmt.Println(&b[0])            ---> 0xc000070030

注意:copy拷贝的只是切片的值,不会拷贝元素内存地址

5、删除元素

GO语言里没有直接删除元素的操作,我们可以通过切片,然后append拼接,看代码:

var a = []int{1,2,3,4,5}
//我现在要删掉索引为2的数据,这里就是删掉3
//我们把a分成2块,[1 2],[4 5],然后append拼接
a = append(a[0:2],a[3:5]...)
fmt.Println(a)                ---> [1 2 4 5]

6、给函数传值

我们往函数里传数组的时候,为了防止产生数组副本影响性能,应该传数组指针。那我们传切片呢,也要传指针吗?在回答这个问题之前,我们先来学习一下数组的底层实现:

切片底层是一个链表,链表维护着3个数据,一个是arry,它存的是一个数组的内存地址,第二个是len,数组长度,第三个是cap,数组容量。动态扩容是怎么实现的呢,就是len和cap的判断,当你执行append的时候,就会去判断len和cap是否相等,相等的话就会去申请一个新的内存空间,把数组的元素拷贝进去,然后更新链表中arry的值。

所以其实切片就存了3个数据,一个是数组的内存地址,一个是数组的长度,还一个是数组的容量,一共占了24个字节。24个字节并不大,所以给函数传切片直接传它本身就好。

###3.3.4映射

什么是映射

映射就是一种对应关系,
a和b相对应,那我通过a就可以找到b,这个就是映射

为什么要有映射

有这么一个场景,你有一个数组,存着用户名和密码,[“laowang” “abcd”],因为是你自己存的,所以你知道,索引0对应的是用户名,索引1对应的是密码。时间久了你还记得吗?或者当别人拿到你的数据的时候,别人不知道哪个是用户名哪个是密码,所以这个时候,就需要一种数据结构,可以给“laowang”,“abcd”分别起个名字“name” ”password“,然后通过这个别名去拿到对应的数据。在映射类型中,这个别名叫做“键”,”laowang“叫做”值“

怎么用映射
####3.3.4.1映射的定义
map类型的格式:map[KeyType]ValueType

KeyType:是映射中键的类型,强调:此类型要能够判断“==”

ValueType:是映射中值的类型,可以是任意类型

dict := map[string]string{}
dict := map[string]string{"name":"laowang","pwd":"abcd"}
dict2 := make(map[string]string)

####3.3.4.2映射的操作
1、元素赋值、创建元素

我们用映射名[key值]去创建元素,以及赋值

dict := map[string]string{}   //声明一个空映射
dict["name"] = "laowang"
dict["password"] = "abc"      //创建元素
dict["password"] = "123"      //重新赋值

2、查找元素

依旧是映射名[key值],查找元素时,它有两个返回值,一个是value值,一个是exists,判断key是否存在的。

 dict := map[string]string{"name":"laowang","pwd":"abc"}value1,esists1 := dict["email"]//若不存在,value为当前类型默认值,exists为fasle

3、删除元素

delete(myMap, key),第一个参数是map类型,第二个参数是要删除的键值对的key值。

dict := map[string]string{"name":"laowang","pwd":"abcd","email":"sb@sb.com"}
delete(dict,"email")   //删除key为"email"的键值对
fmt.Println(dict)
//map[name:laowang pwd:abcd]

4、遍历

for…range…遍历

dict := map[string]string{"name":"laowang","pwd":"abc"}
for key,value := range dict {fmt.Printf("我是key:%v  我是value:%v",key,value)
}
//我是key:name  我是value:laowang
//我是key:pwd  我是value:abc

5、传递给函数

把map传递给函数的时候直接传递它本身就好,因为map底层存的也是指针。

func del (myDict map[string]string) {delete(myDict,"name")
}dict := map[string]string{"name":"laowang","pwd":"abc"}
del(dict)
fmt.Println(dict)
//map[pwd:abc]

第三篇:字符编码、数据类型相关推荐

  1. 三种字符编码:ASCII、Unicode和UTF-8

    原文:三种字符编码:ASCII.Unicode和UTF-8 什么是字符编码? 计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字 ...

  2. No.1 字符编码数据类型

    No.1 字符编码&数据类型 一.字符编码 ASCII,主要用于现代英语,定长1byte,最多表示2^8-1=255个符号 GB2312,约7k个汉字,数量太少 GBK扩展到了2w汉字,还有藏 ...

  3. 常见的三种字符编码ASCII、Unicode、UTF-8

    发展史 ASCII 码 -> Unicode -> UTF-8 背景 计算机内部,信息都已二进制储存,每一个二进制位有 0 或 1 两种状态,采用 8 个 二进制位 (bit) 作为一个字 ...

  4. 常见三种字符编码的区别:ASCII、Unicode、UTF-8

    什么是字符编码? 计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255( ...

  5. 一文带你弄懂C++中的ANSI、Unicode和UTF8三种字符编码及相互转换

    目录 1.概述 2.Visual Studio中的字符编码 3.ANSI窄字节编码 4.Unicode宽字节编码 5.UTF8编码

  6. python中输出变量对应值的字符_第2章 Python基础-字符编码数据类型 字符编码字符串 练习题...

    1.简述位.字节的关系 位(bit)是计算机中最小的表示单元,数据传输是以"位"为单位的,1bit缩写为1b 字节(Byte)是计算机中最小的存储单位,1Byte缩写为1B 8bi ...

  7. 12道Python基础字符编码数据类型练习题

    1.转换 将字符串s = "alex"转换成列表 s = "alex" s_list = list(s) print(s_list) 将字符串s = " ...

  8. js笔记(三)ES5、ES5新增的数组的方法、字符串的方法、字符编码、对象的序列化和反序列化、bind

    数组方法.字符串方法总结 大目录 小目录 一.ES5严格模式 1. 严格模式: 2. 严格模式的行为变更: 二.ES5新增的数组的方法 1. 判断是否为数组:Array.isArray(): 2. 判 ...

  9. 数据类型的相互转换 ,字符编码

    一,复习: ''' 1,深浅拷贝 ls = [1,'a',[10]] 值拷贝:直接赋值 ls1 = ls,ls中的任何值发生改变,ls1也随之发生改变 浅拷贝:通过copy()方法 ls2 = ls. ...

  10. day03_20170514_字符编码/文件存储/函数(一)

    第一节:字符编码 一.在会字符编码之前必须要了解的东西: 1.文本编辑器存取文件的原理(nodepad++,pycharm,word) 文本编辑器在存储的时候是怎样的一个过程?----编辑文字,第一件 ...

最新文章

  1. Maven搭建springMVC+spring+hibernate环境
  2. Blazor University (10)组件 — 捕获意外参数
  3. 大数据基础技术和应用
  4. postscript打印机什么意思_涨知识|你不知道的关于打印机的打印过程和打印机驱动的那些事...
  5. input 标签在做动画时的bug
  6. 信息学奥赛C++语言:梯形面积
  7. Squid 2.6 Configuration Manual - Log File Path Names and Cache Directories
  8. spring事务分类简述
  9. 再品Resnet残差网络
  10. 【渝粤教育】国家开放大学2018年春季 0089-21DInternet和Intranet应用 参考试题
  11. java实现顺序表的增加,删除,查找,打印
  12. Keli Linux与网络安全(2)——初探Keli
  13. Linux安装Diamond软件,1.1 Linux下安装diamond
  14. 码农自述:猝死瞬间,我在想些什么?
  15. 树莓派简单入门(基本小案例)
  16. js压缩图片到指定大小
  17. python入门知识以及print的用法
  18. iOS从相册选择视频和保存视频到相册
  19. Java后端技术框架
  20. 新版Edge浏览器怎么长截图?

热门文章

  1. 抖音开发对接之订单取消消息
  2. mysql 姓刘或姓李_爸爸姓刘妈妈姓李,娃的名字或成全国唯一,网友:比王者荣耀霸气...
  3. 与央视联手打造“3.15京东国品日”,体现出刘强东的平台经济学
  4. myCobot pro 机械臂(1)初次使用(开发环境:w10 / UI flow)
  5. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java招生管理系统2ij21
  6. Ubuntu 环境下 php 安装 swoole 扩展
  7. java猜数字游戏实验报告_java猜数游戏实验报告.doc
  8. 破军(云集初始团队长,达令家操盘执行)的人生经历,没有人可以随便成功。
  9. 优柔寡断的人必读忠告!
  10. 为什么uber有星级_Uber的终结游戏到底是什么?