Go 语言规范学习(6)
文章目录
- Statements
- Terminating statements
- Empty statements
- Labeled statements
- Expression statements
- Send statements
- IncDec statements
- Assignment statements
- If statements
- Switch statements
- Expression switches
- Type switches
- For statements
- For statements with single condition
- For statements with `for` clause
- For statements with `range` clause
- Go statements
- Select statements
- Return statements
- Break statements
- Continue statements
- Goto statements
- Fallthrough statements
- Defer statements
Statements
Statements control execution.
Statement = Declaration | LabeledStmt | SimpleStmt |GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |DeferStmt .SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
Terminating statements
A terminating statement interrupts the regular flow of control in a block. The following statements are terminating:
-
A “return” or “goto” statement.
-
A call to the built-in function
panic
. -
A block in which the statement list ends in a terminating statement. 【如果一个代码块的最后一条语句是终止语句,那么整个代码块也是终止的。】
-
An “if” statement in which:
- the “else” branch is present, and
- both branches are terminating statements.
如果
if
带有else
分支,并且两个分支都是终止语句,则整个if
是终止的。 -
A “for” statement in which:
- there are no “break” statements referring to the “for” statement, and
- the loop condition is absent, and
- the “for” statement does not use a range clause.
如果
for
没有条件(即for {}
)、没有range
子句,并且内部没有break
跳出循环,那么它是一个终止语句(因为会无限循环,不会继续执行后续代码)。 -
A “switch” statement in which:
- there are no “break” statements referring to the “switch” statement,
- there is a default case, and
- the statement lists in each case, including the default, end in a terminating statement, or a possibly labeled “fallthrough” statement.
如果
switch
满足:- 没有
break
跳出switch
, - 有
default
分支, - 每个
case
(包括default
)的语句列表以终止语句或fallthrough
结尾,
则整个
switch
是终止的。 -
A
“select” statement in which:
- there are no “break” statements referring to the “select” statement, and
- the statement lists in each case, including the default if present, end in a terminating statement.
如果
select
满足:- 没有
break
跳出select
, - 每个
case
(包括default
,如果有)的语句列表以终止语句结尾,
则整个
select
是终止的。 -
A labeled statement labeling a terminating statement.
All other statements are not terminating.
A statement list ends in a terminating statement if the list is not empty and its final non-empty statement is terminating.
Empty statements
The empty statement does nothing.
EmptyStmt = .
Labeled statements
A labeled statement may be the target of a goto
, break
or continue
statement.
LabeledStmt = Label ":" Statement .
Label = identifier .
Error: log.Panic("error encountered")
Expression statements
With the exception of specific built-in functions, function and method calls and receive operations can appear in statement context. Such statements may be parenthesized.
ExpressionStmt = Expression .
The following built-in functions are not permitted in statement context:
append cap complex imag len make new real
unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo") // illegal if len is the built-in function
Send statements
A send statement sends a value on a channel. The channel expression’s core type must be a channel, the channel direction must permit send operations, and the type of the value to be sent must be assignable to the channel’s element type.
SendStmt = Channel "<-" Expression .
Channel = Expression .
Both the channel and the value expression are evaluated before communication begins. Communication blocks until the send can proceed. A send on an unbuffered channel can proceed if a receiver is ready. A send on a buffered channel can proceed if there is room in the buffer. A send on a closed channel proceeds by causing a run-time panic. A send on a nil
channel blocks forever.
ch <- 3 // send value 3 to channel ch
IncDec statements
The “++” and “–” statements increment or decrement their operands by the untyped constant 1
. As with an assignment, the operand must be addressable or a map index expression.
IncDecStmt = Expression ( "++" | "--" ) .
The following assignment statements are semantically equivalent:
IncDec statement Assignment
x++ x += 1
x-- x -= 1
Assignment statements
An assignment replaces the current value stored in a variable with a new value specified by an expression. An assignment statement may assign a single value to a single variable, or multiple values to a matching number of variables.
Assignment = ExpressionList assign_op ExpressionList .assign_op = [ add_op | mul_op ] "=" .
Each left-hand side operand must be addressable, a map index expression, or (for =
assignments only) the blank identifier. Operands may be parenthesized.
x = 1
*p = f()
a[i] = 23
(k) = <-ch // same as: k = <-ch
An assignment operation x
op=
y
where op is a binary arithmetic operator is equivalent to x
=
x
op (y)
but evaluates x
only once.【只求值一次】 The op=
construct is a single token. In assignment operations, both the left- and right-hand expression lists must contain exactly one single-valued expression, and the left-hand expression must not be the blank identifier.
a[i] <<= 2
i &^= 1<<n
A tuple assignment assigns the individual elements of a multi-valued operation to a list of variables. There are two forms. In the first, the right hand operand is a single multi-valued expression such as a function call, a channel or map operation, or a type assertion. The number of operands on the left hand side must match the number of values. For instance, if f
is a function returning two values,
x, y = f()
assigns the first value to x
and the second to y
.
In the second form, the number of operands on the left must equal the number of expressions on the right, each of which must be single-valued, and the nth expression on the right is assigned to the nth operand on the left:
one, two, three = '一', '二', '三'
The blank identifier provides a way to ignore right-hand side values in an assignment:
_ = x // evaluate x but ignore it
x, _ = f() // evaluate f() but ignore second result value
The assignment proceeds in two phases. First, the operands of index expressions and pointer indirections (including implicit pointer indirections in selectors) on the left and the expressions on the right are all evaluated in the usual order. Second, the assignments are carried out in left-to-right order.
赋值操作分为两个阶段进行:
- 第一阶段(求值阶段):
- 首先,对左侧的索引表达式(index expressions)、指针解引用(pointer indirections)(包括选择器中的隐式指针解引用)以及右侧的所有表达式,按照常规顺序进行求值。
- 第二阶段(赋值阶段):
- 然后,按照从左到右的顺序执行实际的赋值操作。
a, b = b, a // exchange a and bx := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2 // set i = 1, x[0] = 2i = 0
x[i], i = 2, 1 // set x[0] = 2, i = 1x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)x[1], x[3] = 4, 5 // set x[1] = 4, then panic setting x[3] = 5.type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7。p.x 需要解引用 p,但 p 是 nil,此时不会 panic(因为只是计算地址,尚未赋值)。i = 2
x = []int{3, 5, 7}
for i, x[i] = range x { // set i, x[2] = 0, x[0]break
}
// after this loop, i == 0 and x is []int{3, 5, 3}
In assignments, each value must be assignable to the type of the operand to which it is assigned, with the following special cases:
- Any typed value may be assigned to the blank identifier.
- If an untyped constant is assigned to a variable of interface type or the blank identifier, the constant is first implicitly converted to its default type.
- If an untyped boolean value is assigned to a variable of interface type or the blank identifier, it is first implicitly converted to type
bool
.
When a value is assigned to a variable, only the data that is stored in the variable is replaced. If the value contains a reference, the assignment copies the reference but does not make a copy of the referenced data (such as the underlying array of a slice).
var s1 = []int{1, 2, 3}
var s2 = s1 // s2 stores the slice descriptor of s1
s1 = s1[:1] // s1's length is 1 but it still shares its underlying array with s2
s2[0] = 42 // setting s2[0] changes s1[0] as well
fmt.Println(s1, s2) // prints [42] [42 2 3]var m1 = make(map[string]int)
var m2 = m1 // m2 stores the map descriptor of m1
m1["foo"] = 42 // setting m1["foo"] changes m2["foo"] as well
fmt.Println(m2["foo"]) // prints 42
If statements
“If” statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the “if” branch is executed, otherwise, if present, the “else” branch is executed.
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {x = max
}
The expression may be preceded by a simple statement, which executes before the expression is evaluated.
if x := f(); x < y {return x
} else if x > z {return z
} else {return y
}
Switch statements
“Switch” statements provide multi-way execution. An expression or type is compared to the “cases” inside the “switch” to determine which branch to execute.
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
There are two forms: expression switches and type switches. In an expression switch, the cases contain expressions that are compared against the value of the switch expression. In a type switch, the cases contain types that are compared against the type of a specially annotated switch expression. The switch expression is evaluated exactly once in a switch statement.
Expression switches
In an expression switch, the switch expression is evaluated and the case expressions, which need not be constants, are evaluated left-to-right and top-to-bottom; the first one that equals the switch expression triggers execution of the statements of the associated case; the other cases are skipped. If no case matches and there is a “default” case, its statements are executed. There can be at most one default case and it may appear anywhere in the “switch” statement. A missing switch expression is equivalent to the boolean value true
.
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .
If the switch expression evaluates to an untyped constant, it is first implicitly converted to its default type. The predeclared untyped value nil
cannot be used as a switch expression. The switch expression type must be comparable.
If a case expression is untyped, it is first implicitly converted to the type of the switch expression. For each (possibly converted) case expression x
and the value t
of the switch expression, x == t
must be a valid comparison.
In other words, the switch expression is treated as if it were used to declare and initialize a temporary variable t
without explicit type; it is that value of t
against which each case expression x
is tested for equality.
In a case or default clause, the last non-empty statement may be a (possibly labeled) “fallthrough” statement to indicate that control should flow from the end of this clause to the first statement of the next clause. Otherwise control flows to the end of the “switch” statement. A “fallthrough” statement may appear as the last statement of all but the last clause of an expression switch.
在 Go 语言的 switch
语句中:
case
或default
子句的最后一个非空语句可以是一个(可能带标签的)fallthrough
语句,表示控制流应从当前子句末尾跳转到下一个子句的开头。- 如果没有
fallthrough
,控制流会直接退出整个switch
语句。 fallthrough
只能出现在表达式switch
(非类型switch
)中,且不能是最后一个子句(否则无处可跳转)。
The switch expression may be preceded by a simple statement, which executes before the expression is evaluated.
switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}switch x := f(); { // missing switch expression means "true"
case x < 0: return -x
default: return x
}switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}
Implementation restriction: A compiler may disallow multiple case expressions evaluating to the same constant. For instance, the current compilers disallow duplicate integer, floating point, or string constants in case expressions.
Type switches
A type switch compares types rather than values. It is otherwise similar to an expression switch. It is marked by a special switch expression that has the form of a type assertion using the keyword type
rather than an actual type:
switch x.(type) {
// cases
}
Cases then match actual types T
against the dynamic type of the expression x
. As with type assertions, x
must be of interface type, but not a type parameter, and each non-interface type T
listed in a case must implement the type of x
. The types listed in the cases of a type switch must all be different.
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause = TypeSwitchCase ":" StatementList .
TypeSwitchCase = "case" TypeList | "default" .
The TypeSwitchGuard may include a short variable declaration. When that form is used, the variable is declared at the end of the TypeSwitchCase in the implicit block of each clause. In clauses with a case listing exactly one type, the variable has that type; otherwise, the variable has the type of the expression in the TypeSwitchGuard.
Instead of a type, a case may use the predeclared identifier nil
; that case is selected when the expression in the TypeSwitchGuard is a nil
interface value. There may be at most one nil
case.
Given an expression x
of type interface{}
, the following type switch:
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{})
}
could be rewritten:
v := x // x is evaluated exactly once
if v == nil {i := v // type of i is type of x (interface{})printString("x is nil")
} else if i, isInt := v.(int); isInt {printInt(i) // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {printFloat64(i) // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {printFunction(i) // type of i is func(int) float64
} else {_, isBool := v.(bool)_, isString := v.(string)if isBool || isString {i := v // type of i is type of x (interface{})printString("type is bool or string")} else {i := v // type of i is type of x (interface{})printString("don't know the type")}
}
A type parameter or a generic type may be used as a type in a case. If upon instantiation that type turns out to duplicate another entry in the switch, the first matching case is chosen.
func f[P any](x any) int {switch x.(type) {case P:return 0case string:return 1case []P:return 2case []byte:return 3default:return 4}
}var v1 = f[string]("foo") // v1 == 0
var v2 = f[byte]([]byte{}) // v2 == 2
The type switch guard may be preceded by a simple statement, which executes before the guard is evaluated.
The “fallthrough” statement is not permitted in a type switch.
For statements
A “for” statement specifies repeated execution of a block. There are three forms: The iteration may be controlled by a single condition, a “for” clause, or a “range” clause.
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
For statements with single condition
In its simplest form, a “for” statement specifies the repeated execution of a block as long as a boolean condition evaluates to true. The condition is evaluated before each iteration. If the condition is absent, it is equivalent to the boolean value true
.
for a < b {a *= 2
}
For statements with for
clause
A “for” statement with a ForClause is also controlled by its condition, but additionally it may specify an init and a post statement, such as an assignment, an increment or decrement statement. The init statement may be a short variable declaration, but the post statement must not.
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
for i := 0; i < 10; i++ {f(i)
}
If non-empty, the init statement is executed once before evaluating the condition for the first iteration; the post statement is executed after each execution of the block (and only if the block was executed). Any element of the ForClause may be empty but the semicolons are required unless there is only a condition. If the condition is absent, it is equivalent to the boolean value true
.
for cond { S() } is the same as for ; cond ; { S() }
for { S() } is the same as for true { S() }
Each iteration has its own separate declared variable (or variables) [Go 1.22]. The vargoiable used by the first iteration is declared by the init statement. The variable used by each subsequent iteration is declared implicitly before executing the post statement and initialized to the value of the previous iteration’s variable at that moment.
在 Go 1.22 及更高版本中,for
循环的每次迭代都会创建独立的变量实例(而非复用同一个变量)。具体规则:
- 首次迭代:变量由
for
的 init 语句(如i := 0
)声明。 - 后续迭代:
- 在每次迭代的 post 语句(如
i++
)执行前,隐式声明一个新变量。 - 新变量的初始值为 前一次迭代变量在 post 语句执行前的值。
- 在每次迭代的 post 语句(如
这种设计避免了循环闭包中的常见陷阱(如 goroutine 捕获循环变量问题),并更符合直觉。
var prints []func()
for i := 0; i < 5; i++ {prints = append(prints, func() { println(i) })i++
}
for _, p := range prints {p()
}
prints
1
3
5
注意:闭包捕获的是隐式声明的变量i。
Prior to [Go 1.22], iterations share one set of variables instead of having their own separate variables. In that case, the example above prints
6
6
6
For statements with range
clause
A “for” statement with a “range” clause iterates through all entries of an array, slice, string or map, values received on a channel, integer values from zero to an upper limit [Go 1.22], or values passed to an iterator function’s yield function [Go 1.23]. For each entry it assigns iteration values to corresponding iteration variables if present and then executes the block.
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
The expression on the right in the “range” clause is called the range expression, its core type must be an array, pointer to an array, slice, string, map, channel permitting receive operations, an integer, or a function with specific signature (see below). As with an assignment, if present the operands on the left must be addressable or map index expressions; they denote the iteration variables. If the range expression is a function, the maximum number of iteration variables depends on the function signature. If the range expression is a channel or integer, at most one iteration variable is permitted; otherwise there may be up to two. If the last iteration variable is the blank identifier, the range clause is equivalent to the same clause without that identifier.
The range expression x
is evaluated before beginning the loop, with one exception: if at most one iteration variable is present and x
or len(x)
is constant, the range expression is not evaluated.
-
如果满足以下两个条件,则
x
不会被提前求值:- 最多只有一个迭代变量(如
for i := range x
或for range x
)。 x
是常量,或者len(x)
是常量(例如固定长度的数组)。
示例 1:不提前求值
const arr = [3]int{1, 2, 3} // 常量数组 for i := range arr { // 只有一个迭代变量,且 len(arr) 是常量fmt.Println(i) // 输出 0, 1, 2 }
- 行为:
arr
是常量,len(arr)
也是常量(3),因此arr
不会被提前求值。 - 意义:编译器会直接展开循环,无需运行时计算
arr
。
示例 2:提前求值
slice := []int{1, 2, 3} for i, v := range slice { // 两个迭代变量fmt.Println(i, v) }
- 行为:
slice
会在循环开始前被求值(即使slice
在循环中被修改,循环次数仍由初始长度决定)。
- 最多只有一个迭代变量(如
Function calls on the left are evaluated once per iteration. For each iteration, iteration values are produced as follows if the respective iteration variables are present:
Range expression 1st value 2nd valuearray 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
integer value n integer type, or untyped int value i see below
function, 0 values f func(func() bool)
function, 1 value f func(func(V) bool) value v V
function, 2 values f func(func(K, V) bool) key k K v V
- For an array, pointer to array, or slice value
a
, the index iteration values are produced in increasing order, starting at element index 0. If at most one iteration variable is present, the range loop produces iteration values from 0 up tolen(a)-1
and does not index into the array or slice itself. For anil
slice, the number of iterations is 0. - For a string value, the “range” clause iterates over the Unicode code points in the string starting at byte index 0. On successive iterations, the index value will be the index of the first byte of successive UTF-8-encoded code points in the string, and the second value, of type
rune
, will be the value of the corresponding code point. If the iteration encounters an invalid UTF-8 sequence, the second value will be0xFFFD
, the Unicode replacement character, and the next iteration will advance a single byte in the string. - The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If a map entry that has not yet been reached is removed during iteration, the corresponding iteration value will not be produced. If a map entry is created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is
nil
, the number of iterations is 0. - For channels, the iteration values produced are the successive values sent on the channel until the channel is closed. If the channel is
nil
, the range expression blocks forever. - For an integer value
n
, wheren
is of integer type or an untyped integer constant, the iteration values 0 throughn-1
are produced in increasing order. Ifn
is of integer type, the iteration values have that same type. Otherwise, the type ofn
is determined as if it were assigned to the iteration variable. Specifically: if the iteration variable is preexisting, the type of the iteration values is the type of the iteration variable, which must be of integer type. Otherwise, if the iteration variable is declared by the “range” clause or is absent, the type of the iteration values is the default type forn
. Ifn
<= 0, the loop does not run any iterations. - For a function
f
, the iteration proceeds by callingf
with a new, synthesizedyield
function as its argument. Ifyield
is called beforef
returns, the arguments toyield
become the iteration values for executing the loop body once. After each successive loop iteration,yield
returns true and may be called again to continue the loop. As long as the loop body does not terminate, the “range” clause will continue to generate iteration values this way for eachyield
call untilf
returns. If the loop body terminates (such as by abreak
statement),yield
returns false and must not be called again.
The iteration variables may be declared by the “range” clause using a form of short variable declaration (:=
). In this case their scope is the block of the “for” statement and each iteration has its own new variables [Go 1.22] (see also “for” statements with a ForClause). The variables have the types of their respective iteration values.
If the iteration variables are not explicitly declared by the “range” clause, they must be preexisting. In this case, the iteration values are assigned to the respective variables as in an assignment statement.
var testdata *struct {a *[7]int
}
for i, _ := range testdata.a {// testdata.a is never evaluated; len(testdata.a) is constant// i ranges from 0 to 6f(i)
}var a [10]string
for i, s := range a {// type of i is int// type of s is string// s == a[i]g(i, s)
}var key string
var val interface{} // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]var ch chan Work = producer()
for w := range ch {doWork(w)
}// empty a channel
for range ch {}// call f(0), f(1), ... f(9)
for i := range 10 {// type of i is int (default type for untyped constant 10)f(i)
}// invalid: 256 cannot be assigned to uint8
var u uint8
for u = range 256 {
}// invalid: 1e3 is a floating-point constant
for range 1e3 {
}// fibo generates the Fibonacci sequence
fibo := func(yield func(x int) bool) {f0, f1 := 0, 1for yield(f0) {f0, f1 = f1, f0+f1}
}// print the Fibonacci numbers below 1000:
for x := range fibo {if x >= 1000 {break}fmt.Printf("%d ", x)
}
// output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987// iteration support for a recursive tree data structure
type Tree[K cmp.Ordered, V any] struct {left, right *Tree[K, V]key Kvalue V
}func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool {return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield)
}func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) {t.walk(yield)
}// walk tree t in-order
var t Tree[string, int]
for k, v := range t.Walk {// process k, v
}
Go statements
A “go” statement starts the execution of a function call as an independent concurrent thread of control, or goroutine, within the same address space.
GoStmt = "go" Expression .
The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for expression statements.
The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete. Instead, the function begins executing independently in a new goroutine. When the function terminates, its goroutine also terminates. If the function has any return values, they are discarded when the function completes.
go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Select statements
A “select” statement chooses which of a set of possible send or receive operations will proceed. It looks similar to a “switch” statement but with the cases all referring to communication operations.
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr = Expression .
A case with a RecvStmt may assign the result of a RecvExpr to one or two variables, which may be declared using a short variable declaration. The RecvExpr must be a (possibly parenthesized) receive operation. There can be at most one default case and it may appear anywhere in the list of cases.
Execution of a “select” statement proceeds in several steps:
- For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the “select” statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
- If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the “select” statement blocks until at least one of the communications can proceed.
- Unless the selected case is the default case, the respective communication operation is executed.
- If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned.
- The statement list of the selected case is executed.
select
语句的执行分为以下几个阶段:
- 表达式求值阶段
在进入select
语句时,立即对所有case
中的通道操作数进行一次求值(按源码顺序):- 接收操作(如
case x := <-ch
)的通道(ch
)会被求值。 - 发送操作(如
case ch <- y
)的通道(ch
)和右侧表达式(y
)会被求值。 - 求值结果:生成一组可操作的通道及其对应的发送值。
- 注意:求值过程中的副作用(如函数调用)一定会发生,无论最终选中哪个
case
。 - 例外:短变量声明或赋值语句的左侧表达式(如
case x := <-ch
中的x
)此时不会求值。
- 接收操作(如
- 选择可执行
case
- 如果有至少一个
case
可执行(通道可发送/接收),则随机选择一个(伪均匀随机,避免饥饿)。 - 如果没有
case
可执行:- 存在
default
时,执行default
。 - 不存在
default
时,select
阻塞,直到至少一个case
可执行。
- 存在
- 如果有至少一个
- 执行通信操作
- 如果选中的不是
default
,则执行对应的通道发送或接收操作。
- 如果选中的不是
- 赋值阶段(仅接收操作)
- 如果选中的
case
是带短变量声明或赋值的接收语句(如case x := <-ch
),则此时才求值左侧表达式(x
)并赋值。
- 如果选中的
- 执行选中
case
的代码块- 最后,执行选中
case
的语句列表(如case <-ch: fmt.Println("received")
)。
- 最后,执行选中
Since communication on nil
channels can never proceed, a select with only nil
channels and no default case blocks forever.
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:print("received ", i1, " from c1\n")
case c2 <- i2:print("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3if ok {print("received ", i3, " from c3\n")} else {print("c3 is closed\n")}
case a[f()] = <-c4:// same as:// case t := <-c4// a[f()] = t
default:print("no communication\n")
}for { // send random sequence of bits to cselect {case c <- 0: // note: no statement, no fallthrough, no folding of casescase c <- 1:}
}select {} // block forever
Return statements
A “return” statement in a function F
terminates the execution of F
, and optionally provides one or more result values. Any functions deferred by F
are executed before F
returns to its caller.
ReturnStmt = "return" [ ExpressionList ] .
In a function without a result type, a “return” statement must not specify any result values.
func noResult() {return
}
There are three ways to return values from a function with a result type:
-
The return value or values may be explicitly listed in the “return” statement. Each expression must be single-valued and assignable to the corresponding element of the function’s result type.
func simpleF() int {return 2 }func complexF1() (re float64, im float64) {return -7.0, -4.0 }
-
The expression list in the “return” statement may be a single call to a multi-valued function. The effect is as if each value returned from that function were assigned to a temporary variable with the type of the respective value, followed by a “return” statement listing these variables, at which point the rules of the previous case apply.
func complexF2() (re float64, im float64) {return complexF1() }
-
The expression list may be empty if the function’s result type specifies names for its
result parameters. The result parameters act as ordinary local variables and the function may assign values to them as necessary. The “return” statement returns the values of these variables.
func complexF3() (re float64, im float64) {re = 7.0im = 4.0return }func (devnull) Write(p []byte) (n int, _ error) {n = len(p)return }
Regardless of how they are declared, all the result values are initialized to the zero values for their type upon entry to the function. A “return” statement that specifies results sets the result parameters before any deferred functions are executed.
Implementation restriction: A compiler may disallow an empty expression list in a “return” statement if a different entity (constant, type, or variable) with the same name as a result parameter is in scope at the place of the return.
func f(n int) (res int, err error) {if _, err := f(n-1); err != nil {return // invalid return statement: err is shadowed}return
}
Break statements
A “break” statement terminates execution of the innermost “for”, “switch”, or “select” statement within the same function.
BreakStmt = "break" [ Label ] .
If there is a label, it must be that of an enclosing “for”, “switch”, or “select” statement, and that is the one whose execution terminates.
OuterLoop:for i = 0; i < n; i++ {for j = 0; j < m; j++ {switch a[i][j] {case nil:state = Errorbreak OuterLoopcase item:state = Foundbreak OuterLoop}}}
Continue statements
A “continue” statement begins the next iteration of the innermost enclosing “for” loop by advancing control to the end of the loop block. The “for” loop must be within the same function.
ContinueStmt = "continue" [ Label ] .
If there is a label, it must be that of an enclosing “for” statement, and that is the one whose execution advances.
RowLoop:for y, row := range rows {for x, data := range row {if data == endOfRow {continue RowLoop}row[x] = data + bias(x, y)}}
Goto statements
A “goto” statement transfers control to the statement with the corresponding label within the same function.
GotoStmt = "goto" Label .
goto Error
Executing the “goto” statement must not cause any variables to come into scope that were not already in scope at the point of the goto. For instance, this example:
goto L // BADv := 3
L:
is erroneous because the jump to label L
skips the creation of v
.
A “goto” statement outside a block cannot jump to a label inside that block. For instance, this example:
if n%2 == 1 {goto L1
}
for n > 0 {f()n--
L1:f()n--
}
is erroneous because the label L1
is inside the “for” statement’s block but the goto
is not.
Fallthrough statements
A “fallthrough” statement transfers control to the first statement of the next case clause in an expression “switch” statement. It may be used only as the final non-empty statement in such a clause.
FallthroughStmt = "fallthrough" .
关键规则
- 仅适用于表达式
switch
- 不能用于 类型
switch
(如switch x.(type) { ... }
)。
- 不能用于 类型
- 必须是
case
子句的最后一条非空语句- 如果
case
中有其他语句,fallthrough
必须放在末尾。
- 如果
- 不能用于最后一个
case
或default
- 因为后面没有可跳转的
case
。
- 因为后面没有可跳转的
Defer statements
A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
DeferStmt = "defer" Expression .
The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for expression statements.
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil
, execution panics when the function is invoked, not when the “defer” statement is executed.
For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned. If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)
lock(l)
defer unlock(l) // unlocking happens before surrounding function returns// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {defer fmt.Print(i)
}// f returns 42
func f() (result int) {defer func() {// result is accessed after it was set to 6 by the return statementresult *= 7}()return 6
}
相关文章:
Go 语言规范学习(6)
文章目录 StatementsTerminating statementsEmpty statementsLabeled statementsExpression statementsSend statementsIncDec statementsAssignment statementsIf statementsSwitch statementsExpression switchesType switches For statementsFor statements with single con…...
设计模式——设计模式理念
文章目录 参考:[设计模式——设计模式理念](https://mp.weixin.qq.com/s/IEduZFF6SaeAthWFFV6zKQ)参考:[设计模式——工厂方法模式](https://mp.weixin.qq.com/s/7tKIPtjvDxDJm4uFnqGsgQ)参考:[设计模式——抽象工厂模式](https://mp.weixin.…...
解析 ID 数组传参的解决方案:基于 Axios 的实现
解析 ID 数组传参的解决方案:基于 Axios 的实现 在实际开发中,经常需要将一个 ID 数组作为参数传递给后端接口。然而,不同的后端框架和前端库对数组参数的处理方式可能有所不同。通过一个具体的例子,在前端使用 Axios 框架发送 I…...
C语言快速入门-C语言基础知识
这个c语言入门,目标人群是有代码基础的,例如你之前学过javaSE,看此文章可能是更有帮助,会让你快速掌握他们之间的差异,文章内容大部分都是泛谈,详细的部分我会在之后时间发布,我也在慢慢学习&am…...
Ubuntu 22.04 上安装 VS Code
在 Ubuntu 22.04 上安装 VS Code 的方法如下: 方法 1:通过 APT 包管理器安装 更新系统包索引: 打开终端并执行以下命令: sudo apt update安装依赖项: 执行以下命令以安装所需的依赖项: sudo apt install s…...
AI人工智能-PyCharm的介绍安装应用
下载与安装 创建python项目 项目路径:C:\Users\miloq\Desktop\python_project 配置环境 提前找到conda配置的python-base路径 配置conda环境 运行项目 运行结果...
Todesk介绍
文章目录 ToDesk 软件介绍1. 软件概述2. ToDesk 的功能特点2.1 简单易用2.2 高质量的图像与流畅的操作2.3 跨平台支持2.4 多屏显示与协作2.5 文件传输功能2.6 实时聊天与语音通话2.7 远程唤醒与自动启动2.8 多种权限设置与安全性2.9 无需公网 IP 3. ToDesk 的应用场景3.1 个人使…...
【JavaEE】springMVC返回Http响应
目录 一、返回页面二、Controller和ResponseBody与RestController区别三、返回HTML代码⽚段四、返回JSON五、HttpServletResponse设置状态码六、设置Header6.1 HttpServletResponse设置6.2 RequestMapping设置 一、返回页面 步骤如下: 我们先要在static目录下创建…...
青少年编程与数学 02-011 MySQL数据库应用 02课题、MySQL数据库安装
青少年编程与数学 02-011 MySQL数据库应用 02课题、MySQL数据库安装 一、安装Windows系统Linux系统(以Ubuntu 20.04为例)macOS系统 二、配置(一)Windows系统1. 创建配置文件2. 初始化数据库3. 启动MySQL服务4. 登录MySQL5. 修改ro…...
springboot441-基于SpringBoot的校园自助交易系统(源码+数据库+纯前后端分离+部署讲解等)
💕💕作者: 爱笑学姐 💕💕个人简介:十年Java,Python美女程序员一枚,精通计算机专业前后端各类框架。 💕💕各类成品Java毕设 。javaweb,ssm…...
【安全运营】关于攻击面管理相关概念的梳理(一)
目录 一、ASM 介绍ASM 是“Attack Surface Management”(攻击面管理)的缩写【框架视角,广义概念】1. 介绍2. 兴起的原因3. 工作流程3.1 资产发现3.2 分类和优先级排序3.3 修复3.4 监控 二、EASM 介绍EASM 是 "External Attack Surface M…...
IPv6 网络访问异常 | 时好时坏 / 部分访问正常
注:本文为 “ IPv6 间接性连接异常” 相关文章合辑。 略作重排,未去重。 如有内容异常,请看原文。 IPv6 间接性连接异常?尝试调整路由器的 MTU 设置 Nero978 2024-1-29 17:54 背景 2024 年 1 月 29 日,因寒假返家…...
Unity编辑器功能及拓展(1) —特殊的Editor文件夹
Unity中的Editor文件夹是一个具有特殊用途的目录,主要用于存放与编辑器扩展功能相关的脚本和资源。 一.纠缠不清的UnityEditor 我们Unity中进行游戏构建时,我们经常遇到关于UnityEditor相关命名空间丢失的报错,这时候,只得将报错…...
LLMs之PE:《Tracing the thoughts of a large language model》翻译与解读
LLMs之PE:《Tracing the thoughts of a large language model》翻译与解读 导读:这篇论文的核心贡献在于提出了一种新颖的、基于提示工程的LLMs推理过程追踪技术——“Tracing Thoughts”。该技术通过精心设计的提示,引导LLMs生成其推理过程的…...
[Python] 贪心算法简单版
贪心算法-简单版 贪心算法的一般使用场景是给定一个列表ls, 让你在使用最少的数据的情况下达到或超过n. 我们就来使用上面讲到的这个朴素的例题来讲讲贪心算法的基本模板: 2-1.排序 既然要用最少的数据, 我们就要优先用大的数据拼, 为了实现这个效果, 我们得先给列表从大到小…...
游戏引擎学习第191天
回顾并制定今天的计划 最近几天,我们有一些偏离了原计划的方向,主要是开始了一些调试代码的工作。最初我们计划进行一些调试功能的添加,但是随着工作的深入,我们开始清理和整理调试界面的呈现方式,以便能够做一些更复…...
Git撤回操作全场景指南:未推送与已推送,保留和不保留修改的差异处理
一、未推送到远程仓库的提交(仅本地存在) 特点:可直接修改本地提交历史,不会影响他人 1. 保留修改重新提交 git reset --soft HEAD~1 # 操作效果: # - 撤销最后一次提交 # - 保留工作区所有修改 # - 暂存区内容保持…...
Java 贪吃蛇游戏
这段 Java 代码实现了一个经典的贪吃蛇游戏。玩家可以使用键盘的上下左右箭头键控制蛇的移动方向,蛇会在游戏面板中移动并尝试吃掉随机生成的食物。每吃掉一个食物,蛇的身体会变长,玩家的得分也会增加。如果蛇撞到自己的身体或者撞到游戏面板…...
QT图片轮播器(QT实操学习2)
1.项目架构 1.UI界面 2.widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>#define TIMEOUT 1 * 1000 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent n…...
mac m1/m2/m3 pyaudio的安装
google了很多方法,也尝试了 issue68的方法, 但是均失败了,但是问deepseek竟然成功了,下面是deepseek r1给出的方法。在M3 pro芯片上可以成功运行. 安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent…...
Appium中元素定位的注意点
应用场景 了解这些注意点可以以后在出错误的时候,更快速的定位问题原因。 示例 使用 find_element_by_xx 或 find_elements_by_xx 的方法,分别传入一个没有的“特征“会是什么结果呢? 核心代码 driver.find_element_by_id("xxx") drive…...
《深入探索 Python 数据分析:用 Pandas 高效处理与可视化大型数据集》
《深入探索 Python 数据分析:用 Pandas 高效处理与可视化大型数据集》 引言:从零到分析高手 数据是当代社会最宝贵的资源,而数据分析技能是现代职业人不可或缺的一部分。在数据科学的领域中,Python 已成为当之无愧的“首选语言”,其强大的生态系统和简洁的语法让人如虎添…...
[GWCTF 2019]我有一个数据库1 [CVE phpMyAdmin漏洞]
扫出来一些东西 访问/phpmyadmin 发现界面 这里用到了CVE-2018-12613,光速学习 出现漏洞的代码是: $target_blacklist array (import.php, export.php );// If we have a valid target, lets load that script instead if (! empty($_REQUEST[targe…...
spring 常用注解区别及使用场景
1. 组件注册注解 Bean 作用:用于方法上,表示该方法返回的对象由Spring容器管理。通常用于配置类(Configuration)中,注册第三方库或自定义的Bean。 使用场合: 当你需要将非Spring管理的类(如第…...
【后端】【Django】信号使用详解
Django post_save 信号使用详解(循序渐进) 一、信号的基本概念 Django 的 信号(Signal) 允许不同部分的代码在发生某些事件时进行通信,而不需要直接调用。这种机制可以解耦代码,让不同的模块独立工作。 …...
ML算法数学概念
交叉熵损失(Cross-Entropy Loss) 是机器学习和深度学习中常用的一种损失函数,主要用于衡量两个概率分布之间的差异。它在分类问题中(尤其是多分类问题)被广泛使用,因为它能够有效地评估模型预测的概率分布与…...
wps 怎么显示隐藏文字
wps 怎么显示隐藏文字 》文件》选项》视图》勾选“隐藏文字” wps怎么设置隐藏文字 wps怎么设置隐藏文字...
Vue3 其它API Teleport 传送门
Vue3 其它API Teleport 传送门 在定义一个模态框时,父组件的filter属性会影响子组件的position属性,导致模态框定位错误使用Teleport解决这个问题把模态框代码传送到body标签下...
亚马逊玩具品类技术驱动型选品策略:从趋势洞察到合规基建
一、全球玩具电商技术演进趋势 (技术化重构原市场背景) 数据可视化分析:通过亚马逊SP-API抓取2023年玩具品类GMV分布热力图 监管技术升级: 美国CPSC启用AI质检系统(缺陷识别准确率92.7%) 欧盟EPR合规接口…...
SpringBoot3+EasyExcel通过WriteHandler动态实现表头重命名
方案简介 为了通过 EasyExcel 实现动态表头重命名,可以封装一个方法,传入动态的新表头名称列表(List<String>),并结合 WriteHandler 接口来重命名表头。同时,通过 EasyExcel 将数据直接写入到输出流…...
PHY——LAN8720A 寄存器读写 (二)
文章目录 PHY——LAN8720A 寄存器读写 (二)工程配置引脚初始化代码以太网初始化代码PHY 接口实现LAN8720 接口实现PHY 接口测试 PHY——LAN8720A 寄存器读写 (二) 工程配置 这里以野火电子的 F429 开发板为例,配置以太网外设 这里有一点需要注意原理图 RMII_TXD0…...
HTML5和CSS3的一些特性
HTML5 和 CSS3 是现代网页设计的基础技术,它们引入了许多新特性和功能,极大地丰富了网页的表现力和交互能力。 HTML5 的一些重要特性包括: 新的语义化标签: HTML5 引入了一些重要的语义化标签如 <header>, <footer>, <articl…...
sass报错,忽略 Sass 弃用警告,降级版本
最有效的方法是创建一个 .sassrc.json 文件来配置 Sass 编译器。告诉 Sass 编译器忽略来自依赖项的警告消息。 解决方案: 1. 在项目根目录创建 .sassrc.json 文件: {"quietDeps": true }这个配置会让 Sass 编译器忽略所有来自依赖项&#x…...
DeepSeek+Kimi:PPT制作的效率革命
摘要:传统PPT制作面临模板选择困难、内容逻辑混乱、设计排版能力有限以及反复修改等问题。DeepSeek和Kimi两款AI工具的组合为PPT制作提供了全新的解决方案。DeepSeek擅长内容生成与逻辑推理,能够快速生成高质量的PPT大纲和内容;Kimi则专注于长…...
transformers中学习率warmup策略具体如何设置
在使用 get_linear_schedule_with_warmup(如 Hugging Face Transformers 库中的学习率调度器)时,参数的合理设置需要结合 数据量(dataset size)、批次大小(batch size) 和 训练轮数(…...
linux实现rsync+sersync实时数据备份
1.概述 rsync(Remote Sync) 是一个Unix/linux系统下的文件同步和传输工具 2.端口和运行模式 tcp/873 采用C/S模式(客户端/服务器模式) 3.特点 可以镜像保存整个目录和文件第一次全量备份(备份全部的文件),之后是增量备份(只备份变化的文件) 4. 数…...
CTF类题目复现总结-[MRCTF2020]ezmisc 1
一、题目地址 https://buuoj.cn/challenges#[MRCTF2020]ezmisc二、复现步骤 1、下载附件,得到一张图片; 2、利用010 Editor打开图片,提示CRC值校验错误,flag.png应该是宽和高被修改了,导致flag被隐藏掉;…...
『Linux』 第十一章 线程同步与互斥
1. 线程互斥 1.1 进程线程间的互斥相关背景概念 临界资源:多线程执行流共享的资源就叫做临界资源临界区:每个线程内部,访问临界资源的代码,就叫做临界区互斥:任何时刻,互斥保证有且只有一个执行流进入临界…...
【数据结构】队列
目录 一、队列1、概念与结构2、队列的实现3、队列的初始化4、打印队列数据5、入队6、销毁队列7、队列判空8、出队9、取队头、队尾数据10、队列中有效元素个数 二、源码 个人主页,点击这里~ 数据结构专栏,点击这里~ 一、队列 1、概念与结构 概念&#x…...
【导航定位】GNSS数据协议-RINEX OBS
RINEX协议 RINEX(Receiver INdependent EXchange format,与接收机无关的交换格式)是一种在GPS测量应用中普遍采用的标准数据格式,该格式采用文本文件形式(ASCII码)存储数据数据记录格式与接收机的制造厂商和具体型号无关。目前RINEX版本已经发布到了4.x…...
Qt中的事件循环
Qt的事件循环是其核心机制之一,它是一种消息处理机制,负责处理各种事件(如用户输入、定时器、网络请求等)的分发和处理。Qt中的事件循环是一个持续运行的循环,负责接收事件并将它们分发给相应的对象进行处理。当没有事件需要处理时࿰…...
Android并发编程:线程池与协程的核心区别与最佳实践指南
1. 基本概念对比 特性 线程池 (ThreadPool) 协程 (Coroutine) 本质 Java线程管理机制 Kotlin轻量级并发框架 最小执行单元 线程(Thread) 协程(Coroutine) 创建开销 较高(需分配系统线程资源) 极低(用户态调度) 并发模型 基于线程的抢占式调度 基于协程的协作式调度 2. 核心差异…...
吴恩达深度学习复盘(2)神经网络的基本原理轮廓
笔者注 这两节课主要介绍了神经网络的大的轮廓。而神经网络基本上是在模拟人类大脑的工作模式,有些仿生学的意味。为了便于理解,搜集了一些脑神经的资料,这部分是课程中没有讲到的。 首先要了解一下大脑神经元之间结构。 细胞体࿱…...
【redis】集群 数据分片算法:哈希求余、一致性哈希、哈希槽分区算法
文章目录 什么是集群数据分片算法哈希求余分片搬运 一致性哈希扩容 哈希槽分区算法扩容相关问题 什么是集群 广义的集群,只要你是多个机器,构成了分布式系统,都可以称为是一个“集群” 前面的“主从结构”和“哨兵模式”可以称为是“广义的…...
计算机组成原理笔记(六)——2.2机器数的定点表示和浮点表示
计算机在进行算术运算时,需要指出小数点的位置,根据小数点的位置是否固定,在计算机中有两种数据格式:定点表示和浮点表示。 2.2.1定点表示法 一、基本概念 定点表示法是一种小数点的位置固定不变的数据表示方式,用于表示整数或…...
将树莓派5当做Ollama服务器,C#调用generate的API的示例
其实完全没这个必要,性能用脚后跟想都会很差。但基于上一篇文章的成果,来都来了就先简单试试吧。 先来看看这个拼夕夕上五百多块钱能达到的效果: 只要对速度没要求,那感觉就还行。 Ollama默认只在本地回环(127.0.0…...
MYSQL数据库(一)
一.数据库的操作 1.显示数据库 show databases; 2.创建数据库 create database 数据库名; 3.使用数据库 use 数据库名; 4.删除数据库 drop database 数据库名; drop database if exists 数据库名; 二.表的操作 1.显示所有表 show tables; 2.查看表结构 des…...
Python Cookbook-4.15 字典的一键多值
任务 需要一个字典,能够将每个键映射到多个值上。 解决方案 正常情况下,字典是一对一映射的,但要实现一对多映射也不难,换句话说,即一个键对应多个值。你有两个可选方案,但具体要看你怎么看待键的多个对…...
IDEA 终端 vs CMD:为什么 java -version 显示的 JDK 版本不一致?
前言:离谱的 JDK 版本问题 今天遇到了一个让人抓狂的现象:在 Windows 的 CMD 里输入 java -version 和在 IntelliJ IDEA 终端输入 java -version,居然显示了不同的 JDK 版本! 本以为是环境变量、缓存或者 IDEA 设置的问题&#x…...
Flask登录页面后点击按钮在远程CentOS上自动执行一条命令
templates文件夹和app.py在同一目录下。 templates文件夹下包括2个文件:index.html login.html app.py代码如下: import os import time from flask import Flask, render_template, request, redirect, session, make_response import mysql.con…...