Go语言基础


1 Golang的概述

1.1 Go语言介绍

  Go 即 Golang,是 Google 公司 2009 年 11 月正式对外公开的一门编程语言。

  根据 Go 语言开发者自述,近 10 多年,从单机时代的 C 语言到现在互联网时代的Java,都没有令人满意的开发语言,而 C++往往给人的感觉是,花了 100%的经历,却只有60%的开发效率,产出比太低,Java 和 C#的哲学又来源于 C++。并且,随着硬件的不断升级,这些语言不能充分的利用硬件及 CPU。因此,一门高效、简洁、开源的语言诞生了。

  Go 语言不仅拥有静态编译语言的安全和高性能,而且又达到了动态语言开发速度和易维护性。有人形容 Go 语言:Go = C + Python , 说明 Go 语言既有 C 语言程序的运行速度,又能达到 Python 语言的快速开发。

  Go 语言是非常有潜力的语言,是因为它的应用场景是目前互联网非常热门的几个领域,比如 WEB 开发、区块链开发、大型游戏服务端开发、分布式/云计算开发。国内比较知名的B 站就是用 Go 语言开发的,像 Goggle、阿里、京东、百度、腾讯、小米、360 的很多应用也是使用 Go 语言开发的。

1.2 Go语言成功的项目

  nsq:bitly开源的消息队列系统,性能非常高,每天处理数十亿条的消息

  docker:基于 lxc 的一个虚拟打包工具,能够实现 PAAS 平台的组建

  packer:用来生成不同平台的镜像文件,例如 VM、vbox、AWS 等

  skynet:分布式调度框架

  Doozer:分布式同步工具,类似 ZooKeeper

  Heka:mazila 开源的日志处理系统

  cbfs:couchbase 开源的分布式文件系统

  tsuru:开源的 PAAS 平台,和 SAE 实现的功能一模一样

  groupcache:memcahe 作者写的用于 Google 下载系统的缓存系统

  god:类似 redis 的缓存系统,但是支持分布式和扩展性

  gor:网络流量抓包和重放工具

1.3 什么是程序

  程序:完成某个功能的指令的集合

1.4 Golang语言的特点

  Go语言保证了既能达到静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容 Go 语言:Go = C + Python , 说明 Go 语言既有 C 静态语言程序的运行速度,又能达到 Python 动态语言的快速开发。

  1.从 C 语言中继承了很多理念,包括表达式语法、控制结构、基础数据类型、调用参数传值、指针等,也保留了和 C 语言一样的编译执行方式及弱化的指针

// go语言指针的使用特点
func testPtr(num *int) {
	*num = 20
}

  2.引入包的概念,用于组织程序结构,Go 语言的一个文件都要归属于一个包,而不能单独存在

package main // 一个go文件需要在一个包

import "fmt"

func main() {
	fmt.Println("Ok") // 输出一句话
}

  3.垃圾回收机制,内存自动回收,不需开发人员管理

  4.天然并发

   4.1 从语言层面支持并发,实现简单

   4.2 goroutine,轻量级线程,可实现大并发处理,高效利用多核

   4.3 基于 CPS 并发模型(Communicating Sequential Processes )实现

  5.吸收了管道通信机制,形成 Go 语言特有的管道channel,通过管道channel可以实现不同goroute之间的相互通信

  6.函数可以返回多个值

// 写一个函数,实现同时返回和、差
// go函数支持返回多个值

func getSumAndSub(n1 int, n2 int) (int int) {

	sum := n1 + n2    // go语句后面不要带分号
	sub := n1 - n2
	return sum , sub
}

  7.新的创新:比如切片slice、延时执行defer等

1.5 Go语言快速开发入门

  1.需求:要求开发一个 hello.go 程序,可以输出 “hello,world”

  2.开发的步骤:

   2.1 开发这个程序/项目时,go 的目录结构怎么处理

   2.2 代码如下

// 要求开发一个 hello.go 程序,可以输出"Hello,World!"
package main

import "fmt"

func main() {
	fmt.Println("Hello,World!")
}

对上图的说明:

  1.go 文件的后缀是 .go

  2.package main

   表示该 hello.go 文件所在的包是 main, 在 go 中,每个文件都必须归属于一个包

  3.import “fmt”

   表示:引入一个包,包名 fmt, 引入该包后,就可以使用 fmt 包的函数,比如:fmt.Println

  4.func main() {

   }

   func是一个关键字,表示一个函数

   main是函数名,是一个主函数,即程序的入口

  5.fmt.Println(“Hello World!”)

   表示调用 fmt 包的函数 Println 输出”Hello World!”

  6.通过 go build 命令对该 go 文件进行编译,生成 .exe 文件

  7.运行 hello.exe 文件即可

  注意:通过 go run 命令可以直接运行 hello.go 程序 [类似执行一个脚本文件的形式]

package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

分析以上代码,得知Go语言的代码基本结构如下:

  package main:代表程序或项目运行的主入口文件,如果改为package aa,则设为程序或项目的包

  import “fmt”:代表导入内置包fmt,实现数据的标准化输出

  func main():代表程序运行的主入口,不支持任何返回值和参数传入

1.6 Golang执行流程分析

  1.如果对源码编译后,再执行,Go 的执行流程如下图:

  2.如果对源码直接执行 go run 源码,Go 的执行流程如下图:

两种执行流程的方式区别:

  1.如果先编译生成了可执行文件,那么将该可执行文件拷贝到没有 go 开发环境的机器上,仍然可以运行

  2.如果是直接 go run go 源代码,那么要在另外一个机器上这么运行,也需要 go 开发环境,否则无法执行

  3.在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多

1.7 Go程序开发注意事项

  1.Go 源文件以 “go” 为扩展名

  2.Go 应用程序的执行入口是 main()函数

  3.Go 语言严格区分大小写

  4.Go 方法由一条条语句构成,每个语句后不需要分号(Go 语言会在每行后自动加分号),这也体现出Golang的简洁性

  5.Go 编译器是一行行进行编译的,因此一行就写一条语句,不能把多条语句写在同一个,否则报错

  6.go 语言定义的变量或者 import 的包如果没有使用到,代码不能编译通过

  7.大括号都是成对出现的,缺一不可

1.8 Go语言转义字符(escape char)

  常用的转义字符有如下:

1.9 注释

  用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性;

  注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现

1.9.1 Golang中注释的两种形式

  Go支持C语言风格的/**/块注释,也支持C++风格的//注释

  行注释更通用,块注释只要用于针对包的详细说明或者屏蔽大块的代码

1.行注释
  语法:// 注释内容

2.块注释(多行注释)

语法:
/*
注释内容
*/

使用细节:

  1.对于行注释和块注释,被注释的文字,不会被 Go 编译器执行

  2.块注释里面不允许有块注释嵌套

1.10 规范的代码风格

1.10.1 正确的注释和注释风格

  1.Go 官方推荐使用行注释来注释整个方法和语句

  2.学习Go源码的代码风格

1.10.2 正确的缩进和空白

  1.使用一次 tab 操作,实现缩进,默认整体向右边移动,用 shift+tab可整体向左移

  2.使用 gofmt 来进行格式化

  3.运算符两边习惯性各加一个空格。比如:2 + 4 * 5

  4.Go语言的代码风格

package main
import "fmt"
func main() {
     fmt.Println("hello,world!")
}
// 上面的写法是正确的.
package main
import "fmt"
func main()
{
     fmt.Println("hello,world!")
}

  Go 设计者思想:一个问题尽量只有一个解决方法

  5.一行最长不超过 80 个字符,超过的请使用换行展示,尽量保持格式优雅

1.11 Golang 标准库 API 文档

  1.API(Application Programming Interface,应用程序编程接口)是 Golang 提供的基本编程接口

  2.Go 语言提供了大量的标准库,google为这些标准库提供了相应的API文档,用于告诉开发者如何使用这些标准库,以及标准库包含的方法

  3.Golang中文网在线标准库文档: https://studygolang.com/pkgdoc

  4.Golang的包和源文件和函数的关系简图:

2 变量

2.1 为什么需要变量

  变量是程序的基本组成单位
  不论使用哪种高级程序语言编写程序,变量都是其程序的基本组成单位,如下示意图:

  上图的 sum,sub 都是变量

2.2 变量的介绍

2.2.1 变量的概念

  变量相当于内存中一个数据存储空间的表示,可以把变量看做是一个房间的门牌号,通过门牌号可以找到房间,同样的道理,通过变量名可以访问到变量(值)

2.2.2 变量的使用步骤

  1.声明变量(也叫:定义变量)

  2.非变量赋值

  3.使用变量

package main

import "fmt"

func main() {
	var i int // 定义变量/声明变量
	i = 10    // 给i赋值
	fmt.Println("i =", i)
}
// 输出:i = 10

2.3 变量使用注意事项

  1.变量表示内存中的一个存储区域

  2.该区域有自己的名称(变量名)和类型(数据类型)

  3.Golang变量使用的三种方式:

   第一种:指定变量类型,声明后若不赋值,使用默认值

package main

import "fmt"

func main() {
	// 指定变量类型,声明后若不赋值,使用默认值  int的默认值是0
	var i int
	fmt.Println("i =", i)
}
// 输出:i = 0

   第二种:根据值自行判定变量类型(类型推导)

package main

import "fmt"

func main() {
	// 根据值自行判定变量类型(类型推导)
	var num = 10.11
	fmt.Println("num =", num)
}
// 输出:num = 10.11

   第三种:省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误

package main

import "fmt"

func main() {
	// 省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
	// 下面的方式等价于 var namr string    name = "Tom
	name := "Tom"
	fmt.Println("name =", name)
}
// 输出:name = Tom

  4.多变量声明

   4.1在编程中,有时需要一次性声明多个变量,Golang也提供这样的语法

package main

import "fmt"

func main() {
	// Golang一次性声明多个变量方式1
	var n1, n2, n3 int
	fmt.Println("n1 =", n1, "n2 =", n2, "n3 =", n3)

	// Golang一次性声明多个变量方式2
	var n4, name, n5 = 100, "Tom", 888
	fmt.Println("n4 =", n4, "name =", name, "n5 =", n5)

	// Golang一次性声明多个变量方式3
	n6, sex, n7 := 456, "男", 367
	fmt.Println("n6 =", n6, "sex =", sex, "n7 =", n7)
}

   4.2一次性声明多个全局变量【在 go 中函数外部定义变量就是全局变量】:

package main

import "fmt"

// 定义全局变量
var n8 = 788
var n9 = 789
var sex1 = "女"

// 上面的声明方式,也可以改成一次性声明
var (
	n10    = 906
	n11    = 290
	gender = "男"
)

func main() {
	// 输出全局变量
	fmt.Println("n8 =", n8, "n9 =", n9, "sex1 =", sex1)
	fmt.Println("n10 =", n10, "n11 =", n11, "gender =", gender)
}

  5.该区域的数据值可以在同一类型范围内不断变化

package main

import "fmt"

func main() {
	var i int = 10
	i = 30
	i = 50
	fmt.Println("i =", i)
	// i = 1.2 // int 不能改变数据类型
}

  6.变量在同一个作用域(一个函数或者代码块)内不能重名

package main

import "fmt"

func main() {
	var i int = 10
	fmt.Println("i =", i)
	// var i int = 78
	// i := 99
}

  7.变量三要素:变量=变量名+值+数据类型

  8.Golang的变量如果没有赋初值,编译器会使用默认值, 比如:int默认值是0、string默认值为空串,小数默认为0

2.4 变量声明、初始化和赋值

2.4.1 声明变量

  基本语法:var 变量名 数据类型
  var a int 这就是声明了一个变量,变量名是 a
  var num1 float32 这也声明了一个变量,表示一个单精度类型的小数,变量名是num1

2.4.2 初始化变量

  在声明变量的时候,就给值。
  var a int = 45 这就是初始化变量a
  使用细节:如果声明时就直接赋值,可省略数据类型 var b = 400

2.4.2 给变量赋值

  比如先声明了变量:var num int,然后再给值:num=780;这就是给变量赋值

2.5 程序中 +号的使用

  1.当左右两边都是数值型时,则做加法运算

package main

import "fmt"

func main() {
	var i = 1
	var j = 2
	var r = i + j // 做加法运算
	fmt.Println("r =", r)
}

  2.当左右两边都是字符串,则做字符串拼接

package main

import "fmt"

func main() {
	var str1 = "Hello"
	var str2 = "China"
	var res = str1 + "," + str2 + "!" // 做拼接操作
	fmt.Println("res =", res)
}

3 基本数据类型

  每一种数据都定义了明确的数据类型,在内存中分配了不同大小的内存空间

3.1 整数类型

  就是用于存放整数值的,比如 0, -1, 2345等等

3.1.1 整数类型-有符号

类型 有无符号 占用存储空间 表数范围 备注
int8 1字节 -128 ~ 127
int16 2字节 -2^15 ~ 2^15-1
int32 4字节 -2^31 ~ 2^31-1
int64 8字节 -2^63 ~ 2^63-1
package main

import "fmt"

func main() {
	var i int = 1
	fmt.Println("i =", i)

	// 测试int8的范围   -128~127
	// 其他的 int16  int32  int64  类推...
	var j int8 = 127
	fmt.Println("j =", j)
}

3.1.2 整数类型-无符号

类型 有无符号 占用存储空间 表数范围 备注
uint8 1字节 0 ~ 2^8-1
uint16 2字节 0 ~ 2^16-1
uint32 4字节 0 ~ 2^32-1
uint64 8字节 0 ~ 2^64-1
package main

import "fmt"

func main() {
	// 测试一下 uint8的范围(0 ~ 255)   其他的  uint16、uint32、uint64类推即可
	var k uint8 = 255
	fmt.Println("k =", k)
}

3.1.3 int的其它类型

类型 有无符号 占用存储空间 表数范围 备注
int 32位系统4个字节
64位系统8个字节
-2^31 ~ 2^31-1
-2^63 ~ 2^63-1
uint 32位系统4个字节
64位系统8个字节
0 ~ 2^32-1
0 ~ 2^64-1
rune 与int32一样 -2^31 ~ 2^31-1 等价int32,表示
一个Unicode码
byte 与 uint8 等价 0 ~ 2^8-1 当要存储字符时选用byte
package main

import "fmt"

func main() {
	// int、uint、rune、byte的使用
	var a int = 8900
	var b uint = 1
	var c rune = 1567
	var d byte = 255
	fmt.Println("a =", a, "b =", b, "c =", c, "d =", d)
}

3.1.4 整数类型使用细节

  1.Golang各整数类型分:有符号和无符号,int、uint的大小和系统有关

  2.Golang的整型默认声明为 int 型

package main

import "fmt"

func main() {
	var n1 = 100                   // ? n1 是什么类型
	fmt.Printf("n1的数据类型是%T\n", n1) // fmt.Printf()用于做格式化输出
}

  3.如何在程序查看某个变量的字节大小和数据类型

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var n2 int64 = 8765
	// unsafe.Sizeof(n2)) 是unsafe包的一个函数,可以返回n2变量占用的字节数
	fmt.Printf("n2的数据类型是%T\nn2占用的字节数是%d", n2, unsafe.Sizeof(n2))
}

  4.整型变量在使用时,遵守保小不保大的原则,即:在保证程序正确运行下,尽量使用占用空间小的数据类型。【如:年龄】

  5.bit是计算机中的最小存储单位、byte是计算机中基本存储单元,1byte = 8bit

3.2 小数类型/浮点型

  小数类型就是用于存放小数的,比如:1.2、0.23、-1.911

package main

import "fmt"

// Golang中小数类型的使用
func main() {
	var price float32 = 89.12
	fmt.Println("price =", price)
}

3.2.1 浮点型的分类

类型 占用存储空间 表数范围
单精度float32 4字节 -3.403E38 ~ 3.403E38
双精度float64 8字节 -1.798E308 ~ 1.798E308

  1.浮点数在机器中存放形式的说明:浮点数=符号位+指数位+尾数位

   总结:浮点数都是有符号的

package main

import "fmt"
func main() {
	// 浮点数都是有符号的
	var num1 float32 = -0.00089
	var num2 float64 = -7809656.09
	fmt.Println("num1 =", num1, "num2 =", num2)
}

  2.尾数部分可能丢失,造成精度损失

   总结:float64 的精度比 float32 的要准确,如果要保存一个精度高的数,则应选用 float64

package main

import "fmt"

func main() {
	// 尾数部分可能丢失,造成精度损失
	var num3 float32 = -123.0000901
	var num4 float64 = -123.0000901
	fmt.Println("num3 =", num3, "num4 =", num4)
}
// num3 = -123.00009 num4 = -123.0000901

  3.浮点型的存储分为三部分:符号位+指数位+尾数位 在存储过程中,精度会有丢失

3.2.2 浮点型使用细节

  1.Golang 浮点类型有固定的范围和字段长度,不受具体 OS(操作系统)的影响

  2.Golang 的浮点型默认声明为 float64 类型

package main

import "fmt"

func main() {
	var num5 = 1.4
	fmt.Printf("num5的数据类型是%T \n", num5)
}

  3.浮点型常量有两种表示形式

   十进制,如:5.12 .512 (必须有小数点)

   科学计数法,如:5.1234e2 = 5.12 * 10 的 2 次方、5.12E-2 = 5.12/10 的 2 次方

package main

import "fmt"

func main() {
	// 十进制形式,如:5.12     .512 (必须有小数点)
	num6 := 5.12
	num7 := .134 // => 0.134
	fmt.Println("num6 =", num6, "num7 =", num7)

	// 科学计数法形式
	num8 := 5.1234e2  // => 5.1234 * 10的2次方  注明:e也可以写成E
	num9 := 5.1234e-2 // => 5.1234 / 10的2次方
	fmt.Println("num8 =", num8, "num9 =", num9)
}

  4.通常情况下,应该使用 float64 ,因为它比 float32 更精确。[开发中推荐使用 float64]

3.3 字符类型

  Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存

  字符串就是一串固定长度的字符连接起来的字符序列

  Go 的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的

  官方将string归属到基本数据类型

package main

import "fmt"
func main() {
	var c1 byte = 'a'
	var c2 byte = '0' // 字符的0

	// 当直接输出byte值时,就是输出了对应的字符的码值
	fmt.Println("c1 =", c1)
	fmt.Println("c2 =", c2)

	// 输出字符时,需要使用格式化输出
	fmt.Printf("c1 = %c c2 = %c\n", c1, c2)

	// var c3 byte = '成'  // 成的码值是25104  overflow溢出
	var c3 int = '成'
	fmt.Printf("c3 = %c\nc3对应的码值 = %d", c3, c3)
}

总结:

  1.如果保存的字符在 ASCII 表中,比如[0-1, a-z,A-Z..]直接可以保存到 byte

  2.如果保存的字符对应码值大于255,可以考虑使用 int 类型保存

  3.如果需要按照字符的方式输出,这时需要格式化输出,即 fmt.Printf(“%c”, c1)..

3.3.1 字符类型使用细节

  1.字符常量是用单引号(‘’)括起来的单个字符。例如:var c1 byte = ‘a’、var c2 int = ‘中’、var c3 byte = ‘9’

  2.Go 中允许使用转义字符 ‘\’将其后的字符转变为特殊字符型常量。例如:var c3 char = ‘\n’ // ‘\n’表示换行符

  3.Go 语 言 的 字 符 使 用 UTF-8 编 码

   查 询 字 符 对 应 的 utf8 码 值:http://www.mytju.com/classcode/tools/encode_utf8.asp

   英文字母:1个字节、汉字:3 个字节

  4.在 Go 中,字符的本质是一个整数,直接输出时,是该字符对应的 UTF-8 编码的码值

  5.可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的unicode字符(UTF-8编码)

  6.字符类型是可以进行运算的,相当于一个整数,因为它都对应有 Unicode 码

package main

import "fmt"

func main() {
	// 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的unicode字符(UTF-8编码)
	var c4 int = 22269 // 22269 -> '国’
	var c5 int = 120   // 120 -> 'x'
	fmt.Printf("c4 = %c\nc5 = %c\n", c4, c5)

	// 字符类型是可以进行运算的,相当于一个整数,运算时是按照码值运算的
	var n1 = 10 + 'a' // 10 + 97 = 107
	fmt.Println("n1 =", n1)
}

3.3.2 字符类型的本质

  1.字符型 存储到 计算机中,需要将字符对应的码值(整数)找出来

   存储:字符 —> 对应码值 —-> 二进制 –> 存储

   读取:二进制 —-> 码值 —-> 字符 –> 读取

  2.字符和码值的对应关系是通过字符编码表决定的

  3.Go 语言的编码都统一成了 utf-8

3.4 布尔类型

  1.布尔类型也叫 bool 类型,bool 类型数据只允许取值 true 和 false

  2.bool 类型占 1 个字节

  3.bool 类型适用于逻辑运算,一般用于程序流程控制

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var b = false
	fmt.Println("b =", b)

	// 1.bool类型占用存储空间是1个字节
    // 2.bool类型只能取true或false
	fmt.Println("b的占用空间是", unsafe.Sizeof(b))
}

  注明:布尔类型不可以0或非0的整数替代false和true,这点和C语言不同

3.5 字符串类型

  字符串就是一串固定长度的字符连接起来的字符序列

  Go 的字符串是由单个字节连接起来的

  Go语言字符串的字节使用 UTF-8 编码标识 Unicode 文本

package main

import "fmt"

func main() {
	// string的基本使用
	var address string = "北京长城 110 Hello World!"
	fmt.Println(address)
}

3.5.1 字符串使用细节

  1.Go 语言字符串的字节使用 UTF-8 编码标识 Unicode 文本,这样 Golang 统一使用 UTF-8 编码,中文乱码问题不会再困扰程序员

  2.字符串一旦赋值了,字符串就不能修改了:在 Go 中字符串是不可变的

package main

import "fmt"

func main() {
	var str = "Hello"
	// str[0] = 'a' 这里不能修改str的内容,即go中的字符串是不可变的	
}

  3.字符串的两种表示形式:

   双引号, 会识别转义字符

   反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果

package main

import "fmt"

func main() {
	str2 := "abc\ndef"
	fmt.Println(str2)

	str3 := `
func main() {
	var b = false
	fmt.Println("b =", b)

	// 1.bool类型占用存储空间是1个字节
	// 2.bool类型只能取true或false
	fmt.Println("b的占用空间是", unsafe.Sizeof(b))
}
`
	fmt.Println(str3)

}

  4.字符串拼接方式

package main

import "fmt"

func main() {
	var str = "Hello" + "World"
	str += "哈哈"
	fmt.Println(str)	
}

  5.当一行字符串太长时,需要使用到多行字符串,可以如下处理

package main

import "fmt"

func main() {
	// 当拼接的操作很长时,可按如下进行操作:(需要将+号保留在上一行)
	var str4 = "你好" + "," +
		"中国" + "!" +
		"你好,世界" + "。"
	fmt.Println(str4)
}

3.6 基本数据类型默认值

  在 go 中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值,go中的默认值又叫零值

数据类型 默认值
整型 0
浮点型 0
字符型 “ “
布尔类型 false
package main

import "fmt"

func main() {
	var a int
	var b float32
	var c float64
	var isMarried bool
	var name string

	// %v表示按照变量的值输出
	fmt.Printf("a = %d,b = %v,c = %v,isMarried = %v,name = %v", a, b, c, isMarried, name)
}

4 基本数据类型的转换

  Golang 和 java / c 不同,Go 在不同类型的变量之间赋值时需要显式转换。也就是说 Golang 中数据类型不能自动转换

4.1 基本语法

  表达式 T(v) 将值 v 转换为类型 T

  T:就是数据类型,比如 int32、int64、float32 等等

  v:就是需要转换的变量

package main

import "fmt"

func main() {
	var i int32 = 100

	var n1 float32 = float32(i) // 将 i => float
	var n2 int8 = int8(i)
	var n3 int64 = int64(i)
	fmt.Printf("i = %v n1 = %v n2 = %v n3 = %v", i, n1, n2, n3)
}

4.2 相互转换的细节

  1.Go 中,数据类型的转换可以是从 表示范围小–>表示范围大,也可以 范围大—>范围小

  2.被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化!

package main

import "fmt"

func main() {
	var i int32 = 100

	var n1 float32 = float32(i) // 将 i => float
	var n2 int8 = int8(i)
	var n3 int64 = int64(i)
	fmt.Printf("i = %v n1 = %v n2 = %v n3 = %v \n", i, n1, n2, n3)
	fmt.Printf("i type is %T\n", i)	
}

  3.在转换中,比如将 int64 转成 int8 【-128—127】 ,编译时不会报错,只是转换的结果按溢出处理,与希望的结果不一样, 因此转换时需要考虑范围

package main

import "fmt"

func main() {
	var num1 int64 = 99999
	var num2 int8 = int8(num1)
	fmt.Println("num2 =", num2)
}

4.3 基本数据类型转string

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

  函数解释:

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

   2.fmt.Sprintf().. 会返回转换后的字符串

package main

import (
	"fmt"
	_ "unsafe"
)

func main() {
	var num1 int = 90
	var num2 float64 = 23.467
	var b bool = true
	var myChar byte = 'h'
	var str string // 空的str
    
	str = fmt.Sprintf("%d", num1)  // 整数类型转string
	fmt.Printf("str type %T str = %q\n", str, str)  

	str = fmt.Sprintf("%f", num2)  // 浮点类型转string
	fmt.Printf("str type %T str = %q\n", str, str)

	str = fmt.Sprintf("%t", b)    // 布尔类型转string
	fmt.Printf("str type %T str = %q\n", str, str)

	str = fmt.Sprintf("%c", myChar)  // 字符类型转string
	fmt.Printf("str type %T str = %q\n", str, str)
}

  方式 2:使用 strconv 包的函数

package main

import (
	"fmt"
	"strconv"
	_ "unsafe"
)

func main() {
	var num3 int = 987
	var numStr int64 = 9654
	var num4 float64 = 34.789
	var b2 bool = false

	str = strconv.FormatInt(int64(num3), 10)
	fmt.Printf("str type %T str = %q\n", str, str)
	
    str = strconv.Itoa(int(numStr))
	fmt.Printf("str type %T str = %q\n", str, str)

	// 'f':格式   10:表示小数位保留10位    64:表示这个小数是float64
	str = strconv.FormatFloat(num4, 'f', 10, 64)
	fmt.Printf("str type %T str = %q\n", str, str)

	str = strconv.FormatBool(b2)
	fmt.Printf("str type %T str = %q\n", str, str)
}

4.4 string转基本数据类型

  方式:使用 strconv 包的函数

func main() {
	var str string = "true"
	var b bool
	// b, _ = strconv.ParseBool(str)
	// 说明
	// 1.strconv.ParseBool(str) 函数会返回两个值 (value bool, err error)
	// 2.只想获取到value、bool,不想获取err,所以使用_忽略

	b, _ = strconv.ParseBool(str)
	fmt.Printf("b type %T b = %v\n", b, b)

	var str2 string = "476589098"
	var n1 int64
	var n2 int
	n1, _ = strconv.ParseInt(str2, 10, 64)
	n2 = int(n1)
	fmt.Printf("n1 type %T n1 = %v\n", n1, n1)
	fmt.Printf("n2 type %T n2 = %v\n", n2, n2)

	var str3 string = "345.890"
	var f1 float64
	f1, _ = strconv.ParseFloat(str3, 64)
	fmt.Printf("f1 type %T f1 = %v\n", f1, f1)
}

注明:因为返回的是int64或float64,如果希望得到int32、float32等可按如下处理:

func main() {
	var str3 string = "345.890"
	var f1 float64
	var f2 float32
	f1, _ = strconv.ParseFloat(str3, 64)

	fmt.Printf("f1 type %T f1 = %v\n", f1, f1)
	f2 = float32(f1)
	fmt.Printf("f2 type %T f2 = %v\n", f2, f2)
}

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

  在将 String 转成 基本数据类型时,要确保 String 类型能够转成有效的数据,比如可以把 “123” , 转成一个整数,但是不能把 “hello” 转成一个整数,如果这样做,Golang 直接将其转成 0 ,其它类型也是一样的道理:float => 0、bool => false

func main() {
	// string转基本数据类型的注意事项
	var str4 string = "Hello"
	var n3 int64
	n3, _ = strconv.ParseInt(str4, 10, 64)    // 如果没有转成功,n3 = 0   默认值
	fmt.Printf("n3 type %T n3 = %v\n", n3, n3)
}
// n3 type int64 n3 = 0

5 派生/复杂数据类型

5.1 指针

5.1.1 指针的概念

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

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

   基本数据类型在内存的布局:

package main

import "fmt"
func main() {
	var i int = 10
	// i的地址是什么,&i
	fmt.Println("i的地址 = ", &i)
}

  3.指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:var ptr *int = &num

   指针在内存布局的说明:

  4.获取指针类型所指向的值,使用:*,比如:var ptr *int, 使用 *ptr 获取 ptr 指向的值

func main() {
	// 下面的 var ptr *int = &i
	// 1. ptr 是一个指针变量
	// 2. ptr 的类型 *int
	// 3. ptr 本身的值&i
	var ptr *int = &i
	fmt.Printf("ptr = %v\n", ptr)
	fmt.Printf("ptr的地址 = %v\n", &ptr)
	fmt.Printf("ptr指向的值 = %v", *
}

5.1.2 指针案例演示

1.写一个程序,获取一个 int 变量 num 的地址,并显示到终端

package main

import "fmt"

func main() {
	var num int = 9
	fmt.Printf("num address = %v", &num)
}

2.将 num 的地址赋给指针 ptr , 并通过 ptr 去修改 num 的值

package main

import "fmt"

func main() {
	var num int = 9
	fmt.Printf("num address = %v\n", &num)

	var ptr *int
	ptr = &num
	*ptr = 10 // 这里修改时,会影响到num的值的变化
	fmt.Println("num = ", num)
}

5.1.3 指针使用细节

  1.值类型都有对应的指针类型, 形式为 *数据类型,比如 int 的对应的指针就是 *int、float32对应的指针类型就是 *float32,……

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

5.2 值类型和引用类型

5.2.1 常见的值类型和引用类型

  1.值类型:基本数据类型 int 系列、float 系列、bool、string、数组和结构体(struct)

  2.引用类型:指针、slice切片、map、管道chan、interface 等

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

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

   示意图:

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

   示意图:

   内存中栈区和堆区示意图:

6 标识符与系统保留关键字

6.1 标识符命名规范

6.1.1 标识符概念

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

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

6.1.2 标识符命名规则

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

  2.数字不可以开头

  3.Golang中严格区分大小写

   var num int、var Num int,在 golang 中,num 和 Num 是两个不同的变量

  4.标识符不能包含空格

  5.下划线”_”本身在 Go 中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用

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

6.1.3 标识符命名细节

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

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

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

   注明:可以简单的理解成,首字母大写是公开的,首字母小写是私有的,在 golang 没有public、private等关键字

package main

import (
	"fmt"
	// 为了使用utils.go文件的变量或函数,需要先引入该model包
	"go_code/chapter03/DataType/demo10/model"
)

func main() {
	// 使用utils.go的heroName   包名.标识符
	// fmt.Println(model.heroName)  // 小写时会报错,小写的只能在本包使用,不可跨包使用
	fmt.Println(model.HeroName)
}

6.2 系统保留关键字

  在Go中,为了简化代码编译过程中对代码的解析,其定义的保留关键字只有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

6.3 预定义标识符

  除了保留关键字外,Go还提供了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

7 运算符

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

  Golang中的运算符主要有:

   算术运算符、关系运算符(比较运算符)、逻辑运算符、赋值运算符、位运算符、其它运算符

7.1 算术运算符

  算术运算符是对数值类型的变量进行运算的,比如:加减乘除。在 Go 程序中使用的非常多

运算符 运算 范例 结果
+ 正号 +3 3
- 负号 -4 -4
+ 5 + 5 10
- 6 - 4 2
* 3 * 4 12
/ 5 / 5 1
% 取模(取余) 7 % 5 2
++ 自增 a=2 a++ a=3
自减 a=2 a– a=1
+ 字符串相加 “He” + “llo” “Hello”

7.1.1 /(除法)的使用

func main() {
	// 如果运算的数都是整数,那么除了后,去掉小数部分,保留整数部分
	fmt.Println(10 / 4)

	var n1 float32 = 10 / 4
	fmt.Println(n1)

	// 如果希望保留小数部分,则需要有浮点数参与运算
	var n2 float32 = 10.0 / 4
	fmt.Println(n2)
}

7.1.2 %(取模)的使用

  公式:a % b = a - a / b * b

func main() {
	// 公式 a % b = a - a / b * b
	fmt.Println("10 % 3 =", 10%3)      // 1
	fmt.Println("-10 % 3 =", -10%3)    // -1
	fmt.Println("10 % -3 =", 10%-3)    // 1
	fmt.Println("-10 % -3 =", -10%-3)  // -1
}

7.1.3 ++与–的使用

func main() {	
	var i int = 19
	i++                   // 等价于 i = i + 1
	fmt.Println("i =", i) // 20

	i--                   // 等价于 i = i - 1
	fmt.Println("i =", i) // 19
}

7.1.4 算术运算符使用细节

  1.对于除号 “/“,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:x := 19/5 ,结果是 3

  2.当对一个数取模时,可以等价:a % b = a - a / b * b,这是取模的本质运算

  3.Golang的自增自减只能当做一个独立语言使用,不能这样使用:b := a++或b := a–

func main() {
	var i int = 8
	var a int
	// a = i++  // 错误,i++只能独立使用
	// a = i--  // 错误,i--只能独立使用

	/*
		if i++ > 0 {
			fmt.Println("ok")
		}
	*/
}

  4.Golang 的++ 和 – 只能写在变量的后面,不能写在变量的前面,即:只有 a++、a–,没有++a、–a

func main() {
	var i int = 90
	i++
	++i // 错误,在Golang中没有 前++
	i--
	--i // 错误,在Golang中没有 前--
}

  5.Golang 的设计者去掉了c / java 中自增自减容易混淆的写法,让 Golang 更加简洁,统一

7.2 关系运算符(比较运算符)

  1.关系运算符的结果都是bool型,即只有true、false两种结果

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

运算符 运算 范例 结果
== 相等于 4 == 3 false
!= 不等于 4 != 3 true
< 小于 4 < 3 false
> 大于 4 > 3 true
<= 小于等于 4 <= 3 false
>= 大于等于 4 >= 3 true

7.2.1 关系运算符的使用

func main() {
	// 关系运算符的使用
	var n1 int = 9
	var n2 int = 8
	fmt.Println(n1 == n2)
	fmt.Println(n1 != n2)
	fmt.Println(n1 > n2)
	fmt.Println(n1 >= n2)
	fmt.Println(n1 < n2)
	fmt.Println(n1 <= n2)
	flag := n1 > n2
	fmt.Println("flag = ", flag)
}

7.2.2 关系运算符使用细节

  1.关系运算符的结果都是 bool 型,即只有true、false两种结果

  2.关系运算符组成的表达式称为关系表达式

  3.比较运算符”==”不能误写成 “=”,”==”代表判断,”=”代表赋值

7.3 逻辑运算符

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

  假设A值为True,B值为False

运算符 描述 实例
&& 逻辑与运算符
如果两边的操作数都是True,则为True,否则为False
(A && B)为False
|| 逻辑或运算符
如果两边的操作数有一个True,则为True,否则为False
(A || B)为True
! 逻辑非运算符
如果条件为True,则逻辑为False,否则为True
!(A && B)为True

短路与和短路或:

  1.&&也叫短路与:如果第一个条件为 false,则第二个条件不会判断,最终结果为 false

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

func test() bool {
	fmt.Println("test...")
	return true
}

func main() {
	var i int = 10
	// 短路与
	// 因为 i < 9 为 false ,因此后面的test()就不执行
	if i < 9 && test() {
		fmt.Println("ok...")
	}

    // 逻辑或
	// 因为 i > 9 为 true ,因此后面的test()就不执行
	if i > 9 || test() {
		fmt.Println("Hello...")
	}

}

7.4 赋值运算符

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

运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 C = A + B,将 A + B表达式的结果赋值给C
+= 相加后再赋值 C += A 等于 C = C + A
-= 相减后再赋值 C -= A 等于 C = C - A
*= 相乘后再赋值 C *= A 等于 C = C * A
/= 相除后再赋值 C /= A 等于 C = C / A
%= 求余后再赋值 C %= A 等于 C = C % A
<<= 左移后赋值 C <<= 2 等于 C = C << 2
>>= 右移后赋值 C >>= 2 等于 C = C >> 2
&= 按位与后赋值 C &= 2 等于 C = C & 2
^= 按位异或后赋值 C ^= 2 等于 C = C ^ 2
|= 按位或后赋值 C |= 2 等于 C = C | 2
func main() {
	// 赋值运算符的使用
	// var i int
	// i = 10    // 基本赋值

	// 有个变量,a和b,要求将其进行交换。最终打印结果
	// a = 9 , b = 2   ===> a = 2 , b = 9
	a := 9
	b := 2
	fmt.Printf("交换前的情况是:a = %v,b = %v \n", a, b)

	// 定义一个临时变量
	t := a
	a = b
	b = t
	fmt.Printf("交换后的情况是:a = %v,b = %v \n", a, b)

	// 复合赋值的操作
	a += 17
	fmt.Println("a =", a)

}

赋值运算符的特点:

  1.运算顺序从右往左

func main() {
	var c int
	c = a + 3 // 赋值运算的执行顺序是从右向左
	fmt.Println("c = ", c)	
}

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

func test() int {
	return 90
}

func main() {
	// 表达式:任何有值的都可以看做表达式
	var d int
	d = a           //
	d = 8 + 2*8     // =的右边是表达式
	d = test() + 98 // =的右边是表达式
	d = 876         // 876是常量
	fmt.Println(d)
}

  3.复合赋值运算符等价于下面的效果

   a += 3 等价于 a = a + 3

赋值运算符的面试题:

  有两个变量,a 和 b,要求将其进行交换,但是不允许使用中间变量,最终打印结果

// 有两个变量,a 和 b,要求将其进行交换,但是不允许使用中间变量,最终打印结果
func main() {
	var a int = 15
	var b int = 10

	a = a + b
	b = a - b // b = a + b - b   ==>   b = a
	a = a - b // a = a + b - a   ==>   a = b
	fmt.Printf("a = %v,b = %v", a, b)
}

7.5 位运算符和移位运算符

运算符 描述
& 按位与运算符”&”是双目运算符,其功能是参与运算的两数各对应的二进位相与。
运算规则是: 同时为1,结果为1,否则为0
| 按位或运算符”|”是双目运算符,其功能是参与运算的两数各对应的二进位相或
运算规则是:有一个为1,结果为1,否则为0
^ 按位异或运算符”^”是双目运算符,其功能是参与运算的两数各对应的二进位相异或
运算规则是:当二进位不同时,结果为1,否则为0
<< 左移运算符”<<”是双目运算符,其功能是把”<<”左边的运算数的各二进位全部左移若干位,高位丢弃,低位补0
左移n位就是乘以2的n次方
>> 右移运算符”>>”是双目运算符,其功能是把”>>”左边的运算数的各二进位全部右移若干位
右移n位就是除以2的n次方

  Golang 中有 3 个位运算符:

  分别是:按位与&、按位或|、按位异或^,它们的运算规则是:

   按位与&:两位全为1,结果为 1,否则为 0

   按位或| : 两位有一个为 1,结果为 1,否则为 0

   按位异或 ^ : 两位一个为 0,一个为 1,结果为 1,否则为 0

func main() {
	// 位运算
	fmt.Println("2&3的结果是:", 2&3)   // 2
	fmt.Println("2|3的结果是:", 2|3)   // 3
	fmt.Println("2^3的结果是:", 2^3)   // 1
	fmt.Println("-2^2的结果是:", -2^2) // -4
}

  Golang中有 2 个移位运算符:

func main() {
	a := 1 >> 2 // 补码 0000 0001 ==> 0000 0000 = 0
	fmt.Println(a)
	c := 1 << 2 // 补码 0000 0001 ==> 0000 0100 => 4
	fmt.Println(c)
}

7.6 其它运算符

运算符 描述 实例
& 返回变量存储地址 &a;将给出变量的实际地址
* 指针变量 *a;是一个指针变量

func main() {

	// 演示&和*的使用
	a := 100
	fmt.Println("a的地址 =", &a)

	var ptr *int = &a
	fmt.Println("ptr指向的值是:", *ptr)
}

  在Golang 中实现三元运算的效果:

func main() {
    var n int
	var i int = 10
	var j int = 12
	// 传统的三元运算 n = i > j ? i : j
	// Golang不支持三元运算,Golang的写法如下:
	if i > j {
		n = i
	} else {
		n = j
	}
	fmt.Println("n =", n)
}

7.7 运算符优先级

  下表中的优先级从下到上依次升高:

分类 描述 关联性
后缀 ()、[]、->、.、++、– 左到右
单目 + - ! ~ (type) * & sizeof 右到左
乘法 * / % 左到右
加法 + - 左到右
移位 << >> 左到右
关系 < <= > >= 左到右
相等(关系) == != 左到右
按位AND & 左到右
按位XOR ^ 左到右
按位OR | 左到右
逻辑AND && 左到右
逻辑OR || 左到右
赋值运算符 = += -= *= /= %= >>= <<= &= ^= |= 右到左
逗号 , 左到右

  1.运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序。如上表,上一行运算符总优先于下一行。

  2.只有单目运算符、赋值运算符是从右向左运算的。

  3.大致的优先级顺序整理:

   1:括号,++, –

   2: 单目运算

   3:算术运算符

   4:移位运算

   5:关系运算符

   6:位运算符

   7:逻辑运算符

   8:赋值运算符

   9:逗号

8 键盘输入语句

8.1 介绍

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

8.2 使用步骤

  1.导入 fmt 包

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

func Scanln
语法:func Scanln(a ...interface{}) (n int, err error)
说明:Scanln类似Scan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置。

func Scanf 
语法:func Scanf(format string, a ...interface{}) (n int, err error)
说明:Scanf从标准输入扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。返回成功扫描的条目个数和遇到的任何错误。

fmt.Scanln()的方式:

func main() {
	// 可以从控制台接收用户信息,【姓名,年龄,薪水, 是否通过考试 】
	// 方式1   fmt.Scanln()
	// 先声明需要的变量
	var name string
	var age byte
	var sal float32
	var isPass bool
	fmt.Println("请输入姓名:")
	// 当程序执行到 fmt.Scanln(&name),程序会停止,等待用户输入,并回车
	fmt.Scanln(&name)

	fmt.Println("请输入年龄:")
	fmt.Scanln(&age)

	fmt.Println("请输入薪水:")
	fmt.Scanln(&sal)

	fmt.Println("请输入是否通过考试:")
	fmt.Scanln(&isPass)

	fmt.Printf("名字是 %v \n 年龄是%v \n 薪水是 %v \n 是否通过考试 %v \n", name, age, sal, isPass)
}

fmt.Scanf()的方式:

func main() {
	// 可以从控制台接收用户信息,【姓名,年龄,薪水, 是否通过考试 】
	// 方式2   fmt.Scanf(),可按照指定的格式输入
	// 先声明需要的变量
	var name string
	var age byte
	var sal float32
	var isPass bool

	fmt.Println("请输入你的姓名,年龄,薪水,是否通过考试,使用空格隔开")
	fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
	fmt.Printf("名字是 %v \n 年龄是%v \n 薪水是 %v \n 是否通过考试 %v \n", name, age, sal, isPass)
}

9 进制

  对于整数,有四种表示方式:

  1) 二进制:0、1,满 2 进 1

    golang中不能直接使用二进制来表示一个整数,它沿用了 C 的特点

  2) 十进制:0-9,满 10 进 1

  3) 八进制:0-7,满 8 进 1,以数字0开头表示

  4) 十六进制:0-9 及 A-F,满 16 进 1,以0x 或 0X开头表示

   此处的 A-F 不区分大小写,如:0x21AF +1= 0X21B0

func main() {
	var i int = 5

	// 二进制输出
	fmt.Printf("%b \n", i)

	// 八进制:0-7,满 8 进 1,以数字0开头表示
	var j int = 011 // 011 => 9
	fmt.Println("j =", j)

	// 十六进制:0-9 及 A-F,满 16 进 1,以0x 或 0X开头表示
	var k int = 0x11 // 0X11 => 16 + 1 = 17
	fmt.Println("k =", k)
}
十进制 十六进制 八进制 二进制
0 0 0 0
1 1 1 1
2 2 2 10
3 3 3 11
4 4 4 100
5 5 5 101
6 6 6 110
7 7 7 111
8 8 10 1000
9 9 11 1001
10 A 12 1010
11 B 13 1011
12 C 14 1100
13 D 15 1101
14 E 16 1110
15 F 17 1111
16 10 20 10000
17 11 21 10001

9.1 其它进制转十进制

9.1.1 二进制转十进制

  规则:从最低位开始(右边的),将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和

// 请将二进制:1011转成十进制的数
1011 = 1 * 1 + 1 * 2 + 0 * 2 * 2 + 1 * 2 * 2 * 2 = 1 + 2 + 0 + 8 = 11

func binaryToDecimal(binary string) int {
	decimal := 0
	length := len(binary)

	for i := 0; i < length; i++ {
		if binary[i] == '1' {
			decimal += int(math.Pow(2, float64(length-1-i)))
		}
	}

	return decimal
}

func main() {
	binary := "1011"
	fmt.Printf("二进制%s转换为十进制是: %d\n", binary, binaryToDecimal(binary))
}

9.1.2 八进制转十进制

  规则:从最低位开始(右边的),将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和

// 请将八进制:0123转成十进制的数
0123 = 3 * 1 + 2 * 8 + 1 * 8 * 8 = 3 + 16 + 64 = 83

func main() {
	// 八进制:0-7,满 8 进 1,以数字0开头表示
	var m int = 0123 // 0123 => 83
	fmt.Println("m =", m)
}

9.1.3 十六进制转十进制

  规则:从最低位开始(右边的),将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和

// 请将:0X34A转成十进制的数
0X34A = 10 * 1 + 4 * 16 + 3 * 16 * 16 = 10 + 64 + 768 = 842

func main() {
	// 十六进制:0-9 及 A-F,满 16 进 1,以0x 或 0X开头表示
	var n int = 0x34A  // 0X34A => 842
	fmt.Println("n =", n)
}

9.2 十进制转其它进制

9.2.1 十进制转二进制

  规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制

// 请将56转成二进制  ==> 111000
func main() {
	var i int = 56
	// 二进制输出
	fmt.Printf("%b \n", i)
}

9.2.2 十进制转八进制

  规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的八进制

// 请将156转成八进制    ==> 0234
func main() {
	var f = 156
	fmt.Printf("八进制表示为:%o \n", f)    
}

9.2.3 十进制转十六进制

  规则:将该数不断除以16,直到商为0为止,然后将每步得到的余数倒过来,就是对应的十六进制

// 请将356转成十六进制  =>  0x164
func main() {
	
	decimalNum := 356  // 十进制数
    
	hexStr := strconv.FormatInt(int64(decimalNum), 16)   // 转换为十六进制字符串(不带0x前缀)
	fmt.Println("不带前缀的十六进制:", hexStr) // 输出: ff

	hexStrWithPrefix := fmt.Sprintf("0x%x", decimalNum)   // 转换为带0x前缀的十六进制字符串
	fmt.Println("带前缀的十六进制:", hexStrWithPrefix) // 输出: 0xff
}

9.3 二进制转其他进制

9.3.1 二进制转八进制

  规则:将二进制数每三位一组(从低位开始组合),转成对应的八进制数即可

// 请将二进制:11010101转成八进制  ==> 0325
11 010 101 = 0 3 2 5

func binaryToOctal(binaryStr string) (string, error) {
	// 将二进制字符串转换为十进制整数
	decimal, err := strconv.ParseInt(binaryStr, 2, 64)
	if err != nil {
		return "", err
	}

	// 将十进制整数转换为八进制字符串
	return fmt.Sprintf("%o", decimal), nil
}

func main() {
	binary := "11010101" // 示例二进制数
	octal, err := binaryToOctal(binary)
	if err != nil {
		fmt.Println("转换错误:", err)
		return
	}
	fmt.Printf("二进制: %s 转换为八进制是: %s\n", binary, octal)
}

9.3.2 二进制转十六进制

  规则:将二进制数每四位一组(从低位开始组合),转成对应的八进制数即可

// 请将二进制:11010101转成十六进制  ==> 0xD5
func main() {
	// 二进制字符串
	binaryStr := "11010101"

	// 将二进制字符串转换为十进制整数
	decimal, err := strconv.ParseInt(binaryStr, 2, 64)
	if err != nil {
		fmt.Println("二进制转换错误:", err)
		return
	}

	// 带0x前缀的十六进制输出
	withPrefix := fmt.Sprintf("0x%X", decimal)

	// 不带前缀的十六进制输出
	withoutPrefix := fmt.Sprintf("%X", decimal)

	fmt.Println("带前缀:", withPrefix)
	fmt.Println("不带前缀:", withoutPrefix)
}

9.4 其他进制转二进制

9.4.1 八进制转二进制

  规则:将八进制数每1位转成对应的一个3位的二进制数即可

// 请将八进制:0237转成二进制  ==> 10011111
0237 = 10 011 111
func octalToBinary(octalStr string) (string, error) {
	// 将八进制字符串转换为十进制整数
	decimal, err := strconv.ParseInt(octalStr, 8, 64)
	if err != nil {
		return "", err
	}

	// 将十进制整数转换为二进制字符串
	binaryStr := strconv.FormatInt(decimal, 2)

	return binaryStr, nil
}

func main() {
	octal := "0237"
	binary, err := octalToBinary(octal)
	if err != nil {
		fmt.Println("转换错误:", err)
		return
	}
	fmt.Printf("八进制:%s 转换为二进制是: %s\n", octal, binary)
}

9.4.2 十六进制转二进制

  规则:将十六进制数每1位转成对应的一个4位的二进制数即可

// 请将十六进制:0x237转成二进制  ==> 1000110111
0x237 = 10 0011 0111
func hexToBinary(hexStr string) (string, error) {
	// 将十六进制字符串转换为十进制数值
	decimal, err := strconv.ParseInt(hexStr, 16, 64)
	if err != nil {
		return "", err
	}

	// 将十进制数值转换为二进制字符串
	binaryStr := strconv.FormatInt(decimal, 2)
	return binaryStr, nil
}

func main() {
	hex := "237"
	binary, err := hexToBinary(hex)
	if err != nil {
		fmt.Println("转换错误:", err)
		return
	}
	fmt.Printf("十六进制 %s 的二进制表示为: %s\n", hex, binary)
}

9.5 二进制在运算中的说明

  二进制是逢 2 进位的进位制,0、1 是基本算符。

  现代电子计算机技术全部采用的是二进制,因为它只使用 0、1 两个数字符号,非常简单方便,易于用电子方式实现,计算机内部处理的信息都是采用二进制数来表示的。

  二进制(Binary)数用 0和 1 两个数字及其组合来表示任何数。进位规则是“逢 2 进 1”,数字 1 在不同的位上代表不同的值,按从右至左的次序,这个值以二倍递增。
  总结:在计算机的内部运行各种运算时,都是以二进制的方式来运行

9.6 原码、反码、补码

  原码、反码、补码的解释:

  对于有符号的而言:

  1.二进制的最高位是符号位:0表示正数、1表示负数

1 ===> [0000 0001]
-1 ===> [1000 0001]

  2.正数的原码,反码,补码都一样

  3.负数的反码=它的原码符号位不变,其它位取反(0 -> 1、1 -> 0)

1 ===> 原码[0000 0001]   反码[0000 0001]   补码[0000 0001]
-1 ===> 原码[1000 0001]   反码[1111 1110]   补码[1111 1111]

  4.负数的补码 = 它的反码 + 1

  5.0的反码、补码都是0

  6.在计算机运算的时候,都是以补码的方式来运算的

   计算机中计算:1 - 1 实际是计算:1 + (-1)

10 程序流程控制

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

  顺序控制:程序从上到下逐行地执行,中间没有任何判断和跳转

  分支控制(if-else):让程序有选择的执行,分支控制有三种。

   1.单分支

   2.双分支

   3.多分支

10.1 顺序控制

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

// 1) 假如还有 97 天放假,问:xx 个星期零 xx 天
// 2) 定义一个变量保存华氏温度,华氏温度转换摄氏温度的公式为:5/9*(华氏温度-100),请求出华氏温度对应的摄氏温度

package main

import "fmt"

func main() {
	var days int = 97
	var week int = days / 7
	var day int = days % 7
	fmt.Printf("%d个星期零%d天\n", week, day)

	var huashi float32 = 134.2
	var sheshi float32 = 5.0 / 9 * (huashi - 100)
	fmt.Printf("当华氏温度 = %v(℉)时,对应的摄氏温度 = %v(°C)", huashi, sheshi)

}

10.2 单分支控制语句

10.2.1 基本语法

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

  说明:当条件表达式为true时,就会执行{ }的代码,在Golang中{ }是必须有的

func main() {
	// 编写一个程序,可以输入人的年龄,如果该同志的年龄大于 18 岁,则输出 "你年龄大 于 18,要对自己的行为负责!"
	// 需求---[分析]---->代码
	// 1.年龄  ==> var age int
	// 2.从控制台接收一个输入 fmt.Scanln(&age)
	// 3.if判断
	var age int
	fmt.Println("请输入年龄:")
	fmt.Scanln(&age)
	if age > 18 {
		fmt.Println("你的年龄大于18,要对自己的行为负责!")
	}
}

10.2.2 单分支的流程图和细节

流程图:

  流程图可以用图形方式更加清晰的描述程序执行的流程。

细节说明:

  Golang的 if 还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了

func main() {
	// 编写一个程序,可以输入人的年龄,如果该同志的年龄大于 18 岁,则输出 "你年龄大 于 18,要对自己的行为负责!"
	// Golang支持在if中直接定义一个变量,比如下面:
	if age := 20; age > 18 {
		fmt.Println("你的年龄大于18,要对自己的行为负责!")
	}
}

10.3 双分支控制语句

10.3.1 基本语法

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

  说明:当条件表达式成立,即执行代码块1,否则执行代码块2,在Golang中{ }是必须有的

func main() {
	// 编写一个程序,可以输入人的年龄,如果该同志的年龄大于 18 岁,则输出
	//“你年龄大于 18,要对 自己的行为负责!”。否则 ,输出”你的年龄不大这次放过你了.”
	// 思路分析
	// 1. 年龄 ===> var age int
	// 2. fmt.Scanln()接收
	// 3.if --- else
	var age int
	fmt.Println("请输入年龄:")
	fmt.Scanln(&age)

	if age > 18 {
		fmt.Println("你年龄大于18,要对自己的行为负责!")
	} else {
		fmt.Println("你的年龄不大这次放过你了!")
	}
}

10.3.2 双分支的流程图和总结

流程图:

  流程图可以用图形方式更加清晰的描述程序执行的流程。

双分支的总结:

  1.从上图看:条件表达式就是 age >18

  2.执行代码块 1 ===> fmt.Println(“你的年龄大于 18”) ..

  3.执行代码块 2 ===> fmt.Println(“你的年龄不大….”) .

  4.双分支只会执行其中的一个分支

func isLeapYear(year int) bool {
	if year%4 != 0 {
		return false
	} else if year%100 != 0 {
		return true
	} else {
		return year%400 == 0
	}
}

func main() {
	year := 2024
	if isLeapYear(year) {
		fmt.Printf("%d年是闰年\n", year)
	} else {
		fmt.Printf("%d年不是闰年\n", year)
	}
}

10.4 多分支控制语句

10.4.1 基本语法

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

语法说明:

  1.多分支的判断流程如下:

   (1) 先判断条件表达式 1 是否成立,如果为真,就执行代码块 1

   (2) 如果条件表达式 1 为假,就去判断条件表达式 2 是否成立, 如果条件表达式 2 为真,就执行代码块 2,依次类推……

   (4) 如果所有的条件表达式不成立,则执行 else 的语句块

  2.else 不是必须的

  3.多分支只能有一个执行入口

10.4.2 多分支的流程图

func main() {
	//岳小鹏参加 Golang 考试,他和父亲岳不群达成承诺:
	//如果:
	//成绩为 100 分时,奖励一辆BMW;
	//成绩为(80,99]时,奖励一部iphone7plus;
	//当成绩为[60,80]时,奖励一部iPad;
	//其它时,什么奖励也没有。
	//请从键盘输入岳小鹏的期末成绩,并加以判断
	var score int
	fmt.Println("请输入成绩:")
	fmt.Scanln(&score)

	// 多分支判断
	if score == 100 {
		fmt.Println("奖励一辆BMW")
	} else if score > 80 && score <= 99 {
		fmt.Println("奖励一部iphone7plus")
	} else if score >= 60 && score <= 80 {
		fmt.Println("奖励一部iPad")
	} else {
		fmt.Println("什么都不奖励!")
	}

	// 使用陷阱  ---   只会输出ok1
	var n int = 10
	if n > 9 {
		fmt.Println("ok1")
	} else if n > 6 {
		fmt.Println("ok2")
	} else if n > 3 {
		fmt.Println("ok3")
	} else {
		fmt.Println("ok4")
	}

}

10.5 嵌套分支

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

10.5.1 基本语法

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

  说明:嵌套分支不宜过多,应控制在3层以内

10.5.2 应用案例

  1.参加百米运动会,如果用时 8 秒以内进入决赛,否则提示淘汰。并且根据性别提示进入男子组或女子组。

func main() {
	// 分析思路:
	// 1.定义一个变量,接收跑步使用的秒数 float64
	// 2.定义一个变量,接收性别 string
	// 3.因为判断是嵌套的判断,所以使用嵌套分支
	var second float64

	fmt.Println("请输入秒数:")
	fmt.Scanln(&second)

	if second <= 8 {
		// 进入决赛
		var gender string
		fmt.Println("请输入性别:")
		fmt.Scanln(&gender)
		if gender == "男" {
			fmt.Println("进入决赛的男子组!")
		} else {
			fmt.Println("进入决赛的女子组!")
		}
	} else {
		fmt.Println("您已被淘汰...")
	}
}

  2.出票系统:根据淡旺季的月份和年龄,打印票价
  4_10 旺季:
   成人(18-60):60
   儿童(<18):半价
   老人(>60): 1/3
  淡季:
   成人:40
   其他:20

// 分析思路: 1.month、age两个变量   2.使用嵌套分支
func main() {
	var month byte
	var age byte
	var price float64 = 60.0
	fmt.Println("请输入旅游的月份:")
	fmt.Scanln(&month)

	fmt.Println("请输入游客的年龄:")
	fmt.Scanln(&age)

	if month >= 4 && month <= 10 {
		if age > 60 {
			fmt.Printf("%v月%v岁的票价: %v", month, age, price/3)
		} else if age >= 18 {
			fmt.Printf("%v月%v岁的票价: %v", month, age, price)
		} else {
			fmt.Printf("%v月%v岁的票价: %v", month, age, price/2)
		}

	} else {
		if age >= 18 && age < 60 {
			fmt.Printf("淡季的成人的票价是: %v", price*2/3)
		} else {
			fmt.Printf("淡季的老人、儿童的票价是: %v", price/3)
		}
	}
}

10.6 switch分支结构

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

  2.Golang中匹配项后面不需要再加break

10.6.1 基本语法

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

10.6.2 switch流程图

  流程图的总结:

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

  2.如果 switch 的表达式的值没有和任何的 case 的表达式匹配成功,则执行 default 的语句块。执行后退出 switch 的控制

  3.Golang 的 case 后的表达式可以有多个,使用 逗号 间隔

  4.Golang 中的 case 语句块不需要写 break , 因为默认会有,即在默认情况下,当程序执行完 case 语句块后,就直接退出该 switch 控制结构

// 请编写一个程序,该程序可以接收一个字符,比如: a,b,c,d,e,f,g a表示星期一,b表示星期二 … 根据用户的输入显示相应的信息
// 分析思路:
// 1.定义一个变量接收字符
// 2.使用switch完成
func main() {
	var key byte
	fmt.Println("请输入一个字符 a,b,c,d,e,f,g:")
	fmt.Scanf("%c", &key)

	switch key {
	case 'a':
		fmt.Println("星期一")
	case 'b':
		fmt.Println("星期二")
	case 'c':
		fmt.Println("星期三")
	case 'd':
		fmt.Println("星期四")
	case 'e':
		fmt.Println("星期五")
	case 'f':
		fmt.Println("星期六")
	case 'g':
		fmt.Println("星期日")
	default:
		fmt.Println("输入有误...")
	}
}

10.6.3 switch使用细节

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

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

var n1 int32 = 10
var n2 int64 = 20
switch n1 {
	case n2:      // 错误,原因是n2的数据类型和n1不一致
		fmt.Println("ok1")
	default:
		fmt.Println("没有匹配到")
}

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

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

var n1 int32 = 10
var n2 int32 = 20
switch n1 {
	case n2, 10, 5:
		fmt.Println("ok1")
	case 5:                // 错误,因为前面已有常量5,因此重复,就会报错
		fmt.Println("ok2")  
	default:
		fmt.Println("没有匹配到")
}

  5.case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行 default

  6.default语句不是必须的

  7.switch 后也可以不带表达式,类似 if –else 分支来使用

func main() {
	// switch 后也可以不带表达式,类似 if --else 分支来使用
	var age int = 10
	switch {
	case age == 10:
		fmt.Println("age == 10")
	case age == 20:
		fmt.Println("age == 20")
	default:
		fmt.Println("没有匹配到")
	}

	// case中也可以对范围进行判断
	var score int = 30
	switch {
	case score > 90:
		fmt.Println("成绩优秀...")

	case score >= 70 && score <= 90:
		fmt.Println("成绩优良...")

	case score >= 60 && score < 70:
		fmt.Println("成绩及格...")

	default:
		fmt.Println("不及格")
	}
}

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

func main() {	
	// switch 后也可以直接声明/定义一个变量,分号结束,不推荐
	switch grade := 90; {
	case grade > 90:
		fmt.Println("成绩优秀...")

	case grade >= 70 && grade <= 90:
		fmt.Println("成绩优良...")

	case grade >= 60 && grade < 70:
		fmt.Println("成绩及格...")

	default:
		fmt.Println("不及格")
	}
}

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

func main() {
	var num int = 10
	switch num {
	case 10:
		fmt.Println("ok1")
		fallthrough   // 默认只能穿透一层
	case 20:
		fmt.Println("ok2")
	case 30:
		fmt.Println("ok3")
	default:
		fmt.Println("没有匹配到..")
	}
}
// 输出 ok1 ok2

  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 型")

	case func(int) float64:
		fmt.Printf("x是func(int)型")

	case bool, string:
		fmt.Printf("x是bool 或 string 型")

	default:
		fmt.Printf("未知型")
	}
}

10.7 switch 和 if 的比较

  1.如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型,使用 swtich语句,简洁高效

  2.对区间判断结果为 bool 类型的判断,使用 if,if 的使用范围更广

10.8 for循环控制

10.8.1 基本语法

for 循环变量初始化;循环条件;循环变量迭代 {
    循环操作(语句)
}

语法格式说明:

  对 for 循环来说,有四个要素:

   1.循环变量初始化

   2.循环条件

   3.循环变量迭代

   4.循环操作(语句) ,有人也叫循环体

for 循环执行的顺序说明:

  1.执行循环变量初始化,比如 i := 1

  2.执行循环条件, 比如 i <= 10

  3.如果循环条件为真,就执行循环操作:比如 fmt.Println(“….”)

  4.执行循环变量迭代 , 比如 i++

  5.反复执行 2, 3, 4 步骤,直到循环条件为 False ,就退出 for 循环

10.8.2 for循环执行流程分析

  for循环流程图:

  根据代码分析for循环的执行过程:

10.8.3 for循环使用细节

  1.循环条件是返回一个布尔值的表达式

  2.for循环的第二种使用方式:将变量初始化变量迭代写到其它位置

for 循环判断条件 {
      // 循环执行语句
}

// 代码示例
func main() {
	i := 1        // 循环变量初始化
	for i <= 10 { // 循环条件
		fmt.Println("你好,尚硅谷", i)
		i++ // 循环变量迭代
	}
}

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

for {
      //循环执行语句
}

// 代码示例
package main

import "fmt"

func main() {

	k := 1
	for {
		if k <= 10 {
			fmt.Println("你好,北京", k)
		} else {
			break // break就是跳出for循环
		}
		k++
	}
}

  上面的写法等价 for ; ; {} 是一个无限循环, 通常需要配合break语句使用

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

   for-range在遍历字符串时,是按照字符来遍历的,而不是按照字节来的

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

func main() {
	// 字符串遍历方式1---传统方式
	var str string = "Hello,World!"
	for i := 0; i < len(str); i++ {
		fmt.Printf("%c \n", str[i])
	}
}

  字符串遍历方式2:for-range

func main() {
	// 字符串遍历方式2---for-range
	str = "abc~ok!"
	for index, val := range str {
		fmt.Printf("index = %d,val = %c \n", index, val)
	}
}

  上面代码的细节讨论:
  1.如果字符串含有中文,那么传统的遍历字符串方式,就是错误,会出现乱码。原因是传统的对字符串的遍历是按照字节来遍历,而一个汉字在 utf8 编码是对应 3 个字节。

  解决方案:需要将 str 转成 [ ]rune 切片

func main() {
	var charStr string = "Hello,World!你好,中国!"
	Str := []rune(charStr) // 把charStr转成 [ ]rune
	for i := 0; i < len(Str); i++ {
		fmt.Printf("%c \n", Str[i])
	}
}

  2.对for-range 遍历方式而言,是按照字符方式遍历。因此如果有字符串有中文,也是可以的

func main() {
	// 字符串遍历方式2---for-range
	str = "abc~ok!你好,成都!"
	for index, val := range str {
		fmt.Printf("index = %d,val = %c \n", index, val)
	}
}

10.9 多重循环控制

  1.将一个循环放在另一个循环体内,就形成了嵌套循环,外边的 for 称为外层循环,在里面的 for循环称为内层循环

   一般使用两层,最多不要超过 3 层

  2.实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为 false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。

  3.外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m * n 次

  多重循环控制的案例:

  1.统计 3 个班成绩情况,每个班有5名同学,求出各个班的平均分、所有班级的平均分、三个班的及格人数[学生的成绩从键盘输入]

func main() {
	// 分析思路1:
	// 1.统计1个班成绩情况,每个班有5名同学,求出该班的平均分[学生的成绩从键盘输入]  ==>先易后难
	// 2.学生数就是5个 ==>先死后活
	// 3.声明一个stuSum  统计班级总分

	// 分析思路2:
	// 1.统计3个班成绩情况,每个班有5名同学,求出每个班的平均分[学生的成绩从键盘输入]
	// 2.j 表示第几个班级
	// 3.定义一个变量存放总成绩

	// 分析思路3:
	// 1.定义两个变量,表示班级的个数和班级的人数

	// 分析思路
	// 1.声明一个变量   passCount 用于保存及格人数
	var classNum int = 2
	var stuNum int = 2

	var totalSum float64 = 0.0
	var passCount int = 0
	for j := 1; j <= classNum; j++ {
		stuSum := 0.0
		for i := 1; i <= stuNum; i++ {
			var stuScore float64
			fmt.Printf("请输入第%d个班第%d个学生的成绩: \n", j, i)
			fmt.Scanln(&stuScore)

			// 累计总分
			stuSum += stuScore

			// 判断分数是否及格
			if stuScore >= 60 {
				passCount++
			}
		}
		fmt.Printf("第%d个班级的平均分是%v \n", j, stuSum/float64(stuNum))

		// 将各个班级的总成绩累计到totalSum
		totalSum += stuSum

	}
	fmt.Printf("各个班级的总成绩是%v 所有班级平均分是%v \n", totalSum, totalSum/float64(stuNum*classNum))
	fmt.Printf("及格人数为%v \n", passCount)

}

  2.打印空心金字塔

func main() {
	// 使用for循环完成下面的案例:
	// 编写一个程序,可以接收一个整数,表示层数,打印空心金字塔

	// 编程思路:
	// 1.打印一个矩形
	/*

	 ***
	 ***
	 ***

	 */

	// 2.打印半个金字塔
	/*

	 *
	 **
	 ***

	 */

	// 3.打印整个金字塔
	/*

		  *       1层 1个*   2 * 层数 - 1   空格  2  规律:总层数-当前层数
		 ***      2层 3个*   2 * 层数 - 1   空格  1  规律:总层数-当前层数
		*****     3层 5个*   2 * 层数 - 1   空格  0  规律:总层数-当前层数

	*/

	// 4.将层数做成一个变量  totalLevel
	var totalLevel int = 9

	// 5.打印空心金字塔
	/*

	    *
	   * *
	  *****
	   分析:在给每行打印*时,需要考虑是打印*还是打印空格
	   分析的结果:每层的第一个和最后一个打印*,其他就应该是空的,即输出空格
	   分析结果中的例外情况:最后层(底层)全部打*

	*/
	// i 表示层数
	for i := 1; i <= totalLevel; i++ {
		// 在打印*之前先打印空格
		for k := 1; k <= totalLevel-i; k++ {
			fmt.Print(" ")
		}
		// j表示每层打印多少个*
		for j := 1; j <= 2*i-1; j++ {
			if j == 1 || j == 2*i-1 || i == totalLevel {
				fmt.Print("*")
			} else {
				fmt.Print(" ")
			}
		}
		fmt.Println()
	}

}

  3.打印空心菱形

func main() {
	n := 5 // 菱形高度的一半

	// 打印上半部分
	for i := 1; i <= n; i++ {
		for j := 1; j <= n-i; j++ {
			fmt.Print(" ")
		}
		for k := 1; k <= 2*i-1; k++ {
			if k == 1 || k == 2*i-1 {
				fmt.Print("*")
			} else {
				fmt.Print(" ")
			}
		}
		fmt.Println()
	}

	// 打印下半部分
	for i := n - 1; i >= 1; i-- {
		for j := 1; j <= n-i; j++ {
			fmt.Print(" ")
		}
		for k := 1; k <= 2*i-1; k++ {
			if k == 1 || k == 2*i-1 {
				fmt.Print("*")
			} else {
				fmt.Print(" ")
			}
		}
		fmt.Println()
	}
}

10.10 跳转控制语句-break

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

package main

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

func main() {
	rand.Seed(time.Now().UnixNano())
	var sumCount int = 0
	for {
		n := rand.Intn(100) + 1
		sumCount++
		fmt.Printf("第%d次生成:%d\n", sumCount, n)
		if n == 99 {
			break // 表示跳出for循环
		}
	}
	fmt.Printf("共用了%d次才生成99\n", sumCount)

}

10.10.1 语法与示意图

{
    ......
    break
    ......
}

  以for循环使用break为例,画出示意图:

10.10.2 break使用细节

  break 语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块

func main() {
lable2:
	for i := 0; i < 4; i++ {
		// lable1: // 设置一个标签
		for j := 0; j < 10; j++ {
			if j == 2 {
				// break   // break默认会跳出最近的for循环
				// break lable1
				break lable2 // j=0   j=1
			}
			fmt.Println("j =", j)
		}
	}
}

  说明:

   1.break 默认会跳出最近的 for 循环

   2.break 后面可以指定标签,跳出标签对应的 for 循环

  案例:实现登录验证,有三次机会,如果用户名为”张无忌” ,密码”888”提示登录成功,否则提示还有几次机会

func main() {
	var maxAttempts int = 3
	var correctUsername string = "张无忌"
	var correctPassword string = "888"

	var username, password string
	for i := 1; i <= maxAttempts; i++ {
		fmt.Println("请输入用户名: ")
		fmt.Scanln(&username)
		fmt.Println("请输入密码: ")
		fmt.Scanln(&password)

		if username == correctUsername && password == correctPassword {
			fmt.Println("登录成功!")
			break
		}

		if i >= maxAttempts {
			fmt.Println("尝试次数已用完,登录失败")
			break
		}

		fmt.Printf("用户名或密码错误,您还有%d次机会\n", maxAttempts-i)
	}
}

10.11 跳转控制语句-continue

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

  2.continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环 , 与break 标签的使用的规则一样

10.11.1 基本语法与示意图

{
    ......
    continue
    ......
}

  以for循环使用continue为例,画出示意图:

10.11.2 continue使用示例

func main() {
	for i := 0; i < 4; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				continue
			}
			fmt.Println("j =", j)
		}
	}
}

10.12 跳转控制语句-goto

  1.Go 语言的 goto 语句可以无条件地转移到程序中指定的行

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

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

10.12.1 goto的语法与流程图

goto label
...
label:statement

10.12.2 goto使用示例

func main() {
	var n int = 10
	// goto的使用
	fmt.Println("ok1")
	if n > 20 {
		goto label1
	}
	fmt.Println("ok2")
	fmt.Println("ok3")
	fmt.Println("ok4")
label1:
	fmt.Println("ok5")
	fmt.Println("ok6")
	fmt.Println("ok7")
}

10.13 跳转控制语句-return

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

func main() {
	for i := 1; i <= 10; i++ {
		if i == 3 {
			return
		}
		fmt.Println("你好,中国", i)
	}
	fmt.Println("Hello,World!")
}

说明:

  1.如果 return 是在普通的函数,则表示跳出该函数,即不再执行函数中 return 后面代码,也可以理解成终止函数

  2.如果 return 是在 main 函数,表示终止 main 函数,也就是说终止程序

11 函数、包和错误处理

11.1 为什么需要函数

  需求:输入两个数,再输入一个运算符(+,-,*,/),得到结果

  1.使用传统的方法解决

func main() {
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '+'
	var res float64
	switch operator {
	case '+':

		res = n1 + n2
	case '-':
		res = n1 - n2
	case '*':

		res = n1 * n2
	case '/':
		res = n1 / n2
	default:
		fmt.Println("操作符号有误...")

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

分析一下上面代码问题:

  1.上面的写法是可以完成功能, 但是代码冗余

  2.同时不利于代码维护

  3.函数可以解决这个问题

11.2 函数基本概念与语法

  为完成某一功能的程序指令(语句)的集合,称为函数,在 Go 中,函数分为: 自定义函数、系统函数(查看Go编程手册)

func 函数名(形参列表) (返回值类型列表) {
    执行语句...
    return 返回值列表
}

  1.形参列表:表示函数的输入

  2.函数中的语句:表示为了实现某一功能代码块

  3.函数可以有返回值,也可以没有

11.3 函数的使用示例

// 将计算的功能,放到一个函数中,在需要时调用即可
func calculate(n1 float64, n2 float64, operator byte) float64 {
	var res float64
	switch operator {
	case '+':
		res = n1 + n2

	case '-':
		res = n1 - n2

	case '*':
		res = n1 * n2

	case '/':
		res = n1 / n2

	default:
		fmt.Println("操作符号有误...")

	}
	return res

}

func main() {
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '+'
	calculate_result := calculate(n1, n2, operator)        // 调用函数
	fmt.Println("calculate_result =", calculate_result)
}

11.4 包的引出

  1.实际开发中往往需要在不同的文件中,去调用其它文件定义的函数,比如 main.go中使用 utils.go 文件中的函数,如何实现?===> 包

  2.现在有两个程序员共同开发一个 Go 项目,他们定义函数都希望以Cal命名,两个程序员为此还吵了起来,怎么办? ===> 包

11.5 包的原理图

  包的本质实际上就是创建不同的文件夹,来存放程序文件。

  画图说明一下包的原理:

11.6 包的概念、作用、说明

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

  包的三大作用:

   1.区分相同名字的函数、变量等标识符

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

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

  包的相关说明:

// 打包基本语法
   package 包名
// 引入包的基本语法
   import "包的路径"

文章作者: 罗宇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 罗宇 !
  目录