Skip to content

语法结构

结构体

结构体(struct)是一种复合数据类型,允许你将多个不同类型的项(称为字段)组合成一个单一的类型。这使得结构体成为组织和存储不同数据的强大工具,类似于其他语言中的类或记录。

go
//定义结构体
type User struct{
    id int
    score float32
    enrollment time.Time
    name,addr string //多个字段类型相同时可以简写到一行里
}

//初始化一个实例
func main(){
    //声明,会用相应类型的默认值初始化struct里的每一个字段
    var u User
    //相应类型的默认值初始化struct里的每一个字段
    u = User{}
    //赋值初始化
    u = User{id:3,name:"Aoliao"}
    //赋值初始化,,可以不写字段,但需要跟结构体定义里的字段顺序一致
    u = User{4,100.0,time.Now(),"Aoliao","Los Angeles,Ca,United States"}
}

修改与访问结构体

go
//给结构体的成员变量赋值
u.enrollment = time.Now()
//访问结构体的成员变量
fmt.Printf("id=%d,enrollment=%v,name=%s\n",u.id,u.enrollment,u.name)

成员函数(方法)

go
//可以把hello理解成User内部 的方法
func (u User) hello(man string){
    var sb strings.Builder
    sb.WriteString("Hi ")
    sb.WriteString(man)
    sb.WriteString(",my name is ")
    sb.WriteString(u.name)
    fmt.Println(sb.String())
}

//函数里不需要访问User的成员,可以传匿名_,甚至_也不用传
func (User) think(man string){
    fmt.Println("Hi " + man + ",do you know my name?")
}

//调用User成员函数
u.hello("Aoliao")
u.think("Aoliao")

为任意类型添加方法

go
type UserMap map[int]User  //自定义类型

//可以给自定义类型添加任意方法
func (um UserMap) getUser(id int) User{
    return um[id]
}
  • "func" 关键字用于声明函数。
  • 在函数名称前,括号内的 "um UserMap" 部分定义了这个方法的接收器。
  • "UserMap" 是这个方法附加到的类型(例如,某个结构体的类型)。
  • "getUser" 是方法的名称。
  • "id" 和 "User" 分别是方法的参数列表和返回类型,它们可以根据需要省略。

匿名结构体

在 Go 语言中,匿名结构体是一种没有显式声明名称的结构体。它们通常用于创建一次性使用的简单数据结构,避免了定义全局结构体类型的需要。匿名结构体在处理临时数据结构或实现简单的封装时非常有用。

go
//声明stu是一个结构体,但这个结构体是匿名的
var stu struct {
    Name string
    Addr string
}

//匿名结构体通常用于只使用一次的情况
stu.Name = "Aoliao"
stu.Addr = "USA"

结构体中含有匿名成员

go
type Student struct {
    id int
    string  //匿名字段
    float32 //直接使用数据类型作为字段名,所以匿名字段中不能出现重复的数据类型
}

var stu = Student{id:1,string:"Aoliao",float32:88}
fmt.Printf("anonymous member string member=%s float member=%f\n",stu.string,stu.float32)

结构体指针

go
//创建结构体指针

var u User
//通过取址符(&)得到指针
user := &u

//直接创建结构体指针
user = &User{
    id:3,name:"Aoliao",addr:"America"
}

//通过new()函数实体化一个结构体,并返回其指针
user = new(User)

构造函数

go
//构造函数,返回指针是为了避免值拷贝
func NewUser(id int,name string) *User{
    return &User{id,name}
}

函数接收指针

go
//User传的是值,即传的是整个结构体的拷贝,在函数里修改结构体不会影响原来的结构体(深拷贝)
func hello(u User,man string){
    u.name = "Tom"
    fmt.Println("Hi " + man ",my name is " + u.name)
}

//传的是User指针,在函数里修改User的成员会影响原来的结构体(浅拷贝)
func hello2(u *User,man string){
    u.name = "Tom"
    fmt.Println("Hi " + man ",my name is " + u.name)
}

结构体嵌套

go
type User struct{
    name string
    sex byte
}

type Paper struct{
    name string
    auther User //结构体嵌套
}

//实例化Paper并赋值
p := new(Paper)
p.name = "论文标题"
p.auther.name = "作者姓名"
p.auther.sex = 0

type Vedio struct{
    length int
    name string
    User    //匿名字段,可用数据类型当字段名
}

//实例化Vedio并赋值
v := new(Vedio)
v.length = 13
v.name = "视频名称"
v.user.sex = 0  //通过字段名逐级访问
v.sex = 0   //对于匿名字段也可以跳过中间字段名,直接访问内部得字段名
v.user.name = "作者姓名"    //字段名冲突,由于内部、外部结构体都有name这个字段,名字冲突了,所以需要指定中间字段名

深拷贝和浅拷贝

  • 深拷贝,拷贝得是值
  • 浅拷贝,拷贝得是指针
  • 深拷贝开辟了新的内存空间,修改操作不影响原先得内存
  • 浅拷贝指向得还是原来得内存空间,修改操作直接作用在原内存空间上
go
type Student struct{
    name string
    string
    year int
}

type Vedio struct {
    length int
    author Student
}

func main(){
    s := Student{Name:"Aoliao",string:"America",int:18}
    v := Vedio{25,s}    //s是值拷贝(深拷贝)
    v.Author.name = "789"
    fmt.Println(s.name) //Aoliao,深拷贝不影响原值
}

if 语句

go
if 5 > 9 {
    fmt.Println("5>9")
}
  • 如果逻辑表达式成立,就会执行{}里得内容
  • 逻辑表达式不需要加()
  • "{"必须紧跟在逻辑表达式后面,不能另起一行
go
//初始化多个局部变量,复杂得逻辑表达式
if c,d,e := 5,9,2;c < d && (c > e || c >3){
    fmt.Println("fit")
}
  • 逻辑表达中可以含有变量或常量
  • if 句子中允许包含 1 个(仅 1 个)分号,在分号前初始化一些局部变量(即只在 if 块内可见)

if-else

go
color := "black"
if color == "red"{  //if只能有一个
    fmt.Println("stop")
}else if color == "green"{
    fmt.Println("go")
}else if color == "yellow" {    //else if可以有0个、1个或者连续多个
    fmt.Println("stop")
} else {    //else有0个或1个
    fmt.Printf("invalid traffic signal:%s\n",strings.ToUpper(color))
}

if 表达式嵌套

go
//太深得嵌套不利于代码得维护
if true{
    if true{
        if true{
            if true{

            }
        }
    }
}

switch 语句

go
color := "black"
switch color {
case "green": //相当于 if color=="green"
    fmt.Println("go")

case "red":
    fmt.Println("red") //相当于else if color=="red"
default: //相当于else
    fmt.Printf("invalid traffic signal:%s\n", strings.ToUpper(color))
}
  • switch-case-default 可能模拟 if-else,但只能实现相等判断
  • switch 和 case 后面可以跟常量、变量、或函数表达式,只要它们表示得数据类型相同就行
  • case 后面可以跟多个 case,只要有一个值满足就行

空 switch

go
switch {
case add(5) > 10:
    fmt.Println("right")
default:
    fmt.Println("wrong")
}
  • switch 后带表达式时,switch-case 只能模拟相等的情况;如果 switch 后不带表达式,case 后就可以跟任意的条件表达式

switch type

go
var num interface{} = "33"
//输出结果:number is interfaca 33
switch value := num.(type){ //相当于在每个case内部声明了一个变量value
case int:   //value已被转换为int类型
    fmt.Printf("number is int %d/n",value)
case float64:   //value已被转换为float64类型
    fmt.Printf("number is float64 %f/n",value)
case byte,string:   //如果case后有多个类型,则value还是interfase{}类型
    fmt.Printf("number is interfaca %v/n",value)
default:
    fmt.Println("number是其它类型")
}

fallthrough

  • 从上往下,只要找到成立的 case,就不再执行后面的 case 了,所以为了提高性能,把大概率会满足的情况往前放。
  • case 里如果带了 fallthrough,则执行完本 case 还会去判断下一个 case 是否满足。
  • 在 switch type 语句的 case 字句中不能使用 fallthrough。
go
func fall(age int){
    switch {
	case age > 50:
		fmt.Println("老人家")
		fallthrough
	case age > 35:
		fmt.Println("失业,待富人群")
	case age > 22:
		fmt.Println("牛马")
		fallthrough
	case age > 18:
		fmt.Println("大学生")
		fallthrough
	case age > 16:
		fmt.Println("高中生")
	}
}

func main(){
    fall(60)    //老人家 失业,待富人群
    fall(22)    //大学生 高中生
}

for 循环

go
arr := []int{1,2,3,4,5}
for i := 0;i < len(arr);i++ {   //正序遍历该arr切片
    fmt.Printf("%d:%d\n",i,arr[i])  //循环体
}
  • for 初始化局部变量(只在循环开始时执行一次);条件表达式(在每次迭代前检查);后续操作(在每次迭代后执行)
  • 局部变量指仅在 for 块内可见
  • 初始化变量可以放在 for 上面
  • 后续操作可以放在 for 块内部
  • 只有条件判断时,前后的分号可以不要
  • for{}是一个无限循环

for range

go
for 索引, 值 := range 集合 {
    // 循环体
}
  • 遍历数组或切片
    • for i,ele := range arr
  • 遍历 string
    • for i,ele := range "我会唱国际歌" //ele 是 rune 类型
  • 遍历 map,go 不保证遍历的顺序
    • for key,value := range m
  • 遍历 channel,遍历前一定要先 close channel
    • for ele := range ch
  • for range 拿到的是数据的拷贝

for 嵌套

go
const SIZE = 4
A := [SIze][SIZE]float64{}
//两层for循环嵌套
for i := 0;i<SIZE;i++{
    for j := 0;j < SIZE;j++{
        A[i][j] = rand.Float64()    //[0,1]上的随机数
    }
}

break 与 continue

  • break 与 continue 用于控制 for 循环的代码流程,并且只针对最靠近自己的外层 for 循环
  • break:退出 for 循环,且本轮 break 下面的代码不再执行
  • continue:本轮 continue 下面的代码不再执行,进入 for 循环的下一轮

goto 与 Label

在 Go 语言中,goto 语句可以用于在函数内部进行无条件的跳转。它可以跳转到函数内部的标签(Label)处执行代码。这种机制可以用于从深层嵌套的结构中快速跳出,或者跳过某些执行片段。使用 goto 语句时需要遵循以下规则:

  • **定义标签(Label):**标签是一个简单的标识符,后面跟着一个冒号(:)。标签可以放在函数中的任何地方,但它必须位于同一个函数内部。例如,Label1:。
  • **使用 goto 跳转:**通过 goto 语句跳转到指定的标签。goto 后面跟着标签的名称。例如,goto Label1。
  • **作用域和限制:**goto 只能在同一个函数内跳转。不能跨越函数边界,也不能用于跳入任何类型的循环或条件结构之内。
  • **谨慎使用:**虽然 goto 语句在某些情况下很有用,但它的使用通常应该被限制。过度使用 goto 可能会导致代码难以理解和维护,特别是在创建复杂的控制流程时。
go
//在这个例子中,程序会直接跳转到 LABEL1,因此 "This will be skipped" 不会被打印。
package main

import "fmt"

func main() {
    var a int = 10

    // 使用 goto 跳转到标签
    if a == 10 {
        goto LABEL1
    }

    fmt.Println("This will be skipped")

LABEL1:
    fmt.Println("Jumped to LABEL1")
}

使用 goto 与 Label 退出 for 循环

goto 与 Label 结合可以实现 break 的功能,甚至比 break 更强大

go
const SIZE=5
for i := 0;i<SIZE;i++ {
L2:
    for j := 0;j<SIZE;j++ {
        goto L1
    }
}
L1:
    代码段

break、continue 与 Label

  • break、continue 与 Label 结合使用可以跳转到更外层的 for 循环
  • continue 和 break 针对的 Label 必须写在 for 前面,而 goto 可以针对任意位置的 Label