Golang(四)语言特性(下)
xsobi 2024-11-24 00:28 1 浏览
索引表达式 基本表达式形如
a[x]
表示数组的元素、指向数组的指针、切片、字符串或可由x索引的map,值x分别被称为索引键或map键。以下规则适用
如果a不是map
- 索引x必须是整数类型或未定义类型的常量
- 常量索引必须是非负的,并且可以由int类型的值表示
- 非类型化的常量索引被赋予int类型
- 如果x取值超出[0, len(x) - 1]范围,则索引x越界
对于数组类型A
- 常数索引必须在范围内
- 如果x越界,会发生运行时panic
- a[x]是索引位置x处的数组元素,a[x]的类型为数组A的元素类型
对于数组类型的指针
- a[x]是(*a)[x]的缩写
对于切片类型S
- 如果x运行时越界,产生运行时panic
- a[x]是索引位置x处的切片元素,a[x]的类型为切片S的元素类型
对于字符串类型
- 如果字符串a也是常量,则常量索引必须在有效范围内
- 如果发生运行时越界,会产生运行时panic
- a[x]是索引位置x处的byte值,a[x]的类型为byte
- a[x]不可被赋值
对于Map类型M
- 索引x的类型必须可赋值给Map的Key类型
- 如果Map包含key为x的项,a[x]是Map中key为x的元素,a[x]的类型为M的元素类型
- 如果Map是nil或者不包含这样的项,a[x]是M元素类型的零值
否则,a[x]是非法的。在map[K]V类型的map上的索引表达式,用于特殊形式的赋值或初始化
v, ok = a[x]v, ok := a[x]var v, ok = a[x]
额外产生一个非类型化布尔值,如果键x在map中,ok的值为true,否则为false。向nil映射赋值一个元素,引起运行时异常
切片表达式 切片表达式可以从切片、数组、指针数组或切片构造子串。有两种变体: 一种指定下限和上限的简单形式,以及一种指定容量上限的完整形式
简单切片表达式 对于字符串、数组、指针数组或切片a,使用如下基本表达式
a[low : high]
构造子字符串或切片,索引low和high选择操作数a的哪些元素出现在结果中。结果索引从0开始,长度等于 high-low。对数组A进行切片后
a := [5]int{1, 2, 3, 4, 5}s := a[1:4]
切片s具有类型[]int,长度3,容量为4,它元素为
s[0] == 2s[1] == 3s[2] == 4
为方便起见,有些场景可以省略索引。缺省的low索引默认为零;缺省的high索引默认为切片操作数的长度
a[2:] // same as a[2 : len(a)]a[:3] // same as a[0 : 3]a[:] // same as a[0 : len(a)]
如果a是指向数组的指针,a[low : high]是(*a)[low : high]的缩写形式
对于数组或字符串,有效索引范围为: 0 <= low <= high <= len(a),否则越界。对于切片的上边界是它的容量cap(a),而非长度。常量索引必须是非负的,并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。如果两个索引都是常量,它们必须满足low <= high;如果运行时发生索引号越界,会产生运行时panic
除未类型化字符串外,如果切片操作数是字符串或切片,则切片操作的结果是与操作数类型相同的非常量值。对于非类型化字符串操作数,结果是字符串类型的非常量值。如果切片操作数是数组,则它必须是可寻址的,切片操作的结果是与数组具有相同元素类型的切片
如果有效切片表达式的切片操作数为nil,则结果为nil。否则,如果结果是一个切片,则它与操作数共享其基础数组
var a [10]ints1 := a[3:7] // s1的基础数组是数组a; &s1[2] == &a[5]s2 := s1[1:4] // s2的基础数组是s1的基础数组,即数组a; &s2[1] == &a[5]s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; they all refer to the same underlying array element
全切片表达式 对一个数组、指针数组、切片(但不是字符串),基本表达式为
a[low : high : max]
构造一个类型相同、长度和元素与简单切片表达式相同的切片a[low : high]。此外,它通过将结果切片的容量设置为max-low来控制其容量。只能省略第一个索引,它默认为0。对数组a进行切片后
a := [5]int{1, 2, 3, 4, 5}t := a[1:3:5]
切片t具有类型[]int,长度为2,容量为4,其中元素为
t[0] == 2t[1] == 3
作为简单切片表达式,如果a是指向数组的指针,a[low: high : max] 是 (*a)[low : high : max] 的缩写形式。如果切片操作数是数组,则它必须是可寻址的
索引范围在 0 <= low <= high <= max <= cap(a)内为有效,否则视为越界。常量索引必须是非负的,并且可以由int类型的值表示。对于数组,常量索引也必须在有效范围内。如果多个索引是常数,则存在的常数必须在彼此相对的范围内。如果运行时索引越界,会引发运行时panic
类型断言 对接口类型的表达式x和类型T,基础表达式 x.(T)断言x不是nil,并且存储在x中的值是T类型的。表达式x.(T)称为类型断言。更准确的说,如果T不是接口类型,x.(T)断言x的动态类型与T类型相同。在这种情况下,T必须实现x的(接口)类型;否则类型断言无效,因为x不可能存储T类型的值。如果T是接口类型,x.(T)断言x的动态类型实现了接口T
如果类型断言成立,则表达式的值是存储在x中的值,其类型为T;如果类型断言为false,则会发生运行时panic。换句话说,即使x的动态类型只有在运行时才知道,x.(T)的类型在正确的程序中是T
var x interface{} = 7 // x 动态类型是 int 值是 7i := x.(int) // i 类型是 int 值是 7 type I interface { m() } func f(y I) { s := y.(string) // 非法: string 没有s实现 I (缺少方法 m) r := y.(io.Reader) // r 有类型 io.Reader 并且动态类型 y 必须实现 I 和 io.Reader …}
通常在特殊格式的赋值或初始化中使用类型断言
v, ok = x.(T)v, ok := x.(T)var v, ok = x.(T)var v, ok T1 = x.(T)
额外产生一个未定义类型的布尔值,如果断言成立,ok的值为true;否则它是false,并且v的值是类型T的零值。在这些情况中不会发生运行时panic
调用 给定函数类型F的表达式 f(a1, a2, ..., an),使用参数a1,a2,...an来调用函数f。除一种特殊情况外,参数必须是可赋值给F的参数类型的单值表达式,并且在调用函数之前对其求值,表达式的类型是F的结果类型。与方法调用类似,但方法本身被指定为方法的接收方类型值的选择器
math.Atan2(x, y) // function callvar pt *Pointpt.Scale(3.5) // method call with receiver pt
在函数调用中,函数值和参数按通常的顺序计算。在对它们求值之后,调用的参数按值传递给函数,被调用的函数开始执行。当函数返回时,函数的返回参数按值传递回调用函数。调用nil函数值会发生运行时panic
一种特殊情况,如果一个函数或方法g的返回值在数量上是相等的,并且可以单独地分配给另一个函数或方法f的参数,那么调用f(g(parameters_of_g))将g的返回值绑定到f的参数后将调用f。f的调用不能包含除g的调用以外的任何参数,并且g必须至少有一个返回值。如果f有...参数,它被赋值为在指定正则参数后保留的g的返回值
func Split(s string, pos int) (string, string) { return s[0:pos], s[pos:]} func Join(s, t string) string { return s + t} if Join(Split(value, len(value)/2)) != value { log.Panic("test fails")}
如果x的方法集(类型)包含m并且参数列表可以被赋值给m的参数列表,则方法调用x.m()是有效的。如果x是可寻址的并且&x的方法集包含m,x.m()是(&x).m()的缩写形式
var p Pointp.Scale(3.5)
将参数传递给...参数 如果f是带有类型为...T的最终参数p的变量,则在f中,p的类型等同于类型[]T。如果调用f时没有p的实参,则传递给p的值为nil。否则,传递的值是一个类型为[]T的新切片,其中有一个新的底层数组,该数组的连续元素是实参,所有这些参数都必须可赋值给T。因此,切片的长度和容量是绑定到p的参数的数量,并且对于每个调用站点可能不同
func Greeting(prefix string, who ...string)Greeting("nobody") // who 是 nilGreeting("hello:", "Joe", "Anna", "Eileen") // who 的值 []string{"Joe", "Anna", "Eileen"}
如果最后一个参数可赋值给切片类型[]T,则如果参数后面跟有...在这种情况下,不会创建新的切片
s := []string{"James", "Jasmine"}Greeting("goodbye:", s...) // 在 Greeting 中,who与切片s具有相同的值并且共享相同的底层数组
运算符 运算符将操作数合并到表达式中
Expression = UnaryExpr | Expression binary_op Expression .UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = "||" | "&&" | rel_op | add_op | mul_op .rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" .add_op = "+" | "-" | "|" | "^" .mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
对于其他二进制运算符,除非操作涉及移位或非类型化常量,否则操作数类型必须相同。除移位操作外,如果一个操作数是非类型化常量,而另一个操作数不是,则该常量将隐式转换为另一个操作数的类型
移位表达式中的右操作数必须是整数类型,或者是可由uint类型的值表示的非类型化常量。如果非常量移位表达式的左操作数是非类型化常量,则首先将其隐式转换为移位表达式被其左操作数单独替换时假定的类型
var s uint = 33var i = 1<<s // 1 has type intvar j int32 = 1<<s // 1 has type int32; j == 0var k = uint64(1<<s) // 1 has type uint64; k == 1<<33var m int = 1.0<<s // 1.0 has type int; m == 0 if ints are 32bits in sizevar n = 1.0<<s == j // 1.0 has type int32; n == truevar o = 1<<s == 2<<s // 1 and 2 have type int; o == true if ints are 32bits in sizevar p = 1<<s == 1<<33 // illegal if ints are 32bits in size: 1 has type int, but 1<<33 overflows intvar u = 1.0<<s // illegal: 1.0 has type float64, cannot shiftvar u1 = 1.0<<s != 0 // illegal: 1.0 has type float64, cannot shiftvar u2 = 1<<s != 1.0 // illegal: 1 has type float64, cannot shiftvar v float32 = 1<<s // illegal: 1 has type float32, cannot shiftvar w int64 = 1.0<<33 // 1.0<<33 is a constant shift expressionvar x = a[1.0<<s] // 1.0 has type int; x == a[0] if ints are 32bits in sizevar a = make([]byte, 1.0<<s) // 1.0 has type int; len(a) == 0 if ints are 32bits in size
运算符优先级 单目运算符具有最高的优先级。由于++和--运算符形式语句,而不是表达式,因此它们不属于运算符层次结构。因此,语句*p++与(*p)++相同
二进制运算符有5个优先级别,乘法运算符最强,然后是加法运算符、比较运算符,&&(逻辑与),最后是||(逻辑或)
Precedence Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
具有相同优先级的二元运算符按从左到右结合。例如,x / y * z与(x / y) * z相同
+x23 + 3*x[i]x <= f()^a >> bf() || g()x == y+1 && <-chanPtr > 0
算术运算符 算术运算符应用于数值并产生与第一个操作数类型相同的结果。四个标准算术运算符(+,-,*,/)应用于整数、浮点数和复数类型; + 也可以用于字符串类型。模运算符、按位逻辑运算符和移位运算符仅适用于整数
+ sum integers, floats, complex values, strings- difference integers, floats, complex values* product integers, floats, complex values/ quotient integers, floats, complex values% remainder integers & bitwise AND integers| bitwise OR integers^ bitwise XOR integers&^ bit clear (AND NOT) integers << left shift integer << unsigned integer>> right shift integer >> unsigned integer
整数运算符 对于两个整数值x和y,整数商 q = x / y 和余数 r = x % y满足下列关系
x = q*y + r and |r| < |y|
x / y 被截断为零
x y x / y x % y 5 3 1 2-5 3 -1 -2 5 -3 -1 2-5 -3 1 -2
另一个规则是如果被除数x是整数类型x的最负值,则商 q = x / -1 等于x(且r=0),这是由于2的补整数溢出所致
x qint8 -128int16 -32768int32 -2147483648int64 -9223372036854775808
如果除数是常数,必须非零,否则会发生运行时panic。如果被除数是非负的,除数是2的常数幂,除数可以用右移运算符代替,余数可以用按位运算(&)代替
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3-11 -2 -3 -3 1
移位运算符按右操作数指定的移位计数移位左操作数,该移位计数必须为正;如果移位计数为负,会发生运行时panic。如果左操作数是有符号整数,则移位运算符实现算术移位;如果是无符号整数,则移位运算符实现逻辑移位。移位计数没有上限,移位的行为就像在移位计数为n时将左操作数1移位n次。因此,x<<1与x*2相同,x>>1与x/2相同,但被截断为负无穷大
对于整数操作数,一元运算符+、-、^被定义为
+x is 0 + x-x negation is 0 - x^x bitwise complement is m ^ x with m = "all bits set to 1" for unsigned x and m = -1 for signed x
整数溢出 对于无符号整数值,运算+、-、*和<<是以2n为模计算的,其中n是无符号整数值类型的位宽度。这些无符号整数操作在溢出时丢弃高位,程序可能依赖于“环绕”
对于有符号整数,运算+、-、*、/、<<可能合法溢出,结果值存在,并由有符号整数表示、运算及其操作数确定定义,溢出不会发生运行时panic。编译器可能不会在不发生溢出的假设下优化代码,例如,它可能不会假设x < x + 1总是对的
浮点运算符 对于浮点数和复数,+x 与 x 相同,而 -x 是 x 的反。浮点或复数除零的结果没有超出IEEE-754标准,是否发生运行时panic是特定于实现的
一个实现可以将多个浮点操作组合成一个单一的融合操作(可能是跨语句的),并产生不同于单独执行和舍入指令所获得的值的结果;显式浮点类型转换将舍入到目标类型的精度,防止聚变而丢弃舍入
例如,一些体系结构提供了一个"融合乘法和加法"(FMA)指令,该指令在不舍入中间结果x*y的情况下计算x*y+z。这些示例显示go实现何时可以使用该指令
// FMA allowed for computing r, because x*y is not explicitly rounded:r = x*y + zr = z; r += x*yt = x*y; r = t + z*p = x*y; r = *p + zr = x*y + float64(z) // FMA disallowed for computing r, because it would omit rounding of x*y:r = float64(x*y) + zr = z; r += float64(x*y)t = float64(x*y); r = t + z
字符串连接 字符串可以通过+操作符连接或使用+=赋值操作符
s := "hi" + string(c)s += " and good bye"
字符串加法通过连接操作数创建新字符串
比较运算符 比较运算符比较两个操作数并生成非类型化布尔值
== equal!= not equal< less<= less or equal> greater>= greater or equal
在任何比较中,第一个操作数必须可赋值给第二个操作数的类型,反之亦然
等式运算符==和!=应用于可比较的操作数,排序操作符<、<=、>、>=应用于操作数的排序。这些术语和比较结果定义如下
- 布尔值是可比较的;如果两个布尔值都为真或都为假,则它们相等
- 整数值是可比较的,并按常规方式排序
- 根据IEEE-754标准的定义,浮点值具有可比性和顺序性
- 复数值具有可比性,两个复数值u和v,如果它们的实部和虚部分别相等,那么它们是相等的
- 字符串值具有可比性和顺序性
- 指针值是可比较的;如果两个指针值指向同一个变量,或者两者的值都为nil,则它们相等。指向不同零大小变量的指针可能相等,也可能不相等
- 通道值是可比较的;如果两个通道值是由同一个调用make创建的,或者两者的值都为nil,则它们相等
- 接口类型是可比较的;如果两个接口值具有相同的动态类型和相同的动态值,或者如果两个接口值都具有值nil,则它们相等
- 非接口类型X的一个值x和一个接口类型T的值t是可比较的,当类型X的值是可比较的并且类型X实现接口类型T;如果t的动态类型与x相同,且t的动态值等于x,则它们相等
- 如果所有字段都是可比较的,则结构值是可比较的;如果对应的非空字段相等,则两个结构值相等
- 如果数组元素类型的值是可比较的,则数组值是可比较的;如果两个数组的对应元素相等,则两个数组值相等
如果比较具有相同动态类型的两个不可比较接口的值,则会导致运行时panic。此行为不仅适用于直接的接口值比较,还适用于将接口值数组或结构与接口值字段进行比较
切片、map和函数值是不可比较的。但是,作为一种特殊情况,切片、map或函数值可以与预声明的标识符nil进行比较。指针、通道和接口值也可以与nil进行比较,并遵循上述一般规则
const c = 3 < 4 // c 是未定义的布尔常量true type MyBool boolvar x, y intvar ( // 这些比较结果是未定义的布尔类型. // 通用的赋值规则适用. b3 = x == y // b3 has type bool b4 bool = x == y // b4 has type bool b5 MyBool = x == y // b5 has type MyBool)
逻辑运算符 应用于布尔值并产生与相同类型操作数的结果。右操作数是按条件计算的
&& conditional AND p && q is "if p then q else false"|| conditional OR p || q is "if p then true else q"! NOT !p is "not p"
地址运算符 对于一个T类型的操作数x,取地址操作&x生成类型为*T并指向x的指针。操作数必须是可寻址的,即变量、指针间接寻址或切片索引操作;或可寻址结构操作数的字段选择器;或可寻址数组的数组索引操作。作为可寻址性要求的一个例外,x也可以是(可能是用括号括起来的)复合文本。如果对x的求值会导致运行时panic,那么对&x的求值也会导致panic
对于指针类型*T的操作数x,间接指针*x表示由x指向的类型T的变量。如果x是nil,试图计算*x将会导致运行时panic
&x&a[f(2)]&Point{2, 3}*p*pf(x) var x *int = nil*x // causes a run-time panic&*x // causes a run-time panic
接收操作符 对于一个通道类型的操作数ch,接收操作的值<-ch表示从通道ch接收到的值。通道方向必须允许接收操作,接收操作的类型是通道的元素类型。表达式将阻塞,直到某个值可用为止;从nil通道接收值永远处于阻塞状态。在关闭的通道时接收操作总是立即处理,在接收到以前发送的任何值之后,将生成元素类型的零值
v1 := <-chv2 = <-chf(<-ch)<-strobe // 等待时钟脉冲并丢弃接收值
在特殊格式的赋值或初始化中使用的接收表达式
x, ok = <-chx, ok := <-chvar x, ok = <-chvar x, ok T = <-ch
生成一个附加的非类型化布尔结果,报告通信是否成功。如果接收到的值是通过发送操作成功传递到通道的,则ok的值为true;而如果是由于通道关闭且为空而生成的零值,则为false
转换 转换将表达式的类型更改为转换指定的类型,转换可以按字面意思出现在源代码中,也可以由表达式出现的上下文暗示。显式转换是T(x)形式的表达式,其中T是类型,x是可以转换为类型T的表达式
Conversion = Type "(" Expression [ "," ] ")" .
如果类型以运算符*、<-开头,或者类型以关键字func开头,但没有返回结果列表,必要时必须用括号括起来以避免歧义
*Point(p) // same as *(Point(p))(*Point)(p) // p is converted to *Point<-chan int(c) // same as <-(chan int(c))(<-chan int)(c) // c is converted to <-chan intfunc()(x) // function signature func() x(func())(x) // x is converted to func()(func() int)(x) // x is converted to func() intfunc() int(x) // x is converted to func() int (unambiguous)
如果x可以由T类型的值表示,则常量x可以转换为类型T。作为特殊情况,整型常量x可以使用与非常量x相同的规则显式转换为字符串类型。转换一个常量会产生一个类型化的常量
uint(iota) // uint类型的iota值float32(2.718281828) // 2.718281828 float32类型complex128(1) // 1.0 + 0.0i of type complex128float32(0.49999999) // 0.5 of type float32float64(-1e-1000) // 0.0 of type float64string('x') // "x" of type stringstring(0x266c) // "?" of type stringMyString("foo" + "bar") // "foobar" of type MyStringstring([]byte{'a'}) // not a constant: []byte{'a'} is not a constant(*int)(nil) // not a constant: nil is not a constant, *int is not a boolean, numeric, or string typeint(1.2) // illegal: 1.2 cannot be represented as an intstring(65.0) // illegal: 65.0 is not an integer constant
以下情况下,非常量值x可以转换为类型T
- x可赋值给T
- 忽略结构标记(见下文),x和T具有相同的基础类型
- 忽略结构标记(见下文),x和T是未定义类型的指针类型,它们的指针基类型具有相同的基础类型
- x和T都是整数或浮点类型
- x和T都是复数类型
- x是整数、byte切片或rune类型,T是string byte
- x是string类型,T是byte切片或rune类型
在比较用于转换的标识的结构类型时,将忽略结构标记
type Person struct { Name string Address *struct { Street string City string }} var data *struct { Name string `json:"name"` Address *struct { Street string `json:"street"` City string `json:"city"` } `json:"address"`} var person = (*Person)(data) // ignoring tags, the underlying types are identical
适用于数字类型之间的(非常量)转换或字符串类型之间转换的特定规则,这些转换可能会改变x的表示形式并产生运行时成本。所有其他转换只更改x的类型,而不更改x的表示形式
指针和整数之间没有转换的语言机制,包unsafe在受限制的情况下实现此功能
数字类型之间的转换 对于非常数数字值转换,以下规则适用
- 整数之间的转换,如果值是有符号整数,则将其符号扩展为隐式无限精度;否则将其扩展为零。然后将其截断以适合结果类型的大小。例如,如果 v := uint16(0x10f0),则uint32(int8(v)) == 0xFFFFFFF0;转换总是产生一个有效值;没有溢出的迹象
- 将浮点数转换为整数时,小数将被丢弃(向零截断)
- 当转换一个整数或浮点数到浮点类型,或者转换一个复数到复数类型,结果值四舍五入到目标类型指定的精度。例如,float32类型的变量x的值可以使用超出IEEE-754 32位数字的附加精度来存储,但是float32(x)表示将x的值舍入到32位精度的结果。类似地,x + 0.1可能使用超过32位的精度,但是float32(x+0.1)不使用
在所有涉及浮点值或复数值的非常量转换中,如果结果类型不能表示转换成功的值,但结果值依赖于实现
字符串类型之间的转换
- 将有符号或无符号整数值转换为字符串类型将生成包含整数的UTF-8表示形式的字符串,超出有效Unicode代码点范围的值将转换为"\uFFFD"
string('a') // "a"string(-1) // "\ufffd" == "\xef\xbf\xbd"string(0xf8) // "\u00f8" == "?" == "\xc3\xb8"type MyString stringMyString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
- 将byte切片转换为字符串类型会产生一个字符串,该字符串的连续字节是该切片的元素
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hell?"string([]byte{}) // ""string([]byte(nil)) // "" type MyBytes []bytestring(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hell?"
- 将宽字符切片转换为字符串类型将生成一个字符串,该字符串是转换为单个宽字符值的连接
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"string([]rune{}) // ""string([]rune(nil)) // "" type MyRunes []runestring(MyRunes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
- 将字符串类型的值转换为byte切片类型将产生一个连续的元素为字符串字节的切片
[]byte("hell?") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}[]byte("") // []byte{} MyBytes("hell?") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
- 将字符串类型的值转换为runes类型的切片将生成一个包含字符串的各个Unicode代码点的切片
[]rune(MyString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4}[]rune("") // []rune{} MyRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
常量表达式 常量表达式只能包含常量操作数,并在编译时计算。非类型化布尔、数字和字符串常量可以用作操作数,只要分别使用布尔、数字或字符串类型的操作数是合法的
常量比较总是产生非类型化的布尔常量。如果常量移位表达式的左操作数是非类型化常量,则结果是整型常量;否则,它是与左操作数类型相同的常量,必须是整型
对非类型化常量执行的任何其他操作都会生成同类的非类型化常量,即布尔、整数、浮点、复数或字符串常量。如果二进制操作(移位除外)的非类型化操作数是不同类型的,则结果是此列表后面显示的操作数类型:整数、rune、浮点、复数。例如,非类型化整型常数除以非类型化复常数,得到非类型化复常数
const a = 2 + 3.0 // a == 5.0 (untyped floating-point constant)const b = 15 / 4 // b == 3 (untyped integer constant)const c = 15 / 4.0 // c == 3.75 (untyped floating-point constant)const Θ float64 = 3/2 // Θ == 1.0 (type float64, 3/2 is integer division)const Π float64 = 3/2. // Π == 1.5 (type float64, 3/2. is float division)const d = 1 << 3.0 // d == 8 (untyped integer constant)const e = 1.0 << 3 // e == 8 (untyped integer constant)const f = int32(1) << 33 // illegal (constant 8589934592 overflows int32)const g = float64(2) >> 1 // illegal (float64(2) is a typed floating-point constant)const h = "foo" > "bar" // h == true (untyped boolean constant)const j = true // j == true (untyped boolean constant)const k = 'w' + 1 // k == 'x' (untyped rune constant)const l = "hi" // l == "hi" (untyped string constant)const m = string(k) // m == "x" (type string)const Σ = 1 - 0.707i // (untyped complex constant)const Δ = Σ + 2.0e-4 // (untyped complex constant)const Φ = iota*1i - 1/1i // (untyped complex constant)
应用内置函数complex到未定义整数、rune、或浮点数常量产生一个非类型化的复数常量
const ic = complex(0, c) // ic == 3.75i (untyped complex constant)const iΘ = complex(0, Θ) // iΘ == 1i (type complex128)
常量表达式的计算总是准确的;中间值和常量本身可能需要的精度远远大于语言中任何预声明类型所支持的精度。以下是合法声明
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (untyped integer constant)const Four int8 = Huge >> 98 // Four == 4 (type int8)
常数除法或余数运算的除数不能为零
3.14 / 0.0 // illegal: division by zero
类型化常量的值必须始终可以由常量类型的值精确表示。以下常量表达式是非法的
uint(-1) // -1 cannot be represented as a uintint(3.14) // 3.14 cannot be represented as an intint64(Huge) // 1267650600228229401496703205376 cannot be represented as an int64Four * 300 // operand 300 cannot be represented as an int8 (type of Four)Four * 100 // product 400 cannot be represented as an int8 (type of Four)
一元逐位补码运算符^使用的掩码与非常量的规则匹配:对于无符号常量,掩码均为1;对于有符号常量和非类型常量,掩码均为-1
^1 // untyped integer constant, equal to -2uint8(^1) // illegal: same as uint8(-2), -2 cannot be represented as a uint8^uint8(1) // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)int8(^1) // same as int8(-2)^int8(1) // same as -1 ^ int8(1) = -2
实现限制:编译器在计算非类型浮点或复数常量表达式时可能使用舍入,这种舍入可能导致浮点常量表达式在整数上下文中无效,即使它在使用无限精度计算时是整数,反之亦然
计算顺序 在包级别,初始化依赖项确定变量声明中单个初始化表达式的求值顺序。否则,在对表达式、赋值或返回语句的操作数求值时,所有函数调用、方法调用和通信操作都将按从左到右的词法顺序求值。例如,在函数局部赋值中
y[f()], ok = g(h(), i()+x[j()], <-c), k()
函数调用和通信按f()、h()、i()、j()、<-c、g()和k()的顺序进行,但未指定这些事件相对于x的求值和索引以及y的求值的顺序
a := 1f := func() int { a++; return a }x := []int{a, f()} // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specifiedm := map[int]int{a: 1, a: 2} // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specifiedn := map[int]int{a: f()} // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified
在包级别,初始化依赖项覆盖单个初始化表达式的从左到右规则,但不覆盖每个表达式中的操作数
var a, b, c = f() + v(), g(), sqr(u()) + v() func f() int { return c }func g() int { return a }func sqr(x int) int { return x*x } // functions u and v are independent of all other variables and functions
函数调用的顺序是u()、sqr()、v()、f()、v()和g()
根据运算符的关联性计算单个表达式中的浮点运算,显式括号通过重写默认优先级来影响计算。在表达式x+(y+z) 中,在与x求和之前执行y+z
语句 语句控制执行
Statement = Declaration | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
终止语句 终止语句阻止在同一块中以词汇形式出现在它后面的所有语句执行,以下终止语句
- return或goto语句
- 调用内置函数panic
- 语句列表以终止语句结尾的块
- 一个if语句块,其中
- else分支语句块存在,并且
- 两个分支都是终止语句块
- 一个for语句块,其中
- 没有引用break语句,并且
- 不存在循环条件
- 一个switch语句块,其中
- 没有引用break语句
- 存在default情况,并且
- 在每种情况下的语句列表(包括default语句)都以终止语句或者一个可能被标记为"fallthrough"的语句结尾
- select语句块,其中
- 没有引用break语句
- 在每种情况下,语句都会以终止语句结尾,包括default(如果存在)
- 标记终止语句的标记语句
所有其他语句块都不是终止语句块。如果列表不为空并且其最后一个非空语句是终止块,则语句列表以终止语句结尾
空语句 不起任何作用
EmptyStmt = .
标记语句 可以是goto、break或continue语句的目标
LabeledStmt = Label ":" Statement .Label = identifier . Error: log.Panic("error encountered")
表达式语句 除了特定的内置函数外,函数和方法调用以及接收操作可以出现在语句上下文中。这些陈述可以用括号括起来
ExpressionStmt = Expression .
语句上下文中不允许使用以下内置函数
append cap complex imag len make new realunsafe.Alignof unsafe.Offsetof unsafe.Sizeof h(x+y)f.Close()<-ch(<-ch)len("foo") // illegal if len is the built-in function
发送语句 在通道上发送值,通道表达式必须是channel类型,通道方向必须允许发送操作,要发送的值的类型必须可赋值给通道的元素类型
SendStmt = Channel "<-" Expression .Channel = Expression .
通道和值表达式都是在通信开始之前计算的。通信阻塞,直到发送可以继续:如果接收器准备就绪,则无缓冲信道上的发送可以继续;如果缓冲区中有空间,缓冲通道上的发送可以继续。在一个关闭的通道上发送会引起运行时的panic。nil通道上的发送会永远阻塞
ch <- 3 // send value 3 to channel ch
自增/自减语句 ++和--语句按非类型常量1自增或自减其操作数。与赋值一样,操作数必须是可寻址的或映射可索引的表达式
IncDecStmt = Expression ( "++" | "--" ) .
以下赋值语句在语义上是等价的
IncDec statement Assignmentx++ x += 1x-- x -= 1
赋值
Assignment = ExpressionList assign_op ExpressionList . assign_op = [ add_op | mul_op ] "=" .
每个左侧操作数都必须是可寻址的、映射索引表达式或(仅适用于=赋值)空标识符。操作数可以用括号括起来
x = 1*p = f()a[i] = 23(k) = <-ch // same as: k = <-ch
赋值操作 x op= y,其中op是二进制算术运算符,与 x = x op (y) 等价,但只对x求值一次。op= 构造是单个标记。在赋值操作中,左边和右边的表达式列表必须正好包含一个单值表达式,左边的表达式不能是空标识符
a[i] <<= 2i &^= 1<<n
元组赋值将多值操作的各个元素赋给变量列表。有两种形式,在第一种情况下,右边的操作数是单个多值表达式,例如函数调用、通道或映射操作或类型断言。左侧的操作数必须与值的数目匹配。例如,如果f是返回两个值的函数
x, y = f()
将第一个值赋给x,将第二个值赋给y。在第二种形式中,左边的操作数必须等于右边的表达式数,每个表达式都必须是单值的,右边的第n个表达式赋给左边的第n个操作数
one, two, three = '一', '二', '三'
空白标识符提供了一种忽略赋值中右侧值的方法
_ = x // 计算 x 但忽略它x, _ = f() // 计算 f() 但忽略第二个结果值
赋值分两阶段进行,首先,左边的索引表达式和指针间接(包括选择器中的隐式指针间接)的操作数和右边的表达式都按通常的顺序计算。其次,赋值按从左到右的顺序进行
a, b = b, a // exchange a and b x := []int{1, 2, 3}i := 0i, x[i] = 1, 2 // set i = 1, x[0] = 2 i = 0x[i], i = 2, 1 // set x[0] = 2, i = 1 x[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 *Pointx[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7 i = 2x = []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 == []int{3, 5, 3}
在赋值中,每个值都必须可赋值给它被赋值的操作数类型,有以下特殊情况
- 任何类型的值都可以赋值给空白标识符
- 如果将非类型化常量分配给接口类型的变量或空标识符,则首先将该常量隐式转换为其默认类型
- 如果将非类型化布尔值赋给接口类型的变量或空标识符,则首先将其隐式转换为bool类型
If语句 根据布尔表达式的值指定两个分支的条件执行。如果表达式的计算结果为true,则执行if分支,否则,执行else分支(如果存在)
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] . if x > max { x = max}
表达式前面可以有一个简单语句,该语句在计算表达式之前执行
if x := f(); x < y { return x} else if x > z { return z} else { return y}
switch语句 提供多路执行,表达式或类型说明符与switch中的cases进行比较,以确定要执行的分支
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
有两种形式:表达式switch和类型switch。在表达式switch中,cases包含与switch表达式的值进行比较的表达式。在类型switch中,cases包含与特殊注释的switch表达式的类型进行比较的类型。switch表达式在switch语句中只计算一次
表达式switch 在表达式switch中,将对switch表达式求值,并从左到右和从上到下求值不必为常量的case表达式;第一个等于switch表达式的表达式将触发执行关联case的语句;其他情况将被跳过
如果没有case匹配,并且存在default case,则执行其语句。最多可以有一个default case,它可能出现在switch语句中的任何位置,缺省的switch表达式等于布尔值true
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .ExprCaseClause = ExprSwitchCase ":" StatementList .ExprSwitchCase = "case" ExpressionList | "default" .
如果switch表达式的计算结果为非类型化常量,则首先隐式转换为其默认类型;如果是非类型化布尔值,则首先隐式转换为bool类型。预声明的非类型化值nil不能用作switch表达式
如果case表达式是非类型化的,则首先将其隐式转换为switch表达式的类型。对于每个(可能转换的)cases表达式x和swicth表达式的值t,x==t 必须是有效的比较
换言之,switch表达式被视为是用来声明和初始化临时变量t而不是显式类型;它是测试每个case表达式x是否相等的t值
在case或default子句中,最后一个非空语句可以是(可能标记为)fallthrough语句,指示控件应该从该子句的结尾流向下一个子句的第一个语句。否则,控制流将流向switch语句的末尾。fallthrough语句可以出现在表达式switch的除最后一个子句外的所有语句的最后一个语句
switch表达式前面可以有一个简单语句,该语句在计算表达式之前执行
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 -xdefault: return x} switch {case x < y: f1()case x < z: f2()case x == 4: f3()}
实现限制:编译器可能不允许对的多个case表达式的同一常量求值。例如,当前编译器不允许在case表达式中使用重复的整数、浮点或字符串常量
类型switch 用于比较类型而不是值,它在其他方面类似于表达式switch。它由一个特殊的switch表达式标记,该表达式具有使用保留字类型而不是实际类型的类型断言的形式
switch x.(type) {// cases}
然后,case将实际类型t与表达式x的动态类型相匹配。与类型断言一样,x必须是接口类型,case中列出的每个非接口类型t必须实现x的类型。类型switch的case中列出的类型必须都是不同的
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .TypeCaseClause = TypeSwitchCase ":" StatementList .TypeSwitchCase = "case" TypeList | "default" .TypeList = Type { "," Type } .
TypeSwitchGuard可能包括一个短变量声明,当使用该形式时,变量将在每个子句的隐式块中的TypeSwitchCase末尾声明。在case列表类型的子句中,变量具有该类型;否则,变量具有TypeSwitchGuard中表达式的类型
一个case可以使用预先声明的标识符nil,而不是一个类型;当TypeSwitchGuard中的表达式是nil接口值时,将选择该case,最多只能有一个nil的case
给定一个类型为interface{}的表达式x,下面的类型swicth
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 intcase float64: printFloat64(i) // type of i is float64case func(int) float64: printFunction(i) // type of i is func(int) float64case 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{})}
可以重写为
v := x // x is evaluated exactly onceif 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") }}
类型switch保护可以在前面加上一个简单语句,该语句在对保护求值之前执行。类型开关中不允许使用fallthrough语句
For语句 指定语句块的重复执行,有三种形式:可以由一个条件控制迭代、一个for子句或一个range子句控制
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .Condition = Expression .
单条件的For语句 在最简单的形式中,for语句指定块的重复执行,只要布尔条件的计算结果为true。条件在每次迭代前都会被计算出来。如果条件不存在,则相当于布尔值true
for a < b { a *= 2}
For语句与for子句 带有ForClause的For语句也受其条件控制,但它还可以指定init和post语句,例如,赋值语句、递增和递减语句。init语句可以是短变量声明,但post语句不能。init语句声明的变量在每次迭代中都会被重用
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .InitStmt = SimpleStmt .PostStmt = SimpleStmt . for i := 0; i < 10; i++ { f(i)}
如果非空,则在计算第一次迭代的条件之前执行init语句一次;post语句在每次执行块之后执行(并且仅在执行块时)。ForClause的任何元素都可以为空,但分号是必需的,除非只有条件。如果条件不存在,则相当于布尔值true
for cond { S() } is the same as for ; cond ; { S() }for { S() } is the same as for true { S() }
For语句与range子句 带有range子句的for语句遍历数组、切片、字符串、map的所有条目或通道上接收到的值。对于每个条目,它将迭代值赋给相应的迭代变量(如果存在),然后执行块
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
range子句右侧的表达式称为range表达式,它可以是数组、指向数组的指针、切片、字符串、映射或允许接收操作的通道。与赋值一样,如果存在,则左侧的操作数必须是可寻址或映射索引表达式;它们表示迭代变量。如果range表达式是通道,则最多允许一个迭代变量,否则最多可以有两个。如果最后一个迭代变量是空标识符,则range子句等同于没有该标识符的同一子句
在开始循环之前,对range表达式x求值一次,但有一个例外: 如果至多存在一个迭代变量且len(x)为常量,则range表达式不会被求值
左边的函数调用在每次迭代中计算一次,对于每个迭代,如果存在相应的迭代变量,则按如下方式生成迭代值
Range expression 1st value 2nd value array or slice a [n]E, *[n]E, or []E index i int a[i] Estring s string type index i int see below runemap m map[K]V key k K m[k] Vchannel c chan E, <-chan E element e E
- 对于数组、指针数组或切片a,从元素索引0开始,按递增顺序生成索引迭代值。如果最多存在一个迭代变量,则range循环将生成从0到len(a)-1的迭代值,并且不会索引到数组或切片本身。对于nil切片,迭代次数为0
- 对于string类型的值,range子句从字节索引0开始的Unicode代码点迭代字符串。在连续的迭代中,索引值将是字符串中连续的UTF-8编码代码点的第一个字节的索引,第二个值(rune类型)将是相应代码点的值。如果迭代遇到一个无效的UTF-8序列,第二个值将是0xFFFD(Unicode替换字符),下一个迭代将在字符串中前进一个字节
- 未指定map上的迭代顺序,并且不能保证从一次迭代到下一次迭代的顺序相同。如果在迭代过程中移除尚未到达的映射项,则不会生成相应的迭代值;如果在迭代期间创建了映射项,则该项可能在迭代期间生成,也可能被跳过。对于创建的每个项以及从一个迭代到下一个迭代,选择可能会有所不同。如果映射为nil,则迭代次数为0
- 对于通道,生成的迭代值是通道上发送的连续值,直到通道关闭。如果通道为nil,则range表达式将永远阻塞
在赋值语句中,迭代值被赋给相应的迭代变量。迭代变量可以由range子句使用短变量声明(:=)的形式声明。在这种情况下,它们的类型被设置为各自迭代值的类型,它们的作用域是for语句的块;它们在每次迭代中都被重用。如果迭代变量是在for语句之外声明的,则在执行之后,它们的值将是上一次迭代的值
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 6 f(i)} var a [10]stringfor i, s := range a { // type of i is int // type of s is string // s == a[i] g(i, s)} var key stringvar val interface {} // element type of m is assignable to valm := 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 channelfor range ch {}
Go语句 go语句在同一地址空间内以独立的并发控制线程或goroutine的形式开始执行函数调用
GoStmt = "go" Expression .
表达式必须是函数或方法调用;不能用括号括起来。内置函数的调用与表达式语句一样受到限制
函数值和参数的计算通常与调用goroutine中的一样,但与常规调用不同,程序执行不会等待调用的函数完成。相反,函数开始在新的goroutine中独立执行。当函数终止时,其goroutine也将终止。如果函数有任何返回值,则在函数完成时丢弃它们
go Server()go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Select语句 选择一组可能的发送或接收操作中的哪一个将继续。它看起来类似于switch语句,但在所有情况下都是指通信操作
SelectStmt = "select" "{" { CommClause } "}" .CommClause = CommCase ":" StatementList .CommCase = "case" ( SendStmt | RecvStmt ) | "default" .RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .RecvExpr = Expression .
带有RecvStmt的case可以将RecvExpr的结果赋给一个或两个变量,这些变量可以使用短变量声明来声明。RecvExpr必须是(可能用括号括起来的)接收操作。最多可以有一个default case,它可能出现在case列表中的任何位置
select语句的执行分几个步骤执行
- 对于语句中的所有cases,在输入select语句时,接收操作的通道操作数和发送语句的通道和右侧表达式将按源顺序精确计算一次,结果是一组要接收或发送到的通道,以及要发送的相应值。无论选择哪个(如果有的话)通信操作继续,都将发生计算中的任何副作用。带有短变量声明或赋值的RecvStmt左侧的表达式尚未求值
- 如果一个或多个通信可以继续,则通过统一的伪随机选择来选择可以继续的单个通信。否则,如果存在default case,则选择该case。如果没有default case,select语句将阻塞,直到至少一个通信可以继续
- 除非所选的是default case,否则执行相应的通信操作
- 如果所选的case是带有短变量声明或赋值的RecvStmt,则计算左侧表达式并赋值接收值
- 执行所选case的语句列表
由于nil通道上的通信永远无法继续,因此只有nil通道且没有default case的select将永远阻塞
var a []intvar c, c1, c2, c3, c4 chan intvar i1, i2 intselect {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 := <-c3 if ok { print("received ", i3, " from c3\n") } else { print("c3 is closed\n") }case a[f()] = <-c4: // same as: // case t := <-c4 // a[f()] = tdefault: print("no communication\n")} for { // send random sequence of bits to c select { case c <- 0: // note: no statement, no fallthrough, no folding of cases case c <- 1: }} select {} // block forever
返回语句 函数F中的return语句终止F的执行,并可选地提供一个或多个返回结果值。任何被F延迟的函数都在F返回其调用方之前执行
ReturnStmt = "return" [ ExpressionList ] .
在没有结果类型的函数中,return语句不能指定任何结果值
func noResult() { return}
有三种方法可以从带有结果类型的函数返回值
- 返回值可以在return语句中显式列出,每个表达式必须是单值的,并且可以赋值给函数结果类型的相应元素
func simpleF() int { return 2} func complexF1() (re float64, im float64) { return -7.0, -4.0}
- return语句中的表达式列表可以是对多值函数的单个调用。其效果就好像从该函数返回的每个值都被赋给了一个临时变量,其类型是相应的值,后面跟着一个列出这些变量的return语句,在这一点上,前一种情况的规则适用
func complexF2() (re float64, im float64) { return complexF1()}
- 如果函数的结果类型指定其结果参数的名称,则表达式列表可能为空。结果参数充当普通局部变量,函数可以根据需要为其赋值。return语句返回这些变量的值
func complexF3() (re float64, im float64) { re = 7.0 im = 4.0 return} func (devnull) Write(p []byte) (n int, _ error) { n = len(p) return}
无论它们是如何声明的,在进入函数时,所有结果值都会初始化为其类型的零值。指定results的return语句在执行任何延迟函数之前设置结果参数
实现限制:如果与结果参数同名的其他实体(常量、类型或变量)在返回位置的作用域中,编译器可能会禁止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语句 break语句终止同一函数中最里面的for、switch或select语句的执行
BreakStmt = "break" [ Label ] .
如果有一个标签,那么它必须是一个封闭的for、switch或select语句,并且它是执行可终止的语句
OuterLoop: for i = 0; i < n; i++ { for j = 0; j < m; j++ { switch a[i][j] { case nil: state = Error break OuterLoop case item: state = Found break OuterLoop } } }
Continue语句 在post语句处开始最里面的for循环的下一次迭代。for循环必须在同一函数内
ContinueStmt = "continue" [ Label ] .
如果有一个标签,那么它必须是一个包含for语句的标签,并且这是促进执行的标签
RowLoop: for y, row := range rows { for x, data := range row { if data == endOfRow { continue RowLoop } row[x] = data + bias(x, y) } }
Goto语句 将控制权转移到同一函数中具有相应标签的语句
GotoStmt = "goto" Label . goto Error
执行goto语句不能导致任何变量进入goto点作用域之外的作用域。例如,此示例
goto L // BAD v := 3L:
是错误的,因为跳转到标签L会跳过v的创建。块外的goto语句不能跳转到该块内的标签。例如,此示例
if n%2 == 1 { goto L1}for n > 0 { f() n--L1: f() n--}
是错误的,因为标签L1在for语句块内,但goto不在
Fallthrough语句 将控制权转移到表达式switch语句中下一个case子句的第一个语句。它只能用作此类子句中的最后一个非空语句
FallthroughStmt = "fallthrough" .
Defer语句 调用一个函数,该函数的执行被推迟到包裹函数返回的那一刻,这可能是因为包裹的函数执行了一个返回语句,到达了其函数体的末尾,或者是因为相应的goroutine发生panic
DeferStmt = "defer" Expression .
表达式必须是函数或方法调用;不能用括号括起来。内置函数的调用与表达式语句一样受到限制
每次执行defer语句时,调用的函数值和参数都会像往常一样计算并重新保存,但不会调用实际的函数。相反,被延迟的函数在包裹函数返回之前立即调用,顺序与defered的顺序相反(LIFO)
也就是说,如果包裹函数通过显式返回语句返回,则在该返回语句设置任何结果参数之后、函数返回其调用方之前执行延迟函数。如果延迟函数值的计算结果为nil,则在调用该函数时执行会发生运行时panic,而不是在执行defer语句时执行
例如,如果延迟函数是一个函数文本,并且包裹的函数已命名了文本范围内的结果参数,则延迟函数可以在返回结果参数之前访问和修改它们;如果延迟函数有任何返回值,则在函数完成时将丢弃这些值
lock(l)defer unlock(l) // 在包裹函数返回之前进行释放锁 // prints 3 2 1 0 before surrounding function returnsfor i := 0; i <= 3; i++ { defer fmt.Print(i)} // f 返回值为 42func f() (result int) { defer func() { // 结果在被return语句设置为6之后被访问 result *= 7 }() return 6}
内置函数
内置函数是预先声明的,它们像任何其他函数一样被调用,但其中一些函数接受类型而不是表达式作为第一个参数。内置函数没有标准go类型,因此它们只能出现在调用表达式中;不能用作函数值
Close 对于通道c,内置函数close(c)记录通道上不再发送值;如果c是一个只接收通道,这样会发生error。向已关闭的通道发送数据会导致运行时panic,关闭nil通道也会发生运行时panic。在调用close之后,并且在接收到任何以前发送的值之后,接收操作将在不阻塞的情况下返回通道类型的零值。多值接收操作返回接收值,并指示信道是否关闭
Length 和 capacity 内置函数len和cap接受各种类型的参数并返回int类型的结果。该实现确保结果始终适合于int类型
Call Argument type Result len(s) string type string length in bytes [n]T, *[n]T array length (== n) []T slice length map[K]T map length (number of defined keys) chan T number of elements queued in channel buffer cap(s) [n]T, *[n]T array length (== n) []T slice capacity chan T channel buffer capacity
切片的容量是在底层数组中为其分配空间的元素数。以下关系在任何时候都成立
0 <= len(s) <= cap(s)
如果切片、数组、map或通道值是nil,则其长度为0;如果s是字符串常量,则表达式len(s)为常量。如果s的类型是数组或指向数组的指针,并且表达式s不包含通道接收或(非常量)函数调用,则表达式len(s)和cap(s)是常量;在这种情况下,不计算s。否则,对len和cap的调用不是常量,将计算s
const ( c1 = imag(2i) // imag(2i) = 2.0 is a constant c2 = len([10]float64{2}) // [10]float64{2} contains no function calls c3 = len([10]float64{c1}) // [10]float64{c1} contains no function calls c4 = len([10]float64{imag(2i)}) // imag(2i) is a constant and no function call is issued c5 = len([10]float64{imag(z)}) // invalid: imag(z) is a (non-constant) function call)var z complex128
Allocation 内置函数new接收T类型,在运行时为该类型的变量分配存储空间,并返回指向该变量的*T类型的值,形如,new(T)。例如
type S struct { a int; b float64 }new(S)
为S类型的变量分配存储空间,初始化它(a=0,b=0.0),并返回包含位置地址的S类型的值
Making slices, map, channels 内置函数make采用类型T,它必须是slice、map或channel类型,后面还可以是特定类型的表达式列表。它返回一个T类型的值(不是*T)
Call Type T Result make(T, n) slice slice of type T with length n and capacity nmake(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type Tmake(T, n) map map of type T with initial space for approximately n elements make(T) channel unbuffered channel of type Tmake(T, n) channel buffered channel of type T, buffer size n
每个大小参数n和m都必须是整数类型或非类型常量。常量大小参数必须是非负的,并且可以由int类型的值表示;如果它是非类型化的常量,则为int类型。如果n和m都提供并且都是常量,则n不能大于m,如果n是复数或者大于m则会发生运行时panic
s := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100s := make([]int, 1e3) // slice with len(s) == cap(s) == 1000s := make([]int, 1<<63) // illegal: len(s) is not representable by a value of type ints := make([]int, 10, 0) // illegal: len(s) > cap(s)c := make(chan int, 10) // channel with a buffer size of 10m := make(map[string]int, 100) // map with initial space for approximately 100 elements
使用map类型和size提示n调用make将创建一个初始空间为n个元素的map。精确的行为依赖于实现
Appending to and coping slices 内置函数append和copy有助于执行常见的切片操作。对于这两个函数,结果与参数引用的内存是否重叠无关
可变函数append将零个或多个值x附加到S类型(必须是切片类型)的s,并返回结果切片(也是s类型)。值x被传递给类型为...T的参数,T是S的元素类型并且各自的参数传递规则适用。作为特殊情况,append还接受第一个参数,该参数可赋值给类型为[]byte,第二个参数的字符串类型后跟….此格式追加字符串的字节
append(s S, x ...T) S // T 是 S 的元素类型
如果s的容量不足以容纳额外的值,append将分配一个新的、足够大的底层数组,该数组既适合现有的slice元素,也适合额外的值。否则,append将重用基础数组
s0 := []int{0, 0}s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2}s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7}s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{}t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} var b []byteb = append(b, "bar"...) // append string contents b == []byte{'b', 'a', 'r' }
函数copy将切片元素从源src复制到目标dst,并返回复制的元素数。两个参数必须具有相同的元素类型T,并且必须可赋值给类型为[]T的切片。复制的元素是len(src)和len(dst)的最小值。作为一种特殊情况,copy还接受一个目标参数,该参数可复制给带有字符串类型的源参数的类型[]byte。此形式将字符串中的字节复制到字节片中
copy(dst, src []T) intcopy(dst []byte, src string) int
例如
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}var s = make([]int, 6)var b = make([]byte, 5)n1 := copy(s, a[0:]) // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}n3 := copy(b, "Hello, World!") // n3 == 5, b == []byte("Hello")
删除map的元素 内置函数delete从映射m中删除键为k的元素,k的类型必须可赋值给m的键的类型
delete(m, k) // remove element m[k] from map m
如果映射m是nil,或者元素m[k]不存在,删除是一个no-op
操作复数 三个函数组合与反组合复数,内置函数complex从浮点实部和虚部构造复数,而real和imag则提取复数的实部和虚部
complex(realPart, imaginaryPart floatT) complexTreal(complexT) floatTimag(complexT) floatT
参数的类型和返回值对应。对于复数,两个参数必须是相同的浮点类型,返回类型是具有相应浮点成分的complex类型:complex64表示float32参数,complex128表示float64参数。如果其中一个参数的计算结果为非类型化常量,则首先将其隐式转换为另一个参数的类型;如果两个参数的计算结果都是非类型化常量,则它们必须是非复数或其虚部必须为零,并且函数的返回值是非类型化的复数常量
对于real和imag,它们的参数必须是复数类型,返回类型是对应的浮点类型:float32表示complex64参数,float64表示complex128参数;如果参数的计算结果是非类型化常量,则它必须是数字,并且函数的返回值是非类型化浮点常量
real和imag函数一起构成复数的逆函数,因此对于复数类型Z的值z,z == Z(complex(real(z), imag(z)))
如果这些函数的操作数都是常量,则返回值是常量
var a = complex(2, -2) // complex128const b = complex(1.0, -1.4) // untyped complex constant 1 - 1.4ix := float32(math.Cos(math.Pi/2)) // float32var c64 = complex(5, -x) // complex64var s int = complex(1, 0) // untyped complex constant 1 + 0i can be converted to int_ = complex(1, 2<<s) // illegal: 2 assumes floating-point type, cannot shiftvar rl = real(c64) // float32var im = imag(a) // float64const c = imag(b) // untyped constant -1.4_ = imag(3 << s) // illegal: 3 assumes complex type, cannot shift
处理panic 两个内置函数,panic和recover,有助于报告和处理运行时panic和程序定义的错误条件
func panic(interface{})func recover() interface{}
在执行函数F时,显式调用panic或运行时panic会终止F的执行,任何被F延迟的函数都会像往常一样执行。接下来,F的调用者运行的任何延迟函数都将运行,以此类推,直到执行goroutine中的顶级函数执行的任何延迟。这时,程序终止并报告错误情况,包括参数panic的值,这种终止序列称为panic
panic(42)panic("unreachable")panic(Error("cannot parse"))
recover函数允许程序管理panic的goroutine的行为,假设函数G延迟函数D它调用recover,并且在G执行的goroutine上的同一个函数中发生panic。当延迟函数的运行到达D时,D的recover调用的返回值将是传递给panic调用的值。如果D返回正常,而没有开始新的panic,panic序列就会停止。在这种情况下,在G和对panic的调用之间调用的函数的状态将被丢弃,并恢复正常执行。然后运行G在D之前延迟的任何函数,G的执行通过返回其调用方而终止
如果满足以下条件,recover返回nil值
- panic的参数是nil
- goroutine没有panic
- 延迟函数未直接调用recover
下面示例中的protect函数调用函数参数G,并保护调用者免受G引发的运行时panic
func protect(g func()) { defer func() { log.Println("done") // Println executes normally even if there is a panic if x := recover(); x != nil { log.Printf("run time panic: %v", x) } }() log.Println("start") g()}
Bootstrapping 当前的实现提供了几个在引导过程中有用的内置函数。这些函数是为了完整性而记录的,但不能保证它们保持在语言中。他们不返回结果
Function Behavior print prints all arguments; formatting of arguments is implementation-specificprintln like print but prints spaces between arguments and a newline at the end
实现限制:print和println不需要接受任意参数类型,但必须支持布尔、数字和字符串类型的打印
包 go程序是通过将包链接在一起来构建的。一个包依次由一个或多个源文件构造,这些源文件一起声明属于该包的常量、类型、变量和函数,并且可以在同一个包的所有文件中访问这些源文件。这些元素可以导出并在另一个包中使用
源文件组织 每个源文件都由一个package子句组成,该子句定义它所属的包,后跟一组可能为空的导入声明,这些声明声明它希望使用其内容的包,后跟一组可能为空的函数、类型、变量和常量声明
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
包子句 开始每个源文件并定义文件所属的包
PackageClause = "package" PackageName .PackageName = identifier .
包名称必须是非空的标识符。一组共享同一包名的文件构成一个包的实现,实现可能要求包的所有源文件都位于同一目录中
导入声明 导入声明声明包含声明的源文件依赖于导入包的功能,并允许访问该包的导出标识符。导入将命名用于访问的标识符(PackageName)和指定要导入的包的ImportPath
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .ImportSpec = [ "." | PackageName ] ImportPath .ImportPath = string_lit .
PackageName在限定标识符中用于访问导入源文件中包的导出标识符。它在文件块中声明。如果省略包名,则默认为导入包的package子句中指定的标识符。如果出现显式句点(.)而不是名称,则在该包的包块中声明的所有包的导出标识符都将在导入源文件的文件块中声明,并且必须在不使用限定符的情况下访问
ImportPath的解释依赖于实现,但它通常是编译包的完整文件名的子字符串,可能与已安装包的存储库有关
Import declaration Local name of Sin import "lib/math" math.Sinimport m "lib/math" m.Sinimport . "lib/math" Sinimport _ "lib/math"
程序初始化和执行
零值 当通过声明或调用new为变量分配存储时,或者当通过复合文本或调用make创建新值时,如果未提供显式初始化,则会为变量或值指定默认值。此类变量或值的每个元素的类型都设置为零值:对于布尔值为false,对于数值类型为0,对于字符串"",对于指针、函数、接口、切片、通道和映射为nil。这个初始化是递归完成的,例如,如果没有指定值,结构数组中的每个元素的字段都将为零值
这两个简单的声明是等价的
var i intvar i int = 0
After...
type T struct { i int; f float64; next *T }t := new(T) // var t T t.i == 0t.f == 0.0t.next == nil
包初始化 在包中,包级变量初始化逐步进行,每个步骤按照声明顺序最早选择一个变量,该变量不依赖于未初始化的变量
更准确地说,如果包级变量尚未初始化并且没有初始化表达式或其初始化表达式不依赖于未初始化的变量,则认为该变量已准备好初始化。初始化是通过重复初始化下一个包级变量来进行的,该变量是声明顺序中最早并准备好初始化的,直到没有准备好初始化的变量为止
如果此进程结束时仍存在变量未初始化,则这些变量是一个或多个初始化周期的一部分,并且程序无效
在多个文件中声明的变量声明顺序由文件呈现给编译器的顺序决定:在第一个文件中声明的变量在第二个文件中声明的任何变量之前声明,依此类推。依赖项分析不依赖于变量的实际值,只依赖于源代码中对它们的词法引用,并进行传递性分析。例如,如果变量x的初始化表达式引用其主体引用变量y的函数,那么x依赖于y
- 对变量或函数的引用是表示该变量或函数的标识符
- 对方法m的引用是形式t.m的方法值或方法表达式,其中t的(静态)类型不是接口类型,方法m在t的方法集中。是否调用结果函数值t.m并不重要
- 如果x的初始化表达式或主体(用于函数和方法)包含对y的引用或对依赖于y的函数或方法的引用,则变量、函数或方法x依赖于变量y
var ( a = c + b // == 9 b = f() // == 4 c = f() // == 5 d = 3 // == 5 after initialization has finished) func f() int { d++ return d}
初始化顺序为d、b、c、a,初始化表达式中子表达式的顺序不相关:在本例中,a=c+b和a=b+c导致相同的初始化顺序。每个包都执行依赖关系分析;只考虑引用当前包中声明的变量、函数和(非接口)方法。如果变量之间存在其他隐藏的数据依赖关系,则未指定这些变量之间的初始化顺序。例如
var x = I(T{}).ab() // x has an undetected, hidden dependency on a and bvar _ = sideEffect() // unrelated to x, a, or bvar a = bvar b = 42 type I interface { ab() []int }type T struct{}func (T) ab() []int { return []int{a, b} }
变量a将在b之后初始化,但x是在b之前、b和a之间还是在a之后初始化,因此也没有指定调用sideEfect()的时刻(在x初始化之前或之后)。变量也可以使用包块中声明的名为init的函数初始化,不带参数和结果参数
func init() { … }
即使在单个源文件中,也可以为每个包定义多个这样的函数。在包块中,init标识符只能用于声明init函数,但标识符本身没有声明。因此,不能从程序的任何地方引用init函数
一个没有导入的包通过给它的所有包级变量分配初始值,然后按它们在源中出现的顺序调用所有init函数(可能在多个文件中)来初始化,如编译器所示。如果包具有导入,则在初始化包本身之前初始化导入的包。如果多个包导入一个包,则导入的包将仅初始化一次。通过构造导入包可以保证不存在循环初始化依赖项
包初始化变量初始化和init函数调用发生在单个goroutine中,按顺序,一次一个包。init函数可以启动其他goroutine,这些goroutine可以与初始化代码同时运行。但是,初始化总是对init函数进行排序:在前一个函数返回之前,它不会调用下一个函数
为了确保可重复的初始化行为,建议构建系统以词法文件名的顺序向编译器呈现属于同一个包的多个文件
程序执行 一个完整的程序是通过将一个名为main包的未导入的包与它导入的所有包以可传递的方式链接起来创建的。main包必须具有包名称main,并声明一个不带参数且不返回值的函数main
func main() { … }
程序执行首先初始化main包,然后调用main函数。当函数调用返回时,程序退出。它不会等待其他(non-main)goroutine完成
Errors 预声明类型error定义为
type error interface { Error() string}
它是表示错误条件的常规接口,nil值表示没有错误。例如,可以定义从文件读取数据的函数
func Read(f *File, b []byte) (n int, err error)
运行时panic 执行错误(如试图索引超出边界的数组)会触发一个运行时异常,该异常等价于使用实现定义的接口类型runtime.Error的值调用内置函数异常。该类型满足预声明接口类型错误。未指定表示不同运行时错误条件的确切错误值
package runtime type Error interface { error // and perhaps other methods}
系统注意事项
unsafe包 内置包unsafe(编译器已知并可通过导入路径unsafe访问)为低级编程(包括违反类型系统的操作)提供了便利。使用unsafe包必须手动进行类型安全检查,并且可能不可移植。包提供以下接口
package unsafe type ArbitraryType int // shorthand for an arbitrary Go type; it is not a real typetype Pointer *ArbitraryTypefunc Alignof(variable ArbitraryType) uintptrfunc Offsetof(selector ArbitraryType) uintptrfunc Sizeof(variable ArbitraryType) uintptr
Pointer是指针类型,但指针值不能被取消引用。基础类型uintptr的任何指针或值都可以转换为基础类型指针的类型,反之亦然。Pointer和uintptr之间的转换效果是由实现定义的
var f float64bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointerbits = *(*uint64)(ptr(&f)) var p ptr = nil
函数Alignof和Sizeof获取任意类型的表达式x并返回对齐或大小,分别是假设变量v,就好像v是通过var v=x声明的一样
函数Offsetof接受(可能带圆括号)选择器s.f,表示由s或*s表示的结构的字段f,并返回相对于结构地址的字段偏移量(字节)。如果f是一个嵌入字段,那么它必须是可访问的,而无需通过结构的字段进行指针间接指向。对于字段为f的结构s
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
计算机体系结构可能需要对齐内存地址;也就是说,如果变量的地址是一个因子的倍数,则变量的类型对齐。函数Alignof接受表示任何类型变量的表达式,并以字节为单位返回变量(类型)的对齐方式。对于变量x
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
对Alignof、Offsetof和Sizeof的调用是uintpttr类型的编译时常量表达式
Size and alignment guarantees 对于数字类型,保证以下size
type size in bytes byte, uint8, int8 1uint16, int16 2uint32, int32, float32 4uint64, int64, float64, complex64 8complex128 16
保证以下最小对齐属性
- 对于任意类型的变量x: unsafe.Alignof(x)至少为1
- 对于结构类型的变量x: unsafe.Alignof(x)是x每个字段f的unsafe.Alignof(x.f)值中最大的一个,但至少为1
- 对于数组类型的变量x: unsafe.Alignof(x)与数组元素类型的变量的对齐方式相同
如果结构或数组类型不包含byte size大于零的字段(或元素),则其size为零。两个不同的zero-size变量在内存中可能具有相同的地址
小结
语言特性,即语法规则和特点的概述。想通过文字来准确描述语法规则,需要保持规则的严谨性,文章读/写起来会有些咬文嚼字,此处也不刻意追求文本流畅程度。关于Golang的语言特性知识点零碎且分散,本文专门归纳整理,希望对您有所帮助
- 上一篇:PSC迎检:这缺陷可以避免
- 下一篇:子网掩码的作用解释一例
相关推荐
- 好用的云函数!后端低代码接口开发,零基础编写API接口
-
前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...
- 快速上手:Windows 平台上 cURL 命令的使用方法
-
在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...
- 使用 Golang net/http 包:基础入门与实战
-
简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
-
你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...
- window使用curl命令的注意事项 curl命令用法
-
cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...
- Linux 系统curl命令使用详解 linuxctrl
-
curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...
- Tornado 入门:初学者指南 tornados
-
Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...
- PHP Curl的简单使用 php curl formdata
-
本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
-
本章涵盖使用Actix提供静态网页...
- 我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些
-
这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...
- linux文件下载、服务器交互(curl)
-
基础环境curl命令描述...
- curl简单使用 curl sh
-
1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...
- 常用linux命令:curl 常用linux命令大全
-
//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...
- 三十七,Web渗透提高班之hack the box在线靶场注册及入门知识
-
一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...
- 一周热门
- 最近发表
-
- 好用的云函数!后端低代码接口开发,零基础编写API接口
- 快速上手:Windows 平台上 cURL 命令的使用方法
- 使用 Golang net/http 包:基础入门与实战
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
- window使用curl命令的注意事项 curl命令用法
- Linux 系统curl命令使用详解 linuxctrl
- Tornado 入门:初学者指南 tornados
- PHP Curl的简单使用 php curl formdata
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- webgl开发 (24)
- parse (24)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)