go基础第三篇:结构体、函数、方法、接口


本站和网页 http://wjhsh.net/koushr-p-13364581.html 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

go基础第三篇:结构体、函数、方法、接口
程序猿
程序猿
go基础第三篇:结构体、函数、方法、接口
struct
结构体struct就相当于java中的类class,用于定义属性和方法。
定义一个Person,有string型的Name和int型的Age两个属性:
type Person struct {
Name string
Age int
创建一个Person实例:
第一种方式:
var p Person = Person{}
这时,所有属性值都是零值。
直接打点给属性赋值:
p.Name = "zhangsan"
p.Age = 18
var p Person = Person{"zhangsan", 18}
一个个把属性值列出来,要求一个都不能少。
第二种方式:
var p Person = Person{Name: "zhansan", Age: 18}
这时,Name属性值为zhangsan,Age属性值为18
把属性名与值一对对列出来,可以只列需要的属性,其他的属性值为零值。注意,属性名没有用双引号括住。
第三种方式:
var pptr *Person = new(Person)
注意,与上面两种方式不同,用new关键字生成的是指向Person实例的指针。var p Person = new(Person)会报编译错误,提示Cannot use 'new(Person)' (type *Person) as type Person。
我们可以通过struct指针操作属性,效果和通过struct实例一样。
如pptr.Name = "lisi"
fmt.Println(pptr.Name)
定义一个Employee,有Person型的p和int型的salary两个属性:
type Employee struct {
p Person
salary int
创建一个Employee实例:
var e Employee = Employee{}
var e Employee = Employee{p: Person{}, salary: 10000}
var eptr *Employee = new(Employee)
如果属性类型是自定义的struct的话,属性名可以省略,如下:
type Employee struct {
Person
salary int
此时创建一个Employee实例:
var e Employee = Employee{}
var e Employee = Employee{Person: Person{}, salary: 10000}
这里由于属性名省略了,所以花括号中的key只能是我们自定义的struct了。
var eptr *Employee = new(Employee)
通过e访问Name、Age属性:
在属性名不省略时,只能通过e打点获取Person实例,然后Person实例再打点操作Name、Age属性,示例如下:
func main() {
var e Employee = Employee{p: Person{}, salary: 10000}
e.p.Name = "zhangsan"
fmt.Println(e)
在属性名省略时,我们既可以通过e打点获取Person实例,然后Person实例再打点操作Name、Age属性,也可以直接e打点操作Name、Age属性,这就有点像java的继承了。示例如下:
func main() {
var e Employee = Employee{Person: Person{}, salary: 10000}
e.Person.Name = "zhangsan"
e.Age = 30
fmt.Println(e)
函数
在go中,函数是一等公民。
函数可以有不定长入参,可以有多个返回值,可以赋值给变量,可以作为函数的入参和出参。
函数定义:func func_name(i int, s string) int {}
定义函数f0,有一个int型入参、无出参:
func f0(i int) {
定义函数f1,有一个int型入参、一个string型入参,一个int型出参:
func f1(i int, s string) int {
return 0
定义函数f2,有两个int型入参,一个string型出参:
func f2(i, j int) string {
return ""
如果多个入参类型一样的话,可以省略前面几个参数的类型关键字,而只保留最后一个参数的类型关键字。
定义函数f3,有两个int型入参,一个string型出参,一个int型出参:
func f3(i, j int) (string, int) {
return "", 0
多个出参的话,要用括号包起来。
定义函数f4,有不定长个int型入参,一个int型出参:
func f4(p ...int) int {
return len(p)
定义函数f5,有一个string型入参和不定长个int型入参,一个int型出参:
func f5(s string, sl ...int) int {
return len(s) + len(sl)
函数赋值给变量
func main() {
var f func(int, string) int = f1
f(1, "a")
变量的类型可以通过fmt.Println(reflect.TypeOf(f))打印出来看。
函数作为函数的入参和出参
func S(f func(i int) int) func(s string) string {
return func(s string) string {
return strconv.Itoa(f(len(s)))
func main() {
var p = S(func(i int) int {
return i + 10
})("100")
fmt.Println(p)
我们在定义函数时,如果入参是一个struct实例,则底层会复制一个struct实例作为入参,函数对实例的改动不会影响原来的struct实例。所以,入参最好是指向struct实例的指针,这样就不用复制了,函数对实例的改动也会体现到原来的struct实例上。示例如下:
func changeName(e Person) {
e.Name = "zhangsan"
func changeNamePtr(e *Person) {
e.Name = "zhangsan"
func main() {
var p Person = Person{}
changeName(p)
fmt.Println(p)
var ptr *Person = new(Person)
changeNamePtr(ptr)
fmt.Println(ptr)
方法
方法和函数长得差不多,区别是方法定义时func后面跟的不是func_name,而是括号,括号里面是struct类型变量,只有这个struct类型实例或者指针才能调用这个方法,之后才是func_name。示例如下:
func (p *Person) exchange(p0 *Person) {
p.Name = p0.Name
p0.Age = p.Age
func main() {
var p Person = Person{Name: "zhansgan", Age: 18}
var p0 Person = Person{Name: "lisi", Age: 10}
p.exchange(&p0)
fmt.Printf("%+v
", p)
fmt.Printf("%+v
", p0)
func (p *Person) exchange(p0 *Person) {}
第一个括号中的p *Person表示本方法调用者只能是Person实例或者指向Person实例的指针,且是引用传递,如果在方法中改变了调用者的属性,会在方法外体现。
第二个括号中的p0 *Person表示方法入参是引用传递,如果在方法中改变了入参的属性,会在方法外体现。
方法的继承
还是以上面的Person、Employee举例。
假设Person有个changeName方法,定义如下:
func (pptr *Person) changeName(name string) {
pptr.Name = name
假如在定义Employee时省略了Person类型属性的名称,则我们可以通过e直接打点调用changeName方法,示例1如下:
func main() {
var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
e.changeName("lisi")
fmt.Printf("%+v
", e)
假设Employee也有个changeName方法,则直接通过e打点调用changeName方法的话,调用的其实是Employee的changeName方法,而不是Person的changeName方法。示例2如下:
func (pptr *Person) changeName(name string) {
pptr.Name = name
func (eptr *Employee) changeName(name string) {
eptr.Name = name + name
func main() {
var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
fmt.Printf("%+v
", e)
假如Employee有个change方法,在change方法中调用了changeName方法,那么e调用change方法时,执行的是Employee的changeName方法呢,还是Person的changeName方法呢?示例3如下:
func (pptr *Person) changeName(name string) {
pptr.Name = name
func (pptr *Person) change(name string) {
pptr.changeName(name)
func (eptr *Employee) changeName(name string) {
eptr.Name = name + name
func main() {
var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
e.change("lisi")
fmt.Printf("%+v
", e)
实测执行的是Person的changeName方法。
假如Employee也有个change方法,在change方法中调用了changeName方法,那么e调用change方法时,执行的是Employee的changeName方法呢,还是Person的changeName方法呢?示例4如下:
func (pptr *Person) changeName(name string) {
pptr.Name = name
func (pptr *Person) change(name string) {
pptr.changeName(name)
func (eptr *Employee) changeName(name string) {
eptr.Name = name + name
func (eptr *Employee) change(name string) {
eptr.changeName(name)
func main() {
var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
e.change("lisi")
fmt.Printf("%+v
", e)
实测执行的是Employee的change方法和Employee的changeName方法。
分析:
在changeName和
总结:
在go中没有继承,不论是属性还是方法。都是太任性的省略搞的鬼。
在go中没有静态方法的概念。
接口
定义格式:
type RedPacketService interface {
add(s string)
delete(s string)
update(s string)
query(s string) string
go中没有implements或者相同作用的关键字。要实现某个接口,不是在定义struct的时候显式声明要实现某个接口,而是采用duck typing的方式,即只要实现了接口的所有方法,就认为这个struct实现了这个接口,就可以向上转型。示例如下:
type Programmer interface {
helloWorld()
type JavaProgrammer struct {
func (jp *JavaProgrammer) helloWorld() {
fmt.Println("System.out.println("Hello World!");")
type GoProgrammer struct {
func (gp *GoProgrammer) helloWorld() {
fmt.Println("fmt.Println("Hello World!")")
func main() {
var p Programmer = new(JavaProgrammer)
p.helloWorld()
p = new(GoProgrammer)
p.helloWorld()
Programmer接口只有一个helloWorld方法,JavaProgrammer实现了这个方法,且是pointer receiver实现,所以JavaProgrammer实现了Programmer接口,同理,GoProgrammer也实现了Programmer接口。
假如再给Programmer接口添加一个hi方法,由于JavaProgrammer和GoProgrammer都没有实现这个方法,所以这两个struct都没有实现Programmer接口,所以把指向JavaProgrammer实例或GoProgrammer实例的指针赋值给Programmer类型变量时会报编译错误。
interface{}
interface{}可以用作任意类型的形参,示例如下:
func PX(s interface{}) {
if s0, ok := s.(int); ok {
fmt.Println("int=", s0)
} else if s0, ok := s.(string); ok {
fmt.Println("string=", s0)
} else if s0, ok := s.(bool); ok {
fmt.Println("bool=", s0)
} else {
fmt.Println("unknown type")
func main() {
PX("1")
PX(1)
PX(false)
PX(new(Programmer))
以上,s可以是任意类型变量。s.(int)有2个返回值,第一个返回值是s,第二个返回值是true或者false,如果s是int型变量,就是true,否则就是false。所以通过判断第二个返回值是否是true,就能判定s是否是int型变量。
以上写法还可以通过switch来简化判断,如下:
func PX(s interface{}) {
switch s.(type) {
case int:
fmt.Println("int=", s)
case string:
fmt.Println("string=", s)
case bool:
fmt.Println("bool=", s)
default:
fmt.Println("unknown type")
func main() {
PX("1")
PX(1)
PX(false)
PX(new(Programmer))
s.(type) 跟在switch后面没毛病,单独写就会报编译错误,估计是个特殊语法,编译器做了特殊支持。
原文地址:https://www.cnblogs.com/koushr/p/13364581.html
推荐文章
面向对象
移动web
移动端zepot&媒体查询media queries
移动web
h5
ajax&模板引擎
ajax
Ajax初体验
php初体验
CSS3小结
TPCH Benchmark with Impala
在Eclipse上建立hbase 0.98.3/0.96.2源代码阅读环境
在Eclipse上建立hadoop2.2.0/hadoop2.4.0源代码阅读环境
Spark Streaming之旅
SparkSQL之旅
Setup Spark source code environment
spark0.9.1 assembly build-RedHat6.4 YARN 2.2.0
转载:ODS简介
Hadoop1.2.1 全集群3节点安装-rpm安装
在standalone模式下运行yarn 0.9.0对HDFS上的数据进行计算
yarn 0.9.0 build spark
redhat6.4上build storm 0.9.0.1
redhat6.4安装storm集群-4节点
Hadoop 2.2.0 4结点集群安装 非HA
redhat6.4上安装mysql
redhat6.4上用apache建立os repos
Hive Over HBase
Hadoop 2.2.0学习笔记20131210
Hadoop 2.2.0学习笔记20131209
IDH2.5.1. Pain Points
Copyright 2008-2020 晋ICP备16003840号-1