zoukankan      html  css  js  c++  java
  • Go语言基础之构造函数、方法和接收者

    Go语言基础之构造函数、方法和接收者

    一、构造函数

    Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。

    // * persion 返回地址的
    func newPerson(name, city string, age int8) *person {
        return &person{
            name: name,
            city: city,
            age:  age,
        }
    }
    

    调用构造函数

    p9 := newPerson("张三", "ah", 90)
    fmt.Printf("%#v\n", p9) //&main.person{name:"张三", city:"ah", age:90}
    

    二、方法和接收者

    Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self

    方法(面向对象中的概念) :函数和方法

    方法有特殊之处,绑定给对象的,在方法内部,可以修改对象

    方法的定义格式如下:

    func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
        函数体
    }
    

    其中,

    • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是selfthis之类的命名。例如,Person类型的接收者变量应该命名为 pConnector类型的接收者变量应该命名为c等。
    • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
    • 方法名、参数列表、返回参数:具体格式与函数定义相同。

    举个例子:

    //Person 结构体
    type Person struct {
        name string
        age  int8
    }
    
    //NewPerson 构造函数
    func NewPerson(name string, age int8) *Person {
        return &Person{
            name: name,
            age:  age,
        }
    }
    
    //Dream Person做梦的方法
    func (p Person) Dream() {
        fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
    }
    
    func main() {
        p1 := NewPerson("小王子", 25)
        p1.Dream()
    }
    

    方法与函数的区别是, 函数不属于任何类型方法属于特定的类型。

    三、指针类型(接收器)的接收者

    指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。

    // SetAge 设置p的年龄
    // 使用指针接收者
    func (p *Person) SetAge(newAge int8) {
        p.age = newAge
    }
    

    调用该方法:

    func main() {
        p1 := NewPerson("randy", 25)
        fmt.Println(p1.age) // 25
        p1.SetAge(18)
        fmt.Println(p1.age) // 18
    }
    

    四、 值类型(接收器)的接收者

    当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

    // SetAge2 设置p的年龄
    // 使用值接收者
    func (p Person) SetAge2(newAge int8) {
        p.age = newAge
    }
    
    func main() {
        p1 := NewPerson("randy", 25)
        p1.Dream()
        fmt.Println(p1.age) // 25
        p1.SetAge2(30) // (*p1).SetAge2(30)
        fmt.Println(p1.age) // 25
    }
    

    什么时候应该使用指针类型接收者

    1. 需要修改接收者中的值
    2. 接收者是拷贝代价比较大的大对象
    3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

    五、匿名字段方法

    //5 匿名字段方法
    
    type Hobby1 struct {
    	id        int
    	hobbyname string
    }
    
    type Person4 struct {
    	name   string
    	age    int
    	Hobby1 //匿名字段
    }
    
    func (h Hobby1) printName() {
    	fmt.Println(h.hobbyname)
    }
    
    func main(){
        p :=Person4{"randy",18,Hobby1{1,"篮球"}}
    
    	// 调用打印hobby名字的方法printName
    	p.Hobby1.printName()  //大家都想到
    	p.printName()   //直接点出来
    	
    
    	//面向对的继承Person4继承了Hobby1,对象.方法 可以直接用父类的方法
    }
    
    • 面向对的继承Person4继承了Hobby1,对象.方法 可以直接用父类的方法

    六、任意类型添加方法

    在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

    非结构体上的方法(需要重命名)

    //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
    }
    

    非结构体上的方法,想给int 绑定一个add方法,以后每add一次,自加1(int,string,数组,都不能直接绑定方法),把int8重命名

    type Myint int8
    func (i *Myint)add()  {
    	(*i)++  //自增1
    }
    	
    func main() {
    	//想给int 绑定一个add方法,以后每add一次,自加1
    	var a Myint=10
    	a.add()
    	a.add()
    	a.add()
    	fmt.Println(a)
    }
    

    13

    注意事项:非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

    七、结构体的“继承”

    Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

    //Animal 动物
    type Animal struct {
        name string
    }
    
    func (a *Animal) move() {
        fmt.Printf("%s会动!\n", a.name)
    }
    
    //Dog 狗
    type Dog struct {
        Feet    int8
        *Animal //通过嵌套匿名结构体实现继承
    }
    
    func (d *Dog) wang() {
        fmt.Printf("%s会汪汪汪~\n", d.name)
    }
    
    func main() {
        d1 := &Dog{
            Feet: 4,
            Animal: &Animal{ //注意嵌套的是结构体指针
                name: "乐乐",
            },
        }
        d1.wang() //乐乐会汪汪汪~
        d1.move() //乐乐会动!
    }
    

    结构体字段的可见性结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

    八、 结构体与JSON序列化

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

    //Student 学生
    type Student struct {
        ID     int
        Gender string
        Name   string
    }
    
    //Class 班级
    type Class struct {
        Title    string
        Students []*Student
    }
    
    func main() {
        c := &Class{
            Title:    "101",
            Students: make([]*Student, 0, 200),
        }
        for i := 0; i< 10; i++ {
            stu := &Student{
                Name:   fmt.Sprintf("stu%02d", i),
                Gender: "男",
                ID:     i,
            }
            c.Students = append(c.Students, stu)
        }
        //JSON序列化:结构体-->JSON格式的字符串
        data, err := json.Marshal(c)
        if err != nil {
            fmt.Println("json marshal failed")
            return
        }
        fmt.Printf("json:%s\n", data)
        
        
        //JSON反序列化:JSON格式的字符串-->;结构体
        str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
        c1 := &Class{}
        err = json.Unmarshal([]byte(str), c1)
        if err != nil {
            fmt.Println("json unmarshal failed!")
            return
        }
        fmt.Printf("%#v\n", c1)
    }
    
    &{101 [0xc000060360 0xc000060390 0xc0000603c0 0xc0000603f0 0xc000060420 0xc000060450 0xc000060480 0xc0000604b0 0xc0000604e0 0xc000060510]}
    
    json:{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}
    &main.Class{Title:"101", Students:[]*main.Student{(*main.Student)(0xc000060870), (*main.Student)(0xc0000608a0), (*main.Student)(0xc0000608d0), (*main.Student)(0xc000060900), (*main.Student)(0xc000060960), (*main.Student)(0xc000060990), (*main.Student)(0xc0000609c0), (*main.Student)(0xc0000609f0), (*main.Student)(0xc000060a20), (*main.Student)(0xc000060a50)}}
    
    

    8.1 结构体标签(Tag)

    Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

    `key1:"value1" key2:"value2"`
    

    结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

    例如我们为Student结构体的每个字段定义json序列化时使用的Tag:

    //Student 学生
    type Student struct {
        ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key
        Gender string //json序列化是默认使用字段名作为key
        name   string //私有不能被json包访问
    }
    
    func main() {
        s1 := Student{
            ID:     1,
            Gender: "男",
            name:   "沙河娜扎",
        }
        data, err := json.Marshal(s1)
        if err != nil {
            fmt.Println("json marshal failed!")
            return
        }
        fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
    }
    
    json str:{"id":1,"Gender":"男"} // id变成了小写,相当于给ID添加字段说明标签
    

    五、总结

    1. 定义 绑定给结构体的func (p Person)speak(){},speak方法绑定给了Person结构体,使用 p.speak()
    2. 有了函数,为什么要有方法?它跟结构体绑定在一起,使用起来更方便
    3. 值类型接收器和指针类型接收器func (p Person)speak(){} func (p *Person)speak(){} 指针会改变原来的结构体,值的话不会改变原来的结构体
    4. 匿名字段的方法(结构体嵌套了另一个结构体,另一个结构体如果是匿名的,并且绑定了方法),方法会提升(类比:面向对象的继承)p.另一个结构体的方法() p.另一个结构体.另一个结构体的方法()
    5. 非结构体的方法, 给类型重命名 type 自己的名字 类型 或者可以给自己命名的类型绑定方法
    6. 在方法中使用值接收器和在函数中使用值参数
    7. 在方法中使用指针接收器和在函数中使用值参数
    8. 结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
    9. JSON序列化:json.Marshal(结构体实例)
    10. JSON反序列化:json.Unmarshal(字符串, 结构体)
  • 相关阅读:
    数据结构化与保存
    使用正则表达式,取得点击次数,函数抽离
    爬取校园新闻首页的新闻
    网络爬虫基础练习
    词频统计
    试题----编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个
    试题---求出现重现次数最多的字母,如有多个重复的则都求出来
    试题----为什么Java的string类要设成immutable(不可变的)
    面试题---题目
    复制文件夹中所有内容到指定位置
  • 原文地址:https://www.cnblogs.com/randysun/p/15417602.html
Copyright © 2011-2022 走看看