文章总结: 该文档通过将编程概念类比为自然语言语法(函数=动词、变量=名词、布尔值=形容词、条件判断=连词),系统讲解Go语言基础。核心方法论是将代码编写视为’讲故事’,逐步演示如何将日常语句转化为可执行程序。文档提供具体代码示例、学习路径设计(从面向过程到面向对象)及实用学习建议(如注意红色警示框),旨在降低编程入门门槛。 综合评分: 85 文章分类: 安全培训,安全开发,其他,安全意识,安全建设
用人话学编程, 把语法当代码,把句子当程序
原创
钟智强 钟智强
哪吒网络安全
2026年6月23日 23:27 马来西亚
在小说阅读器读本章
去阅读
前言:为什么句子能教会你写代码
很多人第一次看到代码,反应都一样:满屏幕的括号、分号、缩进,像一份用外星文字写的合同。于是还没开始,就先被吓退了一半。但我想先告诉你一个让人安心的事实,你其实早就掌握了一门远比任何编程语言都复杂的「语言」,那就是你每天张口就来的人话。
中文有主语、谓语、宾语;英文有名词、动词、形容词、连词。你从小到大造了几十万句话,从来没有人教过你「语法解析算法」,你却能脱口而出、毫无错误。既然你这么擅长「造句」,那我们要做的其实只有一件事:把「造句」这件你早就会的事,翻译成「写代码」。
我自己当年就是靠这个笨办法入门的。与其去硬背「函数」「参数」「布尔值」「实例化」这些一听就让人犯困的术语,我宁愿把它们一个个翻译回我最熟悉的语法成分。一旦你发现「写程序」和「讲故事」根本是同一件事,那种面对代码的恐惧就会忽然消失,剩下的全是「原来如此」的快乐。
核心心法: 代码不是数学题,代码是「把一件事讲清楚」。一句话里谁在做(名词)、做什么(动词)、在什么状态下做(形容词)、遇到岔路怎么选(连词)、要不要反复做(循环),讲明白了,代码也就写出来了。你越会讲故事,就越会写程序,这不是鸡汤,是真的。
这本书全程用 Go 语言来演示。为什么选 Go?因为它干净、直白、不绕弯子,几乎没有花里胡哨的语法糖。它就像一个说话从不藏着掖着的老实人:你让它做什么,它就老老实实做什么,写出来的代码别人一眼就能看懂。对刚入门的人来说,这种「不耍小聪明」的性格,是最好的老师。
这本书该怎么读
这本书是「层层叠高」的,强烈建议你从头往后读,别跳。它的结构是这样的:
第一章打地基:四个最核心的类比(动词、名词、形容词、连词),外加函数怎么「回答问题」。这是后面所有内容的钢筋。
第二章学「讲一段故事」:把动作按顺序排好,再教你用循环偷懒,这就是面向过程。
第三章学「让角色自己活过来」:结构体、方法、封装、组合、接口,这就是面向对象。
第四章做总结:两种思路到底什么时候用哪个,给你一张能直接抄的对照表。
每一节都配了图、配了能直接运行的 Go 代码,还有三种颜色的小框框:蓝色框讲「为什么这样设计」,黄色框是帮你记忆的段子,红色框专门提醒「新手最容易踩的坑」。看到红色框,请务必慢下来读两遍。
先讲个冷笑话热热身: 为什么程序员总把万圣节和圣诞节搞混?因为 Oct 31 == Dec 25。看不懂?完全没关系。等你读完这本书再翻回来,你大概率会先笑一下,然后有点想打我。这个笑话的答案,我藏在了全书的结尾。
第一章 语法即代码:四大黄金类比
整本书都建立在下面这一张对照表上。它简单到有点离谱,但威力巨大,后面所有看起来很「高级」的概念,剥开外壳,都只是这四条的组合与延伸。请你像记住一句广告词那样把它刻进脑子里。
| 编程概念 | 语法成分 | 它在句子里干嘛 | | — | — | — | | 函数 Function | 动词 | 表示一个「动作」:跑、吃、娶、计算 | | 变量 / 参数 | 名词 | 表示「人、地点、东西」:小明、北京、包子 | | 布尔值 Boolean | 形容词 | 描述「状态」:饿不饿、愿不愿意、准备好没 | | If/Else 判断 | 连词 | 做「决定」:如果……就……、除非……否则…… |
文字看着有点抽象,我们换成图来感受一下。左边是你熟悉的人类语言,右边是它在代码里对应的样子,中间用一根箭头连起来,这根箭头,就是这本书要反复带你走的那条路。
图 1-1 四大黄金类比:每一种语法成分,在代码里都有一个老朋友在等你。
1.1 函数 = 动词(动作)
一句话里,最不能少的是什么?是动词。「小明吃包子」,你把「吃」拿掉,就只剩「小明……包子」,什么都没发生,像一句话说到一半被人打断。动词是句子的引擎,是「让事情真正发生」的那个词。
代码里扮演这个角色的,就是函数。函数是一段「打包好的动作」:你给它起个名字,把要做的事写在里面,以后想做这件事,喊一声名字就行,不用每次都从头交代一遍。这也是为什么函数名几乎永远是个动词,eat(吃)、run(跑)、calculate(计算)、login(登录)。
找动词的技巧只有一句话:问自己「这句话到底在干嘛?」,那个「干嘛」就是你的函数名。
func eat() {
// 把「吃」这个动作的具体步骤写在这里面
// 比如:张嘴、咀嚼、吞下
}
为什么要「打包」? 想象你每天都要跟人解释一遍「怎么泡咖啡」:烧水、磨豆、冲泡、搅拌……累不累?不如把这一整套动作打包成一个动词 makeCoffee(),以后只说「泡杯咖啡」四个字,对方就懂了。函数就是帮你「把啰嗦的步骤收成一个词」的工具,写一次,到处用。
记忆口诀: 动词当函数。一句话里凡是「会动、会喘气」的词,基本都能当函数名。「躺平」也是动词,所以它也能写成 func liePing(),只不过函数体里一行代码都不写,非常符合人设,主打一个什么都不做。
动词还有一个常被新手忽略的本事:它不光会「做」,有时还会「给你一个回答」。「小明,算一下一加一」,他做完这个动作,会回头告诉你「2」。这个交还回来的结果,在代码里叫做返回值,用关键字 return 交出去。
// 括号后面那个 int,表示「这个动词做完,会回答你一个整数」
func add(a int, b int) int {
return a + b // return = 把算好的答案交回去
}
sum := add(1, 1) // sum 接住了那个答案,现在它是 2
有没有返回值,是两种动词: 有的动词「做了就完事」,不回答什么,比如 eat(),吃完就吃完了,括号后面什么都不写。有的动词「做完要交个答案」,比如 add(),算完要回答一个数,所以括号后面写上 int。一个函数会不会「回话」、回什么话,全看它括号后面那一小段声明。这是函数能像积木一样互相拼接的关键:一个动词的回答,可以直接喂给下一个动词。
1.2 变量 / 参数 = 名词(人、地点、东西)
光有动词还不够。「吃」,谁吃?吃什么?总得有人来做这个动作、有东西被这个动作作用吧。这些「谁」和「什么」,就是名词,对应到代码里,就是变量和参数。
在「小明吃包子」里,「小明」和「包子」都是名词。把它们塞进动词(函数)里,动作才完整、才有意义。在代码里,我们把这些名词「喂」给函数,术语叫做「传参数」,听起来很专业,其实就是「告诉这个动作:对谁做、用什么做」。
func eat(who string, food string) {
// who = 谁在吃
// food = 吃的是什么
}
// 调用:把两个名词喂给「吃」这个动词
eat(“小明”, “包子”)
这里出现了一个新东西:who 和 food 后面跟着的 string。这是 Go 在要求你「给名词标明类型」。
什么是「类型」? 类型就是「这个名词属于哪一类东西」。string 表示「一串文字」(名字、地名都是文字),int 表示「整数」(年龄、数量),bool 表示「是/否」。这就像填表时,「姓名」栏要填文字、「年龄」栏要填数字,标清楚了,机器才不会把「小明」当成数字去做加减,闹出笑话。Go 对类型要求很严格,这其实是在替你提前挡掉一大堆低级错误。
造一个名词(声明变量)有两种写法,你都会经常见到:
var name string = “小明” // 完整版:把类型 string 写得明明白白
name := “小明” // 偷懒版:Go 一看右边是文字,自己就知道是 string
age := 18 // 同理,右边是整数,它自动判断成 int
新手用 := 就够了: 那个 := 是 Go 的「短变量声明」,意思是「造一个新名词,并且顺手塞个初始值进去,类型你别操心,我替你猜」。绝大多数时候用它又短又省心。只有在「我先占个位置,值待会儿再说」的场合,才需要写完整的 var。
变量这名字起得好: 变量,变量,会变的量。你以为它是个老实的常数,结果它从头到尾一直在变,这一点和谈恋爱前后的某些人非常像。与之相对的是「常量 constant」,一旦定下来就绝不改变,可惜现实里这种靠谱角色,比代码里少多了。
1.3 布尔值 = 形容词(状态、条件、就绪与否)
形容词是用来描述「状态」的:饿的、累的、愿意的、准备好的。在人类语言里,形容词可以有很多层次,「有点饿」「非常饿」「饿到前胸贴后背」。但在代码最底层,有一种最干脆的状态,答案只有两个:是(true)或者否(false),没有中间地带。这种「非黑即白」的值,就叫布尔值(Boolean),它正是形容词在代码世界里的化身。
命名布尔变量有个好习惯:用 is、has、can 这类词开头,让它读起来就像一个「是非题」的答案。isHungry 念出来是「饿吗?」,hasMoney 是「有钱吗?」,canSwim 是「会游泳吗?」。这样写出来的代码,几乎就是一句句能直接读懂的中文。
isHungry := true // 形容词:饿的(是,正饿着)
isWilling := false // 形容词:愿意的(否,并不愿意)
hasMoney := true // 形容词:有钱的(是,有)
布尔值之间还能用「而且 / 或者 / 不是」来组合,对应代码里的 &&、||、!。「又饿又有钱」就是 isHungry && hasMoney,两个都为真才算真;「要么饿要么有钱」是 isHungry || hasMoney,有一个真就算真。这其实就是你平时说话里的「并且」和「或者」。
为什么只有 true 和 false? 因为计算机的世界归根到底是由开关组成的,通电或断电,1 或 0。布尔值就是这种「开/关」思维在代码里的直接体现。它的好处是干脆:电脑做判断时不会纠结、不会「再想想」,给它一个 true,它就坚定地走这条路。这种果断,有时候真值得我们学习。
人间真实: 你妈问你「结婚了没?」,标准答案只有两个:true 或 false。你要是回一句「差不多吧」「快了快了」「在看了在看了」,抱歉,那不是合法的布尔值,那叫抛出异常,而且通常会触发你妈那边一长串的错误处理流程。
1.4 If / Else = 连词(做决定)
连词负责让句子拐弯、做选择:「如果下雨,就带伞;否则,就空手出门。」这种「如果……就……,否则……」的结构,正是代码里的 if / else。它就是程序里的十字路口,是故事开始有「剧情分支」的地方。
if / else 的工作非常简单:它先看一眼某个形容词(布尔值)是 true 还是 false,然后据此决定走哪条路。形容词负责「描述状态」,连词负责「根据状态做决定」,两者一搭配,程序就有了随机应变的能力。
if isHungry {
eat(“小明”, “包子”) // 如果饿了,就去吃
} else {
work(“小明”) // 否则,就继续搬砖
}
现实里的岔路口,常常不止两条路。「太早就再睡会儿;不早不晚就正常出门;太晚了就只能编故事。」三条以上的分支,用 else if 一节一节接下去:
if hour < 6 {
fmt.Println(“太早了,再睡会儿”)
} else if hour < 9 {
fmt.Println(“正常时间,出门上班”)
} else {
fmt.Println(“迟到了,开始编故事”)
}
新手坑: else if 是「前面的条件都不成立,才轮到我」。一旦上面某个分支命中了,下面的分支就全部不看了,程序扭头就走。所以条件的先后顺序很重要,一定要把「最特殊、最严格」的条件放在最前面。要是把 hour < 9 写在 hour < 6 前面,那「凌晨 3 点」也会被当成「正常时间」,因为它先撞上了 hour < 9 这道门。
Go 的一个小脾气:没有三元运算符 很多语言提供「三元运算符」,用 a ? b : c 一行就能写完一个判断,显得很酷。但 Go 故意不给你这个玩具。Go 的理由很直白:与其耍小聪明挤成一行让人猜,不如老老实实写成 if / else,让任何人扫一眼都懂。所以本书全程用 if / else,代码是长了点,但清清楚楚,新手反而最该养成这个习惯。
连词的威力: 人生的剧情分支,几乎都是连词写的。「如果考上了,就请客;否则,就装作没考过。」「除非他先道歉,否则我绝不理他。」你看,你早就是写 if / else 的高手了,只是以前用的是嘴,现在改用键盘而已。
1.5 第一个完整句子:把一句话翻译成 Go
理论讲完,我们立刻动手。来翻译这句话:「小明娶小红,只要她是自愿的。」
先别急着写代码。我们先像小学语文课那样,把这句话「划分句子成分」,看看每个词分别是什么角色:
动词(动作):娶 marry,整句话的核心动作,所以它当函数名。
名词(谁):小明、小红,两个参与者,所以它们当参数。
形容词(状态):自愿的 isWilling,描述小红此刻的状态,所以它是布尔值。
连词(决定):只要……才……,一个条件判断,所以它是 if。
拆解清楚之后,下面这张图把「词」和「代码」用颜色一一连了起来。看懂了这张图,你就看懂了这本书最重要的一步。
图 1-2 同色相连:动词变函数名,名词变参数,形容词变布尔值。
现在,把这些零件拼起来,就是一段真正能运行的 Go 程序:
package main
import “fmt”
// 动词 marry 接收两个名词(参数),并检查一个形容词(布尔条件)
func marry(whoGuy string, whoGirl string, isWilling bool) bool {
if isWilling {
return true // 只要自愿,婚就结成了
}
return false // 否则,门儿都没有
}
func main() {
married := marry(“小明”, “小红”, true)
fmt.Println(“结婚成功了吗?”, married) // 输出:结婚成功了吗? true
}
我们逐行读一遍,确认每个零件都对得上:func marry 是「娶」这个动词;括号里的 whoGuy、whoGirl 接住的是「小明」和「小红」两个名词;isWilling 是「自愿」这个形容词;函数体里的 if isWilling 就是「只要她自愿」这句连词;最后那个 bool 是返回值,表示「这场婚事到底成没成」。在 main 里调用 marry,把三个具体的值填进去,程序就跑起来了。
恭喜出师第一关: 你刚刚用「造句」的方式,写完了人生第一个 Go 程序。如果你把最后那个 true 改成 false,函数就会诚实地返回 false,代码从不口是心非,这一点,它比某些口口声声说「我愿意」的人靠谱多了。
第二章 面向过程:把代码当故事一句一句讲
会写单个句子之后,我们来挑战写「一整段故事」。把好几个动作按顺序排好、一句接一句执行,这种最朴素、最符合直觉的写法,就叫面向过程编程(Procedural Programming)。
它的精髓,浓缩成一句话就是:按顺序,一步接一步,把动作排好队。 第一句做完才做第二句,第二句做完才做第三句,绝不插队。这就是讲故事的方式,也是念流程、走步骤的方式,它和你大脑里「先……然后……再……最后……」的思考习惯几乎完全一致,所以特别好懂。
最贴切的比喻:泡面说明书 面向过程就是一份「泡面说明书」:① 撕开调料包 → ② 倒入开水至刻度线 → ③ 盖盖焖三分钟 → ④ 开吃。这四步的顺序神圣不可侵犯。谁要是手一抖把第 ④ 步提到第 ① 步前面,那他就会喜提一碗「干脆面配凉白开」的人间惨剧,怪不得别人。
2.1 动手盖一座「面向过程」的小房子
我们用前面学的全部工具,写一个完整的小故事,名字就叫《小明兵荒马乱的早晨》。先盘点一下要用到的零件:
一串动词(函数):起床、刷牙、吃早餐、去上班,故事里发生的每个动作。
名词(变量):小明(人物)、包子(道具),故事里的角色和东西。
形容词(布尔值):今天睡过头了吗?一个描述状态的是非题。
连词(if / else):如果迟到了就找借口,否则就准时到,故事的剧情分支。
下面这张流程图,先让你「俯瞰」整个故事的走向:四个动作从上往下依次发生,到最后撞上一个岔路口(要不要找借口),程序根据「迟到了吗」这个布尔值,决定走左边还是右边。
图 2-1 面向过程的故事线:动作顺序发生,遇到判断才分叉。
看懂了走向,再看代码就毫无压力了。注意每个函数都是一个独立的动词,而 main 就是「把这些动词按顺序串起来」的剧本:
package main
import “fmt”
func wakeUp(who string) {
fmt.Println(who, “起床了,眼睛还睁不开。”)
}
func brushTeeth(who string) {
fmt.Println(who, “刷牙,手一抖挤多了牙膏。”)
}
func eatBreakfast(who string, food string) {
fmt.Println(who, “三两口吞掉了”, food)
}
func goToWork(who string, isLate bool) {
if isLate {
fmt.Println(who, “迟到了,决定甩锅给电梯。”)
} else {
fmt.Println(who, “居然准时到了,老板感动得想哭。”)
}
}
func main() {
person := “小明” // 名词:主角
breakfast := “包子” // 名词:道具
overslept := true // 形容词:今天睡过头了
wakeUp(person) // 第一步
brushTeeth(person) // 第二步
eatBreakfast(person, breakfast) // 第三步
goToWork(person, overslept) // 第四步(带判断)
}
把这段程序跑起来,输出就是一个从上往下、规规矩矩发生的故事,和图 2-1 的走向完全一致:
小明 起床了,眼睛还睁不开。
小明 刷牙,手一抖挤多了牙膏。
小明 三两口吞掉了 包子
小明 迟到了,决定甩锅给电梯。
面向过程的优点: 简单、直接、好懂,几乎没有学习门槛。写小脚本、做一次性的数据处理、按固定流程跑一遍任务,这些场景用面向过程最舒服,没有任何多余的仪式感,想到哪写到哪,效率极高。新手的第一个程序,几乎都是面向过程的,这很正常,也很好。
2.2 循环 = 反复做的动作(让电脑替你重复)
现在小明到了公司,老板甩给他一个任务:搬 100 块砖。难道我们要把 work() 这个动作,老老实实复制粘贴一百遍吗?当然不。重复、枯燥、不动脑子的事,恰恰是电脑最擅长、而且永远不会喊累的。把「反复做同一件事」这件事交给电脑,就叫循环(loop)。
在很多语言里循环有好几种写法,但 Go 又一次把事情简化了:所有循环只用一个关键字 for。先看图,感受一下循环「转圈」的样子,做一次动作,回头问一句「还要继续吗」,要就再转一圈,不要就退出。
图 2-2 循环就是一个转圈:做一次动作 → 问还要不要继续 → 要就再来一次。
把这张图翻译成 Go 代码,就是下面这样:
// 搬 5 块砖:把同一个动作,重复 5 次
for i := 1; i <= 5; i++ {
fmt.Println(“小明搬了第”, i, “块砖”)
}
for 后面那一行用分号隔开的三段,是循环的「三件套」,把它们读成人话就一点都不可怕了:
i := 1,从哪开始:从第 1 块砖起步。i 是个帮我们数数的名词(计数器)。
i <= 5,什么时候停:只要还没数过 5,就继续。这是个形容词(布尔条件)。
i++,每轮怎么变:每搬完一块,就把计数加一(i++ 就是「i 加 1」的简写)。
连起来读:「从第 1 块开始,只要还没到第 5 块,就一直搬,每搬完一块记一笔。」是不是和你嘴里说的一模一样?
循环的本质:你只写一遍,它跑很多遍 循环最迷人的地方在于「以少驭多」:你只需要把动作描述清楚一次,电脑就能照着跑五遍、五百遍、五百万遍,代码长度却纹丝不动。处理一个班的学生成绩、一整个购物车的商品、一年的每一天,背后几乎都是循环在默默打工。
新手坑:死循环 如果你的「什么时候停」永远不会变成 false,循环就会没完没了地转下去,程序就「卡死」了,这叫死循环。最常见的原因,是忘了写「每轮怎么变」那一段(比如忘了 i++),于是 i 永远是 1,永远 <= 5,电脑就只能委屈地搬同一块砖,直到天荒地老。所以写循环时,一定要确认那个条件「迟早会被满足」。
循环是程序员的续命神器: 它帮你省下手写九十九行重复代码的时间,好让你能把这些时间,拿去写出更多更高级的 bug。这就是科技进步的意义。
2.3 把零件拼起来:小明的一周打卡
学到这里,函数、变量、布尔、if、循环,你手上的零件已经够拼一个像样的小程序了。我们让小明连续打卡七天,每天看心情决定是否准时,这一个小例子,把前面所有概念都用上了:
package main
import “fmt”
// 动词:打卡。它会「回答」一句话(返回值是 string)
func checkIn(day int, onTime bool) string {
if onTime {
return fmt.Sprintf(“第 %d 天:准时打卡,加鸡腿!”, day)
}
return fmt.Sprintf(“第 %d 天:又双叒迟到了……”, day)
}
func main() {
for day := 1; day <= 7; day++ {
onTime := day%2 == 1 // 形容词:单数天才准时(小明就这么任性)
fmt.Println(checkIn(day, onTime)) // 把循环的计数喂给动词,再打印它的回答
}
}
这段程序里,for 负责「重复七天」,checkIn 这个动词负责「每天做一次判断并回话」,if 负责「准时还是迟到」,而那个 day%2 == 1 则是个布尔表达式(% 是求余数,这里用来判断单双数)。五种零件,严丝合缝地咬在一起,这就是面向过程的全部魅力:像讲故事一样,把动作一个接一个安排明白。
但面向过程也有烦恼: 故事越写越长,你会发现每个动作都得把「小明」抱来抱去地传一遍:wakeUp(小明)、brushTeeth(小明)、checkIn(…)……小明本人看着都累,心里默默吐槽:「就不能让我自己学会打卡吗?非得每次都拎着我去?」,小明这一声呐喊,恰恰就是下一章主角「面向对象」诞生的全部理由。
第三章 面向对象:让名词自己活过来
在面向过程的世界里,名词(小明)是个有点可怜的「工具人」:它什么本事都没有,所有动作都得靠外面的人捧着它、拎着它去完成。动作和名词是分家的,动词在一边,名词在另一边,每次用都要重新「凑」到一起。
面向对象编程(Object-Oriented Programming,简称 OOP)换了一种全新的世界观:既然小明是个活生生的人,那就让他自带技能包吧。让他自己会起床、自己会刷牙、自己会上班。我们不再「站在外面对着名词喊指令」,而是让「名词自己执行属于它的动词」。一句话:把动作还给名词本人。
下面这张图,把两种世界观摆在一起对比。请重点看箭头的方向,这是理解 OOP 的关键。
图 3-1 箭头方向之差:左边动作从外部塞给名词,右边名词自己把动作发出来。
一句话看懂区别: 面向过程 = 你举着个大喇叭,对着一条狗喊「叫!快叫!」;面向对象 = 这条狗自己长了一张嘴,它想叫就叫。后者明显省心多了,而且……狗也比较有尊严,不用天天被人拿喇叭指挥。
3.1 struct = 名词的「人设档案」
要让名词活起来,第一步得先给它建一份档案:这个名词都有哪些「属性」?在 Go 里,这份档案就叫 struct(结构体)。你可以把它理解成一张「人设卡」,或者一个「文件夹」,所有属于同一个名词的信息,都整整齐齐地收进这一个盒子里,归它自己管。
我们以一只狗为例。一只狗身上有什么信息?它有名字(名词属性)、有年龄(名词属性)、有「饿不饿」这个状态(形容词属性)。把这些写进 struct,就给「狗」这个概念造好了模板:
type Dog struct {
Name string // 名词属性:名字
Age int // 名词属性:年龄
IsHungry bool // 形容词属性:饿不饿
}
模板 ≠ 实物,这点很重要: type Dog struct 定义的只是「狗的设计图」,它本身还不是一条具体的狗,就像「身份证表格」不是某个具体的人。设计图只有一份,但你可以照着它造出无数条不同的狗,一条叫旺财、一条叫来福、一条叫小黑。这个「照着图造出真东西」的动作,叫做实例化,我们马上就会用到。
3.2 method = 绑在名词身上的动词
光有档案还不够,那只是一堆静态的资料。我们要让狗会「做事」,就得给它配上专属的动作。这种「专门属于某一个名词的函数」,有个专门的名字,叫 method(方法)。
写法上,method 和普通函数几乎长得一模一样,唯一的区别是:在 func 和函数名之间,多插了一个小括号 (d Dog)。这个小括号是整段代码的灵魂,它在郑重声明:「我这个动作,是绑在 Dog 身上的,是狗专属的技能。」
// (d Dog) 表示:这个「叫」的动作,是属于狗的专属技能
func (d Dog) Bark() {
fmt.Println(d.Name, “汪汪汪!”)
}
// 形容词(IsHungry)照旧用连词 if 来决定动作怎么演
func (d Dog) Eat(food string) {
if d.IsHungry {
fmt.Println(d.Name, “狼吞虎咽地吃了”, food)
} else {
fmt.Println(d.Name, “看都不看一眼”, food)
}
}
语法彩蛋(这个真的好记): (d Dog) 里那个小小的 d,你就把它读成中文的「它」。于是 d.Name 就是「它的名字」,d.IsHungry 就是「它饿不饿」。这样一来,method 念出来其实全是大白话:「它,叫一声」「它,把饭吃了」。你看,OOP 一点都不玄乎,它只是在帮名词说「它自己」的事。
3.3 把它跑起来:一条叫旺财的狗
现在我们照着 Dog 这张设计图,造出一条真正的狗,然后让它自己干活。请特别留意:动作是从狗身上「点」出来的(旺财.Bark()),而不是我们从外面硬塞给一个函数。一个小小的点号「.」,就是面向对象和面向过程最直观的分水岭。
下面这张图,把「设计图」和「造出来的实物」放在一起:左边是 Dog 模板(包含属性区和方法区),右边是照着它造出来的旺财,最下面是旺财自己调用动作的样子。
图 3-2 左边是设计图(struct + method),右边是照图造出的实例「旺财」。
对照着图,再看完整代码就一目了然了:
package main
import “fmt”
type Dog struct {
Name string
Age int
IsHungry bool
}
func (d Dog) Bark() {
fmt.Println(d.Name, “汪汪汪!”)
}
func (d Dog) Eat(food string) {
if d.IsHungry {
fmt.Println(d.Name, “狼吞虎咽地吃了”, food)
} else {
fmt.Println(d.Name, “看都不看一眼”, food)
}
}
func main() {
// 照着设计图,造一条真狗(这一步叫实例化)
wangcai := Dog{Name: “旺财”, Age: 3, IsHungry: true}
wangcai.Bark() // 旺财自己叫,不用我们拿喇叭喊
wangcai.Eat(“骨头”) // 旺财自己吃
}
运行结果:
旺财 汪汪汪!
旺财 狼吞虎咽地吃了 骨头
一个点号,两种世界观: 面向过程会写成 bark(wangcai),读作「我,对着旺财,喊叫」,动作的主语是「我」;面向对象写成 wangcai.Bark(),读作「旺财,叫」,动作的主语是旺财自己。一个点号 . 的差别,背后是「谁是主角」的根本不同。当你的程序里有几十、几百个名词各自忙活时,让它们各自管好自己,会比你一个人拿喇叭指挥全场轻松太多了。
3.4 封装:哪些事让外人看,哪些自己藏着
活生生的东西都有隐私,对象也一样。面向对象里有个重要概念叫封装(Encapsulation):把一部分内部细节藏起来,只对外暴露该暴露的,避免外人乱碰乱改。这就像你家的房子,客厅可以请客人进来坐,但卧室的抽屉,你大概率会关上。
封装这件事,很多语言要用 public、private 一堆关键字来折腾。Go 又一次把它简化到了可爱的程度,只看首字母的大小写就行:大写开头 = 对外公开,小写开头 = 自己私藏。规则就这么一条,没有第二条。
type Dog struct {
Name string // 大写 N 开头:公开,谁都能看到它叫啥
secretBone int // 小写 s 开头:私藏,它到底埋了几根骨头,不告诉外人
}
为什么要藏起来? 因为有些数据一旦被外面随便乱改,整个对象就乱套了。把它们设成「私藏」,外人就只能通过你提供的、安全的「正门」(公开方法)来打交道,不能翻窗进来乱动。这是在给你的代码上一把锁,让它更结实、更不容易出 bug。
封装的人话版: 大写开头 = 发朋友圈,全世界可见,生怕别人不知道;小写开头 = 仅自己可见的私密日记,打死不外传。所以啊,你的「微信步数」可以大写开头公开,但你的「银行卡余额」最好老老实实用小写开头,懂的都懂。
3.5 组合:名词里面,装着别的名词
现实里的名词,常常「拥有」别的名词:一个人有一条狗,一辆车有一个引擎,一支队伍有好几名队员。面向对象把这种「拥有」关系处理得很优雅,直接把一个 struct,放进另一个 struct 里面。这就叫组合(Composition)。
type Person struct {
Name string
Pet Dog // 一个人「拥有」一条狗:把整个 Dog 装进来
}
func main() {
xiaoming := Person{
Name: “小明”,
Pet: Dog{Name: “旺财”, IsHungry: true},
}
fmt.Println(xiaoming.Name, “的狗要叫了:”)
xiaoming.Pet.Bark() // 顺着「点」找下去:小明的→宠物的→叫
}
xiaoming.Pet.Bark() 这一串点号,读起来就是一句完整的话:「小明,的宠物,叫一声。」一层一层顺下去,清清楚楚。狗还是那条会自己叫的狗,只不过现在它「住进了」小明这个更大的名词里。
Go 为什么偏爱组合? 别的语言里常有个叫「继承」的机制,喜欢说「猫是一种动物,所以猫继承动物」。Go 基本不玩这套,它更喜欢用组合说「小明有一条狗」。理由还是那个老实人哲学:「拥有一个」比「是一种的子类」好懂太多了。一条经验法则,能用「有一个」讲清的关系,就别硬绕成「是一种」。
3.6 接口:只看「会不会」,不看「是不是」
最后一个概念有点「高级」,但它其实非常美,也非常好懂。前面我们一直在关心一个名词「是什么」,是狗?是猫?接口(interface)彻底换了个角度,它根本不在乎你的身份,只问你一件事:「你会不会做某件事?」
打个最贴切的比方:墙上的插座,从不过问你是手机、电风扇还是电饭煲。它只认一件事,你有没有那个能插进来的插头。只要插头对得上,它就给你供电。接口,就是代码世界里的这种「插座标准」:它定义一个「能力清单」,谁满足清单,谁就能插进来用。
看图最直观:我们定义一个叫「会叫的(Barker)」的接口,入会条件只有一个,你得有 Bark() 这个方法。于是狗、猫、甚至门铃,这三个八竿子打不着的东西,只要都会「叫」,就统统算「会叫的」,可以被一视同仁地使用。
图 3-3 接口只看能力:谁实现了 Bark(),谁就是「会叫的」,跟它到底是不是动物无关。
// 定义一个「能力标准」:凡是会 Bark() 的,都算「会叫的」
type Barker interface {
Bark()
}
// 这个动词不挑身份,只要你是「会叫的」,递进来就行
func makeNoise(b Barker) {
b.Bark()
}
func main() {
makeNoise(Dog{Name: “旺财”}) // 狗会叫,能用
makeNoise(Cat{Name: “咪咪”}) // 猫也会叫,照样能用
}
接口的好处:让代码「不挑食」 有了接口,makeNoise 这个函数就不用关心「到底来的是狗还是猫」,它只管喊「叫一个」。以后你再新增一种「会叫的」东西(比如门铃、闹钟),完全不用改 makeNoise 一个字,这让程序像搭积木一样好扩展。这也是大型项目离不开接口的原因:它把「谁来做」和「做什么」彻底解耦开了。
接口 = 代码界的「英雄不问出处」: 它不在乎你是名校科班还是自学成才,只看你能不能把活干漂亮(有没有实现那个方法)。能交出 Bark(),你就是合格的「会叫的」。可惜现实里的招聘,远没有接口这么豁达。
第四章 面向过程 vs 面向对象:一张表看懂
学到这里,你手里已经有两把锤子了。但请记住一句话:没有哪把锤子绝对更好,只有「这颗钉子用哪把更顺手」。 把面向对象捧上天、把面向过程贬得一文不值,是新手最常见的误会。下面这张表,帮你一眼看清两者的性格差异。
| 对比项 | 面向过程 | 面向对象 | | — | — | — | | 核心思路 | 按步骤讲一个故事 | 让每个名词自带技能 | | 动作怎么发起 | 函数(名词),从外面喊 | 名词.动作(),它自己做 | | 生活比喻 | 泡面说明书,照着念 | 养宠物,它自己会喘气 | | 怎么复用 | 把动作抽成函数,到处调 | struct + 方法 + 接口,整套搬 | | 最适合 | 小脚本、一次性任务 | 大项目、很多同类东西 | | 代码长大后 | 容易乱成一锅粥 | 各管各的,相对清爽 | | 上手难度 | 低,想到就能写 | 略高,要先搭骨架 |
4.1 那我到底该用哪个?
别纠结,记住下面这几条朴素的判断标准,绝大多数情况都够用了:
东西少、逻辑简单、写完就扔的小活儿,用面向过程,别给自己加戏,三行能搞定的事就别开发布会。
场景里有一大堆「同类东西」(一群狗、一堆用户、成千上万个订单),用面向对象,让它们各自管好自己的数据和行为,你会省心非常多。
多个「长得不一样、但都会做某件事」的东西要被统一对待,上接口,让代码不挑食、好扩展。
Go 最贴心的地方在于:它不逼你选边站。 你完全可以先用面向过程把程序跑起来,等它慢慢长大、变复杂了,再悄悄给关键的名词加上 struct 和方法,平滑地升级到面向对象。没有人会半夜跳出来罚你。
给新手的学习顺序建议: 先把第一、二章练到「闭着眼睛能写」,函数、变量、if、循环,这些是地基,地基不牢,学再多花样都站不稳。等你能用面向过程顺手写出小程序了,再回头啃第三章的 struct 和方法。至于接口,第一遍看个大概印象就行,等你真的遇到「好多东西想被统一对待」的麻烦时,再回来看它,你会瞬间「啊哈」。
最后的忠告(新手必看): 刚学会面向对象的人,最容易犯一个「用力过猛」的错:为了一个只有三行的小脚本,硬是套上五个 struct、十个 interface、外加一堆设计模式,把自己感动得热泪盈眶,结果别人看半天看不懂。请永远记住:能用一句话讲清楚的事,就别开新闻发布会。简单,才是真正的高级。
结语:你已经会「说」代码了
回头看看这一路,你其实做成了不少事。你把一句普通的中文句子,翻译成了一段能跑起来的 Go 程序;你用「讲故事」的方式,写出了完整的面向过程程序,还学会了用循环偷懒;你又让原本被动的名词活了过来,给它建档案、配技能、上锁、装进更大的名词里,甚至让一堆不同的东西因为「会做同一件事」而被平等对待。而支撑这一切的,不是死记硬背的术语,而是一张你打娘胎里就开始练习的语法对照表。
所以请你一定相信:编程从来不是什么高不可攀的黑魔法。它只是「把一件事讲清楚」的另一种方式而已。以后再遇到一段看不懂的代码,先别慌,在心里把它翻译成一句人话,谁(名词)在什么状态下(形容词),做了什么动作(动词),又因为遇到了什么岔路而做了不同的选择(连词),这件事要不要反复做(循环)。一翻译,往往就豁然开朗了。
送你一句压箱底的话: 会讲故事的人,天生就有写代码的天赋,剩下的全都只是熟练度的问题。所以别怕,去多写、多翻译、多犯错。哪怕是写错的代码,它也照样会运行,它只是忠实地执行了一个你没预料到的故事而已。调试的过程,就是你和电脑一起把这个跑偏的故事,重新讲圆的过程。
临别彩蛋(揭晓答案): 现在,回到前言里那个冷笑话:Oct 31 == Dec 25。秘密在于进制,八进制(Octal)里的 31,换算成十进制恰好等于 25;而 Oct 是「十月」,Dec 是「十二月」。于是「十月 31 号」真的等于「十二月 25 号」,万圣节就这么跟圣诞节划上了等号。现在笑出来了吗?笑出来,就说明你真的出师了。
关于作者
钟智强,「哪吒网络安全」。长期专注于网络安全与编程教育,坚信最好的技术讲解,是把复杂的东西说成连外行都能会心一笑的大白话。如果这本小书让你对编程少了一分恐惧、多了一分好奇,那它就没白写。
Go语言 #编程创新 #自学编程 #程序员入门 #哪吒网络安全
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:哪吒网络安全 钟智强 钟智强《用人话学编程, 把语法当代码,把句子当程序》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论