教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 go-面向对象编程(上)

go-面向对象编程(上)

发布时间:2022-01-14   编辑:jiaochengji.com
教程集为您提供go-面向对象编程(上)等资源,欢迎您收藏本站,我们将为您提供最新的go-面向对象编程(上)资源
<h2>一个程序就是一个世界,有很多对象(变量)</h2> <h3>Golang 语言面向对象编程说明</h3> <ol><li>

Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对
象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。

</li> <li>

Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可
以理解 Golang 是基于 struct 来实现 OOP 特性的。

</li> <li>

Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函
数、隐藏的 this 指针等等

</li> <li>

Golang 仍然有面向对象编程的 继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不
一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。

</li> <li>

Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口
(interface)关联,耦合性低,也非常灵活。也就是说在 Golang 中面
向接口编程是非常重要的特性。

</li> </ol><h3>结构体与结构体变量(实例/对象)的关系</h3> <ol><li>将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。</li> <li>通过这个结构体,我们可以创建多个变量(实例/对象)</li> <li>事物可以猫类,也可以是 Person , Fish 或是某个工具类。。。</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) //定义一个Cat结构体,将Cat的各个字段/属性信息,放入到Cat结构体进行管理 type Cat struct { Name string Age int Color string Hobby string Scores [3]int // 字段是数组... } func main() { // 张老太养了20只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花, // 今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字, // 年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。 // //1. 使用变量的处理 // var cat1Name string = "小白" // var cat1Age int = 3 // var cat1Color string = "白色" // var cat2Name string = "小花" // var cat2Age int = 100 // var cat2Color string = "花色" // //2. 使用数组解决 // var catNames [2]string = [...]string{"小白", "小花"} // var catAges [2]int = [...]int{3, 100} // var catColors [2]string = [...]string{"白色", "花色"} // //... map[string]string // fmt.Println("ok") // 使用struct来完成案例 // 创建一个Cat的变量 var cat1 Cat // var a int fmt.Printf("cat1的地址=%p\n", &cat1) cat1.Name = "小白" cat1.Age = 3 cat1.Color = "白色" cat1.Hobby = "吃<・)))><<" fmt.Println("cat1=", cat1) fmt.Println("猫猫的信息如下:") fmt.Println("name=", cat1.Name) fmt.Println("Age=", cat1.Age) fmt.Println("color=", cat1.Color) fmt.Println("hobby=", cat1.Hobby) } </code></code></pre> <h3>结构体和结构体变量(实例)的区别和联系</h3>

通过上面的案例和讲解我们可以看出:

<ol><li>结构体是自定义的数据类型,代表一类事物.</li> <li>结构体变量(实例)是具体的,实际的,代表一个具体变量
结构体变量(实例)在内存的布局(重要!) </li> </ol><h2>如何声明结构体</h2>

基本语法

<pre><code class="lang-go hljs">type 结构体名称 struct { field1 type field2 type } //举例: type Student struct { Name string //字段 Age int //字段 Score float32 } </code></code></pre> <h3>字段/属性</h3>

基本介绍

<ol><li>从概念或叫法上看: 结构体字段 = 属性 = field (即授课中,统一叫字段)</li> <li>字段是结构体的一个组成部分,一般是 基本数据类型、 数组,也可是 引用类型。比如我们前面定
义猫结构体 的 Name string 就是属性</li> </ol>

注意事项和细节说明

<ol><li>字段声明语法同变量,示例:字段名 字段类型</li> <li>字段的类型可以为:基本类型、数组或引用类型</li> <li>在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的
一样:
布尔类型是 false ,数值是 0 ,字符串是 ""。
数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
指针,slice ,和 map 是 的零值都是 nil ,即还没有分配空间。
4)不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个, 结构体
是值类型。</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) //如果结构体的字段类型是: 指针,slice,和map的零值都是 nil ,即还没有分配空间 //如果需要使用这样的字段,需要先make,才能使用. type Person struct{ Name string Age int Scores [5]float64 ptr *int //指针 slice []int //切片 map1 map[string]string //map } type Monster struct{ Name string Age int } func main() { //定义结构体变量 var p1 Person fmt.Println(p1) if p1.ptr == nil { fmt.Println("ok1") } if p1.slice == nil { fmt.Println("ok2") } if p1.map1 == nil { fmt.Println("ok3") } //使用slice, 再次说明,一定要make p1.slice = make([]int, 10) p1.slice[0] = 100 //ok //使用map, 一定要先make p1.map1 = make(map[string]string) p1.map1["key1"] = "tom~" fmt.Println(p1) //不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改, //不影响另外一个, 结构体是值类型 var monster1 Monster monster1.Name = "牛魔王" monster1.Age = 500 monster2 := monster1 //结构体是值类型,默认为值拷贝 monster2.Name = "青牛精" fmt.Println("monster1=", monster1) //monster1= {牛魔王 500} fmt.Println("monster2=", monster2) //monster2= {青牛精 500} } </code></code></pre> <h3>创建结构体变量和访问结构体字段</h3> <h4>方式 1-直接声明</h4>

案例演示: var person Person
前面我们已经说了。

<h4>方式 2-{}</h4>

案例演示: var person Person = Person{}

<h4>方式 3-&</h4>

案例: var person *Person = new (Person)

<h4>方式 4-{}</h4>

案例: var person *Person = &Person{}

<h4>说明:</h4> <ol><li>第 3 种和第 4 种方式返回的是 结构体指针。</li> <li>结构体指针访问字段的标准方式应该是:(<em>结构体指针).字段名 ,比如 (</em>person).Name = "tom"</li> <li>但 go 做了一个简化,持 也支持 结构体指针. 字段名, 比如 person.Name = "tom"。更加符合程序员
使用的习惯,go 层 编译器底层 对 对 person.Name 化 做了转化 (*person).Name</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) type Person struct{ Name string Age int } func main() { //方式1 //方式2 p2 := Person{"mary", 20} // p2.Name = "tom" // p2.Age = 18 fmt.Println(p2) //方式3-& //案例: var person *Person = new (Person) var p3 *Person= new(Person) //因为p3是一个指针,因此标准的给字段赋值方式 //(*p3).Name = "smith" 也可以这样写 p3.Name = "smith" //原因: go的设计者 为了程序员使用方便,底层会对 p3.Name = "smith" 进行处理 //会给 p3 加上 取值运算 (*p3).Name = "smith" (*p3).Name = "smith" p3.Name = "john" // (*p3).Age = 30 p3.Age = 100 fmt.Println(*p3) //方式4-{} //案例: var person *Person = &Person{} //下面的语句,也可以直接给字符赋值 //var person *Person = &Person{"mary", 60} var person *Person = &Person{} //因为person 是一个指针,因此标准的访问字段的方法 // (*person).Name = "scott" // go的设计者为了程序员使用方便,也可以 person.Name = "scott" // 原因和上面一样,底层会对 person.Name = "scott" 进行处理, 会加上 (*person) (*person).Name = "scott" person.Name = "scott~~" (*person).Age = 88 person.Age = 10 fmt.Println(*person) } </code></code></pre> <h3>struct 类型的内存分配机制</h3>

<h3>结构体使用注意事项和细节</h3> <ol><li>结构体的所有字段在 内存中是连续的</li> </ol><pre><code class="lang-go hljs">package main import "fmt" //结构体 type Point struct { x int y int } //结构体 type Rect struct { leftUp, rightDown Point } //结构体 type Rect2 struct { leftUp, rightDown *Point } func main() { r1 := Rect{Point{1,2}, Point{3,4}} //r1有四个int, 在内存中是连续分布 //打印地址 fmt.Printf("r1.leftUp.x 地址=%p r1.leftUp.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p \n", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y) //r2有两个 *Point类型,这个两个*Point类型的本身地址也是连续的, //但是他们指向的地址不一定是连续 r2 := Rect2{&Point{10,20}, &Point{30,40}} //打印地址 fmt.Printf("r2.leftUp 本身地址=%p r2.rightDown 本身地址=%p \n", &r2.leftUp, &r2.rightDown) //他们指向的地址不一定是连续..., 这个要看系统在运行时是如何分配 fmt.Printf("r2.leftUp 指向地址=%p r2.rightDown 指向地址=%p \n", r2.leftUp, r2.rightDown) } </code></code></pre> <ol start="2"><li>结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类
型)</li> <li>结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转</li> <li>struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是 序
列化和反序列化</li> </ol><pre><code class="lang-go hljs">package main import "fmt" import "encoding/json" type A struct { Num int } type B struct { Num int } type Monster struct{ Name string `json:"name"` // `json:"name"` 就是 struct tag Age int `json:"age"` Skill string `json:"skill"` } func main() { var a A var b B a.Num=1 b.Num=2 a = A(b) // ? 可以转换,但是有要求,就是结构体的的字段要完全一样(包括:名字、个数和类型!) fmt.Println(a, b) //1. 创建一个Monster变量 monster := Monster{"牛魔王", 500, "芭蕉扇~"} //2. 将monster变量序列化为 json格式字串 // json.Marshal 函数中使用反射,反射时,详细介绍 jsonStr, err := json.Marshal(monster) if err != nil { fmt.Println("json 处理错误 ", err) } fmt.Println("jsonStr", string(jsonStr)) } </code></code></pre> <h2>方法</h2> <h3>基本介绍</h3>

在某些情况下,我们要需要声明(定义)方法。比如 Person 结构体:除了有一些字段外( 年龄,姓
名..),Person 结构体还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用方法才能完成。
Golang 中的方法是 作用在指定的数据类型上的(即:和指定的数据类型绑定),因此 自定义类型,
都可以有方法,而不仅仅是 struct。

<h3>方法的声明和调用</h3> <pre><code class="lang-go hljs">typeAstruct { Num int } func (aA) test() { fmt.Println(a.Num) } </code></code></pre>

** 对上面的语法的说明**

<ol><li>func (aA) test() {} 表示 A 结构体有一方法,方法名为 test</li> <li>(aA) 体现 test 方法是和 A 类型绑定的</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) type Person struct { Name string } //函数 //对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然 func test01(p Person) { fmt.Println(p.Name) } func test02(p *Person) { fmt.Println(p.Name) } //对于方法(如struct的方法), //接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以 func (p Person) test03() { p.Name = "jack" fmt.Println("test03() =", p.Name) // jack } func (p *Person) test04() { p.Name = "mary" fmt.Println("test03() =", p.Name) // mary } func main() { p := Person{"tom"} test01(p) test02(&p) p.test03() fmt.Println("main() p.name=", p.Name) // tom (&p).test03() // 从形式上是传入地址,但是本质仍然是值拷贝 fmt.Println("main() p.name=", p.Name) // tom (&p).test04() fmt.Println("main() p.name=", p.Name) // mary p.test04() // 等价 (&p).test04 , 从形式上是传入值类型,但是本质仍然是地址拷贝 } </code></code></pre>

对上面的总结

<ol><li>test 方法和 Person 类型绑定</li> <li>test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调
用</li> <li>func (p Person) test() {}... p 表示哪个 Person 变量调用,这个 p 就是它的副本, 这点和函数传参非
常相似。</li> <li>p 这个名字,有程序员指定,不是固定, 比如修改成 person 也是可以</li> </ol><h3>方法快速入门</h3> <ol><li>给 Person 结构体添加 speak 方法,输出 xxx 是一个好人</li> <li>给 Person 结构体添加 jisuan 方法,可以计算从 1 .. 1000 的结果, 说明方法体内可以函数一样,
进行各种运算</li> <li>给 Person 结构体 jisuan2 方法,该方法可以接收一个数 n,计算从 1 .. n 的结果</li> <li>给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果</li> <li>方法的调用</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) type Person struct{ Name string } //给Person结构体添加speak 方法,输出 xxx是一个好人 func (p Person) speak() { fmt.Println(p.Name, "是一个goodman~") } //给Person结构体添加jisuan 方法,可以计算从 1 .. 1000的结果, //说明方法体内可以函数一样,进行各种运算 func (p Person) jisuan() { res := 0 for i := 1; i <= 1000; i { res = i } fmt.Println(p.Name, "计算的结果是=", res) } //给Person结构体jisuan2 方法,该方法可以接收一个参数n,计算从 1 .. n 的结果 func (p Person) jisuan2(n int) { res := 0 for i := 1; i <= n; i { res = i } fmt.Println(p.Name, "计算的结果是=", res) } //给Person结构体添加getSum方法,可以计算两个数的和,并返回结果 func (p Person) getSum(n1 int, n2 int) int { return n1 n2 } //给Person类型绑定一方法 func (person Person) test() { person.Name = "jack" fmt.Println("test() name=", person.Name) // 输出jack } type Dog struct { } func main() { var p Person p.Name = "tom" p.test() //调用方法 fmt.Println("main() p.Name=", p.Name) //输出 tom //下面的使用方式都是错误的 // var dog Dog // dog.test() // test() //调用方法 p.speak() p.jisuan() p.jisuan2(20) n1 := 10 n2 := 20 res := p.getSum(n1, n2) fmt.Println("res=", res) } </code></code></pre> <h3>方法的调用和传参机制原理:(重要!)</h3> <h4>说明:</h4>

方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做
实参也传递给方法。下面我们举例说明。

<h4>说明:</h4> <ol><li>在通过一个变量去调用方法时,其调用机制和函数一样</li> <li>不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类
型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)</li> </ol><h4>案例 2</h4>

请编写一个程序,要求如下:

<ol><li>声明一个结构体 Circle, 字段为 radius</li> <li>声明一个方法 area 和 Circle 绑定,可以返回面积。</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) type Circle struct { radius float64 } //2)声明一个方法area和Circle绑定,可以返回面积。 func (c Circle) area() float64 { return 3.14 * c.radius * c.radius } //为了提高效率,通常我们方法和结构体的指针类型绑定 func (c *Circle) area2() float64 { //因为 c是指针,因此我们标准的访问其字段的方式是 (*c).radius //return 3.14 * (*c).radius * (*c).radius // (*c).radius 等价 c.radius fmt.Printf("c 是 *Circle 指向的地址=%p", c) c.radius = 10 return 3.14 * c.radius * c.radius } func main() { // 1)声明一个结构体Circle, 字段为 radius // 2)声明一个方法area和Circle绑定,可以返回面积。 // 3)提示:画出area执行过程 说明 //创建一个Circle 变量 // var c Circle // c.radius = 4.0 // res := c.area() // fmt.Println("面积是=", res) //创建一个Circle 变量 var c Circle fmt.Printf("main c 结构体变量地址 =%p\n", &c) c.radius = 7.0 //res2 := (&c).area2() //编译器底层做了优化 (&c).area2() 等价 c.area() //因为编译器会自动的给加上 &c res2 := c.area2() fmt.Println("面积=", res2) fmt.Println("c.radius = ", c.radius) //10 } </code></code></pre> <h3>方法的声明(定义)</h3> <pre><code class="lang-go hljs">func (recevier type) methodName(参数列表) (返回值列表){ 方法体 return 返回值 } </code></code></pre> <ol><li>参数列表:表示方法输入</li> <li>recevier type : 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型</li> <li>receiver type : type 可以是结构体,也可以其它的自定义类型</li> <li>receiver : 就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)</li> <li>返回值列表:表示返回的值,可以多个</li> <li>方法主体:表示为了 实现某一功能代码块</li> <li>return 语句不是必须的。</li> </ol><h3>方法的注意事项和细节</h3> <ol><li>结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式</li> <li>如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理</li> <li>Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,
都可以有方法,而不仅仅是 struct, 比如 int , float32 等都可以有方法</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) /* Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是struct, 比如int , float32等都可以有方法 */ type integer int func (i integer) print() { fmt.Println("i=", i) } //编写一个方法,可以改变i的值 func (i *integer) change() { *i = *i 1 } type Student struct { Name string Age int } //给*Student实现方法String() func (stu *Student) String() string { str := fmt.Sprintf("Name=[%v] Age=[%v]", stu.Name, stu.Age) return str } func main() { var i integer = 10 i.print() i.change() fmt.Println("i=", i) //定义一个Student变量 stu := Student{ Name : "tom", Age : 20, } //如果你实现了 *Student 类型的 String方法,就会自动调用 fmt.Println(&stu) } </code></code></pre> <h3>几个小例子</h3>

1)编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个 10<em>8 的矩形,
在 main 方法中调用该方法
2)编写一个方法,提供 m 和 n 两个参数,方法中打印一个 m</em>n 的矩形

<ol start="3"><li>编写一个方法算该矩形的面积(可以接收长 len,和宽 width), 将其作为方法返回值。在 main
方法中调用该方法,接收返回的面积值并打印。</li> <li>编写方法:判断一个数是奇数还是偶数</li> <li>根据行、列、字符打印 对应行数和列数的字符,比如:行:3,列:2,字符*,则打印相应的效
果</li> <li>定义小小计算器结构体(Calcuator),实现加减乘除四个功能
实现形式 1:分四个方法完成:
实现形式 2:用一个方法搞定</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) type MethodUtils struct { //字段... } //给MethodUtils编写方法 func (mu MethodUtils) Print() { for i := 1; i <= 10; i { for j := 1; j <= 8; j { fmt.Print("*") } fmt.Println() } } //2)编写一个方法,提供m和n两个参数,方法中打印一个m*n的矩形 func (mu MethodUtils) Print2(m int, n int) { for i := 1; i <= m; i { for j := 1; j <= n; j { fmt.Print("*") } fmt.Println() } } /* 编写一个方法算该矩形的面积(可以接收长len,和宽width), 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印 */ func (mu MethodUtils) area(len float64, width float64) (float64) { return len * width } /* 编写方法:判断一个数是奇数还是偶数 */ func (mu *MethodUtils) JudgeNum(num int) { if num % 2 == 0 { fmt.Println(num, "是偶数..") } else { fmt.Println(num, "是奇数..") } } /* 根据行、列、字符打印 对应行数和列数的字符, 比如:行:3,列:2,字符*,则打印相应的效果 */ func (mu *MethodUtils) Print3(n int, m int, key string) { for i := 1; i <= n ; i { for j := 1; j <= m; j { fmt.Print(key) } fmt.Println() } } /* 定义小小计算器结构体(Calcuator), 实现加减乘除四个功能 实现形式1:分四个方法完成: , 分别计算 - * / 实现形式2:用一个方法搞定, 需要接收两个数,还有一个运算符 */ //实现形式1 type Calcuator struct{ Num1 float64 Num2 float64 } func (calcuator *Calcuator) getSum() float64 { return calcuator.Num1 calcuator.Num2 } func (calcuator *Calcuator) getSub() float64 { return calcuator.Num1 - calcuator.Num2 } //.. //实现形式2 func (calcuator *Calcuator) getRes(operator byte) float64 { res := 0.0 switch operator { case ' ': res = calcuator.Num1 calcuator.Num2 case '-': res = calcuator.Num1 - calcuator.Num2 case '*': res = calcuator.Num1 * calcuator.Num2 case '/': res = calcuator.Num1 / calcuator.Num2 default: fmt.Println("运算符输入有误...") } return res } func main() { /* 1)编写结构体(MethodUtils),编程一个方法,方法不需要参数, 在方法中打印一个10*8 的矩形,在main方法中调用该方法。 */ var mu MethodUtils mu.Print() fmt.Println() mu.Print2(5, 20) areaRes := mu.area(2.5, 8.7) fmt.Println() fmt.Println("面积为=", areaRes) mu.JudgeNum(11) mu.Print3(7, 20, "@") //测试一下: var calcuator Calcuator calcuator.Num1 = 1.2 calcuator.Num2 = 2.2 fmt.Printf("sum=%v\n", fmt.Sprintf("%.2f",calcuator.getSum())) fmt.Printf("sub=%v\n",fmt.Sprintf("%.2f",calcuator.getSub())) res := calcuator.getRes('*') fmt.Println("res=", res) } </code></code></pre> <h3>方法和函数区别</h3> <ol><li>调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)</li> <li>对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然</li> <li>对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反
过来同样也可以</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) type Person struct { Name string } //函数 //对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然 func test01(p Person) { fmt.Println(p.Name) } func test02(p *Person) { fmt.Println(p.Name) } //对于方法(如struct的方法), //接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以 func (p Person) test03() { p.Name = "jack" fmt.Println("test03() =", p.Name) // jack } func (p *Person) test04() { p.Name = "mary" fmt.Println("test03() =", p.Name) // mary } func main() { p := Person{"tom"} test01(p) test02(&p) p.test03() fmt.Println("main() p.name=", p.Name) // tom (&p).test03() // 从形式上是传入地址,但是本质仍然是值拷贝 fmt.Println("main() p.name=", p.Name) // tom (&p).test04() fmt.Println("main() p.name=", p.Name) // mary p.test04() // 等价 (&p).test04 , 从形式上是传入值类型,但是本质仍然是地址拷贝 } </code></code></pre>

总结:

<ol><li>不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.</li> <li>如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则
是地址拷贝。</li> </ol><h3>面向对象编程应用实例</h3> <h4>步骤</h4> <ol><li>声明(定义)结构体,确定结构体名</li> <li>编写结构体的字段</li> <li>编写结构体的方法</li> </ol><h4>学生案例:</h4> <ol><li>编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、
int、float64 类型。</li> <li>结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。</li> <li>在 main 方法中,创建 Student 结构体实例(变量),并访问 say 方法,并将调用结果打印输出。</li> <li>走代码</li> </ol><pre><code class="lang-go hljs">import ( "fmt" ) /* 学生案例: 编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、int、 float64 类型。 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。 在 main 方法中,创建 Student 结构体实例(变量),并访问 say 方法,并将调用结果打印输出。 */ type Student struct { name string gender string age int id int score float64 } func (student *Student) say() string { infoStr := fmt.Sprintf("student 的信息 name=[%v] gender=[%v], age=[%v] id=[%v] score=[%v]", student.name, student.gender, student.age, student.id, student.score) return infoStr } func main() { //测试 //创建一个 Student 实例变量 var stu = Student{ name : "tom", gender : "male", age : 18, id : 1000, score : 99.98, } fmt.Println(stu.say()) } </code></code></pre> <h3>盒子案例</h3> <ol><li>编程创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终
端获取</li> <li>声明一个方法获取立方体的体积。</li> <li>创建一个 Box 结构体变量,打印给定尺寸的立方体的体积</li> <li>走代码</li> </ol><h3>景区门票案例</h3> <ol><li>一个景区根据游人的年龄收取不同价格的门票,比如年龄大于 18,收费 20 元,其它情况门票免
费.</li> <li>请编写 Visitor 结构体,根据年龄段决定能够购买的门票价格并输出</li> <li>代码:</li> </ol><pre><code class="lang-go hljs">package main import ( "fmt" ) /* 学生案例: 编写一个Student结构体,包含name、gender、age、id、score字段,分别为string、string、int、int、float64类型。 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值。 在main方法中,创建Student结构体实例(变量),并访问say方法,并将调用结果打印输出。 */ type Student struct { name string gender string age int id int score float64 } func (student *Student) say() string { infoStr := fmt.Sprintf("student的信息 name=[%v] gender=[%v], age=[%v] id=[%v] score=[%v]", student.name, student.gender, student.age, student.id, student.score) return infoStr } /* 1)编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取 2)声明一个方法获取立方体的体积。 3)创建一个Box结构体变量,打印给定尺寸的立方体的体积 */ type Box struct { len float64 width float64 height float64 } //声明一个方法获取立方体的体积 func (box *Box) getVolumn() float64 { return box.len * box.width * box.height } // 景区门票案例 // 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于18,收费20元,其它情况门票免费. // 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出 type Visitor struct { Name string Age int } func (visitor *Visitor) showPrice() { if visitor.Age >= 90 || visitor.Age <=8 { fmt.Println("考虑到安全,就不要玩了") return } if visitor.Age > 18 { fmt.Printf("游客的名字为 %v 年龄为 %v 收费20元 \n", visitor.Name, visitor.Age) } else { fmt.Printf("游客的名字为 %v 年龄为 %v 免费 \n", visitor.Name, visitor.Age) } } func main() { //测试 //创建一个Student实例变量 var stu = Student{ name : "tom", gender : "male", age : 18, id : 1000, score : 99.98, } fmt.Println(stu.say()) //测试代码 var box Box box.len = 1.1 box.width = 2.0 box.height = 3.0 volumn := box.getVolumn() fmt.Printf("体积为=%.2f", volumn) //测试 var v Visitor for { fmt.Println("请输入你的名字") fmt.Scanln(&v.Name) if v.Name == "n" { fmt.Println("退出程序....") break } fmt.Println("请输入你的年龄") fmt.Scanln(&v.Age) v.showPrice() } } </code></code></pre> <h3>创建结构体变量时指定字段值</h3>

说明
Golang 在创建结构体实例(变量)时,可以直接指定字段的值

<pre><code class="lang-go hljs">package main import ( "fmt" ) type Stu struct { Name string Age int } func main() { //方式1 //在创建结构体变量时,就直接指定字段的值 var stu1 = Stu{"小明", 19} // stu1---> 结构体数据空间 stu2 := Stu{"小明~", 20} //在创建结构体变量时,把字段名和字段值写在一起, 这种写法,就不依赖字段的定义顺序. var stu3 = Stu{ Name :"jack", Age : 20, } stu4 := Stu{ Age : 30, Name : "mary", } fmt.Println(stu1, stu2, stu3, stu4) //方式2, 返回结构体的指针类型(!!!) var stu5 *Stu = &Stu{"小王", 29} // stu5--> 地址 ---》 结构体数据[xxxx,xxx] stu6 := &Stu{"小王~", 39} //在创建结构体指针变量时,把字段名和字段值写在一起, 这种写法,就不依赖字段的定义顺序. var stu7 = &Stu{ Name : "小李", Age :49, } stu8 := &Stu{ Age :59, Name : "小李~", } fmt.Println(*stu5, *stu6, *stu7, *stu8) // } </code></code></pre> <h3>工厂模式</h3>

Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。

<h4>看一个需求</h4>

一个结构体的声明是这样的:
package model
type Student struct {
Name string...
}
因为这里的 Student 的首字母 S 是大写的,如果我们想在其它包创建 Student 的实例(比如 main 包),
引入 model 包后,就可以直接创建 Student 结构体的变量(实例)。 但是问题来了 , 如果首字母是小写的 ,
如 比如 是 是 type student struct {....} 就不不行了,怎么办---> 工厂模式来解决.

<h4>工厂模式来解决问题</h4>

使用工厂模式实现跨包创建结构体实例(变量)的案例:
如果 model 包的 结构体变量首字母大写,引入后,直接使用, 没有问题
如果 model 包的 结构体变量首字母小写,引入后,不能直接使用, 可以 工厂模式解决, 看老师演
示, 代码:
student.go

<pre><code class="lang-go hljs">package model //定义一个结构体 type student struct{ Name string score float64 } //因为student结构体首字母是小写,因此是只能在model使用 //我们通过工厂模式来解决 func NewStudent(n string, s float64) *student { return &student{ Name : n, score : s, } } //如果score字段首字母小写,则,在其它包不可以直接方法,我们可以提供一个方法 func (s *student) GetScore() float64{ return s.score //ok } </code></code></pre>

main.go

<pre><code class="lang-go hljs">package main import ( "fmt" "go_code/chapter10/factory/model" ) func main() { //创建要给Student实例 // var stu = model.Student{ // Name :"tom", // Score : 78.9, // } //定student结构体是首字母小写,我们可以通过工厂模式来解决 var stu = model.NewStudent("tom~", 98.8) fmt.Println(*stu) //&{....} fmt.Println("name=", stu.Name, " score=", stu.GetScore()) } </code></code></pre>
到此这篇关于“go-面向对象编程(上)”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
go语言和python哪个难
【Go语言入门系列】(八)Go语言是不是面向对象语言?
golang是面向对象吗_Go是面向对象的吗?
龙芯平台构建Go语言环境指南
关于Golang的介绍
Go语言发展历史、核心、特性及学习路线
Go 语言设计模式系列之一 ——Go 语言中的面向对象
go 语言学习历程
php面向对象和面向过程有什么区别
go是面向对象语言吗?

[关闭]
~ ~