- 1 文件夹与包
- 1.1 包
- 1.1.1 包的导入
- 1.1.2 包&文件
- 1.1.3 示例
- 1.2 文件夹
- 1.2.1 文件夹&文件
- 1.2.2 嵌套文件夹
- 1.3 访问权限
- 1.3.1 简介
- 1.3.2 包内的私有可互相访问
- 1.3.3 包访问级别
- 1.1 包
- 2 数据类型&变量
- 2.1 数据类型
- 2.1.1 常见类型
- 2.1.1.1 rune
- 2.1.2 自定义类型
- 2.1.2.1 自定义字符串类型
- 2.1.1 常见类型
- 2.2 类型转换
- 2.2.1 数值类型转换
- 2.2.2 字符串类型转换
- 2.2.3 接口类型转换
- 2.2.4 空接口类型
- 2.3 变量
- 2.3.1 简介
- 2.3.2 变量声明
- 2.3.2.1 指定变量声明
- 2.3.2.2 根据值自行判定
- 2.3.2.3 := 声明变量
- 2.3.2.4 多变量声明
- 2.3.3 空白标识符 _
- 2.4 常量
- 2.4.1 简介
- 2.4.2 声明
- 2.4.3 iota
- 2.1 数据类型
1 文件夹与包
1.1 包
Go 程序是通过 package
来组织的。只有 package
名称为 main
的源码文件可以包含 main
函数。一个可执行程序有且仅有一个 main
包。
1.1.1 包的导入
通过 import
关键字来导入其他非main
包。包引入一般为: 项目名/包名
:import "test/controllers"
可以通过 import
关键字单个导入:
import "fmt"
import "io"
也可以同时导入多个:
import ("fmt""math"
)
使用 <PackageName>.<FunctionName>
调用,比如: fmt.Println(math.Exp2(10))
package 别名
,例如为fmt起别名为fmt2:import fmt2 "fmt"
包省略调用(不建议使用),import . "fmt"
,前面加个点
表示省略调用,那么调用该模块里面的函数,可以不用写模块名称
了,调用的时候只需要Println()
,而不需要fmt.Println()
import . "fmt"
func main (){Println("hello,world")
}
1.1.2 包&文件
在 Go 中,一个包内
可以包含多个文件
,而不是只能有一个文件。
Go 编译器
会将同一包内的所有 .go
文件编译为一个整体,无需额外指定每个文件。
即使分布在不同文件中,它们共享同一个包的作用域。
以下是一些关于包和文件的关键点和规则:
一个包可以包含多个文件,同一个包内的多个文件必须具有相同的包声明(package
)
例如,包名是 mypackage,则所有文件的开头都应声明为:
package mypackage
注意
- 文件命名冲突:
- 包内的文件不能出现重复命名的函数、变量、结构体等,否则会报重复定义的错误。
文件和目录的关系: Go
按文件夹
来划分包,同一个文件夹
下的所有.go 文件
都必须属于同一个包。
- 包内的文件不能出现重复命名的函数、变量、结构体等,否则会报重复定义的错误。
- 初始化函数限制:
每个文件中可以包含自己的init()
函数,用于包初始化,每个文件至少有一个init()
函数
1.1.3 示例
假设包名是 mypackage,它可以包含多个文件,例如:
mypackage/|- file1.go|- file2.go|- file3.go
每个文件的内容如下:
file1.go:
package mypackagefunc Func1() string {return "This is Func1"
}
file2.go:
package mypackagefunc Func2() string {return "This is Func2"
}
file3.go:
package mypackagefunc Func3() string {return "This is Func3"
}
1.2 文件夹
1.2.1 文件夹&文件
在 Go
中,一个文件夹
下的所有 .go 文件
只能属于同一个包
,也就是说它们的包名必须相同。这是 Go
的一个核心规则
,确保每个文件夹代表一个逻辑单元(即一个包)
问题:
- 一个文件夹能有多个包吗?
不行,一个文件夹下只能有一个包。 - 每个
.go
文件的包声明必须相同吗?
是的,一个文件夹
内所有.go文件
必须声明为相同的包名。 - 包文件中导入顺序会影响吗?
不会,Go
会自动解析
依赖关系。
1.2.2 嵌套文件夹
在 Go 中,如果有嵌套文件夹,那么每个文件夹都可以定义一个独立的包。也就是说,每个文件夹对应一个包,嵌套文件夹中的文件可以定义为不同的包。
嵌套文件夹的包结构示例
嵌套文件夹中包的命名规则:
- 每个文件夹对应一个包,文件夹的名字通常就是包名(但可以自定义)。
- Go 中的包导入路径基于
项目的模块路径
和文件夹结构
。
注意事项:
- 文件夹与包的关系:
一个文件夹
只能定义一个包
。- 每个文件夹都可以是
独立的包
,嵌套文件夹
中的文件可以属于不同的包
。
- 包的命名与导入路径:
- 导入路径基于模块路径(
go.mod
中定义)和文件夹层级。 - 包名和文件夹名通常相同,但可以不同。
- 导入路径基于模块路径(
- 避免循环依赖:
嵌套文件夹中的包相互导入时,要避免循环依赖,否则会导致编译错误。
1.3 访问权限
1.3.1 简介
在 Go
中,访问权限
的控制非常简洁且独特,没有像其他语言(如 Java 或 C++)那样使用显式的 public、private 修饰符。Go
通过 标识符首字母大小写
来决定访问权限。
Go 的访问控制规则:
首字母大写
:公共(Public
)访问
如果一个标识符(变量、函数、类型等)的首字母是大写的,则它是导出
(exported) 的,包外可以访问。首字母小写
:私有(Private
)访问
如果一个标识符的首字母是小写的,则它是未导出
(unexported
) 的,包外无法访问。
1.3.2 包内的私有可互相访问
同一个包中的文件可以访问小写(私有
)的标识符,即使它们在不同的文件中。
mypackage/|- file1.go|- file2.go
file1.go:
package mypackage
var privateVar = "Private Variable"
func getPrivateVar() string {return privateVar
}
file2.go:
package mypackagefunc AccessPrivateVar() string {return getPrivateVar()
}
privateVar
和 getPrivateVar()
虽然是私有的,但因为它们都在 mypackage
包中,所以可以相互访问。
1.3.3 包访问级别
Go 没有显式的包内保护级别
: Go 只有两种访问级别:
- 包外不可访问(小写开头)。
- 包外可访问(大写开头)。
文件夹
即包
的作用域:Go
的访问权限
控制基于 包
,而不是基于 文件
。
这意味着,即使在同一个文件中,如果两个标识符属于不同的包,它们的访问权限仍然受限制。
2 数据类型&变量
2.1 数据类型
2.1.1 常见类型
在 Go 编程语言中,数据类型用于声明函数和变量。数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
Go 语言按类别有以下几种数据类型:
- 布尔型
布尔型的值只可以是常量true
或者false
。
一个简单的例子:var b bool = true。 - 数字类型
整型int
和浮点型float32、float64
,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。- 数字类型
uint8
:无符号 8 位整型 (0 到 255)uint16
:无符号 16 位整型 (0 到 65535)uint32
:无符号 32 位整型 (0 到 4294967295)uint64
:无符号 64 位整型 (0 到 18446744073709551615)int8
:有符号 8 位整型 (-128 到 127)int16
:有符号 16 位整型 (-32768 到 32767)int32
:有符号 32 位整型 (-2147483648 到 2147483647)int64
:有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
- 浮点型
float32
:IEEE-754 32位浮点型数float64
:IEEE-754 64位浮点型数complex64
:32 位实数和虚数complex128
:64 位实数和虚数
- 其他数字类型
byte
:类似 uint8rune
:类似 int32uint
:32 或 64 位int
:与 uint 一样大小uintptr
:无符号整型,用于存放一个指针
- 数字类型
- 字符串类型:
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 - 派生类型:
包括:指针类型(Pointer),数组类型,结构化类型(struct),Channel 类型,函数类型,切片类型,接口类型(interface),Map 类型
2.1.1.1 rune
rune
是一个内置的类型,代表一个 Unicode
码点(即字符
)。rune
是 int32
的别名,这意味着它占 4 字节(32 位),表示一个整数值,范围从 0 到 0x10FFFF
(即 Unicode 码点的最大值),可以表示 Unicode 字符集中的任意字符(包括大部分语言字符、符号、标点符号等)。通常,rune
用来表示字符,它是 Go
中处理文本和字符编码的主要方式之一。
Go 中的字符串是 UTF-8
编码的字节切片
,而每个 Unicode
字符在 UTF-8
中可能会占用多个字节
。rune
类型解决了这一问题,它代表单个字符的 Unicode 码点
,可以方便地处理多字节字符。通过将字符串转换为 []rune
,可以确保每个字符被正确处理,而不必担心字节的编码方式。
由于 rune
是基于字符
的,而字符串
是基于字节
的,所以在处理包含多字节字符
的字符串时,使用 rune
类型会更准确地获取字符的数量。
虽然 rune
是一个整数类型
,它通常用于表示字符
。在 Go 中,字符串
是字节的切片([]byte)
,而 rune
是表示单个字符的类型,特别适用于 Unicode 字符
的处理。
var c rune = 'a' // 字符 'a' 对应的 Unicode 码点是 97
fmt.Println(c) // 输出:97var ch rune = '中' // 字符 '中' 的 Unicode 码点是 20013
fmt.Println(ch) // 输出:20013
Go 中的字符串本质上是字节切片 ([]byte
),而不是直接存储字符。为了支持 Unicode 字符
,Go 提供了 rune 类型来处理多字节的字符编码。在遍历字符串时,通常会将字符串转换为 []rune
,这样每个字符
都会被独立
地表示为一个 rune
。
2.1.2 自定义类型
2.1.2.1 自定义字符串类型
var defaultName = "Sam" //allowed
type myString string
var customName myString = "Sam" //allowed
customName = defaultName //not allowed
在上面的代码中,我们首先创建了一个变量defaultName
并且赋值为常量Sam
。常量"Sam" 的默认类型为string,因此赋值之后,defaultName
的类型亦为 string。
下一行我们创建了一个新的类型 myString
,它是 string
的别名。(可以使用 type NewType Type 的语法来创建一个新的类型)。
接着创建了一个名为 customName
的 myString
类型的变量,并将常量 "Sam" 赋给它。因为常量 "Sam" 是无类型的所以可以将它赋值给任何字符串变量。因此这个赋值是合法的,customName
的类型是 myString。
现在我们有两个变量:string
类型的 defaultName
和 myString
类型的 customName
。尽管我们知道 myString
是 string
的一个别名,但是Go的强类型机制不允许将一个类型的变量赋值给另一个类型的变量。因此, customName = defaultName
这个赋值是不允许的,编译器会报错:cannot use defaultName (type string) as type myString in assignment
2.2 类型转换
类型转换
用于将一种数据类型的变量转换为另外一种类型的变量。
Go 语言类型转换基本格式如下:type_name(expression)
,type_name
为类型,expression
为表达式。
2.2.1 数值类型转换
将整型转换为浮点型:
var a int = 10
var b float64 = float64(a)
2.2.2 字符串类型转换
将一个字符串转换成另一个类型,可以使用以下语法:
var str string = "10"
var num int
num, _ = strconv.Atoi(str)
以上代码将字符串变量 str
转换为整型变量 num
。
注意
,strconv.Atoi
函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 _
来忽略这个错误
以下实例将字符串转换为整数
package mainimport ("fmt""strconv"
)func main() {str := "123"num, err := strconv.Atoi(str)if err != nil {fmt.Println("转换错误:", err)} else {fmt.Printf("字符串 '%s' 转换为整数为:%d\n", str, num)}
}
strconv.Atoi
:字符串转换为整数
strconv.Itoa
:整数转换为字符串
strconv.ParseFloat
:字符串转换为浮点数
strconv.FormatFloat
:浮点数转换为字符串
2.2.3 接口类型转换
接口类型转换有两种情况:类型断言
和类型转换
。
- 类型断言
类型断言用于将接口类型
转换为指定类型
,其语法为:value.(type)
或者value.(T)
其中value
是接口类型的变量,type
或T
是要转换成的类型。
如果类型断言成功,它将返回转换后的值和一个布尔值,表示转换是否成功。 - 类型转换
类型转换用于将一个接口类型
的值转换为另一个接口类型
,其语法为:T(value)
T
是目标接口类型,value
是要转换的值。
类型断言实例
package mainimport "fmt"func main() {var i interface{} = "Hello, World"str, ok := i.(string)if ok {fmt.Printf("'%s' is a string\n", str)} else {fmt.Println("conversion failed")}
}
类型转换实例
package mainimport "fmt"// 定义一个接口 Writer
type Writer interface {Write([]byte) (int, error)
}// 实现 Writer 接口的结构体 StringWriter
type StringWriter struct {str string
}// 实现 Write 方法
func (sw *StringWriter) Write(data []byte) (int, error) {sw.str += string(data)return len(data), nil
}func main() {// 创建一个 StringWriter 实例并赋值给 Writer 接口变量var w Writer = &StringWriter{}// 将 Writer 接口类型转换为 StringWriter 类型sw := w.(*StringWriter)// 修改 StringWriter 的字段sw.str = "Hello, World"// 打印 StringWriter 的字段值fmt.Println(sw.str)
}
2.2.4 空接口类型
空接口 interface{}
可以持有任何类型的值。在实际应用中,空接口经常被用来处理多种类型的值。
实例
package mainimport ("fmt"
)func printValue(v interface{}) {switch v := v.(type) {case int:fmt.Println("Integer:", v)case string:fmt.Println("String:", v)default:fmt.Println("Unknown type")}
}func main() {printValue(42)printValue("hello")printValue(3.14)
}
在这个例子中,printValue 函数接受一个空接口类型的参数,并使用类型断言和类型选择来处理不同的类型。
2.3 变量
2.3.1 简介
Go 语言变量名由字母
、数字
、下划线
组成,其中首个字符不能为数字
。
声明变量的一般形式是使用 var
关键字:var identifier type
可以一次声明多个变量:var identifier1, identifier2 type
全局变量和局部变量:
局部变量
:在函数体内声明的变量称之为局部变量
,它们的作用域只在函数体内,参数和返回值变量也是局部变量。声明后必须使用
局部变量不会一直存在,在函数被调用时存在,函数调用结束后变量就会被销毁全局变量
:在函数体外声明的变量称之为全局变量
,全局变量可以在整个包甚至外部包(被导出后)使用。可以声明不使用- 全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
注意
:如果声明了一个局部变量
,却没有在相同的代码块中使用它,得到编译错误:a declared but not used
2.3.2 变量声明
2.3.2.1 指定变量声明
指定变量类型声明,如果没有初始化,则变量默认为零值
。
var v_name v_type
v_name = value
零值
就是变量没有做初始化时系统默认
设置的值:
- 数值类型(包括complex64/128)为 0
- 布尔类型为 false
- 字符串为 ""(空字符串)
- 以下几种类型为
nil
:
var a *int
,var a []int
,var a map[string] int
,var a chan int
var a func(string) int
var a error
// error 是接口
2.3.2.2 根据值自行判定
根据值自行判定变量类型,var v_name = value
实例
package main
import "fmt"
func main() {var d = truefmt.Println(d)
}
结果:
true
2.3.2.3 := 声明变量
可以在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var
关键字其实是显得有些多余了,因此我们可以将它们简写为 a := 50
或 b := false
。
a 和 b 的类型(int 和 bool)将由编译器自动推断。
这是使用变量的首选形式,但是它只能被用在函数体内
,而不可以用于全局变量的声明与赋值。使用操作符 :=
可以高效地创建一个新的变量,称之为初始化声明。
如果变量已经使用 var
声明过了,再使用 :=
声明变量,就产生编译错误,格式:v_name := value
例如:
var intVal int
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明直接使用下面的语句即可:
intVal := 1 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句
intVal := 1
相等于:
var intVal int
intVal =1
2.3.2.4 多变量声明
类型相同多个变量,非全局变量,可以一起声明,如下
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3// 和 python 很像,不需要显示声明类型,自动推断
var vname1, vname2, vname3 = v1, v2, v3 vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误// 这种因式分解关键字的写法一般用于声明全局变量
var (vname1 v_type1vname2 v_type2
)
示例下:
a, b := 20, 30 // declare variables a and b
fmt.Println("a is", a, "b is", b)
b, c := 40, 50 // b is already declared but c is new
fmt.Println("b is", b, "c is", c)
b, c = 80, 90 // assign new values to already declared variables b and c
fmt.Println("changed b is", b, "c is", c)
在 b, c := 40, 50
这一行,虽然变量 b 在之前已经被声明了,但是 c 却是新声明的变量,因此这是合法的
继续示例,将会报错:no new variables on left side of :=
。这是因为变量 a 和变量 b 都是已经声明过的变量,在 :=
左侧并没有新的变量被声明
a, b := 20, 30
a, b := 40, 50
2.3.3 空白标识符 _
空白标识符 _
也被用于抛弃值,如值 5 在:_, b = 5, 7
中被抛弃。
_
实际上是一个只写变量
,不能得到它的值。这样做是因为 Go 语言中必须使用所有被声明的变量,但有时并不需要使用从一个函数得到的所有返回值。
注意
:全局变量是允许声明但不使用
2.4 常量
2.4.1 简介
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
2.4.2 声明
常量的定义格式:const identifier [type] = value
,可以省略类型说明符 [type]
,因为编译器可以根据变量的值来推断其类型。
多个相同类型的声明可以简写为:const c_name1, c_name2 = value1, value2
常量还可以用作枚举:
const (Unknown = 0Female = 1Male = 2
)
在定义常量组时,如果不提供初始值,则表示将使用上行的表达式,即:未显式赋值的常量会自动继承上一行的表达式。
const (a = 1// b、c、d没有初始化,使用上一行(即a)的值b // 输出1c // 输出1d // 输出1
)
常量可以用len(), cap(), unsafe.Sizeof()
函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
package mainimport "unsafe"
const (a = "abc"b = len(a)c = unsafe.Sizeof(a)
)func main(){println(a, b, c)
}
结果为:
abc 3 16
unsafe.Sizeof(a)
方法,是获取的字符串类型长度,而不是字符串内容长度。
字符串类型在 go
里是个结构
,它包含指向底层数组的指针
和长度
,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。
2.4.3 iota
iota
:特殊常量,可以认为是一个可以被编译器修改的常量。
在 const
关键字出现时将被重置为 0
(const 内部的第一行之前),const
中每新增一行常量声明将使 iota
计数一次(iota 可理解为 const 语句块中的行索引)。
iota
可以被用作枚举值:
const (a = iotab = iotac = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (a = iota// b、c 没有初始化,使用上一行(a)的值 iota 但iota 会自增bc
)
iota
只是在同一个 const
常量组内递增,每当有新的 const
关键字时,iota
计数会重新从 0
开始
package mainconst (i = iotaj = iotax = iota
)
const xx = iota
const yy = iota
func main(){println(i, j, x, xx, yy)
}// 输出是 0 1 2 0 0
实例
package mainimport "fmt"func main() {const (a = iota //0b //1c //2d = "ha" //独立值,iota += 1e //"ha" iota += 1f = 100 //iota +=1g //100 iota +=1h = iota //7,恢复计数i //8)fmt.Println(a,b,c,d,e,f,g,h,i)
}
结果为:
0 1 2 ha ha 100 100 7 8