文章目录
简介
学习资料:
- https://golang3.eddycjy.com/posts/func-overloading-defalut/
- 官方练习网站:https://go.dev/tour/list
强类型,带GC,面向过程
package main
import "fmt"
func main() {
fmt.Println("Hello, World") // 打印 nhooo
}
变量与类型
使用var关键字声明变量:var variable_name type = expression
- 类型可以不要,变量的类型支持由表达式中的初始化值进行推断
- =可以不写,只定义变量,不进行初始化
package main
import "fmt"
func main() {
// 变量声明和初始化不使用表达式
var v1 int
var v2 string
var v3 float64
// 显示默认值
fmt.Printf("myvariable1的值是 : %d\n", myvariable1)
fmt.Printf("myvariable2的值是 : %d\n", myvariable2)
fmt.Printf("myvariable3的值是 : %d\n", myvariable3)
}
不初始化则该变量的类型为默认值,数字为零,布尔值为false,字符串为’',接口和引用类型为nil。
如果使用类型,则可以在单个声明中声明相同类型的多个变量。
var a, b, c int = 1, 2, 3
如果删除类型,则可以在单个声明中声明不同类型的多个变量。变量的类型由初始化值确定。
var a, b, c= 2, "GFG", 67.56
获取变量的类型
var a = 20.0
fmt.Printf("the type of a is %T\n", a) // the type of a is float64
未使用的变量
声明变量后未使用,会出现编译错误
var a = 10.0
短变量声明
使用短变量声明来声明在函数中声明和初始化的局部变量。语法:variable_name:= expression
这种方式简洁、灵活,大多数局部变量都是使用短变量声明来声明和初始化的。
var用于声明那些需要与初始值设定项表达式不同的显式类型的局部变量,或用于其值稍后分配且初始值不重要的那些变量。
这种声明变量的方式不能使用var关键字,同时也不能添加类型声明
声明多个变量
可以同时声明多个变量:a, b, c := 1, 2, 3
,多个变量可以有不同类型,可以自动推断
简短的变量声明仅当对于已在同一语法块中声明的那些变量起作用时,才像赋值一样。在外部块中声明的变量将被忽略。如下面的示例所示,这两个变量中至少有一个是新变量。
:= 只能声明新的变量,不能重新声明一个已经声明过的变量。下面这种写法会出现编译错误
a, b := 39, 45
b := 100 // Error! No new variables on the left side of ':='
但使用:=
为多个变量赋值时,如果引入了至少一个新的变量,编译是可以通过的
a, b:= 39, 45
c, b:= 45, 100 // b的值从45更改为100
注意这个时候,并没有重新声明b变量,只是为之前声明的b变量赋值
再看下面这个例子,嵌套作用域情况下可以有相同名称的变量
b := 45
fmt.Printf("%d\n", b) // b = 45
{
fmt.Printf("%d\n", b) // b = 45
b := 100
fmt.Printf("%d\n", b) // b = 100
}
fmt.Printf("%d\n", b) // b = 45
类型别名
从 Go1.9 开始引入了类型别名,使用type关键字
type intalias = int
Go 中可以为任意类型定义别名,比如数组、结构体、指针、函数、接口、Slice、Map、Channel 等,包括为自定义类型定义别名。
type F = func()
type I = interface{}
此外,还可以为其他包中的类型定义别名,比如为标准库类型定义别名:
type MyReader = bufio.Reader
注意事项
1.别名和原类型是一样的,因此 switch-type 结构中,不能存在两个 case,一个是原类型,一个是别名;
2.类型别名不能循环定义,比如以下是不允许的:
type T = struct {
next *T1
}
type T1 = T
3.别名和原类型是一样的,因此共享同样的方法集,不论这个方法是定义在原类型还是别名上;
4.别名的导出性可以和原类型不一样;
5.不能为别的包的类型通过定义别名来增加方法。以下行为是不允许的:
type MyReader = bufio.Reader
func (MyReader) AliasMethod() {
fmt.Println("This is alias method")
}
会导致编译错误:
6.Go 内置类型中,内置别名包括:rune 是 int32 的别名,byte 是 uint8 的别名
在 Go1.9 之前,rune 和 byte 的别名性质就存在,是编译器负责处理的。只是 Go1.9 之后,别名可以用于其他类型了。
作用域
package main
import "fmt"
// 全局变量声明
var v1 int = 100
func main() {
// 主函数内部的局部变量
var v2 int = 200
// 显示全局变量
fmt.Printf("全局变量 myvariable1 的值是 : %d\n", v1)
// 显示局部变量
fmt.Printf("局部变量 myvariable2 的值是 : %d\n", v2)
}
数据类型
Golang是静态类型、编译型语言
基本数据类型
- 布尔型:true/false。
- 整型:包括int8、int16、int32、int64、uint8(别名byte)、uint16、uint32、uint64、int、uint等。其中int和uint的大小是依赖于运行它的系统架构的(32位或64位)
- Floating-Point Types:包括float32和float64两种类型。float64是Go的默认浮点类型。
byte是内置类型uint8的一个别名
nil
在Go语言中,用 nil 表示指针、通道(channel)、函数、接口、映射(map)、切片(slice)或数组的零值。当声明了这些类型的变量但还没有给它们分配任何值时,这些变量的默认值就是 nil。然而,需要注意的是,基本数据类型(如整数、浮点数、布尔值和字符串)的零值并不是 nil,而是该类型的零值(如整数的零值是 0,字符串的零值是空字符串 “” 等)。
字符串
Go的字符串是不可变的字节序列,使用双引号
Go支持两种风格的字符串字面量表示形式:双引号风格(解释型字面表示)和反引号风格(直白字面表示)。
字符串类型的零值为空字符串,空字符串在字面上可以用""或者``来表示
var s4 = "string4"
var s5 = `string5`
可以用运算符+和+=来拼接字符串。
var s4 = "hello" + `world`
字符串类型都是可比较类型。同一个字符串类型的值可以用==和!=比较运算符来比较。 并且和整数/浮点数一样,同一个字符串类型的值也可以用>、<、>=和<=比较运算符来比较。 当比较两个字符串值的时候,它们的底层字节将逐一进行比较。如果一个字符串是另一个字符串的前缀,并且另一个字符串较长,则另一个字符串为两者中的较大者。
package main
import "fmt"
func main() {
const World = "world"
var hello = "hello"
// 衔接字符串。
var helloWorld = hello + " " + World
helloWorld += "!"
fmt.Println(helloWorld) // hello world!
// 比较字符串。
fmt.Println(hello == "hello") // true
fmt.Println(hello > helloWorld) // false
}
字符串值和布尔以及各种数值类型的值可以被用做常量。
常用方法
strings模块提供
- len(s): builtin模块中提供的方法,返回字符串s的长度(字节数,对于UTF-8编码的字符串,可能不等于字符数)。
- +或 fmt.Sprintf: 用于字符串拼接。+ 是直接的字符串连接,而fmt.Sprintf可以用于格式化字符串,包括将非字符串类型转换为字符串并拼接。
- strings.ToUpper(s), strings.ToLower(s): 将字符串s中的所有字符转换为大写或小写。
- strings.TrimSpace(s): 去除字符串s首尾的空白字符(包括空格、换行符\n、制表符\t等)。
- strings.Trim(s, cutset): 去除字符串s首尾的cutset指定的字符集合中的字符。
- strings.TrimLeft(s, cutset), strings.TrimRight(s, cutset): 分别去除字符串s左侧的或右侧的cutset中的字符。
- strings.Replace(s, old, new, n): 将字符串s中的前n个old子串替换为new,如果n为-1,则替换所有。
- strings.Split(s, sep): 使用sep作为分隔符将字符串s分割成切片,但不包括分隔符本身。
- strings.Join(a []string, sep string): 使用sep将字符串切片a中的元素连接成一个字符串。
- strings.Contains(s, substr): 判断字符串s是否包含子串substr。
- strings.HasPrefix(s, prefix), strings.HasSuffix(s, suffix): 判断字符串s是否以prefix开头或以suffix结尾。
- strings.Count(s, substr): 返回字符串s中子串substr非重叠出现的次数。
- strings.Index(s, substr), strings.LastIndex(s, substr): 返回子串substr在s中第一次或最后一次出现的位置索引,如果未找到则返回-1。
遍历字符串
可以使用for range循环遍历字符串中的每个字符
for index, runeValue := range "hello" {
fmt.Println(index, string(runeValue))
}
对于UTF-8编码的字符串,实际上是rune类型
字符串不可变
Go语言的字符串是不可变的,一旦创建了字符串,就不能更改字符串中的字符。上述方法都是基于创建新的字符串来“修改”原有字符串的
Complex
包括complex64和complex128两种类型。
用于表示复数,例如c := complex(1, 2)表示1+2i。
字符类型(Rune Type)
字符类型表示为rune,是int32的别名。
用于表示Unicode字符。
错误类型
Go没有专门的错误类型关键字,但通常使用error接口来表示错误。
错误处理是Go语言的一个重要特性。
复合数据类型
数组
相同的类型、固定长度的元素序列
// 空数组
var arr1 = [5]int{}
var arr2 [5]int = [5]int{1, 2, 3, 4, 5}
// 或者更简洁的写法
arr3 := [5]int{1, 2, 3, 4, 5}
// 如果数组的元素类型可以从初始化中推断出来,你还可以省略类型声明:
arr4 := [...]int{1, 2, 3, 4, 5} // 编译器会自动计算长度
由于数组的长度是类型的一部分,因此不同长度的相同类型数组被视为完全不同的类型。
在Go语言中,数组是值传递的。当你将一个数组作为参数传递给一个函数时,实际上传递的是数组的副本,而不是数组本身。这意味着,函数内部对数组的任何修改都不会影响到原始数组。
对于大型数组,这种传递方式可能会导致性能问题。在需要动态长度或性能敏感的场合,建议使用切片。
package main
import "fmt"
func main() {
var arr [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Println("len(arr) =", len(arr), "arr[2] =", arr[2])
modifyArray(arr) // 传递数组给函数
fmt.Println(arr) // 注意:arr的内容不会改变,因为数组是值传递
}
func modifyArray(a [5]int) {
a[0] = 100 // 修改数组的第一个元素
}
切片 Slice
go的切片类似于java的ArrayList,切片是引用类型
切片的类型为[]int,[]int32等等
package main
import "fmt"
func main() {
var s1 []int // 声明了一个切片s,但s是nil,没有分配内存
s2 := []int{1, 2, 3} // 声明并初始化切片s
var s3 = make([]int, 0, 5) // 使用make初始化s,长度为0,容量为5
// 可以像访问数组元素一样访问切片元素,使用索引
fmt.Printf("s1:%v\ns2:%v\ns3:%v\n", s1, s2, s3)
fmt.Println(len(s3)) // 输出切片的长度
fmt.Println(cap(s3)) // 输出切片的容量
// 容量不够时会进行扩容
s3 = append(s3, 4) // 向切片s追加元素4
s4 := s3[1:3] // 创建一个新的切片s2,包含s中索引为1和2的元素
fmt.Println(s4)
// 切片是引用类型,可以使用copy()函数来复制切片的内容到另一个切片中
var s5 []int = make([]int, len(s4))
copy(s4, s5) // 将s的内容复制到s3中
}
当切片引用的底层数组被GC时,切片也会变得不可用。
切片是引用类型,所以在函数间传递切片时,传递的是切片的引用,而不是切片的副本。
使用append()向切片追加元素时,如果追加导致容量不足,Go会分配一个新的底层数组,并更新切片的引用。因此,如果你将切片作为参数传递给函数,并在函数内部使用append()修改了切片,那么原始切片也会受到影响
Map
go内置map结构,无序
map的键可以是任何可比较的类型,如整数、浮点数、字符串或自定义类型等,但键必须是唯一的。值可以是任意类型。
var m map[string]int // 声明map: map[KeyType]ValueType
这样声明的map是nil的,不能直接使用。你需要在声明后使用make函数来分配内存,或者使用map字面量来初始化它。
m = make(map[string]int) // 使用make函数初始化
// 或者使用map字面量初始化
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
下面是map的一些常见操作
package main
import "fmt"
func main() {
// 使用map字面量初始化
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
value := m["one"] // 返回1
// 检查键是否存在
if value, ok := m["four"]; ok {
fmt.Println("Found:", value)
} else {
fmt.Println("Not found")
}
fmt.Println(value)
// 遍历 map
for key, value := range m {
fmt.Println(key, value)
}
}
map的键必须是可比较的
map的遍历顺序是不确定的,且每次遍历的顺序都可能不同。
map线程不安全
键不存在将返回该值的零值,并且没有错误提示。如果需要检查键是否存在,请使用上面提到的if value, ok := m[key]; ok
的语法。
Struct
go的结构体类似C和C++中的结构体
type Person struct {
Name string
Age int
Email string
}
创建结构体
var person1 Person
person1.Name = "Alice"
person1.Age = 30
person1.Email = "alice@example.com"
// 或者使用结构体字面量直接初始化
person2 := Person{
Name: "Bob",
Age: 25,
Email: "bob@example.com", // 最后一个字段也需要加逗号
// Need a trailing comma before a newline in the composite literal
}
// 如果字段的顺序与结构体中声明的顺序一致,可以省略字段名
person3 := Person{"Charlie", 35, "charlie@example.com"}
// 如果字段多,而只想设置部分字段,可以使用命名字段进行初始化
person4 := Person{
Name: "David",
Email: "david@example.com",
// 注意这里没有设置Age字段,它将使用零值(对于int是0)
}
结构体方法
Go语言的结构体不支持继承和方法重载,但可以有方法。方法是附加到类型上的函数,它们具有特殊的接收者参数,该参数在函数体内部用于访问调用它的实例的字段和方法。
type Person struct {
Name string
Age int
Email string
}
// value receiver
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}
// 或者为指针接收者(pointer receiver)定义方法,这样可以直接修改接收者的字段
func (p *Person) SetAge(age int) {
p.Age = age
}
这两种形式的方法可以同时定义,但是会有警告
Struct Person has methods on both value and pointer receivers. Such usage is not recommended by the Go Documentation.
value receiver 和 pointer receiver
Interface
接口中的方法没有函数体,只有方法名、参数列表和返回类型
type Shape interface {
Area() float64
Perimeter() float64
}
任何实现了这两个方法的类型都可以被视为实现了Shape接口,而不需要用类似java的implements显式声明,因此是隐式实现,例如:
type Circle struct {
radius float64
}
// Circle类型实现了Shape接口的所有方法
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.radius
}
// Rectangle类型也实现了Shape接口
type Rectangle struct {
width, height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
func (r Rectangle) Perimeter() float64 {
return 2*r.width + 2*r.height
}
接口可用于实现多态
func measure(s Shape) {
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
func main() {
c := Circle{radius: 5}
r := Rectangle{width: 10, height: 5}
measure(c)
measure(r)
}
空接口
Go语言还有一个特殊的接口,即空接口(interface{})。空接口不包含任何方法,因此任何类型都实现了空接口。空接口通常用于需要存储任意类型值的场景,如fmt.Println函数和interface{}类型的切片。
var any interface{} = 42
var str interface{} = "foo"
var list interface{} = []int{1, 2, 3}
类型转换
Golang即使数据类型兼容,它也不支持自动类型转换或隐式类型转换。原因是Golang的强类型系统不允许这样做。对于类型转换,必须执行显式转换。
var nhooo1 int = 845
// 显式类型转换
var nhooo2 float64 = float64(nhooo1)
var nhooo3 int64 = int64(nhooo1)
var nhooo4 uint = uint(nhooo1)
由于Golang具有强大的类型系统,因此不允许在表达式中混合使用数字类型(例如加,减,乘,除等),并且不允许在两个混合类型之间执行赋值类型。
var a int64 = 875
// 它会在编译时抛出错误给我们, 因为正在执行混合类型,例如把int64作为int类型
var b int = a
var c int = 100
//它抛出编译时错误
//这是无效操作
//因为类型是混合的 int64 和 int 相加
var d = a + c
流程控制
条件判断
if条件表达式不需要括号
if 条件表达式 {
// 条件为真时执行的代码
} else if 另一个条件表达式 {
// 另一个条件为真时执行的代码
} else {
// 所有条件都不为真时执行的代码
}
Go的 switch 更灵活,它不需要 break 语句来阻止自动落入下一个 case,并且它允许 case 后面跟随一个表达式而不是仅一个常量。
switch 表达式 {
case 值1:
// 当表达式等于值1时执行的代码
case 值2:
// 当表达式等于值2时执行的代码
default:
// 当表达式不等于任何值时执行的代码
}
// 或者不带表达式的switch,相当于switch true
switch {
case 条件1:
// 条件1为真时执行的代码
case 条件2:
// 条件2为真时执行的代码
default:
// 所有条件都不为真时执行的代码
}
循环
go中没有do-while,while-do,while(true)等
// 经典的for循环
for 初始化语句; 条件表达式; 后置语句 {
// 循环体
}
// 类似于while循环
for 条件表达式 {
// 循环体
}
// 无限循环
for {
// 循环体
}
range 关键字用于遍历数组、切片、字符串、映射或通道的元素。它经常与 for 循环一起使用。
for index, value := range 集合 {
// 使用index和value
}
continute和break和其它语言一样
跳转
goto 语句用于跳转到程序中的另一个标签处。尽管 goto 在某些情况下很有用(如跳出多层嵌套循环),但通常建议谨慎使用,因为它可能会使代码难以理解和维护。
函数
函数重载
同名函数具有不同的参数列表
编译错误:‘Sum’ redeclared in this package
func Sum(i int) int {
return i
}
func Sum(i int16) int16 {
return i
}
- 简化编译器实现:函数重载需要编译器执行大量解析和查找工作,这会增加编译器复杂性和性能开销
- 提高代码可读性:每个函数都有唯一的名称,这使得代码更容易阅读和理解,可以明确地知道哪个函数被调用
具名参数 (不支持)
参数默认值 (不支持)
可变参数
Go支持可变参数。在Go中,这是通过在一个参数类型前加上省略号(…)来实现的。这样,该参数就可以接受零个或多个指定类型的参数。
func Sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
和java一样,可变参数只能在最后
// Can only use '...' as the final argument in the list
func Sum(nums ...int, items ...int8) int {
total := 0
for _, num := range nums {
total += num
}
return total
}