Golang学习(一)

1.基础知识

1.1Golang语言

1.1.1简介

Go语言保证了即能静态编译语言的安全和性能,又达到了动态语言考法维护的高效率,Go=C+Python,说明Go语言既有C语言开发维护的高效率,又能达到python快速开发的高效率。

1.1.2特点

1)从C语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等也保留了和C语言一样的编译执行方式及弱化的指针
2)引入了包的概念,用于组织程序结构,Go语言的一个文件都要归属于一个包,而不能单独存在。
3)垃圾回收机制,内存自动回收,不需要开发人员管理
4)天然并发
(1)从语言层面支持并发,实现简单
(2)groutine,轻量级线程,可实现大并发处理,高效利用多核
(3)基于CPS并发模型实现
5)吸收了管道通信机制,形成Go语言特有的管道channel通过管道channel,可以是西安不同的goroute之间的相互通信
6)函数可以返回多个值
7)新的创新:比如切片slice、延时执行defer

1.2GOlang常用的转义字符

1) \t :一个制表位,实现对齐功能

2) \r:一个 回车,从当前的最前面开始输出,覆盖掉以前内容

和C同

1.3常见DOS命令

1)md test100 新建文件夹test100,多个目录用空格间隔就可
2)dir 查看当前目录下的文件夹和文件
3)cd /d d: 切换到d盘下
4)cd .. 回到上级目录
5)cd\ 回到根目录
6)rd test100 删除test100,只能删除空文件夹
7)rd /q/s test100 /q不用询问; /s包括子文件夹和文件
8)rd /s test100 询问,选择是否删除后在删除
9)echo hello 向当前文件写入hello
10)echo hello >d:\test100\abc.txt 向d:\test100\abc.txt这个路径的文件写入hello
11)copy abc.txt d:\text200\ok.txt 拷贝当前文件下的abc.txt到指定目录下的文件,其中如此例中的ok.txt系统可自己建立
12)move abc.txt d:\text200 移动当前文件夹下面的abc.txt到指定路径下面
13)del abc.txt 删除当前文件夹下面的abc.txt
14)del *.txt 删除当前文件夹下面的所有 .txt
15)cls 清屏
16)exit 退出

绝对路径:从当前盘的盘符最上面开始定位,比如D:开始找到需要的文件路径 cd /d d:test100
相对路径:从当前位置开始定位,去找对应的目录。找这个文件夹下面的子文件或者文件夹。cd test100

1.4安装GO及配置环境变量

1.4.1Go语言的SDK

SDK就是软件开发工具包,我们做GO开发,首先需要先安装并配置好SDK

1.4.2GOlang环境变量配置及其作用

  GPROOT:指定go sdk安装目录;
  Path:指令 sdk\bin 目录,
  GOPATH:就是golang工作目录,项目的源码存放在此目录下

1.4.3golang程序的编写、编译、运行步骤

编写:写源码;编译:go build 源码-》生成二进制可执行文件
运行:1)对.exe 可执行文件运行,速度较快;2)go run 源码

1.5golang程序编写规则

1)go文件的后缀.go
2)go程序区分大小写
3)go语句后不需要写分号,系统自动生成
4)go语句中引用的import包和定义的变量必须使用,否则报错。
5)go中不能把多条语句放在同一行,否则报错
6)go中的大括号成对出现,注意代码风格

2.Golang的变量

2.1变量

2.1.1变量的概念

内存中一个数据存储空间的表示;定义(声明)-》赋值-》使用

2.1.2变量使用注意事项

1)指定变量类型,声明后若不赋值默认0
2)根据值类型推导变量类型
例如:var num=10.1 则num为浮点数
3)省略var,注意:=左侧的变量不因该是已经声明过的,否者会导致编译错误
例如:name:="tom" (等价于var name string name="tom")
4)多变量声明
例如1:var nan1,nan2,nan3 int
例如2:var n1,name,m3=100,"tom",300 一一对应
例如3:n1,name,m3:=100,"tom",300 一一对应
例如4:全局声明
var n1=100
var n2=200
var n3=300
也可改写成:
var(
n1=100
n2=200
n3=300
)

5)该区域的数据值可以在同一类型范围内不断变化
var i int =10
i=20
i=30
i如果赋值为1.1则会报错,可改变数值类型,但是不可以改变类型
6)变量在同一一个作用域不可重名
7)变量三要素:变量名+值+数据类型
8)Golang的变量如果么有赋初值,编译器会使用默认值,int型默认0;string类型默认空串

2.1.3声明变量、初始化、赋值

1)声明变量
基本语法:var 变量名 数据类型
var a int 这就是声明了一个变量,变量名是a;
var num1 float32 声明一个单精度小数类型变量名num1
2)初始化变量
在声明变量时就给值
var a int=45
使用细节:如果声明时就直接赋值,可省略数据类型
var a=12
3)赋值
先声明变量,后赋值

2.2程序中+的使用

1)当左右两边都是数值型时,则做加法运算
2)当左右两边都是字符串时,则做字符串拼接

2.3整数类型

2.3.1常见整数类型

类型   有无符号  占用存储空间       表数范围
int8     有     1字节       -128-127
int16   有     2字节      -2^15-2^12-1
int32
int64
uint8   无     1字节        0-255
uint16   无     2字节        0-2^16-1
uint32
uint64
int     有     32位系统4字节,64位系统8字节
uint    有     同上
rune    有     与int32一样(表示一个unicode码)
byte    无     与int8等价(用于存储单个字符)

2.3.2整数使用细节

1)Golang的整型默认声明为int型
2)如何在程序查看某个变量的字节大小和数据类型
数据类型:fmt.printf("%T",i)
字节大小:
3)Golang程序中整数变量在使用时,遵守保小不保大的原则,即:在保证程序正确运行的情况下,尽量使用占用空间小的数据类型
4)bit:计算机中最小的存储单位;byte计算机中基本存储单元
1byte=8bit

2.4浮点型

2.4.1基本介绍

用于存放小数,比如:1.12
类型 占用存储空间 表数范围
单精度float32 4字节 -3.403E38-3.403E38
双精度double64 8字节 -1.798E308-1.798E308
说明:
1)关于浮点数在计算机中的存储形式:浮点数=符号位+指数为+尾数位
2)尾数部分可能丢失,照成精度损失

2.4.2浮点数使用细节

1)Golang浮点数类型有固定的范围和字段长度,不是具体操作系统影响
2)Golang的浮点类型默认float64
3)十进制数形式:2.12;.13
4)支持科学计数法形式
5)通常开发情况下使用float64

2.5字符类型

2.5.1基本介绍

Golang中没有专门的字符类型,如果要存储单个字符,一般使用byte来保存。字符串就是一串固定长度的字符链接起来的字符序列。Go的字符串是由单个字节链接起来的。就是说对于传统的字符串是由字符组成的,而Go的字符串不同,它是由字节组成的。
其它计算机语言也是由字节组成。
说明:
1)如果我们保存的字符在ASCII表中,比如{0-1,a-z,A-Z}可以直接保存到byte
2)如果我们保存的字符对应码值大于255,这时我们因该考虑使用int类型保存
3)输出字符 使用格式化输出-%c

2.5.2字符类型使用细节

1)字符常量是用单引号('')括起来的单个字符。
2)Go中允许使用转义字符'\'来将其后的字符转变为特殊字符型常量。
3)Go语言的字符使用UTF-8编码,英文1字节,汉字3字节
4)在Go中字符的本质是一个整数,直接输出时,是该字符对应的UTF-8编码的码值。
5)可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出unicode字符
6)字符类型都是可以运算的相当于Unicode码

2.5.3字符类型本质探讨

1)字符型 存储到计算机中,需要将字符对应的码值找出来
存储:字符-->对应码值-->二进制-->存储
读取:二进制-->码值-->字符-->读取
2)字符和码值的对应关系是事先通过UTF-8决定好的

2.6布尔类型

2.6.1基本介绍

1)布尔类型也叫bool类型,bool类型数据只允许取值true和false
2)bool类型占一个字节。
3)bool适用于逻辑运算,一般用于程序流程控制——if、for

2.7string类型

2.7.1基本介绍

字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是用单个字节连接起来的,使用UTF-8编码标识的Unicode文本

2.7.2使用注意事项和细节

1)统一编码,这样就不会照成乱码
2)字符串一旦赋值了就不能修改,即不能修改字符串中的某个字符。比如:str[0]=b;
3)字符串的两种表现形式
(1)双引号,会识别转移字符
(2)反引号,以字符串的原生形式输出。包括换行和特殊字符,可以实现防止攻击、输出源代码等效果。
4)字符串拼接
'+'号连接,如果字符串太长+一定要写在上一行

2.8基本数据类型的默认值

2.8.1基本介绍

在Go中,当程序员没有给一个变量赋值时,就会保留默认值

2.8.2基本数据默认值

数据类型  默认值
整型    0
浮点型   0
字符串型       ""
布尔类型       false

2.9基本数据类型的相互转换

2.9.1基本介绍

Golang和java/c不同,Go在不同类型的变量之间赋值需要显示转换。不能自都转换

2.9.2基本语法

表达式: T(v)   将值v转换为类型T
T:就是数据类型,比如int32,int64,float32等
v:需要转换的变量
基本数据类型相互转换的注意事项
1)Go中,数据类型的转换可以是从表示范围小-->表示范围大,也可以从表示范围大-->表示范围小
2)被转换的是变量的值,变量本身数据类型没有变化!
3)在转换过程中从表示范围大-->表示范围小如果超过了表示范围会按照溢出处理,不会报错。

2.10基本数据类型和string的转换

 2.10.1基本介绍

在程序开发中,经常需要string和基本数据类型的转换

2.10.2基本类型转string类型

方法1:fmt.Sprintf("%参数“,表达式)

参数需要和表达式的类型相匹配

方法2:strconv包的函数

1)func FormatBool

func FormatBool(b bool) string

 根据b的值返回"true"或"false"。

2)func Formatint

func FormatInt(i int64, base int) string

返回i的base进制的字符串表示。base 必须在2到36之间,结果中会使用小写字母'a'到'z'表示大于10的数字。

3)func FormatUint

func FormatUint(i uint64, base int) string

是FormatInt的无符号整数版本。

4)formatFloat

func FormatFloat(f float64, fmt byte, prec, bitSize int) string

函数将浮点数表示为字符串并返回。

bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。

fmt表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)。

prec控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。

5)func Itoa

func Itoa(i int) string

Itoa是FormatInt(i, 10) 的简写。

 2.10.3string类型转基本数据类型

 方法:使用strconv包的函数

1)func ParseBool

func ParseBool(str string) (value bool, err error)

返回字符串表示的bool值。它接受1、0、t、f、T、F、true、false、True、False、TRUE、FALSE;否则返回错误。

2)func ParseInt

func ParseInt(s string, base int, bitSize int) (i int64, err error)

返回字符串表示的整数值,接受正负号。

base指定进制(2到36),如果base为0,则会从字符串前置判断,"0x"是16进制,"0"是8进制,否则是10进制;

bitSize指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64;返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange。

3)func ParseUint

func ParseUint(s string, base int, bitSize int) (n uint64, err error)

ParseUint类似ParseInt但不接受正负号,用于无符号整型。

4)func ParseFloat

func ParseFloat(s string, bitSize int) (f float64, err error)

解析一个表示浮点数的字符串并返回其值。

如果s合乎语法规则,函数会返回最为接近s表示值的一个浮点数(使用IEEE754规范舍入)。bitSize指定了期望的接收类型,32是float32(返回值可以不改变精确值的赋值给float32),64是float64;返回值err是*NumErr类型的,语法有误的,err.Error=ErrSyntax;结果超出表示范围的,返回值f为±Inf,err.Error= ErrRange。

2.10.3string转基本数据类型注意事项

将string转基本数据类型时,要确保string类型能够转成有效的数据。比如把”123“转成整数123,但是不能把”hello"转成一个整数

如果数据非有效数据这转成这个类型的默认值。

2.11指针

2.11.1基本介绍

1)基本数据类型,变量存的就是值,也叫值类型

2)获取变量的地址,用&,比如:var nam int,获取num的地址:&num

3)指针变量存的是一个地址,这个地址指向的空间存的才是值

比如:var ptr *int=&num,ptr类型为*int

4)获取指针类型所指向的值使用*,*ptr

2.11.2指针使用细节

1)值类型和对应的指针类型一一对应

2)值类型包括:基本数据类型int系列、float系列、bool、string、数组和结构体struct

2.12值类型和引用类型的使用特点

1)值类型:变量直接存储值,内存通常在栈中分配

2)引用类型:变量存储的是一个地址,这个地址对应的的空间才真正存储数据,内存通常在堆上分配,当没有变量引用这个地址时,该地址对应的数据空间就变成一个垃圾,由GC来回收。

2.13标识符的命名规范

2.13.1标识符概念

1)Golang对各种变量、方法、函数等命名时使用的字符序列称为标识符

2)凡是自己可以起名字的地方都叫标识符

2.13.2标识符的命名规则

1)由26个英文字母大小写,0-9,_组成

2)数字不能开头

3)标识符严格区分大小写

4)不能包含空格

5)不能单写_,"_"在Go中是一个特殊的标识符,称为空标识符。可以代表任何其他的标识符,但是他对应的值会被忽略。所以仅作为占位符使用,不能作为标识符使用

6)不能以系统保留关键字(25个)作为标识符,比如break,if等

2.13.3标识符命名注意事项

1)包名:保持package的名字和目录保持一致,尽量采取有意义的包名,简短、有意义,不要和标准库冲突。

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

例如:var stuName string="tom"

3)如果变量名、函数名、常量名首字母大写,则可以被其他包访问;如果小写则只能在本包使用。

2.13.3系统保留关键字

25个,简化代码编译过程中对代码的解析

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

2.13.4系统预定义标识符

36个预定义标识符,包括基础数据类型和系统内嵌函数

append bool byte cap close complex
complex64 complex128 uint16 copy false float32
float64 imag int  int8 int16 uint32
int32 int64 iota len make new
nil panic uint64 print println real
recover string true uint uint8 uintprt

 

 3.运算符

3.1运算符的基本介绍

运算符是一种特殊的符号,用以标识数据的运算、赋值和比较等

3.2算术运算符

算术运算符是对数值类型的变量进行运算的,包括+、-、*、/、%、++、--、+(符号串相加)、+(正号)、-(负号)

注意:

除法运算"/"时,如果两边的数为整数,则计算的结果也为整数(舍弃小数部分)

%运算:a%b=a-a/b*b

只有后置++、--且比如a++只能独立使用,不能b=a++

3.3关系运算符

3.3.1基本介绍

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

2)关系表达式经常用在if结构的条件中或循环结构的条件中

3)包括:==、!=、<、>、<=、>=

3.4逻辑运算

3.4.1基本介绍

用于链接多个条件(一般来讲就是关系表达式),最终的结果也是一个bool值

包括&&、||、!

3.4.2注意事项

1)&&也叫短路与:如果第一个条件为false,这第二个条件不会被判断

2)||也叫短路或:如果第一个条件为true,则第二个条件不会判断,最终结果为true

3.5赋值运算符

3.5.1基本介绍

赋值运算符就是将运算后的值,赋给指定的变量

包括:

=、+=、*=、-=、/=、%=

<<=:左移后赋值,C<<=2相当于C=C<<2

>>=:右移后赋值

&=、^=(按位异或后赋值)、|=

3.5.2赋值运算符的特点

1)运算顺序从右往左

2)赋值运算符的左边只能是变量,右边可以是变量、表达式、常量

3.6位运算(二进制参与运算)

&:两位全为1,结果为1,否者结果为0

|:两位中都为0,结果为0,否者结果为1

^:相同为0,不同为1

<<:符号位不变,低位补0,左移,相当于乘2

>>:低位溢出,符号位不变,并用符号位补溢出的高位,右移相当于除2

注意:计算机中位运算是数的补码参与运算

3.7其它运算符

&(取地址)、*指针变量取值

3.8特别说明

Go语言明确不支持三元运算符

如果要实现三运算符相同的效果,采用if-else结构

if i>j{
    n=i
} else {
    n=j
}

4.9运算符的优先级

 1)后缀:()、[]、->、++、--

2)单目:+、-、!、~、*、&

3)乘法:*、/、%

4)加法:+、-

5)移位:<<、>>

6)关系:<、<=、>=

7)==、!=

8)&

9)^

10)|

11)&&

12)||

13)赋值运算

14)逗号 

注意:只有单目运算、赋值运算符是从右往左,其他从左往右

3.10键盘输入语句

3.10.1介绍

在编程需要接受用户输入的数据,就可以使用键盘输入语句来获取。

3.10.2步骤

1)导入fmt包

2)调用fmt.Scanln()或者fmt.Scanf()

(1)func Scanf

 

func Scanf(format string, a ...interface{}) (n int, err error)

Scanf从标准输入扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。返回成功扫描的条目个数和遇到的任误。

例如:fmt.Scanf("%d,%f,%c",&a,&b,&c)

(2)func Scanln

 

func Scanln(a ...interface{}) (n int, err error)

Scanln类似Scan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置。

比如:fmt.Scanln(&name)

 3.11原码、补码、反码

对于有符号的而言:

1)二进制的最高位为符号位:0表示正数,1表示负数

2)正数的原码、补码、反码都一样

3)负数的反码=原码符号位不变,逐位取反;补码=反码+1

5)0的反码补码都位0

6)在计算机运算过程中,都是以补码的方式运算的

4.程序流程控制

在程序中,程序运行的流程控制决定程序是如何执行的,主要有顺序控制、分支控制、循环控制

4.1顺序控制

程序从上到下逐行的执行,中间没有任何判断和跳转

注意:变量、函数等必须先声明后使用(调用)

4.2分支控制

让程序有选择的执行,包括单分支、双分支、多分支

4.2.1单分支

基本语法:

if 条件表达式{
    执行代码块
}

说明:

1)if后的条件表达式建议不用括号括起来,括起来也没错

2){}是必须有的,不可省略

3)Golang支持在if中,直接定义一个变量,比如下面

if age:=19;age>18{
    .......
}

4.2.2双分支控制

基本语法:

if 条件表达式{
    执行代码块1
}else {
   执行代码块2 
}

4.2.3多分支控制

基本语法:

if 条件表达式1{
    执行代码块1
}else if条件表达式2{
    执行代码块2
}
.....
else{
    执行代码块n
}

说明;

1)else不是必须的

2)只执行一个代码块

4.2.4嵌套分支

在一个分支结构中又完整的嵌套了另一个分支结构,里面的分支结构称为内层分支,外面的分支结构称为外层分支

基本语法:

if 条件表达式{
    if 条件表达式{
        。。。。。
    }else {
        。。。。。
    }
}

注意:嵌套的分支不易过多,建议控制在三层内

4.3switch分支控制

基本介绍:

1)switch 语句用于基于不同条件执行不同动作,每一个case分支都是唯一的,从上到下逐一测试,直到比配为止

2)匹配项后面不需要加break

4.3.1基本语法

switch 表达式{
case表达式1,表达式2,表达式3....:
    语句块1
case表达式1,表达式2,表达式3....:
    语句块2
//这里可以有多个case语句
default:
    语句块
}

说明:

1)switch的执行流程是,先执行表达式,得到值,然后和case的表达式进行比较,如果相等,就匹配到,然后执行对应的语句块,然后退出switch控制。

如果没有和任何case匹配,则执行default的语句块,然后退出switch控制

2)Golang中的case后的表达式可以有多个,使用逗号隔开

3)不要写break

4.3.2注意事项

1)case/switch后是一个表达式(常量、变量、有返回值的函数等)

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

3)case后面的表达式如果是常量值,则要求不能重复

4)default语句不是必须的

5)switch后面可以不带表达式,case后面条件表达式,当if-else使用

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

例如:switch gread:=90;{

}

7)switch穿透-fallthrough,如果在case语句块后增加fallthrough,则会继续执行下一个case,也叫switch穿透

10)Type Switch:switch 语句可以被用于type-switch来判断某个interface变量中实际指向的变量 类型。

例如:

func main(){
     var x interface{}
     var y=10.0
     x=y
     switch i:=x.(type){
     case nil:
        fmt.Printf("x的类型:%T",i)
     case int:
        fmt.Printf("x的类型:int")
    case float64:
        fmt.Printf("x的类型:float64")
    default:
        fmt.Printf("x的类型未知")
     }
     
}

4.3.3switch和if的比较

1)如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用switch语句

2)其他情况:对区间判断和结果为bool类型的判断,使用if,if的使用范围更广

4.4for循环控制

基本语法:

  for 循环变量初始化;循环条件;循环变量迭代{

    循环操作

  }

注意事项和细节讨论:

1)循环是返回一个bool值的表达式

2)循环的第二种方式:

  for 循环判断条件{

    //循环体

  }

将变量初始化写在for循环前面,迭代写在循环体内

3)for循环的第三种使用方式

  for{

    //循环体

  }

等价于:for;;{}是一个死循环,结束循环需要break语句使用。

4)Golang提供for-range的方式,可以方便遍历字符串和数组

 字符串遍历方式1—传统方式:

func main(){
     var str string="hello,word!"
     for i:=0;i<len(str);i++{
        fmt.Printf("%c,",str[i])
     }
}

字符串遍历2—for -range:

str="abdf~dd"
     for index,val:=range str{
        fmt.Printf("index=%d,val=%c\n",index,val )
     }

说明:如果字符串遍历中包含中文,则不能采用传统方式。原因是传统的字符串遍历是按照字节来遍历的,go采用UTF-8编码,一个中文字符,对应3字节。

解决方法:1)for - range;2)将str转成[]rune 切片,如下

var str string="北京欢迎你!"
     str2:=[]rune(str)
     for i:=0;i<len(str2);i++{
        fmt.Printf("%c!\n",str2[i])
     }

4.5while,do-while采用for循环实现

4.6多重循环控制

1)将一个循环放在另一个循环体内,就形成了嵌套循环。建议:一般使用两层,最多不要超过三成

2)只有当内层循环为false时跳出内层循环,开始下一次外层循环

3)外层循环m次,内层循环n次,总共循环次数m*n

 4.6.1应用案例

1)统计3个班成绩情况,每个班有5名同学,求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]

代码:

var gread float32
      var B_avg float32
      var S_avg float32
    for i:=1;i<=3;i++{
        fmt.Printf("请输入第%d班学生成绩信息:\n",i)
        for j:=1;j<=5;j++{
            fmt.Printf("第%d个学生成绩信息:",i)
            fmt.Scanln(&gread)
            B_avg+=gread*float32(1.0)/5
        }
        fmt.Printf("第%d班学生成绩平均分%f:\n",i,B_avg)
        S_avg+=B_avg*1.0/3
        B_avg=0
    }
    fmt.Printf("所有班级的平均分为%f",S_avg)

2)统计每个班的及格人数,每个班有5名同学

func main(){
      var gread float32
      var cnt byte =0 
    for i:=1;i<=3;i++{
        cnt=0
        fmt.Printf("请输入第%d班学生成绩信息:\n",i)
        for j:=1;j<=5;j++{
            fmt.Printf("第%d个学生成绩信息:",i)
            fmt.Scanln(&gread)
            if gread>=60{
                cnt++
            }
        }
        fmt.Printf("第%d班学生及格人数%d:\n",i,cnt) 
    }
}

3)打印金字塔经典案例

func main(){
     var tol int =20
     for i:=1;i<=tol;i++{
        for j:=1;j<=tol-i;j++{
            fmt.Printf(" ")
        }
        for k:=1;k<=2*i-1;k++{
            fmt.Printf("*")
        }
        fmt.Printf("\n")
     }
}

4)打印九九乘法表

func main(){
     for i:=1;i<=9;i++{
        for j:=1;j<=i;j++{
            fmt.Printf("%v * %v = %v\t",j,i,i*j)
        }
        fmt.Printf("\n")
     }
}

4.7跳转控制语句-break

4.7.1基本介绍

break语句用于终止某个语句块的执行,用于中断当前for循环或者跳出switch循环

语法格式:

{

  ……

  break

  ……

}

注意事项:

break语句出现在嵌套语句中时,可以通过标签指明要终止的时哪一层的语句块。默认跳出最近一层循环。

例如:

func main(){
    lable:
     for i:=0;i<10;i++{
        for j:=0;j<5;j++{
            for k:=0;k<4;k++{
                if k==3{
                    break lable
                }
                fmt.Printf("%v,%v,%v\n",i,j,k)
            }
        }
     }
}

 

4.7.2入门例题

1)随机生成1—100的一个数,直到生成了99这个数,看看一共用了几次?

package main
import (
    "fmt"
    "math/rand"
    "time"
)
func main(){
    var cnt int = 0
    for {
        rand.Seed(time.Now().UnixNano())
        n:=rand.Intn(100)+1
        fmt.Printf("n=%v",n)
        cnt++
        if(n==99){
            break
        }
    }
    fmt.Printf("生成99一共使用了%v",cnt)     
}

2)100以内的连续整数求和,输出当第一次和大于20的当前整数

func main(){
     sum:=0
     i:=1
     for ;;i++{
        sum+=i
        if sum>20{
            break
        }
     }
     fmt.Printf("%v",i)
}

 

3)实现登录验证,有三次机会,如果用户名为“张三”,密码:999,提示登陆成功,否者提示还剩多少次机会

func main(){
     var strName string
     var strCode string
     for i:=3;i>0;i--{
        fmt.Printf("请输入用户名:")
        // fmt.Scanln(&strName)
        // fmt.Printf("请输入密码:")
        // fmt.Scanln(&strCode)
        fmt.Scanf("%s %s",&strName,&strCode)
        if strName=="张三" && strCode=="999"{
            fmt.Printf("登录成功!\n")
            break
        }
        fmt.Printf("输入密码或用户名错误\n还剩%v次登录机会!\n",i-1)
     }
}

第二次循环有点小问题,下次来改吧,迭代升级水平

4.8跳转控制语句—continue

4.8.1基本介绍

continue语句用于结束本次循环,继续执行下一次循环

continue语句出现在多层嵌套 的循环语句体时,可以通过标签指明要跳转的是那一层循环。与break同

基本语法:

{

  ……

  continue

  ……

}

4.8.2例题

1)打印1-100的奇数[使用for—continue]

func main(){
    var cnt int =0
     for i:=1;i<=100;i++{
        if i%2==0{
            continue
        }
        fmt.Printf("%v\t",i)
        cnt++
        if cnt%10==0{
            fmt.Printf("\n")
        }
     }
}

2)从键盘读入个数不确定的整数,并判断读入的正数和负数的个数,输入为0时结束程序

func main(){
    var cnt_Z int =0
    var cnt_F int =0 
    var n int
    for{
        fmt.Printf("请输入数:")
        fmt.Scanln(&n)
        if n>0{
            cnt_Z++
        }else if n<0{
            cnt_F++
        }else {
            break
        }
    }
    fmt.Printf("正数个数%v,负数个数%v\n",cnt_Z,cnt_F)
}

3)有人100000元,每经过一个路口需要缴费,规则如下:

  当现金>50000时,每次缴费5%

  当现金小于50000时,每次缴费1000

变成计算该人可以经过多少个路口,使用for-break方式

func main(){
     var sum float32 =100000
     var cnt int =0
     for{
        if sum>50000{
            sum=sum-sum*0.05
        }else if sum <5000&&sum>1000{
            sum=sum-1000
        }else{
            break
        }
        cnt++
     }
     fmt.Printf("一共能通过%v次路口",cnt)
}

4.9 跳转控制语句-goto 

4.9.1基本介绍

1)Go语句的goto语句可以无条件的跳转到程序中指定的行

2)goto语句通常与条件语句配合使用。可以用来实现条件转移,跳出循环体等功能

3)在Go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难

基本语法:

go label

……

label:语句

4.10跳转控制语句-return

4.10.1基本介绍

return 使用在方法或者函数中,表示跳出所在方法或者函数。

说明:

1)如果return是在普通的函数,则表示跳出该函数,不在执行后面的代码

2)如果return 在main函数,终止程序

5.函数

5.1函数基本概念

为完成某一功能的程序指令(语句)的集合,称为函数

在Go中,函数分为自定义函数、系统函数

5.2函数的基本语法

func 函数名(形参列表)(返回值列表){

  执行语句……

  return 返回值列表

}

可以没有形参列表和返回值列表

5.3包

5.3.1概念

包的本质实际上就是创建不同的文件夹来存放文件。go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的

一个包含文件的最小文件夹就是一个包

5.3.2包的三大作用

1)区分相同名字的函数、变量等标识符(同一个包中变量名和函数名不可以相同)

2)当程序文件很多时可以很好的管理项目

3)控制函数、变量等访问范围,即作用域

5.3.3包的相关说明

打包基本语法:package 包名

引入包的基本语法:import"包的路径“

5.3.4包使用的注意事项和细节讨论

1)在给一个文件打包时,该包对应一个文件夹,比如这里的utils文件夹对应的包名就是utils.文件的包名通常和文件所见的文件夹名一致,一般为小写字母

2)引入包的方式:

(1)import"包名”

(2)import(

    "包名“

    ”包名“

  )  

先打包package ,再import指令

在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入

3)为了让其它包的文件可以访问到本包的函数,这函数名的首字母、变量名需要大写,类似于其他语言的public

4)访问包名.函数名

5)取别名后原来的包名就不能使用了,用别名访问该包的函数和变量

package main
import(
    "fmt"
    utlis"model/utlis"
)

将"model/utlis"重命名为utlis

6)如果要编译成一个可执行文件,就需要将这个包名声明为main,即 package main(语法规范)。如果写的是一个函数库,包名可以自定义。

  • 生成可执行文件:编译的指令在GOPATH的项目目录下,编译路径不需要带src,从src下级目录下面开始带,编译器会自动带。

    go build model/test

  • 如果要指定到指定的路径下面且重命名:

    go build -o bin/my.exe model/test

  • 注意:编译时需要编译文件的文件夹。
  • 项目目录结构如下

 

5.4返回return 语句

形如:func 函数名(形参列表)(返回值列表){

    语句。。。。

    return 返回值列表

  }

注意:如果需要忽略某个返回值,使用_符号表示占位忽略

5.5递归

5.5.1例题

题1:

 

package main
import(
    "fmt" 
)
func Fbl(n int64) int64 {
     if n==1||n==2{
        return 1
     }else{
        return Fbl(n-1)+Fbl(n-2)
     }
}

func main(){
    var n int64
    fmt.Printf("请输入n:n=")
    fmt.Scan(&n)
    var result int64 =Fbl(n)
    fmt.Printf("第n位对应的斐波那契数为%d",result)

}

 

题2:

 

package main
import(
    "fmt" 
)
func Cal(n int64) int64 {
     if n==1{
        return 3
     }else{
        return  2*Cal(n-1)+1
     }
}

func main(){
    var n int64
    fmt.Printf("请输入n:n=")
    fmt.Scan(&n)
    var result int64 =Cal(n)
    fmt.Printf("第n时对应的函数值为%d",result)

}

 

 题3:

package main
import(
    "fmt" 
)

func peach(n int32)int32 {
    if n==10{
        return 1
    }else{
        return 2*(peach(n+1)+1)
    }
}

func main(){
    fmt.Printf("第一天桃子总数为:%v",peach(1))
}

5.6函数使用的注意事项

  1. 函数的形参列表和返回值列表有多个
  2. 形参列表和返回值列表可以是值类型和引用类型
  3. 函数首字母大写才能被其他包调用首字母小写只能本包使用
  4. 基本数据类型和数组默认值传递,如果要修改函数外变量的值,可以传入变量的地址,函数内以指针的方式操纵变量
  5. Go函数不支持函数重载
  6. 在go中函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量,通过该变量可以对函数调用。函数尽然是一种数据类型,则可以作为形参并且调用
    func getSum(n1 int,n2 int) int {
        return n1+n2
    }
    
    func myFun(funvar func(int,int) int,num1 int,num2 int) int {
        return funvar(num1,num2)
    }
    
    func main(){
         a:=getSum
         fmt.Printf("a的类型%T,getSum的类型%T\n",a,getSum)//函数可以赋值给变量
         res:=a(10,40)//变量的使用方式和函数同
         fmt.Println("res=",res)
         fmt.Printf("myFun(a,10,20)=%v",myFun(a,10,20))//函数作为参数调用
    
    }
  7. go支持给数据类型去别名

      基本语法: type 数据类型别名 数据类型

      type myInt int //这时myInt就等价int来使用了

  8.支持对函数返回值命名

func getSumAndSub(n1 int,n2 int) (sum int,sub int){
    sub=n1 - n2
    sum=n1 + n2
    return 
}

//main函数内
     a1,a2:=getSumAndSub(1,3)
     fmt.Printf("\n a1=%v,a2=%v",a1,a2)    

 

  9.Go支持可变参数

  • 支持0到多个参数

    func sum (args...int) sum int{

    }

  • 支持1到多个参数    

    func sum (n1 int,args...int) sum int{

    }

  • 说明:1)args是slice切片,通过args[index]可以访问到各个值

      2)如果一个函数的形参列表中有可变参数,则可变参数要放在形参列表最后

  • 例题:编写一个函数sum ,可以求出1到多个int的和
func sum(n1 int,args... int ) int {
    sum:=n1
    for i:=0;i<len(args);i++{
        sum+=args[i]
    }
    return sum
}
func main(){
    res:=sum(1,2,3,4,5,6,7,8,9)
    fmt.Println("res=",res)
}

5.7init函数 

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

5.7.1inti函数注意事项和细节

1)如果一个源文件同时包含全局变量定义,init函数,main函数。则执行的流程:全局变量定义—>init函数—>main函数

 2)init函数最主要的作用,就是完成一些初始化的工作

3)细节说明:如果main.go和utils.go都含有变量定义,init函数时执行流程:

 

5.8匿名函数

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

5.8.1匿名函数使用方式

1)方式1:定义匿名函数时就直接调用,这种方式匿名函数只能调用一次

func main(){
     res1:=func(n1 int,n2 int ) int {
        return n1+n2
     }(10,20)
     fmt.Println("res1=",res1)
}

2)将匿名函数赋值给一个变量,通过变量来调用

func main(){
    a:=func(n1 int,n2 int ) int {
        return n1+n2
     } 
     res1:=a(12,11)
     fmt.Println("res1=",res1)
}

5.8.2全局匿名函数

如果将匿名函数赋值给一个全局变量,那么这个匿名函数就成为一个全局匿名函数。

var(
    Fun1=func(n1 int,n2 int ) int {
        return n1+n2
    }  
)
func main(){
     
     res1:=Fun1(12,11)
     fmt.Println("res1=",res1)
}

 

5.9闭包

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

5.9.1案例演示

//累加器,函数定义
 func AddUpper() func(int) int {
    var n int =10
    return func (x int) int{
        n=n+x
        return n
    }
 }

func main(){
     f:=AddUpper()
     fmt.Println(f(1))//11
     fmt.Println(f(2))//13
     fmt.Println(f(3))//16
}

 对上面代码的说明:

1)AddUpper是一个函数,返回的数据类型是 fun (int) int

2)闭包的说明:

AddUpper()函数返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成比闭包

3)可以这样理解:闭包是类,函数是操作,n是字段。函数和使用的变量n构成闭包

4)当我们反复调用函数f时,因为n是初始化一次,因此每调用一次就进行累计

5.9.2例题

编写一个函数makeSuffix(suffix string)可以接收一个文件后缀名(比如.jpg),并返回一个闭包。调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀则返回文件名.jpg,如果已经有.jpg则返回原文件

strings.HasSuffix,这个函数可以判断某个字符串是否有指定的后缀

package main
import(
    "fmt" 
    "strings"
)

func makeSuffix(suffix string) func (string ) string {
    return func(name string) string{
        if !strings.HasSuffix(name,suffix){
            return name+suffix
        }else{
            return name
        }
    }
}
  

func main(){
     f2:=makeSuffix(".jpg")
     fmt.Println("文件名处理后=",f2("winter"))
     fmt.Println("文件名处理后=",f2("ttt.jpg"))
}

5.10defer函数

在函数中,程序员经常需要创建资源(比如:数据库链接,文件句柄,锁等)为了在函数执行完成后及时释放资源,GO设计者提供defer(延时机制)

5.10.1案例

代码如下:

func sum(n1 int,n2 int) int {
    defer fmt.Println("ok1 n1=",n1)
    defer fmt.Println("ok1 n2=",n2)
    res:=n1+n2
    fmt.Println("ok3 res=",res)
    return res
}
 
func main(){
     res:=sum(10,20)
     fmt.Println("res=",res)
}

 

输出结果:

 

 

说明:当执行到defer 时,暂时不执行,会将defer后面的语句压入到独立的栈中(defer栈)

当此函数执行完成时,再从defer栈中,按照先入后出的方式出栈

5.10.2defer的注意事项和细节

1)当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中,然后继续执行函数下一个语句

2)当函数执行完毕后,在从defer栈中,依次从栈顶取出执行语句(栈:先入后出机制)

3)defer将语句放入栈中时,也会将相关的值拷贝同时入栈。

5.10.3defer的最佳实践

defer最主要的价值在,当函数执行完毕后,可以及时释放函数创建的资源。比如:defer file.Close();defer connect.Close()

func test(){

  //关闭文件资源

  file=openfile(文件名)

  defer file.close()

  //其他代码

}

5.11函数参数传递方式

两种传递方式:

1)值类型:基本数据类型 int系列,float系列,bool,string,数组和结构体struct    内存通常在栈中分配

2)引用类型:指针、slice切片,map,chan管道,interface等   内存通常在堆上分配

不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是值传递是值拷贝,引用传递是地址拷贝。地址拷贝效率高,数据量小;值拷贝决定数据的大小,数据越大,效率越低。

5.12变量的作用域

5.13字符串常用的系统函数

更多内容见官方文档

1)统计字符串的长度,按字节 len(str)

英文一个字母占一个字节;中文一个汉字占3个字节

2)字符串遍历 ,处理有中文的问题: r:=[]rune(str)

 

str2:="aaa啊啊啊"
    r:=[]rune(str2)
    for i:=0;i<len(r);i++{
        fmt.Printf("r[%d]=%c ",i,r[i])
    }  

输出结果:r[0]=a r[1]=a r[2]=a r[3]=啊 r[4]=啊 r[5]=啊

3)字符串转整数 n,err:=strconv.Atoi("12")

n1,err1:=strconv.Atoi("hello")
    if err1!=nil{
        fmt.Println("\n转换错误:",err1)
    }else {
        fmt.Println("\n转换后的结果是:",n1)
    }


    n2,err2:=strconv.Atoi("12")
    if err2!=nil{
        fmt.Println("\n转换错误:",err2)
    }else {
        fmt.Println("\n转换后的结果是:",n2)
    }

输出结果:

转换错误: strconv.Atoi: parsing "hello": invalid syntax

转换后的结果是: 12

 

注意:字符串转整数的前提是字符串为数字组成的字符串,否者转换失败

4)整数转字符串 str=strconv.Itoa(12345)

 同上

 5)字符串转ASCII码 []byte: var bytes=[]byte(str)

 
        var bytes=[]byte("hello")
    fmt.Println("bytes=",bytes)    

输出结果:bytes= [104 101 108 108 111]

 6)[]byte 转字符串str=string([]byte{97,98,99})

7)十进制转2,8,16进制:str = strconv.FormatInt(123,2)//把123转为二进制。

8)查找子串是否在指定的字符串中:strings.Contains("seafood","food")//查找seafood中是否有子串food

9)统计一个字符串中有几个指定的子串:strings.Count("ceheese","e")//4

10)不区分字母大小写的比较(==是区分字母大小写的比较):strings.EqualFold("abc","ABC")

11)返回字符串第一次/最后一次出现的index值,如果没有返回-1:

strings.Index("NLT_abc","abc")//4

strings.LastIndex("gogolang",go)//3

12)将字符串左右两边的空格去掉:strings.TrimSpace(" hello   ")

13)将字串的字母进行大小写的转换:stings.ToLower("Go")/strings.ToUpper("Go")

14)把字符串左右两边/左边/右边指定的字符串去掉

fmt.Printf("%q\n",strings.Trim("! hello ! ","! "))//将字符串左右两边的空格" "和 "!" 去掉,输出结果:"hello"
fmt.Printf("%q\n",strings.TrimLeft("! hello ! ","! "))//将字符串左两边的空格" "和 "!" 去掉,输出结果:"hello !"
fmt.Printf("%q\n",strings.TrimRight("! hello ! ","! "))//将字符串右两边的空格" "和 "!" 去掉,输出结果:"! hello"

15)判断字符串是否以指定字符串结束/开头:

strings.HasSuffix("NIL_abc.jpg",".jpg")

strings.HasPrefix(("ftp://129.123.11","ftp")

16)将字符串按照指定的标识符分割成一个字符串数组:strings.Split("hello,world!",",")//返回一个字符串数组

17)将指定字符串的子串替换成另一个子串:strings.Replace("go go lange","go","hhhh",n)//n可以指定替换好多个,如果n=-1代表全部替换

5.14时间和日期相关函数

5.14.1基本介绍

1)导入time包

2)time.Time类型,用来表示时间。也就是一个时间,它的类型为time.Time

3)获取时间信息

time.Now()获取当前时间

now.Year(),now.Month()-英文单词,int(now.Month())-数字类型的月份,now.Day()......

4)格式化日期时间

  • 使用Printf或者Sprintf

    %d-%d-%d %d:%d:%d  年月日 时分秒 格式话输出即可

  • time.Format()方式

    fmt.Printf(now.Format("2006-01-02 15:04:05")

    fmt.Printf(now.Format("15:04:05")

    fmt.Printf(now.Format("2006-01-02")

    还可以这样:2006/01/02 15:04:05,时间是固定的不能变,格式随便变,各个数字可以自由组合

5)时间的常量

 

作用:在程序中可以用于获取指定时间单位的时间,比如想得到100毫秒的单位

100*time.Microsecond

6)time.Sleep()函数

例题:每隔1秒打印一个数字,打印到100时退出;每隔0.1秒打印一个数字,打印到100时退出

func main(){
     i:=0
     for{
        i++
        fmt.Println(i)
       // time.Sleep(time.Second)
        time.Sleep(time.Millisecond*100)
        if i==100{
            break
        }
     }
}

7)time的Unix和UnixNano的方法

5.14.2例题

实现统计函数test()的执行时间

package main
import(
    "fmt" 
    "time"
    "strconv"
)

func test(){
    str:=" "
    for i:=0;i<1000;i++{
        str +=strconv.Itoa(i)
    }
}

func main(){
     start:=time.Now().UnixNano()
     test()
     end:=time.Now().UnixNano()
     fmt.Printf("test()函数执行花费的时间为%v纳秒\n",end-start)
}

 

5.15内置函数

内置函数在使用文档builtin模块中

常见内置函数:

1)len():用来求长度,比如string,array,slice,map,channel

2)new()用来分别内存,主要用来分配值类型,比如int,float,struct...,返回的是指针

3)make():用来分配内存,主要用来分配引用类型,比如channel,map,slice

5.16错误处理

在默认情况下,当发生错误后(panic),程序就会退出(崩溃)。如果希望发生错误后可以捕获到错误,并进行处理,保证程序可以继续执行,还可以在捕获错误后,给管理员提示(邮件、短信。。。)

5.16.1基本说明

1)Go语言简洁优雅,所以Go语言不支持传统的try...catch...finally这种处理

2)Go中引入的错误处理方式:defer,panic,recover

3)go中可以泡壶一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

5.16.2使用defer+recover处理错误

package main
import(
    "fmt" 
)

func test(){
    defer func(){
        err:=recover()
        if err!=nil{
            fmt.Println("err=",err)
        }  
    }()
    num1:=10
    num2:=0
    res:=num1/num2
    fmt.Println("res=",res)

}
 
func main(){
     test()
     fmt.Println("main....")
     
}

输出结果:

err= runtime error: integer divide by zero
main....

错误处理的好处:进行错误处理后,程序不会轻易的挂掉,如果加入了预警代码,就可以让程序更加健壮

5.16.3自定义错误

使用errors.New和panic内置函数来自定义错误

1)errors.New("错误说明”),会返回一个error类型的值,表示一个错误

2)painc内置函数接收一个interface{}类型的值(任何类型的值)作为参数。可以接收error类型的变量,输出错误信息,并退出程序。

package main
import(
    "fmt" 
    "errors"
)

func readConf(name string)(err error){
    if name=="config.ini"{
        //......
        return nil
    }else{
        //返回一个自定义错误
        return errors.New("读取文件错误...")
    }
}

func test(){
    err:=readConf("config.i")
    if err!=nil{
        //如果读取错误,则输出这个错误,并终止程序
        panic(err)
    } 
    fmt.Println("test()继续执行")
}

func main(){
    test()
    fmt.Println("main()继续执行")
}

 6.数组和切片

6.1数组

6.1.1数组初始化

  • var numArr [3] int =[3] int{1,2,3}
  • var numArr  =[3] int{1,2,3}
  • var numArr  =[...] int{1,2,3}
  • var numArr  =[...] int{1:400,0:555,2:890}
  • numArr:=[...]string{1:"tome",0:"tata"}

6.1.2数组的遍历

1)方式1:常规方法遍历

2)方式2:for—range形式

格式:for index,value:=range array{

    .....

    }

例题:

package main
import(
    "fmt" 
     
)

func main(){
     heroes:=[...]string{"宋江","武松","吴用"}
     for i,v:=range heroes{
        fmt.Printf("i=%v,v=%v\n",i,v)
     }
}

在这里取值还可以heroes[i]的方式

6.1.3数组使用的注意事项和细节

1)数组一旦声明定义了,其长度是固定,不能动态变化

2)数组创建后如果没有赋值,默认值类型

3)数组中的元素可以是任何类型,值类型和引用类型,但不能混用

4)var arr[] int这时arr是一个切片slice

5)Go中数组属于值类型,在默认情况下是值传递

6.2切片

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

基本语法:

    var 切片名 []类型

    例如:var a []int

6.2.1快速入门案例

func main(){
     var intArr [7]int =[...]int {1,2,3,4,5,7,6}
     slice:=intArr[1:3]
     fmt.Println("intArr=",intArr)
     fmt.Println("slice=",slice)
     fmt.Println("slice的元素个数=",len(slice))
     fmt.Println("slice的容量=",cap(slice))

}

输出结果:

intArr= [1 2 3 4 5 7 6]
slice= [2 3]
slice的元素个数= 2
slice的容量= 6

 slice的容量取决于intArr数组最大的下标

6.2.2切片在内存中的形式

 

 slice是一个引用类型,从底层结构来说,就是一个数据结构体:

type slice struct{

  ptr*[2]int

  len int  

  cap int

}

6.2.3切片的使用

  • 方式1:定义一个切片,然后让切片去引用一个创建好的数组,如上例
  • 方式2:通过make来创建切片

  基本语法:var 切片名 []type=make([]type,len,cap),cap>=len

 

   说明:

    1)如果没有给切片个元素赋值,则元素为默认值

    2)通过make方式创建的切片对应的数组是用make底层维护,对外不可见,只能通过slice去访问各个元素

  • 方式3:定义一个切片就指定具体数组,使用原理类似make的方式

    var str []string=[]string{"tom","Jale","mary"}

方式1和方式2 的区别:

 

 6.2.4切片的遍历

和数组一样,有两种方式

  • for循环常规遍历
  • for-range结构遍历

 

 

6.2.5切片的使用注意事项和细节讨论

1)切片初始化时 var slice =arr[startIndex:endIndex]

取数组arr[startIndex,endIndex)范围的内容

2)切片初始化时仍然不能越界。范围在[0,len(arr)]之间,但是可以动态增长

var slice=arr[0:end]==var slice=arr[:end]

var slice=arr[start:len(arr)]==var slice=arr[start:]

var slice=arr[0:en(arr)]==var slice=arr[:]

3)cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个数组

4)切片定义完后需要让其引用一个数组,或者make一个空间供切片使用

5)切片可以继续切片

slice2:=slice1[1:4]

6)用append内置函数可以对切片进行动态追加

 

对上面代码的说明:

 

切片 append操作的本质就是对数组扩容

go底层会创建一个新的数组,这个数组大小有扩容后的大小,将slice原来包含的数组元素拷贝到新数组 ,append操作后的结果就是一新数组。可将append操作后的结果返回给slice,即slice重新引用新数组

7)切片的拷贝操作,使用内置函数copy()

 

说明:

copy(para1,para2)参数的数据类型是切片

slice4和slice5的数据空间是独立的,互不影响,也就是说slice4[1]=999,slice5[1]仍为原值

 

8)关于切片拷贝,详见下例:

 

输出结果[1],代码没有错

9)切片是引用类型,所以在传递时时,遵守引用传递机制。

 

 

 

6.2.6string和slice

1)string底层是一个byte数组,因此string也可进行切片处理,和int类型的数组用法相同

2)string和切片在内存的形式,以“abdc"画出内存示意图

 

3)string是不可变的,也就是说不能通过str[0]='z'的方式来修改字符串

4)如果要修改字符串,可以先将string->[]byte/[]rune->修改->转换成string

func main(){
    str:="hello,world!"
    arr1:=[]byte(str)
    arr1[0]='z'
    str=string(arr1)
    fmt.Println("str=",str)
    //处理含有中文字符的[]rune
    arr2:=[]rune(str)
    arr2[0]=''
    str=string(arr2)
    fmt.Println("str=",str)
}

 

 

6.2.7切片的课堂练习

func fbn(n int)([]uint64){
    fbnSlice :=make([]uint64,n)
    fbnSlice[0]=1
    if n==2{
        fbnSlice[1]=1
    }else if n>3 {
        fbnSlice[1]=1
        for i:=2;i<n;i++{
            fbnSlice[i]=fbnSlice[i-1]+fbnSlice[i-2]
        }
    }
    return fbnSlice

}

func main(){
     fbnSlice:=fbn(0)
     fmt.Println("fbnSlice=",fbnSlice)
}

 7.排序和查找、二维数组

7.1排序

7.1.1排序的基本内容

分类:

1)内部排序

指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法、插入式排序法)

2)外部排序法

数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)

7.1.2冒泡排序

package main
import(
    "fmt"  
)

func BubbleSort(arr *[5]int){
     var temp int 
     for i:=0;i<len(arr)-1;i++{
        for j:=0;j<len(arr)-1-i;j++{
            if arr[j]>arr[j+1]{
                temp=arr[j]
                arr[j]=arr[j+1]
                arr[j+1]=temp
            }
        }
     }
}

func main(){
     arr:=[5]int{20,10,30,40,23}
     fmt.Println("排序前 arr=",arr)
     BubbleSort(&arr)
     fmt.Println("排序后 arr=",arr)

}

7.2查找

常见两种查找

1)顺序查找

 

 

2)二分查找(该数组是有序

 

 

package main
import(
    "fmt"  
)

func  BinaryFind(arr *[6]int,findVal int){
    var left,right int 
    left=0
    right=len(arr)-1
    middle:=(left+right)/2
    for i:=0;left<right&&i<len(arr);i++{
        if findVal<arr[left]||findVal>arr[right]{
            fmt.Println("找不到此数")
            return
        }
        if  findVal<arr[middle]&&findVal>arr[left]{
            right=middle-1
            left=left+1
        } else  if  findVal<arr[right]&&findVal>arr[middle]{
            left=middle+1
            right=right-1
        }  else {
            fmt.Println("找到了")
            return 
        }
        middle=(right+left)/2
    }
    if left>right{
        fmt.Println("找不到此数")
    }
}

func main(){
     arr :=[6]int {1,2,3,6,8,23}
     BinaryFind(&arr,9)

}

7.3二维数组

7.3.1二维数组的声明和定义

语法: var 数组名 [大小][大小]类型

使用方法:

  • 先声明/定义,在赋值
  • 直接初始化

    var 数组名 [大小][大小]类型 = [大小][大小]类型 {{初值},{初值}...}

    var 数组名 [大小][大小]类型 = [...][大小]类型 {{初值},{初值}...}

    var 数组名   = [大小][大小]类型 {{初值},{初值}...}

    var 数组名   = [...][大小]类型 {{初值},{初值}...}

7.3.2二维数组的遍历

方式1:双层for循环

方式2:for-range 方式完成遍历

func main(){
     var arr=[2][3] int{{1,2,3},{4,5,6}}
     for i,v:=range arr{
        for j,v2 :=range v {
            fmt.Printf("arr[%d][%d]=%d\t",i,j,v2)
        }
     }
}

7.3.3二维数组的应用案例

 

 8.map

map是key-value数据结构,又称为字段或者关联数组。类似其他编程语言的集合

8.1map的声明

基本语法:

  var map 变量名 map[keytype] valuetype

  • keytype:bool,数字,string,指针,channel,接口,结构体,数组;注意slice、map,function 不可以,因为没法用==来判断
  • valuetype 和上面一样

声明实例:

  var a map[string]string

  var a map [stribg]map[string]string

声明不会分配内存,初始化需要make。a=make(map[string]string,10)

说明:

1)map的key不能重复,如果重复,则以最后这个key-value为准

2)map的key-value有序

3)make的内置函数数目

 

 4)make后数据空间可自动增长

5)map求长度len()

8.2map的使用

方式1:

 

方式2:

 

 

方式3:

 

 

 

例题:存放3个学生信息,每个学生信息包括name,sex

 

8.3map的增删查改

1)map["key"]=value   如果"key"不存在就是增加,提前存在就是修改

2)delete(map,key),delete是一个内置函数,如果key存在,就删除key-value,不存在就不操作,也不会报错

如果我们要删除map的所有key:遍历着个删除、map=make(...),make一个新的,让原来的变为垃圾,被gc回收

3)查找案例

func main(){
     cities := map[string]string{
        "no1":"tom",
        "no2":"jack",
        "no3":"marry",
     }
     fmt.Println(cities)

     val,findvar:=cities["no1"]
     if findvar{
        fmt.Printf("找no1,val=%v",val)
     } else {
        fmt.Println("没有no1 key\n")
     }
}

if cities这个map中存在”no1",则findval返回true,val=cities["no1"],否则返回false

8.4map的遍历

 案例:采用for -range 的结构体。

package main
import(
    "fmt"  
)
 
func main(){
     cities := map[string]string{
        "no1":"tom",
        "no2":"jack",
        "no3":"marry",
     }
     for k,v:=range cities {
        fmt.Printf("k=%v,v=%v\n",k,v)
     }

     studentMap:=make(map[string]map[string]string)
     studentMap["stu01"]=make(map[string]string)
     studentMap["stu01"]["name"]="tom"
     studentMap["stu01"]["sex"]=""
     studentMap["stu01"]["address"]="北京"

     studentMap["stu02"]=make(map[string]string)
     studentMap["stu02"]["name"]="marry"
     studentMap["stu02"]["sex"]=""
     studentMap["stu02"]["address"]="成都"
     for k1,v1 :=range studentMap{
        fmt.Printf("%v:\n",k1)
        for k2,v2:=range v1{
            fmt.Printf("%v:%v\t",k2,v2)
        }
        fmt.Println()
     }   
}

输出结果:

k=no1,v=tom
k=no2,v=jack
k=no3,v=marry
stu01:
sex:男 address:北京 name:tom
stu02:
name:marry sex:女 address:成都

8.5map切片

这样可以使map的个数可以动态变化(仔细体会)

案例演示:

package main
import(
    "fmt"  
)

func main(){
     var monsters []map[string]string
     monsters=make([]map[string]string,2)
    
     monsters[0]=make(map[string]string)
     monsters[0]["name"]="牛魔王"
     monsters[0]["age"]="4000"

     monsters[1]=make(map[string]string)
     monsters[1]["name"]="白骨精"
     monsters[1]["age"]="4000"

     newMonster:=map[string]string{
        "name":"铁扇公主",
         "age":"4500",
     }
     monsters=append(monsters,newMonster)

     fmt.Println(monsters)
}

8.5map使用细节

1)map是引用类型,遵守引用类型传递机制,在一个接收map修改后会直接修改原来的map

2)map的容量达到后,在想map增加元素,会自动扩容。

3)map的value也经常使用struct类型,更适合管理复杂的数据

package main
import(
    "fmt"  
)

type Stu struct{
    Name string
    Age byte
    Sex string
}
 
func main(){
     students:=make(map[string]Stu)
    stu1:=Stu{"marry",18,""}
    stu2:=Stu{"jake",18,""}
    students["stu1"]=stu1
    students["stu2"]=stu2

    for k,v:=range students{
        fmt.Printf("%v:%v,%v,%v\n",k,v.Name,v.Age,v.Sex)
    }
}

 

8.6map的课堂练习

 

 

9.面对对象编程上

9.1结构体

9.1.1引入

案例:

 

使用现有技术解决此问题缺点:

1)数组或者变量解决问题:不利于数据的管理和维护。

2)如果我们希望对一只猫的属性(名字,颜色,年龄)进行操作(绑定方法),也不好处理

 

结构体解决此问题,一个程序就是一个世界,有很多对象(变量)

 

9.1.2Golang语言面对对象编程说明

1)Golang也支持面对对象编程(OOP)但是和传统的面对对象编程有区别,并不是纯粹的面对对象语言。

2)Golang没有类(class),Golang支持的结构体和其他语言支持的类有同等地位,可以理解为go时基于结构体实现面对对象的

3)Golang面对对象编程非常简洁,去掉和传统OOP语言的继承,方法重载,构造函数和析构函数,隐藏的this指针等等。

4)Golang仍然有面对对象的继承,封装和多态的特性,只是实现方式和其他语言不一样。比如:继承没有extends关键字,继承是通过匿名字段来实现的。

5)Golang面对对象很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活,。也就是go中面对接编程是非常重要的特性。

 

9.1.3快速入门-解决养猫难题

 1 package main
 2 import(
 3     "fmt"  
 4 )
 5 
 6 type Cat struct{
 7     Name string
 8     Age int
 9     Color string 
10     Hobby string 
11 }
12 func main(){
13     var cat1 Cat
14     cat1.Name="小白"
15     cat1.Age=2
16     cat1.Color="白色"
17     cat1.Hobby="吃鱼"
18 
19     fmt.Println("cat1=",cat1)
20      
21 }

输出结果:cat1= {小白 2 白色 吃鱼}

 

9.1.4结构体变量在内存中的布局(上例)

 

 

9.1.5声明结构体

基本语法:

 type 结构体名称 struct{

  field1 type

  field2 type

}

9.1.6结构体/属性

  • 基本介绍

    1)从概念或者叫法上面看: 结构体字段=属性=field 

    2)字段是结构体的一个组成部分,一般是基本数据类型、数组、也可以是引用类型。

 

  • 注意事项和细节说明

    1)字段声明同变量声明

    2)创建一个结构体变量时,如果没有给一个变量赋值,都对应一个默认值,指针、map、slice的值为nil,即还没分配空间

    3)不同结构体变量的字段是独立的,互不影响,一个结构体变量指端的更改不影响另一个。结构体变量是值类型。

9.1.7创建结构体变量和访问结构体字段

 

方法1-直接声明:var person Person

方法2-var person Person=Person{ }

方法3- var person *Person =new(Person)

方法4- var person *Person = &Person{ }

 

说明:

  1)3、4返回的是结构体指针

  2)结构体指针访问字段的标准形式:(*结构体指针).字段名。但是go做了简化,也支持 结构体指针.字段名。编译器底层做了转换。不能写成

*结构体指针.字段名 ,因为" .  "运算符优先级比 * 高

 

9.1.8 结构体使用注意事项

1)结构体所有字段在内存中是连续的

2)结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数、和类型)

 

 

3)结构体进行type 重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强制转换。

4)struct 的每个字段上可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

 

序列化的使用场景:

 

 

举例:

 1 package main
 2 import(
 3     "fmt"  
 4     "encoding/json"
 5 )
 6 
 7 type Monster struct{
 8     Name string `json:"name"`
 9     Age int `json:"age"`
10     Skill string `json:"skill"`
11 }
12 
13 func main(){
14      monster :=Monster{"牛魔王",500,"芭蕉扇"}
15      //将monster变量序列化为json 字符串
16      //json.Marshal 函数中目的是反射,
17      jsonStr,err:=json.Marshal(monster)
18      if err !=nil{
19         fmt.Println("json 处理错误",err)
20 
21      }else {
22         fmt.Println("jsonStr",string(jsonStr))
23      }
24      
25 }

 

输出结果:jsonStr {"name":"牛魔王","age":500,"skill":"芭蕉扇"}

 

9.2方法

9.2.1基本介绍

Golang的方法是作用在指定的数据类型上,即和指定的数据类型绑定,因此自定义类型都可以有方法,而不仅仅是struct。

9.2.2方法的声明和调用

func (recevir type) methodName(参数列表)(返回值列表){

  方法体

  return 返回值
}

 

1)参数列表:表示方法输入

2)type 表示这个方法和type这个类型绑定

3)receiver:就是type类型的一个变量

其实和函数很相似。

 

9.2.3方法的调用和传参机制原理

 说明:

方法的调用和传递机制和函数基本一样,不一样的地方是方法的调用时,会将调用方法的变量当作一个实参传递给方法。(变量是值类型就进行值拷贝,如果是引用类型就进行地址拷贝)

案例1:

 

 

9.2.4方法的注意事项和细节

1)结构体是值类型

2)方法的访问范围控制规则和函数一样。方法名首字母小写,只能在本包访问,首字母大写可以在其他包访问。

3)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出。

 1 package main
 2 import(
 3     "fmt"       
 4 )
 5 
 6 type Monster struct{
 7     Name string  
 8     Age int  
 9     Skill string  
10 }
11 
12 func(M Monster) String() string {
13     //定义输出格式
14     str:=fmt.Sprintf("Name=%v,Age=%v,Skill=%v",M.Name,M.Age,M.Skill)
15     return str
16 }
17 
18 func main(){
19      monster :=Monster{"牛魔王",500,"芭蕉扇"}
20     
21      fmt.Println(monster)
22      
23 }

 

9.2.5方法的例题

1)编写一个结构体(MethodUtils),编写一个方法,无参数,在方法中打印一个10*8的矩阵

 1 type MethodUtils struct{
 2 
 3 }
 4 
 5 func (method MethodUtils) juzeng() {
 6     for i:=1;i<=10;i++{
 7         for j:=1;j<=8;j++{
 8             fmt.Printf("*")
 9         }
10         fmt.Println()
11     }
12 }
13  func main(){
14      var a MethodUtils
15      a.juzeng()
16  }

 

2)编写一个方法算该矩形的面积(len,width),将其作为方法返回值。并打印出其面积值。

 1 package main
 2 import(
 3     "fmt"      
 4      
 5 )
 6 //编写一个结构体(MethodUtils),编写一个方法,无参数,在方法中打印一个10*8的矩阵
 7 
 8 type Matrix struct{
 9     len float32
10     width float32
11 }
12 
13 func (matrix Matrix) Air(len float32,width float32) float32{
14     return len*width
15 }
16  func main(){
17      var a  Matrix
18      var len float32 =1.2
19      var width float32=2.3
20      fmt.Printf("矩阵的面积为=%v",a.Air(len,width))
21  }

 

3)编写一个方法使给定的一个二维数组(3x3)转置。

 

 

 1 package main
 2 import(
 3     "fmt"       
 4 )
 5 
 6 type Array [3][3]int 
 7 func (array *Array) Transpose(){
 8     for i:=0;i<3;i++{
 9         for j:=0;j<i;j++{
10             var t int
11             t=array[i][j]
12             array[i][j]=array[j][i]
13             array[j][i]=t
14         }
15     }
16 }
17 
18 func(array *Array) String(){
19     for i:=0;i<3;i++{
20         for j:=0;j<3;j++{
21             fmt.Printf("%d\t",array[i][j])
22         }
23         fmt.Println()
24     }
25 }
26  func main(){
27     var a Array=Array{{1,2,3},{4,5,6},{7,8,9}}
28     fmt.Printf("转置前的3x3矩阵\n")
29     a.String()
30     fmt.Printf("转置后的3x3矩阵\n")
31     a.Transpose()
32     a.String()
33  }

 

 

9.2.6方法和函数的区别

1)调用方式不一样

2)对于普通函数,接收者为值类型,不能将指针类型的数据直接传递,反之亦然。

3)对于方法,接收者为值类型时,可以直接用指针类型的变量调用调用方法,反过来同样也可以。

 

 

 

 

总结:不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法和那个类型绑定。如果是和值类型比如(p Person)则是值拷贝,如果......

 

9.3面对对象编程实例

景区门票案例:

 

 1 package main
 2 import(
 3     "fmt"       
 4 )
 5 
 6 type Visitor struct{
 7     Name string 
 8     Age int
 9 }
10 
11 func (visitor Visitor) showPrice(){
12     if visitor.Age>=18{
13         fmt.Println("成人票价格20元!")
14     }else {
15         fmt.Println("未成年票价免费哦!")
16     }
17 }
18  
19  func main(){
20      var v Visitor 
21      for{
22         fmt.Println("请输入您的姓名,退出请输入n")
23         fmt.Scanln(&v.Name)
24         if v.Name=="n"{
25             break
26         }
27         fmt.Println("请输入您的年龄:")
28         fmt.Scanln(&v.Age)
29         v.showPrice()
30      }
31  }

 

9.4创建结构体变量时指定字段值

说明:Golang在创建结构体变量时可以直接指定字段的值

  • 方式一

 

 

  • 方式二

 

 

9.5工厂模式

9.5.1说明

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

 

9.5.2工厂模式案例

一个结构体的声明是这样的:

 

 因为Student首字母大写,如果我们想在其他包创建Student的实例,引入model包后就可以直接创建Student结构体变量。

但是如果是首字母小写,比如type student struct {.....}就不行了,怎么解决----工厂模式

 

代码演示:

 1 package  model
 2 
 3 type student struct{
 4     Name string 
 5     Score float64
 6 }
 7 
 8 //工厂模式解决问题
 9  func NewStudent(n string,s float64) *student{
10     return &student{
11         Name:n,
12         Score:s,
13     }
14  }

main.go调用

 1 package main
 2 import(
 3     "fmt"      
 4     "test01/model" 
 5 )
 6 
 7  func main(){
 8     var stu =model.NewStudent("tom",18)
 9     fmt.Println(*stu)
10 
11     fmt.Println("name=",stu.Name,"score=",stu.Score)
12  }

 

9.5.3思考题

如果将上例中的Score改为score,那么还能正常访问吗?

其实字段名称改为小写后,除了main.go中第11句运行要报错以外,其他都可以运行。如果要是11句能运行。这采用工厂模式决绝问题就可!

10.面对对象编程下

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

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

10.1.1介绍

封装就是把抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的方式才能对字段进行操作。

10.1.2封装的理解和好处

1)隐藏实现细节

2)可以对数据进行验证,保证数据安全合理

 

10.1.3如何实现封装

1)对结构中的属性进行封装

2)通过方法、包实现封装

 

实现步骤:

1)将结构体、字段(属性)的首字母小写(类似Private)

2)给结构体所在的包提供一个工厂模式的函数,首字母大写。(类似构造函数)

3)提供一个首字母大写的Set方法,对属性判断并赋值。

 

 

4)提供一个首字母大写的Get方法,用户获取属性的值。

 

特别说明:Golang开发中没有特别强调封装,这一点不同于Jave。Golang本身对面对对象做了简化。

 

10.1.4案例

 题一:

 

 model包

 1 package  model
 2 
 3 import(
 4     "fmt"
 5 )
 6 
 7 type person struct{
 8     Name string
 9     age  int
10     sal float64
11 }
12 
13 func NewPerson(name string)*person {
14     return &person{
15         Name:name,
16     }
17 }
18 
19 func (p *person)SetAge(age int) {
20     if age>=18&&age<=80{
21         p.age=age
22     }else{
23         fmt.Println("年龄范围不正确。。。")
24     }
25 }
26 
27 func (p *person)GetAge()int {
28     return p.age
29 }
30 
31 func (p *person)Setsal(sal float64){
32     if sal>=3000&&sal<=25000{
33         p.sal=sal
34     } else{
35         fmt.Println("工资范围不正确。。。")
36     }
37 }
38 
39 func (p *person)Getsal()float64{
40     return p.sal
41 }

main.go

 1 package main
 2 import(
 3     "fmt"      
 4     "test01/model" 
 5 )
 6 
 7  func main(){
 8      p:=model.NewPerson("tom")
 9      p.SetAge(18)
10      p.Setsal(6000)
11      fmt.Println(*p)
12  }
13  

 

题二:

 

 model.go

 1 package  model
 2 
 3 import(
 4     "fmt"
 5 )
 6 
 7 type account struct{
 8     accountNo string
 9     pwd string 
10     balance float64
11 }
12  
13 func NewAccount(accountNo string,pwd string,balance float64) *account{
14     if len(accountNo)<6||len(accountNo)>8{
15         fmt.Println("账号长度不对")
16         return nil
17     }
18     if len(pwd)!=6{
19         fmt.Println("密码长度不对")
20         return nil
21     }
22     return &account{
23         accountNo:accountNo,
24         pwd:pwd,
25         balance:balance,
26     }
27 }
28 
29 func (account *account) Deposite(money float64,pwd string){
30     if pwd!=account.pwd{
31         fmt.Println("你输入的密码不正确")
32         return 
33     }
34 
35     if money<=0{
36         fmt.Println("你输入的金额不正确")
37         return 
38     }
39 
40     account.balance+=money
41     fmt.Printf("存入:%v元",money)
42 }
43 
44 func (account *account) WithDraw(money float64,pwd string){
45     if pwd!=account.pwd{
46         fmt.Println("你输入的密码不正确")
47         return 
48     }
49 
50     if money<=0||(money>account.balance){
51         fmt.Println("取款金额不能小于0或者余额不足")
52         return 
53     }
54 
55     fmt.Printf("取出:%v元",money)
56 }
57 
58 func(account *account) Query(pwd string){
59     if pwd !=account.pwd{
60         fmt.Println("你输入的密码不正确")
61         return 
62     }
63 
64     fmt.Printf("你的账户为=%v,余额=%v",account.accountNo,account.balance)
65 }

 

main.go

 1 package main
 2 import(
 3     _"fmt"      
 4     "test01/model" 
 5 )
 6 
 7  func main(){
 8      p:=model.NewAccount("123456","123456",2000)
 9 
10     // p.WithDraw(200,"123456")
11 
12      p.Query("123456")
13  
14  }

 

 

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

10.2.1继承基本介绍

继承可以解决代码复用。当结构体中出现相同的字段和方法,可以从这些结构体中抽象出一个定义这些相同属性的结构体。

其他的结构体不需要重新定义这些属性和方法,只需要嵌套一个匿名结构体就可。

 

10.2.2嵌套匿名结构函数基本语法

 

 

10.2.3案例

 

10.2.4继承的优点

1)代码的复用性提高了

2)代码的扩展性和维护性提高了

10.2.5继承的深入讨论

1)结构体可以使用嵌套匿名结构体的所有字段和方法。

2)匿名结构体字段访问可以简化

 

当我们通过b访问字段或者方法时,其执行流程如下,比如b.Name

编译器先看b对应的类型有没有Name,如果有,则直接调用B类型的Name字段。

如果没有就看嵌套的匿名结构体,如此查找下去,如果没找到就报错。

3)当匿名结构体和结构体具有相同的字段或者方法时,编译器采用就近原则,此时访问匿名结构体的字段或者方法需要写全,通过匿名结构体来区分。嵌套的多个匿名结构体具有相同的字段或者方法时操作相同。

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

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

 

 

10.2.6课堂练习

结构体的匿名字段是基本数据类型,如何访问匿名字段?

 

说明:

1)如果一个结构体有int类型的匿名字段,就不能有第二个

2)如果多个int字段,则必须给int字段指定名字。

 

10.2.7面对对象编程-多重继承

如果一个匿名结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

 

为保证代码的简洁性,尽量不要使用多重继承。

10.3接口(INTERFACE)

10.3.1基本介绍

在Golang中多态的特性主要通过接口来实现的。

interface类型可以定一组方法,但是这些不需要实现,并且interface 不能包含任何变量。到某个自定义类型要使用时再根据具体情况把这些方法写出来。

 

 说明:

1)接口里面的方法都没有方法体,即接口里面的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想。

2)接口不需要显示的实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就可以实现这个接口。因此golang中没有implement这样的关键字。

 

10.3.2快速入门

代码:

 1 package main
 2 
 3 import(
 4     "fmt"
 5 )
 6 
 7 type Usb interface{
 8     Start()
 9     Stop()
10 }
11 
12 type Phone struct{
13 
14 }
15 func (p Phone)Start(){
16     fmt.Println("手机开始工作了")
17 } 
18 func (p Phone)Stop(){
19     fmt.Println("手机停止工作了")
20 }
21 
22 type Camera struct{
23 
24 }
25 func (c Camera)Start(){
26     fmt.Println("相机开始工作了")
27 } 
28 func(c Camera)Stop(){
29     fmt.Println("相机停止工作了")
30 }
31 
32 type Computer struct{
33 
34 }
35 func(com Computer)Working(usb Usb){
36     usb.Start()
37     usb.Stop()
38 }
39 
40 func main(){
41     commputer:=Computer{}
42     phone:=Phone{}
43     camera:=Camera{}
44 
45     commputer.Working(phone)
46     commputer.Working(camera)
47 }

 

10.3.3接口的使用场景

 

 

10.3.4注意事项和细节

1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。

2)接口中的所有方法都没有方法体,即是没有实现的方法体。

3)在Golang中一个自定义类型需要将某个接口的所有方法都实现,我们就说这个自定义类型实现了接口

4)一个自定义类型只有实现了某个接口,才能将该自定义类型的变量赋给接口类型。

5)只要是自定义类型就可以实现接口,不仅仅是结构体。

6)一个自定义类型可以实现多个接口

7)Golang中的接口不能有任何变量

 8)一个接口可以继承多个接口,这时如果要实现这个接口也必须将继承的接口全部实现

9)interface默认是一个指针类型,如果没有初始化就使用,这输出nil

10)空接口interface{} 没有任何方法,所有的类型都实现了空接口,即我们可以把任何一个变量赋给空接口。

 

10.3.5案例

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

 1 package main
 2 
 3 import(
 4     "fmt"
 5     "sort"
 6     "math/rand"
 7 )
 8 
 9 type Hero struct{
10     Name string
11     Age int
12 }
13 
14 type HeroSlice []Hero
15 
16 
17 //三个必须实现的接口
18 func (hs HeroSlice)Len() int {
19     return len(hs)
20 }
21 //怎么样的方法进行排序,此处是从小到大
22 func(hs HeroSlice)Less(i,j int) bool{
23     return hs[i].Age<hs[j].Age
24 }
25 
26 func(hs HeroSlice)Swap(i,j int){
27     hs[i],hs[j]=hs[j],hs[i]
28 }
29 
30 type Student struct{
31     Name string 
32     Age int
33     SCore float64
34 }
35 
36 func main(){
37     var intSlice =[]int{0,-1,10,7,90}
38     sort.Ints(intSlice)
39     fmt.Println(intSlice)
40 
41     var heroes HeroSlice
42     for i:=0;i<10;i++{
43         hero:=Hero{
44             Name:fmt.Sprintf("英雄|%d",rand.Intn(100)),
45             Age:rand.Intn(100),
46         }
47         heroes=append(heroes,hero)
48     }
49 
50     for _,v :=range heroes {
51         fmt.Println(v)
52     }
53 
54     sort.Sort(heroes)
55     fmt.Println("---------排序后--------")
56 
57     for _,v :=range heroes {
58         fmt.Println(v)
59     }
60 
61     i:=10
62     j:=20
63     //使i,j交换
64     i,j=j,i
65     fmt.Println("i=",i,"j=",j)
66 }

 

10.3.6实现接口vs继承

 1 package main
 2 import(
 3     "fmt"
 4 )
 5 
 6 type Monkey struct{
 7     Name string
 8 }
 9 func(monkey *Monkey) climbing(){
10     fmt.Println(monkey.Name,"猴子生来会爬树")
11 }
12 
13 type BridAble interface{
14     Flying()
15 }
16 
17 type FishAble interface{
18     Swimming()
19 }
20 
21 type LittleMonkey struct{
22     Monkey
23 }
24 func (this *LittleMonkey)Flying(){
25     fmt.Println(this.Name,"通过学习会飞!")
26 }
27 func (this *LittleMonkey)Swimming(){
28     fmt.Println(this.Name,"通过学习会游泳!")
29 }
30 
31 func main(){
32     monkey :=LittleMonkey{
33         Monkey{
34             Name:"悟空",
35         },
36     }
37 
38     monkey.climbing()
39     monkey.Flying()
40     monkey.Swimming()
41 
42 }

 

接口可以看作是对继承的一种补充:

 

 

 

 接口和继承解决的问题不同:

  继承的价值主要在于:解决代码的复用性和可维护性

  接口的价值主要在于:设计,设计好各种规范(方法),让其他自定义类型去实现这些方法。

 

接口比继承更加灵活 Person  Student  BirdAble LittleMonkey,继承满足的是is-a的关系,而接口满足的是like-a的关系。

 

接口在一定程度上实现了代码解耦

 

10.4面对对象编程三大特性-多态

10.4.1基本介绍

多态的特征是通过接口来实现的,可以按照统一的接口来调用不同的实现,这时接口变量就会呈现不同的类型。

 

10.4.2快速入门

前面Usb接口快速入门案例就很好的体现了多态的特性

 

 

10.4.3接口体现多态的两种方式

方式一:多态参数

前面的  Usb接口变量 usb Usb 既可以接受phone变量又可以接受camera变量

 

方式二:多态数组

案例:给usb 数组存放一个Phone结构体和Camera结构体变量

 1 package main
 2 
 3 import(
 4     "fmt"
 5 )
 6 
 7 type Usb interface{
 8     Start()
 9     Stop()
10 }
11 
12 type Phone struct{
13     Name string
14 }
15 func (p Phone)Start(){
16     fmt.Println(p.Name,"手机开始工作了")
17 } 
18 func (p Phone)Stop(){
19     fmt.Println(p.Name,"手机停止工作了")
20 }
21 
22 type Camera struct{
23     Name string
24 }
25 func (c Camera)Start(){
26     fmt.Println(c.Name,"相机开始工作了")
27 } 
28 func(c Camera)Stop(){
29     fmt.Println(c.Name,"相机停止工作了")
30 }
31 
32 func main(){
33      var usbArr [3]Usb
34      usbArr[0]=Phone{"小米"}
35      usbArr[1]=Camera{"佳能"}
36      usbArr[2]=Phone{"华为"}
37 
38      fmt.Println(usbArr)
39 }

 

10.5类型断言

10.5.1引入

 

 

10.5.2基本介绍

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

具体如下:

在进行类似断言时,如果类型不匹配,就会报panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型

在进行断言时,带上检测机制,如果成功就ok,否则也不会报panic.

 

 

10.5.3案例

案例1:在前面Usb接口案例做如下的改进:

给Phon结构体增加一个特有的方法call(),当Usb接口接收的是Phon变量时,还需要调用call方法。

 1 package main
 2 
 3 import(
 4     "fmt"
 5 )
 6 
 7 type Usb interface{
 8     Start()
 9     Stop()
10 }
11 
12 type Phone struct{
13     Name string
14 }
15 func (p Phone)Start(){
16     fmt.Println(p.Name,"手机开始工作了")
17 } 
18 func (p Phone)Stop(){
19     fmt.Println(p.Name,"手机停止工作了")
20 }
21 func(p Phone)Call(){
22     fmt.Println(p.Name,"手机在打电话了")
23 }
24 
25 type Camera struct{
26     Name string
27 }
28 func (c Camera)Start(){
29     fmt.Println(c.Name,"相机开始工作了")
30 } 
31 func(c Camera)Stop(){
32     fmt.Println(c.Name,"相机停止工作了")
33 }
34 
35 type Computer struct{
36 
37 }
38 func (computer Computer)Working(usb Usb){
39     usb.Start()
40     if phone,ok:=usb.(Phone);ok{
41         phone.Call()
42     }
43     usb.Stop()
44 }
45 
46 func main(){
47      var usbArr [3]Usb
48      usbArr[0]=Phone{"小米"}
49      usbArr[1]=Camera{"佳能"}
50      usbArr[2]=Phone{"华为"}
51     var computer Computer
52     for _,v:=range usbArr{
53         computer.Working(v)
54         fmt.Println()
55     }
56 }

 

案例2:写一函数,循环判断传入参数的类型

 1 package main
 2 import(
 3     "fmt"
 4 )
 5 func TypeJudge(items... interface{}){
 6     for index,x:=range items {
 7         switch x.(type){
 8         case bool:
 9             fmt.Printf("第%v个参数是bool类型,值是%v\n",index,x)
10         case int,int32,int64:
11             fmt.Printf("第%v个参数是整数类型,值是%v\n",index,x)
12         case float32,float64:
13             fmt.Printf("第%v个参数是浮点数类型,值是%v\n",index,x)
14         case string:
15             fmt.Printf("第%v个参数是字符串类型,值是%v\n",index,x)
16         default:
17             fmt.Printf("第%v个参数是不确定类型,值是%v\n",index,x)
18         }
19     }
20 
21 }
22 func main(){
23     var n1 float32=1.1
24     var n2 int =1
25     var n3 string="aaaa"
26     var n4 interface{}
27     TypeJudge(n1,n2,n3,n4)
28 }

 

案例3:在前面代码的基础上,增加Student类型和*Student类型。

 

 

11.文件操作

11.1文件的基本介绍

概念:文件主要的作用是保存数据,是数据源的一种

输入流和输出流:

 

 

os.File封装所有文件相关操作,File是一个结构体

详细建Golang语言标准库文档。

 

11.2打开文件和关闭文件

使用函数的方法:

 

 

 

 案例演示:

 1 package main
 2 import(
 3     "fmt"
 4     "os"
 5 )
 6  
 7 func main(){
 8      file,err:=os.Open("d:/hello.txt")
 9      if err!=nil{
10         fmt.Println("open file err=",err)
11      }
12 
13      fmt.Printf("file=%v",file)
14 
15      err=file.Close()
16      if err!=nil{
17         fmt.Println("close file err=",err)
18      }
19 
20 }

 

11.3读/写文件操作应用实例

11.3.1读文件

1)读文件的内容并显示在终端(带缓冲区的方式),使用os.Open,file.Close(),bufio.NewReader(),reader.ReadString函数和方法

 1 package main
 2 import(
 3     "fmt"
 4     "os"
 5     "bufio"
 6     "io"
 7 )
 8  
 9 func main(){
10     file,err:=os.Open("d:/hello.txt")
11     if err!=nil{
12         fmt.Println("open file err=",err)
13     }
14     //及时关闭文件,否则会有内存泄漏
15     defer file.Close()
16 
17     //创建一个类型为*Reader,是带缓冲的
18     /*
19     const (
20         defaultBufSize = 4096
21     )
22     */
23     reader:=bufio.NewReader(file)
24     //循环的读取文件的内容
25     for{
26         str,err:=reader.ReadString('\n')
27         if err==io.EOF{//此处io.EOF代表文件的末尾
28             break
29         }
30         fmt.Print(str)
31     }
32 
33     fmt.Println("文件读取结束...")     
34 }

2)读取文件的内容并显示在终端,(使用io/ioutil一次性将整个文件读入到内存中)这种方法适用于文件不大的情况。ioutil.ReadFile

 1 package main
 2 import(
 3     "fmt"
 4     "io/ioutil"
 5 )
 6  
 7 func main(){
 8     file:="d:/hello.txt"
 9     content,err:=ioutil.ReadFile(file)
10     if err!=nil{
11         fmt.Println("open file err=",err)
12     }
13     //及时关闭文件,否则会有内存泄漏
14     fmt.Printf("%v",string(content))
15 
16     fmt.Println("文件读取结束...")     
17 }

11.3.2os.OpenFile函数

 

 第二个参数:

 

 第三个参数:在windows操作系统下无效。

11.3.3写文件操作实例

1)创建一个新文件,写入内容5句"hello,Gardon"

 1 package main
 2 import(
 3     "fmt"
 4     "bufio"
 5     "os"
 6 )
 7  
 8 func main(){
 9      filePath:="d:/abc.txt"
10      file,err:=os.OpenFile(filePath,os.O_WRONLY|os.O_CREATE,0666)
11      if err!=nil{
12         fmt.Println("open file err=",err)
13         return 
14      }
15      defer file.Close()
16      str:="hello,Gardon\n"
17      writer:=bufio.NewWriter(file)
18      for i:=0;i<5;i++{
19         writer.WriteString(str)
20      }
21     //writer是带缓存的,在调用writerString时,先将内容写入缓存,在调用flush方法,将缓存中的数据写入文件
22      writer.Flush()
23 
24 }

 

2)打开一个已经存在的文件,将原来的内容覆盖成新的内容 10句“你好,尚硅谷”

 1 package main
 2 import(
 3     "fmt"
 4     "bufio"
 5     "os"
 6 )
 7  
 8 func main(){
 9      filePath:="d:/abc.txt"
10      file,err:=os.OpenFile(filePath,os.O_WRONLY|os.O_TRUNC,0666)
11      if err!=nil{
12         fmt.Println("open file err=",err)
13         return 
14      }
15      defer file.Close()
16      str:="你好,尚硅谷\n"
17      writer:=bufio.NewWriter(file)
18      for i:=0;i<10;i++{
19         writer.WriteString(str)
20      }
21     //writer是带缓存的,在调用writerString时,先将内容写入缓存,在调用flush方法,将缓存中的数据写入文件
22      writer.Flush()
23 
24 }

 

3)打开一个存在的文件,在原来的内容后面追加内容“ABC!hello world!”

 1 package main
 2 import(
 3     "fmt"
 4     "bufio"
 5     "os"
 6 )
 7  
 8 func main(){
 9      filePath:="d:/abc.txt"
10      file,err:=os.OpenFile(filePath,os.O_WRONLY|os.O_APPEND,0666)
11      if err!=nil{
12         fmt.Println("open file err=",err)
13         return 
14      }
15      defer file.Close()
16      str:="ABD,hello!\n"
17      writer:=bufio.NewWriter(file)
18      for i:=0;i<10;i++{
19         writer.WriteString(str)
20      }
21     //writer是带缓存的,在调用writerString时,先将内容写入缓存,在调用flush方法,将缓存中的数据写入文件
22      writer.Flush()
23 
24 }

4)打开一个存在的文件,在原来的内容读出显示在终端,并且后面追加内容“ABC!”

 1 package main
 2 import(
 3     "fmt"
 4     "bufio"
 5     "os"
 6     "io"
 7 )
 8  
 9 func main(){
10      filePath:="d:/abc.txt"
11      file,err:=os.OpenFile(filePath,os.O_RDWR|os.O_APPEND,0666)
12      if err!=nil{
13         fmt.Println("open file err=",err)
14         return 
15      }
16      defer file.Close()
17      reader:=bufio.NewReader(file)
18      for{
19         str,err:=reader.ReadString('\n')
20         if err==io.EOF{
21             break
22         }
23         fmt.Print(str)
24      }
25      str:="ABD!\n"
26      writer:=bufio.NewWriter(file)
27      for i:=0;i<10;i++{
28         writer.WriteString(str)
29      }
30     //writer是带缓存的,在调用writerString时,先将内容写入缓存,在调用flush方法,将缓存中的数据写入文件
31      writer.Flush()
32 
33 }

 

5)编写一个程序,将一个文件的内容写入到另一个文件。(文件已经提前存在)ioutil.ReadFile/ioutil.WriteFile

 

 1 package main
 2 import(
 3     "fmt"
 4     "io/ioutil"
 5 )
 6  
 7 func main(){
 8      filePath1:="d:/abc.txt"
 9      filePath2:="d:/hello.txt"
10      data,err:=ioutil.ReadFile(filePath1)
11      if err!=nil{
12         fmt.Println("open file err=",err)
13         return 
14      }
15       err=ioutil.WriteFile(filePath2,data,0666)
16       if err!=nil{
17         fmt.Println("open file err=",err)
18      }
19 
20 }

 

11.3.4判断文件是否存在

 Golang判断文件或者文件夹,是否存在的方法为使用os.Stat()函数 返回的错误值进行判断:

1)如果返回的错误为nil,说明文件或者文件夹存在

2)如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或者文件夹不存在

3)返回的错误为其他类型,则不确定是否存在

 

 

 

11.4文件编程应用实例

11.4.1拷贝文件

将一张图片/电影/mp3拷贝到另一个文件 e:/abc.jpg

io包  func Copy(dst Writer,src Reader)(written int64,err error)

 

 1 package main
 2 import(
 3     "fmt"
 4     "os"
 5     "io"
 6     "bufio"
 7 )
 8  
 9 func CopyFile(dstFileName string,srcFileName string )(written int64,err error){
10     srcFile,err:=os.Open(srcFileName)
11     if err!=nil{
12         fmt.Println("open file err=",err)
13      }
14      defer srcFile.Close()
15      reader:=bufio.NewReader(srcFile)
16      dstFile,err:=os.OpenFile(dstFileName,os.O_WRONLY|os.O_CREATE,0666)
17      if err!=nil{
18         fmt.Println("open file err=",err)
19         return 
20      }
21      defer dstFile.Close()
22 
23      writer:=bufio.NewWriter(dstFile)
24      return io.Copy(writer,reader)
25 
26 }
27 func main(){
28     srcFile:="d:/image/abc.jpg"
29     dstFile:="d:/image/abd.jpg"
30     _,err:=CopyFile(dstFile,srcFile)
31     if err==nil{
32         fmt.Println("拷贝完成!")
33     }else {
34         fmt.Println("拷贝错误 err=%v\n",err)
35     }
36 
37 }

 

 

11.4.2统计英文、数字、空格和其他字符串

统计一个文件中含有的英文、数字、空格及其他字符数量

 

 1 package main
 2 import(
 3     "fmt"
 4     "os"
 5     "io"
 6     "bufio"
 7 )
 8  
 9 type CharCount struct{
10     ChCount int
11     NumCount int
12     SpaceCount int
13     OtherCount int
14 }
15 
16 func main(){
17     fileName:="d:/hello.txt"
18     file,err:=os.Open(fileName)
19     if err!=nil{
20         fmt.Printf("open file err=%v\n",err)
21         return 
22     }
23     defer file.Close()
24 
25     var count CharCount
26     reader:=bufio.NewReader(file)
27     for{
28         str,err:=reader.ReadString('\n')
29         if err==io.EOF {
30             break
31         }
32         //为了兼容中文字符串
33         str1 := []rune (str)
34 
35         for _,v:=range str1{
36             switch{
37             case v>='a'&&v<='z':
38                 fallthrough
39             case v>='A'&&v<='Z':
40                 count.ChCount++
41             case v==' '||v=='\t':
42                 count.SpaceCount++
43             case v>='0'&&v<='9':
44                 count.NumCount++
45             default:
46                 count.OtherCount++
47 
48             }
49         }
50 
51     }
52 
53     fmt.Println(count)
54 
55 }

 

11.5命令行参数

我们需要获得一个命令行参数,该如何处理?

 如图:=>命令行参数

 

 

11.5.1基本介绍

os.Args是一个string切片,用来存储所有的命令行参数

 

11.5.2案例

请编写一段代码,可以获取命令行各个参数

 1 package main
 2 import(
 3     "fmt"
 4     "os"
 5 )
 6  
 7 func main(){
 8     fmt.Println("命令行的参数有",len(os.Args))
 9     for i,v:=range os.Args{
10         fmt.Printf("args[%v]=%v\n",i,v)
11     }
12 
13 }

 

11.5.3flag包用来解析命令行参数

前面的方式比较原生态,对带有指定参数形式的命令行不是很方便。

 

 

 

比如:

 

 

案例:

 

 1 package main
 2 import(
 3     "fmt"
 4     "flag"
 5 )
 6  
 7 func main(){
 8     //
 9     var user string
10     var pwd string
11     var host string
12     var port int
13     flag.StringVar(&user,"u","","用户名,默认为空")
14     flag.StringVar(&pwd,"pwd","","密码,默认为空")
15     flag.StringVar(&host,"h","localhost","主机名,默认localhost")
16     flag.IntVar(&port,"port",3306,"端口号,默认为3306")
17 
18     flag.Parse()
19     fmt.Printf("use=%v,pwd=%v,host=%v,port=%v",user,pwd,host,port)
20 
21 }

 

11.6JSON基本介绍

JSON(JaveScript Object Notation)是一种轻量级的数据交换格式。易于人的阅读和编写,也易于机器的解析和生成。 key-val

JSON是在2001年开始推广使用的数据格式,目前已经成为主流的数据格式。

JSON易于机器解析和生成,并有效的提升网络传输效率,通常程序在网络传输时会先将数据(结构体、map等)序列化为json字符串,到接收方得到json字符串时,在反序列化恢复成原来的数据类型。这种方式已经成为各个语言的标准。

 

 

应用场景:

 

 

11.7JSON数据格式说明

在JS语言中,一切都是对象,因此任何的数据类型都可以通过JSON来表示,例如字符串,数字,对象,数组,map,结构体等

 

 

11.8JSON数据在线解析

JSON在线解析及格式化验证 - JSON.cn      https://www/json.cn/

网站可以检验json格式的数据是否正确,尤其是编写复杂的json格式数据时。

 

11.9JSON的序列化

介绍:

  json序列化是指将有key-vaule 结构体的数据类型(比如结构体、map、切片)序列化json字符串的操作

应用案例:

  结构体、map、切片的序列化,其他数据类型类似

代码:

package main
import(
    "fmt"
    "encoding/json"
)

type Monster struct{
    Name string
    Age int
    Birthday string
    Sal float64
    Skill string
}

func testStruct(){
    monster :=Monster{
        Name :"牛魔王",
        Age:500,
        Birthday:"1002-02-04",
        Sal:8000.0,
        Skill:"牛魔拳",
    }
    //结构体是值拷贝,&传递处理熟读更快
    data,err:=json.Marshal(&monster)
    if err!=nil{
        fmt.Printf("序列号错误 err=%v\n",err)
    }
    fmt.Printf("monster 序列化=%v\n",string(data))
}

func testMap(){
    // key是string类型,val是任意类型,空接口可以接收任意类型的值
    var a map [string]interface{}
    a=make(map[string]interface{})
    a["name"]="红孩儿"
    a["age"]=30
    a["address"]="洪崖洞"
    //map是指针传递
    data,err:=json.Marshal(a)
    if err!=nil{
        fmt.Printf("序列号错误 err=%v\n",err)
    }
    fmt.Printf("map 序列化=%v\n",string(data))

}

func testSlice(){
    var slice []map[string]interface{}
    var m1 map[string]interface{}
    m1=make(map[string]interface{})
    m1["name"]="jake"
    m1["age"]=7
    m1["address"]=[]string{"北京","上海"}

    slice=append(slice,m1)

    var m2 map[string]interface{}
    m2=make(map[string]interface{})
    m2["name"]="Tom"
    m2["age"]=8
    m2["address"]="北京"
    slice=append(slice,m2)

    data,err:=json.Marshal(slice)
    if err!=nil{
        fmt.Printf("序列号错误 err=%v\n",err)
    }
    fmt.Printf("切片 序列化=%v\n",string(data))

} 

func testfloat64(){
    //序列化后变成一个json字符串,因此对基本数据类型进行序列化意义不大
    var num float64=123.1
    data,err:=json.Marshal(&num)
    if err!=nil{
        fmt.Printf("序列号错误 err=%v\n",err)
    }
    fmt.Printf(" float64 序列化=%v\n",string(data))
}
func main(){
     testStruct()
     testMap()
     testSlice()
     testfloat64()
}

 

注意事项:对于结构体的序列化,如果我们希望序列化后的key的名字又是我们重新制定的,那么可以给struct指定一个tag标签

1 type Monster struct{
2     Name string `json:"name"`
3     Age int `json:"age"`
4     Birthday string `json:"birthday"`
5     Sal float64 `json:"sal"`
6     Skill string `json:"skill"`
7 }

 

输出数据:monster 序列化={"name":"牛魔王","age":500,"birthday":"1002-02-04","sal":8000,"skill":"牛魔拳"}

11.10JSON的反序列化

介绍:

  json序列化是指将 json字符串反序列化为原来的的数据类型(比如结构体、map、切片)的操作

应用案例:

  json字符串反序列化结构体、map、切片,其他数据类型类似

代码:

package main
import(
    "fmt"
    "encoding/json"
)

//对于结构体的反序列化一定要先定义相对于的结构体
type Monster struct{
    Name string
    Age int
    Birthday string
    Sal float64
    Skill string
}

func unmarshalstructural(){
    str:="{\"name\":\"牛魔王\",\"age\":500,\"birthday\":\"1002-02-04\",\"sal\":8000,\"skill\":\"牛魔拳\"}"
    var monster Monster
    err:=json.Unmarshal([]byte(str),&monster)
    if err!=nil{
        fmt.Println("unmershal err=",err)
    }
    fmt.Println("反序列化后 monster=",monster)
}

func UnmarshalMap(){
    str:="{\"address\":\"洪崖洞\",\"age\":30,\"name\":\"红孩儿\"}"
    //反序列化底层会make一个空间
    var a map[string]interface{}
    err:=json.Unmarshal([]byte(str),&a)
    if err!=nil{
        fmt.Println("unmershal err=",err)
    }
    fmt.Println("反序列化后 a=",a)

}

func UnmarshalSlice(){
    str:="[{\"address\":\"北京\",\"age\":7,\"name\":\"jake\"},"+
        "{\"address\":[\"北京\",\"上海\"],\"age\":8,\"name\":\"Tom\"}]"
    //对于slice Ummarshl底层也封装make函数
    var slice []map[string]interface{}
    err:=json.Unmarshal([]byte(str),&slice)
    if err!=nil{
        fmt.Println("unmershal err=",err)
    }
    fmt.Println("反序列化后  slice=",slice)

}
 
func main(){
     unmarshalstructural()
     UnmarshalMap()
     UnmarshalSlice()
}

 

注意事项:

1)反序列化一个json字符串,要确保反序列前的数据类型和反序列后的类型保持一致

2)如果json是通过程序得到的则 “ 不需要转义处理

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2022-07-30 18:09  Grit_L。  阅读(97)  评论(0编辑  收藏  举报