golang_3_结构体

1.声明结构体

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

注意事项和细节说明
1)字段声明语法同变量,示例:字段名 字段类型
2)字段的类型可以为:基本类型、数组或引用类型
3)在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的 一样:
布尔类型是 false ,数值是 0 ,字符串是 “”。
数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
指针,slice,和 map 的零值都是 nil ,即还没有分配空间。

//如果结构体的字段类型是: 指针,slice,和map的零值都是 nil ,即还没有分配空间
//如果需要使用这样的字段,需要先make,才能使用.

type Person struct{
	Name string
	Age int
	Scores [5]float64
	ptr *int //指针 
	slice []int //切片
	map1 map[string]string //map
}
//定义结构体变量
	var p1 Person
	fmt.Println(p1)

	//使用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)

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-直接声明
案例演示: var person Person
方式 2-{}
案例演示: var person Person = Person{}

//方式2
	p2 := Person{"mary", 20}
	// p2.Name = "tom"
	// p2.Age = 18
	fmt.Println(p2)

方式 3-&
案例: var person *Person = new (Person)

//方式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{}

//方式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)

1)第 3 种和第 4 种方式返回的是 结构体指针。
2)结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = “tom”
3)但 go 做了一个简化,也支持 结构体指针.字段名, 比如 person.Name = “tom”。更加符合程序员 使用的习惯,go 编译器底层 对 person.Name 做了转化 (*person).Name。
结构体使用注意事项和细节
1)结构体的所有字段在内存中是连续的
2)结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类 型)
3)结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
4)struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序 列化和反序列化。

2.方法

方法的声明和调用

type A struct {
 Num int 
 }
 func (a A) test() {
  fmt.Println(a.Num) 
 }

对上面的语法的说明
1)func (a A) test() {} 表示 A 结构体有一方法,方法名为 test
2)(a A) 体现 test 方法是和 A 类型绑定的

//给Person类型绑定一方法
func (person Person) test() {
	person.Name = "jack"
	fmt.Println("test() name=", person.Name) // 输出jack
}
func main() {
	var p Person
	p.Name = "tom"
	p.test() //调用方法
	fmt.Println("main() p.Name=", p.Name) //输出 tom
}

总结:
1)test 方法和 Person 类型绑定
2)test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调 用
3)func (p Person) test() {}… p 表示哪个 Person 变量调用,这个 p 就是它的副本, 这点和函数传参非 常相似。
4)p 这个名字,有程序员指定,不是固定, 比如修改成 person 也是可以

方法的声明(定义)
1)参数列表:表示方法输入
2)recevier type : 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
3)receiver type : type 可以是结构体,也可以其它的自定义类型
4)receiver : 就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)
5)返回值列表:表示返回的值,可以多个
6)方法主体:表示为了实现某一功能代码块 7) return 语句不是必须的。

方法的注意事项和细节
1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
2)如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理

//为了提高效率,通常我们方法和结构体的指针类型绑定
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 
	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
}

3)Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是 struct, 比如 int , float32 等都可以有方法

type integer int

func (i integer) print() {
	fmt.Println("i=", i)
}
//编写一个方法,可以改变i的值
func (i *integer) change() {
	*i = *i + 1
}
func main() {
	var i integer = 10
	i.print()
	i.change()
	fmt.Println("i=", i)
} 

4)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母 大写,可以在本包和其它包访问。[讲解]
5)如果一个类型实现了 String()这个方法,那么 fmt.Println 默认会调用这个变量的 String()进行输出

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
}

//定义一个Student变量
	stu := Student{
		Name : "tom",
		Age : 20,
	}
	//如果你实现了 *Student 类型的 String方法,就会自动调用
	fmt.Println(&stu) 

3.方法和函数区别

1)调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
2)对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
3)对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反 过来同样也可以
总结:
1)不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
2)如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则 是地址拷贝。

4.工厂模式

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

//定义一个结构体
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
}
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())
}

面向对象编程demo

//定义一个结构体Account
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 : "gs1111111",
		Pwd : "666666",
		Balance : 100.0,
	}
	//这里可以做的更加灵活,就是让用户通过控制台来输入命令...
	//菜单....
	account.Query("666666")
	account.Deposite(200.0, "666666")
	account.Query("666666")
	account.WithDraw(150.0, "666666")
	account.Query("666666")
}

面向对象编程三大特性-封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其 它包只有通过被授权的操作(方法),才能对字段进行操作
封装的理解和好处
1)隐藏实现细节
2)可以对数据进行验证,保证安全合理(Age)
如何体现封装
1)对结构体中的属性进行封装
2)通过方法,包 实现封装
封装的实现步骤
1)将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
2) 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
3)提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值 func (var 结构体类型名) SetXxx(参数列表) (返回值列表) { //加入数据验证的业务逻辑 var.字段 = 参数 }
4)提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值 func (var 结构体类型名) GetXxx() { return var.age; } 特别说明:在 Golang 开发中并没有特别强调封装,这点并不像 Java. 所以提醒学过 java 的朋友, 不用总是用 java 的语法特性来看待 Golang, Golang 本身对面向对象的特性做了简化的.

面向对象编程三大特性-继承

提取出共有的方法,供其他包继承使用。
也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访 问匿名结构体的字段和方法,从而实现了继承特性。

//编写一个学生考试系统

type Student struct {
	Name string
	Age int
	Score int
}

//将Pupil 和 Graduate 共有的方法也绑定到 *Student
func (stu *Student) ShowInfo() {
	fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
	//业务判断
	stu.Score = score
}

//小学生
type Pupil struct { 
	Student //嵌入了Student匿名结构体
}

//显示他的成绩

//这时Pupil结构体特有的方法,保留
func (p *Pupil) 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))
}

结构体继承

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
	Name string 
}

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 = "smith"
	// b.age = 20
	// b.SayOk()
	// b.hello()

	var b B
	b.Name = "jack" // ok
	b.A.Name = "scott"
	b.age = 100  //ok
	b.SayOk()  // B SayOk  jack
	b.A.SayOk() //  A SayOk scott
	b.hello() //  A hello ? "jack" 还是 "scott"
}

嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

type Goods struct {
	Name string
	Price float64
}

type Brand struct {
	Name string
	Address string
}

type TV struct {
	Goods
	Brand	
}

type TV2 struct {
	*Goods
	*Brand	
}
//嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
	tv := TV{ Goods{"电视机001", 5000.99},  Brand{"海尔", "山东"}, }

	//演示访问Goods的Name
	fmt.Println(tv.Goods.Name)
	fmt.Println(tv.Price) 

	tv2 := TV{ 
		Goods{
			Price : 5000.99,
			Name : "电视机002", 
		},  
		Brand{
			Name : "夏普", 
			Address :"北京",
		}, 
	}

	fmt.Println("tv", tv)
	fmt.Println("tv2", tv2)

	tv3 := TV2{ &Goods{"电视机003", 7000.99},  &Brand{"创维", "河南"}, }

	tv4 := TV2{ 
			&Goods{
				Name : "电视机004", 
				Price : 9000.99,
			},  
			&Brand{
				Name : "长虹", 
				Address : "四川",
			}, 
		}

	fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
	fmt.Println("tv4", *tv4.Goods, *tv4.Brand)

接口(interface)

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个 自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)
在这里插入图片描述小结说明:
1)接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的 多态和高内聚低偶合的思想。
2)Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个 变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字
注意事项和细节
1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
2)接口中所有的方法都没有方法体,即都是没有实现的方法。
3)在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现 了该接口。
4)一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
5)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
6)一个自定义类型可以实现多个接口
7)Golang 接口中不能有任何变量
8)一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必 须将 B,C 接口的方法也全部实现。
9)interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
10)空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量 赋给空接口。

接口编程的最佳实践

实现对 Hero 结构体切片的排序: sort.Sort(data Interface)

package main

import (
	"fmt"
	"math/rand"
	"sort"
)

//1.声明Hero结构体
type  Hero struct{
	Name string
	Age int
}

//2.声明一个Hero结构体切片类型
type HeroSlice []Hero

//3.实现Interface 接口
func (hs HeroSlice) Len() int {
	return len(hs)
}

//Less方法就是决定你使用什么标准进行排序
//1. 按Hero的年龄从小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
	return hs[i].Age < hs[j].Age
	//修改成对Name排序
	//return hs[i].Name < hs[j].Name
}

func (hs HeroSlice) Swap(i, j int) {
	//交换
	// temp := hs[i]
	// hs[i] = hs[j]
	// hs[j] = temp
	//下面的一句话等价于三句话
	hs[i], hs[j] = hs[j], hs[i]
}


//1.声明Student结构体
type  Student struct{
	Name string
	Age int
	Score float64
}

//将Student的切片,安Score从大到小排序!!

func main() {

	//先定义一个数组/切片
	var intSlice = []int{0, -1, 10, 7, 90}
	//要求对 intSlice切片进行排序
	//1. 冒泡排序...
	//2. 也可以使用系统提供的方法 
	sort.Ints(intSlice) 
	fmt.Println(intSlice)

	//请大家对结构体切片进行排序
	//1. 冒泡排序...
	//2. 也可以使用系统提供的方法

	//测试看看我们是否可以对结构体切片进行排序
	var heroes HeroSlice
	for i := 0; i < 10 ; i++ {
		hero := Hero{
			Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),
			Age : rand.Intn(100),
		}
		//将 hero append到 heroes切片
		heroes = append(heroes, hero)
	}

	//看看排序前的顺序
	for _ , v := range heroes {
		fmt.Println(v)
	}

	//调用sort.Sort
	sort.Sort(heroes)
	fmt.Println("-----------排序后------------")
	//看看排序后的顺序
	for _ , v := range heroes {
		fmt.Println(v)
	}

	i := 10
	j := 20
	i, j = j, i
	fmt.Println("i=", i, "j=", j) // i=20 j = 10
}

接口vs继承

package main
import (
	"fmt"
)

//Monkey结构体
type Monkey struct {
	Name string
}

//声明接口
type BirdAble interface {
	Flying()
}

type FishAble interface {
	Swimming()
}

func (this *Monkey) climbing() {
	fmt.Println(this.Name, " 生来会爬树..")
}

//LittleMonkey结构体
type LittleMonkey struct {
	Monkey //继承
}


//让LittleMonkey实现BirdAble
func (this *LittleMonkey) Flying() {
	fmt.Println(this.Name, " 通过学习,会飞翔...")
}

//让LittleMonkey实现FishAble
func (this *LittleMonkey) Swimming() {
	fmt.Println(this.Name, " 通过学习,会游泳..")
}

func main() {

	//创建一个LittleMonkey 实例
	monkey := LittleMonkey{
		Monkey {
			Name : "悟空",
		},
	}
	monkey.climbing()
	monkey.Flying()
	monkey.Swimming()

}

1)当 A 结构体继承了 B 结构体,那么 A 结构就自动的继承了 B 结构体的字段和方法,并且可以直 接使用
2)当 A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我 们可以认为:实现接口是对继承机制的补充.

接口和继承解决的解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。

接口比继承更加灵活 Person Student BirdAble LittleMonkey
接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
接口在一定程度上实现代码解耦

面向对象编程-多态

变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可 以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态

类型断言

由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。
实践1

package main
import (
	"fmt"
)

//声明/定义一个接口
type Usb interface {
	//声明了两个没有实现的方法
	Start()
	Stop()
}

type Phone struct {
	name string
}  

//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
	fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
	fmt.Println("手机停止工作。。。")
}

func (p Phone) Call() {
	fmt.Println("手机 在打电话..")
}


type Camera struct {
	name string
}
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
	fmt.Println("相机停止工作。。。")
}

type Computer struct {

}

func (computer Computer) Working(usb Usb) {
	usb.Start()
	//如果usb是指向Phone结构体变量,则还需要调用Call方法
	//类型断言..[注意体会!!!]
	if phone, ok := usb.(Phone); ok {
		phone.Call()
	}
	usb.Stop()
}

func main() {
	//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
	//这里就体现出多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"vivo"}
	usbArr[1] = Phone{"小米"}
	usbArr[2] = Camera{"尼康"}

	//遍历usbArr
	//Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,
	//除了调用Usb 接口声明的方法外,还需要调用Phone 特有方法 call. =》类型断言
	var computer Computer
	for _, v := range usbArr{
		computer.Working(v)
		fmt.Println()
	}
	//fmt.Println(usbArr)
}

实践2

package main
import (
	"fmt"
)


//定义Student类型
type Student struct {

}

//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items... interface{}) {
	for index, x := range items {
		switch x.(type) {
			case bool :
				fmt.Printf("第%v个参数是 bool 类型,值是%v\n", index, x)
			case float32 :
				fmt.Printf("第%v个参数是 float32 类型,值是%v\n", index, x)
			case float64 :
				fmt.Printf("第%v个参数是 float64 类型,值是%v\n", index, x)
			case int, int32, int64 :
				fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)
			case string :
				fmt.Printf("第%v个参数是 string 类型,值是%v\n", index, x)
			case Student :
				fmt.Printf("第%v个参数是 Student 类型,值是%v\n", index, x)
			case *Student :
				fmt.Printf("第%v个参数是 *Student 类型,值是%v\n", index, x)
			default :
				fmt.Printf("第%v个参数是  类型 不确定,值是%v\n", index, x)
		}
	}
}
func main() {
	var n1 float32 = 1.1
	var n2 float64 = 2.3
	var n3 int32 = 30
	var name string = "tom"
	address := "北京"
	n4 := 300
	stu1 := Student{}
	stu2 := &Student{}
	TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值