跨境购物网站建设wordpress防止并发数
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:32
当前位置: 首页 > news >正文
跨境购物网站建设,wordpress防止并发数,电商平台定制,新乡商城网站建设哪家优惠Go语言在变量声明、初始化以及赋值语句上相比其先祖C语言做了一些改进#xff0c;诸如#xff1a; ● 支持在同一行声明和初始化多个变量#xff08;不同类型也可以#xff09; var a, b, c 5, hello, 3.45 a, b, c : 5, hello, 3.45 // 短变量…Go语言在变量声明、初始化以及赋值语句上相比其先祖C语言做了一些改进诸如 ● 支持在同一行声明和初始化多个变量不同类型也可以 var a, b, c 5, hello, 3.45 a, b, c : 5, hello, 3.45 // 短变量声明形式● 支持在同一行对多个变量进行赋值 a, b, c 5, hello, 3.45这种语法糖在给我们带来便利的同时也可能带来一些令人困惑的问题。 Go语言之父Rob Pike在Go语言早期r60版本2011年曾经讲过一门名为“The GoProgramming Language” [1] 的课程虽然距今年代有些久远但该课程仍然是笔者心中的经典强烈推荐Gopher学习一下。 在该门课程第二天 [2] 的内容中Rob Pike出了这样一道练习题下面语句执行完毕后n0和n1的值分别是多少 n0, n1 n0n1, n0或者 n0, n1 op(n0, n1), n0对于这个问题很多Go语言初学者无法给出答案一些Go语言老手虽然能给出正确答案但也说不出个所以然。显然这个问题涉及Go语言的表达式求值顺序evaluationorder。 上面问题中赋值语句中的表达式求值仅仅是表达式求值的众多应用场景中的一个。表达式的求值顺序在任何一门编程语言中都是比较“难缠的”。很多情形下语言规范给出的 答 案 可 能 是“undefined”未 定 义、“not specified”未 明 确 说 明或“implementation-dependent”实现相关。 理解表达式求值顺序的机制对于编写出正确、逻辑清晰的Go代码很有必要因此在这一条中我们一起结合直观的实例来深入理解Go语言的表达式求值顺序。 包级别变量声明语句中的表达式求值顺序 在一个Go包内部包级别变量声明语句的表达式求值顺序是由初始化依赖initialization dependencies规则决定的。那初始化依赖规则是什么呢根据Go语言规范中的说明这里将该规则总结为如下几点。 ● 在Go包中包级别变量的初始化按照变量声明的先后顺序进行。 ● 如果某个变量如变量a的初始化表达式中直接或间接依赖其他变量如变量b那么变量a的初始化顺序排在变量b后面。 ● 未初始化的且不含有对应初始化表达式或初始化表达式不依赖任何未初始化变量的变量我们称之为“ready for initialization”变量。 ● 包级别变量的初始化是逐步进行的每一步就是按照变量声明顺序找到下一个“ready for initialization”变量并对其进行初始化的过程。反复重复这一步骤直到没有“ready for initialization”变量为止。 ● 位于同一包内但不同文件中的变量的声明顺序依赖编译器处理文件的顺序先处理的文件中的变量的声明顺序先于后处理的文件中的所有变量。规则往往抽象难懂例子则更直观易理解。 我们看一个Go语言规范中的例子并使用 上述规则进行分析Go编译器版本1.13 package mainimport fmtvar (a c bb f()c f()d 3 )func f() int {dreturn d } func main() {fmt.Println(a, b, c, d) } 运行结果 9 4 5 5 对于上面的代码不同的包变量初始化顺序会导致变量值不同因此明确四个变量的初始化顺序至关重要。我们结合上面的初始化依赖规则来分析一下该程序执行后的a、b、c、d四个变量的结果值。 1根据规则包级变量初始化按照变量声明先后顺序进行因此每一轮寻找“readyfor initialization”变量的过程都会按照a - b - c - d的顺序依次进行。 2我们先来进行第一轮选择“ready for initialization”变量的过程。我们从变量a开始。变量a的初始化表达式为c b这使得a的初始化依赖b和c而b、c通过函数f间接依赖未初始化变量d因此a并不是“ready for initialization”变量。 3按照声明顺序接下来是b。b的初始化表达式依赖函数f而函数f依赖未初始化变量d因此b也不是“ready for initialization”变量。 4按照声明顺序接下来是c。c的初始化表达式依赖函数f而函数f依赖未初始化变量d因此c也不是“ready for initialization”变量。 5按照声明顺序接下来是d。d没有需要求值的初始化表达式而是直接被赋予了初值因此d是我们第一轮找到的“ready for initialization”变量我们对其进行初始化d 3。当前已初始化变量集合为[d3]。 6接下来进行第二轮“ready for initialization”变量的寻找。我们依然从a开始和第一轮一样b、c依旧是未初始化变量a不符合条件我们继续看b。b依赖函数f函数f依赖d但d已经是已初始化变量集合中的元素了因此b具备了成为“ready forinitialization”的条件于是第二轮我们选出了b并对b进行初始化b d 1 4。 此时已初始化变量集合为[d4, b4]。 7接下来进行第三轮“ready for initialization”变量的寻找。我们依然从a开始和前两轮一样c依旧是未初始化变量a不符合条件我们继续看c。c依赖函数f函数f依赖d但d已经是已初始化变量集合中的元素了因此c具备了成为“ready forinitialization”的条件于是第三轮我们选出了c并对c进行初始化c d 1 5。 此时已初始化变量集合为[d5, b4, c5]。 8接下来进行最后一轮“ready for initialization”变量的寻找。此时只剩下变量a了并且a依赖的b、c都是已初始变量集合中的元素了因此a符合“ready forinitialization”的条件于是最后一轮我们选出a并对a进行初始化a 4 5 9。 此时已初始化变量集合为[d 5, b 4, c 5, a 9]。 9初始化结束根据上述分析程序应该输出9 4 5 5。 如果在包级变量声明中使用了空变量空变量也会得到Go编译器一视同仁的对待。我们看下面的例子 package mainimport fmtvar (a c bb f() f()c f()d 3 )func f() int {dreturn d } func main() {fmt.Println(a,b,c,d) } 有了第一个例子中详细的分析这里我们的分析从简。 1 初 始 化 过 程 按 照 a - b - _ - c - d 的 顺 序 进 行“ready forinitialization”变量的查找。 2第一轮变量a、b、_、c都不符合条件d被选出并初始化已初始化变量集合为[d3]。 3第二轮变量b符合条件被选出并初始化已初始化变量集合为[d4, b4] 4第三轮空变量符合条件被选出并初始化但空变量忽略了初始值这一过程的副作用是使得变量d增加1已初始化变量集合为[d5, b4]。 5第四轮变量c符合条件被选出并初始化已初始化变量集合为[d6, b4, c6]。 6第五轮变量a符合条件被选出并初始化已初始化变量集合为[d6, b4, c6,a10]。 7包变量初始化结束分析输出结果应为10 4 6 6。 还有一种比较特殊的情况值得我们在这里一并分析那就是当多个变量在声明语句左侧且右侧为单一表达式时的表达式求值情况。在这种情况下无论左侧哪个变量被初始化同一行的其他变量也会被一并初始化。 我们来看下面这个例子 package mainimport fmtvar (a cb, c f()d 3 )func f() (int, int) {dreturn d, d 1 } func main() {fmt.Println(a, b, c, d) } 1根据包级变量初始化规则初始化过程将按照a - bc - d顺序进行“ready forinitialization”变量的查找。 2第一轮变量a、b、c都不符合条件d被选出并初始化已初始化变量集合为[d3]。 3第二轮变量b和c一起符合条件以b被选出为例b被初始化的同时c也得到了 初始化因此已初始化变量集合为[d4, b4, c5]。 4第三轮变量a符合条件被选出并初始化已初始化变量集合为[d4, b4, c5,a5]。 5包变量初始化结束分析输出结果应为5 4 5 4。 运行上述代码 5 4 5 4输出结果也与我们分析的一致。 普通求值顺序 除了包级变量由初始化依赖决定的求值顺序Go还定义了普通求值顺序usualorder用于规定表达式操作数中的函数、方法及channel操作的求值顺序。Go规定表达式操作数中的所有函数、方法以及channel操作按照从左到右的次序进行求值。 同样来看一个改编自Go语言规范中的例子 package mainimport fmtfunc f() int {fmt.Println(calling f)return 1 } func g(a, b, c int) int {fmt.Println(calling g)return 2 } func h() int {fmt.Println(calling h)return 3 } func i() int {fmt.Println(calling i)return 1 } func j() int {fmt.Println(calling j)return 1 } func k() bool {fmt.Println(calling k)return true } func main() {var y []int{11, 12, 13}var x []int{21, 22, 23}var c chan int make(chan int)go func() {c - 1}()y[f()], _ g(h(), i()x[j()], -c), k() } y[f()], _ g(h(), i()x[j()], -c), k()这行语句是赋值语句但赋值语句的表 达式操作数中包含函数调用、channel操作。按照普通求值规则这些函数调用、channel操作按从左到右的顺序进行求值。 ● 按照从左到右的顺序先对等号左侧表达式操作数中的函数进行调用求值因此第一个是y[f()]中的f()。 ● 接下来是等号右侧的表达式。第一个函数是g()但g()依赖其参数的求值其参数列表依然可以看成是一个多值赋值操作其涉及的函数调用顺序从左到右依次为h()、i()、j()、-c这样该表达式操作数函数的求值顺序即为h() - i() - j() - c取值操作 -g()。 ● 最后还剩下末尾的k()因此该语句中函数以及channel操作的完整求值顺序是f() -h() - i() - j() - c取值操作 - g() - k()。 例子的实际运行结果如下 calling f calling h calling i calling j calling g calling k输出结果与我们分析的一致。 赋值语句的求值 package mainimport fmtfunc example() {n0, n1 : 1, 2n0, n1 n0n1, n0fmt.Println(n0, n1) } func main() {example() } 这是一个赋值语句。Go语言规定赋值语句求值分为两个阶段、 1第一阶段对于等号左边的下标表达式、指针解引用表达式和等号右边表达式中的操作数按照普通求值规则从左到右进行求值 2第二阶段按从左到右的顺序对变量进行赋值。 根据上述规则我们对这个问题等号两端的表达式的操作数采用从左到右的求值顺序。 假定n0和n1的初值如下n0, n1 1, 2 第一阶段等号两端表达式求值。上述问题中等号左边没有需要求值的下标表达式、指针解引用表达式等只有右端有n0n1和n0两个表达式但表达式的操作数(n0n1) 都是已初始化了的因此直接将值代入得到求值结果。 求值后语句可以看成n0, n1 3, 1。 第二阶段从左到右赋值即n0 3n1 1。 switch/select语句中的表达式求值 上面的三类求值顺序原则已经可以覆盖大部分Go代码中的场景了如果说在表达式求值方面还有值得重点关注的那肯定非switch/select语句中的表达式求值莫属了。 我们先来看switch-case语句中的表达式求值这类求值属于“惰性求值”范畴。惰性求值指的就是需要进行求值时才会对表达值进行求值这样做的目的是让计算机少做事从而降低程序的消耗对性能提升有一定帮助。 package mainimport fmtfunc Expr(n int) int {fmt.Println(n)return n } func main() {switch Expr(2) {case Expr(1), Expr(2), Expr(3):fmt.Println(enter into case1)fallthroughcase Expr(4):fmt.Println(enter into case2)} } 运行结果 2 1 2 enter into case1 enter into case2从例子的输出结果我们看到 1对于switch-case语句而言首先进行求值的是switch后面的表达式Expr(2)这个表达式在求值时输出2。 2接下来将按照从上到下、从左到右的顺序对case语句中的表达式进行求值。如果某个表达式的结果与switch表达式结果一致那么求值停止后面未求值的case表达式将被 忽略。结合上述例子这里对第一个case中的Expr(1)和Expr(2)进行了求值由于Expr(2) 求值结果与switch表达式的一致所以后续Expr(3)并未进行求值。 3fallthrough将执行权直接转移到下一个case执行语句中了略过了case表达式Expr(4)的求值。 我们再来看看select-case语句的求值。Go语言中的select为我们提供了一种在多个channel间实现“多路复用”的机制是编写Go并发程序最常用的并发原语之一。 我们通过一个例子直观看一下select-case语句中表达式的求值规则 package mainimport (fmttime )func getAReadOnlyChannel() -chan int {fmt.Println(invoke getAReadOnlyChannel)c : make(chan int)go func() {time.Sleep(3 * time.Second)c - 1}()return c } func getASlice() *[5]int {fmt.Println(invoke getASlice)var a [5]intreturn a } func getAWriteOnlyChannel() chan- int {fmt.Println(invoke getAWriteOnlyChannel)return make(chan int) } func getANumToChannel() int {fmt.Println(invoke getANumToChannel)return 2 } func main() {select {// 从channel接收数据case (getASlice())[0] -getAReadOnlyChannel():fmt.Println(recv something from a readonly channel)// 将数据发送到channelcase getAWriteOnlyChannel() - getANumToChannel():fmt.Println(send something to a writeonly channel)} } 运行结果 invoke getAReadOnlyChannel invoke getAWriteOnlyChannel invoke getANumToChannel invoke getASlice recv something from a readonly channel 从上述例子可以看出以下两点。 1select执行开始时首先所有case表达式都会被按出现的先后顺序求值一遍。 invoke getAReadOnlyChannel invoke getAWriteOnlyChannel invoke getANumToChannel有一个例外位于case等号左边的从channel接收数据的表达式RecvStmt不会被求值这里对应的是getASlice()。 2如果选择要执行的是一个从channel接收数据的case那么该case等号左边的表达式在接收前才会被求值。比如在上面的例子中在getAReadOnlyChannel创建的goroutine 在3s后向channel中写入一个int值后select选择了第一个case执行此时对等号左侧的 表达式(getASlice())[0]进行求值输出“invoke getASlice”这也算是一种惰性求 值。 表达式本质上就是一个值表达式求值顺序影响着程序的计算结果。 Gopher应牢记以下几点规则。 ● 包级别变量声明语句中的表达式求值顺序由变量的声明顺序和初始化依赖关系决定 并且包级变量表达式求值顺序优先级最高。 ● 表达式操作数中的函数、方法及channel操作按普通求值顺序即从左到右的次序进 行求值。 ● 赋值语句求值分为两个阶段先按照普通求值规则对等号左边的下标表达式、指针解 引用表达式和等号右边的表达式中的操作数进行求值然后按从左到右的顺序对变量 进行赋值。 ● 重点关注switch-case和select-case语句中的表达式“惰性求值”规则。
- 上一篇: 跨境电商知名网站建设文创产品设计创意图片
- 下一篇: 跨境网站开发百度一下首页网页百度
相关文章
-
跨境电商知名网站建设文创产品设计创意图片
跨境电商知名网站建设文创产品设计创意图片
- 技术栈
- 2026年03月21日
-
跨境电商网站建设流程图团购网站建站
跨境电商网站建设流程图团购网站建站
- 技术栈
- 2026年03月21日
-
跨境电商建站工具微信小商店坑死人
跨境电商建站工具微信小商店坑死人
- 技术栈
- 2026年03月21日
-
跨境网站开发百度一下首页网页百度
跨境网站开发百度一下首页网页百度
- 技术栈
- 2026年03月21日
-
跨境网站有哪些小型电商平台有哪些
跨境网站有哪些小型电商平台有哪些
- 技术栈
- 2026年03月21日
-
跨站攻击 wordpress上海网站建设公司推荐
跨站攻击 wordpress上海网站建设公司推荐
- 技术栈
- 2026年03月21日
