当前位置: 首页 > news >正文

如何做登录网站电子商务网站建设方案的总结

如何做登录网站,电子商务网站建设方案的总结,企业手机网页设计,折扣网站模板for循环的两种方式 for-range 常见“坑”与避坑方法 坑1#xff1a;循环变量的重用 下面这个示例是对一个整型切片进行遍历#xff0c;并且在每次循环体的迭代中都会创建一个新的#xff0c;Goroutine#xff08;Go 中的轻量级协程#xff09;#xff0c;输出这次迭代…for循环的两种方式 for-range 常见“坑”与避坑方法 坑1循环变量的重用 下面这个示例是对一个整型切片进行遍历并且在每次循环体的迭代中都会创建一个新的GoroutineGo 中的轻量级协程输出这次迭代的元素的下标值与元素值。 package mainimport (fmttime )func main() {var m []int{1, 2, 3, 4, 5}for i, v : range m {go func() {time.Sleep(time.Second * 3)fmt.Println(i, v)}()}time.Sleep(time.Second * 10) }控制台 go run test4.go 4 5 4 5 4 5 4 5 4 5但是以上打印结果与我们的预期不符这是为啥。 基于golang隐式代码块的规则大家可以自行百度这里不在赘述我们可以将上面的 for range 语句做一个等价转换这样可以帮助你理解 for range 的工作原理。等价转换后的结果是这样的 func main() {var m []int{1, 2, 3, 4, 5}{i, v : 0, 0 // 其实代码应该是这样的for range循环执行的时候会在每次遍历的时候将值赋值给隐士代码块里的v而不是重新声明一个变量vfor i, v range m {// 由于go func() {time.Sleep(time.Second * 3)// 这里每个函数都是一个闭包且没有参数传递所以当闭包里的代码执行的之后闭包没有的变量它就会引用了作用域之外的变量并且所有的协程都是睡眠3s后执行确保for循环结束之前所有的协程不会运行又因为for range的循环每次都是公用的同一个变量于是当睡眠时间过了之后所有的i,v就都是最后一次运行时的i,v的值fmt.Println(i, v)}()}}time.Sleep(time.Second * 10) }同样的情况如果把切片里的元素为引用类型则打印结果会是啥呢 通过等价转换后的代码我们可以清晰地看到循环变量 i 和 v 在每次迭代时的重用。而 Goroutine 执行的闭包函数引用了它的外层包裹函数中的变量 i、v这样变量 i、v 在主 Goroutine 和新启动的 Goroutine 之间实现了共享而 i, v 值在整个循环过程中是重用的 仅有一份。在 for range 循环结束后i 4, v 5因此各个 Goroutine 在等待 3 秒后进行 输出的时候输出的是 i, v 的最终值。 闭包函数在 Golang 中闭包是一个引用了作用域之外的变量的函数。闭包的存在时间可以超过创建它的作用域因此它可以访问该作用域中的变量即使在该作用域被销毁之后。 那么如何修改代码可以让实际输出和我们最初的预期输出一致呢我们可以为闭包函数增加 参数并且在创建 Goroutine 时将参数与 i、v 的当时值进行绑定看下面的修正代码 func main() {var m []int{1, 2, 3, 4, 5}for i, v : range m {// 这里在闭包函数里传递了参数所以在for循环结束之时开始运行协程里的代码所以每次循环传递到闭包函数里的就都是i,v的副本又因为这里传递参数为不是指针类型所以不受外部函数的iv值的影响。所以每个闭包函数注册到函数栈上的都是参数的副本。当for循环完毕之后运行的就都是每个副本的具体的值。go func(i, v int) {time.Sleep(time.Second * 3)fmt.Println(i, v)}(i, v)}time.Sleep(time.Second * 10) }控制台 go run test4.go 2 3 3 4 1 2 0 1 4 5坑2参与循环的是 range 表达式的副本 我们知道在 for range 语句中range 后面接受的表达式的类型可以是数组、指向数 组的指针、切片、字符串还有 map 和 channel需具有读权限。我们以数组为例来看一 个简单的例子 package mainimport (fmt )func main() {var a [5]int{1, 2, 3, 4, 5}var r [5]intfmt.Println(original a , a)for i, v : range a {if i 0 {a[1] 12a[2] 13}r[i] v}fmt.Println(after for range loop, r , r)fmt.Println(after for range loop, a , a) } 控制台 go run test4.go original a [1 2 3 4 5] after for range loop, r [1 2 3 4 5] after for range loop, a [1 12 13 4 5]解析 我们原以为在第一次迭代过程也就是 i 0 时我们对 a 的修改 (a[1] 12,a[2] 13) 会在 第二次、第三次迭代中被 v 取出但从结果来看v 取出的依旧是 a 被修改前的值2 和 3。 为什么会是这种情况呢原因就是参与 for range 循环的是 range 表达式的副本。也就是 说在上面这个例子中真正参与循环的是 a 的副本而不是真正的 a。 为了方便你理解我们将上面的例子中的 for range 循环用一个等价的伪代码形式重写一 下 func main() {for i, v : range a { //a是a的一个值拷贝if i 0 {a[1] 12a[2] 13}r[i] v} }现在真相终于揭开了这个例子中每次迭代的都是从数组 a 的值拷贝 a’中得到的元素。 a’是 Go 临时分配的连续字节序列与 a 完全不是一块内存区域。因此无论 a 被如何修改 它参与循环的副本 a’依旧保持原值因此 v 从 a’中取出的仍旧是 a 的原值而不是修改后 的值。 那么应该如何解决这个问题让输出结果符合我们前面的预期呢我们前面说过在 Go 中 大多数应用数组的场景我们都可以用切片替代这里我们也用切片来试试看 package mainimport fmtfunc main() {var a [5]int{1, 2, 3, 4, 5}var r [5]intfmt.Println(original a , a)for i, v : range a[:] {if i 0 {a[1] 12a[2] 13}r[i] v}fmt.Println(after for range loop, r , r)fmt.Println(after for range loop, a , a) } 在 range 表达式中我们用了 a[:]替代了原先的 a也就是将数组 a 转换为一个 切片作为 range 表达式的循环对象。运行这个修改后的例子结果是这样的 控制台 go run test4.go original a [1 2 3 4 5] after for range loop, r [1 12 13 4 5] after for range loop, a [1 12 13 4 5]我们看到输出的结果与最初的预期终于一致了显然用切片能实现我们的要求。 那切片是如何做到的呢因为切片在 Go 内部表示为一个结 构体由array, len, cap组成其中 array 是指向切片对应的底层数组的指针len 是切 片当前长度cap 为切片的最大容量。 所以当进行 range 表达式复制时我们实际上复制的是一个切片也就是表示切片的结构 体。表示切片副本的结构体中的 array依旧指向原切片对应的底层数组所以我们对切片副 本的修改也都会反映到底层数组 a 上去。而 v 再从切片副本结构体中 array 指向的底层数组中获取数组元素也就得到了被修改后的元素的值。 坑3方法中使用for-range 我敢出一题打赌在做的各位有一半人要写错狗头保命 请各位看下以下代码输出是啥 package mainimport (fmttime )type field struct {name string }func (p *field) print() {fmt.Println(p.name) }func main() {data1 : []*field{{one}, {two}, {three}}for _, v : range data1 {go v.print()}data2 : []field{{four}, {five}, {six}}for _, v : range data2 {go v.print()}time.Sleep(3 * time.Second) }控制台 go run test4.go one two three six six six以上代码因为有多协程所以输出顺序可能不尽相同但是都有一个疑惑那就是第二个for循环里为啥输出了六个six而不是four、five、six是因为这段代码666吗 我们来分析一下 我们根据 Go方法的本质也就是 一个以方法的receiver参数作为第一个参数的普通函数对这个程序做个 等价变换。这里我们利用Method Expression方式等价变换后的源码如下 type field struct {name string }func (p *field) print() {fmt.Println(p.name) }func main() {data1 : []*field{{one}, {two}, {three}}for _, v : range data1 {go (*field).print(v)}data2 : []field{{four}, {five}, {six}}for _, v : range data2 {go (*field).print(v)}time.Sleep(3 * time.Second) }由此我们看到其实for循环里的代码go协程部分其实就是一个闭包函数 我们来分析代码 package mainimport (fmttime )type field struct {name string }func (p *field) print() {fmt.Println(p.name) }func main() {data1 : []*field{{one}, {two}, {three}}{// 隐式代码块v : objfor _, v : range data1 {// 这里相当于每次循环都调用了一个函数并且将每个元素以参数传递进去// 然后呢因为for-range循环的是range表达式的副本所以这里循环的是data1的一个拷贝但是// 因为data1里的每个元素都是指针类型的所以这些元素里存储的都是元素对应的地址// 所以拷贝的副本相当于是一个个新的指针变量这些新变量里存储的还是原先每个元素的地址// 相当于是新元素和旧元素都是一个地址他们指向的是同一个内存里的东西// 又因为 Go方法的本质也就是 一个以方法的receiver参数作为第一个参数的普通函数注意此处方法的reciver就是*field而非field这点很重要// 所以go v.print()等价于go (*field).print(v)其中v是指针类型因为方法的reciver就是*field而非field这点很重要// 好了所有的条件都清楚了那么我们分析每一次for循环的逻辑// 第一次for循环第一个元素在隐式代码块里将值赋值给了循环变量v,然后创建协程将v作为函数的参数注册到函数栈上其实就是元素的地址注册到了函数栈上又因为go所有函数/方法传参都是拷贝副本传参数但是此处的参数是一个指针类型那么拷贝的新变量是一个指针类型的变量它指向的还是原先元素的地址所以每次注册在函数上的都是不同的地址// 第二次for循环第二个元素在隐式代码块里将值赋值给了循环变量v,然后创建协程将v作为函数的参数注册到函数栈上其实就是元素的地址注册到了函数栈上又因为go所有函数/方法传参都是拷贝副本传参数但是此处的参数是一个指针类型那么拷贝的新变量是一个指针类型的变量它指向的还是原先元素的地址所以每次注册在函数上的都是不同的地址// 第三次for循环第三个元素在隐式代码块里将值赋值给了循环变量v,然后创建协程将v作为函数的参数注册到函数栈上其实就是元素的地址注册到了函数栈上又因为go所有函数/方法传参都是拷贝副本传参数但是此处的参数是一个指针类型那么拷贝的新变量是一个指针类型的变量它指向的还是原先元素的地址所以每次注册在函数上的都是不同的地址// 然后等到函数在函数栈上运行的时候每个print函数打印的就都是分别不同的元素的name字段的值于是输出的就是one、two、threego (*field).print(v)}}data2 : []field{{four}, {five}, {six}}for _, v : range data2 {// 这里相当于每次循环都调用了一个函数并且将每个元素以参数传递进去// 然后呢因为for-range循环的是range表达式的副本所以这里循环的是data1的一个拷贝但是// 因为data2里的每个元素都是非指针类型的所以每个元素就都是一个新的元素与data2的元素就没有关联了// 又因为 Go方法的本质也就是 一个以方法的receiver参数作为第一个参数的普通函数因为方法的reciver就是*field而非field这点很重要// 但是我们的元素都是非指针类型的所以这里要传递指针类型的参数才可以go帮我们隐士转换了// 所以 go v.print()等价于 (*field).print(v)本质上就是一个普通的函数参数为指针类型的field其中v是指针类型因为方法的reciver就是*field而非field这点很重要// 好了所有的条件都清楚了那么我们分析每一次for循环的逻辑// 第一次for循环第一个元素在隐式代码块里将值赋值给了循环变量v,然后创建协程, 将v作为函数的参数注册到函数栈上其实就是元素的地址注册到了函数栈上// 但是函数要求参数必须是指针类型的reciver为函数的第一个参数所以这里我们要传递指针进去所以取的就是循环变量的v的值// 但是此处数组元素都为非指针所以每次循环都只是将元素的值赋给了v,v的地址并没有发生变化然后函数传参时传递的是参数的拷贝但是此处参数是个指针类型拷贝出来的参数也是一个指针类型它指向的v的地址相当于3次for循环注册到函数上的是同一个地址// 所以3次for循环里通过go调用print函数传递的都是同一个v对象又因为go协程为异步所以for循环完毕之后才执行了协程然后执行的之后就是for循环最后一个元素的值即six,six,six(所以这里如果不是异步是同步那么你在每次for循环里让协程sleep 1秒那么输出的就是456了)// 然后等到函数在函数栈上运行的时候每个print函数打印的就是最后一次循环的v的值于是输出的就是six、six、sixgo (*field).print(v)}time.Sleep(3 * time.Second) }那么还有问题怎么让第二个for打印456呢 其实我们只需要将field类型print方法的receiver类型由*field改为field就可以了。我们直接来看一下修改后的代码 type field struct {name string }func (p field) print() {fmt.Println(p.name) }func main() {data1 : []*field{{one}, {two}, {three}}for _, v : range data1 {go v.print()}data2 : []field{{four}, {five}, {six}}for _, v : range data2 {go v.print()}time.Sleep(3 * time.Second) }为啥将方法print的receiver由指针类型改为非指针类型就可以了呢 我们简单分析一下 package mainimport (fmttime )type field struct {name string }func (p field) print() {fmt.Println(p.name) }func main() {data1 : []*field{{one}, {two}, {three}}for _, v : range data1 {go v.print()}data2 : []field{{four}, {five}, {six}}for _, v : range data2 {// 这里不管切片里的元素是不是指针类型也不管for-range拷贝的副本里的元素是不是指针类型// 在每次for循环的时候调用的都是v.print()方法相当于是 在函数栈上注册了一个这样的函数// 参数为一个field类型的函数于是每次for循环注册在函数栈上的都直接是for的每次循环的元素的值就是123456// 然后go的所有函数/方法传参没有引用一说全部都是拷贝参数的副本传参不管拷贝多少次这里都是非指针类型// 所以打印出来的当然就是每次元素的值。go v.print()}time.Sleep(3 * time.Second) } 总结 1.凡是用到for-range的地方一定要小心 2.凡是用到闭包的地方参数取值或传参一定要小心 3.go方法的本质也就是 一个以方法的receiver参数作为第一个参数的普通函数 4.for-range的时候如果调用闭包千千万万要看清在将函数注册到函数栈上时有没有注册参数 若有则注意参数的类型是指针还是非指针。 若没有那么函数运行时就找闭包函数之外的变量运行了注意此时变量的值。
http://www.zqtcl.cn/news/181543/

相关文章:

  • pv3d 优秀网站18种最有效推广的方式
  • 一站式网站建设顾问网站建设公司专业网站科技开发
  • python做网站比php好网站开发财务费用
  • 图片上传网站变形的处理北京网站建设有哪些公司
  • 昆山品牌网站建设wordpress 浮动二维码
  • 网站网页建设论文cms免费源码
  • wordpress登录的图片不显示seo竞价网站建设
  • 邢台做移动网站找谁网上推广平台哪个好
  • 做网站准备广州短视频拍摄公司
  • 网站建设学什么软件做电影资源网站有哪些
  • 怎么样让百度搜到自己的网站wordpress的短代码
  • 聊城专业网站建设公司电子商务网站建设与维护李建忠下载
  • icp备案网站接入信息怎么写长兴县网站建设
  • 如何在网上注册公司网站网站不想让百度收录
  • 服务器做jsp网站教程视频免费的舆情网站app下载
  • 肇庆网站建设方案优化家居定制类网站建设
  • 自助建站加盟备案的网站有什么好处
  • 科技公司企业网站建设重庆seo优化
  • 空间站天宫vr全景尚层装饰
  • 有没有专门做中考卷子的网站网络公司推广公司
  • 网站建设费用如何列支wordpress页面构建
  • 用dw做网站怎么做出下拉菜单企业进行网站建设的方式有( )
  • 纯静态网站索引怎么做如何用wampp 做网站
  • 怎样做网站吸引人wordpress数据可视化插件
  • 网站运营管理教材中国设计之窗官方网站
  • 高端网站设计高端网站制作P2P网站怎么建设
  • 一般网站建设的流程故事app怎么制作
  • 一般在什么网站上做电子请帖国外产品设计网
  • 成都网站建设987netADPR国际传媒网站建设
  • 网站开发培训光山价格低