最新要闻
- 吕钱浩:苹果2026年可能会推真全面屏iPhone 那个时候中兴已是第7代了
- 焦点快报!博主阿秋准备坐地铁游香港:晒证件自证是90后
- 环球快资讯:手机换号伤不起!当代人换手机号的成本有多高?
- 百事通!iPhone 17 Pro将是最完美iPhone!果粉还要等两年时间
- 苦是一种享受!董明珠回应仍做手机:格力一些部件必靠进口 国产质量不达标
- 冷水江市气象局发布大风蓝色预警【Ⅳ级/一般】
- 自称从清华退学考北大博主删除视频:此前收获50万点赞 快速涨粉
- 全球新资讯:2999元国产显卡抢疯了 Steam游戏实测能玩 老黄旧将打造
- 当前快看:实拍成都6-7级大风来袭:狂风呼啸树折腰 行人雨伞秒被吹翻
- 邹磊委员:推进“风光水火储”多能互补综合能源供给体系建设
- 国内已超越GPS 北斗卫星今年再发三颗星:下一代已在路上
- 世界观点:称根据身体来划分性别比较好 女星桥本爱被LGBT攻击
- 【独家】女生月薪两万辞职考研八次失败:次次铩羽而归
- 环球今热点:SSD比机械硬盘更容易坏?实测来了:跟想象中不一样
- 全球观天下!2月轿车销量排名:两“逸”同病相怜、秦汉一马平川
- 通讯!姚劲波代表:高学历干家政可能会越发普遍
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球观焦点:Go 面向对象
- 面向对象介绍
- 匿名字段
- 什么是继承?
- 指针类型匿名字段
- 多重继承
- 方法
- 什么是封装?
- 方法创建
- 接口
- 接口定义
- 空接口
- 类型断言
- 综合示例
- 接口作为函数参数
- 接口嵌套
面向对象介绍
面向对象和面向过程都是解决问题的一种思路。
(相关资料图)
面向过程:
- 是一种以过程为中心的编程思想,实现功能的每一步都是自己实现的。面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,尽量忽略面向对象的复杂语法,即面向过程是
“强调做什么,而不是以什么形式去做”
。 - 开发过程的思路是将数据与方法按照执行的逻辑顺序组织在一起,数据与方法分开考虑,也就是拿数据做操作。
- 是一种以过程为中心的编程思想,实现功能的每一步都是自己实现的。面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,尽量忽略面向对象的复杂语法,即面向过程是
面向对象:
- 是一种以对象为中心的编程思想,通过指挥对象实现具体的功能,
强调“必须通过对象的形式来做事情”
。 - 将数据(成员变量)与功能(成员方法)绑定到一起,进行封装,以增强代码的模块化和重用性,这样能够减少重复代码的编写过程,提高开发效率。
- 是一种以对象为中心的编程思想,通过指挥对象实现具体的功能,
Go 中的“面向对象”
在 Go 中没有对象这一说法,因为 Go 是一个面向过程的语言。但是我们又知道面向对象在开发中的便捷性,所以在 Go 中有了结构体这一类型。
在结构体中可以添加属性(成员)和方法(函数)。
匿名字段
什么是继承?
继承是面向对象三大特征之一(封装、继承、多态),可以使得子类具有父类的属性和方法,还可以在子类中重新定义以及追加属性和方法。
虽然在 GO 中是没有继承概念的,但可以通过“匿名字段(也叫匿名组合)”来实现继承的效果。
// 父类type Person struct { name string age int}// 子类type Student struct { Person // 匿名字段 score int}func main() { // 顺序初始化 s1 := Student{Person{"xiaoming", 18}, 97} fmt.Printf("%+v\n", s1) // {Person:{name:xiaoming age:18} score:97} // 指定成员初始化 s2 := Student{Person: Person{name: "xiaohua"}, score: 97} fmt.Printf("%+v\n", s2) // {Person:{name:xiaohua age:0} score:97} // 操作成员 s1.age = 19 // 子类直接拥有父类的字段 s2.Person.age = 18 fmt.Printf("%p\n", &s2.Person) // 0x11004130 s2.Person = Person{name: "xiaohua2"} fmt.Printf("%p\n", &s2.Person) // 0x11004130 仍是同一内存空间}
若父类和子类存在同名字段
,那么操作时采用就近原则
:如果能够在自己对象所属的类(结构体)中找到对应的成员,那么直接进行操作;如果找不到就去对应的父类(结构体)中查找。
指针类型匿名字段
结构体(类)中的匿名字段的类型,也可以是指针。
type Person struct { name string age int}type Student struct { *Person // 匿名字段为指针类型 score int}func main() { s1 := Student{&Person{"xiaoming", 18}, 97} // 此时Person存的是地址 fmt.Printf("%+v\n", s1) // {Person:0x11004110 score:97} fmt.Println(s1.name, s1.age, s1.score) // xiaoming 18 97 /* var s2 Student s2.name = "xiaohua" // 报错:panic: runtime error: invalid memory address or nil pointer dereference 原因: *Person还没有指向任何的内存地址,那么其默认值为nil,所以对象s2也就无法操作Person中的成员 解决方案: s2 := new(Person) // new()分配了内存空间并赋值给s2 s2.name = "xiaohua" */}
多重继承
多重继承指的是一个类可以继承另外一个类,而另外一个类又可以继承别的类,比如 A 类继承 B 类,而 B 类又可以继承 C 类。
Person score int}func main() { // 对象初始化 s1 := Student{Person{Object{name: "xiaoming"}, 18}, 97} // 成员操作 s1.Person.Object.name = "xiaoming2" s1.name = "xiaoming3" fmt.Printf("name=%+v\n", s1) // name={Person:{Object:{name:xiaoming3} age:18} score:97}}
方法
什么是封装?
封装的概述:
- 封装是面向对象三大特征之一(封装、继承、多态)。
- 封装是指将对象的属性和行为进行包装,不需要让外界知道具体的实现细节。
封装的原则:
- 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
封装的好处:
- 通过方法来控制成员变量的操作,提高了代码的安全性。
- 用方法将代码进行封装,提高了代码的复用性。
而在 GO 中,封装是通过方法来实现的。
方法创建
方法是一类特殊的函数,是绑定在某种类型的变量上的函数,且有一定的约束范围。
其中,变量的类型不仅仅局限于结构体类型,也可以是任意类型,如 int、bool 等的别名类型。
即一个结构体加上绑定在这个类型上的方法,就等价于一个类。
// 函数定义func 函数名(args) 返回类型// 方法定义func (recv recv_type) 函数名(args) 返回类型
- recv 表示【接收器】,是某种类型(recv_type)的变量。
- 这个接收器的类型可以是普通变量或者指针变量。
只有指针接收者类型的方法,才能修改这个接收器的原成员值;而非指针接收者,方法修改的只是这个传入的指针接收者的一个拷贝
。- 类型的定义和绑定在它上面的方法的定义可以不放置在同一个文件中,它们可以存在在不同的源文件,唯一的要求是:它们必须在同一个包下面。
import ( "fmt" "strconv")type Student struct { name string age int}func (s Student) GetInfo() string { fmt.Printf("%s在GetInfo方法中的内存地址:%p\n", s.name, &s) return "name=" + s.name + ", age=" + strconv.Itoa(s.age)}func (s *Student) SetAge(age int) { fmt.Printf("%s在SetAge方法中的内存地址:%p\n", s.name, s) s.age = age}func main() { s1 := Student{"xiaoming", 18} info1 := s1.GetInfo() fmt.Printf("%s在main中的内存地址:%p\n", s1.name, &s1) // 对于非指针接收器,它是拷贝原来的数据 // xiaoming在GetInfo方法中的内存地址:0x11004120 // xiaoming在main中的内存地址:0x11004110 fmt.Println("info1: ", info1) // info1: name=xiaoming, age=18 s2 := &s1 s2.SetAge(17) fmt.Printf("%s在main中的内存地址:%p\n", s2.name, s2) // 对于指针接收器,可以直接拿到原数据所在的内存地址,也就是说可以直接修改原来的数值 // xiaoming在SetAge方法中的内存地址:0x11004110 // xiaoming在main中的内存地址:0x11004110 // xiaoming在GetInfo方法中的内存地址:0x11004140 fmt.Println("s2.info: ", s2.GetInfo())}
注意:不管是指针接收者还是非指针接收者,他在接受一个对象的时候,会自动将这个对象转换为这个方法所需要的类型。也就是说,如果现在有一个非指针类型的对象,去调用一个指针接收者的方法,那么这个对象将会自动被【取地址】然后再被调用。换句话说,方法的调用类型不重要,重要的是方法是怎么【定义】的。
接口
接口是一种规范,一种约定。
在 GO 语言中,任何类型的数据只要实现了一个接口中的所有方法集,那么他就属于这个接口类型。所以,当我们在实现一个接口的时候,需要实现这个接口下的所有方法,否则编译将不能通过。
接口定义
接口定义:
type 接口名 interface { 方法1(参数) 返回类型 方法2(参数) 返回类型 ...}
接口使用示例:
// 定义接口type Action interface { Dark() Eat()}// 再定义多接口type Look interface { Color(color string) string}// 定义结构体type Dog struct { name string}type Cat struct { name string}// 定义方法来实现接口(方法名,参数,返回类型,必须和接口中所定义的完全一致)// 方法中的接收者,就是调用这个方法的对象类型func (dog Dog) Dark() { fmt.Printf("%s汪汪叫\n", dog.name)}func (dog Dog) Eat() { fmt.Printf("%s喜欢吃狗粮\n", dog.name)}func (dog Dog) Color(color string) string { return dog.name + "是" + color + "肤色"}func (cat Cat) Dark() { fmt.Printf("%s喵喵叫\n", cat.name)}func (cat Cat) Eat() { fmt.Printf("%s喜欢吃猫粮\n", cat.name)}func (cat Cat) Color(color string) string { return cat.name + "是" + color + "肤色"}func main() { dog := Dog{"旺财"} cat := Cat{"苗苗"} // 在调用接口的时候,需要先声明这个接口类型的变量 var a1 Action // 也可以用自动推导 a1 := dog var a2 Action var l1 Look var l2 Look // 然后把实现了这个方法的接收器对象赋值给这个变量 a1 = dog a2 = cat l1 = dog l2 = cat // 随后就可以用接口变量来调用已实现的方法 a1.Dark() // 旺财汪汪叫 a1.Eat() // 旺财喜欢吃狗粮 a2.Dark() // 苗苗喵喵叫 a2.Eat() // 苗苗喜欢吃猫粮 fmt.Println(l1.Color("黄色")) // 旺财是黄色肤色 fmt.Println(l2.Color("蓝色")) // 苗苗是蓝色肤色}
空接口
没有任何方法声明的接口称之为空接口:interface{}
所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。
Go 很多库的源代码都会以空接口作为参数,表示接受任意类型的参数,比如 fmt 包下的 Print 系列函数
func Println(a ...interface{}) (n int, err error)func Print(a ...interface{}) (n int, err error)// 自定义函数func Test(arg ...interface{}) {} // 表示可以接收任意个数和类型的参数
空接口使用示例:
func main() { var any interface{} any = 10 fmt.Println(any) // 10 any = "golang" fmt.Println(any) // golang any = true fmt.Println(any) // true}
类型断言
先看一个例子:
func main() { var a int = 1 var i interface{} = a var b int = i}
运行结果:
./prog.go:6:14: cannot use i (variable of type interface{}) as type int in variable declaration: need type assertion
编译报错,不能将 interface{} 类型的变量 i 赋值给整型变量 b
结论:可以将任意类型的变量赋值给空接口 interface{} 类型,但是反过来不行。
那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?这时就可以用到类型断言。类型断言(Type Assertion)用来检查接口变量值是否实现了某个接口,或者是否等于某个具体类型
。
// 断言的一般格式value, ok := x.(T) // x为接口类型,ok为bool类型
- 若 T 是具体类型,则类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的值。
- 若 T 是接口类型,则类型断言会检查 x 的动态类型是否满足 T。如果检查成功,返回值是 T 接口类型。
- 若 T 是空接口,则断言失败。
示例1:类型断言
func main() { var x interface{} x = 8 val, ok := x.(int) fmt.Printf("val is %d, ok is %t\n", val, ok) // val is 8, ok is true val2, ok := x.(string) fmt.Printf("val is %s, ok is %t\n", val2, ok) // val is , ok is false x = "go" val3, ok := x.(int) fmt.Printf("val is %d, ok is %t\n", val3, ok) // val is 0, ok is false}
示例2:类型查询
type Student struct { name string id int}func main() { i := make([]interface{}, 3) i[0] = 1 // int i[1] = "hello go" // string i[2] = Student{"mike", 666} // Student for index, data := range i { switch value := data.(type) { // 类型查询 case int: fmt.Printf("x[%d] 类型为int, 内容为%d\n", index, value) case string: fmt.Printf("x[%d] 类型为string, 内容为%s\n", index, value) case Student: fmt.Printf("x[%d] 类型为Student, 内容为name = %s, id = %d\n", index, value.name, value.id) } }}
综合示例
实现计算器的加减法功能
package mainimport "fmt"// 定义父类type Numbers struct { num1 float64 num2 float64}// 定义子类 加法type Add struct { Numbers}// 定义子类 减法type Sub struct { Numbers}// 定义计算器的接口type Calculator interface { CheckData(args ...interface{}) bool // 数据校验 OperateData() float64 // 完成计算}// 加法类实现接口func (add *Add) CheckData(args ...interface{}) bool { if len(args) != 2 { fmt.Println("Error, need two params.") return false } if _, ok := args[0].(float64); !ok { fmt.Println("Error, need float param1.") return false } if _, ok := args[1].(float64); !ok { fmt.Println("Error, need float param2.") return false } // 赋值num1和num2 add.num1 = args[0].(float64) add.num2 = args[1].(float64) // 为什么要用 .(float64) ? // 由于args是接口类型,接口类型只有成员方法,没有成员变量, // 若要修改某接口的具体成员变量的值,要先通过类型断言将这个接口类型做一层转换,转换为具体实现类型 return true}// 完成加法操作func (add *Add) OperateData() float64 { return add.num1 + add.num2}// 减法类实现接口func (sub *Sub) CheckData(args ...interface{}) bool { // 空接口表示接收任意类型 if len(args) != 2 { fmt.Println("Error, need two params.") return false } if _, ok := args[0].(float64); !ok { fmt.Println("Error, need float param1.") return false } if _, ok := args[1].(float64); !ok { fmt.Println("Error, need float param2.") return false } // 赋值num1和num2 sub.num1 = args[0].(float64) sub.num2 = args[1].(float64) return true}// 完成减法操作func (sub *Sub) OperateData() float64 { return sub.num1 - sub.num2}// 定义工厂类type CalcFactory struct {}// 创建加法类对象,返回指针类型func NewAdd() *Add { return new(Add)}// 创建减法类对象,返回指针类型func NewSub() *Sub { return new(Sub)}// 根据入参opType创建出不同的对象func (f *CalcFactory) CreateOperation(opType string) Calculator { var c Calculator switch opType { case "+": c = NewAdd() case "-": c = NewSub() default: panic("Error! this operator doesn"t exist.") } return c}func main() { factory := new(CalcFactory) op := factory.CreateOperation("+") op.CheckData(1.12, 3.14) fmt.Println("加法结果:", op.OperateData()) op2 := factory.CreateOperation("-") op2.CheckData(7.12, 3.14) fmt.Println("减法结果:", op2.OperateData())}
接口作为函数参数
在函数定义时,若形参为接口类型,那么在函数调用时,实参需为该接口的具体实现。
type Calculer interface { Add() int}type Operator struct { num1 int num2 int}func (o *Operator) Add() int { return o.num1 + o.num2}func DoCal(c Calculer) { fmt.Println("计算结果为:", c.Add())}func main() { o := &Operator{1, 4} // 如果方法的接口形参是指针类型,那实参只能传接口指针类型 // 如果接口形参是普通类型,那实参可传接口普通类型或接口指针类型 DoCal(o)}
接口嵌套
接口嵌套就是一个接口中又包含了其它接口。此时若要实现外部接口,则需要把内部嵌套接口对应的所有方法也一同实现。
type A interface { run1()}type B interface { run2()}// 定义嵌套接口Ctype C interface { A B run3()}type Runner struct {} // 实现嵌套接口A的方法func (r Runner ) run1() { fmt.Println("run1!!!!")}// 实现嵌套接口B的方法func (r Runner ) run2() { fmt.Println("run2!!!!")}func (r Runner ) run3() { fmt.Println("run3!!!!")}func main() { var runner C runner = new(Runner) // runner实现了C接口的所有方法 runner.run1() runner.run2() runner.run3()}
关键词:
全球观焦点:Go 面向对象
全球快播:Vue——Vue初始化【三】
吕钱浩:苹果2026年可能会推真全面屏iPhone 那个时候中兴已是第7代了
焦点快报!博主阿秋准备坐地铁游香港:晒证件自证是90后
环球快资讯:手机换号伤不起!当代人换手机号的成本有多高?
百事通!iPhone 17 Pro将是最完美iPhone!果粉还要等两年时间
苦是一种享受!董明珠回应仍做手机:格力一些部件必靠进口 国产质量不达标
冷水江市气象局发布大风蓝色预警【Ⅳ级/一般】
自称从清华退学考北大博主删除视频:此前收获50万点赞 快速涨粉
世界热议:PMP项目变更管理及变更流程总结
利用CSS使博客园图片自动居中,而文字保持居左
天天热文:量化交易基础 - 012 - 检验中的假设条件
全球新资讯:2999元国产显卡抢疯了 Steam游戏实测能玩 老黄旧将打造
当前快看:实拍成都6-7级大风来袭:狂风呼啸树折腰 行人雨伞秒被吹翻
邹磊委员:推进“风光水火储”多能互补综合能源供给体系建设
Log4j 配置
每日速递:flink入门-流处理
全球消息!汉诺塔问题——分而治之(引入递归,解决重复子问题)
世界球精选!Java基础入门-数组练习
国内已超越GPS 北斗卫星今年再发三颗星:下一代已在路上
世界观点:称根据身体来划分性别比较好 女星桥本爱被LGBT攻击
【独家】女生月薪两万辞职考研八次失败:次次铩羽而归
全球新资讯:第128篇:浏览器存储(cookie、webStorage、 IndexedDB)
世界简讯:关于使用python脚本将同级的其他目录下的所有文件根据年份移动到当脚本位置的年份目录
环球今热点:SSD比机械硬盘更容易坏?实测来了:跟想象中不一样
全球观天下!2月轿车销量排名:两“逸”同病相怜、秦汉一马平川
通讯!姚劲波代表:高学历干家政可能会越发普遍
《庆余年2》冲上热搜榜:网曝郭麒麟演的角色范思辙换人了
资讯:shiro-550反序列化漏洞分析
资讯:64.异常
世界热文:[新媒体运营]新媒体运营概述
9名初中生花18元点18串里脊庆生 网友看哭:回不去的年华
今日报丨金钱豹回拜游客 动物园回应:没人教、第一次见
世界今日报丨隅田川进口胶囊咖啡液8杯到手10.9元:更鲜更好喝
有赞一面:还有任务没执行,线程池被关闭怎么办?
全球快看点丨注解与反射
java基础二-面向对象的三大特性
全球最资讯丨A卡有能力跟RTX 4090正面刚 AMD:不做是因为太贵、功耗高
微头条丨深山红烛 照亮希望
环球新消息丨Redis 深度学习
环球快资讯:前端设计模式——策略模式
新消息丨MySQL学习笔记-SQL实践1
每日快报!安全性不行 Win7系统真的别用了 又一个游戏《堡垒之夜》放弃支持
每日快播:童年回来了!《灌篮高手》发布流川枫角色海报
每日报道:使用web client对 vcenter 进行补丁升级
有监督学习——梯度下降
全球百事通!聪明的燕姿
当前视点!Oracle数据库中没有scott账户的方法
今日热闻!光速破发?iPhone 14黄色版还未开售便降价600元
华硕发布ROG Strix Impact III鼠标:双手通用、可更换微动插槽
真香!重庆购买HUAWEI问界M7直补3万:起售仅25.98万
当前简讯:滴滴快车预付车费什么时候退款_滴滴快车预付车费什么意思
世界通讯!网络安全(中职组)-B模块:Web安全应用-2
今日播报!客服回应疯狂小杨哥带货翻车:有巡查、违规会处罚
怕了吗?长城公布“1000万悬赏计划”:严厉打击网络水军
【世界新要闻】四位计数器testbench的设计
世界滚动:chatgpt 集成飞书实践指南
自以为是 与 思考
天天通讯!伊朗外长:伊沙恢复外交关系将为两国和地区发展注入巨大动能
世界快讯:日系车掀起买一送一热潮:买皓影插混送飞度、买日产楼兰送轩逸
天天热资讯!50万以内最舒适二排 理想L7正式交付:31.98万起售
环球新资讯:10年分红1000多亿!董明珠鼓励员工“砸锅卖铁”买格力股票
剪刀石头布的算法
资讯推荐:全球最强!传音发布260W有线、110无线快充:8分钟充满
世界观热点:美国科学家改变人类科技、终极能源即将实现?真相来了!
世界快讯:《原子之心》更新:添加FOV设置、删除种族主义动画
Shell命令-常用操作2
【质因数分解算法详解】C/Java/Go/Python/JS/Dart/Swift/Rust等不同语言实现
今亮点!霸道女总裁加盟《碟中谍8》
天天播报:00后男生每天下班后卖烤肠解压 50元投资日赚200元:厌恶刷视频、打游戏
【时快讯】网络安全(中职组)-B模块:Web安全渗透测试
环球新消息丨K8S 性能优化-K8S Node 参数调优
环球微速讯:比进口价格便宜!海南三亚榴莲今年6月将大规模上市
当废物挺好?委员称年轻人想躺平更多是调侃 奋斗的是大多数
天天亮点!中国足彩网竞彩11日推荐:曼城取胜无悬念
焦点短讯!使用SSM+Shiro+Layui框架,基于RBAC3模型开发的权限管理系统
snort入侵检测基础概述
当前播报:amusements
环球热议:6月上映!《变形金刚7》角色海报发布:擎天柱、猩猩队长亮相
【速看料】降价潮席卷全国 车企尽数参战是为何?乘联会解答:国六B要来了
观热点:01-C语言概述
环球今热点:真正油电同价!比亚迪投放“深水炸弹”:13.4万买宋Pro DM-i超级混动
环球通讯!立春来首场寒潮横扫我国大部:多地将遭遇滑梯式降温 最高降20℃
【新要闻】活久见!女生家中发现神奇圆柱形手机:登QQ、手电筒、拍照 功能多到炸
读Java性能权威指南(第2版)笔记13_堆内存下
最便宜竖折叠继任者!摩托罗拉Razr 2023真机图出炉:首次拼色后壳
当前快报:汽车价格战新进展:南北大众同日入局 丰田买一辆送一辆
世界速递!day05-功能实现04
Vue————Vue v2.7.14 入口文件【二】
【时快讯】《满江红》中国影史票房榜第6:力压《唐人街探案3》 票房突破45.23亿
环球即时:2023开门红!长四丙成功发射“一箭双星”
环球消息!第一批PCIe 5.0 SSD都是残血!14GB/s满血版还早呢
世界快资讯丨有了ChatGPT 动动嘴就能使唤Excel:我的童年梦想实现了
每日热门:8岁男孩单手打破汉诺塔世界纪录:4.305秒搞定4层
当前头条:海绵宝宝卡通图片线条图_海绵宝宝卡通图片
天生要完美电视剧28集完整版_天生要完美电视剧
对C++做爬虫的代码进行简单分析
世界热推荐:2.HelloSpring
孙海洋夫妇餐饮公司被列经营异常:本人回应
今日报丨香港男子深圳上班每天通勤4小时:月薪3万 每天通勤费用80元