Go语言入门-流程控制(for、switch、select、range、break、goto、continue)

Go语言入门-流程控制(for、switch、select、range、break、goto、continue)

目录

Go语言入门-流程控制

条件语句

if语句

switch语句

select语句

循环语句

for语句

for range语句

循环跳转语句

goto语句

break语句

continue语句



条件语句

  • if语句

if语句语法

if bool表达式 { //左花括号必须跟随bool表达式
    //当bool表达式为true是执行。
    Todo....
}

示例如下:

package main

func main() {
	a := 10
	if a >= 10 {
		println("a 大于等于10") //10
	}

	if a < b100 {
		println("a小于等于100") //100
	}
}
/**
output:
a 大于等于10
a小于等于100
 */

if...else 语法

if bool表达式1 {
    Todo..分支1
} else if bool表达式2 {
     Todo..分支2
} else{
     Todo..分支3
}

示例如下:

//获取绩效等级
func getPerformanceLevel(score int) {
	if score > 120 {
		fmt.Println("怕是要上天")
	} else if score > 100 {
		fmt.Println("超越")
	} else if score >= 90 {
		fmt.Println("优秀")
	} else if score >= 80 {
		fmt.Println("良好")
	} else if score >= 60 {
		fmt.Println("合格")
	} else {
		fmt.Println("卷铺盖走人吧")
	}
}
func main() {
	score1 := 300
	score2 := 110
	score3 := 95
	score4 := 81
	score5 := 61
	score6 := 50
	getPerformanceLevel(score1)
	getPerformanceLevel(score2)
	getPerformanceLevel(score3)
	getPerformanceLevel(score4)
	getPerformanceLevel(score5)
	getPerformanceLevel(score6)
}
/**
output:
怕是要上天
超越
优秀
良好
合格
卷铺盖走人吧
 */

 

if 初始化语句

func main() {
    if a := 10; a > 0 {
    fmt.Println("if初始化测试")
    }
}
/**
output:
if初始化测试
 */

以上例子中定义了局部变量a,局部变量a的作用域为整个if else语句块。这一点区别于C、C++、Java语言

备注:Go语言中不支持三元操作符(三目运算符) "a > b ? a : b"。

  • switch语句

“ Switch”语句提供分支执行功能。 将表达式或类型说明符与“ switch”中的“ case”表达式进行比较,以确定要执行的分支。也就是说switch分为两类,第一类是 基于表达式选择的switch语句,第二类是基于类型选择的switch语句。

表达式switch语句语法

    switch expr { //expr 表达式
    case var1: Todo1 //expr表达式的值同var1进行比较,如果相等 执行Todo1语句并且退出switch
    case var2: Todo2  //expr表达式的值同var2进行比较,如果相等 执行Todo2语句并且退出switch
    case var3: Todo3  //expr表达式的值同var3进行比较,如果相等 执行Todo3语句并且退出switch
    default: Todo4  //expr表达式的值都不满足case语句后面表达式的值 则会执行default
    }

   //分支使用表达式,类似于if else
   switch  { //无表达式
   case expr1: Todo1 //expr表达式为true 执行Todo1语句并且退出switch
   case expr2: Todo2  //expr表达式为true 执行Todo3语句并且退出switch
   case expr3: Todo3  //expr表达式为true 执行Todo3语句并且退出switch
   default: Todo4  //以上case都不满足时, 执行Todo4语句并且退出switch
   }

通过以上可以看出是与C语言、C++、Java是不一样的,执行了某一条case语句以后实际上不需要再次break主动退出switch语句。

示例如下

func getWeekDay(day int) {
    switch day {
    case 7 :
        fmt.Println("周 日")
    case 6:
        fmt.Println("周", day)
    case 5:
        fmt.Println("周", day)
    case 4:
        fmt.Println("周", day)
    case 3:
        fmt.Println("周", day)
    case 2:
        fmt.Println("周", day)
    case 1:
        fmt.Println("周", day)
    default:
        fmt.Println("ERROR")
    }
}

func main() {
    for i := 0; i < 9; i++ {
        getWeekDay(i)
    }
}
/**
ouput:
ERROR
周 1
周 2
周 3
周 4
周 5
周 6
周 日
ERROR
 */

由此可以看出,switch后跟表达式语句时,更加适用与某些点固定值的分支执行。ifelse适用与某些段的分支执行,当然两种也可以互相转换。那么switch能够模拟if ... else按段执行吗?答案是可以的。可以使用switch { case expr:...}的方式进行处理。如下:



//用switch改造if...else
//获取绩效等级
func getPerformanceLevelBySwitch(score int) {
    switch {
    case score > 120:
            fmt.Println("怕是要上天")
    case score > 100:
            fmt.Println("超越")
    case score >= 90:
        fmt.Println("优秀")
    case score >= 80:
            fmt.Println("良好")
    case score >= 60:
            fmt.Println("合格")
    default:
            fmt.Println("卷铺盖走人吧")
    }
}
func main() {
    score1 := 300
    score2 := 110
    score3 := 95
    score4 := 81
    score5 := 61
    score6 := 50
    getPerformanceLevelBySwitch(score1)
    getPerformanceLevelBySwitch(score2)
    getPerformanceLevelBySwitch(score3)
    getPerformanceLevelBySwitch(score4)
    getPerformanceLevelBySwitch(score5)
    getPerformanceLevelBySwitch(score6)
}
/**
output:
怕是要上天
超越
优秀
良好
合格
卷铺盖走人吧
 */

以上例子通过switch后面无表达式的方式,通过case关键字后增加bool表达式来判断是否执行case语句。从而达到模拟if...else进行数据分段的逻辑判断。

该用法为go语言特殊用法

类型switch语句语法

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型如下:

func typeSwitchDemo(x interface{}) {
    switch x.(type) {
    case nil:
        fmt.Println("nil")
    case int:
        fmt.Println("int")
    case float64:
        fmt.Println("float64")
    case bool, string:
        fmt.Println("bool or string")
    default:
        fmt.Println("no found Type")
    }
}

func main() {
    var a1 chan int
    a2 := 1
    a3 := 1.0
    a4 := true
    a5 := "dsfsd"
    typeSwitchDemo(a1) //no found Type
    typeSwitchDemo(nil) //nil
    typeSwitchDemo(a2) //int
    typeSwitchDemo(a3) //float64
    typeSwitchDemo(a4) //bool or string
    typeSwitchDemo(a5) //bool or string
}
/**
output:
no found Type
nil
int
float64
bool or string
bool or string
 */

类型switch官网示例2

switch i := x.(type) { //带了初始化语句。
case nil:
	printString("x is nil")                // type of i is type of x (interface{})
case int:
	printInt(i)                            // type of i is int
case float64:
	printFloat64(i)                        // type of i is float64
case func(int) float64:
	printFunction(i)                       // type of i is func(int) float64
case bool, string:
	printString("type is bool or string")  // type of i is type of x (interface{})
default:
	printString("don't know the type")     // type of i is type of x (interface{})
}

以上例子中定义了局部变量i (switch i := x.(type))  实际i存放x的类型。然后使用i去和case条件进行匹配。 同时可以发现 case bool,string的写法。也就是 case后面可以并联多个条件。如下:

//case后多个条件并列写法
func main() {
    for tmp := 0; tmp < 4; tmp++ {
        switch tmp {
        case 0, 1: //tmp = 0 或者tmp = 1走该分支
            fmt.Println("1")
        case 2:
            fmt.Println("2")
        default:
            fmt.Println("def")
        }
    }
}
/**
output:
1
1
2
def
 */

特殊用法fallthough

Go里面switch默认相当于每个case执行后后就结束switch块,匹配成功后不会自动向下执行其他case,而是跳出整个switch,
        但是可以使用fallthrough强制执行后面的case代码。

func main() {
    for tmp := 0; tmp < 4; tmp++ {
        fmt.Println("循环次数 tmp----->:", tmp)
        switch tmp {
        case 0, 1: //tmp = 0
            fmt.Println(tmp, "此处fallthrough")
            fallthrough
        case 2:
            fmt.Println(tmp, "2")
        default:
            fmt.Println(tmp, "def")
        }
    }
}
/**
output:
循环次数 tmp----->: 0
0 此处fallthrough
0 2
循环次数 tmp----->: 1
1 此处fallthrough
1 2
循环次数 tmp----->: 2
2 2
循环次数 tmp----->: 3
3 def
 */

以上例子中,当tmp为0和1是,执行完case 0,1后,即使不满足case 2:也会执行 case2的语句。

fallthrough强制执行后面的case代码,但是不能用于类型switch中。

The "fallthrough" statement is not permitted in a type switch.-------划重点。。。

示例如下:

func typeSwitchDemo(x interface{}) {
    switch x.(type) {
    case nil:
        fmt.Println("nil")
        //Cannot fallthrough in type switch--编译报错
        fallthrough
    case int:
        fmt.Println("int")
    case float64:
        fmt.Println("float64")
    case bool, string:
        fmt.Println("bool or string")
    default:
        fmt.Println("no found Type")
    }
}

 

  • select语句

    select 类似于switch语句,但是select会随机执行一个可以运行的case语句。每个case语句后面必须是通讯操作(io),要么是发送操作要么是接收语句。select会监听case语句的读写操作。当case语句中读写操作变为非阻塞时(读取到值或者能写入值)就会被select监听到,当select监听到只有一个case语句读写动作后就会执行改case语句。当select监听到多个case语句时的读写动作时会随机选择一条case语句执行。当没有读写动作发送时,如果存在default则会执行default语句否则就会阻塞在select监听上,直到有读写事件触发。

select语法

    select {
    case sendOrReceive1: block1 //通讯操作IO
    ...
    case sendOrReceive1: blockN //通讯操作IO
    default:
        blockDefault //没有事件触发则执行default
    }

简单示例

unc main() {
    ch := make(chan int)  //无缓存通道
    ch1 := make(chan int) //无缓存通道
    var get, put int
    go func() { //go rountine启动读取循环读取ch1的值
        for {
            <-ch1
            time.Sleep(time.Second * 2)
        }
    }()
    go func() {
        for { //每隔2秒定时往通道ch写一个0-10的随机整数。
            ch <- rand.Intn(10)
            time.Sleep(time.Second * 2)
        }
    }()
    for {
        select {
        case ch1 <- put: //监听通道ch是否发生写事件,因为是无缓存通道,只有当消息被获取到认为写成功
            fmt.Println("put:", put)
        case get = <-ch: //监听通道ch中是否有值如果有值取出来赋值给get,
            fmt.Println("get:", get)
        default: //如果没有监听到读写事件则执行default
            fmt.Println("no event")
            time.Sleep(time.Second)
        }
    }
}
/**
output:
no event
put: 0
get: 1
no event
no event
no event
put: 0
get: 7
no event
no event
put: 0
no event
get: 7
no event
put: 0
no event
.....
 */

以上实例中 有两个协程分别往无缓冲的通道中每隔2秒写数据和读数据,select语句分别监控读写动作,当没有读写动作发生是执行default语句休眠一秒以后通过循环继续监听读写动作。所以在结果打印中no event 出现的概率大概是put或者get2倍。

进阶示例1 -超时判断

var ch = make(chan int)
func selectWaitTimeTest()  {
    select {
        case data := <- ch: //监听读数据是否完成
            fmt.Println("get data :", data)
    case <-time.After(time.Second * 2): //3秒后出发读数据
        fmt.Println("select wait time")
    }
}

func main() {
    //模拟超时判断
    selectWaitTimeTest()
}
/**
output:
3秒后打印
select wait time
 */

进阶示例2 -安全退出程序

//模拟协同退出
var quitChan = make (chan struct{})

func main() {
    go func() { //子协程控制主协程退出
        i := 0
        for i < 5 {
            i++
            time.Sleep(time.Second * 1)
            fmt.Printf("休眠第[%d]秒\n", i)
        }
        fmt.Println("5秒后通知主协程退出")
        quitChan <- struct{}{}
    }()
    select { 
    case <- quitChan: //只有一个case,主协程阻塞等待通道中有数据并出发读操作。从通道中获取到值以后打印退出数据,并退出
        println("exit!!!")
        return
    }
}
/**
output:
休眠第[1]秒
休眠第[2]秒
休眠第[3]秒
休眠第[4]秒
休眠第[5]秒
5秒后通知主协程退出
exit!!!
 */

 

循环语句

  • for语句

Go语言中循环语句的关键词只有for没有其他语言中while,但是while语句可以通过for语句来模拟实现

ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .

//语法1
for 初始化表达式; 条件表达式; 赋值表达式 {
    Todo
} 

//语法2
for 条件表达式 {
    Todo
} 
//相当于其他语言中
while 条件表达式 {
    Todo
}

//语法3--死循环
for {
    Todo
} 
//相当于其他语言中
while (1) {
    Todo
}

基本用法示例

示例1-简单的 for 初始化表达式; 条件表达式; 赋值表达式 {Todo} 

func main() {
    s := "test"
    //循环遍历字符串
    for i := 0; i < len(s); i++ {
        fmt.Printf("%c\n", s[i])
    }
}
/**
output:
t
e
s
t
 */

示例2-for 条件表达式 {Todo} 

func main() {
    i := 5
    for i < 10 {
        i++
        fmt.Println(i)
    }
}
/**
output:
6
7
8
9
10
 */

示例3-//语法3--死循环for {Todo}

func main() {
    i := 0
    for { //死循环
       fmt.Println(i)
       i++
       if i > 5 {
           break //i自增到5以后退出循环
       }
    }
}
/**
output:
0
1
2
3
4
5
 */

 

  • for range语句

go语言中可以使用for range语句来遍历数组、切片、字符串、通道、map。

数组、切片、字符串返回索引值

map返回键和值

通道返回通道内元素的值

如下:

Range expression                          1st value          2nd value

array or slice  a  [n]E, *[n]E, or []E    index    i  int    a[i]       E
string          s  string type            index    i  int    see below  rune
map             m  map[K]V                key      k  K      m[k]       V
channel         c  chan E, <-chan E       element  e  E

示例1--演示字符串迭代

func main() {
    s := "abcdef"
    for i := range s { //i为第一个返回值下标
        fmt.Printf("%c\n", s[i])
    }
    fmt.Println("=============================================")
    for i, v := range s { //同时获取第一个返回值-下标。和第二个返回值-元素值
        fmt.Printf("index[%d] value=[%c]\n", i, v)
    }
    fmt.Println("=============================================")
    for _, v := range s { //丢弃下标。获取第二个返回值-元素值
        fmt.Printf("value=[%c]\n", v)
    }
    fmt.Println("=============================================")
    for i, _ := range s { //获取下标。丢弃-元素值
        fmt.Printf("index[%d]\n", i)
    }
    
}
/**
output:
a
b
c
d
e
f
=============================================
index[0] value=[a]
index[1] value=[b]
index[2] value=[c]
index[3] value=[d]
index[4] value=[e]
index[5] value=[f]
=============================================
value=[a]
value=[b]
value=[c]
value=[d]
value=[e]
value=[f]
=============================================
index[0]
index[1]
index[2]
index[3]
index[4]
index[5]
 */

示例2--演示map迭代

func main() {
    m := map[string]int {"key1":1,"key2":2, "key3":3}
    // range打印的map为无序的
    for k, v := range m {
        fmt.Println(k, v)
    }
    fmt.Println("=============================================")
    //丢弃值
    for k, _ := range m {
        fmt.Println(k)
    }
    fmt.Println("=============================================")
    //丢弃键
    for _, v := range m {
        fmt.Println(v)
    }
}
/**
output:
key2 2
key3 3
key1 1
=============================================
key1
key2
key3
=============================================
1
2
3
 */

示例3-通道range遍历


func main() {
    var ch = make(chan int, 5)
    var ch1 = make(chan int, 5)
    for i := 0; i < 5; i++ {
        ch <-i
    }
    close(ch) //通道要么被关闭,要么持续有值写入,否则使用range会发生死锁
    for v := range ch {
        fmt.Println(v)
    }
    go func() {
        for { //无限循环,通道没有关闭,也可以通过range持续获取值。
            ch1 <- 0
            time.Sleep(time.Second)
        }
    }()
    //遍历通道ch1
    for v := range ch1 {
        fmt.Println(v)
    }

}
/**
0
1
2
3
4
0
0
0
0
0
0
0
主动退出
Process finished with exit code 2

 */

 

循环跳转语句

  • goto语句

 

goto statement transfers control to the statement with the corresponding label within the same function.--goto语句将控制权转移到同一函数中带有相应标签的语句。重点是同一函数中。

goto语句语法

goto Lable //指定跳转的标签
....

Lable:statement //标签指定的语句块。


示例1-简单使用goto1

func main() {
    i := 0
    if i == 0 {
        fmt.Println("will goto exit")
        goto exit//跳转到exit标签指定的语句。
    }
    fmt.Println("normal")
exit://exit只是一个标签,即使前面没有发生goto跳转会执行exit标记的后续语句
    fmt.Println("exit")
}
/**
output:
will goto exit
exit
 */

上面示例中当i为0的时候,就会执行goto语句,直接执行exit标记的后面语句,而跳过fmt.Println("normal")的执行。

示例2-简单使用goto2

func main() {
    i := 1
    if i == 0 {
        fmt.Println("will goto exit")
        goto exit//跳转到exit标签指定的语句。
    }
    fmt.Println("normal")
exit://exit只是一个标签,即使前面没有发生goto跳转会执行exit标记的后续语句
    fmt.Println("exit")
}
/**
output:
normal
exit
 */

上面示例中当i是1的时候,不会在执行goto语句,但是exit标记语句还会执行。因此exit标记后的语句。不是goto语句专属的。只要满足执行条件也会执行exit后续的语句。

使用要点:

1. goto语句只能在一个函数中使用,并且不能跳转到其他块的内部作用域。举个例子:

func main44() {
    i := 0
   if i == 0 {
        goto Label1
    }
    for {
    
        //goto不在Label1的作用域中所以报错。
Label1: //Unused label 'Label1'
        fmt.Println("vvvvvvvvvvvv")
    }
    return
}

  2. goto语句和标签label中间没有变量声明,这点很好理解。如果goto语句跳转到标签后要使用一个变量,而这个变量没有在goto前面定义,编译器就会报错。算是逻辑错误+语法错误。(在这里:为啥会一棒子打死,按理如果goto和lable中有变量声明,但是label后面没有用到该变量应该也没啥问题吧?

举个例子:

func main55()  {
    goto Lable2 //Lable2 jumps over declaration of i 
    i := 0
    fmt.Println(i)
Lable2:
    fmt.Println("exit")
    return
}

3. goto不能跨函数使用,同第二点很好理解。不在一个作用域中。

最后备注:在能避免使用goto的情况下尽量避免使用goto。在需要集中进行资源清理的情况下,可以考虑使用goto。对于goto是谨慎使用,而不是滥用。

  • break语句

A "break" statement terminates execution of the innermost "for""switch", or "select" statement within the same function.---“ break”语句终止同一函数中最内层的“ for”,“ switch”或“ select”语句的执行。

break语法:

func funcname() {
    [label:]
    for/select/switch {
        break [label] 
    }
}

示例1-break的基本使用场景

func main()  {
    //在for语句中的使用
    for {
        fmt.Println("测试 break")
        break//跳出当前for循环
    }
    fmt.Println("for 语句结束")
    //在select中使用 实际上在select中使用break(不带标签是没有任何意义的)
    select {
    case <-time.After(time.Second):
        fmt.Println("一秒后退出")
        break
    case <-time.After(time.Second * 10):
        fmt.Println("十秒后退出")
    }
    fmt.Println("select 语句结束")
    //在switch中使用
    switch {
    case true:
        fmt.Println("true")
        fallthrough //模拟c语言中 switch
    case false:
        fmt.Println("false")
        break //跳出switch不会在去执行接下来的fallthrough后面的分支
        fallthrough
    default:
        fmt.Println("default")
        break //分支中没有fallthrough使用单独的break(没有label是没有啥意义的,本身执行完就会结束switch)
    }
    fmt.Println("switch 语句结束")
}
/**
output:
测试 break
for 语句结束
一秒后退出
select 语句结束
true
false
switch 语句结束
 */

可以看出:

1. 在select中使用break是没有用的(break没有标签)

2.在表达式switch中如果没有fallthought也是没有用的, 如果存在fallthough,那么break就会跳出switch循环。

示例2-break的进阶-带标签

func main()  {
    //在for语句中的使用
    FOR:
        for {
            for {
                fmt.Println("测试 break")
                //break//跳出当前for循环,
                break FOR //带标签break 跳出当前for语句块的上一个for语句块的中
            }
        }
    fmt.Println("for 语句结束")
    //在select中使用 实际上在select中使用break(不带标签是没有任何意义的)
    SELECT:
    for {
        select {
        case <-time.After(time.Second):
            fmt.Println("一秒后退出")
            //break 跳出select
            break SELECT  //带标签的break,实际上跳出到select外层的for语句块
        case <-time.After(time.Second * 10):
            fmt.Println("十秒后退出")
            break
        }
    }

    fmt.Println("select 语句结束")
    //在switch中使用
SWITCH:
    for {
        for {
            switch {
            case true:
                //fmt.Println("true")
                //fallthrough //模拟c语言中 switch
                break SWITCH //带标签的break,实际上跳出是作用域是 switch -> for -> for最外层的for语句
            case false:
                fmt.Println("false")
                break //跳出switch不会在去执行接下来的fallthrough后面的分支
                fallthrough
            default:
                fmt.Println("default")
                break //分支中没有fallthrough使用单独的break(没有label是没有啥意义的,本身执行完就会结束switch)
            }
        }
    fmt.Println("switch 语句结束")
}
/**
output:
  测试 break
  for 语句结束
  一秒后退出
  select 语句结束
  switch 语句结束
 */

以上例子可以看出:

1. 带标签的break,可以跳出多层select/for/ switch作用域。让break更加灵活,写法更加简单灵活,不需要使用控制变量一层一层跳出循环,没有带break的只能跳出当前语句块。

2. 同时goto语句使用标签和break使用标签也是有区别。break使用标签只是标记跳转到哪层作用域,并跳跃到被跳转的语句块后面执行,而goto使用标签是跳转指定代码行,并执行后续代码。

  • continue语句

A "continue" statement begins the next iteration of the innermost "for" loop at its post statement. The "for" loop must be within the same function.--“ continue”语句可以结束当前循环,开始下一次的循环迭代过程。 “ for”循环必须在同一函数内。

continue语法

func funcname() {
    [label:]
    for {
        continue [label] 
    }
}

示例1-continue基本使用

func main() {
    for i := 0; i < 10; i++ {
        if i % 2 != 0 {
            fmt.Println(i)
        } else {
            continue //当i是偶数时跳过当次循环
        }
    }
}
/***
output:
1
3
5
7
9
 */

示例1-continue进阶使用带label

func main() {
    j := 0
CONTINUEFOR: //跳转到和标签同级循环,并执行。
    for j < 10 {
        j++
        fmt.Println("j = ", j)
        for i := 0; i < 10; i++ {
            if i % 2 != 0 {
                fmt.Println(i)
            } else {
                //continue //当i是偶数时跳过当次循环
                continue CONTINUEFOR
            }
        }
    }
    fmt.Println("====================")
CONTINUEBREAK: //跳转到和标签同级循环,并跳过同级作用域后继续执行
    for j < 10 {
        j++
        fmt.Println("j = ", j) //不会打印,因为break已经跳转到CONTINUEBREAK,并跳过同级作用域。
        for i := 0; i < 10; i++ {
            if i % 2 != 0 {
                fmt.Println(i)
            } else {
                break CONTINUEBREAK
            }
        }
    }
    fmt.Println("====================")
}

由此可以看出:

1. continue带标签是跳转到标签同级的作用域(循环)后继续执行循环作用域。而break则是跳过同级作用域后继续执行。

2. continue只能用于for语句中。

3.continue带标签和goto带标签在循环中可以转换。-等价 (如下)

func main() {
    j := 0
CONTINUEFOR: //跳转到和标签同级循环,并执行。
    for j < 3 {
        j++
        fmt.Println("j = ", j)
        for i := 0; i < 10; i++ {
            if i % 2 != 0 {
                fmt.Println(i)
            } else {
                //continue //当i是偶数时跳过当次循环
                continue CONTINUEFOR
                //goto CONTINUEFOR
            }
        }
    }
    fmt.Println("====================")
    j = 0
GOTO: //跳转到和标签同级循环,并跳过同级作用域后继续执行
    for j < 3 {
        j++
        fmt.Println("j = ", j)
        for i := 0; i < 10; i++ {
            if i % 2 != 0 {
                fmt.Println(i)
            } else {
                //continue //当i是偶数时跳过当次循环
                goto GOTO
            }
        }
    }
    fmt.Println("====================")
}
/**
output:
j =  1
j =  2
j =  3
====================
j =  1
j =  2
j =  3
====================
 */

 


版权声明:本文为u011461385原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。