一、基础语法
1. 变量
1. var identifier type
var a string = "Runoob" // 没有初始化则默认为零值,声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误2. var v_name = value // 根据值自行判定变量类型
var d = true3. v_name := value // 等价于 var identifier type = value4. var vname1, vname2, vname3 = v1, v2, v3 //多变量声明
5. vname1, vname2, vname3 := v1, v2, v3
命名规则:
1)声明在函数内部,是函数的本地值,类似private
2)声明在函数外部,是对当前包可见(包内所有.go文件都可见)的全局值,类似protect
3)声明在函数外部且首字母大写是所有包可见的全局值,类似public
2. 常量
1. const identifier [type] = value
const LENGTH int = 102. const (Unknown = 0Female = 1Male = 2
)
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
const (a = iotabc
)
// a=0,b=1,c=2
3. 指针
Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。
& 返回变量存储地址
* 指针变量
ptr = &a /* 'ptr' 包含了 'a' 变量的地址 */
fmt.Printf("*ptr 为 %d\n", *ptr) // 输出a的值
在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。
使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值
a := new(int)
make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型
var b map[string]int
b = make(map[string]int, 10)
new与make的区别
1.二者都是用来做内存分配的。
2.make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
3.而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针
4. 条件语句
switch marks {case 90: grade = "A"case 80: grade = "B"case 50,60,70 : grade = "C"default: grade = "D"
}// switch 可以用来判断存储类型
switch i := x.(type) {case nil: fmt.Printf(" x 的类型 :%T",i) case int: fmt.Printf("x 是 int 型") case float64:fmt.Printf("x 是 float64 型") case func(int) float64:fmt.Printf("x 是 func(int) 型") case bool, string:fmt.Printf("x 是 bool 或 string 型" ) default:fmt.Printf("未知型") }
5. 循环语句
sum := 0
for i := 0; i <= 10; i++ {sum += i
}// 这样写也可以,更像 While 语句形式
for sum <= 10{sum += sum
}for {sum++ // 无限循环下去
}//For-each range 循环
strings := []string{"google", "runoob"}for i, s := range strings {fmt.Println(i, s)}// 读取 key 和 valuefor key, value := range map1 {fmt.Printf("key is: %d - value is: %f\n", key, value)}// 读取 keyfor key := range map1 {fmt.Printf("key is: %d\n", key)}// 读取 valuefor _, value := range map1 {fmt.Printf("value is: %f\n", value)}// range 迭代字符串时,返回每个字符的索引和 Unicode 代码点(rune)for i, c := range "hello" {fmt.Printf("index: %d, char: %c\n", i, c)}// 遍历通道ch := make(chan int, 2)ch <- 1ch <- 2close(ch)for v := range ch {fmt.Println(v)}
6. 函数
func function_name( [parameter list] ) [return_types] {函数体
}
func max(num1, num2 int) int {/* 定义局部变量 */var result intif (num1 > num2) {result = num1} else {result = num2}return result
}// Go 函数可以返回多个值
func swap(x, y string) (string, string) {return y, x
}
init函数:go语言中init函数用于包(package)的初始化,如初始化包里的变量等
- 每个包可以拥有多个init函数
- 包的每个源文件也可以拥有多个init函数
- 同一个包中多个init函数的执行顺序go语言没有明确的定义
- 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序。初始化顺序是先导入的包,然后是当前包中的 init 函数,如果有多个 init 函数,它们会按定义的顺序依次调用
- init函数不能被其他函数调用,而是在main函数执行之前,自动被调用
6.5. 字符串
多行字符串使用反引号
s1 := `第一行第二行第三行`
字符串常用操作使用 strings 包
Go 语言的字符有以下两种:
uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
rune类型,代表一个 UTF-8字符。
当需要处理中文、日文或者其他复合字符时,需要使用rune类型
for _, r := range s { //runefmt.Printf("%v(%c) ", r, r)}
修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。
s1 := "hello"// 强制类型转换byteS1 := []byte(s1)byteS1[0] = 'H'fmt.Println(string(byteS1))s2 := "博客"runeS2 := []rune(s2)runeS2[0] = '狗'fmt.Println(string(runeS2))
7. 数组
var arrayName [size]dataType
var numbers [5]int
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
d := [...]struct {name stringage uint8}{{"user1", 10}, // 可省略元素类型。{"user2", 20}, // 别忘了最后一行的逗号。}// 如果设置了数组的长度,可以通过指定下标来初始化元素
balance := [5]float32{1:2.0,3:7.0} // 将索引为 1 和 3 的元素初始化
8. 切片
// 可以声明一个未指定大小的数组来定义切片
var identifier []type
// make([]T, length, capacity)函数定义切片, capacity容量为可选参数
slice1 := make([]type, len)
// 通过下标切片
s := arr[startIndex:endIndex]
// len() 和 cap() 函数获取长度和容量
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
注意,切片是对数组的引号,读写操作实际目标是底层数组
//append() 和 copy() 函数
var numbers []int
numbers = append(numbers, 0)
numbers = append(numbers, 2,3,4) // 向切片中添加元素numbers1 := make([]int, len(numbers), (cap(numbers))*2) // 创建切片 numbers1 是之前切片的两倍容量
copy(numbers1,numbers) // 拷贝 numbers 的内容到 numbers1
超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。 通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。
string底层就是一个byte的数组,因此,也可以进行切片操作
str := "hello world"
s1 := str[0:5]
9. Map
/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)
m := make(map[string]int)// 使用字面量创建 Map
m := map[string]int{"apple": 1,"banana": 2,"orange": 3,
}v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
// 删除键值对
delete(m, "banana")
10. 结构体
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
type struct_variable_type struct {member definitionmember definition...member definition
}type Books struct {title stringauthor stringsubject stringbook_id int
}//一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})var p = new(Book)
p.title = "格林童话" // 实际上是 (*p).title,Go语言帮我们实现了语法糖
// 结构体指针
var Book1 Books
printBook(&Book1)
func printBook( book *Books ) {// Go语言中支持对结构体指针直接使用.来访问结构体的成员fmt.Printf( "Book title : %s\n", book.title)fmt.Printf( "Book author : %s\n", book.author)fmt.Printf( "Book subject : %s\n", book.subject)fmt.Printf( "Book book_id : %d\n", book.book_id)
}
匿名结构体
var user struct{Name string; Age int}
user.Name = "pprof.cn"
构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
func newPerson(name, city string, age int8) *person {return &person{name: name,city: city,age: age,}
}
方法和接收者
// 官方建议使用接收者类型名的第一个小写字母
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体
}// 指针类型的接收者
// 调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的
func (p *Person) SetAge(newAge int8) {p.age = newAge
}p1 := NewPerson("测试", 25)
p1.SetAge(30)// 值类型的接收者
// 当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
func (p Person) SetAge2(newAge int8) {p.age = newAge
}
什么时候应该使用指针类型接收者
1.需要修改接收者中的值
2.接收者是拷贝代价比较大的大对象
3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
嵌套结构体
user1 := User{Name: "pprof",Gender: "女",Address: Address{Province: "黑龙江",City: "哈尔滨",},}
11. 类型转换
// 数值类型转换
type_name(expression)var a int = 10
var b float64 = float64(a)// 字符串转整数
var str string = "10"
var num int
num, _ = strconv.Atoi(str) // 第一个是转换后的整型值,第二个是可能发生的错误,可以使用空白标识符 _ 来忽略这个错误// 字符串转浮点数
str := "3.14"
num, err := strconv.ParseFloat(str, 64)// 整数转字符串
num := 123
str := strconv.Itoa(num)// 浮点数转字符串
num := 3.14
str := strconv.FormatFloat(num, 'f', 2, 64)
实现继承
//Animal 动物
type Animal struct {name string
}func (a *Animal) move() {fmt.Printf("%s会动!\n", a.name)
}//Dog 狗
type Dog struct {Feet int8*Animal //通过嵌套匿名结构体实现继承
}func (d *Dog) wang() {fmt.Printf("%s会汪汪汪~\n", d.name)
}func main() {d1 := &Dog{Feet: 4,Animal: &Animal{ //注意嵌套的是结构体指针name: "乐乐",},}d1.wang() //乐乐会汪汪汪~d1.move() //乐乐会动!
}
12. 接口
Go 中没有关键字显式声明某个类型实现了某个接口
只要一个类型实现了接口要求的所有方法,该类型就自动被认为实现了该接口
接口的零值是 nil,一个未初始化的接口变量其值为 nil,且不包含任何动态类型或值
接口的常见用法
多态:不同类型实现同一接口,实现多态行为。
解耦:通过接口定义依赖关系,降低模块之间的耦合。
泛化:使用空接口 interface{} 表示任意类型。
// 定义接口
type Shape interface {Area() float64Perimeter() float64
}// 定义一个结构体
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
}func main() {c := Circle{Radius: 5}var s Shape = c // 接口变量可以存储实现了接口的类型fmt.Println("Area:", s.Area())fmt.Println("Perimeter:", s.Perimeter())
}
空接口:定义为 interface{},可以表示任何类型
任意类型都实现了空接口。常用于需要存储任意类型数据的场景,如泛型容器、通用参数等。
func printValue(val interface{}) {fmt.Printf("Value: %v, Type: %T\n", val, val)
}
接口类型转换
// 类型断言,将接口类型转换为指定类型
value.(type)
value.(T)var i interface{} = "Hello, World"str, ok := i.(string)if ok {fmt.Printf("'%s' is a string\n", str)} else {fmt.Println("conversion failed")}// 类型转换,将一个接口类型的值转换为另一个接口类型
T(value)
// 定义一个接口 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"
}
接口可以通过嵌套组合,实现更复杂的行为描述
type Reader interface {Read() string
}type Writer interface {Write(data string)
}type ReadWriter interface {ReaderWriter
}
13. 错误处理
Go 标准库定义了一个 error 接口,表示一个错误的抽象,任何实现了 Error() 方法的类型都可以作为错误
type error interface {Error() string
}
使用 errors 包创建错误
import ("errors""fmt"
)func main() {err := errors.New("this is an error")fmt.Println(err) // 输出:this is an error
}// 函数通常在最后的返回值中返回错误信息,使用 errors.New 可返回一个错误信息
func Sqrt(f float64) (float64, error) {if f < 0 {return 0, errors.New("math: square root of negative number")}// 实现
}result, err:= Sqrt(-1)if err != nil {fmt.Println(err)
}
自定义错误
type DivideError struct {Dividend intDivisor int
}func (e *DivideError) Error() string {return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}func divide(a, b int) (int, error) {if b == 0 {return 0, &DivideError{Dividend: a, Divisor: b}}return a / b, nil
}func main() {_, err := divide(10, 0)if err != nil {fmt.Println(err) // 输出:cannot divide 10 by 0}
}
var ErrNotFound = errors.New("not found")func findItem(id int) error {return fmt.Errorf("database error: %w", ErrNotFound)
}func main() {err := findItem(1)// errors.Is检查某个错误是否是特定错误或由该错误包装而成。if errors.Is(err, ErrNotFound) {fmt.Println("Item not found")} else {fmt.Println("Other error:", err)}
}type MyError struct {Code intMsg string
}func (e *MyError) Error() string {return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}func getError() error {return &MyError{Code: 404, Msg: "Not Found"}
}func main() {err := getError()var myErr *MyError// errors.As将错误转换为特定类型以便进一步处理if errors.As(err, &myErr) {fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)}
}
panic 和 recover
panic:
导致程序崩溃并输出堆栈信息。
常用于程序无法继续运行的情况。
recover:
捕获 panic,避免程序崩溃。
func safeFunction() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r)}}()panic("something went wrong")
}
14. 内置函数
append – 用来追加元素到数组、slice中,返回修改后的数组、slice
close – 主要用来关闭channel
delete – 从map中删除key对应的value
panic – 停止常规的goroutine (panic和recover:用来做错误处理)
recover – 允许程序定义goroutine的panic动作
imag – 返回complex的实部 (complex、real imag:用于创建和操作复数)
real – 返回complex的虚部
make – 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new – 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
cap – capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy – 用于复制和连接slice,返回复制的数目
len – 来求长度,比如string、array、slice、map、channel ,返回长度