Go语言的条件判断和循环语句

news2024/11/24 16:44:07

目录

【if语句】

if语句的内部变量

if语句的优雅写法

【switch语句】

switch语句的特点

switch语句的表达式类型

switch获取变量类型 x.(type)

【for语句】

for语句的变体

for...range

break 和 continue

goto

for 语句的常见“坑”与避坑方法


Go语言的条件判断有 if 和 switch-case 两种形式;循环结构只有 for 这一种形式。

  • Go 中的if、switch、fot语句不需要使用小括号包裹
  • Go 坚持 “一件事情仅有一种做法的理念”,只保留了 for 这一种循环结构,去掉了传统语言中的 while 和 do-while 循环结构;
  • Go 中 switch 分支结构中每个 case 语句不需要以 break 收尾,并且支持 fallthrough 穿透到后面的分支;
  • Go 支持了 type switch 特性,让“类型”信息也可以作为分支选择的条件;
  • Go 的 switch 控制结构的 case 语句还支持表达式列表,让相同处理逻辑的多个分支可以合并为一个分支。

【if语句】

操作符优先级决定了操作数优先参与哪个操作符的求值运算,优先级如下所示

a, b := false, true
//下面的if语句实际上不是 (a && b) != true,而是 a && (b != true)
if a && b != true {
	println("return true")
	return
}
println("return false") //输出 return false,因为 != 的优先级高于 &&

因此为了防止混淆优先级,记得使用小括号把想要优先运算的部分括起来。 

if语句的内部变量

可以在 if 的布尔表达式前声明属于if语句自己的变量,这些变量只可以在 if 语句的代码块范围内使用。

//下面的 diff 只能在 if ... else 块里面使用
yesterday := 27
today := 30
if diff := today - yesterday; diff > 0 {
	fmt.Printf("今天气温比昨天升高了: %d\n", diff)
} else {
	fmt.Printf("今天气温比昨天降低了: %d\n", diff)
}
//fmt.Printf(diff) //此处报错:undefined: diff

上面这种写法一般用在判断一个函数的返回值是否正确,看下面的例子:

package main

import "fmt"

func main() {
	if username, err := getUserName(); err == nil {
		fmt.Println("username: ", username)
	} else {
		fmt.Println("get username error")
	}
}

func getUserName() (string, error) {
	return "zhangsan", nil
}

if语句的优雅写法

一般不推荐层层嵌套的复杂冗长的if语句块,而是建议遇到false的情况及时return,能提高代码的阅读效果,如下所示:

package main

import "fmt"

func main() {
	//if语句的优雅写法
	score := 80
	ifDemo1(score)
	ifDemo2(score)
}

func ifDemo1(score int) {
	if score >= 0 && score < 60 {
		fmt.Println("未及格")
	} else if score >= 60 && score < 80 {
		fmt.Println("及格了但不够优秀")
	} else if score >= 80 && score < 95 {
		fmt.Println("比较优秀但还有上升空间")
	} else if score >= 95 && score <= 100 {
		fmt.Println("三好学生")
	} else {
		fmt.Println("分数有问题")
	}
}

func ifDemo2(score int) {
	if score >= 0 && score < 60 {
		fmt.Println("未及格")
		return
	}
	if score >= 60 && score < 80 {
		fmt.Println("及格了但不够优秀")
		return
	}
	if score >= 80 && score < 95 {
		fmt.Println("比较优秀但还有上升空间")
		return
	}
	if score >= 95 && score <= 100 {
		fmt.Println("三好学生")
		return
	}
	fmt.Println("分数有问题")
}

很明显,上面的 ifDemo2() 的可读性更高,尤其是在if语句里面又嵌套了一层if语句的时候,这种改造效果会更好。很多优秀的设计模式都是对复杂的条件判断进行的优化。

【switch语句】

先用switch语句改造一下上面的代码:

score := 70
switch score {
case 10:
	fmt.Println("未及格10")
case 20:
	fmt.Println("未及格20")
	break
    fmt.Println("Unreachable code") //当前分支的 break后面的语句 不会再执行
case 60, 70:
	fmt.Println("及格了60/70")
	fallthrough
case 80, 90:
	fmt.Println("比较优秀80/90")
default:
	fmt.Println("分数有问题")
}
/*
	输出:
	及格了60/70
	比较优秀80/90
*/

switch语句的特点

从上面的例子中可以得到Go语言中的switch有如下特点: 

  • 匹配到复合条件的分之后直接返回,不需要添加 break;(如果加了break,当前分支的 break后面的语句 不会再执行,没必要这么写)
  • 使用 fallthrough 可以穿透当前分支,但是如果某个 case 语句已经是最后一个 case 并且它的后面也没有 default 分支了,那么这个 case 中就不能再使用 fallthrough
  • 每个 case 后面可以写多个值
  • 无论 default 分支出现在什么位置,它都只会在所有 case 都没有匹配上的情况下才会被执行的。
  • 建议将匹配成功概率高的 case 表达式排在前面,就会提升 switch 语句执行效率。

针对上面的例子,如果score是75则不能匹配。在Go语言中,switch可以使用类似if语句的范围判断:

score := 75
switch {
case score >= 0 && score < 60:
	fmt.Println("未及格")
case score >= 60 && score < 80:
	fmt.Println("及格了")
case score >= 80 && score < 95:
	fmt.Println("比较优秀")
case score >= 95 && score <= 100:
	fmt.Println("三好学生")
default:
	fmt.Println("分数有问题")
}
//输出:及格了

switch语句的表达式类型

switch 语句中表达式类型可以是:整型、布尔类型、字符串类型、复数类型、数组类型、结构体类型。

type person struct {
	name string
	age  int
}
p := person{
	"tom", 13,
}

switch p {
case person{"tony", 33}:
	println("match tony")
case person{"tom", 13}:
	println("match tom")
case person{"lucy", 23}:
	println("match lucy")
default:
	println("no match")
}
//输出: match tom

switch language := "golang"; language {
case "php":
	fmt.Println("PHP语言。。")
case "golang":
	fmt.Println("Go语言")
case "java":
	fmt.Println("Java语言")
}
//输出: Go语言

switch获取变量类型 x.(type)

package main

import "fmt"

func main() {
	x := "hello"
	fmt.Println(getVariableType(x)) //the type of v is string, v = hello
}

func getVariableType(x interface{}) string {
	switch v := x.(type) { //此处的 v 存储的是变量 x 的动态类型对应的值信息
	case nil:
		return "v is nil"
	case int:
		return fmt.Sprintf("the type of v is int, v = %v", v)
	case string:
		return fmt.Sprintf("the type of v is string, v = %v", v)
	case bool:
		return fmt.Sprintf("the type of v is bool, v = %v", v)
	default:
		return fmt.Sprintf("don't support the type")
	}
}

Go 中所有类型都实现了 interface{}类型,所以 case 后面可以是任意类型信息。

【for语句】

Go 语言的 for 循环支持声明多个变量,并且可以应用在循环体以及判断条件中

//for循环中声明多个变量
var sum int
for i, j, k := 0, 1, 2; (i < 20) && (j < 10) && (k < 30); i, j, k = i+1, j+1, k+5 {
	sum += i + j + k
	fmt.Print(sum, " ") //3 13 30 54 85 123
}

for语句的变体

//省略前置语句
i := 0
for ; i < 10; i++ {
	i++
}
//省略后置语句
for i := 0; i < 10; {
	i++
}
//省略前置和后置语句
for i < 10 {
	println(i)
	i++
}
//全都省略,类似于PHP的 while 循环
m := 0
for {
	println("死循环")
	m++
	if m >= 3 {
		break
	}
}

for...range

在 for range 语句中,range 后面接受的表达式的类型可以是数组、数组的指针、切片、字符串、map、channel(注意:要对 map 进行循环操作,for range 是唯一的方法。关于map的更多用法请参考:Go语言中array、slice、map的用法和细节分析_浮尘笔记的博客-CSDN博客)

var map1 = map[string]int{
	"zhangsan": 18,
	"lisi":     22,
	"wangwu":   15,
}
for k, v := range map1 {
	fmt.Print(k, "=>", v, "; ") //lisi=>22; wangwu=>15; zhangsan=>18;
}

for...range也有几种变体,如下所示:

//如果不关心元素的值,可以省略元素值的变量,只声明下标
for i := range map1 {
	fmt.Print(i, " ") //zhangsan lisi wangwu
}

//如果不关心元素下标,只关心元素值,可以用空标识符替代下标
for _, v := range map1 {
	fmt.Print(v, " ") //18 22 15
}

//如果既不关心下标值也不关心元素值,只是循环它的次数
for _, _ = range map1 {
	fmt.Print("A", " ") //A A A
}

//或者可以直接这样写
for range map1 {
	fmt.Print("B", " ") //B B B
}

使用 for...range 循环字符串的注意事项:for range 对于 string 类型来说,每次循环得到的 v 值是一个 Unicode 字符码点(也就是 rune 类型值),而不是一个字节,返回的第一个值 i 为该 Unicode 字符码点的内存编码(UTF-8)的第一个字节在字符串内存序列中的位置。

// 使用 for...range 遍历英文和中文字符串
var str1 = "hello"
for k, v := range str1 {
	fmt.Printf("%d => %c ,", k, v) //0 => h ,1 => e ,2 => l ,3 => l ,4 => o ,
}
println()

var str2 = "你好呀"
for k, v := range str2 {
	fmt.Printf("%d => %c ,", k, v)                    //0 => 你 ,3 => 好 ,6 => 呀 ,
	fmt.Printf("%d -> %s -> 0x%x, ", k, string(v), v) //0 -> 你 -> 0x4f60, 3 -> 好 -> 0x597d, 6 -> 呀 -> 0x5440,
}

break 和 continue

break用于跳出所有循环,continue用于跳出当前循环,并且都可以指定标签,看下面的例子:

//break用法
for i := 1; i <= 3; i++ {
	for j := 1; j <= 3; j++ {
		if j == 2 {
			break
		}
		fmt.Printf("i:%d,j:%d\n", i, j)
	}
}

/*输出
i:1,j:1
i:2,j:1
i:3,j:1
*/

//break 加上标签
out:
	for i := 1; i <= 3; i++ {
		for j := 1; j <= 3; j++ {
			if j == 2 {
				break out
			}
			fmt.Printf("i:%d,j:%d\n", i, j)
		}
	}

/*输出
i:1,j:1
*/

//continue用法
for i := 1; i <= 5; i++ {
	for j := 1; j <= 5; j++ {
		if j == 2 {
			continue
		}
		fmt.Printf("i:%d,j:%d\n", i, j)
	}
}

/*输出
i:1,j:1
i:1,j:3
i:2,j:1
i:2,j:3
i:3,j:1
i:3,j:3
*/

//continue 加上标签
out:
	for i := 1; i <= 3; i++ {
		for j := 1; j <= 3; j++ {
			if j == 2 {
				continue out
			}
			fmt.Printf("i:%d,j:%d\n", i, j)
		}
	}
/*输出
i:1,j:1
i:2,j:1
i:3,j:1
*/

goto

	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			fmt.Printf("i:%d,j:%d\n", i, j)
			if j == 2 {
				goto breakHere
			}
			fmt.Println("这里的内容不会跳过")
		}
		fmt.Println("这里的内容会跳过1")
	}
	fmt.Println("这里的内容会跳过2")
	fmt.Println("一直到breakHere标记之间的内容都会跳过")

breakHere:
	fmt.Println("直接跳到这里来了")

/*输出
i:0,j:0
这里的内容不会跳过
i:0,j:1
这里的内容不会跳过
i:0,j:2
直接跳到这里来了
*/

for 语句的常见“坑”与避坑方法

(1)在goroutine中使用for循环的问题

//问题一:对一个整型切片进行遍历,并且在每次循环体的迭代中都会创建一个新的Goroutine,输出这次迭代的元素的下标值与元素值。
var m = []int{1, 2, 3, 4, 5}
for i, v := range m {
	go func() {
		fmt.Print(i, "=>", v, ",")
	}()
}
time.Sleep(time.Second * 1)
fmt.Println()
//预期结果: 0=>1,1=>2,2=>3,3=>4,4=>5, (以为每次迭代都会重新声明两个新的变量 i 和 v)
//实际结果: 4=>5,4=>5,4=>5,4=>5,4=>5, (这些循环变量在 for range 语句中仅会被声明一次,且在每次迭代中都会被重用)

问题分析:Goroutine 执行的闭包函数引用了它的外层包裹函数中的变量 i、v,这样变量 i、v 在主Goroutine 和新启动的 Goroutine 之间实现了共享, 而 i, v 值在整个循环过程中是重用的,仅有一份。在 for range 循环结束后,i = 4, v = 5,因此各个 Goroutine 输出的是 i, v 的最终值。解决办法如下:

//为闭包函数增加参数,并且在创建 Goroutine 时将参数与 i、v 的当时值进行绑定
for i, v := range m {
	go func(i, v int) {
		fmt.Print(i, "=>", v, ",")
	}(i, v)
}
time.Sleep(time.Second * 1)
fmt.Println()
//执行结果:4=>5,1=>2,3=>4,2=>3,0=>1, (备注:每次执行的顺序可能不一致,这是由 Goroutine 的调度所决定的)

(2)问题二:在 for...range 循环中参与循环的是range的副本

var a = [5]int{1, 2, 3, 4, 5}
var r [5]int
fmt.Println("原始的a=", a) // [1 2 3 4 5]
for i, v := range a {
	if i == 0 {
		a[1] = 12
		a[2] = 13
	}
	r[i] = v
}
fmt.Println("循环后a =", a) // [1 12 13 4 5]
fmt.Println("循环后r =", r) // [1 2 3 4 5]
fmt.Println()

//修改一种方式,用切片 a[:] 替代原先的数组a
fmt.Println("原始的a=", a) // [1 2 3 4 5]
for i, v := range a[:] {
	if i == 0 {
		a[1] = 12
		a[2] = 13
	}
	r[i] = v
}
fmt.Println("循环后a =", a) // [1 12 13 4 5]
fmt.Println("循环后r =", r) // [1 12 13 4 5]

(3)问题三:由于遍历 map 中元素的随机性:如果在循环的过程中,对map进行了修改,那么这样修改的结果是否会影响后续迭代?

var map1 = map[string]int{
	"A": 21,
	"B": 22,
	"C": 23,
}
point := 0
for k, v := range map1 {
	if point == 0 { //当 point 值为 0 时删除map中的一个元素
		delete(map1, "A")
	}
	point++
	fmt.Print(k, "=>", v, ",")
}
fmt.Println("point is ", point)

//反复运行这个例子多次,可能会得到两个不同的结果:
//当 k="A" 是第0个元素出现时会得到如下结果:A=>21,B=>22,C=>23,point is  3
//当 k="A" 不是第0个元素出现时会得到如下结果:B=>22,C=>23,point is  2

//如果针对 map 类型的循环体中新创建了元素,那这项元素可能出现在后续循环中也可能不出现
var map1 = map[string]int{
	"A": 21,
	"B": 22,
	"C": 23,
}
point := 0
for k, v := range map1 {
	if point == 0 { //当 point 值为 0 时 给 map中新增一个元素
		map1["D"] = 24
	}
	point++
	fmt.Print(k, "=>", v, ",")
}
fmt.Println("point is ", point)
//反复运行这个例子,执行结果也会有两种情况
//A=>21,B=>22,C=>23,D=>24,point is  4
//或者 A=>21,B=>22,C=>23,point is  3

 

考虑到上述这种随机性,在遇到遍历 map 的时候同时需要对 map 修改的场景要格外小心。

源代码:go-demo-2023: Go语言基本用法和实用笔记 - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/535086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数组map用法以及特殊值的情况

数组map用法以及特殊值的情况 一、map用法的说明 map(callbackFn, thisArg); // callbackFn回调函数&#xff0c;thisArg可选 callbackFn是个回调函数&#xff0c;该回调函数的参数按照顺序为element&#xff08;当前正在处理的元素&#xff09;&#xff0c;index&#xff0…

深化企业数据智能应用 用友敢当“急先锋”

面对扑面而来的数字经济时代&#xff0c;一场轰轰烈烈的企业数智化转型正进行得如火如荼。 然而许多企业虽然明知道数智化转型势在必行&#xff0c;但是又担忧自己不具备相关能力。这些企业在数据和智能上面临哪些挑战&#xff1f;如何才能如何加速数智化创新&#xff1f;AIGC和…

人机融合智能与哲学

GPT系列的大型语言模型&#xff08;LLM&#xff09;在初步成功之后&#xff0c;需要人们重新审视图灵的计算理论&#xff0c;重新认识计算的本质和形式&#xff0c;重新思考计算机和计算机理论&#xff0c;以及深入思考计算的家族、广义的计算和计算的哲学等问题。这是因为GPT系…

从面对代码下不去的文章,到DBA群讨论

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

小米加速技术突破,为充电生态赋能,领航未来

5月13日&#xff0c;中国电工技术学会电力电子专业委员会第十八届学术年会在上海召开。小米公司作为快充技术领域代表&#xff0c;手机部基带总监杨玉巍出席本次年会并发表以《小米下一个十年的“助推器”——硬件技术创新与应用》为主题的报告&#xff0c;介绍小米最新的充电技…

简单做一下 银川第九届数模A题

A题 随着三年新冠疫情结束后第一个五一假期的到来&#xff0c;许多人选择出门旅游&#xff0c;在有限的几天假期怎样玩好就是一件值得考虑的事。小明是一位旅游爱好者&#xff0c;想在五一期间到宁夏一些著名景点旅游。由于跟着旅游团会受到若干限制&#xff0c;所以他&#xf…

Google Bard 对战 ChatGPT4

话题之一&#xff1a;如何降低血压 我家老爷子血压有点高&#xff0c;所以我挑了这么个话题。 如果用中文来问 Bard, 有点欺负它&#xff0c;那么索性用英文 1. Bard 在速度上占有绝对优势 2. GPT4 在最后一条监测就医建议上&#xff0c;完胜。 很多老人得过且过&#xff0c;不…

OpenCV实战(24)——相机姿态估计

OpenCV实战&#xff08;24&#xff09;——相机姿态估计 0. 前言1. 相机姿态估计2. 3D 可视化模块 cv::Viz3. 完整代码小结系列链接 0. 前言 校准相机后&#xff0c;就可以将捕获的图像与物理世界联系起来。如果物体的 3D 结构是已知的&#xff0c;那么就可以预测物体如何投影…

【Shell脚本】Linux安装Nexus的两种方式以及开机自启

目录 一、Linux安装Nexus的两种方式1、直接把下载好的安装包上传到服务器①、打开Nexus页面后&#xff0c;登录时会出现以下提示&#xff0c;根据路径提示可找到初始密码②、找到初始登录Nexus的初始密码 2、通过wget安装Nexus①、修改Nexus端口号②、默认的端口号为8081&#…

华为OD机试真题 Java 实现【找数字】【2023Q2 100分】

一、题目描述 给一个二维数组nums&#xff0c;对于每一个元素nums[i]&#xff0c;找出距离最近的且值相等的元素&#xff0c;输出横纵坐标差值的绝对值之和&#xff0c;如果没有等值元素&#xff0c;则输出-1。 例如 输入数组nums为 0 3 5 4 2 2 5 7 8 3 2 5 4 2 4 对于 n…

Java每日一练(20230517) 重复元素、链表重复元素、旋转数组

目录 1. 存在重复元素 &#x1f31f; 2. 删除排序链表中的重复元素 &#x1f31f; 3. 旋转数组 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 存在重…

让孩子们零基础也能学习人工智能,这家科技企业是这样做的

在偏远地区的孩子&#xff0c;即便没有任何人工智能知识和理论基础&#xff0c;也可以一步步迈入人工智能科技的殿堂&#xff1f; 你没有看错&#xff0c;这就是亚马逊云科技推出的“AI在未来”公益计划项目&#xff0c;如今已经进入了第二个学年。 “AI在未来”公益计划走进宁…

案例分享|地弹现象导致DCDC电源芯片工作不正常

很多读者都应该听过地弹&#xff0c;但是实际遇到的地弹的问题应该很少。本案例就是一个DCDC电源芯片的案例。 1. 问题描述 如下图1 &#xff0c;产品其中一个供电是12V转3.3V的电路&#xff0c;产品发货50K左右以后&#xff0c;大约有1%的产品无法启动&#xff0c;经过解耦定…

【Java8新特性】史上最全Optional实战教程,太厉害了!

目录 一、前置基础 二、什么是Optional 2.1理论拓展 三、为什么要用Optional 3.1俄罗斯式套娃判空详解 四、Optional基本知识 4.1API的思考 五、工作中如何正确使用Optional 5.1 orElseThrow 5.2 filter 5.3 orElse和orElseGet 5.4 map和flatMap 5.5 项目实战 实战…

16位单片机去哪儿了?

关注星标公众号&#xff0c;不错过精彩内容 作者 | strongerHuang 微信公众号 | strongerHuang 最近网友问了一个问题&#xff1a;为什么现在很少看见16位单片机了&#xff1f; 你是不是也有这样的疑问&#xff1a;现在市面上大多都是32位Arm Coretx-M内核的单片机&#xff0c;…

〖技术人职业规划白宝书 - 职业规划篇①〗- 大学生选择职业前的自我认知与剖析

历时18个月&#xff0c;采访 850 得到的需求。 不管你是在校大学生、研究生、还是在职的小伙伴&#xff0c;该专栏有你想要的职业规划、简历、面试的答案。说明&#xff1a;该文属于 技术人职业规划白宝书 专栏&#xff0c;购买任意白宝书体系化专栏可加入TFS-CLUB 私域社区&am…

【JS】1684- 重学 JavaScript API - Resize Observer API

❝ 前期回顾&#xff1a; 1. Page Visibility API 2. Broadcast Channel API 3. Beacon API ❞ &#x1f3dd; 什么是 Resize Observer API Resize Observer API[1] 可以帮助我们监听元素尺寸的变化&#xff0c;并在尺寸变化时执行一些操作。例如&#xff0c;我们可以使用 Resi…

突发!骨灰级程序员心梗离世!

大家注意&#xff1a;因为微信最近又改了推送机制&#xff0c;经常有小伙伴说错过了之前被删的文章&#xff0c;比如前阵子冒着风险写的爬虫&#xff0c;再比如一些限时福利&#xff0c;错过了就是错过了。 所以建议大家加个星标&#xff0c;就能第一时间收到推送。&#x1f44…

什么是鉴权?这些postman鉴权方式你又知道多少?

一、什么是鉴权&#xff1f; 鉴权也就是身份认证&#xff0c;就是验证您是否有权限从服务器访问或操作相关数据。发送请求时&#xff0c;通常必须包含相应的检验参数以确保请求具有访问权限并返回所需数据。通俗的讲就是一个门禁&#xff0c;您想要进入室内&#xff0c;必须通过…

PostgreSQL 为什么PG 的适用性很强(译)

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…