0


golang开发工程师-第一步:golang入门基础教学

golang入门基础教学


前言

人生格言:好记性不如烂笔头!
建议:看这篇文章,前提是必须会java,javascript等一门语言。你可以把golang语言看做是缝合怪;集c,java,javascript等语言优点于一身。如果你没有任何编程语言傍身,本人还是建议你去哔站看视频,就不要在这里浪费时间了~~~


一、golang的优势何在?

优势一:golang代码很简洁,很优雅。先给大家看一个由golang编写的网页(如下);用少许代码去写出一个网页,java等语言办不到!【但是golang的缺点也很明显:代码太简洁,看不懂了。这个因人而异吧】

main包

package main

import("github.com/gin-gonic/gin""net/http")funcmain(){//创建一个路由引擎
    server := gin.Default()//加载静态页面 index.html//全局加载 路由引擎.LoadHTMLGlob("指定的静态页面路径")
    server.LoadHTMLFiles("demo02/template/index.html")//加载静态资源//server.Static()//响应
    server.GET("/index",func(context *gin.Context){
        context.HTML(http.StatusOK,"index.html",gin.H{"message":"golang语言,我来了!",})})//启动
    server.Run()

demo02/template/index.html

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>golang</title></head><body><h1>{{.message}}</h1></body></html>

输入url:127.0.0.1:8080/index

效果如下:
在这里插入图片描述

优势二:golang编写的服务器启动很快!java的springBoot框架的内置服务器启动就很慢。

优势三:golang运行代码,生成的是go.exe文件。只要有这个文件,就可以在任意一台电脑上运行,即:跨平台。这个时候就有人说了:java不也是垮平台吗?注意:java是依据JVM的不同才实现的跨平台特性。golang生成的go.exe文件,可以在不同的操作系统下直接调用Linux内核去执行代码。【Linux内核是所有操作系统的基层】

二、goland破解教程

goland、VScode等软件是开发go语言的平台。本文就使用goland去开发go。
goland正版是要钱的,我这里能破解2020.1版本的goland【这个版本相对是老版本了,将就用吧】

百度网盘URL:https://pan.baidu.com/s/173QIlv6KrSGzsWwwJfXp4A
提取码:hang
期限:永久有效
在这里插入图片描述

这个是2020.1版本的goland应用程序,双击点开即可。安装看其他博主教程。
打开过后选择试用,进入goland工作台
然后将Crack/jetbrains-agent-latest.zip文件丢入该goland工作台。
如果你想使用汉化版,就将resources_zh_CN_GoLand_2020_r1.jar文件也丢入该工作台
然后,重启goland。就破解完毕了

三、goland的使用教程

配置主题
在这里插入图片描述

这个主题,我觉得好看一些。看着也不伤眼睛。主题选择,你可以随便搞

配置go语言的源库SDK

跟java一样。go语言也有源库。为什么我要在这里才说源库呢?因为goland可以帮我们去拉取源库

在这里插入图片描述
配置工作空间
在这里插入图片描述
配置go-modules

go-modules是什么呢?它就类似于java的Maven仓库。比如,你以后要使用gin框架,你就必须去拉取gin框架的源码。放在哪里呢?就放在go-modules中
在这里插入图片描述

goland的基本必要的配置就这些。至于字体什么的,就自己去调。挺简单的。如果我说的不明白,就去看看别的文章。勿恼!

四、一个简单的go代码

var.go

package main
import"fmt"funcmain(){
    fmt.Println("golang ,I am coming!")}

一个go函数必须归属于一个包;main()必须归属于package main 【main包】
main()是一个go程序的入口
fmt是go语言自带的标准库内的包
fmt.Println(“golang ,I am coming!”) 是调用了fmt包内的Println()【P是大写的】

golang标准库文档

五、变量的声明和赋值

接下来开始,我们就学习golang的基础语法了。挺简单的。

任何一个编程语言,都必须有变量。可以说,变量是编程语言的基础

怎么理解变量呢?

举个例子:现在你身上有棒棒糖,而你在一家酒店的888号房间。888是变量??不对的。888号是地址,是你所待的空间的地址。你才是变量。棒棒糖是变量值。就好比:a = 棒棒糖 ;获得棒棒糖的方式有两种。一是访问你,二是访问那处空间地址。即指针的概念。

在这里插入图片描述
变量的步骤有三个:【适用于所有的编程语言】

声明变量 ----赋值 -----使用变量

代码如下:

package main

import"fmt"funcmain(){//第一种创建变量的方式:声明 赋值 分开操作//声明,int 是声明变量的数据类型var number int//赋值【若不给变量赋值,该变量是使用默认值 0 , "" , false】
    number =10//第二种:声明和赋值一起var name string="王麻子"//第三种:简写//这种就是把 var == : ;略写数据类型了,由系统去判断什么类型的
    age :=23//使用
    fmt.Printf("number = %d,name = %s,age = %d \nnumber变量的地址==>%d",number,name,age,&number)}

在这里插入图片描述

这里指针的概念,我就提一嘴。以后讲到指针会详细说的
变量声明了就必须使用,不然报错。

作业:判断一个变量只声明而不赋值,会不会使用默认值?int ,string ,bool 类型的默认值是什么?

多个变量的批量声明和赋值

package main

import"fmt"//声明全局变量 在函数外部,必须有 var func const 关键字修饰,即:不能使用 number := 23//单个var number =23//批量var(
    name ="行者"
    age =23
    city ="北京")funcmain(){//    声明局部变量
    name,age,city :="王麻子",12,"重庆"//    问:如果在全局变量和局部变量中,有相同变量名时,函数会使用哪一个?
    fmt.Printf("name = %v,age = %v , city = %v",name,age,city)}

在这里插入图片描述

函数内部会优先使用该函数内部的局部变量。
变量在同一作用域内是不能重名的。
变量 = 变量名 + 变量值 + 数据类型

程序中 + 号运算符的使用

如果两个数据类型均为int的变量相加,结果会如何?
如果两个数据类型均为string的变量相加,结果会如何?
如果一个string类型的变量去加一个int类型的变量呢?

package main

import"fmt"var(
    num01 =20
    num02 =10
    str01 ="com"
    str02 ="ing"
    str03 ="30")funcmain(){//    两个数据类型均为int的变量相加
    first := num01 + num02
//    两个数据类型均为string的变量相加
    second := str01 + str02
//    一个string类型的变量去加一个int类型的变量 string(num01) 将num01的int数据类型强制转换为 string类型
    three := str01 +string(num01)

    fmt.Printf("first = %v,second = %v,three = %v",first,second,three)}

在这里插入图片描述

六、数据类型的基本介绍

在这里插入图片描述int数据类型【存放整数的】

package main

import("fmt")funcmain(){//    int8 1字节空间大小 -128~~127var small int8=126//    int16 2字节var model int16=200//    int32 4字节 本机的操作系统如果是32位的,你写 var number int 就是声明; number的类型为 int32var num32 int32=500//    int64 8字节 本机的操作系统如果是64位的,你写 var num int 就是声明; number的类型为 int64var num64 int64=8000//    uint8 、 uint16 、 uint32、uint64 这个是无符号的int类型,即:非负数//  uint8一般用于年龄的数据类型var age uint8=99

    fmt.Printf("int8 ==> %d,int16 ==> %d,int32 ==> %d,int64 ==> %d,uint8 ==> %d",small,model,num32,num64,age)}

在这里插入图片描述

Golang 程序中整型变量在使用时,遵守保小不保大的原则,即:在保证程序正确运行下,尽量
使用占用空间小的数据类型

小数类型【浮点数类型float32\float64】
在这里插入图片描述
number := 1.222222 这个变量的类型是float32?还是float64?

package main

import"fmt"import"unsafe"funcmain(){

    number :=1.22222

    fmt.Printf("number的类型是:%T,nnumber的字节长度是:%d个字节",number,unsafe.Sizeof(number))}

在这里插入图片描述

unsafe.Sizeof(number) 是unasfe包下的Sizeof()函数的调用。查看指定变量类型的字节长度

字符类型

Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存。
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。

问题:我能不能以一个字节为单位去遍历带有汉字的字符串?

package main

import"fmt"funcmain(){

    str :="我来北京打工m,coming!"for i:=0;i<len(str);i++{
        fmt.Printf("第%d个下标的值为:%c \n",i,str[i])}}

在这里插入图片描述

可以看出:乱码问题!为什么会乱码?
在go的字符串中,遵循 UTF-8编码方案,一个汉字是由三个字节包括,一个字母是由一个字节包括。你一个字节一个字节的遍历,肯定会出现乱码问题。

解决方案:以三个字节为单位去遍历字符串

package main

import"fmt"funcmain(){

    str :="我来北京打工m,coming!"for index,value :=range str{
        fmt.Printf("第%d的下标的值为:%c \n",index,value)}}

在这里插入图片描述

for index,value := range str{} 是go特有的range遍历。其本质就是以三个字节为单位的遍历方式

布尔类型 bool

package main

import("fmt""unsafe")funcmain(){var bo bool=false
    fmt.Printf("bo的类型为:%T,bo的字节长度为:%d",bo,unsafe.Sizeof(bo))}

在这里插入图片描述
字符串string类型

package main

import("fmt")funcmain(){

    str :="hang"//不能使用 str[0] = a 去修改str中的值//1.将str转换为 []uint8
    by :=[]byte(str)//2.修改by中的元素值
    by[0]='a'//3.将by的[]uint8数据类型转换为string
    str =string(by)
    fmt.Printf("str的值为:%s ,by的数据类型为:%T",str,by)}

在这里插入图片描述

go语言有个特性:在每行代码的末尾会自动添加分号 ;

基本数据类型的相互转换

Go 在不同类型的变量之间赋值时需要显式转换。也就是说 Golang 中数
据类型不能自动转换。表达式 T(v) 将值 v 转换为类型 T
Go 中,数据类型的转换可以是从 表示范围小–>表示范围大,也可以 范围大—>范围小
在转换中,比如将 int64 转成 int8 【-128—127】 ,编译时不会报错,只是转换的结果是按
溢出处理,和我们希望的结果不一样。

基本数据类型转换为string类型

package main

import("fmt""strconv")var(
    intTest =12
    floatTest =12.22
    boolTest =false
    byTest ='a'

    str01 ="123.2315454445454545454")funcmain(){//    1.基本数据类型转换为string
    ints := fmt.Sprintf("%d",intTest)//strconv.FormatFloat(floatTest,'f',10,64) 'f' 表示float类型的 64表示float64 10表示小数点后保留10位
    floats := strconv.FormatFloat(floatTest,'f',10,64)
    bools := fmt.Sprintf("%t",boolTest)
    fmt.Printf("ints == %s,floats == %s,bools == %s\n",ints,floats,bools)//    2.string转换为基本数据类型
    sfloat,err := strconv.ParseFloat(str01,64)if err !=nil{
        fmt.Println("有异常")}else{
        fmt.Printf("sfloat == %f",sfloat)}}

数据类型转换【总结】

除了string类型,其他基本数据类型的相互转换均是强转
基本数据类型转换为string类型,就用strconv包内的FormatFloat() | FormatInt() | FormatUint() | FormatBool()等
string类型转换为基本数据类型,也是strconv包下的ParseFloat() |ParseInt() |ParseBool() 等

指针

指针也是数据类型,是复杂的数据类型。指针的用法无非是 取值 和 取地址
指针变量存的是一个地址,这个地址指向的空间存的才是值

package main

import"fmt"funcmain(){var name string="hang"//var age uint8 = 23//    通过指针获取name的地址//1.声明了一个string类型指针变量ptr,必须加 *int 才能表示指针var ptr *string

    ptr =&name
    //ptr ==> 0xc0001021e0
    fmt.Printf("ptr ==> %v\n",ptr)//    我知道了name变量的地址,我就可以通过地址修改值
    fmt.Println("name ==>",name)*ptr ="我已经修改了name中的值"
    fmt.Println("name ==>",name)}

在这里插入图片描述

1.注意:指针变量存放的是一个地址. var name string = “myname” 声明指针必须是 var prt *string
&name 是一个地址且只能用指针来接收 ; *(&name) == name

值类型与引用类型

值类型:打个比方,有一张带画的纸,你去复印了一张。现在你就有两张纸了。然后,你在一张纸上随便画,却不影响另一张。即:拷贝【copy】
引用类型:顾名思义,你改变了一张纸,另一张上面也有。指针、slice 切片、map、管道 chan、interface 等都是引用类型

其他复杂的数据类型,之后再讲

七、访问权限【公开、私有】

变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写,则只能
在本包中使用
注意:只有全局变量才有效

包结构
在这里插入图片描述
pub.go

package pub

import"fmt"//声明变量var(//首字母大写的变量才能被其他包内的函数访问
    Name ="王麻子"
    age =23)//声明常量,const(
    Ip =3.14)funcMyFunction(){
    fmt.Println("我是pub包下的Myfunction()函数")}

main.go

package main

//导入自定义的包import("fmt""go_code/study/Test/pub")funcmain(){

    fmt.Printf("pub包里的Name = %s\n,Ip = %.2f \n",pub.Name,pub.Ip)
    pub.MyFunction()}

在这里插入图片描述

运算符的用法,都差不多。我就不说了。跟java的是一样的。唯一与java不同的就是 i++ | i-- ;
golang语言不支持三元运算符

八、程序流程控制语句

顺序控制

这个没什么好说的

在这里插入图片描述

if-else分支语句
在这里插入图片描述在这里插入图片描述

判断该年是不是闰年

年份能被4整除,不能被100整除;能被400整除

pub.go

package pub

import"fmt"//返回一个bool值,首字母大写,go语言的函数可以有多个返回值funcIsRun()(yea int, result bool){//声明了一个年份变量,不必初始化。其使用默认值var year int

    fmt.Println("请输入当前年份")//通过地址,将值赋予给year变量
    fmt.Scanln(&year)if(year %4==0&& year %100!=0)||year %400==0{return year,true}else{return year,false}}

main.go

package main

import("fmt""go_code/study/Test/pub")funcmain(){
    year,result := pub.IsRun()
    fmt.Printf("当前年为 %d,是否是闰年:%t",year,result)}

在这里插入图片描述

switch分支控制语句

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上到下逐一测
试,直到匹配为止。
匹配项后面也不需要再加 break
在这里插入图片描述
太简单,自己去试试即可

for循环语句
在这里插入图片描述

就是java的for循环,没有加括号而已
go语言遍历字符串,常用的是: for-range
如果我们的字符串含有中文,那么传统的遍历字符串方式,就是错误,会出现乱码。原因是传统的
对字符串的遍历是按照字节来遍历,而一个汉字在 utf8 编码是对应 3 个字节。

for index,value := range 目标字符串{}

九、函数、包和错误处理

上述的知识点,都是跟其他语言类似的。我就没有说得太细。说了也记不住。以后遇到了,就说一下即可。我这个人有点懒~。这章才是重点

在这里插入图片描述

go 的每一个文件都是属于一个包的,也就是说 go 是以包的形式来管理文件和项目目录结构

在访问其它包函数,变量时,其语法是 包名.函数名, 比如这里的 main.go 文件中
如果给包取了别名,则需要使用别名来访问该包的函数和变量

基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传
入变量的地址&,函数内以指针的方式操作变量

包结构
在这里插入图片描述
fun.go

package fun

import"fmt"var(
    Name ="hang")//指定返回值名,系统可以判断,自行返回funcChange(name *string)(result bool){*name ="王麻子"
     result =truereturn}funcIsChange(){
    fmt.Printf("Name = %s",Name)}

main.go

package main

import"fmt"import"go_code/study/OOP/demo01/fun"funcmain(){
    boo := fun.Change(&fun.Name)if boo {
        fmt.Println("Name值已经改变")
        fun.IsChange()}}

在这里插入图片描述
init函数【初始化函数】

每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也
就是说 init 会在 main 函数前被调用

包结构
在这里插入图片描述
first.go

package first

import"fmt"funcinit(){
    fmt.Println("我现在在first的Init()中")}funcFirst(){
    fmt.Println("我现在在first的First()中")}

second.go

package second

import"fmt"import"go_code/study/OOP/demo02/first"funcinit(){
    fmt.Println("我现在在Second的Init()中")}funcSecond(){
    fmt.Println("我现在在Second的Second()中")
    first.First()}

three.go

package three

import("fmt""go_code/study/OOP/demo02/second")funcinit(){
    fmt.Println("我现在在Three的Init()中")}funcThree(){
    fmt.Println("我现在在Three的Three()中")
    second.Second()}

main.go

package main

import("fmt")import"go_code/study/OOP/demo02/three"funcinit(){
    fmt.Println("我现在在main的Init()中")}funcmain(){

    fmt.Println("我现在在main的main()中")
    three.Three()}

效果如下
在这里插入图片描述

Init()【初始化函数】的优先级是最高的

匿名函数与全局匿名函数

Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考
虑使用匿名函数,匿名函数也可以实现多次调用

package main

import("fmt""strconv")//全局匿名函数//定义一个函数,参入多个string类型,转换为float,相加,返回结果值var hri =func(str ...string)(result float64){for_,value :=range str{
        va,err := strconv.ParseFloat(value,64)if err ==nil{
            result += va
        }}return}funcmain(){// 只使用一次的局部匿名函数
    first :=func(number int64)(result string){return strconv.FormatInt(number,10)}(10)

    fmt.Printf("first的类型为:%T\n",first)//    可以使用多次的局部匿名函数//    定义一个将多个int类型的变量转换为string类型后使用‘+’拼接,返回一个string
    sec :=func(number ...int64)(result string){for index,_:=range number{
            result += strconv.FormatInt(number[index],10)}return}

    fmt.Printf("sec结果为: %s\n",sec(11,22,33,44,55))
    fmt.Printf("hri结果为: %f",hri("11","22","33"))}

在这里插入图片描述
闭包

闭包就是一个函数和与其相关的引用环境组合的一个整体;【;类似于累加器】

包结构
在这里插入图片描述

upper.go

package upper

funcAddUpper()func(string)string{var str stringreturnfunc(s string)string{
        str += s
        return str
    }}

main.go

package main

import("fmt""go_code/study/OOP/demo04/upper")funcmain(){

    re := upper.AddUpper()
    fmt.Println(re("S"))
    fmt.Println(re("B"))
    fmt.Println(re("在"))
    fmt.Println(re("笑"))
    fmt.Println(re("wo!"))}

在这里插入图片描述
延时加载defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完
毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)
defer是一个栈结构,其作用域是函数或则方法内部;一般与异常处理机制搭配使用
defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源

main.go

package main

import"fmt"funcmain(){defer fmt.Println("我是one,我被defer修饰,所以我是第一个被放入一个defer栈里")defer fmt.Println("我是tow,我被defer修饰,所以我是第er个被放入一个defer栈里,one被我踩着")
    fmt.Println("栈结构--> 先进后出")}

在这里插入图片描述

这个等将到连接数据库的时候再说。

变量作用域

函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用
域在整个程序有效
如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块

字符串常用的系统函数

统计字符串的长度,按字节 len(str)
字符串 转 []byte: var bytes = []byte(“hello go”)
[]byte 转 字符串: str = string([]byte{97, 98, 99})
不 区 分 大 小 写 的 字 符 串 比 较 (== 是 区 分 字 母 大 小 写 的 ): fmt.Println(strings.EqualFold(“abc”,
“Abc”))
返回子串在字符串第一次出现的 index 值,如果没有返回-1 : strings.Index(“NLT_abc”, “abc”)

这些不用记,用的时候去百度一下就ok了

错误处理

一些时间函数,常用函数我都没讲。因为我觉得没必要。学到后面,肯定是用新技术,比如gin框架,Gorm 框架形式的ORM模型。所以,我们基础部分学个主要内容就ok了。至于那些不重要的,你学了,可能一辈子都用不到。有个印象就好。
错误处理就是一个重点!我会仔细的讲

只要是编程语言,都有一套完整的错误处理机制。比如,java的try{}catch{}finally{};同样,go也有

先看一个错误案例:

package main

import"fmt"funcmain(){//分母为0,报错var num,ber uint8=12,0
    result := num/ber

    fmt.Println("result = ",result)}

在这里插入图片描述在这里插入图片描述

在默认情况下,当发生错误后(panic) ,程序就会退出
你可以这么理解:当程序报异常【go语言中:异常 == 错】时,会抛出一个panic变量,这个panic变量存放着错误信息。然后在控制台打印出来

如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。有没有这种错误机制呢?

Go 中引入的处理方式为:defer, panic, recover【defer来了
Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。

package main

import"fmt"//定义一个test函数,传入多个int类型的值,返回一个结果和异常信息functest(number ...int)(result int){//    使用defer + recover 来捕获异常和压栈异常deferfunc(){//1.使用recover函数去捕获异常
        er :=recover()if er !=nil{
            fmt.Println("er ==",er)}}()//假设传入两个参数值
    result = number[0]/number[1]return}funcmain(){

    fmt.Println("result = ",test(10,0))
    fmt.Println("我是异常后的部分语句")}

在这里插入图片描述

上述的代码中,有个疑问:defer func() {}是匿名函数,对吧。谁调用了??就是说:没人管他,他凭什么执行了?
被defer修饰的语句,在即将结束该函数的时候,会自动执行defer内的语句代码

问题:defer能来修饰变量吗??

自定义错误

Go 程序中,也支持自定义错误, 使用 errors.New 和 panic 内置函数。
errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序.

package main

import("errors""fmt")//定义一个test函数,传入多个int类型的值,返回一个结果和异常信息functest(number ...int)(result int,err error){if number[1]==0{
        err = errors.New("number的第2位数不能为 0 !")return}//假设传入两个参数值
    result = number[0]/number[1]return}funcmain(){
    result,err :=test(10,0)

    fmt.Printf("result = %d ,err = %v",result,err)}

在这里插入图片描述

十、数组与切片

任何一门编程语言,都有数组;这不必说。在go中的切片,就是一个动态的数组,没什么高大上的。

声明数组

在这里插入图片描述

数组是一个却确定大小和数据类型的一串连续的内存空间。
数组的地址可以通过数组名来获取 &Array
数组的第一个元素的地址,就是数组的首地址
数组的各个元素的地址间隔是依据数组的类型决定

初始化数组的四种方式

package main

import("fmt""unsafe")//全局变量可以光写不用var(//    1.声明一个string类型的,大小为5个单位的数组并赋值初始化
    str [5]string=[5]string{"one","tow","three"}//    2.声明一个可以放任意类型元素的,大小由系统决定的数组并初始化
    ene =[...]interface{}{"string",112,true}//    3.声明一个可以指定下标存储元素的数组
    num =[...]float64{1:11.11,3:33.33,2:22.22})funcmain(){//    1.证明数组是否连续空间存储for index,value :=range str{
        fmt.Printf("str数组的第 %d 个下标的地址是:%d,占用了%d个字节,元素值为%s\n",index,&str[index],unsafe.Sizeof(value),value)}//    2.在声明时,未自定数组大小,系统会指定大小;下划线是忽略for_,value :=range ene{
        fmt.Println("ene的值为",value)}
    fmt.Println("ene的大小为:",len(ene))//    3.证明数组是否按下标指定存值for index,value :=range num{
        fmt.Printf("第%d个下标的值为:%f",index,value)}}

在这里插入图片描述

数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化

切片

切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制
切片的长度是可以变化的,因此切片是一个可以动态变化数组

切片就是没有指定大小的数组

package main

import"fmt"var(
    array =[...]interface{}{"first","second",12,false})funcmain(){//    声明一个切片,去切array数组,array[0:2]表示从array的下标为0的位置开始切,切到下标为2的位置 【左包右不包】var slicp []interface{}= array[0:2]//    遍历切片for index,value :=range slicp{
        fmt.Printf("slicp 的下标为 %d ,值为: %v\n",index,value)}//    改变切片中的元素,看看array数组是否改变
    slicp[0]="王麻子"for index,value :=range array{
        fmt.Printf("array 的下标为 %d ,值为: %v\n",index,value)}}

在这里插入图片描述
不通过数组,自定义一个切片

通过 make 方式创建切片可以指定切片的大小和容量
如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool =>false]
通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素

//    1.通过make 初始化空间 定义了一个任意类型的容量为12的切片var first =make([]interface{},12)//    2.隐藏make,直接存放元素,初始化var second []interface{}=[]interface{}{"one",true,false}

十一、map【键值对】

Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现。跟java的map接口下实现的hashmap是一样的
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用

map[KeyType]ValueType

//map类型的变量默认初始值为nil,需要使用make()函数来分配内存.跟切片一致make(map[KeyType]ValueType,[cap])//cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量
package main

import"fmt"var(//定义了一个key=int,value=任意类型的,容量为20的map,并且进行了初始化【默认值】,赋予了空间
    smap =make(map[int]interface{},20)//隐藏make
    wmap =map[int]interface{}{1:12,2:"刘备",3:false,})funcran(xmap map[int]interface{}){for index,value :=range xmap{
        fmt.Printf("index == %d ,value = %v\n",index,value)}}funcmain(){
    smap[1]="张三"
    smap[0]="李四"
    smap[2]="王五"//    删除指定map中的key-valuedelete(smap,1)//    判断指定map的key是否存在
    value, ok := smap[1]if ok ==true{
        fmt.Println("值存在:", value)}else{
        fmt.Println("不存在该值")//    遍历mapran(smap)ran(wmap)}}

在这里插入图片描述

十二、结构体

go中的结构体,就类似于java中的类

数据类型别名

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型

package main

import"fmt"//类似于给int数据类型取别名,很鸡肋的//这还不是结构体type myInt intfuncmain(){var age myInt =12//既然给了别名。原则上就不能用int了,但用了也不报错var year int=2022
    fmt.Println(age,year)}

效果就不展示了。以后也不建议你们这么用。用了会挨揍!【我被揍过,揍了还得改回来】

给你们看个好看的
在这里插入图片描述

看见了吧,byte就是uint8的别名!其实他们是一样的;还有rune,就是个int32。

//类型定义type NewInt int//类型别名type MyInt =intfuncmain(){var a NewInt
    var b MyInt
    
    fmt.Printf("type of a:%T\n", a)//type of a:main.NewInt
    fmt.Printf("type of b:%T\n", b)//type of b:int}

结构体

上面的一切都不重要,真的。因为以后学gin框架后有新技术,新函数代替。但是结构体是绝对重要的。
就说java,java中定义的实体类必须要跟数据库的字段映射。go也是这样!

定义结构体,记住:结构体是一种类型,是一个规范【模板】,其本身是没有空间的;不是变量

type 类型名 struct{
    字段名 字段类型
    字段名 字段类型
    …
}

类型名:标识自定义结构体的名称,在同一个包内不能重复
字段名:表示结构体字段名。结构体中的字段名必须唯一。
字段类型:表示结构体字段的具体类型

实例化结构体

var 结构体实例 结构体类型

只有实例化了,这个实例才能有空间
你可以理解为java的类

包结构
在这里插入图片描述
person.go

package person

//定义一个首字母大写的结构体type Person struct{
    name string
    age uint8
    isMarry bool}

main.go

package main

import("fmt""go_code/study/OOP/demo09/struct/person")funcmain(){//    实例化一个personvar onePeople person.Person
    //使用指针变量var prt *person.Person =&onePeople
//    这个时候,onePeople其实已经有默认值了,即:有空间了
    fmt.Printf("onePeople的空间地址为%v,其值为:%v",prt,onePeople)}

在这里插入图片描述

匿名结构体

所谓的匿名结构体,一般是只有一个实例

//定义一个匿名结构体var User struct{    name string
                    age int}

User就是这个匿名结构体的实例了

package main

import("fmt""go_code/study/OOP/demo09/struct/person")funcmain(){//    实例化一个personvar onePeople person.Person
    //使用指针变量var prt *person.Person =&onePeople
//    这个时候,onePeople其实已经有默认值了,即:有空间了
    fmt.Printf("onePeople的空间地址为%v,其值为:%v\n",prt,onePeople)//使用new(type)返回的是指针var num =new(person.Person)
    fmt.Printf("num的空间地址为%v,其值为:%v\n",num,*num)if prt == num {
        fmt.Println("相同地址prt == num")}else{
        fmt.Println("不同地址prt != num")}}

在这里插入图片描述

看哈:onepeople与num的地址都是&{0 false} ;结果却是不同地址的。因为,onepeople与num是两个不相关的变量。你记住:new()返回的是指针类型的

new(Person) == &Person

在这里插入图片描述对结构体实例进行初始化

包结构
在这里插入图片描述
person.go

package person

//定义一个首字母大写的结构体type Person struct{//字段的首字母必须大写,才能被其他包的实例访问到
    Name string
    Age uint8
    IsMarry bool}//定义一个匿名结构体var User struct{    Name string
                    Age int}

main.go

package main

import("fmt""go_code/study/OOP/demo09/struct/person")funcmain(){

    one := person.Person{
        Name :"sss",
        Age:12,
        IsMarry:false,}
    fir :=&person.Person{
        Name:"王麻子",}

    person.User.Age =23
    person.User.Name ="用户"

    fmt.Println(one)
    fmt.Println(fir)
    fmt.Println(person.User)}

结构体占用一块连续的内存。

构造函数

Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型

person.go

package person

//定义一个首字母大写的结构体type Person struct{//字段是私有的,必须通过构造函数才能访问
    name string
    age uint8
    isMarry bool}funcNewPerson(name string,age uint8,isMarry bool)(per *Person){
    per =&Person{// 字段 : 值【参数】
        name : name,
        age: age,
        isMarry: isMarry,}return}

main.go

package main

import("fmt""go_code/study/OOP/demo09/struct/person")funcmain(){//    通过构造函数NewPerson去创建一个Person实例,返回的是指针变量,即:地址
    per := person.NewPerson("张三",12,false)
    fmt.Println(*per)}

方法的定义

func(接收者变量 接收者类型) 方法名(参数列表)(返回参数){
    函数体
}

方法跟函数差不多,只不过:函数是用去调用,方法就是用结构体实例去调用。就这么点区别
接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等
person.go

//注意: p Person 不是参数,只是表明 这个方法隶属于哪个结构体而已,这个p也可以指代调用这个方法的person实例func(p Person) Bmg (number int){
    fmt.Println(p,number)}

main.go

package main

import("fmt""go_code/study/OOP/demo09/struct/person")funcmain(){//    通过构造函数NewPerson去创建一个Person实例,返回的是指针变量,即:地址
    per := person.NewPerson("张三",12,false)
    per.Bmg(12)
    fmt.Println(*per)}

在这里插入图片描述

十三、封装、继承、多态

Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样

封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作
类似于java中私有字段的get/set方法

封装适用于结构体中私有的属性,即:首字母小写的字段
在这里插入图片描述我们先用构造函数来试试,看能不能去操作私有字段?

包结构
在这里插入图片描述
sg.go

package sg

//定义一个私有的结构体 studenttype student struct{//公共字段
    Name string//私有字段
    age uint8
    school string}//工厂模式---构造函数,返回student字段的指针变量//问:我能不能直接通过构造函数去操作私有字段?funcNewStudent(name string,age uint8,school string)*student{return&student{
        Name: name,
        age: age,
        school: school,}}

main.go

package main

import("fmt""go_code/study/OOP/demo10/sg")funcmain(){
    prt := sg.NewStudent("hang",23,"大学")
    fmt.Println(prt)//    我想单独取出私有字段,可行吗?即:prt.age   prt.school
    prt.Name ="王麻子"
    fmt.Println(prt)//    我能通过构造函数去修改私有字段的值吗?
    prt = sg.NewStudent("赵六",12,"小学")
    fmt.Println(prt)}

效果
在这里插入图片描述

结论:通过构造函数,可以一次性的去修改所有字段,也能把所有字段集中取出。但是无法操作单个的私有字段【因为无法访问】;所以,我们针对单个的私有字段,去给私有字段写一个方法,去专门的操作它。
你可以这么理解:构造函数是所有字段的门;get() | set() 是针对于一个私有字段的门

get() | set()

sg.go

package sg

import"fmt"//定义一个私有的结构体 studenttype student struct{//公共字段
    Name string//私有字段
    age uint8
    school string}//工厂模式---构造函数,返回student字段的指针变量//问:我能不能直接通过构造函数去操作私有字段?funcNewStudent(name string,age uint8,school string)*student{return&student{
        Name: name,
        age: age,
        school: school,}}//针对于age私有字段的写入操作func(s *student)SetAge(age uint8){if age <8{
        fmt.Println("未成年,不可写入")}else{//写入
        s.age = age
    }}//针对于age私有字段的取出操作func(s *student) GetAge ()(age uint8){return s.age
}//同理,school私有字段func(s *student) SetSchool (school string){if school =="小学"|| school =="大学"|| school =="中学"{
        s.school =school
    }else{
        fmt.Println("未识别学校")}}func(s *student) GetSchool ()(school string){return s.school
}

main.go

package main

import("fmt""go_code/study/OOP/demo10/sg")funcmain(){
    first := sg.NewStudent("张三",22,"大学")
    fmt.Printf("%s在%s读书,今年%d岁了\n",first.Name,first.GetSchool(),first.GetAge())//    改变first的school值
    first.SetSchool("小学")
    first.SetAge(10)
    fmt.Printf("%s在%s读书,今年%d岁了",first.Name,first.GetSchool(),first.GetAge())}

在这里插入图片描述继承

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个匿名结构体即可

go的继承跟java不一样;go的继承更类似于组合

包结构
在这里插入图片描述stu.go

package stu

import("fmt")//定义一个结构体 person ,私有的结构体如果想被其他包访问,就只能通过构造器type person struct{
    name string}//person的构造器;问题来了:假如一个结构体内只有一个私有字段,是不是只写一个构造器就可以了?不用写get() | set()funcNewPerson(name string)*person{return&person{
        name: name,}}//定义一个womantype woman struct{//    继承person的name
    person
}//woman的构造器func NewWoman (name string)*woman{return&woman{//这个是woman中的person匿名结构体字段
        person{
            name: name,},}}//写一个woman的方法func(w *woman) WoDoing (){
    fmt.Println(w.name,"只能坐着窝尿")}type man struct{//同样,继承person
    person
}//woman的构造器func NewMan (name string)*man{return&man{//这个是woman中的person匿名结构体字段
        person{
            name: name,},}}func(m *man)MaDoing(){
    fmt.Println(m.name,"我会站着窝尿")}

main.go

package main

import"go_code/study/OOP/demo11/stu"funcmain(){
    woman :=stu.NewWoman("女人")
    man :=stu.NewMan("男人")

    woman.WoDoing()
    man.MaDoing()}

效果
在这里插入图片描述

总结:

私有的结构体,只能通过公开的构造器才能被其他包访问
构造器返回的是指针变量,即:你创建的实例地址
结构体寻找字段是依据 【就近原则】

接口

Go语言中提倡使用面向接口的编程方式实现解耦

接口的定义格式如下

type 接口类型名 interface{
    方法名1( 参数列表1) 返回值列表1
    方法名2( 参数列表2) 返回值列表2
    …
}

接口中只能有方法

接口类型名:Go语言的接口在命名时,一般会在单词后面添加er
方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问
接口就是规定了一个需要实现的方法列表,在 Go 语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口。

接口的作用:在不改变旧代码的情况下,扩展新代码

package main

import"fmt"//定义一个接口type buyer interface{//    方法buy()}//定义一个结构体type VX struct{
    Name string}//实现了buyer接口func(v VX)buy(){
    fmt.Println(v.Name,"去买东西")}funcmain(){
    vx := VX{
        Name:"微信",}
    vx.buy()}

在这里插入图片描述
我现在想用支付宝去买东西,怎么在不破坏原代码的基础上去增添新代码?

package main

import"fmt"//定义一个接口type buyer interface{//    方法buy()}//定义一个结构体 --微信type VX struct{
    Name string}//实现了buyer接口func(v VX)buy(){
    fmt.Println(v.Name,"去买东西")}//支付宝type ZFB struct{
    Name string}func(z ZFB)buy(){
    fmt.Println(z.Name,"去买东西")}funcmain(){
    vx := VX{
        Name:"微信",}
    vx.buy()

    zub := ZFB{
        Name:"支付宝",}
    zub.buy()}

在这里插入图片描述

接口更像是一种规范!!!

断言

//类型断言
x.(T)
  • x:表示接口类型的变量
  • T:表示断言x可能是的类型

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。

十四、总结

我知道我写的很简单,还有好多知识点没有写到。但是呢,作为过来人,说一句:一些知识点,可能你一辈子也遇不到。不要追求把所有基础的知识点都掌握。不然,你的学习效率非常低,而且没有学习的乐趣。就算真的遇到了很冷门的知识点,你也可以去百度上搜。你今天记住了,过两天就忘了;还不如不去记,对吧。何必呢?这么为难自己?再说一句吧。老实+勤奋+不知变通的学习方式,是学不好编程的!没有一个程序猿是呆瓜!

找一个你认为效率高的学习方式最重要;只要能敲出代码,比什么都强!

标签: golang

本文转载自: https://blog.csdn.net/qq_46955316/article/details/128471387
版权归原作者 彳Microchip亍 所有, 如有侵权,请联系我们删除。

“golang开发工程师-第一步:golang入门基础教学”的评论:

还没有评论