Go学习笔记2

五、数据类型

5.复杂数据类型

1.指针

指针就是内存地址

*赋值:var ptr int = &age

image-20230906094000251

func main(){
        var age int = 18
        //&符号+变量 就可以获取这个变量内存的地址
        fmt.Println(&age) //0xc0000a2058
        //定义一个指针变量:
        //var代表要声明一个变量
        //ptr 指针变量的名字
        //ptr对应的类型是:*int 是一个指针类型 (可以理解为 指向int类型的指针)
        //&age就是一个地址,是ptr变量的具体的值
        var ptr *int = &age
        fmt.Println(ptr)
        fmt.Println("ptr本身这个存储空间的地址为:",&ptr)
        //想获取ptr这个指针或者这个地址指向的那个数据:
        fmt.Printf("ptr指向的数值为:%v",*ptr) //ptr指向的数值为:18
}

取值:*ptr = 21

总结:最重要的就是两个符号:

1.& 取内存地址

2.* 根据地址取值

指针细节

【1】可以通过指针改变指向值

func main(){
        var num int = 10
        fmt.Println(num)
        var ptr *int = &num
    	// 改变值为20
        *ptr = 20
        fmt.Println(num)
}

【2】指针变量接收的一定是地址值

image-20230906094956243

【3】指针变量的地址不可以不匹配

image-20230906095001996

PS:*float32意味着这个指针指向的是float32类型的数据,但是&num对应的是int类型的不可以。

【4】基本数据类型(又叫值类型),都有对应的指针类型,形式为数据类型,
比如int的对应的指针就是
int, float32对应的指针类型就是*float32。依次类推。

6.标识符的使用

标识符定义规则:

四个注意:不可以以数字开头,严格区分大小写,不能包含空格,不可以使用Go中的保留关键字

起名规则:
(1)包名:尽量保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,和标准库不要冲突
1.为什么之前在定义源文件的时候,一般我们都用package main 包 ?
main包是一个程序的入口包,所以你main函数它所在的包建议定义为main包,如果不定义为main包,那么就不能得到可执行文件。

(2)变量名、函数名、常量名 : 采用驼峰法。

(3)如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;

注意:
import导入语句通常放在文件开头包声明语句的下面。
导入的包名需要使用双引号包裹起来。
包名是从$GOPATH/src/后开始计算的,使用/进行路径分隔。

需要配置一个环境变量:GOPATH

并且需要:go env -w GO111MODULE=off

image-20230906105516139

image-20230906105622580

外部包使用

image-20230906105608538

六、运算符

1.算数运算符

算术运算符:+ ,-,*,/,%,++,--

package main
import "fmt"
func main(){
        //+加号:
        //1.正数 2.相加操作  3.字符串拼接
        var n1 int = +10
        fmt.Println(n1)
        var n2 int = 4 + 7
        fmt.Println(n2)
        var s1 string = "abc" + "def"
        fmt.Println(s1)
        // /除号:
        fmt.Println(10/3) //两个int类型数据运算,结果一定为整数类型
        fmt.Println(10.0/3)//浮点类型参与运算,结果为浮点类型
        // % 取模  等价公式: a%b=a-a/b*b
        fmt.Println(10%3) // 10%3= 10-10/3*3 = 1
        fmt.Println(-10%3)
        fmt.Println(10%-3)
        fmt.Println(-10%-3)
        //++自增操作:
        var a int = 10
        a++
        fmt.Println(a)
        a--
        fmt.Println(a)
        //++ 自增 加1操作,--自减,减1操作
        //go语言里,++,--操作非常简单,只能单独使用,不能参与到运算中去
        //go语言里,++,--只能在变量的后面,不能写在变量的前面 --a  ++a  错误写法
}

2.赋值运算符

package main
import "fmt"
func main(){
        var num1 int = 10
        fmt.Println(num1)
        var num2 int = (10 + 20) % 3 + 3 - 7   //=右侧的值运算清楚后,再赋值给=的左侧
        fmt.Println(num2)
        var num3 int = 10
        num3 += 20 //等价num3 = num3 + 20;
        fmt.Println(num3)
}

3.关系运算符

关系运算符:==,!=,>,<,> =,<=

关系运算符的结果都是bool型,也就是要么是true,要么是false

package main
import "fmt"
func main(){
        fmt.Println(5==9)//判断左右两侧的值是否相等,相等返回true,不相等返回的是false, ==不是=
        fmt.Println(5!=9)//判断不等于
        fmt.Println(5>9)
        fmt.Println(5<9)
        fmt.Println(5>=9)
        fmt.Println(5<=9)
}

4.逻辑运算符

逻辑运算符:&&(逻辑与/短路与),||(逻辑或/短路或),!(逻辑非)

package main
import "fmt"
func main(){
        //与逻辑:&& :两个数值/表达式只要有一侧是false,结果一定为false
        //也叫短路与:只要第一个数值/表达式的结果是false,那么后面的表达式等就不用运算了,直接结果就是false  -->提高运算效率
        fmt.Println(true&&true)
        fmt.Println(true&&false)
        fmt.Println(false&&true)
        fmt.Println(false&&false)
        //或逻辑:||:两个数值/表达式只要有一侧是true,结果一定为true
        //也叫短路或:只要第一个数值/表达式的结果是true,那么后面的表达式等就不用运算了,直接结果就是true -->提高运算效率
        fmt.Println(true||true)
        fmt.Println(true||false)
        fmt.Println(false||true)
        fmt.Println(false||false)
        //非逻辑:取相反的结果:
        fmt.Println(!true)
        fmt.Println(!false)
}

5.优先级

为了提高优先级,可以加()

image-20230906112306196

6.获取用户终端输入

要传入地址变量,因为scan内部是值改变,只有传入地址变量,才能影响到地址变量的值

image-20230906112348773

image-20230906112354130

package main
import "fmt"
func main(){
        //实现功能:键盘录入学生的年龄,姓名,成绩,是否是VIP
        //方式1:Scanln
        var age int
        // fmt.Println("请录入学生的年龄:")
        //传入age的地址的目的:在Scanln函数中,对地址中的值进行改变的时候,实际外面的age被影响了
        //fmt.Scanln(&age)//录入数据的时候,类型一定要匹配,因为底层会自动判定类型的
        var name string
        // fmt.Println("请录入学生的姓名:")
        // fmt.Scanln(&name)
        var score float32
        // fmt.Println("请录入学生的成绩:")
        // fmt.Scanln(&score)
        var isVIP bool
        // fmt.Println("请录入学生是否为VIP:")
        // fmt.Scanln(&isVIP)
        //将上述数据在控制台打印输出:
        //fmt.Printf("学生的年龄为:%v,姓名为:%v,成绩为:%v,是否为VIP:%v",age,name,score,isVIP)
        //方式2:Scanf
        fmt.Println("请录入学生的年龄,姓名,成绩,是否是VIP,使用空格进行分隔")
        fmt.Scanf("%d %s %f %t",&age,&name,&score,&isVIP)
        //将上述数据在控制台打印输出:
        fmt.Printf("学生的年龄为:%v,姓名为:%v,成绩为:%v,是否为VIP:%v",age,name,score,isVIP)
}

七、流程控制

1.if

if 条件表达式 {
  逻辑代码
}

当条件表达式为ture时,就会执行得的代码。

条件表达式左右的()可以不写,也建议不写

if和表达式中间,一定要有空格

在Golang中,{}是必须有的,就算你只写一行代码。

if 条件表达式 {
   逻辑代码1
} else {
   逻辑代码2
}
if 条件表达式1 {
    逻辑代码1
} else if 条件表达式2 {
    逻辑代码2
}
.......
else {
                逻辑代码n
}

2.Switch

switch 表达式 {
                        case 值1,值2,.….:
                                                        语句块1
                        case 值3,值4,...:
                                                        语句块2
                        ....
                        default:
                         语句块
}

注意

switch后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)

image-20230906150823923

case后面的值如果是常量值(字面量),则要求不能重复

image-20230906150832811

case后的各个值的数据类型,必须和 switch 的表达式数据类型一致

image-20230906150846549

case后面可以带多个值,使用逗号间隔。比如 case 值1,值2...

image-20230906150853451

case后面不需要带break

default语句不是必须的,位置也是随意的。

switch后也可以不带表达式,当做if分支来使用

image-20230906150915954

switch后也可以直接声明/定义一个变量,分号结束,不推荐

image-20230906150926711

switch穿透,利用fallthrough关键字,如果在case语句块后增加fallthrough ,则会继续执行下一个case,也叫switch穿透。

image-20230906150930469

3.for循环

for的初始表达式 不能用var定义变量的形式,要用:=

for 初始表达式; 布尔表达式; 迭代因子 {
          循环体;
}
var sum int = 0
        for i := 1 ; i <= 5 ; i++ {
                sum += i
        }
        

image-20230906151508454

for range

(键值循环) for range结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道,for range 语法上类似于其它语言中的 foreach 语句

一般形式为:

for key, val := range coll {
    ...
}
for i , value := range str {
                fmt.Printf("索引为:%d,具体的值为:%c \n",i,value)
        }
        //对str进行遍历,遍历的每个结果的索引值被i接收,每个结果的具体数值被value接收
        //遍历对字符进行遍历的

4.关键字

1.break

1.switch分支中,每个case分支后都用break结束当前分支,但是在go语言中break可以省略不写。

2.break可以结束正在执行的循环

标签的使用

package main
import "fmt"
func main(){
        //双重循环:
        label2:
        for i := 1; i <= 5; i++ {
                for j := 2; j <= 4; j++ {
                        fmt.Printf("i: %v, j: %v \n",i,j)
                        if i == 2 && j == 2 {
                                break label2   //结束指定标签对应的循环
                        }
                }
        }
        fmt.Println("-----ok")
}

注意:如果那个标签没有使用到 的话,那么标签不用加,否则报错:定义未使用
结果:

2.continue

continue的作用结束这一层循环,继续进行下一层

package main
import "fmt"
func main(){
        //双重循环:
        for i := 1; i <= 5; i++ {
                for j := 2; j <= 4; j++ {			
                        if i == 2 && j == 2 {
                                continue
                        }
                        fmt.Printf("i: %v, j: %v \n",i,j)
                }
        }
        fmt.Println("-----ok")
}

3.goto

【1】Golang的 goto 语句可以无条件地转移到程序中指定的行。
【2】goto语句通常与条件语句配合使用。可用来实现条件转移.
【3】在Go程序设计中一般不建议使用goto语句,以免造成程序流程的混乱。
【4】代码展示:

package main
import "fmt"
func main(){
        fmt.Println("hello golang1")
        fmt.Println("hello golang2")
        if 1 == 1 {
                goto label1 //goto一般配合条件结构一起使用
        }
        fmt.Println("hello golang3")
        fmt.Println("hello golang4")
        fmt.Println("hello golang5")
        fmt.Println("hello golang6")
        label1:
        fmt.Println("hello golang7")
        fmt.Println("hello golang8")
        fmt.Println("hello golang9")
}

4.return

package main
import "fmt"
func main(){
        for i := 1; i <= 100; i++ {
                fmt.Println(i)
                if i == 14 {
                        return //结束当前的函数
                }
        }
        fmt.Println("hello golang")
}

八、函数

1.基本语法

func   函数名(形参列表)(返回值类型列表){
                        执行语句..
                        return + 返回值列表
}
//自定义函数:功能:两个数相加:
func call01 (num1 int ,num2 int) (int){//如果返回值类型就一个的话,那么()是可以省略不写的
	return num1 + num2
}

2.返回多个

image-20230906155548106

省略返回值:

image-20230906155610256

3.不支持重载

Golang中函数不支持重载

image-20230906161442652

4.可变数量的形参

Golang中支持可变参数 (如果你希望函数带有可变数量的参数)

package main
import "fmt"
//定义一个函数,函数的参数为:可变参数 ...  参数的数量可变
//args...int 可以传入任意多个数量的int类型的数据  传入0个,1个,,,,n个
func test (args...int){
        //函数内部处理可变参数的时候,将可变参数当做切片来处理
        //遍历可变参数:
        for i := 0; i < len(args); i++ {
                fmt.Println(args[i])
        }
}
func main(){	
        test()
        fmt.Println("--------------------")
        test(3)
        fmt.Println("--------------------")
        test(37,58,39,59,47)
}

5.修改数值使用地址传递

基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

image-20230906161551202

以值传递方式的数据类型,如果希望在函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果来看类似引用传递。

image-20230906161558638

6.函数添加变量名称

在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

package main
import "fmt"
//定义一个函数:
func test(num int){
        fmt.Println(num)
}
func main(){
        //函数也是一种数据类型,可以赋值给一个变量	
        a := test//变量就是一个函数类型的变量
        fmt.Printf("a的类型是:%T,test函数的类型是:%T \n",a,test)//a的类型是:func(int),test函数的类型是:func(int)
        //通过该变量可以对函数调用
        a(10) //等价于  test(10)
}

7.函数作为形参

函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
(把函数本身当做一种数据类型)

package main
import "fmt"
//定义一个函数:
func test(num int){
        fmt.Println(num)
}
//定义一个函数,把另一个函数作为形参:
func test02 (num1 int ,num2 float32, testFunc func(int)){
        fmt.Println("-----test02")
}
func main(){
        //函数也是一种数据类型,可以赋值给一个变量	
        a := test//变量就是一个函数类型的变量
        fmt.Printf("a的类型是:%T,test函数的类型是:%T \n",a,test)//a的类型是:func(int),test函数的类型是:func(int)
        //通过该变量可以对函数调用
        a(10) //等价于  test(10)
        //调用test02函数:
        test02(10,3.19,test)
        test02(10,3.19,a)
}

8.自定义数据类型

为了简化数据类型定义,Go支持自定义数据类型

基本语法: type 自定义数据类型名 数据类型

可以理解为 : 相当于起了一个别名

例如:type mylnt int -----》这时mylnt就等价int来使用了.

image-20230906161804692

支持对函数返回值命名

image-20230906161819051

升级写法:对函数返回值命名,里面顺序就无所谓了,顺序不用对应

image-20230906161836775

9.包的引入

不可能把所有的函数放在同一个源文件中,可以分门别类的把函数放在不同的原文件中

image-20230906162301225

1.简单实例

项目结构

image-20230906162410986

main包

image-20230906162424379

db包

image-20230906162436398

2.包的命名

可以给包取别名,

取别名后,原来的包名就不能使用了

image-20230906162651734

10.init函数

init函数:初始化函数,可以用来进行一些初始化的操作
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。

image-20230906163005269

11.包执行流程

image-20230906163021859

12.多个包执行流程

image-20230906163119067

image-20230906163127578

13.匿名函数

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)

image-20230906165946514

将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数(用的少)

image-20230906170016402

让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了

package main
import "fmt"
var Func01 = func (num1 int,num2 int) int{
        return num1 * num2
}
func main(){
        //定义匿名函数:定义的同时调用
        result := func (num1 int,num2 int) int{
                return num1 + num2
        }(10,20)
        fmt.Println(result)
        //将匿名函数赋给一个变量,这个变量实际就是函数类型的变量
        //sub等价于匿名函数
        sub := func (num1 int,num2 int) int{
                return num1 - num2
        }
        //直接调用sub就是调用这个匿名函数了
        result01 := sub(30,70)
        fmt.Println(result01)
        result02 := sub(30,70)
        fmt.Println(result02)
        result03 := Func01(3,4)
        fmt.Println(result03)
}

14.闭包

闭包就是一个函数和与其相关的引用环境组合的一个整体

package main
import "fmt"
//函数功能:求和
//函数的名字:getSum 参数为空
//getSum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getSum() func (int) int {
        var sum int = 0
        return func (num int) int{
                sum = sum + num 
                return sum
        }
}
//闭包:返回的匿名函数+匿名函数以外的变量num
func main(){
        f := getSum()
        fmt.Println(f(1))//1 
        fmt.Println(f(2))//3
        fmt.Println(f(3))//6
        fmt.Println(f(4))//10
}

匿名函数中引用的那个变量会一直保存在内存中,可以一直使用

1.本质

闭包本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数

匿名函数+引用的变量/参数 = 闭包

2.特点

(1)返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数 ,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。
(2)闭包中使用的变量/参数会一直保存在内存中,所以会一直使用---》意味着闭包不可滥用(对内存消耗大)

15.defer

在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer关键字

image-20230906172108184

遇到defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝入栈中,不会随着函数后面的变化而变化。

image-20230906173642668

应用场景:

比如你想关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句),
所以你用完随手写了关闭,比较省心,省事

16.系统函数

【1】统计字符串的长度,按字节进行统计:
len(str)
使用内置函数也不用导包的,直接用就行

image-20230906192108608

【2】字符串遍历:
(1)利用方式1:for-range键值循环:

image-20230906192116360

(2)r:=[]rune(str)

image-20230906192126247

【3】字符串转整数:

n, err := strconv.Atoi("66") 

【4】整数转字符串:

str = strconv.Itoa(6887)
image-20230906192303266

【5】查找子串是否在指定的字符串中:
strings.Contains("javaandgolang", "go")

【6】统计一个字符串有几个指定的子串:

  strings.Count("javaandgolang","a") 
image-20230906192356301

【7】不区分大小写的字符串比较:

 strings.EqualFold("go" , "Go")

【8】返回子串在字符串第一次出现的索引值,如果没有返回-1 :

 strings.lndex("javaandgolang" , "a") 
image-20230906192407545

【9】字符串的替换:
strings.Replace("goandjavagogo", "go", "golang", n)
n可以指定你希望替换几个,如果n=-1表示全部替换,替换两个n就是2

image-20230906194721946

【10】按照指定的某个字符,为分割标识,将一个学符串拆分成字符串数组:
strings.Split("go-python-java", "-")

image-20230906194727859

【11】将字符串的字母进行大小写的转换:
strings.ToLower("Go")// go
strings.ToUpper"go")//Go

image-20230906194733604

【12】将字符串左右两边的空格去掉:
strings.TrimSpace(" go and java ")

image-20230906194738513

【13】将字符串左右两边指定的字符去掉:
strings.Trim("golang ", " ~")

image-20230906194743261

【14】将字符串左边指定的字符去掉:
strings.TrimLeft("golang", "~")

【15】将字符串右边指定的字符去掉:
strings.TrimRight("golang", "~")

【16】判断字符串是否以指定的字符串开头:
strings.HasPrefix("http://java.sun.com/jsp/jstl/fmt", "http")

【17】判断字符串是否以指定的字符串结束:
strings.HasSuffix("demo.png", ".png")

image-20230906194747859

17.日期和时间

时间和日期的函数,需要到入time包,所以你获取当前时间,就要调用函数Now函数:

package main
import (
        "fmt"
        "time"
)
func main(){
        //时间和日期的函数,需要到入time包,所以你获取当前时间,就要调用函数Now函数:
        now := time.Now()
        //Now()返回值是一个结构体,类型是:time.Time
        fmt.Printf("%v ~~~ 对应的类型为:%T\n",now,now)
        //2021-02-08 17:47:21.7600788 +0800 CST m=+0.005983901 ~~~ 对应的类型为:time.Time
        //调用结构体中的方法:
        fmt.Printf("年:%v \n",now.Year())
        fmt.Printf("月:%v \n",now.Month())//月:February
        fmt.Printf("月:%v \n",int(now.Month()))//月:2
        fmt.Printf("日:%v \n",now.Day())
        fmt.Printf("时:%v \n",now.Hour())
        fmt.Printf("分:%v \n",now.Minute())
        fmt.Printf("秒:%v \n",now.Second())
}

【2】日期的格式化:
(1)将日期以年月日时分秒按照格式输出为字符串:

        //Printf将字符串直接输出:
        fmt.Printf("当前年月日: %d-%d-%d 时分秒:%d:%d:%d  \n",now.Year(),now.Month(),
        now.Day(),now.Hour(),now.Minute(),now.Second())
        //Sprintf可以得到这个字符串,以便后续使用:
        datestr := fmt.Sprintf("当前年月日: %d-%d-%d 时分秒:%d:%d:%d  \n",now.Year(),now.Month(),
        now.Day(),now.Hour(),now.Minute(),now.Second())
        fmt.Println(datestr)

(2)按照指定格式:

(2)按照指定格式:

    //这个参数字符串的各个数字必须是固定的,必须这样写
    datestr2 := now.Format("2006/01/02 15/04/05")
    fmt.Println(datestr2)
    //选择任意的组合都是可以的,根据需求自己选择就可以(自己任意组合)。
    datestr3 := now.Format("2006 15:04")
    fmt.Println(datestr3)

18.内置函数

内置函数存放位置:
在builtin包下,使用内置函数也的,直接用就行

1.常用函数

1.len函数:

统计字符串的长度,按字节进行统计

image-20230906200137630

2.new函数

分配内存,主要用来分配值类型(int系列, float系列, bool, string、数组和结构体struct)

image-20230906200151198

3.make函数

分配内存,主要用来分配引用类型(指针、slice切片、map、管道chan、interface 等)

posted @ 2023-09-07 08:57  橡皮筋儿  阅读(2503)  评论(0编辑  收藏  举报