GoLang学习笔记之基础语法(五):结构体与方法

目录

前言

一、结构体与方法的定义

1.结构体与工厂函数

2.方法与接收者

二、结构体内嵌

1.继承

2.字段冲突

3.方法冲突

三、结构体字段的导出与标签

总结



前言

本文将介绍go语言中结构体的定义与使用,以及介绍方法的含义。通过结构体和方法相结合,能够完成许多面向对象编程语言中对象能完成的事情。



一、结构体与方法的定义

1.结构体与工厂函数

  • go语言通过type与struct关键字定义结构体,每个结构体可以拥有若干个字段,这些字段可以是具名的,也可以是匿名的,结构体的实例可以通过"."符号来访问到对应的字段。
  • 工厂函数,工厂函数是一种约定俗成的函数,通常用于实例化一个结构体,通常以newName方式命名。
  • 结构体与工厂函数的示例
type person struct{
	name string  // 结构体中每个字段名必须唯一
	age int  // 结构体字段名大写字母开头,可以导出给外部变量使用
	int  // 匿名字段:结构体中每种数据类型的匿名字段有且仅有一个
}

// newPerson 工厂函数,类似于面向对面编程语言中的构造函数
func newPerson(name string,age int)*person{
	return &person{
		name: name,
		age: age,
		int:32,
	}
}

2.方法与接收者

  • 方法是一种特殊的函数,它规定只有指定的接收者才能调用该方法,有点像面向对象编程语言中与类绑定的方法,接收者声明在func关键字之后,函数名之前。
  • 方法的接收者分为值接收者与指针接收者,若方法内部有需要改变接收者的值,需要使用指针接收者。
  • 值接收者是实参的值拷贝,会额外消耗内存空间,当接收者占用内存空间很大时,使用值接收者显然有点浪费空间,因此应尽量使用指针接收者
  • 指针对象调用值接收者的方法时,会自动解引用。
    func (p *person) setAge(age int){
    	p.age = age // 改变接收者的值,得用指针接收者
    }
    
    func (p person) getName()string{
    	return p.name 
    }

二、结构体内嵌


1.继承

        在面向对象编程的语言中,子类可以通过继承父类,从而获取与父类相同的字段属性与方法。在Go语言中虽然没有类的概念,但可以通过内嵌结构体实现类似的效果。

// 继承
type man struct{
	person  // 通过匿名字段嵌入person结构体,man可以访问person的所有字段,也能调用其方法
	sex string
}

func newMan(name string,age int,sex string)*man{
	return &man{
		person:person{
			name: name,
			age: age,
			int:32,
		},
		sex: sex,
	}
}

func main(){
    m := newMan("lhq",22,"male")
	fmt.Println(m.name,m.age,m.sex) // 输出: lhq 22 male,man可以访问其内嵌结构体的字段
	m.setAge(19)
	fmt.Println(m.age) // 输出: 19 ,通过内嵌结构体进行继承,man可以调用person的方法
}

        上述代码定义了一个新的man结构体,其中匿名内嵌了一个person结构体,再定义一个属于自己的字段sex,这样,man结构体的实例既可以访问person结构体的字段,又能访问自己的sex字段,同时还能调用person结构体的方法。这就近似实现了继承。


2.字段冲突

        结构体内嵌使得结构体更加丰富,但同时也会带来相应的冲突。考虑如下代码:

type women struct{
	age int  // 外部有一个age字段
	sex string
	person // person内部也有一个age字段
}

func newWomen(name string,age int,sex string)*women{
	return &women{
		age: age,
		sex: sex,
		person:person{
			name: name,
			age: age,
			int:32,
		},
	}
}

        我们定义一个women,里面内嵌了person结构体,其本身又定义了一个age字段。那么当我们使用women.age来访问或者修改age时,到底是访问的哪一个age呢? 

w := newWomen("xyt",18,"female")
	fmt.Println(w.name,w.age,w.person.age,w.sex) //输出:xyt 18 18 female
	w.setAge(21)
	fmt.Println(w.age,w.person.age) // 输出:18 21,可以看到,通过w.age访问age字段,得到的是外部字段的值,想要获取内部字段,需要指定内部结构体对象

        我们可以看到,实际访问的是外部的age字段,go语言中规定,当结构体中不同层次存在相同名字的字段时,默认外部字段会覆盖内部字段。(注意:这里的覆盖并不是指内部字段的值被外部字段的值覆盖,实际上内部字段的值依然存在,只是我们需要使用"."符号进入到内部结构体再去访问内部字段) 

3.方法冲突

        与字段冲突一样,如果结构体中不同层次声明了同一个方法,那么通过外部使用该方法,默认使用的是外部结构体声明的方法。

func (p person) getName()string{
	return p.name
}

func (w *women) getName()string{
	return "i don't tell you"
}

func main(){
    fmt.Println(w.getName()) // 同理,通过w.getName()调用方法,调用的是外部的方法,输出:I don't tell you
}

需要注意的是:当结构体同一层次中出现字段或方法冲突时,go语言将会报错,因为产生了模糊的字段域,我们要避免同一层次中出现字段或方法冲突。 

三、结构体字段的导出与标签

  • 结构体中的字段名有一个属性,即如果该字段名首字母是大写的,则该字段为导出的。(可以被其他包访问)
  • 我们使用标准json库来解释字段导出的意义
    // 标签的使用
    type user struct{
    	name string 
    	age int	
    }
    
    func newUser(name string,age int)*user{
    	return &user{
    		name: name,
    		age: age,
    	}
    }
    
    func main(){
        // 使用json包序列化与反序列化数据
    	u1 := newUser("u1",18)
    	msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
    	if err != nil{
    		fmt.Printf("Marshal failed,err:%v\n",err)
    		return
    	}
    	fmt.Println(string(msg)) // 输出{}
    }

    我们可以看到,程序最后输出的结果是{},即空的字符串,为什么会这样呢?是因为我们将user传给json.Marshal函数时,该函数得去访问user的字段,但是json这个包相对于我们程序所在的包是外部包,它访问user时没有发现首字母大写的字段,即访问不到任何字段,当然返回的就是空字符串了。我们将字段修改成首字母大写,就可以看到以下效果

    // 标签的使用
    type user struct{
    	Name string 
    	Age int	
    }
    
    func newUser(name string,age int)*user{
    	return &user{
    		Name: name,
    		Age: age,
    	}
    }
    
    func main(){
        // 使用json包序列化与反序列化数据
    	u1 := newUser("u1",18)
    	msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
    	if err != nil{
    		fmt.Printf("Marshal failed,err:%v\n",err)
    		return
    	}
    	fmt.Println(string(msg)) // 输出{"Name":"u1","Age":18}
    }

    以上代码表明了结构体字段导出的意义。如果我们的数据对大小写很敏感,一定要序列化出来的json字符串中key值是小写的,要如何做呢。GO语言还为结构体提供了标签功能,标签可以让程序识别特殊字段,从而去实现特殊功能,如下所示:

    // 标签的使用
    type user struct{
    	Name string `json:"name"`
    	Age int	`json:"age"`
    }
    
    func newUser(name string,age int)*user{
    	return &user{
    		Name: name,
    		Age: age,
    	}
    }
    
    func main(){
        // 使用json包序列化与反序列化数据
    	u1 := newUser("u1",18)
    	msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
    	if err != nil{
    		fmt.Printf("Marshal failed,err:%v\n",err)
    		return
    	}
    	fmt.Println(string(msg)) // 输出{"name":"u1","age":18}
    }



总结

结构体可以定义具名字段与匿名字段,同时可以进行结构体内嵌,但要注意字段冲突与方法冲突。当外部包需要访问结构体的字段或方法时,首字母必须大写让字段或方法导出。方法是指定接收者调用的函数,接收者分为值接收者与指针接收者,尽可能使用指针接收者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值