//这里记录一些在学习golang语言基础的时候,发现的一些容易记错的特殊情况,后续发现其他的再补充。
一、变量和数据方面
1.切片和map引用类型
问题描述:在go中,切片和map是引用类型,所以当修改切片值的时候,会同步修改到原数据中,下面用切片数据进行演示
package main
import (
"fmt"
)
func main() {
var arr []int = []int{1,2,3,4,5} // 声明一个切片数据
fmt.Println(arr) // [1 2 3 4 5]
tarr := arr[1:4] // 针对arr数据进行切片获取
fmt.Println(tarr) // [2 3 4]
tarr[2] = 22 // 赋值切片数据
fmt.Println(arr) // [1 2 3 22 5]
fmt.Println(tarr) // [2 3 22]
}
2.结构体间比较
问题描述:在 Go 语言中,Go 结构体有时候并不能直接比较,当其基本类型包含:slice、map、function 时,是不能比较的。若强行比较,就会导致出现报错
3.遗漏的byte和rune类型
问题描述:记录下遗漏的byte和rune类型
byte: uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。(比如: a输出就是)
rune: rune类型,代表一个 UTF-8字符。
package main
import (
"fmt"
)
func main() {
s := "www.huixiong.com博客" //设定字符串
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i]) //输出字符串中对应的ASCII码和字符
}
fmt.Println()
for _, r := range s { //rune
fmt.Printf("%v(%c) ", r, r) //输出字符串中对应的ASCII码和字符
}
fmt.Println()
}
4.数组和切片
- 切片容量可伸缩;数组容量不可变;
- 切片与切片不可比较,切片只能与nil比较;数组之间可以比较;
- 当切片原有空间不足时,且切片原有空间<1024,那么切片的空间将会直接翻倍。如原切片空间为3,则空间不足时,go会直接扩展为6
二、变量与作用域方面
1.语句中声明变量的作用域
问题描述:即使在同一函数中,在if、for等语句中声明的变量,都是全新且作用于该语句内(类似语句中的局部变量)。
package main
import (
"fmt"
)
func main() {
i, j := 0, 0 // 赋值 i,j 为0
if true {
fmt.Printf("i = %d, j = %d\n", i, j) // i = 0, j = 0
i, j := 1, 1 // 新变量 i,j 赋值为1
fmt.Printf("i = %d, j = %d\n", i, j) // i = 1, j = 1
}
fmt.Printf("i = %d, j = %d\n", i, j) // i = 0, j = 0
for k := 0 ; k < 1; k++ {
fmt.Printf("i = %d, j = %d\n", i, j) // i = 0, j = 0
i, j := 2, 2 // 新变量 i,j 赋值为2
fmt.Printf("i = %d, j = %d\n", i, j) //i = 2, j = 2
}
fmt.Printf("i = %d, j = %d\n", i, j) // i = 0, j = 0
}
三、函数和方法方面
1.函数可变参数
问题描述:函数中有可变参数传入,如下代码所示
package main
import (
"fmt"
)
func main() {
fmt.Println(sum(1))
fmt.Println(sum(1,2,3))
}
func sum(nums ...int)int{ //形参和类型中间用...来声明 可变参数,如果不传则为nil
fmt.Println("len of nums is : ", len(nums))
res := 0
for _, v := range nums{
res += v
}
return res
}
注意:1.当函数形参用...声明为可变参数时,如果不传入参数则 nums 的值为nil。2. 可变参数必须在函数参数列表的尾部,即最后一个(如放前面会引起编译时歧义)
2.main和init的区别
问题描述:在go中,除了默认入口函数(主函数) main 以外,还有个初始化包(package)的 init 函数。
相同点:(init和main)两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
不同点:init可以应用于任意包中,且可以重复定义多个。main函数只能用于main包中,且只能定义一个。
3.匿名函数
问题描述:记录一下匿名函数,匿名函数是指不需要定义函数名的一种函数实现方式。
package main
import (
"fmt"
)
func main() {
getSqrt := func(a float64) float64 { //声明匿名函数
return math.Sqrt(a)
}
fmt.Println(getSqrt(4))
}
4.闭包函数
问题描述:记录下go语言中的 闭包函数,闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)
package main
import (
"fmt"
)
func addNum() func() int{
var count int =0 //计数
addFun := func () int{
count++
sumNum := 15+2
fmt.Println(count,sumNum) //输出count和sumNum值
return sumNum
}
return addFun
}
func main() {
sum := addNum() // 不输出
sum() // 输出 1 17
sum() // 输出 2 17
addNum() // 不输出
}
由上面的代码,我们可以看出,addNum() 函数其实不会主动调用addFun()函数,而且在 sum :=addNum() 之后,每次调用 sum() 函数,其实都是调用 addFun() 函数,所以我们可以粗浅的将addNum() 函数当作 addFun 函数的指针,当sum := addNum() 之后,其实可以理解为 将addFun() 传递给了 sum ,当执行 sum() 函数时,其实是执行的 addNum() 函数。
5.有参数的闭包函数
问题描述:下面记录一下有参数调用的闭包函数
package main
import (
"fmt"
)
func addNum(a int, b int) func(int,int) int{
var count int =0
addFun := func (a int, b int) int{
count++
sumNum := a+b
fmt.Println(count,sumNum)
return sumNum
}
return addFun
}
func main() {
sum := addNum(0, 0) // 不输出
sum(1, 2) // 输出 1 3
sum(2, 3) // 输出 2 5
addNum(0, 0) // 不输出
}
6.defer 函数和方法的延迟调用
问题描述:defer
语句的用途是:含有defer
语句的函数,会在该函数将要返回之前,调用另一个函数;
package main
import (
"fmt"
)
func printA(a int) {
fmt.Println("函数一调用", a) //输出 函数一调用 5
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("函数二调用", a) //输出 函数二调用 10
}
//使用 defer 延迟调用关键字后,上面程序优先输出 函数二调用,后输出 函数调用一
注意:在for循环和range中,由于 i 和 range 分配的key值 的内存地址不变,所以延迟输出都是取的最后一次循环。如下代码所示
package main
import (
"fmt"
)
func main() {
for i := 0;i < 5;i++ {
fmt.Println(&i,i)
defer func() { fmt.Println(i) }()
}
}
//使用 defer 关键字后,上面程序执行完毕,输出的 i 全为 5, 这是因为在for循环中,所有遍历的i都是用的同一个内存地址
注意:defer 函数或方法后,依旧是在 return 前执行,所以在特定情况下依旧可以修改return返回值。
四、面向对象编程
1. New() 函数
new函数作用于实例化结构体,返回的是实例化后的指针
package main
import (
"fmt"
)
type Student struct{ //声明结构体
name string
age int
}
func main() {
stu := new(Student) //实例化结构体 Student ( 这里是指针,同等于:stu := &Student{} )
stu.name = "王强" //结构体属性赋值
stu.age = 18 //结构体属性赋值
fmt.Println(stu)
fmt.Println(*stu)
}
2.结构体的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
package main
import (
"fmt"
)
type Pepole struct{
name string
}
func (s *Pepole) Move(){
fmt.Println(s.name,"向着北边移动中...")
}
type Wangqiang struct{
leg int8
*Pepole
}
func (wq *Wangqiang) Step(){
fmt.Println(wq.name,"迈开",wq.leg,"条腿 ")
}
func main() {
wq := &Wangqiang{
leg:2,
Pepole:&Pepole{
name:"王强",
},
}
wq.Step() //王强 迈开 2 条腿
wq.Move() //王强 向着北边移动中...
}
五、错误机制
1.error
go没有异常机制,下面接口是通过错误断言判断
func main() { f, err := os.Open("/test.txt")if err, ok := err.(*os.PathError); ok {fmt.Println("File at path", err.Path, "failed to open")return}fmt.Println(f.Name(), "opened successfully") }
2.自定义错误
errors.New可以快速创建错误实例(需要实现error接口)
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
3.类似try-catch语法
func Try(fun func(), handler func(interface{})) {
defer func() {
if err := recover(); err != nil {
handler(err)
}
}()
fun()
}
golang学习笔记-推荐看云-概述 · Go学习笔记 · 看云