go-面向对象编程(上)
一个程序就是一个世界,有很多对象(变量)
Golang 语言面向对象编程说明
- Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对 
 象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
- Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可 
 以理解 Golang 是基于 struct 来实现 OOP 特性的。
- Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函 
 数、隐藏的 this 指针等等
- Golang 仍然有面向对象编程的 继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不 
 一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。
- Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口 
 (interface)关联,耦合性低,也非常灵活。也就是说在 Golang 中面
 向接口编程是非常重要的特性。
结构体与结构体变量(实例/对象)的关系
- 将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。
- 通过这个结构体,我们可以创建多个变量(实例/对象)
- 事物可以猫类,也可以是 Person , Fish 或是某个工具类。。。
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)
}
结构体和结构体变量(实例)的区别和联系
通过上面的案例和讲解我们可以看出:
- 结构体是自定义的数据类型,代表一类事物.
- 结构体变量(实例)是具体的,实际的,代表一个具体变量
 结构体变量(实例)在内存的布局(重要!)
如何声明结构体
基本语法
type 结构体名称 struct {
field1 type
field2 type
}
 //举例:
type Student struct {
Name string //字段
Age int //字段
Score float32
}
字段/属性
基本介绍
- 从概念或叫法上看: 结构体字段 = 属性 = field (即授课中,统一叫字段)
- 字段是结构体的一个组成部分,一般是 基本数据类型、 数组,也可是 引用类型。比如我们前面定
 义猫结构体 的 Name string 就是属性
注意事项和细节说明
- 字段声明语法同变量,示例:字段名 字段类型
- 字段的类型可以为:基本类型、数组或引用类型
- 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的
 一样:
 布尔类型是 false ,数值是 0 ,字符串是 ""。
 数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
 指针,slice ,和 map 是 的零值都是 nil ,即还没有分配空间。
 4)不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个, 结构体
 是值类型。
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}
}
创建结构体变量和访问结构体字段
方式 1-直接声明
案例演示: var person Person
前面我们已经说了。
方式 2-{}
案例演示: var person Person = Person{}
方式 3-&
案例: var person *Person = new (Person)
方式 4-{}
案例: var person *Person = &Person{}
说明:
- 第 3 种和第 4 种方式返回的是 结构体指针。
- 结构体指针访问字段的标准方式应该是:(结构体指针).字段名 ,比如 (person).Name = "tom"
- 但 go 做了一个简化,持 也支持 结构体指针. 字段名, 比如 person.Name = "tom"。更加符合程序员
 使用的习惯,go 层 编译器底层 对 对 person.Name 化 做了转化 (*person).Name
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)
}
struct 类型的内存分配机制
略
结构体使用注意事项和细节
- 结构体的所有字段在 内存中是连续的
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)
}
- 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类
 型)
- 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
- struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是 序
 列化和反序列化
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))
}
方法
基本介绍
在某些情况下,我们要需要声明(定义)方法。比如 Person 结构体:除了有一些字段外( 年龄,姓
名..),Person 结构体还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用方法才能完成。
Golang 中的方法是 作用在指定的数据类型上的(即:和指定的数据类型绑定),因此 自定义类型,
都可以有方法,而不仅仅是 struct。
方法的声明和调用
typeAstruct {
Num int
}
func (aA) test() {
fmt.Println(a.Num)
}
** 对上面的语法的说明**
- func (aA) test() {} 表示 A 结构体有一方法,方法名为 test
- (aA) 体现 test 方法是和 A 类型绑定的
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 , 从形式上是传入值类型,但是本质仍然是地址拷贝
}
对上面的总结
- test 方法和 Person 类型绑定
- test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调
 用
- func (p Person) test() {}... p 表示哪个 Person 变量调用,这个 p 就是它的副本, 这点和函数传参非
 常相似。
- p 这个名字,有程序员指定,不是固定, 比如修改成 person 也是可以
方法快速入门
- 给 Person 结构体添加 speak 方法,输出 xxx 是一个好人
- 给 Person 结构体添加 jisuan 方法,可以计算从 1+..+1000 的结果, 说明方法体内可以函数一样,
 进行各种运算
- 给 Person 结构体 jisuan2 方法,该方法可以接收一个数 n,计算从 1+..+n 的结果
- 给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果
- 方法的调用
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)
}
方法的调用和传参机制原理:(重要!)
说明:
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做
实参也传递给方法。下面我们举例说明。
说明:
- 在通过一个变量去调用方法时,其调用机制和函数一样
- 不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类
 型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)
案例 2
请编写一个程序,要求如下:
- 声明一个结构体 Circle, 字段为 radius
- 声明一个方法 area 和 Circle 绑定,可以返回面积。
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
}
方法的声明(定义)
func (recevier type) methodName(参数列表) (返回值列表){
方法体
return 返回值
}
- 参数列表:表示方法输入
- recevier type : 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
- receiver type : type 可以是结构体,也可以其它的自定义类型
- receiver : 就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)
- 返回值列表:表示返回的值,可以多个
- 方法主体:表示为了 实现某一功能代码块
- return 语句不是必须的。
方法的注意事项和细节
- 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
- 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
- Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,
 都可以有方法,而不仅仅是 struct, 比如 int , float32 等都可以有方法
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)
}
几个小例子
1)编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个 108 的矩形,
在 main 方法中调用该方法
2)编写一个方法,提供 m 和 n 两个参数,方法中打印一个 mn 的矩形
3) 编写一个方法算该矩形的面积(可以接收长 len,和宽 width), 将其作为方法返回值。在 main
方法中调用该方法,接收返回的面积值并打印。
4) 编写方法:判断一个数是奇数还是偶数
5) 根据行、列、字符打印 对应行数和列数的字符,比如:行:3,列:2,字符*,则打印相应的效
果
6) 定义小小计算器结构体(Calcuator),实现加减乘除四个功能
实现形式 1:分四个方法完成:
实现形式 2:用一个方法搞定
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)
}
方法和函数区别
- 调用方式不一样
 函数的调用方式: 函数名(实参列表)
 方法的调用方式: 变量.方法名(实参列表)
- 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
- 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反
 过来同样也可以
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 , 从形式上是传入值类型,但是本质仍然是地址拷贝
}
总结:
- 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
- 如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则
 是地址拷贝。
面向对象编程应用实例
步骤
- 声明(定义)结构体,确定结构体名
- 编写结构体的字段
- 编写结构体的方法
学生案例:
- 编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、
 int、float64 类型。
- 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。
- 在 main 方法中,创建 Student 结构体实例(变量),并访问 say 方法,并将调用结果打印输出。
- 走代码
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())
}
盒子案例
- 编程创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终
 端获取
- 声明一个方法获取立方体的体积。
- 创建一个 Box 结构体变量,打印给定尺寸的立方体的体积
- 走代码
景区门票案例
- 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于 18,收费 20 元,其它情况门票免
 费.
- 请编写 Visitor 结构体,根据年龄段决定能够购买的门票价格并输出
- 代码:
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()
	}
}
创建结构体变量时指定字段值
说明
Golang 在创建结构体实例(变量)时,可以直接指定字段的值
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) //
}
工厂模式
Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
看一个需求
一个结构体的声明是这样的:
package model
type Student struct {
Name string...
}
因为这里的 Student 的首字母 S 是大写的,如果我们想在其它包创建 Student 的实例(比如 main 包),
引入 model 包后,就可以直接创建 Student 结构体的变量(实例)。 但是问题来了 , 如果首字母是小写的 ,
如 比如 是 是 type student struct {....} 就不不行了,怎么办---> 工厂模式来解决.
工厂模式来解决问题
使用工厂模式实现跨包创建结构体实例(变量)的案例:
如果 model 包的 结构体变量首字母大写,引入后,直接使用, 没有问题
如果 model 包的 结构体变量首字母小写,引入后,不能直接使用, 可以 工厂模式解决, 看老师演
示, 代码:
student.go
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
}
main.go
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())
}
go-面向对象编程(上)的更多相关文章
- python面向对象编程(上)
		面向对象编程(OOP,Object Oriented Programming)是每一个高级编程语言都支持的编程方法,比如JAVA/C++/C#等等.学习面向对象编程是每一个程序员都绕不开的重点内容. ... 
- Go语言基础之结构体(面向对象编程上)
		1 自定义类型和类型别名 1.1 自定义类型 Go语言中可以基于一些基本的数据类型,使用type关键字定义自定义类型的数据 . 自定义类型定义了一个全新的类型,该新类型具有基本数据类型的特性.自定义类 ... 
- Python 第六篇(上):面向对象编程初级篇
		面向:过程.函数.对象: 面向过程:根据业务逻辑从上到下写垒代码! 面向过程的编程弊:每次调用的时候都的重写,代码特别长,代码重用性没有,每次增加新功能所有的代码都的修改!那有什么办法解决上面出现的弊 ... 
- sdut 在机器上面向对象编程练习11(运算符重载)
		在机器上面向对象编程练习11(运算符重载) Time Limit: 1000MS Memory limit: 65536K 标题叙述性说明 有两个矩阵a和b,均为2行3列,求两个矩阵之和.重载运算符& ... 
- Java面向对象 网络编程 上
		 Java面向对象 网络编程 上 知识概要: (1)网络模型 (2)网络通讯要素 (3)UDP TCP 概念 (4)Socket (5)UDP TCP 传输 ... 
- Python面向对象编程(上)
		Python不仅支持面向过程编程,同时也支持面向对象编程.面向工程就是分析解决问题所需的步骤,然后用函数把这些步骤逐一实现,使用的时候再一个个调用函数就可以.面向对象则是把解决的问题按照一定规则划分为 ... 
- Java编程基础阶段笔记 day 07 面向对象编程(上)
		 面向对象编程 笔记Notes 面向对象三条学习主线 面向过程 VS 面向对象 类和对象 创建对象例子 面向对象的内存分析 类的属性:成员变量 成员变量 VS 局部变量 类的方法 方法的重载 可变个 ... 
- 带你一分钟理解闭包--js面向对象编程
		上一篇<简单粗暴地理解js原型链--js面向对象编程>没想到能攒到这么多赞,实属意外.分享是个好事情,尤其是分享自己的学习感悟.所以网上关于原型链.闭包.作用域等文章多如牛毛,很多文章写得 ... 
- PHP 面向对象编程和设计模式 (1/5) - 抽象类、对象接口、instanceof 和契约式编程
		PHP高级程序设计 学习笔记 2014.06.09 什么是面向对象编程 面向对象编程(Object Oriented Programming,OOP)是一种计算机编程架构.OOP 的一条基本原则是计算 ... 
- python基础-面向对象编程
		一.三大编程范式 编程范式即编程的方法论,标识一种编程风格 三大编程范式: 1.面向过程编程 2.函数式编程 3.面向对象编程 二.编程进化论 1.编程最开始就是无组织无结构,从简单控制流中按步写指令 ... 
随机推荐
- Python的生成器和生成器表达式
			一,生成器和生成器表达式 什么是生成器,生成器实质就是迭代器,在python中有三种方式来获取生成器: 1. 通过生成器函数 和普通函数没有区别,里面有yield的函数就是生成器函数,生成器函数在执行 ... 
- 第11章   UDP:用户数据报协-----读书笔记
			1.分片应用程序只关心IP数据报的长度,如果它超过MTU值,那么就要对数据包进行分片. 2.UDP首部字段图: (16位源端口号+16位目端口号+16位UDP长度+16位UDP校验和+数据) 3.UD ... 
- opencv---(腐蚀、膨胀、边缘检测、轮廓检索、凸包、多边形拟合)
			一.腐蚀(Erode) 取符合模板的点, 用区域最小值代替中心位置值(锚点) 作用: 平滑对象边缘.弱化对象之间的连接. opencv 中相关函数:(erode) // C++ /** shape: ... 
- 目前下载VS2017你可能会遇到这个坑
			可能现在大伙都已经开始使用VS2019进行开发了.VS2019的下载使用也都很简单.由于工作需要,今天要在笔记本上安装VS2017,结果发现,VS2017的下载变得不是那么容易了,官方的下载方式也隐藏 ... 
- go语言之map
			go语言的map就相当于python的dict 1.map的初始化 //创建map //k的类型是int,v的类型是string var test25_1 map[int]string fmt.Pri ... 
- CentOS7升级OpenSSL版本
			1.CentOS7.6默认安装的openssl版本为 # 查看openssl版本 openssl version 2.下载最新的openssl wget https://www.openssl.org ... 
- Octave移动数据
			A=[1 2;3 4;5 6] size(A) 会返回(3,2) length(A) 会返回矩阵的维度 =3 pwd :查看当前Octave的路径 cd D:/... :改变Octave的 ... 
- CGAL的安装与使用
			CGAL CGAL系大名鼎鼎的计算几何算法库,采用C++语言,代码中大量使用模板,相对比较难读.可以支持float, double, CORE的高精度或者gmp等任意精度库. 安装CGAL 在Wind ... 
- 【西北师大-2108Java】第九次作业成绩汇总
			[西北师大-2108Java]第九次作业成绩汇总 作业题目 面向对象程序设计(JAVA) 第11周学习指导及要求 实验目的与要求 (1)理解泛型概念: (2)掌握泛型类的定义与使用: (3)掌握泛型方 ... 
- Re-DD-Hello
			题目地址 https://dn.jarvisoj.com/challengefiles/1.Hello.12b9bde7c0c8558a9da42aa1798cafc8 用IDA打开,找到算法函数 写 ... 
