蹒跚学Go第三天-复合类型

工作目录

GoPATH有何意义?

你可以把GOPATH简单理解成Go语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个目录都代表Go语言的一个工作区(workspace),这些工作区,去放置Go 语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file),由于Go语言项目在其生命周期内的所有操作(编码、依赖管理、构建、测试、安装等)基本上都是围绕着GOPATH和工作区进行的.

  • (1)src目录:用于以代码包的形式组织并保存Go源码文件。(比如:.go .c .h .s等)

  • (2)pkg目录:用于存放经由go install命令构建安装后的代码包(包含Go库源码文件)的“.a”归档文件

  • (3)bin目录:与pkg目录类似,在通过go install命令完成安装后,保存由Go命令源码文件生成的可执行文件

在Linux的环境中:

  • $GOROOT

SGOROOT是Go的安装根目录。在Windows下安装包会自动设置,默认是C:\Go\,Linux下的环境默认是/usr/local/go。如果SGOROOT位于上述位置,则不需要显式地设置SGOROOT环境变量;如果不是默认安装目录,则需要显式地设置SGOROOT环境变量。

  • $GOPATH

SGOPATH是Go语言编程的工作目录(workspace)。如果没有设置GOPATH环境变量,则Linux 下系统默认是SHOME/go,Windows 下默认是%USERPROFILE%1g。

  • $GOBIN

SGOBIN是带有main函数的源程序执行goinstall时生成的可执行程序安装目录,默认是SGOPATH/bin。如果想在任何路径执行安装的程序,则可以将SGOBIN添加到SPATH中。

  • GOOS和$GOARCH

SGOOS用来设置目标操作系统,SGOARCH用来设置目标平台的CPU体系结构。这两个参数主要用在交叉编译中,交叉编译在8.1.3节讨论。Go 语言支持的OS和ARCH

复合类型

数组

数组的类型名是【n】elemetType,其中n是数组长度,elementType是数组元素类型。比如一个包含2个int类型元素的数组类型可表示为【2】int。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。

  • 所谓的数组:是指一系列同一类型数据的集合

  • 数组中的元素类型是整型,定义完成后,直接输出,结果全部是0.

  • var a [10]float64 //如果不赋值,直接输出,结果默认全部是0

  • var a[10]string//如果不赋值,直接输出,结果默认全部是空字符

  • var a [10]bool//如果不赋值,直接输出,结果默认全部是false

func main() {
    //数组在声明后元素的个数就已经固定无法再次添加了,数组是一个常量不允许赋值操作
    //定义数组:var 数组名 [个数]数据类型
    var a [10]int
    //使用自动类型推倒,可以根据元素的个数创建数组
    c :=[...]int{1,2,3}
    //数组在定义时候,可以部分初始化元素的值
    var b [10]int=[10]int{1,2,3,4,5,56}
    a[0]=1
    a[2]=2
    //计算数组的集合数
    fmt.Println(len(b))
    //遍历数组元素信息
    for _,v:=range b{
        fmt.Println(v)
        //打印数组的地址->这个地址会变换的,申请的栈区不同
        fmt.Printf("%p",&v)
    }
}

数组的初始化

数组求值

  • 从一个整数数组中取出最大的整数,最小整数,总和,平均值
func main() {
    a := [6]int{3,5,56,12,4,1}
    var min,max,sum int
    xx :=1
    for i := 0; i < len(a); i++ {
        if max < a[i]{
            max = a [i]
        }else {
            min = a[i]
        }
        sum += a[i]
        xx *= a[i]
    }
    fmt.Println("最大值:",max)
    fmt.Println("最小值:",min)
    fmt.Println("合计:",sum)
    fmt.Println("总乘积:",xx)
}

输出值:
-> 最大值: 56
-> 最小值: 1
-> 合计: 81
-> 总乘积: 40320
  • 函数的数组颠倒
func main() {
    //定义一个奇数的数组
    var a [7]int = [7]int{1, 2, 3, 4, 5, 6, 7}
    //定义一个偶数的数组
    b := [6]int{1, 2, 3, 4, 5, 6}
    //定义两个控制下角标stat/end
    stat := 0
    end := len(a) - 1
    for {
        if stat > end {
            break
        }
        a[stat], a[end] = a[end], a[stat]
        stat ++
        end --
    }
    fmt.Println("颠倒顺序:", a)
}
  • 数组的冒泡排序实现

func main() {
    a := [10]int{9,1,5,6,7,3,10,2,4,8}
    for i:=0;i<len(a)-1;i++{
        for j:=0;j<len(a)-1-i;j++ {
            //比较两个相邻数据大小,满足的交换数据
            if a[j]<a[j+1] {
                a[j],a[j+1] = a[j+1],a[j]
            }
        }
    }
    fmt.Println(a)
}
  • 计算出一个整型数组的平均值。保留两位小数
//定义函数处理累加并返回值
func add(num []int) float64{
    var sum int
    for i:=0;i<len(num);i++{
        sum += i
    }
    return float64(sum)/float64(len(num))
}

func main() {
    var a float64
    b:=[]int{3,44,2,5,7,1,100}
    a = add(b)
    //定义输出位数 -> %.2f
    fmt.Printf("平均值:%.2f",a)
}
  • 练习:12个猪称重
func main() {
    arr := [12]int{6,5,3,7,8,9,2,5,4,10,0,1}
    max,min:=arr[0],arr[0]
    for i:=0;i<len(arr);i++{
        if max<arr[i] {
            max = arr[i]
        }
        if min>arr[i] {
            min = arr[i]
        }
    }
    fmt.Println("最大的猪重:",max)
    fmt.Println("最小的猪",min)
}
  • 求出该数组中第二大的数{5,100,32,45,21,67,32,68,41,99,13,71}
func main(){
    var a []int = []int {5,100,32,45,21,67,32,68,41,99,13,71}
    for i:=0;i<len(a)-1;i++{
        for j:=0;j<len(a)-1-i;j++ {
            //比较两个相邻数据大小,满足的交换数据
            if a[j]<a[j+1] {
                a[j],a[j+1] = a[j+1],a[j]
            }
        }
    }
    fmt.Println(a,"第二大的值为:",a[1])
}
  • 练习:随机数生产
//产生很大的随机数
func Randfunc() {
    //设置种子
    rand.Seed(123)
    for i := 0; i<5;i++{
        fmt.Println("rand=",rand.Int())
    }
}

//指定生产的随机数的范围,基于系统的时间
func Randbe() {
    //使用当前的系统时间作为种子
    rand.Seed(time.Now().UnixNano())
    //产生随机数
    for i:=0;i<5;i++{
        fmt.Println("rand=",rand.Intn(100))
    }
}
  • 练习:随机生产3个随机数并计算他们的平均值
func add(num [3]int) float64{
    var sum int
    for i:=0;i<len(num);i++{
        sum += num[i]
    }
    return float64(sum)/float64(len(num))
}

func Randbe() [3]int {
    var data [3]int
    //使用当前的系统时间作为种子
    rand.Seed(time.Now().UnixNano())
    //产生随机数
    for i:=0;i<3;i++{
        data[i]= rand.Intn(100)
    }
    return data
}

func main() {
    var a float64
    c := Randbe()
    a = add(c)
    //定义输出位数 -> %.2f
    fmt.Printf("平均值:%.2f",a)
}

切片

Go语言的数组的定长性和值拷贝限制了其使用场景,Go 提供了另一种数据类型slice(中文为切片),这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型。切片与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大,所以可以将切片理解成“动态数组”,但是,它不是数组。

//简单的切片追加
func main() {
    //初始化切片
    s := []int{1,2,3}
    //通过append函数向切片中追加数据
    s = append(s,5,6,7)
    fmt.Println(s)
}

切片与数组区别

  • a:=[5]int{ } -> 定义初始化 -> 数组中[ ]是一个固定的数字,表示长度。定义完后,长度是固定,最多存储5个数字。

  • s:=[ ]int{ }//定义空切片 -> 看定义的方式发现与数组很相似 -> 但是注意:切片中的[ ]是空的,或者是“…”. 切片的长度和容量可以不固定。

切片定义方式

  • var s1 [ ]int //声明切片和声明数组一样,只是少了长度,此为空(nil)切片

  • make( )函数实现 -> s := make([]int, 5, 10) //借助make函数, 格式 make(切片类型, 长度, 容量)

什么是切片的长度与容量?

长度是已经初始化的空间(以上切片s初始空间默认值都是0)。容量是已经开辟的空间,包括已经初始化的空间和空闲的空间。

func main() {
    //切片的定义
    var arr = [...]int{1,2,3,4,5,6}
    s1 := arr[1:3]
    s2 := arr[1:]
    s3 := arr[:3]
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
    //通过make创建切片在
    //使用make( )函数定义切片时,一定要注意,切片长度要小于容量
    a := make([]int,8)
    b := make([]int,10,15)
    fmt.Println(a)
    fmt.Println(b)
}
输出值:
[2 3]
[2 3 4 5 6]
[1 2 3]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]

切片支持的操作

  • 内置函数len0返回切片长度

  • 内置函数cap0返回切片底层数组容量

  • 内置函数append0对切片追加元素

  • 内置函数copy0用于复制一个切片

切片截取

  • s[low : high : max]
func main() {
    //定义切片完成初始化
    a := []int{1,2,3,4,0,0,0}
    //从切片a中截取数据
    b := a[0:3:5]
    fmt.Println(b)
}
  • 第一个数(low)表示下标的起点(从该位置开始截取),如果low取值为0表示从第一个元素开始截取,也就是对应的切片s中的10

  • 第二个数(high)表示取到哪结束,也就是下标的终点(不包含该位置),3表示取出下标是0,1,2的数据(1,2,3),不包括下标为3的数据,那么也就是说取出的数据长度是3. 可以根据公式:3-0 计算(len=high-low),也就是第二个数减去第一个数,差就是数据长度。在这里可以将长度理解成取出的数据的个数。

  • 第三个数用来计算容量,所谓容量:是指切片目前可容纳的最多元素个数。通过公式5-0计算(cap=max-low),也就是第三个数据减去第一个数。

操作 含义
s[ n ] 切片s中索引位置为n的项
s[ : ] 从切片s的索引位置0到len(s)-1处所获得的切片
s[ low : ] 从切片s的索引位置low到len(s)-1处所获得的切片
s[ : high ] 从切片s的索引位置0到high处所获得的切片,len=high
s[low:high] 从切片s的索引位置low到high处所获得的切片,len=high-low
s[low: high :max] 从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low
len(s) 切片s的长度,总是<=cap(s)
cap(s) 切片s的容量,总是>=len(s)

切片作为函数参数

  • Init函数里面的赋值影响到了main中的S,发现以数组的形式作为参数,并不能完成我们的要求,所以切片作为函数实参与数组作为函数实参,进行传递时,传递的方式是不一样的

  • 数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。

什么是值传递?什么是引用传递?

值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值
引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

  • 例子:计算出一组整型数据之和
func Init(num []int) {
    for i := 0;i<len(num);i++{
        //循环输入用切片接受参数
        fmt.Printf("请输入第%d参数的值:\n",i+1)
        fmt.Scanf("%d",&num[i])
    }
}

func sumadd(num []int) int{
    var sum int
    for i :=0;i<len(num);i++{
        sum+= num[i]
    }
    return sum
}

func main()  {
    var count int
    var sum int
    fmt.Println("请输入要计算的整形数据的个数:")
    fmt.Scanf("%d",&count)
    s := make([]int,count)
    Init(s) //开始切片的初始化
    sum = sumadd(s) //计算数据返回想要的结果
    fmt.Println("sum值",sum)
}
  • 简单的初始化
func Init(num []int){
    for i :=0;i<len(num);i++{
        num[i] = i
    }
}

func main() {
    //创建一个切片
    s := make([]int,10)
    //初始化切片
    Init(s)
    //打印切片
    for _,v:= range s{
        fmt.Println(v)
    }
}
  • 练习:实现冒泡排序
import (
    "fmt"
    "math/rand"
    "time"
)

func sort(s []int){
    n:= len(s)
    for i := 0; i < n-1; i++ {
        for j := 0; j < n-1-i; j++ {
            if s[j]>s[j+1]{
                s[j],s[j+1] = s[j+1],s[j]
            }
        }
    }
}

func Initdata(s []int) {
    //设置种子
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < len(s); i++ {
        s[i]=rand.Intn(100)
        //100以内的随机值
    }
}

func main(){
    n:=10   //定义切片的个数
    //创建一个切片
    s:=make([]int,n)
    //初始化数组
    Initdata(s)
    //冒泡排序开始
    fmt.Println("初始化后的数组:",s)
    sort(s) //开始冒泡排序
    fmt.Println("排序后",s)
}

Map

Go语言内置的字典类型叫map。map的类型格式是:map【K】T,其中K可以是任意可以进行比较的类型,T是值类型。map也是一种引用类型。

  • 某个键(key)都对应的一个值(value),如果现在要查询某个值,直接根据键就可以查询出某个值。在这里需要注意的就是 字典中的键是不允许重复的,就像身份证号一样。

字典结构定义

  • map[keyType]valueType

  • 注意:字典中不能使用cap函数,只能使用len( )函数。len( )函数返回map拥有的键值对的数量

  • 可以直接使用键完成赋值,再次强调键是唯一的,同时发现字典m2的输出结果,不一定是按照赋值的顺序输出的,每次运行输出的顺序可能都不一样,所以这里一定要注意:map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺利有可能不同。

定义字典结构使用map关键字,[ ]中指定的是键(key)的类型, 后面紧跟着的是值的类型。
键的类型,必须是支持==和!=操作符的类型,切片、函数以及包含切片的结构类型不能作为字典的键,使用这些类型会造成编译错误:

func main() {
    var dir map[string]int
    //make定义字典
    dir2 := make(map[int]string)
    //初始化字典
    dir3 := map[string]int{"azs":1,"a":2}
    //make创建字典也可以指定容量,不够自动扩容
    fmt.Println(dir,dir2,dir3)
    fmt.Println(len(dir),len(dir2),len(dir3))
}

map支持的操作

  • map的单个键值访问格式为mapName【key】,更新某个key的值时mapName【key】放到等号左边,访问某个key的值时mapName【key】放在等号的右边。

  • 可以使用range遍历一个map类型变量,但是不保证每次迭代元素的顺序。

  • 删除map中的某个键值,使用如下语法:delete(mapName,key)。delete是内置函数,用来删除map中的某个键值对。

  • 可以使用内置的len0函数返回map中的键值对数量

输出字典的值

func test(m map[int]string){
    delete(m,2)
}

func main() {
    m := map[int]string{1:"true",2:"q",3:""}
    //通过key值的方式直接的输出
    fmt.Println(m[1])
    //通过循环遍历的方式输出
    for key,value:= range m{
        fmt.Println("key=",key,"value=",value)
    }
    //循环输出时候判断,输出的时候是无序的
    for key1,value1:=range m{
        if value1 != ""{
            fmt.Println("key=",key1,"value=",value1)
        }
    }
    //删除map中的某个元素
    delete(m,1)
    //map作为函数参数是引用传递
    test(m)
}

结构体

structGo中的struct类型和C类似,中文翻译为结构,由多个不同类型元素组合而成。这里面有两层含义:第一,struct结构中的类型可以是任意类型;第二,struct的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求)。

  • struct有两种形式:一种是struct 类型字面量,另一种是使用type声明的自定义struct类型。

为什么需要有结构体呢?

  • 把结构体类型中的一个字段看作是它的一个属性或者一项数据,再把隶属于它的一个方法看作是附加在其中数据之上的一个能力或者一项操作。将属性及其能力(或者说数据及其操作)封装在一起,是面向对象编程(object-oriented programming)的一个主要原则。

结构体声明格式

  • 实际使用struct字面量的场景不多,更多的时候是通过type自定义一个新的类型来实现的。

  • type是自定义类型的关键字,不但支持struct类型的创建,还支持任意其他子定义类型的创建。

//struct类型字面量的声明格式
struct{
    FeildName FeildType
    FeildName FeildType
    FeildName FeildType
}
//自定义struct类型声明格式
type TypeName struct{
    FeildName FeildType
    FeildName FeildType
    FeildName FeildType
}

结构体初始

  • 注意:顺序初始化,每个成员必须初始化,在初始化时,值的顺序与结构体成员的顺序保持一致
type Student struct {
    id int
    name string
    sex string
    age int
    addr string
}
func main(){
    //结构体初始化每个成员必须初始化,没有被赋值的自动为0
    xm := Student{id:1,name:"ccy",sex:"boy",age:5}
    fmt.Println(xm)
    //第二种定义方式
    var s Student//定义结构体变量
    s.id=1001
    s.name="zhangsan"
    s.age=18
    s.addr="北京"
    s.sex="boy"
    fmt.Println(s)
}

指针

从传统意义上说,指针是一个指向某个确切的内存地址的值。这个内存地址可以是任何数据或代码的起始地址,比如,某个变量、某个字段或某个函数。每个内存位置都有其定义的地址,可以使用&运算符来访问它,这个运算符表示内存中的地址,指针的声明类型为*T,Go同样支持多级指针**T,通过在变量名前加&来获取变量的地址。

指针的特点

  • (1)在赋值语句中,T出现在“=”左边表示指针声明,T出现在“=”右边表示取指针指向的值(varName为变量名)

  • (2)结构体指针访问结构体字段仍然使用“.”点操作符,Go语言没有“>”操作符。

  • (3)Go不支持指针的运算 -> Go由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在C和C++里面指针运算很容易出现问题,因此Go直接在语言层面禁止指针运算。

  • (4)函数中允许返回局部变量的地址 -> Go编译器使用“栈逃逸”机制将这种局部变量的空间分配在堆上。

指针变量

  • 通过指针变量来存储 -> 所谓的指针变量:就是用来存储任何一个值的内存地址

func main(){
    var i int = 100  //在内存中开辟一个新的空间存储100值
    var p *int
    p = &i  //该行代码的意思是,将变量i的地址取出来,并且赋值给指针变量p,也就是指针变量p指向了变量i的存储单元
    *p=80   //*p的作用就是根据存储的变量的地址,来操作变量的存储单元(包括输出变量存储单元中的值,和对值进行修改)
    fmt.Printf("i=%d -> p=%v",i,p)
}

注意事项

  • 默认值为nil

  • 不要操作没有合法指向的内存

new( )函数

  • 指针变量,除了以上介绍的指向以外(p=&a),还可以通过new( )函数来指向

  • new(int)作用就是创建一个整型大小(4字节)的空间然后让指针变量p指向了该空间

  • new( )函数的作用就是C语言中的动态分配空间。但是在这里与C语言不同的地方,就是最后不需要关系该空间的释放。GO语言会自动释放

func main(){
    var p *int
    p = new(int)
    *p=55
    fmt.Println(*p)
}

指针做函数参数

  • 指针也可以作为函数参数,那么指针作为函数参数在进行传递的时候是值传递
func swap(num1 int,num2 int){
    num1,num2 = num2,num1
    fmt.Println(num1,num2)
}

func main(){
    var a int = 10
    var b int = 20
    swap(a,b)
    fmt.Printf("a=%d,b=%d",a,b)
}
  • 指针作为参数进行传递时,为引用传递,也就是传递的地址。在调用Swap( )函数时,将变量a与变量b的地址传分别传递给指针变量num1,num2,这时num1和num2,分别指向了变量a,与变量b的内存存储单元,那么操作num1,num2实际上操作的就是变量a与变量b,所以变量a与变量b的值被交换。
func swap(num1 ,num2 *int){
    *num1,*num2 = *num2,*num1
    fmt.Println(*num1,*num2)
}
func main(){
    var a int = 10
    var b int = 20
    swap(&a,&b)
    fmt.Printf("a=%d,b=%d",a,b)
}

数组指针

  • 指针数组指的是一个数组中存储的都是指针(也就是地址)。也就是一个存储了地址的数组
func main(){
    var p [2]*int
    var i int =1
    var n int =2
    p[0] = &i
    p[1] = &n
    fmt.Println("p[0]->",p[0],"p[1]->",p[1])
    for index,value:= range p {
        fmt.Printf("index=%d,value=%d",index,value)
    }
}
输出的值:
p[0]-> 0xc04200a0a8 p[1]-> 0xc04200a0c0
index=0,value=825741058216index=1,value=825741058240

结构体指针变量

type Student struct {
    id int
    name string
    score float64
}

func test(p *Student){
    p.id = 10
}
func main(){
    var p *Student=&Student{1,"ccy",12.1}
    test(p)
    fmt.Println(*p)
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注