golang入门个人总结文档

全面解析Go语言特性,涵盖数据类型、流程控制、函数与方法、接口实现等核心内容,深入探讨变量声明、类型转换、切片与映射的高效使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

学习总结

官方文档 —— 需翻墙

注意点

  • 符号“{” 不能单独成行,必须跟在末尾
  • 编译期会判断,自动在每一行语句后面加必要的分号“;”
  • 需要多个语句写在同一行时,需要每个语句后加符号“;”
  • 标识符严格区分大小写,首字符不能为数字,命名规则为“驼峰式”,不可与“关键字”或“预定义标识符”重复
  • 变量、函数、常量名称的首字母也可以大写或小写
    • 如果首字母大写,则表示它可以被其它的包访问(类似于 Java 中的 public);
    • 如果首字母小写,则表示它只能在本包中使用 (类似于 Java 中 private)

操作符

普通操作符(不包括“运算符”,注意运算符的优先级)

操作符符号作用应用
:=变量声明初始化,可以多个变量同时赋值a := 1; c,d := 1.0,“hello”
=变量赋值(变量需要在此之前被声明),可以多个变量同时赋值a = 1; c,d = 1.0, “hello”
‘’单引号,定义字符,内容不会被转义(引号中的内容与实际的输出内容一致)str := `he\nllo`
``反引号,定义字符串,内容不会被转义(引号中的内容与实际的输出内容一致)str := `he\nllo`
“”双引号,定义字符串,内容会被转义(引号中的特殊字符会被转义)str := “he\nllo”
T()类型强制转换,T 表示目标转换类型intVal := string(32)
param.(T)类型断言与转换,判断变量param的数据类型是否实现了期望的接口或者具体的类型,T 表示期望的接口或具体类型。只有interface类型的变量才能使用断言。所以需要将先强转为interface类型,才能使用断言。可以使用“switch param.(type){}”确定变量类型。对值为nil的接口值进行断言会失败。valueOfInterface, isTypeMatched := interface{}(“interfaceOfStringValue”).(int)
new(T)创建T类型的指针ptr := new(string)
&取地址操作符,取出变量值的内存存储地址var big int16 = 1024; ptr := &big
*1、放在变量前,为取值操作符,根据地址取出地址指向的值var big int8 = *ptr
*2、放在类型前,用于声明变量的类型为指针var ptr *int8 = new(int8)
_空白标识符,任何赋给这个标识符的值都将被抛弃(这些值不能在后续的代码中使用)var _ int16 = *ptr
1、数组的长度是根据初始化值的个数来计算,[…]指定的数组大小==初始化值的个数,只能使用简短格式声明,标准格式会报错(类型不匹配)byteArr := […]byte{1, 2}
2、sliceParam…,对分片对象进行解包,得到切片中的所有元素sliceFive := append([]byte(“hellp”), []byte{1,2}…)
3、…T,声明变量类型为“可变参数类型”,指示函数传入的参数个数是可变的,参数本质上是一个数组切片。当 T 为 interface{},则可以传任意类型的数据。可根据“arg.(type)”断言,switch 出类型。func myfunc(args …int) {}

运算操作符(优先级值越大,表示优先级越高)

优先级分类运算符结合性
1逗号运算符,从左到右
2赋值运算符=、+=、-=、*=、/=、 %=、 >=、 <<=、&=、^=、|=从右到左
3逻辑或||从左到右
4逻辑与&&从左到右
5按位或&&从左到右
6按位异或^从左到右
7按位与&从左到右
8相等/不等(先比较类型,再比较值。都相同才称为相等。动态类型不可做等值比较,如map和切片)==、!=从左到右
9关系运算符<、<=、>、>=从左到右
10位移运算符<<、>>从左到右
11加法/减法+、-从左到右
12乘法/除法/取余*(乘号)、/、%从左到右
13单目运算符!、*(指针)、& 、++、–、+(正号)、-(负号)从右到左
14后缀运算符( )、[ ]、->从左到右

关键字(保留字)

关键字作用
var声明变量(全局变量、局部变量)
const声明常量(变量值需要在声明式指定)
type声明类型(声明类型定义,或类型别名,注意类型别名不是类型,本质上类型仍然为原类型)
struct声明自定义类型的结构体
range配合关键字 for 来迭代字符串\数组\切片\集合\通道里的每一个元素
defer延迟执行语句。代码在return之后执行(类似于finally), 存在多个延迟语句时,按照语句声明的先后顺序,从后往前执行。注意,只有defer后的第一个语句被延迟(多个执行语句时时可以写在函数中)。应用:关闭资源(文件流、连接)

预定义标识符

预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用
测试一下好像有些可以重复,不深究。。。。
数据类型,builtin包中的部分内容

关键字含义
iota常量生成器
panic(interface{})宕机,使程序终止运行
recover()恢复,防止程序崩溃,让进入宕机流程的goroutine恢复过来(仅在 defer延迟函数中有效),可以捕获异常的输入值。无异常时,recover()返回nil。注意异常后面的代码不会再执行
true无类型的布尔常量值
int不定长有符号整型
append
cap
close

对象声明\初始化格式

零值(默认值)

声明时未指定初始值的变量,会自动根据变量类型,赋零值。

“零值”Go语言中变量在声明之后但是未初始化被赋予的该类型的一个默认值

对象声明格式

1、标准格式

var 变量名 类型( = 表达式)
const 变量名 类型( = 表达式)

2、简短格式 —— 声明时必须指定初值,不能声明常量

变量名 := 表达式

  • 同时满足以下限制:
    • 定义变量,同时显式初始化。(该变量为初次声明)
    • 不能提供数据类型。
    • 只能用在函数内部。
3、批量格式

var(
变量名 类型 = 表达式
变量名 类型
变量名 = 表达式 // “无类型”变量

)

const(
变量名 类型 = 常量表达式
变量名 = 常量表达式 // “无类型”常量

)

初始化方式

make
  • 只能对chan、map 以及 slice 的内存创建
  • 返回的类型是引用类型,就是这三个类型本身
  • 分配后会进行初始化(比如分配空间、预分配空间)
new(T)
  • 可以分配任意类型的数据
  • new 函数只接受一个参数,这个参数是一个类型
  • 返回类型是指针,一个指向该类型内存区域起始位置的指针
  • 会把分配的内存置为零,也就是类型的零值
var sum *int
sum = new(int) //分配空间
*sum = 98
fmt.Println(*sum)

常量与非常量

1、常量

const

  • 需要通过关键字 const显示声明。
  • 大多数为无类型常量: 没有声明类型的常量,声明形式: const 变量名称 = 变量值
  • 常量间的运算(算术运算、逻辑运算和比较运算),运算结果也是常量
  • 对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof
  • (iota应用) 如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的
  • 批量声明常用来声明“枚举值”

2、变量(非常量)

var

  • 可以使用 :=简短格式声明

数据类型

没有“枚举”类型,可使用常量+别名,来模拟模拟枚举

// 声明芯片类型
type ChipType int
const (
    None ChipType = iota
    CPU    // 中央处理器
    GPU    // 图形处理器
)
func (c ChipType) String() string {// %s时调用
    switch c {
    case None:
        return "None"
    case CPU:
        return "CPU"
    case GPU:
        return "GPU"
    }
    return "N/A"
}
func main() {
    // 输出CPU的值并以整型格式显示
    fmt.Printf("%s %d", CPU, CPU)
}

类型转换

不会隐式转换,只能强制转换

  • byte <–> int
  • byte <–> string
  • bool 与 整型或字符或字符串 不可转换
  • string 与 整型或布尔型 不可转换
  • 整型 与 布尔型或字符串 不可转换
  • 长度不同的数组之间 不可转换

参数传递类型

type Data struct {
	first   int
	second  int
	arrow   *byte
	memberZ Member
	memberY Member
}
type Member struct {
	one string
}
值传递:param
  • 新对象为原对象的深度拷贝值
  • 新对象改动不会影响原对象
/* 所有值都是拷贝值,所以值的内存地址不同(再深层次的成员地址都是不同的) --> 所以无法修改 */
func valueCopy(obj Data) (d Data) {
	ptrOneForMemberY := &obj.memberY.one
	fmt.Printf("valueCopy: pointer=%p, value=%+v\n", ptrOneForMemberY, *ptrOneForMemberY)
	cloneMemberY := "YYYY"
	*ptrOneForMemberY = cloneMemberY
	fmt.Printf("valueCopy: pointer=%p, value=%+v\n", ptrOneForMemberY, *ptrOneForMemberY)
	return
}
func main() {
	// 初始化对象
	dataObj := Data{
		first:   0,
		second:  1,
		arrow:   nil,
		memberZ: Member{one:"zh"},
		memberY: Member{one:"yolanda"},
	}
    // 输出对象成员的内存地址和值
    ptrOneForMemberY := &dataObj.memberY.one
	fmt.Printf("initial: pointer=%p, value=%+v\n", ptrOneForMemberY, *ptrOneForMemberY)
	valueCopy(dataObj)
	fmt.Printf("initial: pointer=%p, value=%+v\n", ptrOneForMemberY, *ptrOneForMemberY)
}
引用传递(指针传递):&param
  • 新对象为原对象的指针,指向原对象的内存区域的起始地址
  • 可以通过指针,对原对象的成员进行修改
/* 对象内存地址的拷贝值,所以由该地址找到的所有成员都是原对象的成员 --> 所以会修改原对象 */
func referenceCopy(obj *Data) (d Data) {
	//obj = nil
	fmt.Printf("referenceCopy: pointer=%p, value=%+v\n",obj, *obj)
	obj.first++
	fmt.Printf("referenceCopy: pointer=%p, value=%+v\n", obj, *obj)
	return
}
func main() {
	// 初始化对象
	dataObj := Data{
		first:   0,
		second:  1,
		arrow:   nil,
		memberZ: Member{one:"zh"},
		memberY: Member{one:"yolanda"},
	}
    // 输出对象的内存地址和值
    fmt.Printf("initial: pointer=%p, value=%+v\n",&dataObj, dataObj)
	referenceCopy(&dataObj)
	fmt.Printf("initial: pointer=%p, value=%+v\n",&dataObj, dataObj)
}

分类

1、数值类型
整数
  • 定长有符号
    int8, int16, int32, int64
  • 定长无符号
    uint8, uint16, uint32, uint64
  • 不定长有符号
    int
  • 不定长无符号
    uint
浮点数(定长有符号)

float32
float64

复数(定长有符号)

complex64(32 位实数和虚数),
complex128(64 位实数和虚数)

2、布尔类型(定长)

bool

  • true,false 为无类型常量
3、字符类型(定长)

byte(uint8, 代表一个 ASCII 字符),
rune(int32, 代表一个 UTF-8 字符)

  • 利用“%T”打印数据类型时,为括号内的整型类型
4、字符串类型(不定长)

string

  • 字符串中的每一个元素叫做“字符”
  • 遍历方式
    • ASCII 字符串遍历直接使用下标
 for i := 0; i < len(theme); i++ {
		fmt.Printf("ascii: %c  %d\n", theme[i], theme[i])
	}
  • Unicode 字符串遍历用 for range
 for index, s := range theme {
     fmt.Printf("Unicode: %c  %d\n", s, s)
 }
5、指针类型(不定长)

*T,表示是T的指针类型

 var big int16 = 1024
 var small int8 = int8(big)
 var ptr *int8 = &small// 指针类型为 *int8,用于接收变量的内存地址。 表示是int8的指针类型,*表示指针
6、数组类型(声明时指定数组长度)
  • 多维数组:var array_name [size1][size2]…[sizen] array_type
  • 长度相同、类型相同的数组对象,可以直接使用 ==!= 比较是否相等(所有值都相等时,值相等)
  • 长度不同、类型相同的数组对象,不属于同一类型,不可进行强制转换
  • 长度相同、类型相同的数组对象与切片对象,不可等值比较(数组类型与切片类型不可互相比较)
  • 指定的数组长度表达式需要为省略号“…”(不能用标准声明格式)或常量表达式(在编译期就能确定值)
var byteOne [2]byte
byteTwo := [...]byte{0, 0}// ...
var byteThree [2]byte = [2]byte{0,0}
fmt.Println(byteOne==byteTwo, byteTwo == byteThree)// 输出:true true
// 多维数组
// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var arrayOne [4][2]int
arrayOne[0][0] = 10
// 使用数组字面量来声明并初始化一个二维整型数组
arrayTwo = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1 和 3 的元素
arrayThree = [4][2]int{1: {20, 21}, 3: {40, 41}}
// 将 arrayThree 的索引为 1 的维度复制到一个同类型的新数组里
var arrayThree1 [2]int = arrayThree[1]
// 将数组中指定的整型值复制到新的整型变量里
var value int = arrayThree[1][0]
7、切片(声明时不指定数组长度)

sliceVariable [开始位置 : 结束位置]

  • 多维切片:var sliceName [size1][size2]…[sizen] sliceType
  • 声明未初始化的切片对象,默认为 nil
    • 区别:声明为初始化的数组对象(指定了数组长度),不为 nil,各索引位上的值为指定类型的默认值
    • 初始化为空切片的对象,不为 nil
  • 切片对象之间不可使用运算符“==” 进行等值比较
    • 区别:数组对象之间可以使用运算符“==” 进行等值比较
  • 从数组或切片生成新的切片 —— 通过给定开始与结束位置(包括切片复位)的切片,!不会!发生内存分配操作(只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置)
    • 从指定范围中生成切片(不可越界):sliceVariable[1:2],sliceVariable[1:],sliceVariable[:2]
    • 重置切片,得到空切片(注意切片内存地址的开始位置):sliceVariable[index:index]
    • 克隆生成新切片(切片内元素相同,因为共享同一个底层数组): sliceVariable[:]
  • 利用 make\append方法生成新的切片 —— make生成切片!一定发生了!内存分配操作
  • 利用 copy方法复制切片(对象可以为 nil)
    • 类型必须一致,长度可以不一致。
      • 源对象长度a > 目标对象长度b,取源对象的前b个元素复制到目标对象中
      • 源对象长度a < 目标对象长度b,取源对象的全部元素复制到目标对象的前b个元素中
    • 可以对 nil切片进行 copy复制,无论作为源对象还是目标对象,都不会产生任何影响
  • 切片的增删改通过上述修改切片的方法实现
// 声明初始化
var sliceOne []int// 声明未初始化的切片对象, sliceOne == nil 结果为true
var sliceTwo []int = []int{}// 初始化为空切片的对象,sliceTwo == nil 结果为false,但sliceTwo[0]时有数组越界异常
var sliceThree []int = []int{} // 切片对象之间不可使用运算符“==” 进行等值比较
// 从数组或切片生成新的切片
sliceFive := []byte("hello");
sliceFiveClone := sliceFive[:]
fmt.Println(sliceFiveClone[1], sliceFive[1])
sliceFiveClone[1] = 'z'
fmt.Println(sliceFiveClone[1], sliceFive[1])// 变量的内存地址一致
sliceFiveEmpty := sliceFive[0:0]
fmt.Println(sliceFiveEmpty == nil, sliceFive[1])// 得到空切片(非nil)
// 利用 make\append方法生成新的切片
var sliceFour = make([]int, 1, 10)
fmt.Println(sliceFour[0])// 越界:sliceFour[1]
sliceFive := append([]byte("hello "), []byte{1, 2}...)// 利用符号“...”对分片进行解包
// 多维切片
slice := [][]int{{10}, {100, 200}}// 声明一个二维整型切片并赋值
slice[0] = append(slice[0], 20)// 为第一个切片追加值为 20 的元素
// 声明处理函数链
chain := []func(string) string{// 成员可以是任意满足形参与返回值类型的函数的函数名
    removePrefix,// 自定义函数
    strings.TrimSpace,// 标准库函数
    strings.ToUpper,
}
8、Map(又称关联数组、字典)
map
  • 无并发控制,非线程安全。线程安全的Map为“10.应用.1.sync.Map”
  • 声明(valuetype前可以有空格)
var mapname map[keytype]valuetype = map[keytype]valuetype{key1:value1, key2:value2}
var mapname map[keytype]valuetype = make(map[keytype]valuetype)
  • 添加元素
mapname[key1]=value1
mapname[key2]=value2
  • 遍历元组
var map1 map[int16]string = map[int16]string{1:"a",2:"b"}
var valueList []string
map1[3] = "c"
for k,v := range map1{
valueList = append(valueList, v)
fmt.Printf("key=%v, value=%v\n",k,v)
}
fmt.Println(valueList)
  • 删除元组
delete(map1,1)// 删除map1中key=1的元组
  • 清空所有元组:没有自带方法。推荐直接make新的对象,因为逐一删除效率远低于Go的并行垃圾回收操作的效率
9、通道channel
10、结构化类型(自定义类型)
声明类型别名

type TypeAlias = Type

  • TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型(利用%T打印出的是Type)
 type NewInteger = int32
声明类型定义
 type TypeAlias Type// 不包含成员
 type TypeAlias struct{// 包含成员,成员名称可省略
     (member1) Type1
     member2, member3,... Type2 // 相同类型变量可以声明在一起
   ...
     (memberN) TypeN
  }
  struct{// 匿名结构体
     (member1) Type1
   ...
     (memberN) TypeN
  }
 type Brand int// 不包含成员
 type BrandGroup struct {// 包含成员:struct
     a int
     string
 }
匿名结构体
  • 没有类型名的自定义结构
type BasicColor struct{
	r int
	g int
	b int
}

func (b *BasicColor) paint(){
	fmt.Println("print painting")
}

type Color struct {
	// 至多只能包含一个属于同种类型的匿名变量。
	// 多个匿名变量的内嵌成员(成员指:成员变量和成员方法)的名称重复时,无法通过编译
	int
	count int
	// 外部结构的实例可以直接访问内部结构匿名变量的成员,但不可直接访问非匿名变量的成员(成员指:成员变量和成员方法)。
	// 外部结构的实例也可以通过内嵌结构的类型名作为变量名,访问内部结构匿名变量的成员(成员指:成员变量和成员方法)。
	one BasicColor
	BasicColor
	// 声明Location变量,类型为内嵌匿名结构体:该变量初始化时,需要重新声明结构体
	Location struct{
		x int
		y int
	}
}

func main(){
    colorCollerction := new(Color)
    colorCollerction.int=1
    colorCollerction.BasicColor.r=2// 匿名变量的成员变量
    colorCollerction.g=3// 匿名变量的成员变量
    fmt.Printf("%+v\n",colorCollerction)
    colorCollerction.paint()// 匿名变量的方法
    colorCollerction.Location =struct {// 内嵌匿名结构体,初始化时需要再次声明结构
        x int// 不能写逗号
        y int
    }{
        x:1,// 要写逗号
        y:2,
    }
}
结构体的初始化
  • 匿名结构体的初始化同理,变量声明时定义匿名结构体,变量的初始化可以在声明时,也可以在声明之后
  • 利用“键值对”进行初始化
  • 利用“多个值的列表”进行初始化,即忽略变量名(键)
    • 利用“多个值的列表”进行初始化时需注意:
    • 1、必须初始化结构体的所有字段。
    • 2、每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    • 3、键值对与值列表的初始化形式不能混用
  • 至多只能包含一个属于同种类型的匿名变量
  • 多个匿名变量的内嵌成员(成员指:成员变量和成员方法)的名称重复时,无法通过编译
  • 外部结构的实例可以直接访问内部结构匿名变量的成员,但不可直接访问非匿名变量的成员(成员指:成员变量和成员方法)
  • 外部结构的实例也可以通过内嵌结构的类型名作为变量名,访问内部结构匿名变量的成员(成员指:成员变量和成员方法)
  • 内嵌匿名结构体(定义在结构体内的匿名结构体)类型的变量,初始化时,需要重新声明结构体
type People struct {
    name  string
    child *People
}
relation := &People{// 键值对形式
    name: "爷爷",
    child: &People{
        name: "爸爸",
        child: &People{
            name: "我",
        },
    },
}

type Address struct {
    Province    string
    City        string
    ZipCode     int
    PhoneNumber string
}
addr := Address{// 多个值的列表形式
    "四川",
    "成都",
    610000,
    "0",
}
  • 以下属性的代码示例
    • 非本地类型(?)不能自定义方法
    • 不同成员含有相同的方法时,调用异常(无法确定是调用哪个成员的方法)
标签:Tag

`key1:“value1” key2:“value2”`

  • 位于在结构体的字段后,最外层使用反引号 `
  • 结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔(键与值之间只能用一个冒号间隔,冒号不允许出现空格)
type cat struct {
    Name string
    Type int `json:"type" id:"100"`
}
应用
1、sync.Map
  • 线程安全,比map实现复杂、执行速率低,易引起运行时转换错误导致宕机
  • 是sync包下的特殊结构,不是以语言原生形态提供,与map关键字生成的对象不同
  • 只能使用 sync.Map 的方法实现键值对的新增、删除、修改、获取、遍历操作
  • 只需声明变量,无需初始化
  • 元素可以是任意类型:可以向一个实例中存储不同的任意类型的 key、value
var map2 sync.Map
map2.Store(1,"a")
map2.Store("1","a")
fmt.Printf("concurrent Map type is %T, value=%[1]#v\n",map2)
valueOfMap1, isExisted1 := map2.Load(1)
fmt.Printf("map[1]=%v, (key,value) exists: %v\n", valueOfMap1, isExisted1)
map2.Range(func(k,v interface{}) bool{
    fmt.Printf("key=%v, value=%v\n",k,v)
    if(k==1){
        map2.Delete(k)
    }
    return true
})
fmt.Printf("concurrent Map type is %T, value=%[1]#v\n",map2)
valueOfMap2, isExisted2 := map2.LoadOrStore(1,1)
fmt.Printf("map[1]=%v, (key,value) exists: %v\n", valueOfMap2, isExisted2)
fmt.Printf("concurrent Map type is %T, value=%[1]#v\n",map2)
2、list(container\list)
  • 易引起运行时转换错误导致宕机
  • 是contailner包下的特殊结构,不是以语言原生形态提供
  • 只能使用 container.list 的方法实现值的新增、删除、修改、获取、遍历操作
  • 元素可以是任意类型
//var list1 list.List // 使用此声明形式时,无需初始化
list1 := list.New() 
list1.PushBack(1)
ele := list1.PushBack("1")
list1.InsertBefore("2",ele)
list1.Remove(ele)
for element := list1.Front(); element != nil; element= element.Next() {// 遍历
    fmt.Printf("%T value=%[1]v\n",element)
    fmt.Printf("%T value=%[1]v\n",element.Value)
}
3、nil
  • map、slice、pointer、channel、func、interface 的零值
  • nil没有默认类型
  • 不同类型的nil的指针是一样的:0x0
  • 不同类型的nil的内存空间大小可能不同 —— 由类型决定,与同类型中的非 nil 类型的大小是一样的
  • nil标识符不能比较(nil == nil),不同类型的nil变量不能进行等值比较,同类型也不一定可以(切片间不能进行等值比较)
  • nil 不是关键字或保留字(我们可以定义一个名称为 nil 的变量,如 nil := “hah”,但并不提倡。声明nil变量之后,需要引用空值nil时,实际调用的是本地声明的nil变量)
var m map[int]string
var ptrInt *int
var ch chan int
var sl []int
var f func()
var i interface{}
fmt.Printf("type=%T, pointer=%[1]p, value=%[1]#v\n", m)
fmt.Printf("type=%T, pointer=%[1]p, value=%[1]#v\n", ptrInt)
fmt.Printf("type=%T, pointer=%[1]p, value=%[1]#v\n", ch)
fmt.Printf("type=%T, pointer=%[1]p, value=%[1]#v\n", sl)
fmt.Printf("type=%T, pointer=%[1]p, value=%[1]#v\n", f)
fmt.Printf("type=%T, pointer=%[1]p, value=%[1]#v\n", i)
11、函数类型:func

? Go语言函数类型实现接口

  • 一组形参或返回值有相同的类型,不必为每个形参都写出参数类型

  • 可以使用空白标识符“_” 声明参数(如形参、返回值),强调某个参数未被使用

  • 每一次函数在调用时都必须按照声明顺序为所有参数提供实参

  • 实参通过“值传递”的方式进行传递

  • 因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。

  • 支持多返回值

  • 每个函数至多指定一个接收器

  • 同一个包下,没有接收器的函数的函数名不可重复,但不同接收器间的函数名可以重复

参数声明的两种格式
  • 适用于形参和返回值
  • 两种形式不可混用,会报错:syntax error: mixed named and unnamed function parameters
func typedTwoValues() (z int, int) {// 异常
    return 1, 2
}
1、同一种类型返回值
func typedTwoValues() (int, int) {
    return 1, 2
}
func main() {
    a, b := typedTwoValues()
    fmt.Println(a, b)
}
2、带有变量名的返回值

返回值带有变量名时,return语句中可以忽略返回值(只需声明“return”即可)

func sub(x, y int, c, d string) (z int, v int) {
	z = x - y // z 无需再声明
	v = 100
	return // 可以不指定返回值
	//return -1,z// 指定返回值时,必须按顺序声明返回值
}
参数的值传递方式

见“数据类型.参数传递类型”

匿名函数
// 定义格式
func(参数列表)(返回参数列表){
    函数体
}
声明调用方式
  • 1、在定义时调用匿名函数
func(data int) {
	fmt.Println("hello", data)
}(100)
  • 2、将匿名函数赋值给变量
// 将匿名函数体保存到f()中
f := func(data int) {
	fmt.Println("hello", data)
}
// 使用f()调用
f(100)
闭包(Closure)

函数 + 引用环境 = 闭包

  • 引用了外部变量的匿名函数称为“闭包”
  • 函数为“匿名函数”
  • 引用环境为“被函数调用,且声明在函数外部的变量”
  • 若匿名函数中,引用\调用了在匿名函数外部声明的变量,则该匿名函数称为“闭包”
  • 变量不同,同一个匿名函数可以形成不同的“闭包”
变参函数
  • 包含可变参数类型形参的函数
  • 可变参数类型:…T,T表示数据类型,本质上是一个数组切片
  • 当 T为 interface{}时,表示参数可以是任意类型的数据,可利用“switch param.(type)”获取param变量的实际类型
  • “可变”指的是参数的个数可变
  • 当且仅当数据类型为 interface{}时,可以使用断言param.(T)
  • 在多个可变参数函数中传递参数,需要使用“…”进行解包
// 实际打印的函数
func rawPrint(rawList ...interface{}) {
    // 遍历可变参数切片
    for _, a := range rawList {
        // 打印参数
        switch a.(type) {
		case int:
			fmt.Println(a, "is an int value.")
		case string:
			fmt.Println(a, "is a string value.")
		case int64:
			fmt.Println(a, "is an int64 value.")
		default:
			fmt.Println(a, "is an unknown type.")
		}
    }
}
// 打印函数封装
func print(slist ...interface{}) {
    // 将slist可变参数切片完整传递给下一个函数
    rawPrint(slist...)// 利用 ... 对“切片”进行解包
}
func main() {
    print(1, 2, 3)
}
模拟构造函数
  • GO没有构造函数,可没有“重载”的概念
  • 可以实现创建实例返回指针的方法,实现构造函数的功能
    type Cat struct {
        Color string
        Name  string
    }
    func NewCatByName(name string) *Cat {
        return &Cat{
            Name: name,
        }
    }
    func NewCatByColor(color string) *Cat {
        return &Cat{
            Color: color,
        }
    }
方法(结构体方法)
  • GO方法是作用在接收器(receiver)上的一个函数
  • 面向过程编程中没有方法的概念,只有“结构体”和“函数”的概念,可以通过“结构体+函数”,有使用者使用函数参数与调用关系来形成接近“方法”的概念 —— GO不是严格的面向过程或是面向对象
  • 类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的
  • 需要为基本类型添加方法时,可以为方法指定接收器类型为“基本类型的自定义类型别名”
方法的接收器
  • 接收器类型
    • 可以是(几乎)任何类型,不仅仅是结构体类型,任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型
    • 但不能是一个接口类型(因为接口是抽象的定义,方法是具体的实现)
    • 也不能是一个指针类型(但可以是允许类型的指针,? 指针不就是指针类型吗。。。)
  • 每个方法只能有一个接收器
  • 接收器变量名,官方建议为接收器类型名的第一个小写字母,如Socket 类型的接收器变量应该命名为 s
  • 接收器类型和参数类似,可以是指针类型和非指针类型
  • 对于具体结构类型T,指针类型*T接收器的方法集与同一个类型的非指针类型T接收器的方法集不同,“非指针类型接收器的方法集”包含在“指针类型接收器的方法集”中
  • 当接收器类型为指针类型时,利用接收器变量是结构体实例的引用,利用变量进行的修改会对该实例生成影响 —— 适用于大对象
  • 当接收器类型为非指针类型时,接收器变量是结构体实例的值拷贝,利用变量进行的修改不会对该实例生成影响 —— 使用小对象
  • 小对象由于值复制时的速度较快,所以适合使用非指针接收器,大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。
  • 接收器强调方法的作用对象是接收器,也就是类实例(而函数没有作用对象)
  • 类型 T(或 T)上的所有方法的集合叫做“类型 T(或 T)的方法集”,T 可以是具体类型或他的指针类型
// 面向过程实现方法 --> 缺少方法与类型的归属关系
    type Bag struct {
        items []int
    }
    // 将一个物品放入背包的过程
    func Insert(b *Bag, itemid int) {// 将操作对象作为形参
        b.items = append(b.items, itemid)
    }
    func main() {
        bag := new(Bag)
        Insert(bag, 1001)
    }
// Go语言的结构体方法
    type Bag struct {
        items []int
    }
    func (b *Bag) Insert(itemid int) {// 指定接收器,表明方法的作用对象实例
        b.items = append(b.items, itemid)
    }
    func main() {
        b := new(Bag)
        b.Insert(1001)
    }
12、接口类型:interface
type InterfaceName interface {
	methodNameOne(形参列表)(返回参数列表)// 参数可以不指定参数变量名称
	methodNameTwo(形参列表)(返回参数列表)
	...
}
  • 接口不可声明变量(吗?),接口是抽象的定义
  • 一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样
    • 只要类型实现了所有内嵌接口的方法,则该类型实现了外层接口
空接口

interface{}

  • 所有类型都可以实现了空接口类型,因此可以强转为空接口类型
    • 应用:所有类型的变量需要断言时,都应该先强转为interface{}类型
  • 任何类型的值都可以直接赋值给空类型变量,但空类型变量值需要类型强转后才能赋值给具体类型的变量
    • 空类型变量可以被不同类型的值重复赋值
实现关系的检查者
  • Go编译器将自动在需要的时候检查两个类型之间的实现关系
断言

value, ok := x.(T)

  • 如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的动态值,其类型是 T
    • 对具体类型变量进行断言,返回对象的类型也是具体类型
  • 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是一个类型为 T 的接口值
    • 对接口类型变量进行断言,返回对象的类型也是接口类型
    • 具体类型的指针变量,强转为接口类型后获取的指针变量,也是接口类型,断言返回接口类型变量
  • 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败
    • fmt.Println(interface{}(nil).(int)) // 异常: interface {} is nil, not int
// 类型定义见下文
// impAll := GameService{}// typeService为具体类型GameService
impAll := new(GameService)// typeService为接口类型Service
switch typeService:=interface{}(impAll).(type) {
    case Service:
    	fmt.Println("Service")
    case GameService:
    	fmt.Println("GameService")
    case ImplementAllService:
    	fmt.Println("ImplementAllService")
    default:
   		fmt.Printf("%T,value=%+[1]v\n",typeService)
}
类型实现接口的条件

结构实现接口的“类型转换”的问题讨论:https://stackoverflow.com/questions/40823315/x-does-not-implement-y-method-has-a-pointer-receiver

  • 接口的方法与实现接口的类型方法签名一致
    • 方法签名一致:方法名称、参数列表、返回参数列表
  • 接口中所有方法均被实现:不一定需要由一个类型完全实现
    • 接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的
// 一个服务需要满足能够开启和写日志的功能
type Service interface {
    Start()  // 开启服务
    Log(string)  // 日志输出
}
// 日志器
type Logger struct {
}
func (g *Logger) Log(l string) {// 实现Service的Log()方法
}
// 游戏服务:通过内嵌Logger类型的成员,实现了Service类型的所有方法 --> 类型GameService实现了接口Service
type GameService struct {
	Logger  // 嵌入日志器
}
func (g *GameService) Start() {// 实现Service的Start()方法
}
// 结构的方法集包含了接口的所有方法,但部分属于类型的方法集,部分属于指针类型的方法集
type ImplementAllService struct {
}
func (i ImplementAllService) Start(){// 属于类型的方法集
	fmt.Println("start ImplementAllService")
}
func (i *ImplementAllService) Log(l string){// 属于指针类型的方法集
	fmt.Println(l)
}
类型与接口的转换
  • 接口类型interface{}的指针类型 *T,可以强转为类型实现的接口类型 InterfaceName
    • 接口类型interface{}的类型实例 T,不一定可以强转为类型实现的接口类型 InterfaceName:只有当接口的所有实现方法都属于类型的方法集(区别与指针类型的方法集)时,才可以进行强转
  • 类型实现的接口类型的指针 *InterfaceName,可以强转为指针类型 *T,必须是要转换的对应的指针类型(转换目标的类型不可以是其他实现了该接口类型的类型)
  • 实现了接口的类型的实例变量,可以直接赋值给接口类型的变量
    • 非侵入式设计:隐式实现接口,无须让实现接口的类型写出实现了哪些接口
    • 接口中定义了多个方法时,类型需要实现接口中的所有方法, 才能说该类型实现了接口
  • 只有类型的方法集包含接口的所有待实现方法时,该类型的实例变量值才可以直接赋给被实现接口的实例变量
    • “类型的方法集”:注意类型可以是具体类型,也可以是他们的指针类型
    • “具体类型的方法集”不同于“指针类型的方法集”
  • “具体类型的方法集”包含在“指针类型的方法集”中,“指针类型的方法集”不包含“具体类型的方法集” 文档链接:搜索关键字“ promoted methods”
    • 因此,最好使用“指针变量”进行“类型转换”
      • 任意类型都可以强转为 interface{} 类型
        • 但转为具体的结构类型或接口类型(断言)时,需要看是否满足一定的关系
    • 若接口的所有方法(多个)分别属于两个种接收器类型(指针类型和非指针类型),则
      • 该具体类型的实例不可以被强制转换为接口类型(其实例的指针可以)
        • interface{}(structInstance).(InterfaceName) // 异常
        • iinterface{}(&structInstance).(InterfaceName) // 正常
      • 该具体类型的实例不可以直接赋值给被实现接口的变量,但具体类型的指针可以直接赋值给被实现接口的变量,且接口变量可以调用该接口的所有方法,方法实现体为具体类型的方法
        • interInstance := structInstance // 异常
        • interInstance := &structInstance // 正常
type GameClone struct{// 继承了成员GameService的方法,因此也实现了接口Service
	GameService
}
func main(){
	//game := GameService{Logger{}}// 类型T不可转为Service
	game := new(GameService)// 指针类型*T可以转为Service
	insertionGame := interface{}(game).(Service)// 类型不匹配时 panic
	//ptrOfGame := insertionGame.(*GameClone)// main.Service is *main.GameService, not *main.GameClone
	fmt.Printf("存储地址为:%T,value=%+[1]v, 强转后存储地址为:%T,value=%+[1]v\n",game,insertionGame)
	
}
类型与接口的关系
  • 一个类型可以实现多个接口
  • 一个接口可以被多个类型实现
// 定义一个数据写入器
type DataWriter interface {
    WriteData(data interface{}) error
}
// 定义文件结构,用于实现DataWriter
type file struct {
}
// 实现DataWriter接口的WriteData方法
func (d *file) WriteData(data interface{}) error {
    // 模拟写入数据
    fmt.Println("WriteData:", data)
    return nil
}
func main() {
    f := new(file)// 创建变量,变量类型实现了接口
    var writer DataWriter// 声明一个接口的变量
    writer = f// 将接口赋值f,也就是*file类型
    // 使用DataWriter接口进行数据写入
    writer.WriteData("data")
}

流程控制

结构 —— 都不需要括号

if…else if… else…
if 变量声明初始化语句;条件表达式 {// 可以加变量声明初始化语句,需要初始化多值时,需要使用多值同时赋值(a,b := 1, 2)
	// TODO
}else if 条件表达式{
	// TODO
}else {
	// TODO
}
switch…case…default…
// case内为独立代码块,执行结束后,不会像C++\java一样,不会继续执行下一个代码块
// 但可以使用关键字“fallthrough”,强制本代码块执行结束后进入下一个case\default代码块
switch 变量{// 变量在switch中,case 后跟常量值
	case 常量值:
		// TODO
	case 常量值1, 常量值2, ...// 可以多值
		// TODO
	...
	default:// 可忽略,至多出现一次
		// TODO
}

switch {// switch中没有变量,case 后跟条件表达式
	case 条件表达式:
		// TODO
	case 条件表达式:
		// TODO
	...
	default:// 可忽略,至多出现一次
		// TODO
}
type-switch 结构(类型断言结构)
  • 接口变量为 nil常量值时,断言
switch 接口变量.(type) {
    case 类型1:
    // 变量是类型1时的处理
    case 类型2:
    // 变量是类型2时的处理default:
    // 变量不是所有case中列举的类型时的处理
}

switch tempValue := 接口变量.(type) {// 可以获取断言结果
    case 类型1:
    // 变量是类型1时的处理
    case 类型2:
    // 变量是类型2时的处理default:
    // 变量不是所有case中列举的类型时的处理
        fmt.Printf("Unexpected type %T\n", tempValue)// 利用fmt包获取变量的信息:类型、值...
}
for…
for 初始化语句;条件表达式;结束语句 {// 三个语句都忽略时,for后面的分号";"也不用写
	// TODO
}

跳转语句

goto LabelName
  • LabelName 可以声明在任意语句前后
  • LabelName声明与被调用的前后顺序可以不一样
break (LabelName) —— 跳出循环
  • 可以跳出指定的循环
  • LabelName 只能声明在流程控制结构声明前:switch、for、select
  • LabelName被调用的位置必须在声明的循环内
continue (LabelName) —— 跳转至下一个循环
  • 可以结束指定的本次循环,继续下一次循环
  • LabelName 只能声明在流程控制结构声明前:for
  • LabelName被调用的位置必须在声明的循环内
fallthrough
  • 使用与switch的case代码块最后,声明继续执行下一个case\default代码块
  • 为了兼容存在,不推荐使用
panic
return

fmt:格式化、打印输出

strconv:字符串和其他类型的相互转换

math:数值运算

unicode:字符集

bytes:字节切片,与strings包功能类似,I/O

flag:命令行参数解析

sort:排序与查找

regexp:正则表达式

encoding:对结构化信息的标准格式的编码解码协议

json
xml
asn1

bufio:I/O对象及操作

ReadWriter
Reader
Writer

http:网络请求

time:时间的获取与计算

runtime:异常、GC

log:日志

errors:异常

  • 异常抛出会引起“宕机”,程序中断结束
  • panic()后的语句不会被执行,但 panic()前的 defer语句会在异常抛出后、程序退出前执行

testing:测试

  • 测试文件名,必须以“_test.go”为文件后缀名
  • 测试函数,必须以“Test”或“Benchmark”为函数名前缀,运行时会直接执行测试函数,不需要用main函数作为程序入口
  • 运行测试函数时,使用命令“go test”
1、单元测试
  • Test_函数的形参:(t *testing.T)
2、性能(压力)测试
  • Benchmark_函数的形参:(t *testing.B)
3、覆盖率测试

环境配置

下载sdk包:

地址:https://golang.google.cn/dl/

建议下载1.13以上,直接下最新的即可

配置环境变量:

# golang sdk包的根路径
GOROOT=%sdk的根路径%

# golang可执行文件的地址
PATH=%GOROOT%\bin

# golang项目代码存放路径
# 项目真实存放在 `%GOPATH%\src` 目录下
# go mod 拉取的依赖包会自动下载到 `%GOPATH%\pkg` 下
GOPATH=%golang项目代码的存放根路径% 

# 公共代理镜像仓库。默认的中国无法访问
GOPROXY = "https://goproxy.io,direct"

# 模块支持,go 会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod 下载依赖。旧版gosdk只支持vendor下载依赖
GO111MODULE=on

常用命令

下载依赖

# 下载依赖。要存在 go.mod 文件。
go mod download

# 清理无用依赖,下载需要的依赖
go mod tidy

编译生成可执行文件 —— 支持跨平台编译(如windows机器上可以编译出linux版本的可执行文件)

go mod {main.go的路径}

# 指定编译生成的可执行文件的名称
go mod -o {nameWithSuffix_of_executable_file} main.go

# 编译linux x86-64格式的文件
go env -w GOOS=linux
go env -w GOARCH=amd64
go mod -o {nameWithSuffix_of_executable_file} main.go

# 编译linux arm格式的文件
go env -w GOOS=linux
go env -w GOARCH=arm64
go mod -o {nameWithSuffix_of_executable_file} main.go

# 编译windows x86-64格式的文件
go env -w GOOS=windows
go env -w GOARCH=amd64
go mod -o {nameWithSuffix_of_executable_file} main.go
### Golang入门教程与基础知识 #### 什么是GolangGolang(也称为Go语言)是一门由Google开发的编程语言,起源于2007年并于2009年正式对外发布。这门语言的主要目标是结合动态语言(如Python)的快速开发能力和编译型语言(如C/C++)的高性能与安全性[^2]。 #### Golang的特点 以下是Golang的一些核心特点: - **高效性**:作为一种编译型语言,Go能够在不牺牲程序执行速度的情况下减少内存占用,从而提升运行效率。 - **并发性**:通过内置的`goroutine`机制,Go能够轻松实现高效的并发处理。 - **简单性**:Go语言的设计注重简洁明了,语法易于学习且代码具有较高的可读性。 - **安全性**:提供内存安全和类型安全保障,有效防止常见的内存泄漏和类型错误问题[^1]。 #### 初学者的学习路径 对于初学者来说,可以从以下几个方面入手来掌握Golang的基础知识: ##### 1. 安装与环境配置 访问官方站点(https://golang.org/dl/),根据自己的操作系统下载合适的版本并完成安装。随后按照指南设置必要的环境变量,例如将Go的安装路径加入系统的PATH中。 ##### 2. 基本语法 熟悉基本的数据类型、控制结构以及函数定义等内容。例如,了解如何使用`switch`语句来进行条件判断: ```go package main import "fmt" func main() { x := 1 switch x { case 1: fmt.Println("One") case 2: fmt.Println("Two") default: fmt.Println("Unknown") } } ``` 上述代码展示了如何利用`switch`语句根据不同情况打印不同的字符串[^3]。 ##### 3. 并发编程 深入理解`goroutines`和`channels`的概念及其实际应用场景。这是Go区别于其他语言的重要特性之一。 ##### 4. 实践项目 理论学习之后,可以通过构建小型项目巩固所学的知识点。比如编写简单的Web服务或者命令行工具。 #### 推荐学习资源 - **官方网站文档**: https://golang.org/doc/ - **A Tour of Go**: 提供了一个交互式的在线课程帮助新手快速上手 (https://tour.golang.org/welcome/1). - 各种开源书籍和技术博客也是不错的补充材料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值