Go基础语法 (一)
基础语法
一个目录(dir)一个包(package),所有文件共享作用域
main函数概览
每个文件都需要有一个package声明
函数关键字func + 方法名 + 参数列表 + 返回值 + 方法体组成
go不要求每一句后边要加分号
main函数无参数 无返回值
main函数要在main包里 go run main.go
可以运行
package main
func main(){
print("hello world")
}
如果main函数里引用了同一个包(即package的名称一样)的其他方法 那么要加上对应的文件来运行
也可以通过ide来设置,执行时的目录
go run main.go hello.go
go run . //编译当前目录下的所有文件并运行
go build . 然后再执行build生成可运行文件 ./hello_go
// main.go
package myapp <----
import "fmt"
func main() {
fmt.Println("Main function")
helperFunction()// 调用 helper.go 中的函数
}
// helper.go
package myapp <----
import "fmt"
func helperFunction() {
fmt.Println("Helper function")
}
package声明
每一个文件都必须有一个package声明 同一个目录下,go文件package声明必须一致 统一目录下,go文件和test.go文件的package的声明可以不一致 package名字可以和目录名字不一样
基本类型string
基本类型:int整型数 unit无符号整型数 float浮点数
四则运算: go语言只有相同类型才能够进行四则运算,go不支持自动类型转换 可强制类型转换
复杂运算:math 包 包括极值也在math包 IDE会提示
string: 双引号包裹 "hello go" 换行等操作需要转义 || 反引号 hello go
-->反引号可以换行
string拼接:使用+ 但是go不支持与其他类型拼接
len:获取string字节长度--->非字符个数---->使用utf8.RuneCountInString
strings包可以看到string相关的的使用 源码中也会有注释
package main
func main() {
var a uint = 3
var b int = 3
var c = uint(b)
println(a + c)
//此函数返回 float64 类型的值
var d = math.Abs(-12)
println(d)
var name = "zzh"
var newName = name + strconv.Itoa(345)
println(newName)
println(len(newName)) //15
println(utf8.RuneCountInString(newName)) //11
}
基本类型:byte === uint8 uint8
的别名,它用于表示单个字节的值 也会用它来变大ASCII 字符 对应操作
在byte上
·rune === uint32·
byte[] 和 string 之间可以相互转换 <--这个在web3 solidity中 就是 string就是byte数组-->
基本类型:bool:false true
bool运算 a && b
a || b
!a
组合取反 !(a && b) !(a || b)
变量与常量的声明
- var name type = value
简短的声明方法 a := 12 局部变量使用 依赖类型推断 指定类型还是用var
包(全局)变量
- 定义:包级变量是在函数之外定义的变量,它们在整个包中都是可见的。
- 作用域:这些变量可以在整个包的任何地方被访问。
- 生命周期:包级变量的生命周期与程序的运行期相同。 局部变量
- 定义:局部变量是在函数内部定义的变量。
- 作用域:局部变量只能在定义它们的函数内部被访问。
- 生命周期:局部变量的生命周期仅限于函数的执行期。 块
- 定义:块级变量是在诸如
if
语句、for
循环等控制结构内部定义的变量。 - 作用域:这些变量仅在它们所在的特定代码块中可见。
- 生命周期:块级变量的生命周期从声明开始,到控制结构块的结束。
var()
块可以让您在一个地方声明多个变量,这有助于保持代码的整洁和组织。变量的作用域(包级别 或函数级别)取决于var()
块声明的位置。这种方式没有改变 Go 中的作用域规则,只是提供了一种更简洁的语 法来声明多个变量。 - 驼峰命名
- 首字母大仙控制可访问性,大写的包外可以访问
- 支持类型推断:整数默认int 浮点数默认float64
- 局部变量声明不用会报错
- 也可以只声明变量不赋值 变量的值就是默认值 易错点:同作用域下变量只能被声明一次 常量声明就是把var变成const
iota用法 控制变量初始化 数学运算 位移操作 使用_可以跳过值
主动插入值 会打断
iota
是一个特殊的常量,用于在 const
声明中生成一系列相关的值,通常是递增的整数。每当遇到一个新的 const
关键字时,iota
就会重置为 0,然后在每新增一个常量声明时自动递增。
package main
import "fmt"
const (
a = iota // 0
b // 1
c // 2
)
func main() {
fmt.Println(a, b, c) // 输出:0 1 2
}
package main
import "fmt"
const (
flag1 = 1 << iota // 1 << 0,即 1
flag2 // 1 << 1,即 2
flag3 // 1 << 2,即 4
)
func main() {
fmt.Println(flag1, flag2, flag3) // 输出:1 2 4
}
package main
import "fmt"
const (
_ = iota // 跳过 0
a // 1
b // 2
c // 3
)
func main() {
fmt.Println(a, b, c) // 输出:1 2 3
}
方法的声明与调用
基础
关键字 func 方法名首字符是否大写决定作用域
a := 1
func test(a)int{
retrun a
}
多个返回值,分割
func test2(a,b,c int,d string)(string,error){
retrun "Hi",nil
}
//返回值要么都有名字 要么都没有名字
func test3(a,b,c int,d string)(str string,err error){
str := "胖胖"
err := nil
retrun
}
func test4(a,b,c int,d string)(name string,err error){
name := "胖胖"
err := nil
retrun "newName",err
}
test(a)
name,err := test3(1,2,3,"胖胖")
name,_ = test3(1,2,3,"胖胖")
newName,_ := test3(1,2,3,"胖胖")
递归
小心栈溢出
package main
import "fmt"
// factorial 函数递归地计算阶乘
func factorial(n int) int {
if n <= 1 {
return 1 // 基础情况
}
return n * factorial(n-1) // 递归调用
}
func main() {
fmt.Println(factorial(5)) // 输出: 120
}
函数式编程
函数是一等公民,什么都能干 可以赋值给变量 可以作为参数 可以在函数内部声明函数 可以作为返回值
package main
import "fmt"
// 定义一个函数类型
type binOp func(int, int) int
// add 是一个可以被赋值给 binOp 类型变量的函数
func add(a, b int) int {
return a + b
}
// multiply 是另一个可以被赋值给 binOp 类型变量的函数
func multiply(a, b int) int {
return a * b
}
// operate 接收一个函数作为参数
func operate(a, b int, op binOp) int {
return op(a, b)
}
// chooseOperation 返回一个函数作为返回值
func chooseOperation(op string) binOp {
if op == "add" {
return add
} else if op == "multiply" {
return multiply
} else {
return nil
}
}
func main() {
// 将函数赋值给变量
var op binOp = add
fmt.Println("Result of addition:", op(3, 4)) // 输出: 7
// 将函数作为参数传递
result := operate(5, 6, multiply)
fmt.Println("Result of multiplication:", result) // 输出: 30
// 从函数中获取一个函数作为返回值,并使用它
selectedOp := chooseOperation("multiply")
if selectedOp != nil {
fmt.Println("Result of selected operation:", selectedOp(3, 3)) // 输出: 9
}
}
返回一个接收参数并有返回值的匿名函数
package main
import "fmt"
// returnFunc 返回一个接受两个 int 类型参数并返回一个 int 类型结果的匿名函数
func returnFunc() func(a int, b int) int {
return func(a int, b int) int {
return a + b
}
}
func main() {
sumFunc := returnFunc()
result := sumFunc(5, 7)
fmt.Println(result) // 输出: 12
}
闭包
如果是按照js来说的话,闭包 就是函数返回函数 而 内部函数又引用外层的变量,那被返回的函数就会背上一个严格私密的背包,背包里就是他所引用的外部的变量,不允许别人访问,所以也不会被垃圾收集。 函数走到哪就背到哪 函数死了 包还在(: 简单来说 不管函数在出生的时候,就和其所引用的所有变量保持长久的联系,永远不会丢失。因为函数是一等公民,所有高阶函数。被返回的函数引用着外部变量,因为这个长久的联系导致外部函数执行完毕后,被返回函数所引用的变量不会被清除 并保持长久的联系 严格私密 所以叫 close backpack || closure
不定参数
在内部可以作为切片使用
package main
import "fmt"
// sum 接受任意数量的 int 类型参数,并返回它们的总和
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
fmt.Println(sum(1, 2)) // 输出: 3
fmt.Println(sum(1, 2, 3, 4)) // 输出:
defer
返回的前调用的函数
先定义的后执行
使用defer 在函数结束前 关闭文件访问
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // 确保在函数结束时关闭文件
// 执行一些文件操作
// ...
fmt.Println("文件成功打开,并在函数结束时关闭")
}
derfer与闭包
确定值的原则:作为参数传入的:定义defer的时候就定义了 作为闭包使用,执行的时候才确定值
fun fn1(){
i := 0
defer (){
println(i)//闭包 println函数引用外部变量
}()
defer fn2(num int){
print(num)
}(i) //参数传入
}
defer 修改命名返回值
在 Go 语言中,defer
语句可以在函数返回之前执行一些代码。然而,defer
对函数返回值的影响有一些微妙之处。具体来说,如果一个函数返回一个命名的返回值,并且在该函数中使用 defer
语句修改了这个返回值,那么修改后的值将被返回。这是因为命名的返回值在函数开始时就被声明了,而 defer
语句在函数结束时才执行,因此它可以操作这些返回值。
示例:defer 修改命名返回值
下面是一个使用 defer
修改命名返回值的示例:
goCopy code
package main
import "fmt"
func test() (result int) {
defer func() {
result *= 2 // 修改命名返回值
}()
return 5 // 返回值先被设置为 5
}
func main() {
fmt.Println(test()) // 输出: 10
}
在这个例子中:
- 函数
test
定义了一个命名返回值result
。 - 在返回 5 之前,
defer
语句中的匿名函数被调用,它将result
的值乘以 2。 - 因此,最终返回的结果是 10 而不是 5。
示例:defer 和未命名返回值
如果函数返回的是未命名的返回值,defer
将不能以这种方式影响返回值:
goCopy code
package main
import "fmt"
func test() int {
var result int
defer func() {
result *= 2 // 这不会影响返回值
}()
return 5 // 直接返回 5
}
func main() {
fmt.Println(test()) // 输出: 5
}
在这个例子中:
result
只是函数内部的一个局部变量,并非返回值。- 即使
defer
语句修改了result
的值,也不会影响函数实际返回的值,函数仍然返回 5。
总结
defer
语句在处理命名返回值时能够影响函数的最终返回值,这是因为它在函数结束时执行,可以操作在函数返回之前设置的返回值。在使用 defer
修改返回值时需要特别小心,以确保代码的行为符合预期。这种特性在某些特定的场景下很有用,但也可能导致不直观的结果,因此需要谨慎使用。
测试
关键点 defer的执行顺序 与 闭包 for循环会形成10个defer压入栈中
package main
func main() {
test3()
}
func test() {
for i := 0; i < 10; i++ {
defer func() {
println(i)
}()
}
}
func test2() {
for i := 0; i < 10; i++ {
defer func(num int) {
println(num)
}(i)
}
}
func test3() {
for i := 0; i < 10; i++ {
j := i
defer func() {
println(j)
}()
}
}
转载自:https://juejin.cn/post/7343509431714775081