3. Struct 与 面向对象 |《 刻意学习 Golang 》

go 的变量是 name type, 老是写成 c 的 type name
go 的 type name struct c 的typedef struct name

struct

对比 PHP:
PHP 中没有 struct 类型,这里对比 C 的 struct

  • 支持匿名字段「嵌入字段」
  • type = c 的 typedef
  • 初始化有所区别
  • 声明
  • 初始化
  • 访问

先看一下 C 中的 struct

// 结构体声明常用的两种
// 通过 typedef
typedef struct Human {
    char* name;
    int age;
} *pHuman;
// 直接声明
struct Human {
    char* name;
    int age;
};
struct Staff {
    Human* human; // 指向结构体的指针
    Human human; // 包含结构体
    pHuman human; // 包含指向结构体的指针
    int wage;
}

再看 Go

type Human struct {
    name string
    age int
}
var tom Human
// 通过赋值初始化
tom.name, tom.age = "Tom", 18
// 详细初始化
jerry := Human{age:25, name:"Jerry"}
// 按照结构体声明顺序
peter := Human{"Peter", 34}
// 通过 . 访问
fmt.Printf("%s is %d old\n", tom.name, tom.age)

匿名字段

匿名字段也叫「嵌入字段」顾名思义,嵌入到一个结构体的字段

只提供类型,而不写字段名。『通过这个来定义』

  • 支持:类型、自定类型、结构体
  • 如果是结构体:隐式的引入这个结构体的字段到新结构体
  • 重复字段最外层优先原则,里层通过 隐式类型访问
type Staff struct {
    Human // 隐式的引入 Human 的字段
    wage int
    age float32 // 覆盖 Human 的 age
}
tom := Staff{Human{"Tom", 18}, 1000, 18.5}
jerr := Staff{Human:Human{age:32, name:"Jerr"}, wage: 2000, age: 32.5}
fmt.Printf("%s is %f old,%d years, wage is %d", tom.name, tom.Human.age, tom.age, tom.wage)
fmt.Printf("%s is %f old,%d years, wage is %d", jerr.name, jerr.Human.age, jerr.age, jerr.wage)

面向对象

看完了 go 的面相对象后,emmmm... 感觉有点颠覆。首先是告别 class 。 然后一系列的 static public protected private 都没有了,甚至是 extends parent 都省了。我现在还很不习惯... 魔术方法?构造函数?...且边学边看

methd

"A method is a function with an implicit first argument, called a receiver."

这么理解呢,这么说
一个 method 就是 function 的第一个『参数』是一个 receiver 「接收者,接收这个函数的是谁即是方法的所有者」\
如果还没明白的话就再通俗一点

receiver = class
method = 成员函数

// 下面这个 function 是 ReceiverType (class) 的一个 method, 
// funcName 首字母大写是公有、小写是私有
// ReceiverType 可以是结构体、自定义类型、内置类型「PS这个是真的骚」
func (r ReceiverType) funcName(parameters) {}

指针 作为 receiver

go 知道我们需要调用或传递的是指针,你写值他会自动帮你去用指针。理解这个先来理解下面 c 代码

void setZore(int* a) {
    *a = 0;
    // 在 go 中可以理解为
    a = 0; // go 自动给你变成 *a = 0
}
int x = 10;
// 在 C 中必须传递地址 go 中可以是 setZore(x)
setZore(&x);

如果一个 method 的 receiver 是 *T, 你可以在一个 T 类型的实例变量 V 上面调用这个 method,而不需要 &V 去调用这个 method

如果一个 method 的 receiver 是 T,你可以在一个 T 类型的变量 P 上面调用这个 method,而不需要 P 去调用这个 method

继承跟重载

继承跟重载都跟匿名字段一样的

  • 继承:如果匿名字段实现一个 method 包含这个匿名字段的结构体也能使用这个 method
  • 重写:在包含者上面重新定义这个 method,重写匿名字段的方法
type Human struct {
    name string
    age int
}

type Employee struct {
    Human
    wage int
}

func (h *Human) Say() {
    fmt.Printf("Hi, I am %s, My age is %d", h.name, h.age)
}

// Employee 重写 Say
func (e *Employee) Say() {
    fmt.Printf("Hi, I am %s, %d old year, wage is %d per mothn", e.name, e.age, e.wage)
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
高度自律,深度思考,以勤补拙
讨论数量: 14

这继承好蛋疼的感觉

type Employee struct {
    Human
    wage int
}

type Staff {这里少了struct 吧

5年前 评论

@lovecn 『已纠正,感谢~』 我也是 觉得他这个很另类, 跟接触的面相对象都不一样。

5年前 评论

@lovecn go 的面向对象 跟 接口 在设计上就有所区别,目前我还说不出这种设计跟传统我们了解的 之间有何优缺点。再完后看慢慢应该就能找到这么设计的精髓

5年前 评论

老哥有 fmt.Printls() 这个函数吗?

5年前 评论

@Alifmt.Printf() 哈哈哈 笔误,谢谢~ 已纠正

5年前 评论

@jerrkill 另外还有我用你的代码执行,会报错 %s 的输出错误.,什么非法字符.

5年前 评论

@Ali 能截图看下吗,还有具体代码,我在我电脑上是执行通过的

5年前 评论

@jerrkill 可以我也是刚学,也可能是我 语法上有错误.希望指正. 感觉像是覆盖 Human 的 age 的错误.但我换了 int 也不行.
代码如下:

file

输出结果如下:

file

5年前 评论

@Ali
file
如上图

fmt.Printf("%s is %d old, %.2f years, wage is %d \n", jerr.name, jerr.Human.age, jerr.age, jerr.wage)
fmt.Printf("%s is %d old, %.1f years, wage is %d \n", tom.name, tom.Human.age, tom.age, tom.wage)
5年前 评论

@jerrkill 谢谢 ! 我懂了,主要还是对 %f %d %s 不了解啊. 我去查询下资料补补去.
那你的不用指定几位浮点吗?

5年前 评论

@Ali %.1f 保留一位小数 %.2f 两位,就这样。如果没有设置保留小数位的话默认是 6 位。
32 位浮点数跟 64 位浮点数都用 %f 默认是保留 6 位小数,这个应该在 fmt 包里面的 Printf() 里面可以看源码。
正准备接下来分析标准库跟包,并写在《刻意学习 golang》 里面来。欢迎关注哈~

5年前 评论

@jerrkill 必须关注啊.我流程式的学习,看到你文章,就像是我自己的总结一样,甚至还有我没学到的 一些知识.
这样子的话,应该是因为电脑环境的原因.我的 MacOS .

5年前 评论

@Ali %f 是用来格式化 float 的,而 jerr.Human.age 是 int 类型,所以会报错

5年前 评论

@jerrkill 嗯嗯.我是把顺利搞混了. 哈哈.

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!