*本文笔记参考:b站【尚硅谷】Golang入门到实战教程

张老太养了两只猫:一只名字叫小白,今年3岁,白色;另一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字、年龄、颜色;如果用户输入的小猫名错误,则显示张老太没有这只猫。

1、结构体
  • 将一类事务的特性提取出来(比如猫类),形成一个新的数据类型,就是一个结构体;

  • 通过这个结构体,我们可以创建多个变量(实例/对象);

  • 事务可以是猫类,也可以是person或其他。

package main
​
import "fmt"
​
//定义一个Cat结构体
type Cat struct {
    Name  string
    Age   int
    Color string
}
​
func main() {
    //创建一个Cat的变量
    cat1 := Cat{"小白", 3, "白色"}
    cat2 := Cat{"小花", 100, "花色"}
    fmt.Println(cat1, cat2)
}
//输出
//{小白 3 白色} {小花 100 花色}

1)结构体的声明

type 结构体名称 struct {
    field1  type
    field2   type
}

2)字段/属性

注意事项和细节说明:

package main
​
import "fmt"
​
//定义一个Cat结构体
type Cat struct {
    Name   string
    Age    int
    Scores [5]float64
    Color  []string          //切片
    Ptr    *int              // 指针
    Map1   map[string]string //map
}
​
func main() {
    var cat Cat
    fmt.Println(cat)
}
//输出
//{ 0 [0 0 0 0 0] [] <nil> map[]}
package main
​
import "fmt"
​
//定义一个Cat结构体
type Cat struct {
    Name  string
    Age   int
    Color []string //切片
}
​
func main() {
    var cat1 Cat
    cat1.Name = "小白"
    cat1.Age = 3
    cat1.Color = make([]string, 1)
    cat1.Color[0] = "白色"
    fmt.Println("cat1:", cat1)
    cat2 := cat1
    //Name为值类型,cat2.Name的改变不会影响到cat1.Name
    cat2.Name = "小花"
    //Color为引用类型,会导致cat1.Color的值也发生改变
    cat2.Color[0] = "花色"  
    fmt.Printf("cat1: %v\ncat2: %v", cat1, cat2)
}
//输出:
//cat1: {小白 3 [白色]}
//cat1: {小白 3 [花色]}
//cat2: {小花 3 [花色]}

3)创建结构体变量和访问结构体字段

方式1:直接声明

var cat1 Cat

方式2:{}

var cat1 = Cat{}      //用var声明
cat1 := Cat{}         //自动推导
cat1 := Cat{"tom", 3} //直接在{}中赋值

方式3:new

var cat1 *Cat = new(Cat) //cat1为一个指针
cat1.Name = "tom"        //等价于(*cat1).Name = "tom"
cat1.Age = 3             //等价于(*cat1).Age = 3

方式4:&

var cat1 *Cat = &Cat{}   //cat1为一个指针
cat1.Name = "tom"        //等价于(*cat1).Name = "tom"
cat1.Age = 3             //等价于(*cat1).Age = 3

说明:

(*结构体指针).字段名(*cat1).Name结构体指针.字段名cat1.Namecat1.Name(*cat1).Name

4)结构体的注意事项和使用细节

package main
​
import "fmt"
​
//定义一个Cat结构体
type Point struct {
    x int
    y int
}
​
type Rect struct {
    leftUp, rigntDown Point
}
​
func main() {
    r1 := Rect{Point{1, 2}, Point{3, 4}}
    fmt.Printf("r1.leftUp.x的地址%p\nr1.leftUp.y的地址%p\nr1.rightDown.x的地址%p\nr1.rightDown.y的地址%p\n",
        &r1.leftUp.x, &r1.leftUp.y, &r1.rigntDown.x, &r1.rigntDown.y)
}
//输出
r1.leftUp.x的地址0xc000126060
r1.leftUp.y的地址0xc000126068
r1.rightDown.x的地址0xc000126070
r1.rightDown.y的地址0xc000126078
package main
​
import "fmt"
​
//定义一个Cat结构体
type A struct {
    Num int
}
​
type B struct {
    Num int
}
​
func main() {
    var a A
    var b B
    a = b //结构体类型不一样,无法进行转换:cannot use b (type B) as type A in assignment
    fmt.Println(a, b)
}
package main
​
import "fmt"
​
//定义一个Cat结构体
type A struct {
    Num int
}
​
type B struct {
    Num int
}
​
func main() {
    var a A
    var b B
    a = A(b) //两个结构体的字段名、字段个数、字段类型完全相同时才能进行转换
    fmt.Println(a, b)
}
package main
​
import "fmt"
​
//定义一个Cat结构体
type Student struct {
    Name string
    Age  int
}
​
type Stu Student
​
func main() {
    var stu1 Student
    var stu2 Stu
    stu2 = stu1  //错误。Student与Stu是两种数据类型:cannot use stu1 (type Student) as type Stu in assignment
    stu2 = Stu(stu1)  //正确,强制转换
    fmt.Println(stu1, stu2)
}
package main
​
import (
    "encoding/json"
    "fmt"
)
​
//定义一个Cat结构体
type Monster struct {
    Name  string `json:"name"`  //tag:将大写字母开头的字段名转换成小写
    Age   int    `json:"age"`
    Skill string `json:"skill"`
}
​
func main() {
    //1.创建一个monster变量
    monster := Monster{"牛魔王", 6, "芭蕉扇"}
    //2.将monster变量序列化为json格式字符串
    jsonMonster, err := json.Marshal(monster) //序列化返回的是byte格式的内容
    if err != nil {
        fmt.Println("json为空", err)
    }
    fmt.Println(string(jsonMonster))
}
//输出
fmt.Println(jsonMonster)的输出(输出byte类型):
[123 34 78 97 109 101 34 58 34 231 137 155 233 173 148 231 142 139 34 44 34 65 103 101 34 58 54 44 34 83 107 105 108 108 34 58 34 232 138 173 232 149 137 230 137 135 34 125]
fmt.Println(string(jsonMonster))的输出(不加tag):
{"Name":"牛魔王","Age":6,"Skill":"芭蕉扇"}
fmt.Println(string(jsonMonster))的输出(加tag):
{"name":"牛魔王","age":6,"skill":"芭蕉扇"}
2、方法

golang中的方法是作用在指定的数据类型上的(和指定的数据类型绑定),因此,自定义类型都可以有方法,而不仅仅是struct。

1)方法的声明和调用

func (recevier type) methodName(参数列表) (返回值列表) {
    方法体
    return 返回值
}
  1. recevier type:表示这个方法和这个type绑定或者说该方法作用于type类型;type可以说结构体,也可以是其他自定义类型;

  2. recevier:就是type类型的一个变量(实例),比如:Person结构体的一个变量person;

  3. 返回值列表:表示返回的值,可以多个;

  4. 方法体:表示为了实现某一功能模块;

  5. return语句不是必须的,有返回值列表才需要有return语句。

package main
​
import (
    "fmt"
)
//定义结构体
type Person struct {
    Name string
}
//定义方法
func (p Person) test() { //表示A结构体有一种方法,方法名为test
    fmt.Println(p.Name)
}
​
func main() {
    var p Person
    p.Name = "Tom"
    p.test() //调用方法
}
func (p Person) test(){}...p

2)方法快速入门

(1)给Person结构体添加speak方法,输出”是一个好人“。

package main

import (
	"fmt"
)

type Person struct {
	Name string
}

func (p Person) speak() { //表示A结构体有一种方法,方法名为test
	p.Name = "Tom"
	fmt.Printf("%v是一个好人", p.Name)
}

func main() {
	var p Person
	p.speak() //调用方法
}
//输出
Tom是一个好人

(2)给Person结构体添加计算方法,可以计算从1+...+1000的结果。

package main

import (
	"fmt"
)

type Person struct {
	Name string
}

func (p Person) Calcu() { //表示A结构体有一种方法,方法名为test
	p.Name = "Tom"
	res := 0
	for i := 1; i <= 1000; i++ {
		res += i
	}
	fmt.Println(p.Name, "计算的结果是", res)
}

func main() {
	var p Person
	p.Calcu() //调用方法
}
//输出
Tom 计算的结果是 500500

(3)给Person结构体添加计算方法,该方法可以接收一个数n,计算从1+...+n的结果。

package main

import (
	"fmt"
)

type Person struct {
	Name string
}

func (p Person) Calcu(n int) { //表示A结构体有一种方法,方法名为test
	p.Name = "Tom"
	res := 0
	for i := 1; i <= n; i++ {
		res += i
	}
	fmt.Println(p.Name, "计算的结果是", res)
}

func main() {
	var p Person
	p.Calcu(1000) //调用方法
}
//输出
Tom 计算的结果是 500500

(4)给Person结构体添加getSum方法,可以计算两个数的和,并返回结果。

package main

import (
	"fmt"
)

type Person struct {
	Name string
}

func (p Person) getSum(a, b int) int { //表示A结构体有一种方法,方法名为test
	p.Name = "Tom"
	res := a + b
	fmt.Println(p.Name, "计算的结果是", res)
	return res
}

func main() {
	var p Person
	p.getSum(100, 200) //调用方法
}
//输出
Tom 计算的结果是 300

3)方法的调用和传参机制

方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量当作实参也传递给方法。

4)方法的课堂练习

(1)编写结构体(MethodUtils),编写一个方法,方法不需要参数,在方法中打印一个10*8的矩形,在main方法中调用该方法。

package main

import (
	"fmt"
)

type Person struct {
	Name string
}

func (p Person) getSum(a, b int) int { //表示A结构体有一种方法,方法名为test
	p.Name = "Tom"
	res := a + b
	fmt.Println(p.Name, "计算的结果是", res)
	return res
}

func main() {
	var p Person
	p.getSum(100, 200) //调用方法
}
//输出
Tom 计算的结果是 300

(2)编写一个方法,提供m和n两个参数,方法中打印一个m*n的矩形。

package main

import (
	"fmt"
)

type Person struct {
	Name string
}

func (p Person) getSum(a, b int) int { //表示A结构体有一种方法,方法名为test
	p.Name = "Tom"
	res := a + b
	fmt.Println(p.Name, "计算的结果是", res)
	return res
}

func main() {
	var p Person
	p.getSum(100, 200) //调用方法
}
//输出
Tom 计算的结果是 300

(3)编写一个方法计算该矩形的面积(可以接收长len和宽width),将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。

package main

import "fmt"

type MethodUtils struct {
	//字段...
}

func (rect MethodUtils) area(len, width float64) float64 {
	return len * width
}

func main() {
	var rect MethodUtils
	areaRes := rect.area(10.0, 8.5)
	fmt.Println("面积为:", areaRes)
}
//输出
面积为: 85

(4)编写方法:判断一个数是奇数还是偶数。

package main

import "fmt"

type MethodUtils struct {
	//字段...
}

func (rect MethodUtils) JudgeNum(num int) {
	if num%2 == 0 {
		fmt.Println("偶数")
	} else {
		fmt.Println("奇数")
	}
}

func main() {
	var rect MethodUtils
	rect.JudgeNum(3)  //奇数
}

(5)根据行、列、字符打印对应行数和列数的字符,比如行:3,列:2,字符:*。

package main

import "fmt"

type MethodUtils struct {
	//字段...
}

func (rect MethodUtils) Print(row, col int, str string) {
	for i := 1; i <= row; i++ {
		for j := 1; j <= col; j++ {
			fmt.Print(str)
		}
		fmt.Println()
	}
}

func main() {
	var rect MethodUtils
	rect.Print(3, 7, "$")
}
//输出
$$$$$$$
$$$$$$$
$$$$$$$

(6)定义小小计算器结构体(Calculator),实现加减乘除4个功能。实现形式1:分四个方法完成;实现形式2:用一个方法搞定。

//方法一:分4个方法完成
package main

import "fmt"

type Calculator struct {
	Num1 float64
	Num2 float64
}

func (rect Calculator) getSum() float64 {
	return rect.Num1 + rect.Num2
}

func (rect Calculator) getSub() float64 {
	return rect.Num1 - rect.Num2
}

func (rect Calculator) getMulti() float64 {
	return rect.Num1 * rect.Num2
}

func (rect Calculator) getDivide() float64 {
	if rect.Num2 == 0 {
		fmt.Print("被除数不能为0")
		return 0
	} else {
		return rect.Num1 / rect.Num2
	}
}

func main() {
	var rect = Calculator{1, 10}
	fmt.Println("乘法", rect.getMulti())
	fmt.Println("除法", rect.getDivide())
	fmt.Println("减法", rect.getSub())
	fmt.Println("加法", rect.getSum())
}
//输出
乘法 10
除法 0.1
减法 -9
加法 11
//方法二:用一个方法实现
package main

import "fmt"

type Calculator struct {
	Num1 float64
	Num2 float64
}

func (rect Calculator) getCal(operator byte) float64 {
	switch operator {
	case '+':
		return rect.Num1 + rect.Num2
	case '-':
		return rect.Num1 - rect.Num2
	case '*':
		return rect.Num1 * rect.Num2
	case '/':
		if rect.Num2 == 0 {
			fmt.Print("被除数不能为0")
			return 0
		} else {
			return rect.Num1 / rect.Num2
		}
	default:
		fmt.Println("运算符输入错误")
		return 0
	}
}

func main() {
	var rect = Calculator{10, 1}
	fmt.Println(rect.getCal('+'))  //11
}

(7)在MethodUtils结构体编写方法,从键盘接收整数(1-9),打印对应乘法表。

package main

import (
	"fmt"
)

type MethodUtils struct {
	n int
}

func (num MethodUtils) getMulti() {
	for i := 1; i <= num.n; i++ {
		for j := 1; j <= i; j++ {
			fmt.Print(j, "*", i, "=", j*i, " ")
		}
		fmt.Println()
	}
}

func main() {
	var num MethodUtils
	fmt.Print("请输入一个数字:")
	fmt.Scanln(&num.n)
	num.getMulti()
}
//输出
请输入一个数字:3
1*1=1 
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9

(8)编写一个方法,使给定的二维数组(3*3)转置。

package main

import (
	"fmt"
)

type MethodUtils struct {
	//
}

func (num MethodUtils) reverse(arr [3][3]int) {
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			fmt.Print(arr[j][i], " ")
		}
		fmt.Println()
	}
}

func main() {
	var num MethodUtils
	arr := [3][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
	num.reverse(arr)
}
//输出
1 4 7 
2 5 8
3 6 9

5)方法与函数的区别

  1. 调用方式不一样:

    函数:函数名(实参列表)

    方法:变量.方法名(实参列表)

  2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然;

  3. 对于方法(如struct方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以。

不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法绑定的是值类型还是指针类型。

3、面向对象编程应用实例

1)学生案例

编写一个Student结构体,包含name、gender、age、id、score字段,分别为string、string、int、int、float64类型。结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值。在main方法中,创建Student结构体实例(变量),并访问say方法,将调用结果打印输出。

package main

import (
	"fmt"
)

type Student struct {
	name   string
	gender string
	age    int
	id     int
	score  float64
}

func (student Student) say() string {
	infoString := fmt.Sprintf("student的信息:%v", student)
	return infoString
}

func main() {
	var student Student
	student = Student{"tom", "male", 18, 1201, 85.7}
	fmt.Println(student.say())
}
//输出
student的信息:{tom male 18 1201 85.7}

2)盒子案例

创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取。声明一个方法获取立方体的体积。创建一个Box结构体变量,打印给定尺寸的立方体的体积。

package main

import (
	"fmt"
)

type Box struct {
	length float64
	width  float64
	height float64
}

func (box Box) getVolumn() float64 {
	return box.length * box.width * box.height
}

func main() {
	var box Box
	fmt.Print("请输入长、宽、高:")
	fmt.Scanln(&box.length, &box.width, &box.height)
	fmt.Printf("体积为:%.2f", box.getVolumn())
}
//输出
请输入长、宽、高:1.1 2.2 3.3
体积为:7.99

3)景区门票案例

一个景区根据游客的年龄收取不同价格的门票,比如年龄大于18,收费20元,其他情况门票免费。请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出。

package main

import (
	"fmt"
)

type Visitor struct {
	name string
	age  int
}

func (visitor Visitor) getPrice() int {
	if visitor.age > 18 {
		fmt.Printf("游客%v的年龄为%v岁,门票20元\n", visitor.name, visitor.age)
		return 20
	} else {
		fmt.Printf("游客%v的年龄为%v岁,门票免费\n", visitor.name, visitor.age)
		return 0
	}
}

func main() {
	var visitor Visitor
	for {
		fmt.Print("请输入游客姓名:")
		fmt.Scanln(&visitor.name)
		fmt.Print("请输入游客年龄:")
		fmt.Scanln(&visitor.age)
		visitor.getPrice()
	}
}
//输出
请输入游客姓名:tom
请输入游客年龄:3
游客tom的年龄为3岁,门票免费
请输入游客姓名:jack
请输入游客年龄:20
游客jack的年龄为20岁,门票20元
4、工厂模式

1)结构体首字母小写,结构体字段首字母大写

//utils/utils.go文件
package utils

type student struct {
	Name string
	Age  int
}

//因为student结构体首字母小写,因此只能在utils包使用
//通过工厂模式解决此问题
func NewStudent(n string, a int) *student {
	return &student{
		Name: n,
		Age:  a,
	}
}
//main/main.go文件
package main

import (
	"code/test1/utils"
	"fmt"
)

func main() {
	var stu = utils.NewStudent("tom", 3)
	fmt.Println(*stu)
}
//输出
{tom 3}

2)结构体首字母小写,结构体字段首字母小写

//utils/utils.go文件
package utils

type student struct {
	name string
	age  int
}

//因为student结构体首字母小写,因此只能在utils包使用
//通过工厂模式解决此问题
func NewStudent(n string, a int) *student {
	return &student{
		name: n,
		age:  a,
	}
}

//如果结构体字段首字母为小写,解决跨包访问结构体的字段的问题
func (stu *student) GetName() string {
	return stu.name
}
//main/main.go文件
package main

import (
	"code/test1/utils"
	"fmt"
)

func main() {
	var stu = utils.NewStudent("tom", 3)
	fmt.Println(stu.GetName())
}
//输出
tom