zoukankan      html  css  js  c++  java
  • Go 语言基础(五) 之 进阶

    内存分配
    Go 同样也垃圾收集,也就是说无须担心内存分配和回收。
    Go 有两个内存分配原语,new 和 make。它们应用于不同的类型,做不同的工作, 可能有些迷惑人,但是规则很简单。
    1、用 new 分配内存
    内建函数 new 本质上说跟其他语言中的同名函数功能一样: new(T) 分配了零值填充的 T 类型的内存空间,
    并且返回其地址,一个 *T 类型的值。用 Go 的术语 说,它返回了一个指针,指向新分配的类型 T 的零值。
    有一点非常重要:
    new 返回指针。
    这意味着使用者可以用 new 创建一个数据结构的实例并且可以直接工作。
    type SyncedBuffer struct {
    lock sync.Mutex
    buffer bytes.Buffer
    }
    SyncedBuffer 的值在分配内存或定义之后立刻就可以使用。
    在这个片段中, p 和v 都可以在没有任何更进一步处理的情况下工作。
    p := new(SyncedBuffer) ← Type *SyncedBuffer,已经可以使用
    var v SyncedBuffer ← Type SyncedBuffer,同上
     
     
    2、用 make 分配内存
    内建函数 make(T, args) 与 new(T) 有着不同的功能。它只能创建 slice,map 和 channel,
    并且返回一个有初始值(非零)的 T 类型,而不是 *T。
    本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前 必须被初始化。
     
    3、new 和 make 的区别
    例如,一个 slice,是一个包含指向数据(内部 array)的指针, 长度和容量的三项描述符;
    在这些项目被初始化之前,slice 为 nil。对于 slice, map 和 channel,
    make 初始化了内部的数据结构,填充适当的值。
    make 返回初始化后的(非零)值。
    例如, make([]int, 10, 100) 分配了 100 个整数的数组,然后用长度 10 和容量 100 创建了 slice
    结构指向数组的前 10 个元素。区别是,new([]int) 返回指向新分配的内存的指针,
    而零值填充的 slice 结构是指向 nil 的 slice 值。
     
     
    这个例子展示了 new 和 make 的不同。
    var p *[]int = new([]int) ← 分配 slice 结构内存;*p == nil, 已经可用
    var v []int = make([]int,100) ← v 指向一个新分配的有 100 个整数的数组。
    var p *[]int = new([]int) ← 不必要的复杂例子
    *p = make([]int, 100, 100)
    v := make([]int, 100) ← 更常见
     
    务必记得 make 仅适用于 map,slice 和 channel,并且返回的不是指针。
    应当用 new 获得特定的指针。
    new 分配;make 初始化上面的两段可以简单总结为:
    • new(T) 返回 *T 指向一个零值 T
    • make(T) 返回初始化后的 T
    当然 make 仅适用于 slice,map 和 channel。
     
    4、构造函数与复合声明
    有时零值不能满足需求,必须要有一个用于初始化的构造函数
    例如这个来自 os 包的例子。
    func NewFile(fd int, name string) *File {
    if fd < 0 {
    return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
    }
    有许多冗长的内容。可以使用复合声明使其更加简洁,每次只用一个表达式创建一个新的实例。
    func NewFile(fd int, name string) *File {
    if fd < 0 {
    return nil
    }
    f := File{fd, name, nil, 0} ← Create a new File
    return &f ← Return the address of f
    }
    返回本地变量的地址没有问题;在函数返回后,相关的存储区域仍然存在。
    事实上,从复合声明获取分配的实例的地址更好,
    因此可以最终将两行缩短到一行。
    return &File{fd, name, nil, 0}
     
    在特定的情况下,如果复合声明不包含任何字段,它创建特定类型的零值。
    表达式 new(File) 和 &File{} 是等价的。
    复合声明同样可以用于创建 array,slice 和 map,通过指定适当的索引和 map 键 来标识字段。
     
     
     
    十二、定义自己的类型
    1、Go 允许定义新的类型,通过保留字 type 实现:
    type foo int
    创建了一个新的类型 foo 作用跟 int 一样。创建更加复杂的类型需要用到 struct 保留字。
    这有个在一个数据结构中记录某人的姓名(string)和年龄(int),
    并且使其成为一个新的类型的例子:
    package main
    import "fmt"
    type NameAge struct {
    name string ← 不导出
    age int ← 不导出
    }
    func main() {
    a := new(NameAge)
    a.name = "Pete"
    a.age = 42
    fmt.Printf("%v ", a)
    }
    通常,fmt.Printf("%v ", a) 的输出是 &{Pete 42}
    如果仅想打印某一个,或者某几个结构中的 字段,需要使用 .<field name>。例如,仅仅打印名字:
    fmt.Printf("%s", a.name) ← %s 格式化字符串
     
    2、结构字段
    之前已经提到结构中的项目被称为field。
    没有字段的结构:struct {}
    有四个字段的:
    struct {
    x, y int
    A *[]
    int F func()
    }
    如果省略字段的名字,可以创建匿名字段,例如:
    struct {
    T1 ← 字段名字是 T1
    *T2 ← 字段名字是 T2
    P.T3 ← 字段名字是 T3
    x,y int ←字段名字是x 和y
    }
    注意首字母大写的字段可以被导出,也就是说,在其他包中可以进行读写。
    字段名以小写字母开头是当前包的私有的。包的函数定义是类似的。
     
    3、方法
    可以对新定义的类型创建函数以便操作,可以通过两种途径:
    1. 创建一个函数接受这个类型的参数。
    func doSomething(in1 *NameAge, in2 int) { /* ... */ }
    (你可能已经猜到了)这是 函数调用。
    2. 创建一个工作在这个类型上的函数:
    func (in1 *NameAge) doSomething(in2 int) { /* ... */ }
    这是方法调用,可以类似这样使用:
    var n *NameAge
    n.doSomething(2)
    这里 Go 会查找 NameAge 类型的变量 n 的方法列表,没有找到就会再查找 *NameAge
    类型的方法列表,并且将其转化为 (&n).doSomething(2)。
    使用函数还是方法完全是由程序员说了算,但是若需要满足接口就必须使用方法。
    如果没有这样的需求,那就完全由习惯来决定是使用函 数还是方法了。
    如果 x 可获取地址,并且 &x 的方法中包含了 m, x.m() 是 (&x).m() 更
    短的写法。
     
     
    十四、转换
    1、有时需要将一个类型转换为另一个类型。在 Go 中可以做到,不过有一些规则。
    首先,将一个值转换为另一个是由操作符(看起来像函数:byte())完成的,
    并且不是所有的转换都是允许的。
     
    从string到字节或者ruin的slice。
    mystring := "hello this is string"
    byteslice := []byte(mystring)
    转换到 byte slice,每个 byte 保存字符串对应字节的整数值。
    注意 Go 的字符串是 UTF-8 编码的,一些字符可能是 1、2、3 或者 4 个字节结尾。
    runeslice := []rune(mystring)
    转换到 rune slice,每个 rune 保存 Unicode 编码的指针。
    字符串中的每个字符对应一个整数。
    从字节或者整形的slice到string。
    b := []byte{'h','e','l','l','o'} ← 复合声明
    s := string(b)
    i := []rune{257,1024,65}
    r := string(i)
    对于数值,定义了下面的转换:
    • 将整数转换到指定的(bit)长度:uint8(int);
    • 从浮点数到整数:int(float32)。这会截断浮点数的小数部分;
    • 其他的类似:float32(int)。
     
     
    2、用户定义类型的转换
    如何在自定义类型之间进行转换?这里创建了两个类型 Foo 和 Bar,
    而 Bar 是 Foo 的一个别名:
    type foo struct { int } ← 匿名字段
    type bar foo ← bar 是 foo 的别名
    然后:
    var b bar=bar{1} ←声明b 为bar 类型
    var f foo=b ←赋值b 到f
     
    最后一行会引起错误:
    cannot use b (type bar) as type foo in assignment(不能使用 b(类型 bar)作为
    类型 foo 赋值)
    这可以通过转换来修复:
    var f foo = foo(b)
    注意转换那些字段不一致的结构是相当困难的。
    同时注意,转换 b 到 int 同样 会出错;
    整数与有整数字段的结构并不一样。
  • 相关阅读:
    【JAVA笔记——术】java枚举类使用
    【JAVA笔记——术】java枚举类使用
    【JAVA笔记——道】JAVA 基本类型内存探究
    【JAVA笔记——道】JAVA 基本类型内存探究
    【JAVA笔记——道】Hibernate 线程本地化基础篇
    Java实现HTML转PDF的总结
    JqGrid使用经验
    C# 保留小数点后两位(方法总结)
    SQL SERVER表不能修改表结构的处理方法
    C# DLL文件注册问题(涉及AxInterop.WMPLib.dll等)
  • 原文地址:https://www.cnblogs.com/wjq310/p/6545503.html
Copyright © 2011-2022 走看看