【Java转Go】快速上手学习笔记(二)之基础篇二

news2024/11/15 2:00:21

【Java转Go】快速上手学习笔记(二)之基础篇一

了解了基本语法、基本数据类型这些使用,接下来我们来讲数组、切片、值传递、引用传递、指针类型、函数、泛型、map、结构体。

目录

    • 数组和切片
    • 值传递、引用传递
    • 指针类型
    • defer延迟执行
    • 函数
    • 泛型
    • map
    • 结构体
      • 匿名结构体(继承)
      • 有名结构体(组合)
    • 方法

数组和切片

数组定义:var 变量名 = [个数]类型{元素}
例如:var arr = [4]int{1, 2, 3, 4}

切片:可以扩容的数组(不指定个数),定义:var 变量名 = []类型{元素}
例如:var arr = []int{1, 2, 3, 4}

/*
	数组:[个数]类型{元素} arr := [4]int{1, 2, 3, 4}
	切片:可以扩容的数组 []类型{元素} arr := []int{1, 2, 3, 4}
*/
arr := [4]int{1, 2, 3, 4} // 数组
fmt.Println("数组arr", arr)
slice1 := []int{1, 2, 3, 4} // 切片
fmt.Println("slice1", slice1)

值传递、引用传递

值传递:传递的是原始数据的副本,修改数据(副本)时,并不会对原始数据造成影响

值传递的数据类型:基础类型、array、struct

// updateArr 更改数组里的值
// arr2的值是从arr中复制过来的,这两个数组是两个不同的内存空间,所以修改arr2不会影响arr
func updateArr(arr2 [4]int) {
	fmt.Println("arr2更改前:", arr2)
	arr2[0] = 100 // 更改数组里的某个值
	fmt.Println("arr2更改后:", arr2)
}
// 这里是值传递,将arr传给updateArr函数时,是拷贝操作;也就是说,传过去后在update函数里面就是一个新的数组了,不会影响arr本身
updateArr(arr)
fmt.Println("arr", arr)

引用传递:指向同一个内存空间,所以修改传递的值,也会影响原始数据

引用传递的数据类型:slice、map、channel

// updateSlice 更改切片里的值
func updateSlice(slice2 []int) {
	fmt.Println("slice2更改前:", slice2)
	slice2[0] = 100 // 更改切片里的某个值
	fmt.Println("slice2更改后:", slice2)
}
// slice1传入updateSlice函数中,用slice2接收,这里是引用传递,slice1和slice2指向同一个内存空间。所以当修改slice2时,slice1的值也会改变。
updateSlice(slice1)
fmt.Println("slice1", slice1)

指针类型

我们都知道,当定义一个变量时,会在内存里面开辟一个空间,这个空间有一个地址,这个空间里存放的就是变量的值。

比如:

func main() {
	var num int
	fmt.Println("num的地址=", &num) // 这里输出的就是变量num在内存中的地址
}

而指针变量,指向了一个值的内存地址。指针变量也是一个变量,它也可以存值,它存储的值就是另一个变量的地址,而这个指针变量本身也有一个地址。
在这里插入图片描述
基本数据类型在内存的布局:

  • 基本数据类型,变量存的就是值,也叫值类型
  • 获取变量的地址,用&,比如:var num int,获取num的地址用:&num
  • 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:var ptr *int = &num
  • 获取指针类型所指向的值,用:*,比如:var prt *int,使用 *prt 获取ptr指向的值
var i int = 10
fmt.Println("i的地址=", &i) // 0xc00000a0c8
// ptr是一个指针变量,它的类型是 *int,它本身的值是 &i
var ptr *int = &i
fmt.Printf("ptr=%v\n", ptr)     // 这里输出的值是i的地址:0xc00000a0c8
fmt.Printf("ptr的地址=%v\n", &ptr) // 这个指针变量它本身在内存中,也有一个地址:0xc00004a028 
fmt.Printf("ptr指向的值=%v\n", *ptr) // ptr的值是i地址,这个地址所在空间存的值就是i的值:10

defer延迟执行

defer 延迟执行:一个函数或方法的执行被延迟了(放到最后执行)

当有多个defer时,这些defer语句会按照逆序执行(先放进去的后执行)

func f(num int) {
	fmt.Println(num)
}
// 调用
f(1)
f(2)
defer f(3) // 这里调用f函数时,传了3,这个时候就已经把3传过去了,只是执行的时候放到了最后执行。
f(4)
defer f(5) // 这里比 defer f(3) 先执行
f(6)
defer f(7) // 这里会比 defer f(5)、defer f(3) 先执行
f(8)

// 最后打印的结果是:1,2,4,6,8,7,5,3

函数

定义函数用func:func 函数名(参数1,参数2...)(返回值1,返回值2...)

// func 函数名(参数1,参数2...)(返回值1,返回值2...)
// 可以返回多个值(不同类型也可以)
func test1() (int, int, string) {
	return 100, 200, "hh"
}

func test1(a, b int,str1, str2 string) (int, string) {
	return a + b , str1 + str2
}

func main() {
	num , str := test1(100, 200, "hh", "666")
	fmt.Println(num , str)
}

写一个递归求和的函数

// 递归求和
func getSum(n int) int {
	if n > 0 {
		return getSum(n-1) + n
	}
	return 0
}

// 调用函数
sum := getSum(5)
fmt.Println(sum)

函数类型:func()本身是一个数据类型 ,它可以作为参数去使用

// 定义一个f1函数
func f1(a, b int) {
	fmt.Println(a, b)
}

// 函数名后面不加括号,函数就是一个类型,加了括号就是函数的调用
// 获取 f1 的类型,输出的结果是 func(int,int)
fmt.Printf("%T\n", f1) // func()、func(int,int)、finc(int,int) int
// 定义函数类型的变量
var f2 func(int, int)
f2 = f1  // 这里f2的内存地址指向的是f1的地址(引用传递)
f3 := f1 // 这里f3的内存地址指向的是f1的地址(引用传递)
fmt.Println(f1) // 输出:0x796120
fmt.Println(f2) // 输出:0x796120
fmt.Println(f3) // 输出:0x796120
// 调用函数
f2(1, 2)
f3(3, 4)

匿名函数:没有名字的函数

// 将一个匿名函数赋值给f4
f4 := func() {
	fmt.Println("这里是匿名函数f4")
}
f4() // 调用匿名函数
// 匿名函数自己调用自己
func() {
	fmt.Println("这里是匿名函数f5")
}() //这里后面直接加括号就是调用函数

func(a, b int) {
	fmt.Println("这里是匿名函数f6,a,b=", a, b)
}(50, 100) //这里后面直接加括号就是调用函数

// 匿名函数返回值赋值给r
r := func(a, b int) int {
	fmt.Println("这里是匿名函数f7,a,b=", a, b)
	return a + b
}(50, 100) //这里后面直接加括号就是调用函数
fmt.Println("r=", r)

回调函数:将函数作为另一个函数的参数

将fun1()函数,作为fun2()函数的参数,则:fun2()函数叫做高阶函数,接收了一个函数作为参数;fun1()函数叫做回调函数,作为另一个函数的参数。

// 高阶函数,可以接收一个函数作为参数(函数本身可以作为参数)
func oper(a, b int, fun func(int, int) int) int {
	r := fun(a, b)
	return r
}
func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}

r1 := add(1, 2)
fmt.Println("r1=", r1)

r2 := oper(3, 4, add)
fmt.Println("r2=", r2)

r3 := oper(8, 4, sub)
fmt.Println("r3=", r3)

r4 := oper(3, 4, func(a int, b int) int {
	if b == 0 {
		fmt.Println("除数不能为0")
		return 0
	}
	return a / b
})
fmt.Println("r4=", r4)

闭包结构:一个外层函数中,有内层函数,在内层函数中,会操作外层函数的局部变量

  • 并且该外层函数的返回值就是这个内层函数,这个内层函数和外层函数的局部变量,统称为闭包结构
  • 局部变量的生命周期就会发生改变。正常局部变量会随着函数的调用而创建,随着函数的结束而销毁
  • 但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用
// 自增
func increment() func() int {
	i := 0
	// 定义一个匿名函数,给变量自增并返回
	fun := func() int { // 内层函数,但是还没有执行
		// 局部变量声明周期发生了变化
		i++
		return i
	}
	return fun
}

r1 := increment()
fmt.Println(r1)
v1 := r1()
fmt.Println(v1) // 1
v2 := r1()
fmt.Println(v2)   // 2
fmt.Println(r1()) // 3
fmt.Println(r1()) // 4
fmt.Println(r1()) // 5

r2 := increment()
v3 := r2()
fmt.Println(v3)   // 1
fmt.Println(r1()) // 6 这里r1里面的局部变量并没有被销毁,所以继续自增
fmt.Println(r2()) // 2

泛型

定义泛型:func 函数名 [泛型参数类型] (函数参数) {}

Go内置的两个泛型:any 和 comparable

  • any:表示go里面所有的内置基本类型,等价于 interface{}
  • comparable:表示go里面所有内置的可比较类型:int、uint、float、booi.struct、指针 等一切可以比较的类型

自定义泛型:

// 自定义一个切片泛型
type mySlice[T int | float64] []T

// 给自定义的切片泛型添加一个求和方法
func (s mySlice[T]) sum() T {
	var sum T
	for _, v := range s {
		sum += v
	}
	return sum
}

// 泛型函数
func add[T int | float64 | float32 | string](a T, b T) T {
	return a + b
}

// 自定义一个类型别名(将int8类型设置一个别名)
type int8A int8

// 自定义一个泛型约束
type myInt interface {
	// 当类型设置了别名,在使用的时候要么在后面把这个别名约束也加进去
	//int | int8 | int16 | int32 | int64 | int8A
	// 要么在这个类型前面,加一个 ~ 符号,因为类型的别名是这个类型的衍生类型,在类型前面加 ~ 号就可以适配这个类型的全部衍生类型了
	int | ~int8 | int16 | int32 | int64
}

// 给自定义的泛型约束添加一个比较大小的方法
func getMaxNum[T myInt](a, b T) T {
	if a > b {
		return a
	}
	return b
}

func main() {
	// 泛型的使用
	strArr := []string{"白夜", "炽翎"}
	intArr := []int{1, 2}
	printArr(strArr)
	printArr(intArr)

	// 往自定义的切片泛型里,添加int类型的值
	var i mySlice[int] = []int{1, 2, 3, 4}
	fmt.Println(i.sum()) // 可以直接调用为切片泛型添加的一个求和方法
	// 往自定义的切片泛型里,添加float64类型的值
	var f mySlice[float64] = []float64{1.5, 2.7, 3.89, 4.55}
	fmt.Println(f.sum())

	//fmt.Println(add[int](1, 2))
	fmt.Println(add(1, 2)) // 调用时,可以自动推导传入的参数的类型
	//fmt.Println(add[string]("hh", "66"))
	fmt.Println(add("hh", "66"))
	//fmt.Println(add[float64](1.6, 2.8))
	fmt.Println(add(1.6, 2.8))

	//var a int = 10
	var a int8A = 10 // 使用int8的别名int8A作为变量类型
	//var b int = 20
	var b int8A = 20
	fmt.Println(getMaxNum(a, b))
}

// 定义一个泛型函数,使用的是Go内置的泛型:any
func printArr[T any](arr []T) {
	for _, item := range arr {
		fmt.Println(item)
	}
}

map

map定义:可以使用内建函数 make 或使用 map 关键字来定义 Map

  • make定义:
    • myMap := make(map[键类型]值类型, 初始容量) // 创建一个空的 Map

    • myMap := make(map[string]int)、m := make(map[string]int, 10) // 创建一个初始容量为 10 的 Map

    • 当map中的键值对数量超过了初始容量时,map会自动扩容;如果不指定初始容量,Go会根据实际情况选择一个合适的值。

  • map定义:myMap := map[键类型]值类型{} // 使用字面量创建 map
package main

import "fmt"

func main() {
	mapUse()
}

// map用例
func mapUse() {
	/*
		map定义:可以使用内建函数 make 或使用 map 关键字来定义 Map
		make定义:
			myMap := make(map[键类型]值类型, 初始容量) // 创建一个空的 Map
			myMap := make(map[string]int)、m := make(map[string]int, 10) // 创建一个初始容量为 10 的 Map
		当map中的键值对数量超过了初始容量时,map会自动扩容;如果不指定初始容量,Go会根据实际情况选择一个合适的值。
		map定义:myMap := map[键类型]值类型{} // 使用字面量创建 map
	*/
	// 创建一个空的 Map
	//m := make(map[string]int)
	// 创建一个初始容量为 10 的 Map
	//m := make(map[string]int, 10)

	// 使用字面量创建 Map
	m := map[string]int{
		"apple":  1,
		"banana": 2,
		"orange": 3,
	}
	// 获取键值对
	v1 := m["apple"]
	//v2, isKey := m["apple"] // 如果键存在,isKey 的值为 true
	v2, isKey := m["pear"] // 如果键不存在,isKey 的值为 false,v2 的值为该类型的零值
	fmt.Println(v1)
	fmt.Println(v2, isKey)
	// 修改元素
	m["apple"] = 5
	// 获取 Map 的长度
	mlen := len(m)
	fmt.Println("m的长度=", mlen)
	// 遍历 Map
	for k, v := range m {
		fmt.Printf("key=%s, value=%d\n", k, v)
	}
	// 删除元素
	delete(m, "banana")
	mlen = len(m)
	fmt.Println("删除banana后,m的长度=", mlen)

	// 定义一个map
	var siteMap map[string]string
	siteMap = make(map[string]string)

	// map中插入 key - value 对
	siteMap["xunyu"] = "迅羽"
	siteMap["baiye"] = "白夜"
	siteMap["chiling"] = "炽翎"
	siteMap["yunmo"] = "云墨"
	siteMap["yuelun"] = "月轮"

	// 遍历key,通过key拿到value
	for site := range siteMap {
		fmt.Println(site, "的名字是", siteMap[site])
	}

	//查看元素在集合中是否存在
	name, ok := siteMap["fuhua"] // 如果确定是真实的,则存在,否则不存在
	fmt.Println(name, ok)
	if ok {
		fmt.Println("fuhua 的 名字是", name)
	} else {
		fmt.Println("fuhua 的名字不存在")
	}
}

结构体

结构体其实就相当于Java中的实体对象。

定义 struct 结构体:结构体定义需要使用 type 和 struct 语句。

  • struct 语句定义一个新的数据类型,结构体中有一个或多个成员(属性)。
  • type 语句设定了结构体的名称。
  • 格式如下:type 结构体名称 struct {
        属性1 类型
        属性2 类型
        属性3 类型
        …
    }
  • 一旦定义了结构体类型,它就能用于变量的声明,语法如下:
    • 1、结构体实例化变量 := 结构体名称 {value1, value2…valuen}
    • 2、结构体实例化变量 := 结构体名称 { key1: value1, key2: value2…, keyn: valuen}

还有一点要注意,还有一点要注意,定义的结构体如果只在当前包内使用,那么结构体名称和属性名大小写无所谓。如果要在其他包使用,则结构体名、属性名的首字母必须要大写。。这个就类似于Java的public和private,首字母大写是public,首字母小写是private。

// 定义一个结构体:用户信息
type SysUser struct {
	userName string // 用户账号
	realName string // 真是姓名
	password string // 用户密码
	phone    string // 手机号码
	address  string // 用户地址
	state    int    // 状态(0 正常 1 禁用)
}

// 结构体使用(结构体其实就相当于Java的实体对象)
func structUse() {
	// 创建一个新的结构体(实例化结构体),这种方式实例化,就需要将全部属性都赋值,并且一一对应
	// 类似于Java的有参构造函数(只不过Java的有参构造可以选哪几个属性传参,而这个需要全部属性都赋值传过去)
	fmt.Println(SysUser{"fuhua", "符华", "fuhua123", "13245778412", "广东省惠州市", 0})

	// 也可以使用 key => value 格式,通过key进行赋值,这种方式就不用说全部的属性都赋值了,而是可以自己选哪些属性进行赋值,忽略的字段为 0 或 空
	fmt.Println(SysUser{userName: "baiye", realName: "白夜", password: "baiye123", phone: "15125552365"})

	// 声明SysUser变量
	var user1 SysUser
	var user2 SysUser

	// 如果要访问结构体属性,直接 结构体变量名.属性 或者 指针变量.属性 就行
	// 给user1对象属性赋值
	user1.userName = "yunmo"
	user1.realName = "云墨"
	user1.password = "yunmo123"
	user1.phone = "15422667878"
	user1.address = "广东省惠州市"
	user1.state = 0

	// 给user2对象属性赋值
	user2.userName = "yuelun"
	user2.realName = "月轮"
	user2.password = "yuelun123"
	user2.state = 0

	fmt.Println("用户 1 的信息 : ", user1)
	printUser(user2) // 结构体作为函数的参数,值传递

	// 结构体指针,和普通的指针是一样的,声明:var 指针变量名称 *结构体 = &结构体变量
	//var userPoint *SysUser // 这个指针是SysUser类型的
	//userPoint = &user1 // user1是SysUser的一个实例化对象,&user1就是把这个结构体的内存地址赋值给了userPoint
	var userPoint *SysUser = &user1
	// 使用这个指针的时候,通过 *指针变量名称 就可以把指针存的user1的地址对应的值给取出来了。
	fmt.Println("使用结构体指针,打印用户1", *userPoint) // *userPoint 的效果和直接用 user1 打印的效果一样
	// 使用结构体指针访问结构体成员,使用 "." 操作符
	fmt.Println("使用结构体指针,访问用户1 userName : ", userPoint.userName) // 这个效果相当于 user1.userName

	printUserPoint(&user2) // 结构体指针作为函数的参数,引用传递
	fmt.Println("结构体指针用户信息更改后")
	fmt.Printf("结构体指针用户 userName : %s\n", user2.userName) // 这里 userName 的值由 yuelun 变成了 xunyu
	fmt.Printf("结构体指针用户 realName : %s\n", user2.realName) // 这里 userName 的值由 月轮 变成了 迅羽
	fmt.Printf("结构体指针用户 password : %s\n", user2.password)
	fmt.Printf("结构体指针用户 state : %d\n", user2.state)
}

// 结构体作为函数的参数:值传递
func printUser(user SysUser) {
	fmt.Printf("用户 userName : %s\n", user.userName)
	fmt.Printf("用户 realName : %s\n", user.realName)
	fmt.Printf("用户 password : %s\n", user.password)
	fmt.Printf("用户 state : %d\n", user.state)
}

// 结构体指针作为函数的参数:引用传递
func printUserPoint(user *SysUser) {
	fmt.Println("结构体指针用户信息更改前")
	fmt.Printf("结构体指针用户 userName : %s\n", user.userName)
	fmt.Printf("结构体指针用户 realName : %s\n", user.realName)
	fmt.Printf("结构体指针用户 password : %s\n", user.password)
	fmt.Printf("结构体指针用户 state : %d\n", user.state)
	user.userName = "xunyu"
	user.realName = "迅羽"
}

匿名结构体(继承)

Go里面也有继承,在某个结构体A里面,嵌套了一个匿名结构体B,那么就A就继承了B的所有属性和方法,不管匿名结构体首字母是大写小写的属性、方法,A都可以使用。

语法:

type goods struct {
	name string,
	price fload64
}
type book struct{
	goods, // 这里就是匿名结构体goods,这样book就继承了goods的属性和方法
	writer string
}

当结构体和匿名结构体有相同的属性或方法时,编译器采用就近原则,如果想访问匿名结构体的属性和方法,可以通过匿名结构体名来进行区分


当结构体里面有多个匿名结构体(多重继承),并且这多个匿名结构体里有同名的属性或方法,则访问这些属性和方法时,必须要指定匿名结构体,否则编译报错

type 商品 struct {
	name  string
	price float64
}

func (a *商品) show() {
	fmt.Println("商品 name=", a.name)
}

type 家电 struct {
	name  string
	brand string
}

func (a *家电) show() {
	fmt.Println("家电 name=", a.name)
}

type 电脑 struct {
	name string // 和匿名结构体的name属性相同
	商品          // 这里就是匿名结构体商品(嵌套了 商品 结构体,但是没有指定名字),这样 电脑 就继承了 商品 的属性和方法
	家电          // 这里就是匿名结构体商品(嵌套了 家电 结构体,但是没有指定名字),这样 电脑 就继承了 家电 的属性和方法
}

type 打印机 struct {
	*商品 // 这里就是匿名结构体商品(嵌套了 商品 结构体,但是没有指定名字),这样 打印机 就继承了 商品 的属性和方法
	*家电 // 这里就是匿名结构体商品(嵌套了 家电 结构体,但是没有指定名字),这样 打印机 就继承了 家电 的属性和方法
}

func main() {
	// 匿名结构体
	//var a 电脑
	//a.name = "联想Y9000" // 电脑 的name属性和匿名结构体属性同名了,采用就近原则,这里访问的是电脑的name属性,而不是商品或家电的name属性
	//a.price = 7000.0   // 这个是 商品 的属性,然后没有和其他结构体重名,所以可以直接调用
	//a.brand = "联想"     // 这个是 家电 的属性,然后没有和其他结构体重名,所以可以直接调用
	//a.商品.name = "电脑"   // 如果要访问匿名结构体的属性,可以通过匿名结构体去访问
	//a.家电.name = "联想"   // 如果要访问匿名结构体的属性,可以通过匿名结构体去访问
	a.show() // 因为匿名结构体 商品、家电 都有show方法,并且 电脑 自己本身没有同名方法,所以调用show时,必须要指定是哪个匿名结构体的show方法,否则报错:引用不明确
	//a.商品.show() // 必须指定匿名结构体
	//a.家电.show() // 必须指定匿名结构体

	//a := 电脑{"联想Y9000", 商品{"电脑", 7000.0}, 家电{"联想", "联想"}}
	//a := 电脑{"联想Y9000", 商品{name: "电脑", price: 7000.0}, 家电{name: "联想", brand: "联想"}}
	//a.商品.show() // 必须指定匿名结构体
	//a.家电.show() // 必须指定匿名结构体

	a := 打印机{&商品{name: "打印机", price: 3000.0}, &家电{name: "打印机", brand: "惠普"}}
	fmt.Println(a) // 这里打印的是商品、家电的地址
	//fmt.Println(a.商品, a.家电)
	fmt.Println(*a.商品, *a.家电)
}

有名结构体(组合)

有名结构体,又叫 组合

结构体嵌套了一个有名结构体,这种模式叫组合,如果是组合关系,那么在访问组合的结构体的方法或属性时,必须带上结构体的名字。

也就是说:在结构体A中,嵌套了一个结构体B,并且给B命名为b,那么就b相当于是一个B类型的变量。A同样可以访问B的所有属性和方法,只不过给B命名之后,要调用B里面的属性或方法,必须要结构体的名字。

type 书籍 struct {
	g 商品 // 这里给嵌套的结构体命名了,那么 书籍 和 商品 就是组合关系
}

func main() {
	// 有名结构体
	var b 书籍
	b.g.name = "《Go从入门到入土》" // 访问有名结构体里面的属性,必须要带上有名结构体名称
	b.g.show()
}

方法

方法和函数是不一样。方法是作用于指定的数据类型上的,也就是说和指定的数据类型绑定。自定义的类型(用type声明)都可以有方法,不只是结构体。

方法定义:func (变量名 数据类型) 方法名(参数 参数类型) 返回类型 {}

方法比函数多了 要绑定的数据类型。

比如:user结构体,它有一个方法,那这个方法就是和user绑定的,在 func (变量名 数据类型) 这里 数据类型 就是user类型

调用函数直接用 函数名() 即可,但是方法需要用变量.出来调用,比如一个结构体的方法,需要用 结构体变量.方法() 调用。


  • 函数:如果参数是值类型,则调用的时候只能是值类型,不能传引用类型,反之亦然。

  • 方法:

    • 如果参数是值类型,调用的时候可以是引用类型也可以是值类型,反之亦然。
    • 但是本质上是值类型还是引用类型,需要看方法的参数是用的是值类型还是引用类型。
    • 例如:func (u *user) SetAge(age int) {}
      • SetAge()方法,参数是 u *user,因为用的是指针,所以是引用类型;
      • 那么调用这个方法的时候,无论是用 u.SetAge()(这种调用方式其实是等价于 (&u).SetAge() 的) 还是 &u.SetAge(),其实都是引用传递。
      • 如果把 u *user 改成 u user,那么这里就是值类型,调用时,无论是用 u.SetAge() 还是 &u.SetAge(),都是值传递。

这里不知道大家能不能明白,我看视频的时候是看懂了,但是用文字总结出来的时候,总感觉说不清楚🤣

大家直接看下案例吧

person.go 这个文件在model包下

// 定义一个person结构体
type person struct {
	Name      string  // 姓名 首字母大写其他包可以访问
	accountNo string  // 账号(长度在6-10位之间) 首字母小写其他包无法访问
	pwd       string  // 密码(长度8-20)
	age       int     // 年龄(0-150)
	balance   float64 // 余额(大于0)
}

// 写一个工厂模式函数,相当于构造函数(返回指针类型)
func NewPerson(name string) *person {
	return &person{Name: name}
}

// 为了在其他包访问 accountNo、pwd、age、balance,我们编写一对setter、getter方法
// 这里 (p *person) 绑定的是person的指针类型,那么调用这个方法的时候,无论是用 p.SetAccountNo() 还是 &p.SetAccountNo(),其实都是引用传递
func (p *person) SetAccountNo(accountNo string) {
	// 账号长度在6-10之间,允许字母、数字、下划线,以字母开头
	rex := "^[a-zA-Z][a-zA-Z0-9_]{6,10}$"
	b, _ := regexp.MatchString(rex, accountNo)
	if !b {
		fmt.Println("账号格式错误,账号长度在6-10之间,允许字母、数字、下划线,以字母开头")
	}
	p.accountNo = accountNo
}
// (p person) 这里就是值传递了,那么调用这个方法的时候,无论是用 p.SetAccountNo() 还是 &p.SetAccountNo(),都是值传递
// func (p person) SetAccountNo(accountNo string) {}

func (p *person) GetAccountNo() string {
	return p.accountNo
}

func (p *person) SetPwd(pwd string) {
	// 密码长度在8-20之间,必须包含数字、大小写字母、特殊字符串
	rex := "^[a-zA-Z][a-zA-Z0-9_]{8,20}$"
	b, _ := regexp.MatchString(rex, pwd)
	if !b {
		fmt.Println("密码格式错误,密码长度在8-20之间,允许字母、数字、下划线,以字母开头")
	}
	p.pwd = pwd
}
func (p *person) GetPwd() string {
	return p.pwd
}

main.go 这个文件在main包下

import (
	"fmt"
	"go-demo1/model" // 这里引入model包(go-demo1是项目名)
)

func main() {
	// NewPerson返回的是指针类型
	p := model.NewPerson("符华") // model包,点出这个包里面的NewPerson函数
	p.SetAccountNo("fu_hua01")
	p.SetPwd("fuhua_001")
	p.SetAge(20)
	p.SetBalance(100000)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/888035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++通过gsop调用基于https的webservice接口总结

ww哦步骤: 第一步:生成头文件 webservice接口一般会有一个对外接口文档。比如:http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL 问号后面的参数表示WSDL文档,是一个XML文档,看不懂配置没关系&a…

UglifyJS 和JShaman相比有什么不同?都可以进行js混淆加密吗?

UglifyJS 和JShaman相比有什么不同? UglifyJS主要功能是压缩JS代码,减小代码体积;JShaman是专门用于对JS代码混淆加密,目的是让JS代码变的不可读、混淆功能逻辑、加密代码中的隐秘数据或字符,是用于代码保护的。 因此…

linux两台服务器互相备份文件(sshpass + crontab)

crontab crontab是linux系统自带的定时调度软件,可用于设置周期性被执行的指令,一般用在每天的非高峰负荷时间段运行作业,可在无需人工干预的情况下运行作业。支持在一周或一月中的不同时段运行。 crontab命令允许用户提交、编辑或删除相应的…

【HBZ分享】java中的BitSet 与 Redis中的BitMap 与 布隆过滤器

BitMap的存储原理 bitMap他会标识出某个整数是否存在,存在即为1,不存在对应位即为0bitMap是存储int类型的,int 4byte, 1byte 8bit,因此bitMap数组中的每个下标可以标识出32个数字是否存在bitMap相当于一个个小格子&…

【数据结构】二叉树的链式结构的实现 -- 详解

一、前置说明 在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习。 typedef char BTDataType;typedef struct Binar…

管理类联考——逻辑——真题篇——按知识分类——汇总篇——一、形式逻辑——模态——-句式转换+性质

文章目录 第三节 模态命题-句式转换性质题-模态命题-句式转换性质-句式转换-逻辑转换:①不一定不可能;②不一定可能不未必。句式转换-语文转换:①一定不可能不必然;②一定不不可能必然不。性质-两命题间的关系-包含:①…

c语言——计算两个数的乘积

//计算两个数的乘积 #include<stdio.h> #include<stdlib.h> int main() {double firstNumber,secondNumber,product;printf("两个浮点数&#xff1a;");scanf("%lf,%lf",&firstNumber,&secondNumber);productfirstNumber*secondNumber…

shell scripts 学习记录

shell scripts 学习记录 1. 环境变量的使用2. Shell中的数组使用Array关联数组 (理解为python中的字典) 3. shell中的基本运算符4. shell 中流程控制语法case...esac使用 5. 函数定义与调用5.1 带返回值的函数5.2 带传参数的函数 6. shell 中的输入/输出重定向6.1 输出重定向6.…

糖尿病视网膜病变,黄斑病变,年龄相关检测研究(Matlab代码)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

sCrypt于8月12日在上海亮相BSV数字未来论坛

2023年8月12日&#xff0c;由上海可一澈科技有限公司&#xff08;以下简称“可一科技”&#xff09;、 临港国际科创研究院发起&#xff0c;携手美国sCrypt公司、福州博泉网络科技有限公司、复旦大学区块链协会&#xff0c;举办的BSV数字未来论坛在中国上海成功落下帷幕。 本次…

好消息!多地明确鼓励提取公积金作首付

大家好&#xff01; 我是老洪&#xff0c;看到一则公积金资讯&#xff0c;聊两句。 据媒体报道&#xff0c;多地明确鼓励提取公积金作首付。 我个人觉得鼓励提取公积金作首付&#xff0c;对于购房者来说挺好的。 这种做法不仅可以减轻首付资金压力&#xff0c;还可以提高购房者…

出现丢包问题的情况以及解决方法

为什么会出现丢包&#xff1f; 我们在网络上传输数据的时候&#xff0c;数据需要经过很多的路由器/交换机&#xff08;交通枢纽&#xff09;进行传输&#xff0c;而路由器/交换机的结构是很复杂的&#xff0c;传输的数据量也是不确定的&#xff0c;可能这一会传输的数据比较少&…

mysql 03.查询(重点)

先准备测试数据&#xff0c;代码如下&#xff1a; -- 创建数据库 DROP DATABASE IF EXISTS mydb; CREATE DATABASE mydb; USE mydb;-- 创建student表 CREATE TABLE student (sid CHAR(6),sname VARCHAR(50),age INT,gender VARCHAR(50) DEFAULT male );-- 向student表插入数据…

vscode用ssh远程连接linux

1、vscode是利用ssh远程连接linux的&#xff0c;所以首先确保vscode已经安装了这两个插件 2、点击左下角的连接 3、选择Connect to Host…… 5、按格式输入 ssh 主机名ip 比如我的&#xff1a;ssh mnt192.168.198.128 6、选择第一个打开配置文件&#xff0c;确保输入正确 7、…

el-table :span-method合并单元格

2023.8.17今天我学习了如何使用el-table进行单元格的合并&#xff0c;效果如下&#xff1a; 在开发的过程中&#xff0c;如果有多个重复的值&#xff0c;我们希望可以进行合并显示&#xff0c;这样就不会显得重复太多&#xff0c;el-table有自带的方法&#xff1a; Element - …

安装编译器

安装MinGW&#xff0c;下载地址&#xff1a; MinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net 下载x86_64-win32-seh&#xff0c;下载完解压到相应的路径下。

Leetcode-每日一题【剑指 Offer 32 - I. 从上到下打印二叉树】

题目 从上到下打印出二叉树的每个节点&#xff0c;同一层的节点按照从左到右的顺序打印。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回&#xff1a; [3,9,20,15,7] 提示&#xff1a; 节点总数 < 1000 解题思路 1.题目要求我们从…

创建SpringBoot项目报错 - Process finished with exit code 1

错误信息 [ERROR] The build could not read 1 project -> [Help 1] [ERROR] [ERROR] The project com.example:ruoyi-excel-demo:0.0.1-SNAPSHOT (D:\Java\work\ruoyiExcelDemo\pom.xml) has 1 error [ERROR] Non-resolvable parent POM for com.example:ruoyi-e…

爬虫逆向实战(十六)--某建筑市场平台

一、数据接口分析 主页地址&#xff1a;某建筑市场平台 1、抓包 通过抓包可以发现数据接口是list 2、判断是否有加密参数 请求参数是否加密&#xff1f; 无请求头是否加密&#xff1f; 无响应是否加密&#xff1f; 通过查看“响应”模块可以发现&#xff0c;返回的响应是…

【共同缔造 情暖襄阳】 暑期关爱未成年人志愿服务活动合集(三)

结合2023年襄阳市民政局“共同缔造 情暖襄阳”社会工作服务项目&#xff0c;在襄阳市民政局、襄州区民政局支持下&#xff0c;襄州社工协会联合肖湾街道育红社区开展暑期“希望家园”志愿服务活动&#xff0c;关爱未成年人。 8月4日&#xff0c;为培育孩子们广泛的兴趣爱好和动…