Go语言中能够基于一些基本的数据类型,使用type
关键字定义自定义类型的数据 。html
自定义类型定义了一个全新的类型,该新类型具备基本数据类型的特性。自定义类型定义的方法以下:程序员
type TypeName Type //将 NewType定义为int 类型 type NewType int
NewType
是一个新的类型,其具备int
的特性。面试
类型别名是Go1.9
版本添加的新功能。利用类型别名的功能,能够给一些基本的数据类型定义一些让读者见名知意的名字,从而提升代码的可读性。类型别名定义的方法以下:编程
type TypeAlias = Type
Go语言中的rune
和byte
就是类型别名,它们的定义以下:json
type byte = uint8 type rune = int32
自定义类型:自定义类型定义了一个全新的类型,其继承了基本类型的全部特性,而且能够实现新类型的特有的一些方法。数组
类型别名:只存在代码编写的过程,代码编译之后根本不存在这个类型别名。其做用用来提升代码的可读性。编程语言
以下代码,体现了两者的区别:函数
//自定义类型 type NewInt int //类型别名 type MyInt = int func main(){ var a NewInt var b MyInt var c int // c = a //? 可使用强制类型转换c = int(a) c = b //c和b是同一类型 fmt.Println("type of a:%T\n", a) //type of a:main.NewInt fmt.Println("type of b:%T\n", b) //type of b:int }
在Go语言中可使用基本数据类型表示一些事物的属性,可是若是想表达一个事物的所有或部分属性,好比说一个学生(学号、姓名、年龄、班级等),这时单一的基本数据类型就不可以知足需求。ui
Go语言中提供了一种自定义数据类型,能够将多个基本数据类型或引用类型封装在一块儿,这种数据类型叫struct
结构体。Go语言也是经过struct
来实现面向对象的。this
struct
来实现OOP特性的;this
指针等;// 建立一个结构体类型的student type student struct { name string age int gender string hobby []string } func main() { // 建立一个student实例 var viktor = student{ name: "viktor", age: 24, gender: "男", hobby: []string{"乒乓球", "羽毛球"}, } fmt.Println(viktor) // {viktor 24 男 [乒乓球 羽毛球]} // 分别取出viktor实例中的每一个字段 fmt.Println(viktor.name) // viktor fmt.Println(viktor.age) // 24 fmt.Println(viktor.gender) // 男 fmt.Println(viktor.hobby) // [乒乓球 羽毛球] }
基本语法:
type StructName struct { field1 type field2 type }
示例,声明一个学生的结构体Student
:
type Student struct { Name string Age int Score float32 }
struct
中封装了一些基本数据类型的变量,咱们称之为结构体字段或者是该结构体的属性。
字段是结构体的一个组成部分,通常是基本数据类型、数组,也能够是引用类型,甚至是struct
(嵌套结构体)等。
注意事项和细节说明:
字段声明语法同变量;
在建立一个结构体变量后,若是没有给字段赋值,都对应一个零值(默认值):布尔类型是false,数值是0,字符串是"",数组的默认值和它的元素类型相关,好比 score [3]int
则为[0, 0, 0]
,指针、slice和map的零值都是nil
,即尚未分配空间。
type Person struct { Name string Age int Scores [5]float64 ptr *int slice []int map1 map[string]string } func main() { //定义结构体变量 var p1 Person fmt.Println(p1) if p1.ptr == nil { fmt.Println("ok1") } if p1.slice == nil { fmt.Println("ok2") } if p1.map1 == nil { fmt.Println("ok3") } p1.slice = make([]int, 10) p1.slice[0] = 100 p1.map1 = make(map[string]string) p1.map1["key1"] = "tom" fmt.Println(p1) }
能够这样理解,声明一个结构体相似创造一个模具,若是想要真正的描述一个事物,那么就得使用这个模具来制造一个事物,这个制造事物的过程称为 建立结构体变量或者结构体实例化。结构体的实例化有四种方式:
type Person struct { Name string Age int } func main() { //定义结构体变量 var p1 Person fmt.Println(p1) }
//使用值列表初始化 p2 := Person{"tom", 20} fmt.Println(p2)
struct
是值类型,那么就可使用new
关键字定义一个结构体指针:
var p3 *Person = new(Person) (*p3).Name = "smith" p3.Name = "john" (*p3).Age = 20 p3.Age = 30 fmt.Println(p3)
//使用键值对初始化 var person *Person = &Person{ Name : "tom", Age : 19, } //也能够经过字段访问的形式进行赋值 (*p3).Name = "scott" p3.Name = "scott~" (*p3).Age = 20 p3.Age = 30 fmt.Println(p3)
说明:
(*结构体指针).字段名
;person.Name
作了转化(*person).Name
。type Point struct { x, y int } type Rect struct { leftUp, rightDown Point } type Rect2 struct { leftUp, rightDown *Point } func main() { r1 := Rect(Point{1,2}, Point{3,4}) //r1有四个int,在内存中是连续分布 fmt.Printf("r1.leftUp.x 地址=%p r1.leftUp.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y) //r2有两个*Point类型,这两个*Point类型的自己地址也是连续的,可是其指向的地址不必定是连续的 r2 := Rect(&Point{10,20}, Point{30,40}) fmt.Printf("r2.leftUp 自己地址=%p r2.rightDown 自己地址=%p", &r2.leftUp, &r2.rightDown) fmt.Printf("r2.leftUp 指向地址=%p r2.rightDown 指向地址=%p", r2.leftUp, r2.rightDown) }
struct
的每一个字段上,能够写上一个tag
,该tag
能够经过反射机制获取,常见的使用场景:序列化和反序列化。type Monster struct { Name string `json:"name"` Age int `json:"age"` Skill string `json:"skill"` } func main() { //建立一个Monster实例 monster := Monster{"蜘蛛精", 200, "吐丝"} //将monster序列化 jsonStr, err := json.Marshal(monster) if err != nil { fmt.Println("json 处理错误", err) } fmt.Println("jsonStr", string(jsonStr)) }
下面代码的执行结果?
type student struct { name string age int } func main() { m := make(map[string]*student) stus := []student{ {name: "李四", age: 18}, {name: "张三", age: 23}, {name: "李明", age: 9000}, } for _, stu := range stus { m[stu.name] = &stu } for k, v := range m { fmt.Println(k, "=>", v.name) } }
方法是什么?在声明了一个结构体后,好比说Person
结构体,那么这我的都有哪些功能,或者说都有什么能力,这些功能或者能力就是一个结构体的方法。
Golang中的方法是做用在指定的数据类型上(即,和指定的数据类型绑定),所以自定义类型,均可以有方法,而不只仅是struct
。以下示例:
//MyInt 将int定义为自定义MyInt类型 type MyInt int //SayHello 为MyInt添加一个SayHello的方法 func (m MyInt) SayHello() { fmt.Println("Hello, 我是一个int。") } func main() { var m1 MyInt m1.SayHello() //Hello, 我是一个int。 m1 = 100 fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt }
方法的声明语法:
//声明一个自定义类型struct type A struct { Num int } //声明A类型的方法 func (a A) test() { //其中的(a A)表示test方法和A类型绑定 fmt.Println(a.Num) }
举例说明:
type Person struct { Name string } func (p Person) test() { fmt.Println("test() name=", p.Name) } func main() { var p Person //实例化 p.Name = "tom" p.test() //调用方法 }
test
方法和Person
类型绑定;
test
方法只能经过Person
类型的变量来调用,不能直接调用,也不能使用其它类型变量来调用;
func (p Person) test() {...}
,其中p
表示哪一个Person
变量调用,这个p就是它的副本,表明接收者。这点和函数传参很是类似,而且p
能够有程序员任意指定;
在3.1中 提到,方法要和指定自定义类型的变量绑定,那个这个绑定方法的变量被称为接收者,而方法的传参机制被这个接收者的类型不一样分为值类型的接收者和指针类型的接收者,下面分别来看这这两方式的传参机制:
指针类型的接收者由一个结构体的指针组成,因为指针特性,调用方法时修改接收者的任意成员变量,在方法接收后,修改都是有效的。例如为Person
结构体添加一个SetAge
方法,来修改实例中的年龄:
//Person 结构体 type Person struct { name string age int8 } // SetAge 设置p的年龄 // 使用指针接收者 func (p *Person) SetAge(newAge int8) { p.age = newAge } func main() { p1 := NewPerson("小王子", 25) fmt.Println(p1.age) // 25 p1.SetAge(30) fmt.Println(p1.age) // 30 }
当方法和值类型接收者绑定是,Go语言会在代码运行时将接收者的值复制一份 。在值类型接收者的方法中能够获取接收者的成员值,但修改操做只是针对副本,没法修改接收者变量自己。
// SetAge2 设置p的年龄 // 使用值接收者 func (p Person) SetAge2(newAge int8) { p.age = newAge } func main() { p1 := NewPerson("张三", 25) p1.Dream() fmt.Println(p1.age) // 25 p1.SetAge2(30) // (*p1).SetAge2(30) fmt.Println(p1.age) // 25 }
声明方式不同
func 函数名(形参列表) (返回值列表) {...}
func (变量 自定义类型) 函数名(形参列表) (返回值列表) {...}
调用方式不同
函数名(实参列表)
变量.方法名(实参列表)
对于普通函数,接收者为值类型时,不能讲指针类型的数据直接传递,反之亦然;
type Person struct { Name string } func test01(p Person) { fmt.Println(p.Name) } func test02(p *Person) { fmt.Println(p.Name) } func main() { var p = Person{"tom"} test01(p) test02(&p) }
struct
的方法),接收者为值类型时,能够直接用指针类型的变量调用方法,反之亦然;func (p Person) test03() { p.Name = "jack" fmt.Println(p.Name) //jack } func (p *Person) test03() { p.Name = "jerry" fmt.Println(p.Name) //jerry } func main() { p := Person{"viktor"} p.test03() fmt.Println(p.Name) // viktor (&p).test03() //从形式上传入地址,可是本质扔然是值拷贝 fmt.Println(p.Name) // viktor (&p).test04() fmt.Println(p.Name) // jerry p.test04() //等价于(&p).test04(),从形式上是传入值类型,可是本事仍然是地址拷贝 }
对于方法来讲,无论调用形式如何,真正决定是之拷贝仍是地址拷贝,看这个方法是和哪一种类型绑定,也就是接收者的类型是值类型仍是指针类型。
Golang的结构体没有构造函数,一般可使用工厂模式来解决这个问题。
首选,在Golang语言中公有和私有变量这一说法。若是说一个包中的变量的首字母为小写,在其余包若是引入这个包,就不能访问这个变量;若是这个变量的变量名为大写字母,那么能够直接访问。
一样对于自定义的struct
类型也是,而工厂模式,就是为了解决变量的首字母为小写的结构体可以被其它包引用的问题。
//student.go属于model包 package model //定义一个结构体 type student struct { Name string Score float64 } func NewStudent(n string, s float64) *stuent { return &student{ Name : n, Score : s, } } //mian.go中声明一个student实例,并初始化 func main() { var stu = model.NewStudent("viktor", 86.6) fmt.Println(*stu) fmt.Println("name=", stu.Name, "score=", stu.Score) }
另外,若是结构体中的某个字段的首字母也为小写该如何访问?
//student.go属于model包 package model //定义一个结构体 type student struct { Name string score float64 } func NewStudent(n string, s float64) *stuent { return &student{ Name : n, Score : s, } } func (s *student) GetScore() float64 { return s.score } //mian.go中声明一个student实例,并初始化 func main() { var stu = model.NewStudent("viktor", 86.8) fmt.Println(*stu) fmt.Println("name=", stu.Name, "score=", stu.GetScore()) }
原文出处:https://www.cnblogs.com/dabric/p/12361979.html