对象

对象构建

new(T)会为类型为 T 分配已置零的内存空间,并返回它的地址,也就是一个类型为 *T 的值。用 Go 的术语来说,它返回一个指针,该指针指向新分配的,类型为 T 的零值。不像其他语言的关键字 new,除了赋零值外,Go 不会对其做任何初始化工作。

初始化方式

通过 var 声明结构体

在 Go 语言中当一个变量被声明的时候,系统会自动初始化它的默认值,比如 int 被初始化为 0,指针为 nil。var 声明同样也会为结构体类型的数据分配内存,所以我们才能像上一段代码中那样,在声明了 var s T 之后就能直接给他的字段进行赋值。

使用 new

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)

type struct1 struct {
    i1 int
    f1 float32
    str string
}

func main() {
    ms := new(struct1)
    ms.i1 = 10
    ms.f1 = 15.5
    ms.str= "Chris"

    fmt.Printf("The int is: %d\n", ms.i1)
    fmt.Printf("The float is: %f\n", ms.f1)
    fmt.Printf("The string is: %s\n", ms.str)
    fmt.Println(ms)
}

与面向对象语言相同,使用点操作符可以给字段赋值:structname.fieldname = value。同样的,使用点操作符可以获取结构体字段的值:structname.fieldname

使用字面量

type Person struct {
    name string
    age int
    address string
}

func main() {
    var p1 Person
    p1 = Person{"lisi", 30, "shanghai"}   //方式A
    p2 := Person{address:"beijing", age:25, name:"wangwu"} //方式B
    p3 := Person{address:"NewYork"} //方式C
}

在(方式 A)中,值必须以字段在结构体定义时的顺序给出。(方式 B)是在值前面加上了字段名和冒号,这种方式下值的顺序不必一致,并且某些字段还可以被忽略掉,就想(方式 C)那样。new 关键字也支持一种初始化结构体实体更简短和常用的方式,如下:

ms := &Person{"name", 20, "bj"}
ms2 := &Person{name:"zhangsan"}

&Person{a, b, c} 是一种简写,底层仍会调用 new(),这里值的顺序必须按照字段顺序来写,同样它也可以使用在值前面加上字段名和冒号的写法(见上文的方式 B,C)。表达式 new(Type)&Type{} 是等价的。

初始化方式差异

到目前为止,我们已经了解了三种初始化结构体的方式:

// 第一种,在Go语言中,可以直接以 var 的方式声明结构体即可完成实例化
var t T
t.a = 1
t.b = 2

// 第二种,使用 new() 实例化
t := new(T)

// 第三种,使用字面量初始化
t := T{a, b}
t := &T{} //等效于 new(T)

使用 var t T 会给 t 分配内存,并零值化内存,但是这个时候的 t 的类型是 T。使用 new 关键字时 t := new(T),变量 t 则是一个指向 T 的指针。从内存布局上来看,我们就能看出这三种初始化方式的区别:

三种初始化方式区别

package main
import "fmt"

type Person struct {
 name string
 age int
}

func main() {
 var p1 Person
 p1.name = "zhangsan"
 p1.age = 18
 fmt.Printf("This is %s, %d years old\n", p1.name, p1.age)

 p2 := new(Person)
 p2.name = "lisi"
 p2.age = 20
 (*p2).age = 23 //这种写法也是合法的
 fmt.Printf("This is %s, %d years old\n", p2.name, p2.age)

 p3 := Person{"wangwu", 25}
 fmt.Printf("This is %s, %d years old\n", p3.name, p3.age)
}

/*
	This is zhangsan, 18 years old
	This is lisi, 23 years old
	This is wangwu, 25 years old
*/

虽然 p2 是指针类型,但我们仍然可以像 p2.age = 23 这样赋值,不需要像 C++ 中那样使用 -> 操作符,Go 会自动进行转换。注意也可以先通过 * 操作符来获取指针所指向的内容,再进行赋值:(*p2).age = 23

构造函数与复合字面(composite literal)

Go 其实不存在构造函数这种说法。有时(通常)零值还不够好,这时就需要一个初始化构造函数,他们通常是以 New 开头的导出方法,例如来自 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}
    return &f
}

最后两行可以简写成:

return &File{fd: fd, name: name}

make 分配

make(T, args...) 仅用于创建 slice,map 和 channel, 并且返回类型为 T(不是 *T)。

make([]int, 10, 100) // 创建一个长度为 10,容量为100的slice
make([]int, 10) // 创建一个长度为 10,容量为 10 的slice

make(map[string]string) // 创建一个 map

make(chan int) // 创建一个 chan
make(chan int, 10) // 创建一个 buffer size 为 10 的chan

单例模式

var (
	instance *Singleton
	once     sync.Once
)

func Instance() *Singleton {
	once.Do(func() {
		instance = &Singleton{}
	})
	return instance
}
上一页
下一页