logo

方法 概念、函数 学习
方法的概念 Go语言同时有函数和方法,方法的本质是函数,但是方法和函数又有所不同。 1. 含义不同 函数function是一段具有独立功能的代码,可以被反复多次调用,从而实现代码复用。而方法method是一个类的行为功能,只有该类的对象才能调用。 2. 方法有接受者,而函数无接受者 Go语言的方法method是一种作用于特定类型变量的函数。这种特定类型变量叫做Receiver(接受者、接收者、接收器)。接受者的概念类似于传统面向对象语言中的this或self关键字。 Go语言的接受者强调了方法具有作用对象,而函数没有作用对象。一个方法就是一个包含了接受者的函数。 Go语言中,接受者的类型可以是任何类型,不仅仅是结构体,也可以是struct类型外的其他任何类型。 3. 函数不可以重名,而方法可以重名 只要接受者不同,方法名就可以相同。 基本语法 方法的语法格式如下。 func (接受者变量 接受这类型) 方法名(参数列表)(返回值列表){ //方法体 } 接受者在func关键字和方法名之间编写,接受者可以是struct类型或非struct类型,可以是指针类型和非指针类型。接受者中的变量在命名时,官方建议使用接受者类型的第一个小写字母。下面通过一个示例对比函数和方法在语法上的区别,具体如例所示。 package main import "fmt" type Employee struct { name , currency string salary float64 } func main() { emp1 := Employee{"Daniel" , "$" , 2000} emp1.printSalary() printSalary(emp1) } //printSalary方法 func (e Employee) printSalary() { fmt.Printf("员工姓名:%s ,薪资:%s%.2f \n", e.name , e.currency , e.salary) } //printSalary函数 func printSalary(e Employee) { fmt.Printf("员工姓名:%s ,薪资:%s%.2f \n", e.name , e.currency , e.salary) } 运行 员工姓名:Daniel ,薪资:$2000.00 员工姓名:Daniel ,薪资:$2000.00 方法和函数 一段程序可以用函数来写,却还要使用方法,主要有以下两个原因。 Go不是一种纯粹面向对象的编程语言,它不支持类。因此其方法旨在实现类似于类的行为。 相同名称的方法可以在不同的类型上定义,而具有相同名称的函数是不允许的。假设有一个正方形和一个圆形,可以分别在正方形和圆形上定义一个名为Area的求取面积的方法。 下面通过一个示例来观察不同的结构体中方法名,如例所示。 package main import ( "math" "fmt" ) type Rectangle struct { width, height float64 } type Circle struct { radius float64 } func main() { r1:=Rectangle{10,4} r2:=Rectangle{12,5} c1:=Circle{1} c2:=Circle{10} fmt.Println("r1的面积" , r1.Area()) fmt.Println("r2的面积" , r2.Area()) fmt.Println("c1的面积" , c1.Area()) fmt.Println("c2的面积" , c2.Area()) } //定义Rectangle的方法 func (r Rectangle) Area() float64 { return r.width * r.height } //定义C ircle的方法 func (c Circle) Area() float64 { return c.radius * c.radius * math.Pi } 运行 r1的面积 40 r2的面积 60 c1的面积 3.141592653589793 c2的面积 314.1592653589793 若方法的接受者不是指针,实际只是获取了一个拷贝,而不能真正改变接受者中原来的数据。当指针当接受者时,情况如例所示。 package main import "fmt" type Rectangle struct { width, height float64 } func main() { r1 := Rectangle{5 , 8} r2 := r1 //打印对象的内存地址 fmt.Printf("r1的地址:%p \n" , &r1) fmt.Printf("r2的地址:%p \n" , &r2) r1.setValue() fmt.Println("r1.height=" , r1.height)//8 fmt.Println("r2.height=" , r2.height)//8 fmt.Println("----------------") r1.setValue2() fmt.Println("r1.height=" , r1.height)//8?20? fmt.Println("r2.height=" , r2.height)//8 fmt.Println("----------------") } func (r Rectangle) setValue() { fmt.Printf("setValue方法中r的地址:%p \n" , &r) r.height = 10 } func (r *Rectangle) setValue2() { fmt.Printf("setValue2方法中r的地址:%p \n" , r) r.height = 20 } 运行 r1的地址:0xc0000100b0 r2的地址:0xc0000100c0 setValue方法中r的地址:0xc0000100f0 r1.height= 8 r2.height= 8 ---------------- setValue2方法中r的地址:0xc0000100b0 r1.height= 20 r2.height= 8 ----------------
结构体(二) 学习
匿名结构体和匿名字段 1.匿名结构体 匿名结构体就是没有名字的结构体,无需通过type关键字定义就可以直接使用。创建匿名结构体时,同时要创建对象。匿名结构体由结构体定义和键值对初始化两部分组成。语法格式示例如下。 变量名 := struct { //定义成员属性 } {//初始化成员属性} package main import ( "math" "fmt" ) func main() { //匿名函数 res := func(a, b float64) float64 { return math.Pow(a, b) }(2, 3) fmt.Println(res) //匿名结构体 addr := struct { province, city string }{"陕西省", "西安市"} fmt.Println(addr) cat := struct { name, color string age int8 }{ name: "绒毛", color: "黑白", age: 1, } fmt.Println(cat) } 运行 8 {陕西省 西安市} {绒毛 黑白 1} 2.结构体的匿名字段 匿名字段就是在结构体中的字段没有名字,只包含一个没有字段名的类型。这些字段被称为匿名字段。 如果字段没有名字,那么默认使用类型作为字段名,同一个类型只能写一个。结构体嵌套中采用匿名结构体字段可以模拟继承关系。 package main import "fmt" type User struct { //name string //sex byte //age int8 //height float64 //weight float64 string byte int8 float64 } func main() { // 实例化结构体 user:= User{"Steven" , 'm' , 35 , 177.5} fmt.Println(user) //如果想依次输出姓名、年龄、身高、性别 fmt.Printf("姓名:%s \n" , user.string) fmt.Printf("身高:%.2f \n" , user.float64) fmt.Printf("性别:%c \n" , user.byte) fmt.Printf("年龄:%d \n" , user.int8) } 运行 {Steven 109 35 177.5} 姓名:Steven 身高:177.50 性别:m 年龄:35 结构体嵌套 将一个结构体作为另一个结构体的属性(字段),这种结构就是结构体嵌套。 结构体嵌套可以模拟面向对象中的以下两种关系。 聚合关系:一个类作为另一个类的属性 继承关系:一个类作为另一个类的子类。子类和父类。 继承是传统面向对象编程中三大特征之一。用于描述两个类之间的关系。一个类(子类、派生类)继承于另一个类(父类、超类)。 子类可以有自己的属性和方法,也可以重写父类已有的方法。子类可以直接访问父类所有的属性和方法。 在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。换言之,父类中的字段就是提升字段。 继承既可以避免重复代码,又可以扩展类的功能。 采用匿名字段的形式就是模拟继承关系。而模拟聚合关系时一定要采用有名字的结构体作为字段。 结构体嵌套时,可能拥有相同的成员名,成员重名会发生成员名字冲突,示例如下。 package main import "fmt" type Address struct { province, city string } type Person struct { name string age int address *Address } func main() { //模拟结构体对象之间的聚合关系 p := Person{} p.name = "Steven" p.age = 35 //赋值方式1: addr := Address{} addr.province = "北京市" addr.city = "海淀区" p.address = &addr fmt.Println(p) fmt.Println("姓名:", p.name, " 年龄:", p.age, " 省:", p.address.province," 市:", p.address.city) fmt.Println("-----------------------") //修改Person对象的数据,是否会影响Address对象的数据? p.address.city = "昌平区" fmt.Println("姓名:", p.name," 年龄:", p.age," 省:", p.address.province," 市:", p.address.city," addr市:", addr.city) fmt.Println("-----------------------") //修改Address对象的数据,是否会影响Person对象的数据? addr.city = "大兴区" fmt.Println("姓名:", p.name," 年龄:", p.age," 省:", p.address.province," 市:", p.address.city," addr市:", addr.city) fmt.Println("-----------------------") //赋值方式2 p.address = &Address{ province: "陕西省", city: "西安市", } fmt.Println(p) fmt.Println("姓名:", p.name, " 年龄:", p.age, " 省:", p.address.province," 市:", p.address.city) } 运行 {Steven 35 0xc0000044a0} 姓名:Steven 年龄:35 省:北京市 市:海淀区 ----------------------- 姓名:Steven 年龄:35 省:北京市 市:昌平区 addr市:昌平区 ----------------------- 姓名:Steven 年龄:35 省:北京市 市:大兴区 addr市:大兴区 ----------------------- {Steven 35 0xc000004500} 姓名:Steven 年龄:35 省:陕西省 市:西安市
结构体(一) 学习
定义结构体与实例化 单一的数据类型已经满足不了现实开发需求,于是Go语言提供了结构体来定义复杂的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。 结构体的定义格式,具体示例如下。 type 类型名 struct { 成员属性1 类型1 成员属性2 类型2 成员属性3, 成员属性4 类型3 } 在使用结构体的过程中注意以下三点。 类型名:标识结构体的名称,在同一个包内不能重复。 结构体中属性,也叫字段,必须唯一。 同类型的成员属性可以写在一行。 定义结构体与实例化 结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正分配内存。因此必须在定义结构体并实例化后才能使用结构体; 实例化就是根据结构体定义的格式创建一份与格式一致的内存区域。结构体实例之间的内存是完全独立的。实例化的语法如例所示。 package main import "fmt" //定义Teacher结构体 type Teacher struct { name string age int8 sex byte } func main() { //1、var声明方式实例化结构体,初始化方式为:对象.属性=值 var t1 Teacher fmt.Println(t1) fmt.Printf("t1:%T , %v , %q \n", t1, t1, t1) //if t1 == nil { // fmt.Println() //} t1.name = "Steven" t1.age = 35 t1.sex = 1 fmt.Println(t1) fmt.Println("-------------------") //2、变量简短声明格式实例化结构体,初始化方式为:对象.属性=值 t2 := Teacher{} t2.name = "David" t2.age = 30 t2.sex = 1 fmt.Println(t2) fmt.Println("-------------------") //3、变量简短声明格式实例化结构体,声明时初始化。初始化方式为:属性:值 。属性:值可以同行,也可以换行。(类似map的用法) t3 := Teacher{ name: "Josh", age: 28, sex: 1, } t3 = Teacher{name: "Josh2", age: 27, sex: 1} fmt.Println(t3) fmt.Println("-------------------") //4、变量简短声明格式实例化结构体,声明时初始化,不写属性名,按属性顺序只写属性值 t4 := Teacher{"Ruby", 30, 0} fmt.Println(t4) fmt.Println("-------------------") } 运行 { 0 0} t1:main.Teacher , { 0 0} , {"" '\x00' '\x00'} {Steven 35 1} ------------------- {David 30 1} ------------------- {Josh2 27 1} ------------------- {Ruby 30 0} ------------------- 结构体的语法糖 语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。 通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。结构体和数组中都含有语法糖。 使用内置函数new()对结构体进行实例化,结构体实例化后形成指针类型的结构体。new内置函数会分配内存。第一个参数是类型,而不是值,返回的值是指向该类型新分配的零值的指针。该函数用于创建某个类型的指针。 package main import "fmt" //定义结构体 Emp type Emp struct { name string age int8 sex byte } func main() { //使用new()内置函数实例化struct emp1 := new(Emp) fmt.Printf("emp1: %T , %v , %p \n", emp1, emp1, emp1) (*emp1).name = "David" (*emp1).age = 30 (*emp1).sex = 1 //语法糖写法 emp1.name = "David2" emp1.age = 31 emp1.sex = 1 fmt.Println(emp1) fmt.Println("-----------------") SyntacticSugar() } func SyntacticSugar() { // 数组中的语法糖 arr := [4]int{10, 20, 30, 40} arr2 := &arr fmt.Println((*arr2)[len(arr)-1]) fmt.Println(arr2[0]) // 切片中的语法糖? arr3 := []int{100, 200, 300, 400} arr4 := &arr3 fmt.Println((*arr4)[len(arr)-1]) //fmt.Println(arr4[0]) } 运行 emp1: *main.Emp , &{ 0 0} , 0xc0000044a0 &{David2 31 1} ----------------- 40 10 400 结构体是值类型 结构体作为函数参数,若复制一份传递到函数中,在函数中对参数进行修改,不会影响到实际参数。证明结构体是值类型。如例所示。 package main import "fmt" type Human struct { name string age int8 sex byte } func main() { //1、初始化Human h1 := Human{"Steven", 35, 1} fmt.Printf("h1: %T , %v, %p \n", h1, h1, &h1) fmt.Println("--------------------") //将结构体对象拷贝 h2 := h1 h2.name = "David" h2.age = 30 fmt.Printf("h2修改后: %T , %v, %p \n", h2, h2, &h2) fmt.Printf("h1: %T , %v, %p \n", h1, h1, &h1) fmt.Println("--------------------") //将结构体对象作为参数传递 changeName(h1) fmt.Printf("h1: %T , %v, %p \n", h1, h1, &h1) } func changeName(h Human) { h.name = "Daniel" h.age = 13 fmt.Printf("函数体内修改后:%T, %v , %p \n" , h , h , &h) } 运行 h1: main.Human , {Steven 35 1}, 0xc0000044a0 -------------------- h2修改后: main.Human , {David 30 1}, 0xc000004520 h1: main.Human , {Steven 35 1}, 0xc0000044a0 -------------------- 函数体内修改后:main.Human, {Daniel 13 1} , 0xc0000045c0 h1: main.Human , {Steven 35 1}, 0xc0000044a0 结构体的深拷贝和浅拷贝 值类型是深拷贝,深拷贝就是为新的对象分配了内存。引用类型是浅拷贝,浅拷贝只是复制了对象的指针。结构体的拷贝实例,如例所示。 package main import ( "fmt" ) type Dog struct { name string color string age int8 kind string } func main() { //1、实现结构体的深拷贝 //struct是值类型,默认的复制就是深拷贝 d1 := Dog{"豆豆", "黑色", 2, "二哈"} fmt.Printf("d1: %T , %v , %p \n", d1, d1, &d1) d2 := d1 //深拷贝 fmt.Printf("d2: %T , %v , %p \n", d2, d2, &d2) d2.name = "毛毛" fmt.Println("d2修改后:", d2) fmt.Println("d1:", d1)//d1没有因为d2的改变而改变,所以是深拷贝 fmt.Println("------------------") //2、实现结构体浅拷贝:直接赋值指针地址 d3 := &d1 fmt.Printf("d3: %T , %v , %p \n", d3, d3, d3) d3.name = "球球" d3.color = "白色" d3.kind = "萨摩耶" fmt.Println("d3修改后:", d3) fmt.Println("d1:", d1)//d1因为d3的改变而改变,所以是浅拷贝 fmt.Println("------------------") //3、实现结构体浅拷贝:通过new()函数来实例化对象 d4 := new(Dog) d4.name = "多多" d4.color = "棕色" d4.age = 1 d4.kind = "巴哥犬" d5 := d4 fmt.Printf("d4: %T , %v , %p \n", d4, d4, d4) fmt.Printf("d5: %T , %v , %p \n", d5, d5, d5) fmt.Println("------------------") d5.color = "金色" d5.kind = "金毛" fmt.Println("d5修改后:", d5) fmt.Println("d4:", d4) fmt.Println("------------------") } 运行 h1: main.Human , {Steven 35 1}, 0xc0000044a0 -------------------- h2修改后: main.Human , {David 30 1}, 0xc000004520 h1: main.Human , {Steven 35 1}, 0xc0000044a0 -------------------- 函数体内修改后:main.Human, {Daniel 13 1} , 0xc0000045c0 h1: main.Human , {Steven 35 1}, 0xc0000044a0 D:\www\go>go run test.go d1: main.Dog , {豆豆 黑色 2 二哈} , 0xc000044040 d2: main.Dog , {豆豆 黑色 2 二哈} , 0xc000044100 d2修改后:{毛毛 黑色 2 二哈} d1:{豆豆 黑色 2 二哈} ------------------ d3: *main.Dog , &{豆豆 黑色 2 二哈} , 0xc000044040 d3修改后:&{球球 白色 2 萨摩耶} d1:{球球 白色 2 萨摩耶} ------------------ d4: *main.Dog , &{多多 棕色 1 巴哥犬} , 0xc000044300 d5: *main.Dog , &{多多 棕色 1 巴哥犬} , 0xc000044300 ------------------ d5修改后:&{多多 金色 1 金毛} d4:&{多多 金色 1 金毛} ------------------ 结构体作为函数的参数及返回值 结构体作为函数的参数及返回值有两种形式,值传递和引用传递,接下来通过案例了解两者的区别 package main import "fmt" type Flower struct { name, color string } func main() { //1、结构体作为参数的用法 f1 := Flower{"玫瑰", "红"} fmt.Printf("f1: %T , %v , %p \n" , f1 , f1 , &f1) fmt.Println("----------------------") //将结构体对象作为参数 changeInfo1(f1) fmt.Printf("f1: %T , %v , %p \n" , f1 , f1 , &f1) fmt.Println("----------------------") // 将结构体指针作为参数 changeInfo2(&f1) fmt.Printf("f1: %T , %v , %p \n" , f1 , f1 , &f1) fmt.Println("----------------------") //2、结构体作为返回值的用法 //结构体对象作为返回值 f2 := getFlower1() f3 := getFlower1() fmt.Println("更改前" , f2 , f3) f2.name = "杏花" fmt.Println("更改后" , f2 , f3) fmt.Printf("f2地址为 %p,f3地址为%p\n ", &f2, &f3) //结构体指针作为返回值 f4 := getFlower2() f5 := getFlower2() fmt.Println("更改前" , f4 , f5) f4.name = "桃花" fmt.Println("更改后" , f4 , f5) } //返回结构体对象 func getFlower1() (f Flower){ f = Flower{"牡丹", "白"} fmt.Printf("函数getFlower1内f: %T , %v , %p \n" , f , f , &f) return } //返回结构体指针 func getFlower2() (f *Flower){ //f = &Flower{"芙蓉", "红"} temp := Flower{"芙蓉", "红"} fmt.Printf("函数getFlower2内temp: %T , %v , %p \n" , temp , temp , &temp) f = &temp fmt.Printf("函数getFlower2内f: %T , %v , %p , %p \n" , f , f , f , &f) return } //传结构体对象 func changeInfo1(f Flower) { f.name = "月季" f.color = "粉" fmt.Printf("函数changeInfo1内f: %T , %v , %p \n" , f , f , &f) } //传结构体指针 func changeInfo2(f *Flower) { f.name = "蔷薇" f.color = "紫" fmt.Printf("函数changeInfo2内f: %T , %v , %p , %p \n" , f , f , f , &f) } 运行 f1: main.Flower , {玫瑰 红} , 0xc0000044a0 ---------------------- 函数changeInfo1内f: main.Flower , {月季 粉} , 0xc000004520 f1: main.Flower , {玫瑰 红} , 0xc0000044a0 ---------------------- 函数changeInfo2内f: *main.Flower , &{蔷薇 紫} , 0xc0000044a0 , 0xc000006030 f1: main.Flower , {蔷薇 紫} , 0xc0000044a0 ---------------------- 函数getFlower1内f: main.Flower , {牡丹 白} , 0xc000004640 函数getFlower1内f: main.Flower , {牡丹 白} , 0xc0000046c0 更改前 {牡丹 白} {牡丹 白} 更改后 {杏花 白} {牡丹 白} f2地址为 0xc000004620,f3地址为0xc0000046a0 函数getFlower2内temp: main.Flower , {芙蓉 红} , 0xc0000047a0 函数getFlower2内f: *main.Flower , &{芙蓉 红} , 0xc0000047a0 , 0xc000006038 函数getFlower2内temp: main.Flower , {芙蓉 红} , 0xc000004820 函数getFlower2内f: *main.Flower , &{芙蓉 红} , 0xc000004820 , 0xc000006040 更改前 &{芙蓉 红} &{芙蓉 红} 更改后 &{桃花 红} &{芙蓉 红}
go语言面向对象编程 学习
面向对象思想的概述 面向对象是一种对现实世界进行理解和抽象的方法。面向对象的程序设计(Object Oriented Programming),简称OOP。传统面向对象主要特点可以概括为封装、继承、多态。 面向过程与面向对象 面向对象,关注的是对象。把构成问题的事务分解成各个对象,不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。 面向过程,关注的是过程,分析出解决问题的步骤,然后用函数实现每一个步骤。 在软件领域,编程初期是面向过程编程,项目一旦庞大就变的不可控,最大的原因就是代码不可复用。例如,一个软件需求需要创建游戏角色,这些角色需要有自己的名称、积分、血值、装备等属性,同时需要有攻击,复活等游戏行为,对于这些游戏属性可以用程序中的数据变量表示,对于游戏行为使用函数可以解决。但是数据和函数的代码散落摆放,一旦复用完全不可控。于是软件有了面向对象的编程思想,游戏角色即被看成一个对象,这个角色封装了它应有的属性和具体的行为。 相较于面向过程,面向对象有以下优势。 容易命名。不同的类中方法名字可以相同,但是函数则不同,必须使用不同的名字,光起函数名字就需要大费力气; 代码管理方便,易于模块化开发。类中包含方法和属性,属性用来记录数据,方法表示行为,类实例化后为对象,行为和数据由对象来统一管理。写出来的代码方法与属性各归各类,代码逻辑清晰,利于扩展;函数也表示行为,但是与函数配合的数据却散落摆放,缺乏“类”这样的结构来统一管理。 代码冗余量小、重用性高。面向对象的代码在使用时,通过调用各个对象中的方法和属性,不同的排列组合就能适应各种不同的业务场景。 在生活中,随处可见的一种事物就是对象,如人、动植物、建筑等。这些对象都具备了属性和行为两大特征: 属性,如一个人,有年龄、性别、爱好、职业等属性特征; 行为即动态特征,比如教师有讲课行为,厨师有做菜行为。 基于这两个特征,对象实现了记录数据与通过行为操作数据的结合,于是构成了多样的世界。 在计算机世界中,面向对象程序设计的思想要以对象来思考问题,首先要将现实世界的实体抽象为对象,然后考虑这个对象具备的属性和行为。例如,现在面临一名厨师想要做出一盘菜的实际问题,试着以面向对象的思想来解决这一实际问题。 首先可以从这一问题中抽象出对象,这里抽象出的对象为一名厨师。然后识别这个对象的属性。对象具备的属性都是静态属性,如厨师有等级、年龄等。接着识别此对象的动态行为,即厨师的切菜、颠勺等,这些行为都是此对象基于其属性而具有的动作。识别出此对象的属性和行为后,这个对象就被定义完成了,然后根据厨师具有的特性制定做菜的具体方案以解决问题。究其本质,所有的厨师都具有以上的属性和行为,可以将这些属性和行为封装起来以描述厨师这类人,由此可见,类实质上就是封装对象属性和行为的载体,而对象则是类抽象出来的一个实例。 Go语言面向对象 其他编程语言大多使用关键字“类”(class)来定义封装对象,表示该类的具体特征,然而Go并不是一个纯面向对象的编程语言。在Go语言中的面向对象,采用更灵活的结构体替代了类。 Go语言并没有提供类class,但是它提供了结构体struct,方法method,可以在结构体上添加。提供了捆绑数据和方法的行为,这些数据和方法与类相似。 Go语言设计的非常简洁优雅,Go没有沿袭传统面向对象编程中的诸多概念,例如继承、虚方法、构造方法和析构方法等。 虽然Go语言没有继承和多态。但是Go语言可以通过匿名字段实现继承,通过接口实现多态。在Go语言中学习面向对象,主要学习结构体struct、方法method、接口interface。
有趣的键盘输入 学习
Scanln()函数 package main import "fmt" func main() { username := "" age := 0 fmt.Scanln(&username , &age) fmt.Println("账号信息为:" , username , age) } 下面这段游戏代码很好玩,大家可以运行下 package main import ( "math/rand" "time" "fmt" ) func main() { playGame() } func playGame() { //获取随机数 target := generateRandNum(10, 100) fmt.Println("请输入随机数:") fmt.Println("--------------------") //记录猜测的次数 count := 0 for { count++ yourNum := 0 fmt.Scanln(&yourNum) if yourNum < target> target { fmt.Println("大了❌") } else { fmt.Println("正确✅") fmt.Printf("您一共猜测了%d次!\n", count) fmt.Println("---------------") playGame() } //错误提示 alertInfo(count, target) } } //错误提示 func alertInfo(count, target int) { if count >= 6 { fmt.Printf("您一共猜了 %d 次都没有猜中,太笨了!😓 ", count) fmt.Println("正确数字:", target) fmt.Println("-------------") playGame() } } //生成随机数 func generateRandNum(min, max int) int { rand.Seed(time.Now().UnixNano()) return rand.Intn(max-min+1) + min } 总结: Go语言有一组功能强大的内置包,这些包提供了很多常见编程问题的解决方案以及简化其他问题的工具。本章主要介绍了strings包(字符串处理)、strconv包(字符串格式转换)、regexp包(正则表达式)、time包(时间)、math包(数学公式)以及键盘输入。
随机数 学习
使用随机数时需要import "math/rand",rand包实现了伪随机数生成器。随机数从资源生成。包水平的函数都使用的默认的公共资源。该资源会在程序每次运行时都产生确定的序列。如果需要每次运行产生不同的序列,应使用Seed函数进行初始化。默认资源可以安全地用于多协程并发。 rand包中核心方法介绍 获取随机数的几种方法 通过默认随机数种子获取随机数,具体方法如下所示。 rand.Int() rand.Float64() rand.Intn(n) 总是生成固定的随机数。默认情况下,随机数种子都是1。seed是一个64位整数。 动态随机数种子生成随机资源,实例随机对象来获取随机数,具体方法如下所示。 s1 := rand.NewSource(time.Now().UnixNano()) r1 := rand.New(s1) randnum := r1.Intn(n) 简写形式:动态变化随机数种子来获取随机数,具体方法如下所示。 (1)获取整型随机数[0,10] rand.Seed(time.Now().UnixNano()) rand.Intn(10) (2)获取浮点型0.0至1.0之间的随机数 rand.Seed(time.Now().UnixNano()) rand.Float64() (3) 获取两数之间随机数[m , n] rand.Seed(time.Now().UnixNano()) 随机数 = rand.Intn(n - m + 1) + m //例如获取5,11的随机数 rand.Intn(7) + 5 示例代码 package main import ( "fmt" "math/rand" "time" ) func main() { randTest() randAnswer() } func randTest() { fmt.Println(rand.Int()) fmt.Println(rand.Intn(50)) fmt.Println(rand.Float64()) s1 := rand.NewSource(time.Now().UnixNano()) r1 := rand.New(s1) randnum := r1.Intn(10) fmt.Println(randnum) rand.Seed(time.Now().UnixNano()) fmt.Println(rand.Intn(10)) fmt.Println(rand.Float64()) num := rand.Intn(7) + 5 fmt.Println(num) } func randAnswer() { answers := []string{ "It is certain", "It is decidedly so", "Without a doubt", "Yes definitely", "You may rely on it", "As I see it yes", "Most likely", "Outlook good", "Yes", "Signs point to yes", "Reply hazy try again", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again", "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful", } rand.Seed(time.Now().UnixNano()) randnum := rand.Intn(len(answers)) fmt.Println("随机回答", answers[randnum]) } false 2 1 1 1.3 0 -1.3 7 0 1 3 2 5 256 0 4 3 D:\www\go>go run test.go 5577006791947779410 37 0.6645600532184904 6 6 0.43513705782753126 7 随机回答 Most likely
time包 学习
time包提供了时间的显示和测量用的函数。日历的计算采用的是公历。time包中核心方法如表所示。 示例代码 package main import ( "time" "fmt" ) func main() { time1 := time.Now() testTime() time2 := time.Now() fmt.Println(time2.Sub(time1).Seconds()) } func testTime() { t := time.Now() fmt.Println("1、", t) fmt.Println("2、", t.Local()) fmt.Println("3、", t.UTC()) t = time.Date(2018, time.January, 1, 1, 1, 1, 0, time.Local) fmt.Printf("4、本地时间%s , 国际统一时间:%s \n", t, t.UTC()) t, _ = time.Parse("2006-01-02 15:04:05", "2018-07-19 05:47:13") fmt.Println("5、", t) fmt.Println("6、" , time.Now().Format("2006-01-02 15:04:05")) fmt.Println("7、" , time.Now().String()) fmt.Println("8、" , time.Now().Unix()) fmt.Println("9、" , time.Now().UnixNano()) fmt.Println("10、" , t.Equal(time.Now())) fmt.Println("11、" , t.Before(time.Now())) fmt.Println("12、" , t.After(time.Now())) year , month , day := time.Now().Date() fmt.Println("13、" , year , month ,day) fmt.Println("14、" , time.Now().Year()) fmt.Println("15、" , time.Now().Month()) fmt.Println("16、" , time.Now().Day()) fmt.Println("17、" , time.Now().Weekday()) hour , minute , second := time.Now().Clock() fmt.Println("18、" , hour , minute , second) fmt.Println("19、" , time.Now().Hour()) fmt.Println("20、" , time.Now().Minute()) fmt.Println("21、" , time.Now().Second()) fmt.Println("22、" , time.Now().Nanosecond()) fmt.Println("23、" , time.Now().Sub(time.Now())) fmt.Println("24、" , time.Now().Sub(time.Now()).Hours()) fmt.Println("25、" , time.Now().Sub(time.Now()).Minutes()) fmt.Println("26、" , time.Now().Sub(time.Now()).Seconds()) fmt.Println("27、" , time.Now().Sub(time.Now()).Nanoseconds()) fmt.Println("28、" , "时间间距:", t.Sub(time.Now()).String()) d,_:=time.ParseDuration("1h30m") fmt.Println("29、" , d) fmt.Println("30、" , "交卷时间:" , time.Now().Add(d)) fmt.Println("31、" , "一年一个月零一天之后的日期:" , time.Now().AddDate(1,1,1)) } 运行 1、 2020-05-14 11:20:56.6427669 +0800 CST m=+0.003969901 2、 2020-05-14 11:20:56.6427669 +0800 CST 3、 2020-05-14 03:20:56.6427669 +0000 UTC 4、本地时间2018-01-01 01:01:01 +0800 CST , 国际统一时间:2017-12-31 17:01:01 +0000 UTC 5、 2018-07-19 05:47:13 +0000 UTC 6、 2020-05-14 11:20:56 7、 2020-05-14 11:20:56.6956258 +0800 CST m=+0.056828801 8、 1589426456 9、 1589426456695625800 10、 false 11、 true 12、 false 13、 2020 May 14 14、 2020 15、 May 16、 14 17、 Thursday 18、 11 20 56 19、 11 20、 20 21、 56 22、 699614200 23、 0s 24、 0 25、 0 26、 0 27、 0 28、 时间间距:-15957h33m43.7016098s 29、 1h30m0s 30、 交卷时间:2020-05-14 12:50:56.7026069 +0800 CST m=+5400.063809901 31、 一年一个月零一天之后的日期:2021-06-15 11:20:56.704602 +0800 CST 0.0618351
regexp正则表达式包 学习
正则表达式简介 正则表达式(regular expression)就是由元字符组成的一种字符串匹配的模式,使用这种模式可以实现对文本内容解析、校验、替换。 正则表达式经常应用在验证用户ID、密码、身份证、手机号等是否符合格式要求。如果填写的内容不匹配,就可以断定内容是不符合要求或者是虚假的信息。 正则表达式还有其他重要的通途就是模糊查询与批量替换。可以在文档中使用一个正则表达式来查找匹配的特定文字,然后可以全部将其删除,或者替换为别的文字。 正则表达式中主要元字符 正则表达式的特别备注说明如下。 (1)大写英文字母的正则表达式,除了可以写成[A-Z],还可以写成[\x41-\x5A]。因为在ASCII码字典中A-Z被排在了65-90号(也就是ASCII码的第66到第91位),换算成16进制就是0x41-0x5A; (2)[0-9],可以写成[\x30-\x39]; (3)[a-z],可以写成[\x61-\x7A]; (4)[A-Z],可以写成[\x41-\x5A]; (5)中文的正则表达式为:[\u4E00-\u9FA5]。 中文在unicode编码字典中排在4E00到9FA5之间。换成10进制,也就是第19968号到40869号是中文字,一共20902个中文字被搜录到unicode编码集中。(第19968号是“一”,而第40869号是“龥”——发音为yu)。 regexp包中核心函数及方法介绍 1)检查正则表达式与字节数组是否匹配。更复杂的查询建议使用 Compile 和完整的 Regexp 接口。具体声明方式如下所示。 func Match(pattern string, b[]byte) (matched bool, err error) 使用方式 flag, _ := regexp.Match("^\\d{6,15}$", []byte("123456789")) //返回 true flag, _ := regexp.Match("^\\d{6,7}$", []byte("123456789")) //返回 false (2)检查正则表达式与字符串是否匹配。具体声明方式如下所示。 func MatchString(pattern string, s string) (matched bool, err error) 使用方式 flag, _ := regexp.MatchString("^\\d{6,15}$", []byte("123456789")) //返回 true flag, _ := regexp.MatchString("^\\d{6,7}$", []byte("123456789")) //返回 false (3)将正则表达式字符串编译成正则表达式对象(Regexp) 。具体声明方式如下所示。 func Compile(expr string) (*Regexp, erroe) 使用方法 MyRegexp, _ := regexp.Compile("^\\d{6}\\D{2}$") (4)MustCompile 用法同Compile,但是不返回error。如果表达式不能被解析就会panic。它简化了正则表达式字符串编译成正则表达式对象的过程。具体声明方式如下所示。 func MustCompile(str string) *Regexp 使用方法 MyRegexp, _ := regexp.MustCompile("^[\u4e00-\u9fa5]$") (5)判断Regexp正则对象是否与给定的字节数组匹配。具体声明方式如下所示。 func (re *Regexp) Match(b []byte) bool 使用方法 MyRegexp, _ := regexp.MustCompile("^[\u4e00-\u9fa5]$") flag = MyRegexp.Match([]byte("一丁丂")) // 返回 true (6)判断Regexp正则对象是否与给定的字符串匹配。具体声明格式如下所示。 func (re *Regexp) MatchString(s string) bool 使用方法 MyRegexp, _ := regexp.Compile("^\\d{6}\\D{2}$") flag = RegExp.MatchString("123456ab") // 返回 true (7)ReplaceAll 将src中符合正则表达式的部分全部替换成指定内容。 func (re *Regexp) ReplaceAll(src, repl []byte) []byte 使用方法 text := "将字符串 123 按照正则表达式 3 4 5 分割成子字符串 56 78 组成的切片" RegExp3 := regexp.MustCompile("[\\d\\s]+") result := string(RegExp3.ReplaceAll([]byte(text), []byte(""))) (8)将字符串按照正则表达式分割成子字符串组成的切片。如果切片长度超过指定参数n,则不再分割。具体声明方式如下所示。 func (re *Regexp) Split(s string, n int) []string 使用方法 text = "第一部分#第二部分##第三部分###第四部分#第五部分##第六部分" MyRegexp := regexp.MustCompile("#+") arr := MyRegexp.Split(text, 5) 实例代码 package main import ( "fmt" "regexp" ) func main() { testRegexp() } func testRegexp() { //1.Match(pattern string, b []byte) (matched bool, err error)匹配检查文本正则表达式是否与字节片匹配。 flag, _ := regexp.Match("^\\d{6,7}$", []byte("123456789")) fmt.Println(flag) //2.MatchString(pattern string, s string) (matched bool, err error) flag, _ = regexp.MatchString("^\\d{6,7}$", "0123456789") fmt.Println(flag) //3.Compile(expr string) (*Regexp, error) RegExp, _ := regexp.Compile("^\\d{6}\\D{2}$") //4.MustCompile(str string) *Regexp RegExp2 := regexp.MustCompile("^[\u4e00-\u9fa5]+$") //5.Match(b []byte) bool flag = RegExp2.Match([]byte("一丁丂")) fmt.Println("xxx:" , flag) //6.MatchString(s string) bool flag = RegExp.MatchString("123456ab") fmt.Println(flag) //7.ReplaceAll(src, repl []byte) []byte text := "将字符串 123 按照正则表达式 3 4 5 分割成子字符串 56 78 组成的切片" RegExp3 := regexp.MustCompile("[\\d\\s]+") result := string(RegExp3.ReplaceAll([]byte(text), []byte("-"))) fmt.Println("替换后的字符串为:" , result) text = "第一部分#第二部分##第三部分###第四部分#第五部分##第六部分" MyRegexp := regexp.MustCompile("#+") arr := MyRegexp.Split(text, 5) fmt.Println(arr) } 运行 false false xxx: true true 替换后的字符串为: 将字符串-按照正则表达式-分割成子字符串-组成的切片 [第一部分 第二部分 第三部分 第四部分 第五部分##第六部分]
学习继续 strconv包的常用函数 学习
最近时间公司要上线一个新项目(PHP项目),时间比较紧,学习被停止了一段时间,加班加点的在做活动项目,今天终于把大部分的功能搞完了,可以继续咱们的学习旅程了。 Parse类函数 Parse类函数主要的功能是将字符串转换为其他类型,常用的Parse类函数如表所示。 看演示代码 package main import ( "strconv" "fmt" ) func main() { TestAtoi() TestParseInt() TestParseUint() TestParseFloat() TestParseBool() } func TestAtoi() { a, _ := strconv.Atoi("100") fmt.Printf("%T , %v \n", a, a+2) fmt.Println("---------------") } func TestParseInt() { num, _ := strconv.ParseInt("-4e00", 16, 64) fmt.Printf("%T , %v \n", num, num) num, _ = strconv.ParseInt("01100001", 2, 64) fmt.Printf("%T , %v\n", num, num) num, _ = strconv.ParseInt("-01100001", 10, 64) fmt.Printf("%T , %v\n", num, num) num, _ = strconv.ParseInt("4e00", 10, 64) fmt.Printf("%T , %v\n", num, num) fmt.Println("---------------") } func TestParseUint() { num, _ := strconv.ParseUint("4e00", 16, 64) fmt.Printf("%T , %v \n", num, num) num, _ = strconv.ParseUint("01100001", 2, 64) fmt.Printf("%T , %v\n", num, num) num, _ = strconv.ParseUint("-1100001", 10, 64) fmt.Printf("%T , %v\n", num, num) num, _ = strconv.ParseUint("4e00", 10, 64) fmt.Printf("%T , %v\n", num, num) fmt.Println("---------------") } func TestParseFloat() { pi := "3.1415926" num , _ := strconv.ParseFloat(pi , 64) fmt.Printf("%T , %v\n", num, num*2) fmt.Println("---------------") } func TestParseBool() { flag , _:=strconv.ParseBool("steven") fmt.Printf("%T , %v\n", flag, flag) fmt.Println("---------------") } 运行结果 int , 102 --------------- int64 , -19968 int64 , 97 int64 , -1100001 int64 , 0 --------------- uint64 , 19968 uint64 , 97 uint64 , 0 uint64 , 0 --------------- float64 , 6.2831852 --------------- bool , false --------------- Format类函数 Format类函数主要的功能是将其他类型格式化成字符串,常用的Format类函数如表所示。