爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      Go 语言初探:从基础到实战

      首页 知识中心 软件开发 文章详情页

      Go 语言初探:从基础到实战

      2024-10-29 09:04:40 阅读次数:36

      Go,函数,接口

      1.Go概述

      程序是一段计算机指令的有序组合。程序=算法+数据结构。任何程序都可以将模块通过三种基本的控制结构(顺序、分支、循环)进行组合来实现。

      Go(也称为Golang)是一种由Google开发的开源编程语言。设计目标是使编程更简单、高效和可靠。Go旨在提供高性能、简洁且易于理解的语法。它结合了传统编译型语言的速度和性能,以及动态类型语言的易用性和便捷性。
      Go语言特性:

      1.静态编译

      2.少即是多,语法简洁简单,可读性强

      3.原生支持并发编程

      4.Duck模型的非侵入式接口

      5.强调组合,组合优于继承

      6.支持多种操作系统和体系结构的交叉编译,这里的 targetOS 是目标操作系统(如 windows、linux、darwin 等),targetArchitecture 是目标体系结构(如 amd64、arm、386 等)。

      GOOS=targetOS GOARCH=targetArchitecture go build
      GOOS=windows GOARCH=amd64 go build
      

      7.大量使用接口和内置函数来提高代码的复用度

      8.支持和C 语言相互调用的机制(CGO),Go 中使用 C 包来调用 C 函数,以及使用特殊的类型来处理在 Go 和 C 之间传递的数据。以下是一个简单的示例,展示了如何在 Go 中调用 C 函数:

      #include <stdio.h>
      
      void helloFromC() {
          printf("Hello from C!\n");
      }
      
      package main
      
      /*
      #cgo CFLAGS: -g -Wall
      void helloFromC();
      */
      import "C"
      
      func main() {
          C.helloFromC()
      }
      

      9.精确依赖,并通过增量编译、并行编译以及缓存编译结果来缩短编译时间

      Go语言基本命令:
      Go 语言初探:从基础到实战

      2.Go基本语法

      Go语言中共有25个关键字,是静态强类型语言。

      强类型:编译器会确认每个变量应有的类型,错误使用将引发错误;

      静态:仅支持编译时自动推断类型

      Go 语言初探:从基础到实战

      Go语言中的条件语句,if判断后面不需要(),同时if可带一个初始化子语句用;跟条件分开。同时Go也不支持三元运算符

      /*
      if SimpleStmt;Expression {
      	statement
      	......
      }
      */
      if i:=10;i>8 {
          //条件语句
      }
      

      switch语句说明:

      注意:math.Floor(num)函数用于返回小于num的最小整数

      同时,switch是惰性求值,只有在需要求值时才去计算表达式,从而降低消耗,提升性能。
      Go 语言初探:从基础到实战

      for循环:

      1.GO 的循环语句只有for ,没有while/do while
      2.for 语句后面同样不用加( )
      3.for语句的三个部分,省略任何一个时,分号不能省略
      4.只留条件判断时,可以不用分号 (相当于while语句)
      5.全部省略,变为无限循环

      //while:
      for experssion {
          
      }
      //无限循环
      for {
          if state {
              break
          }
      }
      

      goto 可以跟标签更紧密合作,可以代替break 跳出多重循环

      手动实现排序

      手写实现的冒泡排序,Go代码如下所示:

      这里需要注意,切片作为参数传递是引用传递!

      func bubbleSort(nums []int){
          n:=len(nums)
          // 这里i是定义排序好的数量
          for i:=0;i<n-1;i++ {
              // 每次排序都是从第一个元素开始冒泡
              for j:=0;j<n-1-i;j++ {
                  if nums[j]>nums[j+1] {
                      nums[j+1],nums[j]=nums[j],nums[j+1]
                  }
              }
          }
      }
      

      手写实现插入排序,Go代码如下所示:

      func insertSort(nums []int){
          n:=len(nums)
          // 从无序组第二个元素开始依次插入有序组中
          for i:=1;i<n;i++{
              key:=nums[i]
              j:=i-1
              for j>=0 && key<nums[j]{
                  nums[j+1]=nums[j]
                  j--
              }
              nums[j+1]=key
          }
      }
      

      手动实现快速排序,具体Go代码如下

      func quickSort(nums []int){
          n:=len(nums)
          if n<2 {
              return 
          }
          // 定义基准线
          pivot:=nums[0]
          low,high:=0,n-1
          for low<=high {
              if nums[low]<=pivot{
                  low++
              }else{
                  nums[low],nums[high]=nums[high],nums[low]
                  high--
              }
          }
          // 交换基准元素位置
          nums[0],nums[high]=nums[high],nums[0]
          // 递归排序左右子数组
          quickSort(nums[:high])
          quickSort(nums[high+1:])
      }
      

      3.基础数据类型及运算

      Go语言中基础数据类型有:整型、浮点型、复数、布尔型、字符型、字符串型以及错误类型

      可以用 reflect.TypeOf 函数来查看类型名称

      1.基础类型

      1.整型

      整型按照是否有符号可以分为:有符号位和无符号位;

      整型按照位数可以分为:int int8 int 16 int32 int32 int64

      这里需要注意:不同的整型之间是不能直接比较,不能直接运算

      2.浮点数

      浮点数主要包括float32和float64

      标准库math包中的函数都使用float64
      Go 语言初探:从基础到实战

      3.复数

      复数由两个浮点数表示,一个实部、一个虚部

      有两种复数类型,即complex64(两个float32组成)和complex128(两个float64组成)

      有三个内置复数处理函数

      complex(float,float) 创建复数

      real() 获取实部

      image() 获取虚部

      package main
      
      import (
      	"fmt"
      )
      
      func main() {
      	// 创建复数
      	var comp1 complex64 = complex(2, 3)     // 实部为2,虚部为3
      	comp2 := complex(4.5, 7.1)             // 使用默认类型complex128
      
      	// 输出复数
      	fmt.Println("Complex 1:", comp1)
      	fmt.Println("Complex 2:", comp2)
      
      	// 访问实部和虚部
      	fmt.Println("Real part of Complex 1:", real(comp1))     // 输出实部
      	fmt.Println("Imaginary part of Complex 1:", imag(comp1)) // 输出虚部
      }
      
      4.布尔类型

      布尔值主要包括true和false,类型长度为1byte

      布尔类型无法被其他类型赋值,也不支持类型转换

      这里布尔类型不支持用0和1表示真假

      if 和for 语句的条件部分必须是布尔类型的值或表达式

      2.运算符

      运算符主要包括算术运算符、关系运算符、逻辑运算符、赋值运算符以及位运算符

      1.算术运算符

      算术运算符主要包括加减乘除、取模、自增、自减

      **注意:**Go语言自增只支持变量++,不支持++变量,自减也是

      2.关系运算符

      关系运算符主要包括

      ==
      !=
      >
      <
      >=
      <=
      

      注意:由于布尔类型不支持转换整型,因此不等式连写,例如x<y<z这种语法是错误的!

      3.逻辑运算符

      逻辑运算符主要包括取反!、且&&、或||

      4.赋值运算符

      Go 语言初探:从基础到实战

      5.位运算符

      位运算符包括

      << 左移 相当于乘以2
      >> 右移 相当于除以2
      &  位与
      |  位或
      ^ 异或
      

      位运算只对整数其作用,是底层运算,效率较高!
      Go 语言初探:从基础到实战

      4.集合数据类型

      Go语言中主要有三种类型的集合,分别是数组Array、切片Slice以及Map

      1.数组

      数组是同类元素的集合,数组变量声明后,其元素类型、数组长度均不可变

      数组声明:

      // 只声明未赋值H
      var arr1 [5]int
      // 直接赋值
      arr2:=[3]int{1,2,3}
      // 数组长度由初始化数量确定
      arr3:=[...]int{1,2,3} //...不可省略
      // 对含有下标的元素赋初值 其余元素保持零值
      arr4:=[4]{0:99,3:100}
      

      数组复制:

      数组变量之间进行复制会拷贝整个数组(值拷贝)

      	a := [...]string{"USA", "China", "India", "Germany"}
          b := a 
          b[0] = "Singapore"
          fmt.Println("a is ", a)
          fmt.Println("b is ", b)   
          //a is [USA China India Germany]  
          //b is [Singapore China India Germany]
      

      数组传参:

      与数组复制类型,只是实参拷贝一份给形参,函数调用结束则销毁,二者相互独立,传递大数组时效率较低!

      func changeLocal(num [5]int) {
          num[0] = 55
          fmt.Println("inside function ", num)
      }
      
      func main() {
          num := [...]int{5, 6, 7, 8, 8}
          fmt.Println("before passing to function ", num)
          changeLocal(num) //num is passed by value
          fmt.Println("after passing to function ", num)
      }
      //before passing to function  [5 6 7 8 8]  
      //inside function  [55 6 7 8 8]  
      //after passing to function  [5 6 7 8 8] 
      

      数组遍历:

      数组遍历可以采用for循环遍历或range遍历

      // for循环
       	a := [...]float64{67.7, 89.8, 21, 78}
          for i := 0; i < len(a); i++ { 
              fmt.Printf("%d th element of a is %.2f\n", i, a[i])
          }
      // range遍历
      	a := [...]float64{67.7, 89.8, 21, 78}
          for i, v := range a { // 第一个参数为序号,第二个为变量
              fmt.Printf("%d the element of a is %.2f\n", i, v)
          }
          //0 the element of a is 67.70  
          //1 the element of a is 89.80  
          //2 the element of a is 21.00  
          //3 the element of a is 78.00
      
      

      此外,range遍历的两个参数,如果只用一个会报错,可以用_占位符来表示只用一个参数

      多维数组:

      	a := [3][2]string{
              {"lion", "tiger"},
              {"cat", "dog"},
              {"pigeon", "peacock"}, //此处,不可忽略,否则报错
          }
          for _, v1 := range a {
              for _, v2 := range v1 {
                  fmt.Printf("%s ", v2)
              }
              fmt.Printf("\n")
          }
      

      2.切片

      由于数组的定长性和值拷贝限制其使用,因此提供切片使用,即提供长度可变的数组引用

      切片声明时不能给定底层数组大小,否则变成了数组声明,同时可以使用内置函数make来声明和初始化!

      切片是引用类型,不支持==运算

      创建切片:

      这里需要注意,如果创建切片时指定底层数组,一旦切片改变,底层数组元素也会发生改变,因为切片是对原数组的引用。因此,多个切片可以共享同一个底层数组。

      // 指定底层数组创建
       	a := [5]int{76, 77, 78, 79, 80}//底层数组
          s1 := a[0:4] // from a[0] to a[3]
          s2 := a[:4]  // from a[0] to a[3]
          s3 := a[2:5] // from a[2] to a[4]
          s4 := a[2:]  // from a[2] to a[4]
          fmt.Printf("%v\n%v\n%v\n%v", s1, s2, s3, s4)
          //[76 77 78 79]
          //[76 77 78 79]
          //[78 79 80]
          //[78 79 80]
      // 同时创建数组和切片
      //指定数组大小,只创建数组    
      c := [3]int{6, 7, 8}
      //不指定数组大小,返回切片引用,底层数组匿名 
      d := []int{6, 7, 8}  
      //用...推断数组大小,只创建数组 
      e := [...]int{6, 7, 8} 
      

      内置函数 len() 返回切片当前长度
      内置函数cap()返回切片底层数组容量

      切片动态增加:

      内置函数 append() 动态扩展切片,在底层数组容量范围内,会直接覆盖底层数组元素

      package main
      
      import "fmt"
      
      func main() {
          arr := [7]int{9, 8, 7, 6, 5, 4, 3}
          sli := arr[1:3]
          sli = append(sli, 20) // 增加一个20,切片容量扩展一倍
          fmt.Printf("%v\n", arr) //[9 8 7 20 5 4 3]
          fmt.Printf("%v\n", sli) //[8 7 20]
      }
      

      切片动态增加时,当超过底层数组容量大小时,会重新创建底层数组,并转移数据
      切片增长在元素小于1000时,成倍增长,超过1000,增长速率大概为1.25

      	cars := []string{"Ferrari", "Honda", "Ford"}
          fmt.Println("cars:", cars, "length", len(cars), "capacity", cap(cars))  
          //cars: [Ferrari Honda Ford] length 3 capacity 3
          fmt.Printf("%x\n", &cars[0])
          //c000080330
          cars = append(cars, "Toyota")
          fmt.Println("cars:", cars, "length", len(cars), "capacity", cap(cars))
          //cars: [Ferrari Honda Ford Toyota] length 4  capacity 6 //why 6
          fmt.Printf("%x\n", &cars[0])    
          //c0000a4000   //Why
      

      切片合并:

      内置函数 append() 还支持切片的合并,用…运算符把对应切片所有元素都取出

      	veggies := []string{"potatoes", "tomatoes", "brinjal"}
          fruits := []string{"oranges", "apples"}
          food := append(veggies, fruits...) //... 不可忽略
          fmt.Println("food:", food)
          //food: [potatoes tomatoes brinjal oranges apples]
      
      

      切片传参:

      在函数传参时,复制的是结构体拷贝,实现引用传递

      func subtactOne(numbers []int) {
          for i := range numbers {
              numbers[i] -= 2
          }
      }
      func main() {
          nos := []int{8, 7, 6}
          fmt.Println("slice before function call", nos)
          //slice before function call [8 7 6]
          subtactOne(nos)
          fmt.Println("slice after function call", nos)
          //slice after function call [6 5 4]
      }
      

      多维切片:

      多维切片比多维数组灵活,每行元素个数不必相同

       	pls := [][]string{
              {"C", "C++", "C#"},
              {"JavaScript"},
              {"Go", "Rust"},
          }
          for _, v1 := range pls {
              for _, v2 := range v1 {
                  fmt.Printf("%s ", v2)
              }
              fmt.Printf("\n")
          }
          //C C++ C# 
          //JavaScript 
          //Go Rust 
      
      

      3.Map

      Map用于存储一系列无序的键值对,是引用类型,不支持==运算(nil除外)

      Map不是线程安全的,不支持并发写

      1.初始化

      Map零值不可用,只声明不初始化为nil值,不分配底层存储空间,不能添加元素
      用字面量或make函数进行初始化后可以添加元素

      	var m1 map[string]int
          fmt.Println(m1 == nil)
          //true
          //m1["a"] = 1 //error
      
          m2 := map[string]int{}
          fmt.Println(m2 == nil)
          //false
          m2["a"] = 1 //ok
      
          m3 := make(map[string]int)
          fmt.Println(m3 == nil)
          //false
          m3["a"] = 1 //ok
      
      2.赋值

      可在Map初始化后进行元素赋值,也可在Map初始化时直接元素赋值

      	personSalary := make(map[string]int)
          personSalary["steve"] = 12000
          personSalary["jamie"] = 15000
          personSalary["mike"] = 9000
          //初始化时,直接赋值
          personSalary := map[string]int{
              "steve": 12000,
              "jamie": 15000,
          }
      
      
      3.元素查找

      Map元素通过下标访问其实可以返回两个值(底层实际为函数,Comma-ok 法)
      1.对应的value
      2.对应的key是否存在的布尔值

      	personSalary := map[string]int{
              "steve": 12000,
              "jamie": 15000,
          }
          value, ok := personSalary["joe"]
          if  ok  == true {
              fmt.Println("Salary of joe is", value)
          } else {
              fmt.Println("joe not found")
          }
      
      4.Map元素遍历

      Map元素可以使用range 遍历,但不保证顺序

      	personSalary := map[string]int{
              "steve": 12000,
              "jamie": 15000,
          }
          personSalary["mike"] = 9000
          for key, value := range personSalary {
              fmt.Printf("personSalary[%s] = %d\n", key, value)
          }
          //personSalary[mike] = 9000
          //personSalary[steve] = 12000
          // personSalary[jamie] = 15000
      
      5.Map元素删除

      使用内置函数delete()删除Map元素
      1.key存在,对应元素被删除
      2.key不存在,什么都不发生

      	personSalary := map[string]int{
              "steve": 12000,
              "jamie": 15000,
          }
          personSalary["mike"] = 9000
          fmt.Println("map before deletion", personSalary)
          delete(personSalary, "steve")
          fmt.Println("map after deletion", personSalary)
          //map before deletion map[jamie:15000 mike:9000 steve:12000]
          //map after deletion map[jamie:15000 mike:9000]
      

      5.Go函数

      函数效率高则程序效率高,建议多用标准库函数

      1.函数定义

      //语法格式
      func funcName(paramList)(resultList) {
      	coding ……
      }
      //paramList = input1 type1,input2 type2 ……
      //resultList = output1 type1,output2 type2 ……
      //多个相邻相同类型参数可以使用简写
      func add(a, b int) int {
          return a + b
      }
      

      Go函数中可以有多个返回值,同时返回值可以有变量名,并在函数体内可见。

      **注意:**不支持函数重载,因为重载只是偶尔有用,但在实践中会引起无解和导致脆弱性

      2.参数(不定参数)

      不定参数,形参数目可变、不确定
      不定参数声明语法格式: param … type

      不定参数的形参在函数内是切片

      func sum(nums ...int) int {
          total := 0
          for _, num := range nums {
              total += num
          }
          return total
      }
      

      上述不定参数是不定数量,但类型是相同的,如果要实现不定数量,同时不定类型,则需要通过接口类型interface{}作为参数实现

      func printAll(vals ...interface{}) {
          for _, val := range vals {
              fmt.Println(val)
          }
      }
      

      3.匿名函数

      匿名函数相当于函数字面量,可以使用函数的地方就可以使用匿名函数

      //匿名函数直接调用
      func(a,b int )int{
          return a-b
      }(5,4)
      //匿名函数赋值给函数变量
      var sum = func(a,b int )int{
          return a+b
      }
      //函数作为返回值
      func getFun(op string) func(a,b int )int {
          return func(a,b int )int{
              return a+b
          }
      }
      

      4.闭包

      闭包=函数+引用环境,常见于在函数内部定义匿名函数,并且该匿名函数访问定义它的外部函数的作用域

      package main
      import "fmt"
      func main() {
          // 外部函数外的变量
          outsideVar := 10
          // 内部函数,形成闭包
          closureFunc := func() {
              fmt.Println(outsideVar) // 闭包函数内部访问外部变量
          }
          closureFunc() // 调用闭包函数
      }
      

      函数柯里化:

      函数柯里化就是把接收多个参数的函数变换成接收单一参数的函数

      函数柯里化是一种将多参数函数转换为一系列单参数函数的过程。这种转换的结果是,原始函数可以通过一系列较少参数的函数来调用。

      5.延迟调用(defer)

      Go函数支持defer进行延迟调用

      defer类似OOP语言异常处理中的final子句,常用来保证系统的资源的回收和释放。

      	defer Println("last")
          Println("main body")
          Println("first")
          //main body
          //first
          //last
      

      使用defer函数时,会把当时的实参值传递给形参,即使后序实参发生变化也不影响函数结果!

      	a := 5
          defer fmt.Println(“defer 注册函数时的a值", a)
          a = 10
          fmt.Println(“普通函数的a值", a)
          //普通函数的a值 10
          //defer 注册函数时的a值 5
      

      此外,使用多个defer时,这些defer调用以先进后出(FILO)顺序在函数返回前被执行!

          name := "Naveen"
          fmt.Printf("Original String: %s\n", string(name))
          fmt.Printf("Reversed String: ")
          for _, v := range []rune(name) {
              defer fmt.Printf("%c", v)
          }
          //Original String: Naveen
          //Reversed String: neevaN
      

      6.递归函数

      形式上:一个正在执行的函数调用了自身(直接递归).

      递归不能无限制调用,因为栈空间有限
      递归中必须有完成终极任务的语句
      递归调用参数逐渐逼近结束条件
      递归的目的是简化设计使程序易读,但通常效率较低

      6.结构体和方法

      1.结构体

      1.结构体定义

      结构体把有内在联系的不同类型的数据统一成一个整体,使它们相互关联

      结构体是变量的集合,从外部看是一个实体

      type Employee struct{
          firstName string
          lastName  string
          age       int
          salary    int
      }
      
      2.带标签的结构体

      结构体中的字段除了名字和类型外,还可以有一个可选的标签(tag)

      标签是一个附属于字段的字符串,用于描述字段信息
      标签还可以按key1:“value1” key2:“value2”键值对进行修饰,来提供编码、解码、ORM等转化辅助

      可以使用反射,获取结构体标签中的每一个键值对

      package main
      
      import (
      	"fmt"
      	"reflect"
      )
      
      type Person struct {
      	Name string `json:"name" validate:"required"`
      	Age  int    `json:"age" validate:"min=18"`
      }
      
      func main() {
      	p := Person{Name: "Alice", Age: 25}
      	// 获取结构体字段的标签信息
      	t := reflect.TypeOf(p)
      	for i := 0; i < t.NumField(); i++ {
      		field := t.Field(i)
      		fmt.Printf("Field: %s, Tag: %s\n", field.Name, field.Tag)
      	}
      }
      
      3.结构变量初始化

      1.可以使用字段名初始化,这样不需要按顺序,未指定的字段为零值

          emp1 := Employee{
              firstName: "Sam",
              age:       25,
              salary:    500,
              lastName:  “Anderson”, //逗号不能忽略
          }
      

      2.用字面量初始化,按字段类型声明顺序并全部设置,顺序不对或遗漏字段报错

      emp2 := Employee{"Thomas", "Paul", 29, 800}
      
      4.访问修改字段值

      1.采用 结构变量.字段

      emp := Employee{"Thomas", "Paul", 29, 800}
      fmt.Println(emp.age)
      

      2.采用(*结构变量指针).字段

      emp := &Employee{"Sam", "Anderson", 55, 6000}
      fmt.Println("First Name:", (*emp).firstName)
      

      3.采用 结构变量指针.字段,不支持->

      emp := &Employee{"Sam", "Anderson", 55, 6000}
      fmt.Println("First Name:", emp.firstName)
      
      4.匿名字段

      结构体字段也可以省略字段名,字段名默认为对应数据类型名称(数据类型不能重复)

      	type Person struct {
              string
              int
          }
          p := Person{"Naveen", 50}
          p.int =60
      

      2.方法

      方法是对具体类型行为的封装,本质上是绑定到该类型的函数。

      OO语言的方法通常有个隐藏的this或self指针来指向对象,Go把这个隐藏指针暴露出来,称为接受者。

      func (t Type) funcName(paramList)(resultList)
      func (t *Type) funcName(paramList)(resultList)
      
      1.方法实例
      type Employee struct {
          name     string
          salary   int
          currency string
      }
      //定义方法
      func (e Employee) displaySalary() {
          fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
      }
      func main() {
          emp1 := Employee{
              name:     "Sam Adolf",
              salary:   5000,
              currency: "$",
          }
          emp1.displaySalary() 
      }
      

      其实方法可以使用等价的函数实现,具体如下所示:

      type Employee struct {  
          name     string
          salary   int
          currency string
      }
      func displaySalary(e Employee) {  
          fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
      }
      func main() {  
          emp1 := Employee{
              name:     "Sam Adolf",
              salary:   5000,
              currency: "$",
          }
          displaySalary(emp1)
      }
      

      既然函数可以做,那为什么还需要方法呢?

      在 Go 语言中,方法和等价的函数都能完成类似的工作。虽然它们可以完成相同的任务,但方法和函数之间存在一些差异和适用场景,其中方法更适合于特定类型的操作和面向对象的编程。

      GO 的函数不能重载,导致不同类型不能用同名函数,而不同类型的方法可以同名
      GO 不支持class ,使用结构代替类,结构字段用来封装对象属性,方法用来封装对象的行为

      此外,方法并非结构体专有,所有自定义类型都可以定义方法

      type myInt int //自定义类型
      
      func (a *myInt) add(b myInt) myInt {
          return *a + b
      }
      
      num1 := myInt(5)
      num2 := myInt(10)
      sum := num1.add(num2)
      

      7.接口

      Go语言中的接口是Duck模型的非侵入式接口,与传统的接口不同,非侵入式接口其具体类型实现接口不需要显式声明,只要其方法集是接口的超集,编译时会进行对应校验!

      GO 接口只有方法签名,没有数据字段,没有函数体代码

      类型的方法集是多个接口的超集,则实现多个接口

      1.接口类定义

      // 命令接口类型
      type interfaceName interface{//接口类型命名通常以er为后缀
          methodName(paramList)(resultList)
          otherInterfaceName
      }
      // 匿名接口类型
      interface{
          methodName(paramList)(resultList)
          otherInterfaceName
      }
      

      而如果匿名接口中方法集为空,即是interface{}是一种空接口,所有的类型都实现了空接口,都可以赋值或传递给空接口。

      2.接口初始化

      只声明未赋值的接口变量为nil
      接口变量初始化需要把接口绑定到具体类型实例
      未初始化的接口变量不能调用其方法
      方法的接收者才能给接口变量赋值
      接口变量的值包括底层类型的值和具体类型

      package main
      
      import (
      	"fmt"
      )
      
      // 接口定义
      type Speaker interface {
      	Speak() string
      }
      
      // 实现接口的结构体
      type Dog struct{}
      
      // Dog 结构体实现 Speak 方法
      func (d Dog) Speak() string {
      	return "Woof!"
      }
      
      // 创建接口的实例
      func NewSpeaker() Speaker {
      	return Dog{} // 返回一个 Dog 类型,它满足了 Speaker 接口
      }
      
      func main() {
      	// 初始化接口并调用方法
      	speaker := NewSpeaker()
      	fmt.Println(speaker.Speak())
      }
      

      此外,一个接口可以包含一个或者多个接口,即嵌套接口

      type ReadWrite interface {
          Read(b Buffer) bool
          Write(b Buffer) bool
      }
      type File interface{
          ReadWrite
          close() bool
      }
      

      3.接口类型断言

      接口类型断言用来判断实现某个接口的变量是否为某个类型

      若是,则返回该类型的值和true

      不是,则返回该类型的零值和false

      // interfaceName.(typeName)
          var a interface{} = 56
          v, ok := a.(int)
          fmt.Println(v, ok)
          //56 true
          var b interface{} = true
          v, ok = b.(int)
          fmt.Println(v, ok)
          //0 false
      

      4.接口类型查询

      接口类型查询是使用swtich语句确定接口变量底层类型

      .(type)只能用于switch表达式是因为变量底层类型判断只能用接口类型断言,go 只能判断变量内存格式是否匹配某种类型,并按某种类型来解析值

      func findType(i interface{}) {
          switch i.(type) {//.(type)只能用于switch表达式
          case string:
              fmt.Printf("string and value is %s\n", i.(string))
          case int:
              fmt.Printf("int and value is %d\n", i.(int))
          default:
              fmt.Printf("Unknown type\n")
          }
      }
      findType(77)
      findType(89.98)
      // int and value is 77 
      // Unknown typ
      

      5.Stringer接口

      Stringer 接口是 Go 语言中的一个接口,它只包含一个方法:String(),用于返回该类型的字符串表示形式。这个接口通常被用来自定义类型的字符串输出格式。

      在 Go 语言中,如果某个类型实现了 Stringer 接口,那么你可以使用 fmt 包中的打印方法(如 Println 或 Sprintf)来自定义该类型的输出方式。

      以下是 Stringer 接口的定义:

      type Stringer interface {
          String() string
      }
      

      接口的 String() 方法返回一个字符串。实现了 Stringer 接口的类型可以定义自己的 String() 方法,以便自定义该类型的字符串输出。

      以下是一个简单的示例,演示了如何使用 Stringer 接口:

      package main
      import (
          "fmt"
      )
      type Person struct {
          Name string
          Age  int
      }
      // 实现 Stringer 接口
      func (p Person) String() string {
          return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
      }
      func main() {
          person := Person{"Alice", 30}
          fmt.Println(person) // 输出: Alice is 30 years old
      }
      

      在这个例子中,Person 类型实现了 Stringer 接口,重写了 String() 方法,这样当我们使用 fmt.Println 打印 Person 类型的变量时,会调用 String() 方法,并输出该类型的自定义字符串格式。

      总结,实现了Stringer接口的类型变量,使用fmt.Println方法打印该对象时,可以按照指定一定的格式输出,与Java中重写toString方法类似。

      6.Sorter接口

      标准库的sort包,定义排序要实现三个方法:

      //Len()   反映元素个数的方法
      //Less(i, j)  比较第 i 和 j 个元素
      //Swap(i, j) 交换第 i 和 j 个元素
      // 具体Sorter接口定义如下
      type Sorter interface {
          Len() int
          Less(i, j int) bool
          Swap(i, j int)
      }
      

      请基于Sorter接口实现冒泡排序:

      package main
      
      import (
      	"fmt"
      )
      
      type Sorter interface {
      	Len() int
      	Less(i, j int) bool
      	Swap(i, j int)
      }
      
      type IntArray []int
      
      func (arr IntArray) Len() int {
      	return len(arr)
      }
      
      func (arr IntArray) Less(i, j int) bool {
      	return arr[i] < arr[j]
      }
      
      func (arr IntArray) Swap(i, j int) {
      	arr[i], arr[j] = arr[j], arr[i]
      }
      
      func BubbleSort(data Sorter) {
      	n := data.Len()
      	for i := 0; i < n-1; i++ {
      		for j := 0; j < n-i-1; j++ {
      			if data.Less(j+1, j) {
      				data.Swap(j, j+1)
      			}
      		}
      	}
      }
      
      func main() {
      	array := IntArray{64, 34, 25, 12, 22, 11, 90}
      	fmt.Println("Unsorted array:", array)
      
      	BubbleSort(array)
      	fmt.Println("Sorted array:", array)
      }
      

      7.接口特性

      接口特性简称动静结合

      接口静态特性:

      支持在编译阶段的类型检查:当一个接口类型变量被赋值时,编译器会检查右值的类型是否实现了该接口方法集合中的所有方法。

      接口动态特性:

      即:使用空接口变量可以使用不同类型的变量赋值

      在运行时存储在接口类型变量中的值的真实类型。比如:var i interface{} = 13中接口变量i的动态类型为int。
      在运行时可以被赋值为不同的动态类型变量,从而支持运行时多态。

      8.反射

      变量的最基本信息是类型和值,反射可以在程序运行时检查变量的类型和值

      通过反射可以获取结构体变量的各字段信息,甚至包括结构字段的tag信息

      package main
      import (
      	"fmt"
      	"reflect"
      )
      type Person struct {
      	Id   int //首字母大写表示公开字段
      	Name string
      	Sex  string
      }
      func (this Person) Call() {
      	fmt.Println("我正在打电话")
      }
      func getTypeAndValue(object interface{}) {
      	//动态获取对象object的类型信息
      	objectType := reflect.TypeOf(object)
      	objectValue := reflect.ValueOf(object)
      	fmt.Println("type =", objectType.Name())
      	fmt.Println("type =", objectType, "value =", objectValue)
      	// objectType.NumField() 获取字段的总数
      	for i := 0; i < objectType.NumField(); i++ {
      		field := objectType.Field(i)
      		value := objectValue.Field(i)
      		fmt.Printf("type %d = %v\n", i, field.Type)
      		fmt.Printf("name %d = %v\n", i, field.Name)
      		fmt.Printf("value %d = %v\n", i, value.Interface())
      	}
      	for i := 0; i < objectValue.NumMethod(); i++ {
      		method := objectValue.Method(i)
      		method.Call(nil)
      	}
      }
      func main() {
      	person := Person{1, "nancy", "mail"}
      	getTypeAndValue(person)
      }
      

      8.错误处理

      Go语言里没有异常机制,只有错误处理,错误通过函数的多返回值来处理

      Go语言的错误主要有:编译时错误、运行时错误以及逻辑错误

      Go语言的错误处理方式

      1.可处理,通过函数返回错误进行处理

      2.不可处理,通过panic抛出错误,退出程序

      1.error接口

      通过error 接口 实现错误处理的标准模式,打印错误时自动调用Error()函数

      type error interface{
          Error() string
      }
      

      可能出错的函数最后一个返回值为错误类型,检查该返回值是否为nil,是则处理错误,否则正常调用

       	f, err := os.Open("/test.txt")
          if err != nil {
              fmt.Println(err)
              return
          }
          fmt.Println(f.Name(), "opened successfully")
      

      2.if快乐路径原则

      “快乐路径”原则是编程中的一种设计理念,其指导思想是使函数的主要路径尽可能保持“快乐”,也就是函数的主要工作或逻辑能够尽快完成,而不被意外情况干扰。

      一个例子就是通过合理的错误检查和返回,将错误处理逻辑放在函数的开头,而将主要逻辑和处理放在函数的主要部分。这样可以尽早退出函数并返回错误,但同时保持主要逻辑在函数主体内部,让主逻辑尽可能快乐。

      func PerformTask(param int) (result int, err error) {
          // 错误检查放在前面
          if param < 0 {
              return 0, errors.New("param cannot be negative")
          }
      
          // 主逻辑放在主体内部
          // 这里是函数的主要逻辑,称为快乐路径
          result = param * 2
          return result, nil
      }
      

      3.自定义错误

      error是Go语言内置的接口类型,只有一个方法Error(),用于返回错误信息的字符串表示。

      标准库中errors包提供了创建简单错误信息的函数。

      package main
      
      import (
      	"errors"
      	"fmt"
      )
      
      func divide(a, b int) (int, error) {
      	if b == 0 {
      		return 0, errors.New("division by zero")
      	}
      	return a / b, nil
      }
      
      func main() {
      	result, err := divide(6, 2)
      	if err != nil {
      		fmt.Println("Error:", err)
      	} else {
      		fmt.Println("Result:", result)
      	}
      
      	result, err = divide(3, 0)
      	if err != nil {
      		fmt.Println("Error:", err)
      	} else {
      		fmt.Println("Result:", result)
      	}
      }
      

      此外,还可以使用fmt包的Errorf 函数创建自定义错误!

      package main
      
      import (
      	"fmt"
      )
      func someFunction() error {
      	return fmt.Errorf("This is a more detailed error: %s", "specific error message")
      }
      
      func main() {
      	err := someFunction()
      	if err != nil {
      		fmt.Println("Error:", err)
      	}
      }
      

      当多处错误处理存在代码重复时,可以使用goto集中处理错误!

      err := firstCheckError()
      if err != nil {
          goto onExit
      }
      err = secondCheckError()
      if err != nil {
          goto onExit
      }
      // 正常处理代码
      onExit:
      fmt.Println(err)
      exitProcess()
      

      4.panic

      panic 是 Go 语言中的内建函数之一,用于在发生不可恢复的错误时引发程序中止。panic 会停止当前函数的执行,并向调用者传播一个引发恐慌的信号,随后程序将被终止。

      通常情况下,panic 用于处理严重错误,如数组越界、空指针引用等。当它被调用时,程序将停止执行当前函数,开始执行延迟(defer)函数,然后程序会崩溃,并显示 panic 产生的错误信息。 在遇到 panic 时,程序的正常流程会被打破,不会再继续执行当前任务。

      在开发过程中,尽量避免使用 panic,而应该在可以预测和处理的情况下使用错误返回(error returns)或其他适当的处理方式,因为 panic 不可恢复,容易引起程序不稳定。

      package main
      import "fmt"
      func someFunc() {
          // 模拟一个无法处理的错误
          err := someErrorOccurred()
          if err != nil {
              panic("An unexpected error occurred: " + err.Error())
          }
      }
      func main() {
          fmt.Println("Starting the program.")
          someFunc()
          fmt.Println("End of the program.")
      }
      

      Go 语言初探:从基础到实战

      5.recover

      在 Go 语言中,recover 函数用于恢复程序的执行,从恐慌状态(panic)中恢复。recover 只有在延迟函数(defer)的内部调用时才会生效。

      通常情况下,recover 与 defer 配合使用,以便在程序进入恐慌状态时恢复程序执行。

      下面是一个使用 recover 来捕获并处理恐慌状态的示例:

      package main
      
      import (
      	"fmt"
      )
      
      func recoverDemo() {
      	if r := recover(); r != nil {
      		fmt.Println("Recovered:", r)
      	}
      }
      
      func someFunc() {
      	defer recoverDemo()
      
      	// 模拟一个恐慌状态
      	panic("Something went wrong!")
      }
      
      func main() {
      	fmt.Println("Starting the program.")
      	someFunc()
      	fmt.Println("End of the program.")
      }
      

      在这个示例中,recoverDemo 函数作为一个延迟函数(defer)在 someFunc 中执行。当 someFunc 函数引发了恐慌状态,recoverDemo 中的 recover 函数捕获到这个恐慌,然后打印出了错误信息。

      请注意,recover 函数只在延迟函数中有效。在非延迟函数中调用 recover 是无效的,且只有在恐慌状态发生时才能捕获错误信息。

      9.并发

      1.进程、线程、协程

      进程是程序在内存中运行时,操作系统对其进行资源分配和调度的独立单位
      线程是进程的一个执行实体,是进程内部进行的一条执行路径,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
      每个进程至少包括一个线程,每个进程的初始线程被称为主线程,主线程终止,进程终止
      协程是轻量级的线程,一个线程可以拥有多个协程
      进程和线程是操作系统级的,协程是编译器级的。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。和多线程比,数量越多,协程的性能优势就越明显。协程的最大优势在于其轻量级,可以轻松创建上万个而不会导致系统资源衰竭

      2.goroutine

      在Go语言中,Goroutine是并发执行的基本单元。它们是Go运行时环境中的轻量级线程,由Go调度器分配到逻辑处理器上执行。Goroutine的运行并不依赖于物理处理器或操作系统线程。每个逻辑处理器(P)负责运行Goroutine,多个P可以运行在一个物理处理器(CPU)上。

      3.协程通信

      不要通过共享内存来通信,而是通过通信来共享内存

      协程间的通信常见是两种方式:

      1.共享数据:很多语言采用共享内存来实现程序数据同步,确保程序以合乎逻辑的方式执行。在程序执行过程中,进程或线程可能对共享数据加锁,以禁止其他进程或线程修改它。总体编程复杂性高

      2.消息机制:每个并发单元是独立个体,多个并发单元的数据不共享,通过消息通信来同步数据。

      4.channel通道

      通道是一种特殊的类型,同时只能有一个 goroutine 访问通道进行发送和获取数据。

      通道写入和读取使用 <- 运算符
      写入 :通道<-变量
      读取: 变量<-通道

      5.缓冲通道

      通道包括无缓冲通道和有缓冲通道
      无缓冲通道 make(chan datatype)
      有缓冲通道 make(chan datatype,capacity)

      无缓冲通道只能存储一条消息,有缓冲通道可以根据make函数的capacity参数存储n条消息,按FIFO读出

      func receiver(c chan string) {
          for msg := range c {
              fmt.Println(msg)
          }
      }
      func main() {
          messages := make(chan string, 2)
          messages <- "hello"
          messages <- "world"
          go receiver(messages)   
          time.Sleep(time.Second * 1)
      }//hello world	
      

      此外,还能使用内置函数返回缓冲通道状态

      ​ len()获取通道当前缓存数
      ​ cap()获取通道缓存容量

          ch := make(chan string, 3)
          ch <- "naveen"
          ch <- "paul"
          fmt.Println("capacity is", cap(ch))
          fmt.Println("length is", len(ch))
          fmt.Println("read value", <-ch)
          fmt.Println("new length is", len(ch))
          //capacity is 3
          //length is 2
          //read value naveen
          //new length is 1
      

      无缓冲通道,写入等待读取,读取等待写入,在双方准备好之前是阻塞的

      有缓冲通道,通道已满时的写入会等待,通道已空的读取会等待

      6.关闭通道

      关闭通道使用内置函数close(),实际上是关闭写入,即发送者告诉接收者不会再有数据发往通道

      接收者能够在通道接收数据的同时,获取通道是否已关闭的参数 。

      func producer(chnl chan int) {  
          for i := 0; i < 10; i++ {
              chnl <- i
          }
          close(chnl)
      }
      func main() {  
          ch := make(chan int)
          go producer(ch)
          for {
              v, ok := <-ch
              if ok == false {
                  break
              }
              fmt.Println("Received ", v, ok)
          }
      }
      

      此外,for range能够自动判断通道是否关闭,具体代码如下所示:

      func producer(chnl chan int) {  
          for i := 0; i < 10; i++ {
              chnl <- i
          }
          close(chnl)
      }
      func main() {  
          ch := make(chan int)
          go producer(ch)
          for v := range ch {
              fmt.Println("Received ",v)
          }
      }
      

      7.WaitGroup

      sync.WaitGroup 用于等待一组 Go 协程执行完成后再执行主程序的方法。它提供了一个简单的机制,以便主程序知道其他所有协程何时执行完成。

      在使用 sync.WaitGroup 时,主要有三个函数:

      • Add(int):增加要等待的协程数量。
      • Done():标志已完成的协程。
      • Wait():等待所有的协程都完成。

      通常,Add 函数用于计数要等待的协程数量,然后在协程的函数中使用 Done 标志已经执行完毕,最后使用 Wait 阻塞主程序,直到所有协程都执行完毕。

      Go 语言初探:从基础到实战

      以下是一个示例,演示了 sync.WaitGroup 的用法:

      package main
      
      import (
      	"fmt"
      	"sync"
      	"time"
      )
      
      func worker(id int, wg *sync.WaitGroup) {
      	defer wg.Done() // 标志协程完成
      	fmt.Printf("Worker %d starting\n", id)
      	time.Sleep(time.Second) // 模拟工作
      	fmt.Printf("Worker %d done\n", id)
      }
      
      func main() {
      	var wg sync.WaitGroup
      
      	for i := 1; i <= 5; i++ {
      		wg.Add(1) // 增加等待的协程数量
      		go worker(i, &wg)
      	}
      
      	wg.Wait() // 等待协程执行完成
      	fmt.Println("All workers have finished")
      }
      

      在这个示例中,我们启动了五个协程,每个协程模拟一些工作(通过 time.Sleep 模拟)。Add 用于增加要等待的协程数量,Done 标志协程已执行完成,而 Wait 阻塞了主程序直到所有的协程都执行完毕。

      8.猜数字例题

      这里分享一道使用协程的例题,具体例题如下:感兴趣的小伙伴可以尝试一下
      Go 语言初探:从基础到实战

      9.计时器Timer

      协程间的通信需设置超时等辅助机制

      一次性计时器:定时器只计时一次,结束便停止

      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      func main() {
      	timer1 := time.NewTimer(2 * time.Second)
      
      	<-timer1.C // 阻塞等待定时器信号
      	fmt.Println("Timer 1 expired")
      
      	timer2 := time.NewTimer(1 * time.Second)
      	go func() {
      		<-timer2.C
      		fmt.Println("Timer 2 expired")
      	}()
      
      	stop2 := timer2.Stop() // 停止定时器2
      	if stop2 {
      		fmt.Println("Timer 2 stopped")
      	}
      }
      

      Go 语言初探:从基础到实战

      10.定时器Ticker

      周期性定时器:定时器周期性进行计时,除非主动停止,否则将永久运行

      在Go语言中,time.Ticker 是用于重复间隔性触发操作的工具。与 time.Timer 不同,time.Ticker 会在一定的时间间隔内重复向通道发送时间事件。

      主要方法如下所示:

      func NewTicker(d Duration) *Ticker 指定一个时间创建一个Ticker , Ticker一经创建便开始计时,不需要额外的启动命令
      func (t *Ticker) Stop() 停止计时,但管道不会被关闭
      

      示例代码如下所示:

      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      func main() {
      	ticker := time.NewTicker(1 * time.Second)
      	defer ticker.Stop()
      
      	for {
      		select {
      		case <-ticker.C:
      			fmt.Println("Ticker ticked")
      		}
      	}
      }
      

      11.select

      多路复用是在一个信道上传输多路信号或数据流,比如网线

      select 借用网络多路复用的概念,用于监听多个通道,同时响应多个通道

      多个通道都没有可写或可读的状态,select 会阻塞
      有一个通道是可写或可读的, select 会执行该通道语句
      有多个通道是可写或可读的, select 会随机选择其中一个执行

      select 语句是 Go 语言用于处理通道操作的关键工具。它可以同时监听多个通道操作,一旦某个通道可操作(有消息可以接收或发送),就执行相应的 case 语句。select 语句有点类似于 switch 语句,但是专门用于通道的操作。

      下面是一个示例,演示了 select 语句的用法:

      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      func main() {
      	ch1 := make(chan string)
      	ch2 := make(chan string)
      
      	go func() {
      		time.Sleep(2 * time.Second)
      		ch1 <- "one"
      	}()
      
      	go func() {
      		time.Sleep(1 * time.Second)
      		ch2 <- "two"
      	}()
      
      	for i := 0; i < 2; i++ {
      		select {
      		case msg1 := <-ch1:
      			fmt.Println("Received", msg1)
      		case msg2 := <-ch2:
      			fmt.Println("Received", msg2)
      		}
      	}
      }
      

      在这个示例中,通过两个协程向两个不同的通道 ch1 和 ch2 发送消息。select 语句会监听这两个通道的状态,一旦有数据可接收,就执行相应的 case 语句,最终输出接收到的消息。

      12.Mutex

      多个线程同时竞争使用某个变量可能会导致结果失控

      mutex,互斥锁,用来保证某个变量在任一时刻,只能有一个线程访问;mutex 用Lock()和Unlock()来创建资源的临界区,这一区间内的代码是线程安全的,任何一个时间点都只能有一个goroutine执行这段区间的代码

      mutex 也可以用通道来代替,通道底层基于mutex,即mutex性能更高,通常不涉及线程交互数据的用mutex,其他性能要求不敏感用通道

      package main
      
      import (
      	"fmt"
      	"sync"
      )
      
      var count = 0
      var mutex sync.Mutex
      
      func increment() {
      	mutex.Lock()         // 通过 Lock() 方法锁住共享资源
      	count++
      	mutex.Unlock()       // 通过 Unlock() 方法解锁共享资源
      }
      
      func main() {
      	var wg sync.WaitGroup
      
      	for i := 0; i < 1000; i++ {
      		wg.Add(1)
      		go func() {
      			defer wg.Done()
      			increment()
      		}()
      	}
      
      	wg.Wait()
      
      	fmt.Println("Count:", count)
      }
      

      13.RWMutex

      Mutex 在大量并发时,同一时刻只有一个协程持有锁,其他阻塞等待,性能下降
      RWMutex在Mutex的基础之上增加了读、写的信号量,并使用了类似引用计数的读锁数量,可使多个协程持有读锁,适合应用在具有一定并发量且读多写少的场合。

      注意:

      RWMutex中可以申请多个读锁,有读锁时申请写锁将会被阻塞

      只要有写锁,后序申请读锁和写锁都会被阻塞

      主要方法如下:

      func (rw *RWMutex) Lock() //申请写锁
      func (rw *RWMutex) Unlock() //释放写锁
      func (rw *RWMutex) RLock() //申请读锁
      func (rw *RWMutex) RUnlock()//释放读锁
      
      package main
      import (
      	"fmt"
      	"sync"
      )
      
      var sharedData int
      var rwMutex sync.RWMutex
      
      func readData() {
      	rwMutex.RLock() // 读取共享资源时使用 RLock() 方法
      	defer rwMutex.RUnlock()
      	fmt.Println("Read Data:", sharedData)
      }
      
      func writeData(value int) {
      	rwMutex.Lock() // 写入共享资源时使用 Lock() 方法
      	defer rwMutex.Unlock()
      	sharedData = value
      	fmt.Println("Write Data:", value)
      }
      
      func main() {
      	// 读取数据
      	for i := 0; i < 5; i++ {
      		go readData()
      	}
      
      	// 写入数据
      	for i := 0; i < 5; i++ {
      		go writeData(i)
      	}
      
      	// 等待所有协程执行完毕
      	fmt.Scanln()
      }
      
      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://lglxv587.blog.csdn.net/article/details/134295134,作者:散一世繁华,颠半世琉璃,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:Java服务端性能优化:JVM垃圾回收策略

      下一篇:Python之装饰器

      相关文章

      2025-05-19 09:04:44

      js小题2:构造函数介绍与普通函数对比

      js小题2:构造函数介绍与普通函数对比

      2025-05-19 09:04:44
      new , 关键字 , 函数 , 对象 , 构造函数
      2025-05-19 09:04:30

      【Canvas技法】辐射式多道光影的实现

      【Canvas技法】辐射式多道光影的实现

      2025-05-19 09:04:30
      代码 , 函数 , 实现
      2025-05-19 09:04:22

      外设驱动库开发笔记54:外设库驱动设计改进的思考

      外设驱动库开发笔记54:外设库驱动设计改进的思考

      2025-05-19 09:04:22
      使用 , 函数 , 初始化 , 定义 , 对象
      2025-05-19 09:04:14

      C语言字符函数和字符串函数--(超全超详细)

      C语言字符函数和字符串函数--(超全超详细)

      2025-05-19 09:04:14
      函数 , 字符 , 字符串
      2025-05-16 09:15:24

      如何将一串数字用函数的方法倒过来(C语言)

      如何将一串数字用函数的方法倒过来(C语言)

      2025-05-16 09:15:24
      函数 , 数字 , 数组
      2025-05-14 10:33:31

      【数据结构】第一章——绪论(2)

      【数据结构】第一章——绪论(2)

      2025-05-14 10:33:31
      函数 , 实现 , 打印 , 理解 , 算法 , 输入 , 输出
      2025-05-14 10:33:31

      计算机小白的成长历程——习题演练(函数篇)

      计算机小白的成长历程——习题演练(函数篇)

      2025-05-14 10:33:31
      函数 , 字符串 , 数组 , 知识点 , 编写 , 迭代 , 递归
      2025-05-14 10:33:25

      30天拿下Rust之高级类型

      Rust作为一门系统编程语言,以其独特的内存管理方式和强大的类型系统著称。其中,高级类型的应用,为Rust的开发者提供了丰富的编程工具和手段,使得开发者可以更加灵活和高效地进行编程。

      2025-05-14 10:33:25
      Rust , type , 代码 , 函数 , 类型 , 返回
      2025-05-14 10:33:25

      超级好用的C++实用库之网络

      在网络相关的项目中,我们经常需要去获取和设置设备的IP地址、子网掩码、网关地址、MAC地址等信息。这些信息一般与操作系统相关,在Windows系统和Linux系统上调用的接口是不一样的。

      2025-05-14 10:33:25
      Linux , 参数 , 地址 , 接口 , 网卡 , 返回值
      2025-05-14 10:33:16

      C++ 11新特性之tuple

      在C++编程语言的发展历程中,C++ 11标准引入了许多开创性的新特性,极大地提升了开发效率与代码质量。其中,tuple(元组)作为一种强大的容器类型,为处理多个不同类型的值提供了便捷的手段。

      2025-05-14 10:33:16
      std , 元素 , 函数 , 初始化 , 模板 , 类型
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5229244

      查看更多

      最新文章

      【Canvas技法】辐射式多道光影的实现

      2025-05-19 09:04:30

      外设驱动库开发笔记54:外设库驱动设计改进的思考

      2025-05-19 09:04:22

      C语言字符函数和字符串函数--(超全超详细)

      2025-05-19 09:04:14

      如何将一串数字用函数的方法倒过来(C语言)

      2025-05-16 09:15:24

      30天拿下Rust之高级类型

      2025-05-14 10:33:25

      超级好用的C++实用库之网络

      2025-05-14 10:33:25

      查看更多

      热门文章

      JAVA__接口的作用

      2023-04-18 14:14:13

      什么是api接口

      2023-03-22 09:03:21

      Python 函数调用父类详解

      2023-04-23 09:44:31

      Go 语言入门很简单 -- Go 语言解析JSON #私藏项目实操分享#

      2023-04-19 09:23:13

      游戏编程之六 游戏编程的特点

      2024-09-25 10:13:46

      C#8.0新语法

      2023-02-07 10:34:04

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      元函数与运行期(普通)函数的区别,为什么要用元函数?

      4 个 JavaScript 最基础的问题 —— Eric Elliott

      ffmpeg音视频开发从入门到精通——常用结构体介绍(一)

      【CPP】C++模板:初阶到进阶语法与实用编程示例

      #私藏项目实操分享#Go 语言入门很简单:String

      【debug】程序调用栈记录profile-backtrace和backtrace|分析瓶颈|分析bug所在

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号