问题:
张老太养了两只猫,一个名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序。当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只小猫
使用现有的技术来解决这个问题
1)单独的定义变量解决
代码演示
//1.使用变量的处理方式
var cat1Name string = "小白"
var cat1Age int = 3
var cat1Color string = "白色"
var cat2Name string = "小化"
var cat2Age int = 100
var cat2Color string = "花色"
2)使用数组解决
代码演示
//2.使用数组来解决
var catNames [2]string = [...]string{"小白","小花"}
var catAges [2]int = [...]int{3,100}
var catColor [2]string = [...]string{"白色","花色"}
//
现有的技术解决的缺点分析
一个程序就是一个世界,有很多对象(变量)
1)Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程的特性是比较准确的
2)Golang没有类(class),go语言的结构体(struct)和其他编程语言的类(class)有同等的地位,你可以理解Golang是基于struct来实现OOP特性的
3)Golang面向对象编程非常简洁,去掉了传统OOP语言的继承,方法重载、构造函数和析构函数、隐藏的this指针等等
4)Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他的OOP语言不一样,比如继承:Golang没有extends关键字,继承是通过匿名字段来实现
5)Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面学习就可以体会到了,也就是说在Golang中面向接口编程是非常重要的特性
简单来说就是类似与java的类和对象
对上图的说明
1)将一类事物的特性提取出来(比如猫类),形成一个新的数据类型,就是结构体
2)通过这个结构体,我们可以创建多个变量(实例对象)
3)事物可以是猫类,也可以是Person,Fish或是某个工具类
特征对象抽取图:
package main
import (
"fmt"
)
/*
张老太养了两只猫,一个名字叫小白,今年3岁,白色。
还有一只叫小花,今年100岁,花色。请编写一个程序。
当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。
如果用户输入的小猫名错误,则显示张老太没有这只小猫
*/
// func vars(){
// //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 catColor [2]string = [...]string{"白色","花色"}
// //
// }
//定义一个cat结构体,将cat的各个字段/属性放入cat结构体进行管理
type Cat struct {
Name string
Age int
Color string
Hobby string
}
func main() {
//创建一个cat结构体变量
var cat1 Cat //类似于var a int
cat1.Name = "小白"
cat1.Age = 3
cat1.Color = "白色"
cat1.Hobby = "吃<.))>>>"
fmt.Println("cat1=",cat1)
fmt.Println("猫猫的信息如下:")
fmt.Println("name=",cat1.Name,"Age=",cat1.Age,"Color=",cat1.Color)
fmt.Println("cat1.Hobby",cat1.Hobby)
//输出结果如下:
/*cat1= {小白 3 白色}
猫猫的信息如下:
name= 小白 Age= 3 Color= 白色
*/
}
4》结构体和结构体变量(实例)的区别和联系
通过上面的案例和讲解我们可以看出
1)结构体是自定义的数据类型。代表一类事物
2)结构体变量(实例)是具体的,实际的,代表一个具体变量
5》结构体变量(实例)在内存中存在的布局
var cat1 Cat
cat1.Age = 3
cat1.Color = "白色"
画出示意图
1)结构体的声明
type 结构体名称 struct {
filed1 type
filed2 type
}
//举例
//如果结构体的名称首字母为大大写,那就意味着该结构体可以被其他包使用
type Student struct {
Name string
Age int
Socre float32
}
2)字段/属性
基本介绍:
(1)从概念或叫法上看,结构体字段=属性=filed(集授课中,统一叫字段)
(2)字段是结构体的一一个组成部分,一般是基本数据类型、数组。比如我们前面定义猫结构体的Name string就是属性
3)字段和属性的细节说明
1、字段声明语法同变量,示例:字段名 字段类型
2、字段的类型可以为:基本类型、数组或引用类型
3、在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样
布尔类型 false,整型是0 ,字符是 " "
数组类型的默认值和它的元素类型相关,比如score [3]int的默认值是[0 0 0]
指针,slice和map的零值都是nil即没有分配空间(需要用make进行分配空间的操作)
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//切片
}
func main() {
//定义结构体变量
var p1 Person
fmt.Println(p1) //未赋值之前输出的是{ 0 [0 0 0 0 0] [] map[]}
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
//使用map也一定先要make操作
p1.map1 = make(map[string]string)
p1.map1["key1"] = "tom"
fmt.Println(p1)
}
4)不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另一个。结构体是值类型
//不同结构体变量的字段是独立的,互不影响,
// 一个结构体变量字段的更改,不影响另一个。结构体是值类型
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)方式1-直接声明
案例演示:var person Person
2)方式2-{}
案例演示:var person Person = Person{}
//方式2
p2 := Person{"mary",20}
// p2.Name = "Tom"
// p2.Age = 18
fmt.Println(p2)
3)方式3
案例:var person *Person = new (Person)
//方式3-8
//案例: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 = "jhon"
(*p3).Age = 30
fmt.Println(*p3)//{jhon 30}
4)方式4-{}
案例:var person *Person = &Person{}
//var person *Person = &Person{"jerry",60}写成这种形式也是对的
//方式4-{}案例:var person *Person = &Person{}
var person *Person = &Person{}
//因为person是一个指针,因此标准的访问其字段的方法是
//(*peron).Name = "scott"
//go的设计者为了方便,也可以person.Name = "scott"
//底层会进行优化操作
(*person).Name = "scott"
(*person).Age= 30
fmt.Println(*person)
说明
看看结构体在内存中的存在形式
var p4 Person
p4.Name= "小明"
p4.Age = 23
var p5 *Person = &p4
//fmt.Println(*p5.Age)这种用法是错误的
//需要将*号包起来,因为.的优先级比*的优先级更高
fmt.Println((*p5).Age)//10
fmt.Println(p5.Age) //10
p5.Name = "tom~"
fmt.Printf("p5.Name=%v p4.Name=%v \n",p5.Name,p4.Name)
fmt.Printf("p5.Name=%v p4.Name=%v \n",(*p5).Name,p4.Name)
/*
输出结果如下所示
p5.Name=tom~ p4.Name=tom~
p5.Name=tom~ p4.Name=tom~
*/
fmt.Printf("p4的地址是%p\n",&p4)
//p4的地址是0xc042056420
fmt.Printf("p5的地址是%p p5的值是%p\n",&p5,p5)
//p5的地址是0xc04207a020 p5的值是0xc042056420
其内存图分析如下
package main
import (
"fmt"
)
//结构体
type Point struct {
x int
y int
}
//结构体
type Rect struct {
leftup,rightDown Point
}
func main (){
r1 := Rect{Point{1,2},Point{3,4}}
//r1有4个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)
//输出结果如下
//r1.leftup.x 地址=0xc04200a2c0 r1.leftup.y 地址=0xc04200a2c8
// r1.rightDown.x 地址=0xc04200a2d0 r1.rightDown.y 地址=0xc04200a2d8
//r2有两个*Point类型,这两个*Point类型的本身地址也是连续的
//但是他们指向的地址不一定是连续的
r2 := Rect2{&Point{10,20}, &Point{30,40}}
fmt.Printf("r2.leftup本身地址=%p r2.rightDown 本身地址=%p \n",
&r2.leftup,&r2.rightDown)
//输出结果为:r2.leftup本身地址=0xc0420421c0 r2.rightDown 本身地址=0xc0420421c8
//他们指向的地址不一定是连续的 这个要看编译器或系统运行时而定
fmt.Printf("r2.leftup指向地址=%p r2.rightDown 指向地址=%p \n",
r2.leftup,r2.rightDown)
//r2.leftup指向地址=0xc0420120b0 r2.rightDown 指向地址=0xc0420120c0
}
内存图
2)结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字,个数和类型要完全一致)
type A struct{
Num int
}
type B struct{
Num int
}
func main(){
var a A
var b B
fmt.Println(a,b)
//a=b两个不同类型的结构体不能相互赋值操作
//进行强制转换
a = A(b) //成功,前提是两个结构体的字段名称
//,字段类型是一模一样的,其中一个不一样都不可以
fmt.Println(a,b)
}
3)结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互可以强转
type Student struct{
Name string
Age int
}
type Stu Student
func main(){
var stu1 Student
var stu2 Stu
//stu2 = stu1 //正确吗?会报错,可以这样修改 stu2=Stu(stu1)
stu2=Stu(stu1)
fmt.Println(stu1,stu2)
}
type integer int
func main(){
var i integer = 10
var j int = 20
//j = i //正确吗? 也不可以的如果要就必须强制进行转换
j = int(i)
fmt.Println(i,j)
}
4)struct的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列号和反序列化
举例:
package main
import (
"fmt"
"encoding/json"
)
type Monster struct {
Name string `json:"name"`
Age int `json:"age"`
Skill string `json:"skill"`
}
func main(){
//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))
//输出如下:jsonStr {"Name":"牛魔王","Age":500,"Skill":"芭蕉扇"}
//在字段后面加上json序列化后:
//jsonStr {"name":"牛魔王","age":500,"skill":"芭蕉扇"}
}
序列化的应用场景:
在某些情况下,我们需要声明(定义)方法,比如Person结构体,除了有一些字段外(年龄,姓名…)Person结构体还有一些行为比如:可以说话、跑步。。通过学习,还可以做算术题,这时要使用方法才可以完成
Golang中的方法是作用在指定的数据类型上的(即。和指定的数据类型绑定)因此自定义类型都可以有方法,而不仅仅是struct
type A struct{
Num int
}
func (a A)test(){
fmt.Println(a.Num)
}
对上面的语法的说明
举例说明:
package main
import (
"fmt"
)
type Person struct{
Name string
}
//给A类型绑定一个方法
func (p Person) test() {
p.Name = "jack"
fmt.Println("test()",p.Name) //jack
}
func main(){
//定义一个Person实例
var p Person
p.Name = "小红"
p.test() //调用方法
fmt.Println("main()",p.Name) //小红,这里不会因为test()方法中的操作而改变,因为struct不是引用传递而是值传递还是小红
}
test方法和Person类型绑定
test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其他类型变量来调用
下面的使用方式都是错误的
var dog Dog
dog.test()
test()
func (p Person) test() {}…p表示哪个Person变量调用,这个p就是它的副本,这点和函数传参非常相似
p这个·名字,是可以有程序员指定,不是固定的,比如修改成person也是可以的
(1)给Person结构体添加speak方法,输出xxx是一个好人
func (p Person) speak(){
fmt.Printf("%v是一个好人\n",p.Name)
}
(2)给Person结构体添加jisuan方法,可以计算从1+…+1000的结果
func (p Person) jisuan() {
var sum int = 0
for i :=1;i <=1000;i++{
sum += i
}
fmt.Printf("1+..+1000的结果是%v\n",sum)
}
(3)给Person结构体添加jisuan2方法,该方法可以接收一个数n,计算从1+…n的结果
func (p Person) jisuan2(n int) {
var sum int = 0
for i :=1;i <=n;i++{
sum += i
}
fmt.Printf("1+..+%v的结果是%v\n",n,sum)
}
(4)给Person结构体添加getSum方法,可以计算两个数的和并返回结果。
func (p Person) getsum(n1 int,n2 int) int{
return n1 + n2
}
(5)方法的调用
p.speak()
p.jisuan()
p.jisuan2(10000)
res := p.getsum(10,20)
fmt.Println("res=",res)
说明:方法的调用和传参机制和函数基本不一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。
案例1:画出前面getSum()方法的执行过程+说明
说明:
案例2:请编写一个程序,要求如下
package main
import (
"fmt"
)
/*
案例2:请编写一个程序,要求如下
1. 声明一个结构体Circle,字段为radius
2. 声明一个方法area和CIrcle绑定,可以返回面积
3. 提示画出area执行过程+说明
*/
type Circle struct {
radius float64
}
func (c Circle) area() float64 {
return 3.14 * c.radius * c.radius
}
func main(){
//创建一个结构体
var c Circle
c.radius = 4.0
area := c.area()
fmt.Printf("圆的面积为%v\n",area)
}
执行内存图:
其实这样的效率非常慢,会一对一进行值拷贝,用指针可以直接修改它就更快
func (recevier type) methodName (参数列表) (返回值列表){
方法体
return 返回值
}
结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
//为了提高效率,通常我们方法和结构体的指针类型进行绑定
func (c *Circle) area2() float64 {
//因为c是一个指针,因此我们标准访问其字段的方式是(*c)
return 3.14 * (*c).radius * (*c).radius
//(*c).radius等价为c.radius
}
func main(){
//创建一个cicle变量
var c Circle
c.radius = 5.0
//res2 := (&c).area2()
//编译底层做了优化,(&c).area2()等价c.area2()
//因为编译器会自动加上
res2 := c.area2()
fmt.Printf("圆的面积为%v\n",res2) //面积为78.5
}
如果程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
func (c *Circle) area2() float64 {
c.radius = 10
//因为c是一个指针,因此我们标准访问其字段的方式是(*c)
return 3.14 * (*c).radius * (*c).radius
//(*c).radius等价为c.radius
}
func main(){
//创建一个cicle变量
var c Circle
c.radius = 6.0
//res2 := (&c).area2()
//编译底层做了优化,(&c).area2()等价c.area2()
//因为编译器会自动加上
res2 := c.area2()
fmt.Printf("圆的面积为%v\n",res2) //314
fmt.Println("c.radius = ",c.radius) //10
}
//为了提高效率,通常我们方法和结构体的指针类型进行绑定
func (c *Circle) area2() float64 {
fmt.Printf("c是*cicle指向的地址=%p\n",c)
c.radius = 10
//因为c是一个指针,因此我们标准访问其字段的方式是(*c)
return 3.14 * (*c).radius * (*c).radius
//(*c).radius等价为c.radius
}
func main(){
//创建一个结构体
// var c Circle
// c.radius = 4.0
// area := c.area()
// fmt.Printf("圆的面积为%v\n",area)
//创建一个cicle变量
var c Circle
fmt.Printf("main的c结构体变量地址 =%p\n",&c)
c.radius = 6.0
//res2 := (&c).area2()
//编译底层做了优化,(&c).area2()等价c.area2()
//因为编译器会自动加上
res2 := c.area2()
fmt.Printf("圆的面积为%v\n",res2) //314
fmt.Println("c.radius = ",c.radius) //10
/*
运行结果如下
main的c结构体变量地址 =0xc0420120a0
c是*cicle指向的地址=0xc0420120a0
圆的面积为314
c.radius = 10
*/
}
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) ch() {
*i = *i +1
}
func main(){
var i integer = 10
i.print() //1 = 10
i.ch()
i.print() //i =11
}
方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问
如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
//定义一个Student结构体
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(){
//定义一个Student变量
stu := Student{
Name : "tom",
Age : 20,
}
//如果实现了*Student类型的String()方法会自动调用String()要传入&stu
fmt.Println(&stu)
}
编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10 *8 的矩形,在main方法中调用该方法
package main
import (
"fmt"
)
type MethodUtils struct{
//字段...
}
//给MethodUtils编写方法
func (m MethodUtils) print(){
for i :=1; i<=10;i++{
for j :=1;j<=8;j++{
fmt.Print("* ")
}
fmt.Println()
}
}
func main(){
/*
编写结构体(MethodUtils),编程一个方法,
方法不需要参数,在方法中打印一个10 *8 的矩形,
在main方法中调用该方法
*/
var m MethodUtils
m.print()
}
编写一个方法,提供m和n两个参数,方法中打印m*n的矩形
//编写一个方法,提供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()
}
}
func main(){
/*
编写结构体(MethodUtils),编程一个方法,
方法不需要参数,在方法中打印一个10 *8 的矩形,
在main方法中调用该方法
*/
var m MethodUtils
// m.print()
m.print2(3,4)
}
编写一个方法算该矩形的面积(可以接收长len和宽width),将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印
func (mu MethodUtils) area(length float32,width float32)float32{
return length * width
}
func main(){
res :=m.area(4.0,5.6)
fmt.Println("该矩形的面积为:",res)
}
编写方法,判断一个数是奇数还是偶数
func (mu *MethodUtils) qi(m int) {
if m %2==0{
fmt.Printf("%v是偶数",m)
}else{
fmt.Printf("%v是奇数",m)
}
}
根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符 * ,则打印相应的效果
func (mu *MethodUtils) printe(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:分4个方法完成
//实现形式1:
type Calcuator struct{
Num1 float64
Num2 float64
}
//求和
func (calcuator *Calcuator) getSum() float64{
return calcuator.Num1 +calcuator.Num2
}
//求差
func (calcuator *Calcuator) getJian() float64{
return calcuator.Num1 -calcuator.Num2
}
//求积
func (calcuator *Calcuator) getJi() float64{
return calcuator.Num1 *calcuator.Num2
}
//求商
func (calcuator *Calcuator) getShang() float64{
return calcuator.Num1 /calcuator.Num2
}
func main(){
n := Calcuator{24.0,8.0}
he :=n.getSum()
cha :=n.getJian()
ji :=n.getJi()
shangha :=n.getShang()
fmt.Printf("和为%v 差为%v 积为%v 商为%v",he,cha,ji,shangha)
}
实现形式2:用一个方法搞定
//实现形式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(){
s := Calcuator{32.0,8.0}
res :=s.getRes('*')
fmt.Println("运算的结果为:",res)
}
调用方式不一样
函数的调用方式 : 函数名(实参列表)
方法的调用方式:变量.方法名(实参列表)
对于普通函数,接收者为值类型的时候,不能将指针类型的数据直接传递,反之亦然
type Person struct{
Name string
}
//函数
func test01(p Person){
fmt.Println(p.Name)
}
func test02(p *Person){
fmt.Println(p.Name)
}
func main(){
p := Person{"tom"}
test01(p)
// test01(&p)//不可以传地址
test02(&p)
}
对于方法如(struct的方法),接受者为值类型的时候,可以直接用指针类型的变量调用方法,反过来同样也可以
//方法
func (p Person) test03(){
fmt.Print("test03=",p.Name)
}
func main(){
p := Person{"tom"}
p.test03() //可以
(&p).test03()//指针传入地址也可以,从形式上是传入了地址但是本质上仍然是值拷贝
}
总结:
1)步骤
2)案例
编写一个Student结构体,包含name、gender、age、id、score字段,分别是string、string、int、int、float54类型
结构体中声明一个say()方法,返回string类型,方法返回信息包含所有字段值
在main1方法中,创建一个stsudent结构体实例(变量),并访问say方法,并将调用结构打印输出
package main
import (
"fmt"
)
type Student struct{
name string
gender string
age int
id int
score float64
}
func (stu *Student) say() string{
//使用fmt.Sprintf()函数将值转换为字符串
inforstr :=fmt.Sprintf("student的信息如下 name = [%v] gender=[%v] age = [%v] id=[%v] score=[%v]",stu.name,stu.gender,stu.age,stu.id,stu.score)
return inforstr
}
func main(){
var stu = Student{"小红","男",34,1001,99.5}
info :=stu.say()
fmt.Println(info)
}
3)案例2:盒子案例
编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽、高,长宽高要从终端获取
声明一个方法获取立方体的体积
创建一个Box结构体变量,打印给定尺寸的立方体的体积
type Box struct{
length float64
width float64
high float64
}
func (b Box) getVolumn() float64{
return b.length * b.width * b.high
}
func main(){
//测试box
var box Box
box.length = 1.1
box.width = 2.0
box.high = 3.0
Volum := box.getVolumn()
fmt.Printf("这个黑子的体积是%.2f",Volum)
}
3)案例3:景区门票案例
一个景区根据游人的年龄收取不同价格的民票,比如年龄大于18,收费20元,其他情况门票免费
请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出
type Vistor struct{
Name string
Age int
}
func (vistor Vistor) showPrice(){
if vistor.Age >18{
fmt.Println("门票价格为20元")
}else{
fmt.Println("免费")
}
}
func main(){
var vio Vistor
for {
fmt.Println("请输入你的名字")
fmt.Scanln(&vio.Name)
if vio.Name == "n" {
fmt.Println("退出程序")
break
}
fmt.Println("请输入你的年龄")
fmt.Scanln(&vio.Age)
vio.showPrice()
}
}
1)说明:Golang在创建结构体实例(变量)时,可以直接指定字段值
2)创建结构体变量时指定字段值方式
way1
//在创建结构体变量时,就直接指定字段的值
var stu1 = Stu{"tom",23}
stu2 :=Stu{"晓明",20}
//创建结构体变量时。把字段名和字段值写在一起
var stu3 = Stu{ //这种写法更加稳健不依赖于字段的顺序
Name : "jack",
Age : 20,
}
//可以类型颠倒
var stu3 = Stu{ //这种写法更加稳健不依赖于字段的顺序
Age : 20,
Name : "jack",
}
way2
//方式2.返回结构体的指针类型(!!!)
var stu5 *(Stu)= &Stu{"小王",29} //stu2--->地址 ---》结构体数据[xxxx,xxxx]
stu6 :=&Stu{"小王",39}
//在创建结构体指针变量是,把字段名和字段值写在一起,这种写法,就不依赖于字段的定义顺序
var stu7 = &Stu{
Name : "小李",
Age : 49,
}
var stu8 = &Stu{
Age :59,
Name : "小李~",
}
fmt.Println(*stu5,*stu6,*stu7,*stu8)//
}
1)说明
Golang的额结构体没有构造函数,通常可以使用工厂模式来解决这个问题
看一个需求
一个结构体的声明是这样的
package model
type Student struct{
Name string ....
}
因为这里的Student的首字母S是大写的,如果我们想在其它包创建Student的实例(比如main包),引入model包后,就可以直接创建Student结构体的变量(实例)
但是问题来了,如果首字母是小写的,比如是type student struct {,}就不行了,怎么办—》工厂模式来解决
2)工厂模式来解决问题
使用工程模式实现挎包创建结构体实例(变量)的案例
1》如果model包的结构体变量首字母大写,引入后,直接使用没有任何问题
package model
//定义一个结构体
type Student struct{
Name string
Score float64
}
package main
import (
"fmt"
"go_code/object2/factory/model"
)
func main(){
//创建一个Student的实例
var stu = model.Student{
Name : "Tom",
Score : 78.9,
}
fmt.Println(stu)
}
2》如果model包的结构体变量首字母小写,引入后,不能直接使用,可以使用工厂模式解决
package model
//定义一个结构体
type student struct{
Name string
Score float64
}
//因为student结构体首字母是小写,因此是只能在
//model包使用,通过工厂模式解决
func NewStudent(n string,s float64) *student{
return &student{
Name : n,
Score : s,
}
}
在main包下的main,go中使用
package main
import (
"fmt"
"go_code/object2/factory/model"
)
func main(){
//当student结构体首字母是小写的我们可以通过工厂模式解决
var stu = model.NewStudent("tom~",88.8)
fmt.Println(*stu)
}
问题:
思考一下,如果model包的Student的结构体字段Score改为score,我们还可以正常访问吗?当然不可以正常访问,我们要使用一个方法去访问
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 //(底层会优化为//return (*s).score)
}
package main
import (
"fmt"
"go_code/object2/factory/model"
)
func main(){
//当student结构体首字母是小写的我们可以通过工厂模式解决
var stu = model.NewStudent("tom~",88.8)
fmt.Println(*stu)
//注意了,这里我们使用getScore方法去访问stu,score
fmt.Printf("name=%v Score=%v",stu.Name,stu.GetScore())
//fmt.Printf("name=%v score的值为=%v",stu.Name,(*stu).GetScore())
}
1)删除当前行 ctrl +shift +k [也可自定义]
2)向上/向下复制当前行Shfit + Alt + 下箭头/上箭头
3)补全代码 alt +/
4)添加注释和取消注释 ctrl + /
5)快速修复 alt + /
6)快速格式化代码 shift + alt +f
特别说明:
1)VSCODE的快捷键不要和输入法冲突,否则不会生效
2)一些快捷键需要安装Go插件后才能生效
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他的OOP语言不一样,下面就一一来讲解吧
如何理解抽象?
我们在定义一个结构体的时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(结构体),这种研究问题的方法称为抽象。
代码实现:
package main
import (
"fmt"
)
//定义一个结构体
type Account struct{
AccountNo string
Pwd string
Balance float64
}
//方法
//1.存款
func (account *Account) Deposite(money float64,pwd string){
//看看输入的密码是否正确
if pwd !=account.Pwd{
fmt.Println("你输入的密码不正确")
return
}
//看看存款金额是否正确
if money <= 0 {
fmt.Println("你输入的金额不正确")
return
}
account.Balance += money
fmt.Println("存款成功")
}
//取款
func (account *Account) WithDraw(money float64,pwd string){
//看看输入的密码是否正确
if pwd !=account.Pwd{
fmt.Println("你输入的密码不正确")
return
}
//看看存款金额是否正确
if money <= 0 || money >account.Balance{
fmt.Println("你输入的金额不正确")
return
}
account.Balance -= money
fmt.Println("取款成功")
}
//查询余额
func (account *Account)Query(pwd string){
//看看输入的密码是否正确
if pwd !=account.Pwd{
fmt.Println("你输入的密码不正确")
return
}
fmt.Printf("你的账号为=%v 余额=%v \n",account.AccountNo,account.Balance)
}
func main(){
//测试一下
account := Account{
AccountNo : "gs111111",
Pwd : "666666",
Balance : 1000.0,
}
for{
//这里可以做的更加灵活,就是让用户通过控制台来输入命令
//菜单
account.Query("666666")
account.Deposite(200,"666666")
account.Query("666666")
account.WithDraw(150.0,"666666")
account.Query("666666")
}
}
封装就是把抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作
隐藏实现细节
可以对数据进行验证,保证安全合理
type Person struct{
Age int
}
4)封装的实现步骤
将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)
给结构体所在包提供了一个工厂模式的函数,首字母大写。类似一个构造函数
提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值
func (var 结构体类型名)SetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
var.字段 = 参数
}
提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值
func (var 结构体类型名)GetXxx(){
return var.字段
}
特别说明:在Golang开发中并没有特别强调封装,这点并不像java,所以学过java的朋友不能总是用java语法特征来看待Glang,Golang本身对向对象的特性做了简化。
有一个程序,不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证.设计:model包(person go) main包(main.go调用Person结构体)
person.go
package model
import (
"fmt"
)
type person struct{
Name string
age int //字母小写,其他包不能访问
sal float64
}
//写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person{
return &person{
Name : name,
}
}
//为了访问age和sal我们编写一个SertXxx的方法和GetXxx方法
func (p *person) SetAge(age int) {
if age >0 && age < 150 {
p.age = age
}else{
fmt.Println("年龄范围不正确...")
}
}
func (p *person) GetAge() int {
return p.age
}
func (p *person) SetSal(sal float64){
if sal >=3000 && sal <= 30000 {
p.sal = sal
}else{
fmt.Println("年龄范围不正确...")
}
}
func (p *person) GetSal() float64{
return p.sal
}
在其他包的main.go进行调用
package main
import (
"fmt"
"go_code/object2/encapsulate/model"
)
func main (){
p :=model.NewPerson("smith")
p.SetAge(18)
p.SetSal(5000.0)
fmt.Println(*p)
fmt.Println(p.Name,"age =",p.GetAge(),"sal = ",p.GetSal())
}
创建一个程序在model包中定义一个Account结构体:在main函数中体会Golang封装性
package model
import (
"fmt"
)
//定义一个结构体
type account struct{
accountNo string
pwd string
balance float64
}
//工厂模式的函数-构造函数
func NewAccount(accountNo string,pwd string,balance float64) *account{
if len(accountNo) < 6 || len(accountNo) > 10 {
fmt.Println("账号的长度不对...")
return nil
}
if len(pwd) != 6 {
fmt.Println("密码的长度不对...")
return nil
}
if balance < 20 {
fmt.Println("余额数目不对")
return nil
}
return &account{
accountNo : accountNo,
pwd : pwd,
balance : balance,
}
}
//方法
//1.存款
func (ac *account) Deposite(money float64,pwd string){
//看看输入的密码是否正确
if pwd !=ac.pwd{
fmt.Println("你输入的密码不正确")
return
}
//看看存款金额是否正确
if money <= 0 {
fmt.Println("你输入的金额不正确")
return
}
ac.balance += money
fmt.Println("存款成功")
}
//取款
func (ac *account) WithDraw(money float64,pwd string){
//看看输入的密码是否正确
if pwd !=ac.pwd{
fmt.Println("你输入的密码不正确")
return
}
//看看存款金额是否正确
if money <= 0 || money >ac.balance{
fmt.Println("你输入的金额不正确")
return
}
ac.balance -= money
fmt.Println("取款成功")
}
//查询余额
func (ac *account)Query(pwd string){
//看看输入的密码是否正确
if pwd !=ac.pwd{
fmt.Println("你输入的密码不正确")
return
}
fmt.Printf("你的账号为=%v 余额=%v \n",ac.accountNo,ac.balance)
}
main。go中进行测试
package main
import (
"fmt"
"go_code/object3/ex2/model"
)
func main(){
account := model.NewAccount("jzh1111","999999",48)
if account !=nil{
fmt.Println("创建成功=",*account)
}else{
fmt.Println("创建失败")
}
}
当然是为了代码复用
走代码看看
package main
import (
"fmt"
)
//编写一个学生考试系统
type Pupil struct {
Name string
Age int
Score int
}
//显示成绩
func (p *Pupil) ShowInfo(){
fmt.Printf("显示学生名字=%v 年龄=%v 成绩=%v",p.Name,p.Age,p.Score)
}
func (p *Pupil) SetScore(score int) {
p.Score = score
}
func (p *Pupil) testing() {
fmt.Println("小学生正在考试")
}
//大学生,研究生。。
type Graduate struct {
Name string
Age int
Score int
}
//显示成绩
func (p *Graduate) ShowInfo(){
fmt.Printf("显示学生名字=%v 年龄=%v 成绩=%v",p.Name,p.Age,p.Score)
}
func (p *Graduate) SetScore(score int) {
p.Score = score
}
func (p *Graduate) testing() {
fmt.Println("大学生正在考试")
}
func main(){
//测试
var pupil = &Pupil{
Name : "Tom",
Age : 10,
}
pupil.testing()
pupil.SetScore(90)
pupil.ShowInfo()
//测试
var graduate = &Graduate{
Name : "jhon",
Age : 10,
}
graduate.testing()
graduate.SetScore(90)
graduate.ShowInfo()
}
//代码冗余 。。。高中生。。。
对上面代码做一个小结
继承可以解决代码复用,让我们的编程更加靠近人类思维
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。
其他的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可
也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性
type Goods struct{
Name string
Price int
}
type Book struct {
Goods//这里就是嵌套匿名结构体Goods相当于加入了Name和Pirce这两个字段
Writer string
}
&4.应用案例
我们对先前有关student的结构体进行改进操作减少其冗余性,代码实现
package main
import (
"fmt"
)
//编写一个学生考试系统
type Student struct{
Name string
Age int
Score int
}
//将Pupil和Graduate共有的方法也绑定到Student
func (stu *Student) ShowInfo(){
fmt.Printf("显示学生名字=%v 年龄=%v 成绩=%v",stu.Name,stu.Age,stu.Score)
}
func (stu *Student)SetScore(score int) {
stu.Score = score
}
//给*Student增加一个方法,那么Pupil 和Graduate都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int{
return n1 + n2
}
//小学生
type Pupil struct {
Student //嵌入Student匿名结构体
}
//这是Pupil结构体特有的方法,保留
func (p *Pupil) testing() {
fmt.Println("小学生正在考试")
}
//大学生,研究生。。
type Graduate struct {
Student //嵌入Student匿名结构体
}
//保留特有的方法
func (p *Graduate) testing() {
fmt.Println("大学生正在考试")
}
func main(){
//当我们对结构体嵌入了匿名结构体使用方法会发生变化
pupil :=&Pupil{}
pupil.Student.Name ="tom~"
pupil.Student.Age = 8
pupil.testing()
pupil.Student.SetScore(70)
pupil.Student.ShowInfo()
fmt.Println("res=",pupil.Student.GetSum(1,2))
//大学生进行操作
graduate :=&Graduate{}
graduate.Student.Name ="MARY"
graduate.Student.Age = 28
graduate.testing()
graduate.Student.SetScore(90)
graduate.Student.ShowInfo()
fmt.Println("res=",graduate.Student.GetSum(10,79))
}
&4.继承的深入讨论1
结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
package main
import (
"fmt"
)
type A struct {
Name string
age int
}
func (a *A) SayOk(){
fmt.Println(" A SayOk",a.Name)
}
func (a *A) Hello(){
fmt.Println(" A Hello",a.Name)
}
type B struct {
A
}
func main(){
var b B
b.A.Name = "tom"
b.A.age = 19
b.A.SayOk()
b.A.Hello()
}
匿名结构体字段访问可以简化(如果B结构体中没有变量那么就会自动去寻找嵌入的结构体中的字段或方法)。
var b B
b.A.Name = "tom" b.Name = "tom"
b.A.age = 19 ==> b.age = 19
b.A.SayOk() b.SayOk()
b.A.Hello() b.Hello()
总结
2.编译器会先看b对应的类型有没有Name,如果有则直接调用B类型的 Name字段,如果没有继续查找,没有找到就报错
3.如果没有就去看嵌入中的匿名结构体有没有声明Name字段
当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
package main
import (
"fmt"
)
type A struct {
Name string
age int
}
func (a *A) SayOk(){
fmt.Println(" A SayOk",a.Name)
}
func (a *A) hello(){
fmt.Println(" A Hello",a.Name)
}
type B struct {
A
}
func (b *B)SayOk() {
fmt.Println("B SayOK",b.Name)
}
func main(){
var b B
// b.A.Name = "tom"
// b.A.age = 19
// b.A.SayOk()
// b.A.hello()
b.Name = "jack"
b.age = 100
b.SayOk() //B SayOK jack
b.hello() // A Hello jack
}
结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同额字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
package main
import (
"fmt"
)
type A struct {
Name string
age int
}
type B struct {
Name string
score float64
}
type C struct {
A
B
Name string
}
func main(){
var c C
//如果 c 无Name字段,而A和B有Name,这时就必须指定匿名结构体名字来区分
//c.Name = "tom"//会报错,因为不知道选择A里的Name还是B里面的Name
//c.A.Name = "tom" //具体指定哪一个结构体中的Name
c.Name = "tom" //成功是因为C有Name
}
//这个规则对于方法也是有效的
如果一个struct嵌套了一个有名的结构体,这种模式就是组合,如果是组合关系,那么在访问组合结构体的字段或方法时,必须带上结构体的名字
type D struct {
a A //有名结构体
}
func main(){
var c C
var d D
// d.Name= "jack"//报错,必须将结构体名字带上
d.a.Name = "jack"
嵌套匿名结构体,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
package main
import (
"fmt"
)
type A struct {
Name string
age int
}
type B struct {
Name string
score float64
}
type C struct {
A
B
Name string
}
type D struct {
a A //有名结构体
}
type Goods struct{
Name string
Price float64
}
type Brand struct{
Name string
Address string
}
type TV struct {
Goods
Brand
}
type TV2 struct { //也可将指针类型传入进去
*Goods
*Brand
}
func main(){
var c C
//如果 c 无Name字段,而A和B有Name,这时就必须指定匿名结构体名字来区分
//c.Name = "tom"//会报错,因为不知道选择A里的Name还是B里面的Name
//c.A.Name = "tom" //具体指定哪一个结构体中的Name
c.Name = "tom" //成功是因为C有Name
var d D
// d.Name= "jack"//报错,必须将结构体名字带上
d.a.Name = "jack"
tv := TV{ Goods{"电视机001",5000.99},Brand{"海尔","山东"}, }
tv2 := TV{
Goods{
Price : 6000.90,
Name : "电视002",
},
Brand{
Name : "长虹",
Address :"北京",
},
}
//创建之时要把地址传入进去
tv3 := TV2{ &Goods{"电视机003",7000.99},&Brand{"创维","深圳"}, }
fmt.Println("TV=",tv)
fmt.Println("TV2=",tv2)
fmt.Println("TV3=",*tv3.Goods,*tv3.Brand)
tv4 := TV2{
&Goods{
Name : "电视机004",
Price : 9900.99,
},
&Brand{
Name : "康佳",
Address : "上海",
},
}
fmt.Println("TV4=",*tv4.Goods,*tv4.Brand)
}
结构体的匿名字段是基本数据类型,如何访问,下面的代码
type A struct {
Name string
Age int
}
type Stu struct {
A
int
}
func main(){
stu :=Stu{}
stu.Name = "tom"
stu.Age = 10
stu.int = 80
fmt.Println(stu)
}
//输出结果为
{{tom 10} 80}
说明:
多重继承说明
如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法。从而实现了多重继承
如果嵌入的匿名结构体有相同的字段名或方法名,则在访问时,需要通过匿名结构体类型来区分
main(){
var c C
var d D
// d.Name= “jack”//报错,必须将结构体名字带上
d.a.Name = “jack”
嵌套匿名结构体,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
package main
import (
"fmt"
)
type A struct {
Name string
age int
}
type B struct {
Name string
score float64
}
type C struct {
A
B
Name string
}
type D struct {
a A //有名结构体
}
type Goods struct{
Name string
Price float64
}
type Brand struct{
Name string
Address string
}
type TV struct {
Goods
Brand
}
type TV2 struct { //也可将指针类型传入进去
*Goods
*Brand
}
func main(){
var c C
//如果 c 无Name字段,而A和B有Name,这时就必须指定匿名结构体名字来区分
//c.Name = "tom"//会报错,因为不知道选择A里的Name还是B里面的Name
//c.A.Name = "tom" //具体指定哪一个结构体中的Name
c.Name = "tom" //成功是因为C有Name
var d D
// d.Name= "jack"//报错,必须将结构体名字带上
d.a.Name = "jack"
tv := TV{ Goods{"电视机001",5000.99},Brand{"海尔","山东"}, }
tv2 := TV{
Goods{
Price : 6000.90,
Name : "电视002",
},
Brand{
Name : "长虹",
Address :"北京",
},
}
//创建之时要把地址传入进去
tv3 := TV2{ &Goods{"电视机003",7000.99},&Brand{"创维","深圳"}, }
fmt.Println("TV=",tv)
fmt.Println("TV2=",tv2)
fmt.Println("TV3=",*tv3.Goods,*tv3.Brand)
tv4 := TV2{
&Goods{
Name : "电视机004",
Price : 9900.99,
},
&Brand{
Name : "康佳",
Address : "上海",
},
}
fmt.Println("TV4=",*tv4.Goods,*tv4.Brand)
}
结构体的匿名字段是基本数据类型,如何访问,下面的代码
type A struct {
Name string
Age int
}
type Stu struct {
A
int
}
func main(){
stu :=Stu{}
stu.Name = "tom"
stu.Age = 10
stu.int = 80
fmt.Println(stu)
}
//输出结果为
{{tom 10} 80}
说明:
多重继承说明
如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法。从而实现了多重继承