zoukankan      html  css  js  c++  java
  • Golang数据类型

    基本数据类型

    数据类型

    • bit计算机内部数据存储最小单位
    • byte 计算机中数据处理的基本单位
    • 计算机中以字节位单位存储和解释信息,规定一个字节由八个二进制位构成, 即一个字节等于8个比特(1Byte=8bit)

    查看变量的数据类型

        var n = 100
        fmt.Printf("n 的数据类型是 %T", n)
    

    查看变量的字节大小

        var n = 100
        fmt.Printf("n 的字节大小是 %d byte", unsafe.Sizeof(n))
    

    整数类型

    整型分为以下两个大类:

    • 按长度分为:int8、int16、int32、int64
    • 还有对应的无符号整型:uint8、uint16、uint32、uint64
    类型 有无符号 占用存储空间 表数范围 备注
    int 32byte/64byte 根据计算机架构决定
    int8 1byte=8bit -128 ~ 127
    int16 2byte=16bit -32768 ~ 32767
    int32 4byte=32bit -2147483648 ~ 2147483647
    int64 8byte=64bit -9223372036854775808 ~ 9223372036854775807
    uint 32byte/64byte 根据计算机架构决定
    uint8 1byte=8bit 0 ~ 255
    uint16 1byte=16bit 0 ~ 65535
    int32 4byte=32bit 0 ~ 4294967295
    int64 8byte=64bit 0 ~ 18446744073709551615
    uintptr 没有指定具体的bit大小 用于存放一个指针
    • uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

    • 其中,uint8 就是我们熟知的 byte 型,int16 对应C语言中的 short 型,int64 对应C语言中的 long 型。

    • Go 语言也有自动匹配特定平台整型长度的类型—— int 和 uint。

    • 逻辑对整型范围没有特殊需求时可以使用 int 和 uint。反之,在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint。

    • 整型变量在使用时,遵守保小不保大的原则,即:在保证程序正确运行下,尽量使用占用空间小的数据类型。

    byte与rune

    byte与rune都属于别名类型。byte是uint8的别名类型,而rune是int32的别名类型。
    一个rune的类型值即可表示一个Unicode字符。一个Unicode代码点通常由"U+"和一个以十六进制表示法表示的整数表示,例如英文字母'A'的Unicode代码点为"U+0041"。
    byte 等同于int8,常用来处理ascii字符
    rune 等同于int32,常用来处理unicode或utf-8字符

    类型 描述 用途
    byte 类似 uint8 常用来处理ascii字符
    rune 类似 int32 常用来处理unicode或utf-8字符

    浮点类型

    Go语言支持两种浮点型数:float32 和 float64。这两种浮点型数据格式遵循 IEEE 754 标准:

    • float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32。
    • float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64。
    • 浮点型的存储分为三部分:符号位+指数位+尾数位,在存储过程中,尾数部分可能丢失,造成精度损失
    • golang的浮点型默认为float64类型
    • 通常情况下,应该使用float64,因为它比float32更精确
    • 0.123可以简写成.123,也支持科学计数法表示:5.1234e2 等价于512.34
    类型 描述 精度
    float32 IEEE-754 32位浮点型数 单精度
    float64 IEEE-754 64位浮点型数 双精度

    打印浮点数时,可以使用 fmt 包配合动词%f,代码如下:

        package main
        import (
                "fmt"
                "math"
        )
        func main() {
                fmt.Printf("%f
    ", math.Pi)     //按默认宽度和精度输出整型。
                fmt.Printf("%.2f
    ", math.Pi)   //按默认宽度,2 位精度输出(小数点后的位数)。
        }
    

    复数类型

    复数类型有两个:complex64和complex128。实际上,complex64类型的值会由两个float32类型的值分别表示复数的实数部分和虚数部分。而complex128类型的值会由两个float64类型的值表示复数的实数部分和虚数部分。
    复数类型的值一般由浮点数表示的实数部分、加号"+"、浮点数表示的虚数部分以及小写字母"i"组成,比如3.9E+1 + 9.99E-2i。

    类型 结构
    complex32 float32实部+虚部
    complex64 float64实部+虚部

    字符类型

    Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用byte来保存。
    字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的,也就是说对于传统的字符串是由字符组成的,而Go的字符串不同,它是由字节组成的。

    • 字符只能被单引号包裹,不能用双引号,双引号包裹的是字符串
    • 当我们直接输出type值时,就是输出了对应字符的ASCII码值
    • 当我们希望输出对应字符,需要使用格式化输出
    • Go语言的字符使用UTF-8编码,英文字母占一个字符,汉字占三个字符
    • 在Go中,字符的本质是一个整数,直接输出时,是该字符对应的UTF-8编码的码值。
    • 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的unicode字符
    • 字符类型是可以运算的,相当于一个整数,因为它们都有对应的unicode码

    但是如果我们保存的字符大于255,比如存储汉字,这时byte类型就无法保存,此时可以使用uint或int类型保存

    字符类型本质探讨

    字符型存储到计算机中,需要将字符对应的码值(整数)找出来
    存储:字符 --> 码值 --> 二进制 --> 存储
    读取: 二进制 -->码值 --> 字符 --> 读取

    字符和码值的对应关系是通过字符编码表决定的(是规定好的)
    Go语言的编码都统一成了UTF-8。非常的方便,很统一,再也没有编码乱码的困扰了

    布尔类型

    布尔型数据在 Go 语言中以 bool 类型进行声明,布尔型数据只有 true(真)和 false(假)两个值。

    • bool类型占1个字节
    • bool类型适用于逻辑运算,一般用于流程控制
    • Go 语言中不允许将整型强制转换为布尔型, cannot convert n (type bool) to type int
    • 布尔型无法参与数值运算,也无法与其他类型进行转换。

    字符串类型

    字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本

    • 字符串一旦赋值了,就不能修改了:在Go中字符串是不可变的
    • 字符串的两种标识形式
    • 双引号,会识别转义字符
        var str = "abc
    abc" //输出时会换行
    
    • 反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
        var str string = `abc
    abc` //输出时原样输出,不会转义
    
    • 字符串拼接方式"+"
        var str string = "hello " + "world"
        str += "!"
    
    • 当一行字符串太长时,需要使用到多行字符串,可以使用如下处理
        //正确写法
        str := "hello" + 
                " world!"
        fmt.Println(str)
    
        //错误写法
        str := "hello "
                + "world!"
        fmt.Println(str)
    

    基本数据类型默认值与数据类型转换

    基本数据类型默认值

    在Golang中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值,在Golang中,默认值也叫做零值。
    基本数据类型默认值如下:

    数据类型 默认值
    整型 0
    浮点型 0
    字符串 ""
    布尔类型 false

    数据类型转换

    Golang和Java/C不同,Golang在不同类型的变量之间赋值时需要显式转换。也就是Golang中数据类型不能自动转换。

    基本语法:
    表达式var_type(var_name) 将值v转换为类型var_type
    var_type:就是数据类型,比如int32, int64, float32等等
    var_name:就是需要转换的变量

        var num int = 42
        var float float64 = float64(num)
        var ui uint8 = uint8(float)
        fmt.Println(num, float, ui)
    

    注意事项

    • Go中,数据类型的转换可以是从表示范围小-->表示范围大,也可以 范围大—>范围小
    • 被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化!
    • 在转换中,比如将int64转成int8,编译时不会报错,只是转换的结果是按溢出处理,和我们希望的结果不一样。
    • 数据的转换必须显式转换,不能自动转换
    • 定义一个int8类型的整数(var num int8 = 0),如果一直自加1,这个变量的值会是(0...127 -128 -127... 0 ...127)循环往复下去,而不会超过类型最大值的范围
    • 其他基本类型转string类型

    在程序开发中,我们经常需要将数值型转成string类型,或者将string类型转成数值型。

    • 方式1:
       func Sprintf(format string, a ...interface{}) string
    

    Sprintf根据format参数生成格式化的字符串并返回该字符串。
    示例

       package main
       import "fmt"
       
       func main() {
           var num1 int = 99;
           var num2 float64 = 23.456
           var isTrue bool = true 
           var char byte = 'A'
       
           var str string
       
           str = fmt.Sprintf("%d", num1)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       
           str = fmt.Sprintf("%f", num2)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       
           str = fmt.Sprintf("%t", isTrue)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       
           str = fmt.Sprintf("%d", char)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       }
    

    输出结果为

     str类型为 string str = "99"
     str类型为 string str = "23.456000"
     str类型为 string str = "true"
     str类型为 string str = "65"
    
    • 方式2:使用strconv包的函数
       package main
       import (
          "fmt"
          "strconv"
       )
       
       func main() {
           var num1 int = 99;
           var num2 float64 = 23.456
           var isTrue bool = true 
           var str string
           str = strconv.FormatInt(int64(num1), 10)
           str = strconv.Itoa(num1)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       
           str = strconv.FormatFloat(num2, 'f', 10, 64)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       
           str = strconv.FormatBool(isTrue)
           fmt.Printf("str类型为 %T str = %q
    ",str, str)
       }
    

    输出结果为

       str类型为 string str = "99"
       str类型为 string str = "23.4560000000"
       str类型为 string str = "23.4560000000"
       str类型为 string str = "true"
    
    • string类型转其他基本类型
    • 方式1:使用strconv包的函数
       package main
       import (
           "fmt"
           "strconv"
       )
       
       func main() {
           var str string = "true"
           var str1 string = "123456"
           var str2 string = "123.456"
       
           var isTrue bool
           var num int64
           var num2 float64
       
           isTrue, _ = strconv.ParseBool(str)
           fmt.Printf("str类型为 %T str = %v
    ",isTrue, isTrue)
       
           num, _ = strconv.ParseInt(str1, 10, 64)
           fmt.Printf("str类型为 %T str = %v
    ",num, num)
       
           num2, _ = strconv.ParseFloat(str2, 64)
           fmt.Printf("str类型为 %T str = %v
    ",num2, num2) 
       }
    

    数据结果为:

       str类型为 bool str = true
       str类型为 int64 str = 123456
       str类型为 float64 str = 123.456
    

    注意:在将string类型转成其它基本数据类型时,要确保string类型能够转成有效的数据。比如,我们可以把”123“转成数字123,但是不能把”hello“转成一个整数,如果这样做,Golang直接将其转成0,其它类型也是一样的道理,float => 0, bool => false

    引用数据类型

    指针

    获取变量的地址,用&,比如var num int,获取num的地址:&num

        var a int = 10
        fmt.Println("a 的地址=" + &a)
    

    指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:var ptr int = &num
    获取指针类型所指向的值,使用:
    ,比如,var ptr int,使用ptr获取ptr指向的值

        var a int = 10                  //变量
        var ptr *int = &a               //指针变量
        fmt.Printf("ptr 的地址=%v 
    ", &ptr)
        fmt.Printf("ptr的值是 a 的地址=%v 
    ", &a)
        fmt.Printf("ptr 所指向地址的值=%v 
    ", *ptr)
    

    值类型,都有对应的指针类型,形式为 *数据类型, 值类型包括:基本数据类型数组结构体struct

        var a int = 10                  //变量
        var ptr *int = &a               //指针变量
        fmt.Printf("ptr 的地址=%v 
    ", &ptr)
    
        var b float64 = .123
        var piont *float64 = &b
        fmt.Printf("piont 的地址=%v 
    ", &piont)
    

    值类型与引用类型

    区分

    • 值类型:基本数据类型(int系列、float系列、bool、string)、数组和结构体
    • 引用类型:指针、slice切片、map、管道chan、interface等都是引用类型

    使用特点

    • 值类型:变量直接存储值,内存通常中分配
    • 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常上分配,当没有任何变量应用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。

    数组

    数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型。

    • 定义与赋值

    var 数组名 [数组大小]数据类型

        var intArr [3]int //int占8个字节
    	//当我们定义完数组后,其实数组的各个元素有默认值 0
    	//赋值
    	intArr[0] = 10
    	intArr[1] = 20
    	intArr[2] = 30
    
        //四种初始化数组的方式
    	var numArr01 [3]int = [3]int{1, 2, 3}
    
    	var numArr02 = [3]int{5, 6, 7}
    
    	var numArr03 = [...]int{8, 9, 10}  //这里的 [...] 是规定的写法,不确定大小
    
    	var numArr04 = [...]int{1: 800, 0: 900, 2:999} //下标赋值
    
        //类型推导
        strArr05 := [...]string{1: "tom", 0: "jack", 2:"mary"}
    
    
    • 数组在内存布局
    1. 数组的地址可以通过数组名来获取 &intArr
    2. 数组的第一个元素的地址,就是数组的首地址
    3. 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4.
    • 数组遍历
    1. 常规 for循环
    2. for-range结构遍历:这是 Go 语言一种独有的结构,可以用来遍历访问数组的元素。
        heroes  := [...]string{"宋江", "吴用", "卢俊义"}
    	for index, value := range heroes {
    		fmt.Printf("index=%v value=%v
    ", index , value)
    		fmt.Printf("heroes[%d]=%v
    ", index, heroes[index])
    	}
    
    	for _, v := range heroes {
    		fmt.Printf("元素的值=%v
    ", v)
    	}
    
          1. 第一个返回值 index是数组的下标
          2. 第二个value是在该下标位置的值
          3. 他们都是仅在 for循环内部可见的局部变量
          4. 遍历数组元素的时 候,如果不想使用下标index,可以直接把下标index标为下划线_
          5. index和value的名称不是固定的,即程序员可以自行指定.一般命名为index和value
    
    
    • 注意事项
    1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化
    2. var arr []int 声明一个数组没有定义长度,arr 就是一个 slice 切片
    3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
    4. 数组创建后,如果没有赋值,有默认值(零值)
        数值类型数组:默认值为 0
        字符串数组:默认值为 ""
        bool 数组: 默认值为 false
    
    1. 使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组
    2. 数组的下标是从 0 开始的
    3. 数组下标必须在指定范围内使用,否则报 panic:数组越界
    4. Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
    5. 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
    6. 长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度

    多维数组

    二维数组

    • 使用方式
    1. 先声明/定义,再赋值
        语法: var 数组名 [大小][大小]类型
        //定义/声明二维数组
    	var arr [2][3]int
    	//赋初值
    	arr[1][2] = 1
    	arr[2][1] = 2
    	arr[2][3] = 3
    
    1. 直接初始化
        var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}
    
        二维数组在声明/定义时也对应有四种写法[和一维数组类似]
        var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}
        var 数组名 [大小][大小]类型 = [...][大小]类型{{初值..},{初值..}}
        var 数组名 = [大小][大小]类型{{初值..},{初值..}}
        var 数组名 = [...][大小]类型{{初值..},{初值..}}
    
    
    1. 二维数组在内存的存在形式
        var arr [2][3]int //以这个为例来分析arr2在内存的布局!!
    	arr[1][1] = 10
    
        fmt.Println(arr) //[[0 0 0] [0 10 0]]
    	fmt.Printf("arr[0]的地址%p
    ", &arr[0]) //arr[0]的地址0xc000018090
    	fmt.Printf("arr[1]的地址%p
    ", &arr[1]) //arr[1]的地址0xc0000180a8
        //0xc000018090和 0xc0000180a8 相差 3x8: 3个int元素 (1个int 8byte)
    
    	fmt.Printf("arr[0][0]的地址%p
    ", &arr[0][0]) //arr[0][0]的地址0xc000018090
        //&arr2[0] == &arr[0][0] 
    
    	fmt.Printf("arr[1][0]的地址%p
    ", &arr[1][0]) //arr[1][0]的地址0xc0000180a8
        &arr[1] == &arr[1][0]
    
        总结
        1. 二维数组内存形式存储的是指针
        2. 二维数组第一组存储的第一组第一个元素的地址,第二组存储的是第二组第一个元素的地址,依次类推
        3. 二维数组两组地址相差的是一组元素所占的字节
    
    1. 二维数组的遍历
    2. 双层 for 循环完成遍历
        var arr  = [2][3]int{{1,2,3}, {4,5,6}}
    
    	for i := 0; i < len(arr); i++ {
    		for j := 0; j < len(arr[i]); j++ {
    			fmt.Printf("%v	", arr[i][j])
    		}
    	}
    
    1. for-range 方式完成遍历
        var arr  = [2][3]int{{1,2,3}, {4,5,6}}
    	for i, v := range arr {
    		for j, v2 := range v {
    			fmt.Printf("arr[%v][%v]=%v 	",i, j, v2)
    		}
    	}
    

    切片

    • 定义
    1. 切片的英文是 slice
    2. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
    3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
    4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
    5. 切片定义的基本语法:
        //var 切片名 []类型
        var a [] int
    
    • 切片的内存形式
    1. slice 的确是一个引用类型
    2. slice 从底层来说,其实就是一个数据结构(struct 结构体)
        type slice struct {
            ptr *[2]int //截取数组开始位置的地址
            len int //截取的长度
            cap  //容量
        }
    
    • 切片的使用
    1. 定义一个切片,然后让切片去引用一个已经创建好的数组
        var slice = arr[startIndex:endIndex]
        说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
    
        var slice = arr[0:end] 可以简写 var slice = arr[:end]
        var slice = arr[start:len(arr)] 可以简写: var slice = arr[start:]
        var slice = arr[0:len(arr)] 可以简写: var slice = arr[:]
    
    1. 通过 make 来创建切片.
        基本语法:var 切片名 []type = make([]type, len, [cap])
        参数说明: type: 就是数据类型 len : 大小 cap :指定切片容量,可选(如果你分配了 cap, 则 cap>=len)
    
        1. 通过 make 方式创建切片可以指定切片的大小和容量
        2. 如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0   string =>""  bool =>false]
        3. 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素.
    
    1. 定义一个切片,直接就指定具体数组,使用原理类似 make 的方式
        var strSlice []string = []string{"tom", "jack", "mary"}
    

    方式 1 和方式 2 的区别

    方式1是直接引用数组,这个数组是事先存在的,程序员是可见的。
    方式2是通过make未创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的。

    • 切片的遍历
    1. for 循环常规方式遍历
        var arr [5]int = [...]int{10, 20, 30, 40, 50}
    	slice := arr[1:4]
    	for i := 0; i < len(slice); i++ {
    		fmt.Printf("slice[%v]=%v ", i, slice[i])
    	}
    
    1. for-range 结构遍历切片
        var arr [5]int = [...]int{10, 20, 30, 40, 50}
    	slice := arr[1:4]
    	for i, v := range slice {
    		fmt.Printf("i=%v v=%v 
    ", i, v)
    	}
    
    • 注意事项
    1. 切片初始化时 var slice = arr[startIndex:endIndex]
      说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
    2. 切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长.
    3. cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
    4. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make一个空间供切片来使用
    5. 切片可以继续切片
    6. 用 append 内置函数,可以对切片进行动态追加
        var slice []int = []int{100, 200, 300}
    	//通过append直接给slice3追加具体的元素
    	slice = append(slice, 400, 500, 600)
    	//通过append将切片slice追加给slice
    	slice = append(slice, slice...) 
    
        切片 append 操作的底层原理分析:
        1. 切片 append 操作的本质就是对数组扩容
        2. go 底层会创建一下新的数组 newArr(安装扩容后大小)
        3. 将 slice 原来包含的元素拷贝到新的数组 newArr
        4. slice 重新引用到 newArr
        5. 注意 newArr 是在底层来维护的,程序员不可见.
    
    
    1. 切片的拷贝:切片使用 copy 内置函数完成拷贝
        var slice1 []int = []int{1, 2, 3, 4, 5}
    	var slice2 = make([]int, 10)
    	copy(slice2, slice1)
    	fmt.Println("slice1=", slice1)// 1, 2, 3, 4, 5
    	fmt.Println("slice2=", slice2) // 1, 2, 3, 4, 5, 0 , 0 ,0,0,0
    
        对上面代码的说明:
        1. copy(para1, para2) 参数的数据类型是切片
        2. 按照上面的代码来看, slice4 和 slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999,slice5[0] 仍然是 1
    
    1. 切片是引用类型,所以在传递时,遵守引用传递机制。
        var arr [5]int = [...]int{10, 20, 30, 40, 50}
    	slice1 := arr[1:4] // 20, 30, 40
        slice2 := slice1[1:2] //[30]
    	slice2[0] = 100  // 因为arr , slice1 和slice2 指向的数据空间是同一个,因此slice2[0]=100,其它的都变化
    
    • string 和 slice
    1. string 底层是一个 byte 数组,因此 string 也可以进行切片处理
    2. string 的内存形式
        type slice struct {
            ptr *[4]byte //截取数组开始位置的地址
            len int //截取的长度
        }
    
    1. string 是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串
    2. 如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string
        str := "hello@world"
        arr1 := []byte(str) 
    	arr1[0] = 'z'
    	str = string(arr1)
    
        //我们转成[]byte后,可以处理英文和数字,但是不能处理中文
    	// 原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
    	// 解决方法是 将  string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字
    
        arr1 := []rune(str) 
    	arr1[0] = '北'
    	str = string(arr1)
    

    map

    map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合

    • 基本语法
        var 变量名 map[keytype]valuetype
        
        keytype:
        golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只包含前面几个类型的 接口, 结构体, 数组 通常 key 为 int 、string
        注意: slice, map, function 不可以,因为这几个没法用 == 来判断
    
        valuetype:
        valuetype 的类型和 key 基本一样
    
    • 声明和使用
    1. 声明:
        var a map[string]string
        var a map[string]int
        var a map[int]string
        var a map[string]map[string]string
        注意:声明是不会分配内存的,初始化需要 make ,分配内存后才能赋值和使用。
    
    1. 使用:
      方式一:
          var a map[string]string
          //在使用map前,需要先make , make的作用就是给map分配数据空间
          a = make(map[string]string, 10)
          a["no1"] = "宋江" 
          a["no2"] = "吴用" 
          a["no1"] = "武松" 
          a["no3"] = "吴用" 
      
      方式二:
          cities := make(map[string]string)
          cities["no1"] = "北京"
          cities["no2"] = "天津"
          cities["no3"] = "上海"
      
      方式三:
          heroes := map[string]string{
              "hero1" : "宋江",
              "hero2" : "卢俊义",
              "hero3" : "吴用",
          }
      
    • map 的增删改查操作
        cities := make(map[string]string)
        //增
    	cities["no1"] = "北京" //如果 key 还没有,就是增加,如果 key 存在就是修改。
    	cities["no2"] = "天津"
    	cities["no3"] = "上海"
    
    	//删
    	delete(cities, "no1")
    	//当delete指定的key不存在时,删除不会操作,也不会报错
    	delete(cities, "no4")
    
        //改
        //因为 no3这个key已经存在,因此下面的这句话就是修改
    	cities["no3"] = "西安" 
    
    	//查
    	val, ok := cities["no2"]
    	if ok {
    		fmt.Printf("有no1 key 值为%v
    ", val)
    	} else {
    		fmt.Printf("没有no1 key
    ")
    	}
    
    	//如果希望一次性删除所有的key
    	//1. 遍历所有的key,逐一删除 [遍历]
    	//2. 直接make一个新的空间
    	cities = make(map[string]string)
    
    • map 遍历
      只能使用for-range遍历
        cities := make(map[string]string)
    	cities["no1"] = "北京"
    	cities["no2"] = "天津"
    	cities["no3"] = "上海"
    	
    	for k, v := range cities {
    		fmt.Printf("k=%v v=%v
    ", k, v)
    	}
    
    • map 切片
        //1. 声明一个map切片
    	var monsters []map[string]string
    	monsters = make([]map[string]string, 2)
    
        //2. 增加第一个妖怪的信息
    	if monsters[0] == nil {
    		monsters[0] = make(map[string]string, 2)
    		monsters[0]["name"] = "牛魔王"
    		monsters[0]["age"] = "500"
    	}
    
        //3. 切片的append函数,可以动态的增加monster
    	newMonster := map[string]string{
    		"name" : "新的妖怪~火云邪神",
    		"age" : "200",
    	}
    	monsters = append(monsters, newMonster)
    
    • map 排序
        map1 := make(map[int]int, 10)
    	map1[10] = 100
    	map1[1] = 13
    	map1[4] = 56
    	map1[8] = 90
    
        //如果按照map的key的顺序进行排序输出
        //1. 先将map的key 放入到 切片中
    	var keys []int
    	for k, _ := range map1 {
    		keys = append(keys, k)
    	}
    
    	//2. 对切片排序 
    	sort.Ints(keys)
    	fmt.Println(keys)
    
        //3. 遍历切片,然后按照key来输出map的值
    	for _, k := range keys{
    		fmt.Printf("map1[%v]=%v 
    ", k, map1[k])
    	}
    
    • 使用细节
    1. map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来
    2. map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长 键值对(key-value)
    3. map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面 value 是一个 map 更好),

    结构体

    • Golang 语言面向对象编程说明
    1. Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
    2. Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
    3. Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等
    4. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。
    5. Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang 中面向接口编程是非常重要的特性。
    • 结构体和结构体变量(实例)的区别和联系
    1. 结构体是自定义的数据类型,代表一类事物.
        type Cat struct {
            Name string 
            Age int 
            Color string 
            Hobby string
            Scores [3]int
        }
    
    1. 结构体变量(实例)是具体的,实际的,代表一个具体变量
        var cat1 Cat
    	
    	cat1.Name = "小白"
    	cat1.Age = 3
    	cat1.Color = "白色"
    	cat1.Hobby = "吃<・)))><<"
    
    • 结构体变量(实例)在内存的布局
        var cat1 Cat  // var a int
    	
    	cat1.Name = "小白"
    	cat1.Age = 3
    	cat1.Color = "白色"
    	cat1.Hobby = "吃<・)))><<"
    	fmt.Printf("cat1的地址=%p
    ", &cat1)
    
    	fmt.Printf("cat1.Name的地址=%p
    ", &cat1.Name)
    	fmt.Printf("cat1.Age的地址=%p
    ", &cat1.Age)
    	fmt.Printf("cat1.Color的地址=%p
    ", &cat1.Color)
    	fmt.Printf("cat1.Hobby的地址=%p
    ", &cat1.Hobby)
    
        结果:
        cat1的地址=0xc00007e000    //824634236928
        cat1.Name的地址=0xc00007e000 //824634236928 //string占位 16byte
        cat1.Age的地址=0xc00007e010 //824634236944 //int占位 8byte
        cat1.Color的地址=0xc00007e018 //824634236952
        cat1.Hobby的地址=0xc00007e028 //824634236968
    
        总结:
        &cat1 == &cat1.Name 
        &cat1.Name + 16 = &cat1.Age
        &cat1.Age + 8 = &cat1.Color
    
    • 声明结构体
        基本语法
        type 结构体名称 struct {
            字段1 type //结构体字段 = 属性 = field 
            字段2 type
        }
        举例:
        type Student struct { //结构体和字段名大写代表public,小写表示private
            Name string
            Age int
            Score float32
        }
    
        字段 :字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。
        字段细节说明
        1) 字段声明语法同变量,示例:字段名 字段类型
        2) 字段的类型可以为:基本类型、数组或引用类型
        3) 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样:
            布尔类型是 false ,数值是 0 ,字符串是 ""。
            数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
            指针,slice,和 map 的零值都是 nil ,即还没有分配空间。
        4) 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个, 结构体是值类型。
    
    • 创建结构体变量和访问结构体字段
    1. 直接声明
        var person Person
    
        var person Person = Person{}
    
        举例:
        p2 := Person{"mary", 20}
    
        var person *Person = new (Person)
        (*p3).Name = "smith"  //(*p3).Name = "smith" 也可以这样写 p3.Name = "smith"
        /*
        * 原因: go的设计者 为了程序员使用方便,底层会对 p3.Name = "smith" 进行处理
    	* 会给 p3 加上 取值运算 (*p3).Name = "smith"
        */
    
        var person *Person = &Person{}
        //var person *Person = &Person{"mary", 60} 
        (*person).Name = "scott"  //person.Name = "scott"
    	(*person).Age = 88  //person.Age = 88
        
    
    • 注意事项
    1. 结构体的所有字段在内存中是连续的
      假如有两个 Point类型,这个两个Point类型的本身地址也是连续的,但是他们指向的地址不一定是连续
    2. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
    3. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
    4. struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
        package main 
        import "fmt"
        import "encoding/json"
    
        type Monster struct{
            Name string `json:"name"` // `json:"name"` 就是 struct tag
            Age int `json:"age"`
            Skill string `json:"skill"`
        }
        func main() {
            //1. 创建一个Monster变量
            monster := Monster{"牛魔王", 500, "芭蕉扇~"}
    
            //2. 将monster变量序列化为 json格式字串
            //   json.Marshal 函数中使用反射,这个讲解反射时,我会详细介绍
            jsonStr, err := json.Marshal(monster)
            if err != nil {
                fmt.Println("json 处理错误 ", err)
            }
            fmt.Println("jsonStr", string(jsonStr))
        }
    

    方法

    Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct。

    • 声明
        func (recevier type) methodName(参数列表) (返回值列表){
            方法体
            return 返回值
        }
    
        说明:
        1. 参数列表:表示数据类型调用传递给方法的参数
        2. recevier type : 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
        3. receiver type : type 可以是结构体,也可以其它的自定义类型
        4. receiver : 就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)
        5. 返回值列表:表示返回的值,可以多个
        6. 方法主体:表示为了实现某一功能代码块
        7. return 语句不是必须的。
    
    
    • 调用
        type A struct {
            Num int
        }
        func (a A) test() {
            fmt.Println(a.Num)
        }
        func main() {
            var a A
            a.test() //调用方法
        }
    
        说明
        1. func (a A) test() {} 表示 A 结构体有一方法,方法名为 test
        2. (a A) 体现 test 方法是和 A 类型绑定的
        3. test 方法只能通过 A 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用
        4.  func (a A) test() {}... a 表示哪个 A 变量调用,这个 a 就是它的副本, 这点和函数传参非常相似。
    
    • 方法的调用和传参机制原理
      方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)。
    • 注意事项
    1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
    2. 如希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
    3. Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct, 比如 int , float32 等都可以有方法
    4. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
    5. 如果一个类型实现了 String()这个方法,那么 fmt.Println 默认会调用这个变量的 String()进行输出

    方法和函数区别

    1. 调用方式不一样
      函数的调用方式: 函数名(实参列表)
      方法的调用方式: 变量.方法名(实参列表)
    2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
    3. 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
        type Person struct {
            Name string
        } 
    
        //函数
        func test01(p Person) {
            fmt.Println(p.Name)
        }
        func test02(p *Person) {
            fmt.Println(p.Name)
        }
    
        //方法
        func (p Person) test03() {
            p.Name = "jack"
            fmt.Println("test03() =", p.Name) // jack
        }
        func (p *Person) test04() {
            p.Name = "mary"
            fmt.Println("test03() =", p.Name) // mary
        }
    
        func main() {
            p := Person{"tom"}
            test01(p)
            test02(&p)
    
            p.test03()
            fmt.Println("main() p.name=", p.Name) // tom
            
            (&p).test03() // 从形式上是传入地址,但是本质仍然是值拷贝
            fmt.Println("main() p.name=", p.Name) // tom
    
            (&p).test04()
            fmt.Println("main() p.name=", p.Name) // mary
            p.test04() // 等价 (&p).test04 , 从形式上是传入值类型,但是本质仍然是地址拷贝
            fmt.Println("main() p.name=", p.Name) // mary
        }
    
        总结:
        1. 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
        2. 如果是和值类型,比如(p Person) , 则是值拷贝; 如果和指针类型,比如是 (p *Person) 则是地址拷贝。
    

    管道

    接口

  • 相关阅读:
    Blender 3DOne
    [翻译]XNA外文博客文章精选之sixteen(中)
    实习技术员的基本功(二)
    [翻译]XNA外文博客文章精选之fifteen
    实习技术员的基本功(三)
    [翻译]XNA外文博客文章精选之sixteen(下)
    实习技术员的基本功(一)
    [翻译]XNA外文博客文章精选之sixteen(上)
    思维导图
    MySQL error 1045(28000): Access denied for user ...
  • 原文地址:https://www.cnblogs.com/KylinBlog/p/13603450.html
Copyright © 2011-2022 走看看