Golang学习笔记

一、GoLang基础知识

1. 优劣势

优势

  • 语法简单,易于学习。类C的语法,
  • 同时比C/C++简洁和干净
  • 自带GC,方便使用
  • 快速编译,高效执行
  • 简单的依赖管理
  • 并发编程,轻松驾驭
  • 静态类型,同时有一些动态语言的特征(var声明)
  • 标准类库,规范统

劣势

  • 不支持泛型
  • 不支持动态加载代码
  • 发展时间短,生态不及Java、C++庞大(我不是说go的第三库少)

2. 用途

  • 巨型中央服务器领域
  • 高性能分布式领域
  • 游戏服务端开发
  • 复杂事件处理
  • 对实时性要求很高的软件开发
  • 可以在Intel和ARM处理器上运行,因此也可以在安卓上运行

3. GO微服务开发

  • 零依赖,让我们可以最小化我们的镜像,节省存储与拉取镜像带宽
  • Runtime使用更小的内存,对比Java的JVM
  • 更好的并行能力,当你真的需求更多CPU的时候
  • 更高的性能,对比解释性语言,在处理数据已经并发方面优势明显
  • 简单,学习成本低,内部人员可以转入Go阵营
  • 使用Go能更接近云原生生态,比如docker, k8s, habor都是用Go开发的

4. 前三个Go程序

初始化go文件

go mod init 名字/地址
  1. Hello World
// hello_world.go
package main

func main() {
   
	println("Hello", "world")
}

  1. 跨文件访问
  • main 函数
    • 注意包名需要和mod文件名一致
    • 公开的方法或者变量需要首字母大写
// HelloWorld
package main

import (
    "fmt"
    "first/mypath"
)

func main() {
   
    a :=4
    fmt.Println("Hello World!",a)
    b:=7
    c:=mypath.Add(a,b)
    fmt.Println(c)
}
  • 文件夹下文件
package mypath

func Add(a int, b int) int {
   
	return a+b
}
  1. 第三方库依赖
  • 下载第三方库
go get -u gonum.org/v1/gonum/stat
// HelloWorld
package main

import (
	"first/mypath"
	"fmt"

	"gonum.org/v1/gonum/stat"
)

func main() {
   
	a := 4
	fmt.Println("Hello World!", a)
	b := 7
	c := mypath.Add(a, b)
	fmt.Println(c)

	arr := []float64{
   1, 2, 4, 5, 6, 7, 8}
	v := stat.Variance(arr, nil)
	fmt.Printf("方差=%f\n", v)
}

5. Go常用命令

  • go help:查看帮助文档
    • go help build
  • go build
    对源代码和依赖的文件进行打包,生成可执行文件
go build [-o 输出名.exe] [-i] [编译标记] [包名]
#如果参数为XX.go文件或文件列表,则编译为一个个单独的包。
#当编译单个main包(文件),则生成可执行文件。

#go build -o my_first_go.exe entrance_class/demo.go
go build hello_word
#查看
ls
#执行
./hello_word
  • go install
    编译并安装包或依赖,安装到$GOPATH/bin下
go install hello_word.go
  • go get
    把依赖库添加到当前module中,如果本机之前从未下载过则先下载并安装(install)
    • go get github.com/tinylib/ msgp会在$ GOPATH/pkg/mod目录下会生成github.com/tinylib/msgp.目录,同时在$GOPATH/bin下生成msgp可执行文件
  • go mod
    module相关的命令
    • go mod init module_name
    • go mod tidy通过扫描当前项目中的所有代码来添加未被记录的依赖至go.mod文件或从go.mod文件中删除不再被使用的依赖
  • go run
    编译并运行程序
  • go test
    执行测试代码
  • go tool
    执行go自带的工具
    • go tool pprof对cpu、内存和协程进行监控
    • go tool trace跟踪协程的执行过程
  • go vet
    检查代码中的静态错误
  • go fmt
    对代码文件进行格式化,如果用了IDE这个命令就不需要了
    • go fmt entrance class/demo.go
  • go doc
    查看go标准库或第三方库的帮助文档
    • go doc fmt.
    • go doc gonum.org/v1/gonum/stat
  • go version
    查看go版本号
  • go env
    查看go环境信息

二、 Golang基础知识

1. 标识符和关键字

命名方式

go变量、常量、自定义类型、包、函数的命名方式必须遵循以下规则:

  1. 首字符可以是任意Unicode字符或下划线
  2. 首字符之外的部分可以是Unicode字符、下划线或数字
  3. 名字的长度无限制

理论上名字里可以有汉字,甚至可以全是汉字,但实际中不要这么做

关键字

break default func interface select
case defer go map struct
chan else goto package switch
const if range type continue
for import return fallthrough var

保留字

  • 常量
    true false iota nil
  • 数据类型
    int int8 int16 int32 int64 uint uint8 uint16 uint32
    uint64 uintptr float32 float64 complex128 complex64
    bool byte rune string error
  • 函数
    make len cap new append copy close delete complex
    real imag panic recover
  • 算术运算符
运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 求余
  • 关系运算符
运算符 描述
== 检查两个值是否相等,如果相等返回True否则返回False
!= 检查两个值是否不相等,如果不相等返回True否则返回False
> 检查左边值是否大于右边值,如果是返回True否则返回False
>= 检查左边值是否大于等于右边值,如果是返回True否则返回False
< 检查左边值是否小于右边值,如果是返回True否则返回False
<= 检查左边值是否小于等于右边值,如果是返回True否则返回False
  • 逻辑运算符
运算符 描述
&& 逻辑AND运算符。如果两边的操作数都是True,则为True,否则为False
|| 逻辑OR运算符。如果两边的操作数有一个True,则为True,否则为False
! 逻辑NOT运算符。如果条件为True,则为False,否则为True
  • 位运算符
运算符 描述
& 参与运算的两数各对应的=进位相与(两位均为1才为1)
| 参与运算的两数各对应的进位相或 (两位有一个为1就为1)
^ 参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1 (两位不一样则为1)。作为一元运算符时表示按位取反,符号位也跟着变
<< 左移n位就是乘以2的n次方。a<<b是把a的各二进位全部左移b位,高位丢弃,低位补0
>> 右移n位就是除以2的n次方。a>>b是把a的各二进位全部右移b位
  • 赋值运算符
运算符 描述
= 简单的赋值运算符,将一个表达式的值赋给一一个左值
+= 相加后再赋值
-= 相减后再赋值
*= 相乘后再赋值
/= 相除后再赋值
%= 求余后再赋值
<<= 左移后赋值
>>= 右移后赋值
&= 按位与后赋值
|= 按位或后赋值
^= 按位异或后赋值

代码测试

package main

import "fmt"

func 中国tc() {
   
	var a float32 = 8
	var b float32 = 3
	var c float32 = a + b
	var d float32 = a - b
	var e float32 = a * b
	var f float32 = a / b

	fmt.Printf("c= %.2f \n", c)
	fmt.Println(d)
	fmt.Println(e)
	fmt.Println(f)

	var m int = 8
	var n int = 3
	var g int = m % n

	fmt.Printf("g= %d \n", g)

}

func main() {
   
	中国tc()
}

//输出结果
c= 11.00 
5
24
2.6666667
g= 2 

2. 变量常量字面量

  • 变量类型
类型 go变量类型 fmt输出
整型 int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 %d
浮点型 float32 float64 %f %e %g
复数 complex128 complex64 %v
布尔型 bool %t
指针 uintptr %d
引用 map slice channel %v
字节 byte %d
任意字符 rune %d
字符串 string %s
错误 error %v
  • 变量初始化
    如果声明后未显式初始化,数值型初始化0,字符串初始化为空字符串,布尔型初始化为false,引用类型、函数、指针、接口初始化为nil
    • 函数内部的变量(非全局变量)可以通过:=声明并初始化

        a:=3
      
    • 下划线表示匿名变量

        _=2+4
        #匿名变量不占命名空间,不会分配内存,因此可以重复使用
      
  • 常量
    常量在定义时必须赋值,且程序运行期间其值不能改变
  • 字面量
    没有出现变量名,直接出现了值。基础类型的字面量相当于是常量
func main() {
   
	const (
		_ = 1 << (10 * iota)
		KB
		MB
		GB
	)
	fmt.Println(KB, MB, GB)
}
//输出结果
//1024 1048576 1073741824

3. 变量作用域

var(A=3)
//全局变量,大写字母开头,所有地方都可以访问,跨package访问时需要带上package名称
b=4 
//全局变量,小写字母开头,本package内都可以访问
func foo(){
   
	b:=5
	//局部变量,仅本函数内可以访问。内部声明的变量可以跟外部声明的变量有冲突,以内部的为准
	{
   b:=6}
	//仅{}圈定的作用域内可以访问,可以跟外部的变量有冲突
}

注意:

  • const常量不能修改
  • 类型不一样不能比较

4. 注释和godoc

多行注释 /**/
单行注释 //

godoc

  • godoc可以为项目代码导出网页版的注释文档
  • 需要先安装go get golang.org/x/tools/cmd/godoc
  • 启动http: godoc -http=:6060
  • 用浏览器访问: http://127.0.0.1:6060/pkg/包名

5. go基础数据类型

5.1 基础数据类型

类型 长度(字节) 默认值 说明
bool 1 false
byte 1 0 uint8, 取值范围[0,255]
rune 4 0 Unicode Code Point, int32
int, uint 4或8 0 32或64位,取决于操作系统
int8, uint8 1 0 -128~ 127, 0~ 255
int16, uint16 2 0 -32768 ~ 32767,0~ 65535
int32, uint32 4 0 -21亿~ 21亿,0 ~ 42亿,rune是int32的别名
int64, uint64 8 0
float32 4 0.0
float64 8 0.0
complex64 8
complex128 16
uintptr 4或8 以存储指针的uint32或uint64整数

5.2 复合数据类型

类型 默认值 说明
array 值类型
struct 值类型
string “” UTF-8字符串
slice nil 引用类型
map nil 引用类型
channel nil 引用类型
interface nil 接口
function nil 函数

5.3 格式化输出

5.3.1 三种输出
  • Print:可以打印出字符串,和变量,没有换行,直接输出每一项
  • Println :可以打印出字符串,和变量,打印的每一项之间都会有空格,结束部分换行
  • Printf : 只可以打印出格式化的字符串,可以输出字符串类型的变量,不可以输出整形变量和整形
5.3.2 格式化输出

fmt.Printf()格式字符串:

import "fmt"
fmt.Printf()
  • 通用
符号 输出内容
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 值的类型的Go语法表示
%% 百分号
  • 布尔类型
符号 输出内容
%t 单词true或false
  • 整数
符号 输出内容
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"
  • 浮点数
符号 输出内容
%b 无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
  • 字符串和[]byte
符号 输出内容
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f)
%X 每个字节用两字符十六进制数表示(使用A-F)
  • 指针
符号 输出内容
%p 表示为十六进制,并加上前导的0x
  • 控制精度
符号 输出内容
%f 默认宽度,默认精度
%9f 宽度9,默认精度
%.2f 默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0
  • 其他flag
占位符 说明
’+’ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义);
’ ‘ 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格
’-’ 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐);
’#’ 八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值;
‘0’ 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;

5.4 自定义类型

  • 类型别名
    • type byte = uint8
    • type ruine = int32
  • 自定义类型
    • type signal uint8
    • type ms map[string]string
    • type add func(a, b int) int
    • type user struct {name string;age int}
//类型别名
package main

import (
	"fmt"
)
type bty = uint8

func main() {
   
	var a bty
	var b byte
	fmt.Printf("%d %d\n", a, b)
	fmt.Printf("%T %T\n", a, b)
	fmt.Printf("%t\n", a == b)
}
/*
0 0
uint8 uint8
true
*/
//结构体
package main

import (
	"fmt"
)

type User struct {
   
	Name string
	Age  int
}

var u = &User{
   "a", 21}

func (self User) Hello() {
   
	fmt.Printf("my name is %s\n", self.Name)
	fmt.Printf("age is %d", self.Age)
}

func main() {
   
	u.Hello()
}

/*输出结果
my name is a
age is 21
*/

三、 复合数据类型

1. 字符串

  • 字符串里可以包含任意Unicode字条
  • 反引号里的转义字符无效。反引号里的原封不动地输出,包括空白符和换行符

1.1 字符串常用操作

方法 介绍
len(str) 求长度
strings.Split 分割
strings.Contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置

1.2 字符串拼接

  • 加号连接
  • func fmt.Sprintf(format string, a …interface{}) string
  • func strings.Join(elems []string, sep string) string
  • 当有大量的string需要拼接时,用strings.Builder效率最高
package main

import (
	"fmt"
	"strings"
)

func str_func() {
   
	s := "hello,how are you"
	//分割函数Split
	arr := strings.Split(s, " ")
	for _, ele := range arr {
   
		fmt.Println(ele)
	}
	/*
	   hello,how
	   are
	   you*/
	fmt.Println(arr) //[hello,how are you]
	//是否存在指定值
	fmt.Printf("%t\n", strings.HasPrefix(s, "he")) //true
	//匹配对应值
	ss := "e"
	position := strings.Index(s, ss)
	fmt.Printf("%d\n", position) //1
	//匹配多值之一
	sss := "le"
	positiona := strings.IndexAny(s, sss)
	fmt.Printf("%d\n", positiona) //1
	//字符串拼接的四种方法
	s1 := "aaa"
	s2 := "bbb"
	s3 := "ccc"
	S1 := s1 + " " + s2 + " " + s3
	S2 := fmt.Sprintf("%s %s %s", s1, s2, s3)
	S3 := strings.Join([]string{
   s1, s2, s3}, " ")
	sb := strings.Builder{
   }
	sb.WriteString(s1)
	sb.WriteString(" ")
	sb.WriteString(s2)
	sb.WriteString(" ")
	sb.WriteString(s3)
	S4 := sb.String()
	fmt.Println(S1)
	fmt.Println(S2)
	fmt.Println(S3)
	fmt.Println(S4)
	/*
	   aaa bbb ccc
	   aaa bbb ccc
	   aaa bbb ccc
	   aaa bbb ccc
	*/
}

func main() {
   
	str_func()
}

1.3 byte和rune

  • string中每个元素叫“字符”,字符有两种:
    • byte:1个字节,代表ASCII码的一个字符
    • rune: 4个字节,代表一个UTF-8字符,一个汉字可用一个rune表示
  • string底层是byte数组,string的长度就是该byte数组的长度,UTF-8编码下一个汉字占3个byte,即一个汉字占3个长度
  • string可以转换为[]byte或[]rune类型
  • string是常量,不能修改其中的字符

byte,rune,string的比较

package main

import (
	"fmt"
)

func main() {
   
	s := "啊a"
	arr := []byte(s)
	brr := []rune(s)
	for _, ele := range arr {
   
		fmt.Printf("%d ", ele)
	}
	fmt.Printf("\ns len %d,arr len %d,brr len %d\n", len(s), len(arr), len(brr))

	fmt.Println(s[1])
	fmt.Println(arr[1])
	fmt.Println(brr[1])
}
//输出结果
/*
229 149 138 97 
s len 4,arr len 4,brr len 2
149
149
97
*/

2 强制类型转换

  • byte和int可以互相转换
  • float和int可以互相转换,小数位会丢失
  • bool和int不能相互转换
  • 不同长度的int或float之间可以相互转换
  • string可以转换为[]byte或[]rune类型,byte或rune可以转为string
  • 低精度向高精度转换没问题,高精度向低精度转换会丢失位数
  • 无符号向有符号转换,最高位是符号位
package main
import (
	"fmt"
	"strings"
)
func change() {
   
	//int和byte转换
	var i int = 9
	var by byte = byte(i)
	i = int(by)
	fmt.Printf("i=%d\n", i)
	//uint64和int的转换
	var ua uint64 = math.MaxUint64
	i8 := int(ua)
	fmt.Printf("ua=%d %b\n", ua, ua)
	//ua=18446744073709551615 1111111111111111111111111111111111111111111111111111111111111111
	ua = uint64(i8)
	fmt.Printf("i8=%d %b\n", i8, i8)
	//i8=-1 -1
	fmt.Printf("ua=%d %b\n", ua, ua)
	//ua=18446744073709551615 1111111111111111111111111111111111111111111111111111111111111111
	s := fmt.Sprintf("%d", 10)//强制将int转string,将10这个数字转成s
	str := strconv.Itoa(intvar)强制将int转string,将intvar这个数字转成str
	str := strconv.FormatInt(int64(intvar),10)强制将int转string,10表示10进制,将intvar这个数字转成str,性能比前一种好一点
}
func main() {
   
	change()
}

转换实例

str := "I am lilei"

//string 转[]byte
b := []byte(str)

//[]byte转string
str = string(b)

//string 转 rune
r := []rune(str)

//rune 转 string
str = string(r)

3. 数组

3.1 数组初始化

  • 数组是块连续的内存空间,在声明的时候必须指定长度,且长度不能改变。所以数组在声明的时候就可以把内存空间分配好,并赋上默认值,即完成了初始化。

  • 长度为5的int数组,每个int占8个字节(64位的操作系统)

  • 数组的地址就是首元素的地址

  • 一维数组初始化如下

var arr1 [5]int = [5]int{
   } //数组必须指定长度和类型,且长度和类型指定后不可改变
var arr2 = [5] int{
   }
var arr3 = [5]int{
   32} //给前2个元素赋值
var arr4 = [5]int{
   2: 15, 4: 30} //指定index赋值
var arr5 = [...]int{
   3, 2, 6, 5, 4}//根据{}里元素的个数推断出数组的长度
var arr6 = [...]struct {
   
	name string
	age int
}{
   {
   "Tom"18}{
   "Jim", 20}} //数组的元素类型由匿名结构体给定
  • 二维数组初始化如下
//5行3列,只给前2行赋值,且前2行的所有 列还没有赋满
var arr1 = [5] [3]int{
   {
   1}, {
   2,3}}
//第1维可以用...推测,第2维不能用...
var arr2 = [...][3]int{
   {
   1}, {
   2,3}}

3.2 访问数组里的元素

  • 通过index访问
    • 首元素arr[0]
    • 末元素arr[len(arr)-1]。
  • 访问二维数组里的元素
    • 位于第三行第四列的元素 arr[2][3]

3.3 遍历数组

//遍历数组里的元素
for i, ele := range arr {
   
	fmt.Printf("index=%d,element=%d\n", i, ele)
}
//或者这样遍历数组
for i:= 0;i<len(arr);i++ {
    //len(arr)获取数组的长度
	fmt.Printf ("index=%d, element=%d\n", i, arr[i])
}
//遍历二维数组
for row,array := range arr {
    //先取出某一行
	for col,ele := range array {
    //再遍历这一行
		fmt.Printf("arr[%d][%d]=%d\n", row,col,ele)
	}
}

代码测试

func Mean(arr [5]int) float64 {
   
	var sum int
	for _, n := range arr {
   //n是元素的拷贝,改动不会改变原值
		fmt.Printf("n=%d\n", n)
		sum += n
	}
	return float64(sum) / float64(len(arr))
}
func main() {
   
	var arr = [5]int{
   1, 2, 3, 4, 7}
	var m float64 = Mean(arr)
	fmt.Printf("%f", m)
}
/*输出结果
n=1
n=2
n=3
n=4
n=7
3.400000
*/

3.4 cap和len

  • cap代表capacity容量
  • len代表length长度
  • len代表目前数组里的几个元素,cap代表给数组分配的内存空间可以容纳多少个元素
  • 由于数组初始化之后长度不会改变,不需要给它预留内存空间,
    所以len(arr)==cap(ar;)

3.5 数组传参

  • 数组的长度和类型都是数组类型的一部分,函数传递数组类型时这两部分都必须吻合
  • go语言没有按引用传参,全都是按值传参,即传递传递数组,实际上传的是数组的拷贝,当数组的长度很大时,仅传参开销都很大
  • 如果想修改函数外部的数组,就把它的指针(数组在内存里的地址)传进来
//传值
func arrPoint(arr [5]int) {
   
	fmt.Println(arr[0])
	arr[0] += 10
	fmt.Println(arr[0])

}
func main() {
   
	var arr = [5]int{
   1, 2, 3, 4, 7}
	fmt.Println(arr[0], "进入函数前")
	arrPoint(arr)
	fmt.Println("进入函数后", arr[0])
}
/*输出结果
1 进入函数前
1
11
进入函数后 1
*/

//传地址
func arrPoint(arr *[5]int) {
   
	fmt.Println(arr[0])
	arr[0] += 10
	fmt.Println(arr[0])

}
func main() {
   
	var arr = [5]int{
   1, 2, 3, 4, 7}
	arrPoint(&arr)
	fmt.Println("进入函数后", arr[0])
}
/*输出结果
1
11
进入函数后 11
*/

4 切面(slice)

切面的地址跟数组首元素的地址是两码事,可以通过初始化的时候或者传参的时候是否对[]中的长度定义来区分

4.1 初始化切片

var s []int //切片声明,len=cap=0
s = []int{
   } //初始化,len=cap=0
s = make([]int,3) //初始化,len=cap=3
s = make([]int,3,5) //初始化,len=3, cap=5
s = []int{
   1,2,3,4,5} //初始化,len=cap=5
s2d := [][]int{
   
	{
   1},{
   2,3}, //二维数组各行的列数相等,但二维切片各行的len可以不等
}

4.2 append

  • 切片相对于数组最大的特点就是可以追加元素,可以自动扩容
  • 追加的元素放到预留的内存空间里,同时len加1
  • 如果预留空间已用完,则会重新申请一块更大的内存空间,capacity变成之前的2倍(cap<1024)或1.25倍(cap> 1024)。把原内存空间的数据拷贝过来,在新内存空间上执行append操作
package main

import (
	"fmt"
)
func slice_append() {
   
	s := make([]int, 3, 5)
	fmt.Println(len(s), cap(s))
	s = append(s, 100)
	s = append(s, 100)
	fmt.Println(s)
	fmt.Println(len(s), cap(s))
	s = append(s, 100)
	fmt.Println(len(s), cap(s))
}

func main() {
   
	slice_append()
}
/*
3 5
[0 0 0 100 100]
5 5
6 10
*/

4.3 截取子切片

s := make([]int, 3, 5) //len=3, cap=5
sub_slice = s[1:3] //len=2, cap=4,截取母切片的第二个元素和第三个元素
  • 刚开始,子切片和母切片共享底层的内存空间,修改子切片会反映到母切片上,在子切片,上执行append会把新元素放到母切片预留的内存空间上
  • 当子切片不断执行append,耗完了母切片预留的内存空间,子切片跟母切片就会发生内存分离,此后两个切片没有任何关系
func sub_slice() {
   
	arr := make([]int, 3, 5)
	crr := arr[1:2] //前闭后开
	crr[0] = 8
	fmt.Println("母子同步")
	fmt.Println(crr)
	fmt.Println(arr)
	fmt.Println(arr[1])
	crr = append(crr, 9)
	fmt.Println("第一次添加元素")
	fmt.Println(arr[2])
	fmt.Println(crr)
	fmt.Println(arr)
	crr = append(crr, 9)
	fmt.Println("第二次添加元素")
	fmt.Println(crr)
	fmt.Println(arr)
	crr = append(crr, 9)
	fmt.Println("第三次添加元素")
	fmt.Println(crr)
	fmt.Println(arr)
	crr = append(crr, 9)
	fmt.Println("第四次添加元素")
	fmt.Println(crr)
	fmt.Println(arr)
	crr[0] = 4
	fmt.Println("达到了容量,母子分离了")
	fmt.Println(crr)
	fmt.Println(arr)
	fmt.Println(arr[1])
}

func main() {
   
	sub_slice()
}
/*
母子同步
[8]
[0 8 0]
8
第一次添加元素
9
[8 9]
[0 8 9]
第二次添加元素
[8 9 9]
[0 8 9]
第三次添加元素
[8 9 9 9]
[0 8 9]
第四次添加元素
[8 9 9 9 9]
[0 8 9]
达到了容量,母子分离了
[4 9 9 9 9]
[0 8 9]
8
*/

注意:
指定子切片的起始位置到最后一个元素如果大于或等于了母切片的最大内存以后才会进行分离

子切片的初始位置是1,故子切片容量是4,在添加四次元素以后超过了子切片的容量,故进行切片分离

arr := make([]int, 3, 5)
crr := arr[1:2] //前闭后开
crr[0] = 8
//添加四次元素
crr[0] = 4
/*
第四次添加元素
达到了容量,母子分离了
[4 9 9 9 9]
[0 8 9]
8
*/

子切片的初始位置是0,故子切片容量是5,在添加四次元素以后刚好等于子切片的容量,故切片不分离

arr := make([]int, 3, 5)
crr := arr[0:1] //前闭后开
crr[0] = 8
//添加四次元素
crr[0] = 4
/*
第四次添加元素
达到了容量,母子分离了
[4 9 9 9 9]
[4 9 9]
4
*/

子切片的初始位置是0,故子切片容量是5,在添加五次元素以后刚好等于子切片的容量,故进行切片分离

arr := make([]int, 3, 5)
crr := arr[0:1] //前闭后开
crr[0] = 8
//添加五次元素
crr[0] = 4
/*
第五次添加元素
达到了容量,母子分离了
[4 9 9 9 9 9]
[8 9 9]
8
*/

4.4 切片传参

  • go语言函数传参,传的都是值,即传切片会把切片的{arrayPointer, len, cap}这3个字段拷贝一份传进来
  • 由于传的是底层数组的指针,所以可以直接修改底层数组里的元素
func update_slice(arr []int) {
   
	arr[0] = 100
}
func update_slice1(arr [3]int) {
   
	arr[0] = 100
}
func update_slice2(arr *[3]int) {
   
	arr[0] = 100
}

func print() {
   
	curr := []int{
   3, 4, 5}
	fmt.Println("通过切片修改")
	fmt.Println(curr)
	update_slice(curr)
	fmt.Println(curr)
	curr1 := [3]int{
   3, 4, 5}
	fmt.Println("通过数组修改")
	fmt.Println(curr1)
	update_slice1(curr1)
	fmt.Println(curr1)
	curr2 := [3]int{
   3, 4, 5}
	fmt.Println("通过数组地址修改")
	fmt.Println(curr2)
	update_slice2(&curr2)
	fmt.Println(curr2)
}

func main() {
   
	print()
}

/*
通过切片修改
[3 4 5]
[100 4 5]
通过数组修改
[3 4 5]
[3 4 5]
通过数组地址修改
[3 4 5]
[100 4 5]
*/

5. map

  • go map的底层实现是hash table,根据key查找value的时间复杂度是O(1)
  • 冲突太多时需要扩容,即增加槽位数,给每个key重新分配槽位

5.1 map的初始化

var m map[string]int //声明map,指定key和value的数据类型
m = make(map[string]int) //初始化,容量为0
m = make(map[string]int, 5) //初始化,容量为5。强烈建议初始化时给一个合适的容量,减少扩容的概率
m = map[string]int{
   "语文": 0,"数学": 39} //初始化时直接赋值

5.2 添加和删除key

m["英语"]= 59 //往map里添加key-value对
m["英语"]=70//会覆盖之前的值
delete(m, "数学") //从map里删除key-value对
len(m) //获取map的长度

go不支持对map上执行cap函数

5.3 根据key找value

  • 读取key对应的value,如果key不存在,则返回value类型的默认值
value:=m["数学"]
  • 取key对应的value建议使用这种方法,先判断key是否存在
if value, exists := m["语文"]; exists {
   
	fmt.Println(value)
} else {
   
	fmt. Println(" map里不存在[语文]这个key")
}


value, exists := m[key]
//通过exists

5.4 遍历map

for key,value := range m {
   
	fmt.Printf("'%s=%d\n", key, value)
}

注意:
遍历的值只是拷贝的数据,修改其值并不影响原始的数据

5.5 测试代码

初始化map、添加、删除、查找和遍历

func getmap() {
   
	var m map[string]int
	fmt.Println(len(m))
	m = make(map[string]int) //等价于	m = make(map[string]int, 0)
	fmt.Println(len(m))
	m = make(map[string]int, 10) //cap=10
	fmt.Println(len(m))
	m = map[string]int{
   "a": 1, "b": 2}
	fmt.Println(len(m))
	delete(m, "b")
	fmt.Println(len(m))
	delete(m, "张三") //noop
	fmt.Println(len(m))
	value := m["Q"]
	fmt.Printf("Q的值是:%d\n", value)
	value = m["a"]
	fmt.Printf("a的值是:%d\n", value)
	fmt.Println("遍历结果:")
	for key, value := range m {
   
		fmt.Printf("key %s,value %d\n", key, value)
	}
}
/*
0
0
0
2
1
1
Q的值是:0
a的值是:1
遍历结果:
key a,value 1
*/

判断元素存在与否

func getmap1() {
   
	m := map[string]int{
   "A": 1}
	key := "A"
	v, ok := m[key]
	fmt.Println(ok)
	if ok {
   
		fmt.Println(v)
	} else {
   
		fmt.Printf("key %s 不存在\n", key)
	}
}
/*
true
1
*/

6. 管道channel

  • 管道底层是一个环形队列(先进先出),send(插入)和recv(取走)从同一个位置沿同一个方向顺序执行
  • sendx表示最后一次插入元素的位置,recvx表示最后一次取走元素的位置

6.1 管道的声明和初始化

var ch chan int //声明
ch = make(chan int, 8) //初始化,环形队列里可容纳8个int,写入数据超过长度会报错

6.2 send和revc

ch<-1//往管道里写入(send)数据
ch<-2
ch<-3
v:=<-ch//从管道里取走(recv)数据
v = <-ch

6.3 遍历管道

close(ch) //遍历前必须先关闭管道,禁止再写入元素,可以取出

//遍历管道里剩下的元素,不仅是遍历还会取出剩余的元素
for ele := range ch {
   
	fmt.Println(ele)
}

//等价于这种方式
L:=len(ch)
for i:=0;i<L;i++{
   
	ele:=<-ch
	fmt.Println(ele)
}

6.4 单向通道

<- chan int // 通道只能取走数据不能写入数据
chan <- int // 通道只能写入数据不能读取数据

6.5 引用类型

  • slice、map和channel是 go语言里的3种引用类型,都可以通过make函数来进行初始化(申请内存分配)
  • 因为它们都包含一个指向底层数据结构的指针,所以称之为“引用”类型
  • 引用类型未初始化时都是nil,可以对它们执行len()函数,返回0

6.6 测试代码

channel初始化、写入、取出和遍历

func main() {
   
	var ch chan int
	fmt.Printf("%v\n", ch)
	fmt.Printf("%d\n", len(ch))
	ch = make(chan int, 4)
	fmt.Printf("%d\n", len(ch))
	fmt.Printf("%d\n", cap(ch))
	ch <- 3
	ch <- 2
	ch <- 3
	ch <- 2
	<-ch
	ch <- 2
	fmt.Printf("%d\n", len(ch))
	fmt.Printf("%d\n", cap(ch))
	close(ch)
	fmt.Println("遍历数据:")
	fmt.Printf("%d\n", len(ch))
	for ele := range ch {
   
		fmt.Println(ele)
	}
	fmt.Println("----------")
	fmt.Printf("%d\n", len(ch))
	for ele := range ch {
   
		fmt.Println(ele)
	}
}
/*
<nil>
0
0
4
4
4
遍历数据:
4
2
3
2
2
----------
0
*/

7. 作业

  • 创建一个初始长度为0、容量为10的int型切片,调用rand.Intn(128)100次,往切片里面添加100个元素,利用map统计该切片里有多少个互不相同的元素。
func task1() {
   
	curr := make([]int, 10)
	key := 0
	m := make(map[int]int)
	for i := 0; i < 100; i++ {
   
		key = rand.Intn(128)
		curr = append(curr, key)
		m[key] = key
	}
	fmt.Println(curr)
	fmt.Printf("目前切片的容量位%d\n", cap(curr))
	fmt.Printf("互不相同的元素个数有%d", len(m))
}
/*
[0 0 0 0 0 0 0 0 0 0 33 15 71 59 1 6 57 44 72 36 70 47 34 113 88 26 11 21 37 98 15 90 104 18 127 43 47 120 54 119 53 120 91 15 37 76 41 119 125 18 13 18 74 67 113 19 94 100 127 89 21 73 117 23 40 17 72 122 103 43 3 30 61 28 106 36 105 2 31 34 75 104 106 118 103 38 73 7 124 52 31 121 1 29 9 43 105 19 3 50 62 88 2 115 36 7 88 35 13 126]
目前切片的容量位160
互不相同的元素个数有69
*/
  • 实现一个函数func arr2string(arr []int) string,比如输入[]int{2,4,6},返回“2 4 6”。输入的切片可能很短,也可能很长。
func arr2string(arr []int) string {
   
	sb := strings.Builder{
   }
	for i := 0; i < len(arr)-1; i++ {
   
		s := fmt.Sprintf("%d", arr[i])
		sb.WriteString(s)
		sb.WriteString(" ")
	}
	s := fmt.Sprintf("%d", arr[len(arr)-1])
	sb.WriteString(s)
	return sb.String()
}

func main() {
   
	var curr = []int{
   2, 4, 6}
	fmt.Println(arr2string(curr))
}
/*
2 4 6
*/

8. go语言结构体

8.1 结构体创建、访问与修改

8.1.1 定义结构体

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

8.1.2 初始化一个实例

var u user//声明,会用相应类型的默认值初始化struct里的每一个字段
u = new (user)//初始化
u = user{
   } //相应类型的默认值初始化struct里的每一个字段
u = user{
   id: 3,name:"zcy"} //赋值初始化
u = user{
   4,100.0,time.Now(),"zcy","beijing"} //赋值初始化,可以不写字段,但需要跟结构体定义里的字段顺序一致

8.1.3 访问与修改结构体

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

测试代码

func test() {
   
	u := User{
   score: 100, name: "aa", id: 1}
	u.score = 10
	u.enrollment = time.Now()
	fmt.Printf("id %d,Score %g, name [%s],addr [%s], enrollment %v\n", u.id, u.score, u.name, u.addr, u.enrollment)
	fmt.Printf("%v\n", u)
}
/*输出结果
id 1,Score 10, name [aa],addr [], enrollment 2022-04-11 17:07:08.305878 +0800 CST m=+0.000540301
{1 10 {13874774404153168880 540301 0x76e960} aa }
*/

8.1.4 成员函数(方法)

//可以把user理解为hello函数的参数,即hello(u user, man string)
func (u user) hello(man string) {
   
	fmt. Println("hi " + man+ ",my name is"+ u.name)
}
//函数里不需要访问user的成员,可以传匿名,甚至_也不传
func(_ user) think(man string) {
   
	fmt.Println("hi " + man + ",do you know my name?")
}

8.1.5 为任意类型添加方法

type UserMap map[int]User //自定义类型
//可以给自定义类型添加任意方法
func (um UserMap) GetUser(id int) User {
   
	return um[id]
}


//测试代码
func (u user) hello(man string) {
   
	fmt.Println("hi " + man + ",my name is" + u.name)
}
func hello(u user, man string) {
   
	fmt.Println("hi " + man + ",my name is" + u.name)
}

func test() {
   
	u := user{
   score: 100, name: "aa", id: 1}
	u.hello(u.name)
	hello(u, u.name)

}
func main() {
   
	test()
}
/*输出结果
hi aa,my name isaa
hi aa,my name isaa
*/

8.1.6 可见性
- go语言关于可见的统一规则:大写字母开头跨package也可以访问;否则只能本package内部访问
- 结构体名称以大写开头时,package外部可见,在此前提下,结构体中以大写开头在成员变量或成员函数在package外部也可见
8.1.7 匿名结构体
匿名结构体通常用于只使用一次的情况

var stu struct{
    //声明stu是一个结构体,但这个结构体是匿名的
	Name string
	Addr string 
}
stu.Name = "zcy"
stu.Addr= "bj"

//测试代码
func main() {
   
	/*等价于下面的代码 
	type People struct {
		Name string
		Sex	byte
	}

	var abc People
	abc.Name="w" */

	var abc struct {
   
		Name string
		Sex  byte
	}
	abc.Name = "w"
}

8.1.8 结构体中含有匿名成员

type Student struct {
   
	Id int
	string//匿名字段
	float32//直接使用数据类型作为字段名,所以匿名字段中不能出现重复的数据类型
var stu = Student{
   Id: 1, string: "zcy", float32: 79.5}
fmt.Printf("anonymous_member string member=%s float mem ber=%f\n", stu.string, stu.float32)

8.2 结构体指针

8.2.1 创建结构体指针

var U User
user := &u//通过取址符&得到指针
user = &User{
    //直接创建结构体指针
	Id: 3, Name: "zcy", addr: "beijing",
}
user = new(User) //通过new()函数实体化一个结构体,并返回其指针

8.2.2 构造函数

//构造函数。返回指针是为了避免值拷贝
func NewUser(id int, name string) *User {
   
	return &User{
   
		Id: id,
		Name: name,
		addr: "China",
		Score: 59,
	}
}

测试代码

type Student struct {
   
	Name string
	string
	int
}
func (Student) say() {
   

}
func NewStudent(name, city string, age int) *Student {
   
	// 等价于下面代码
	// var s Student
	// s.Name=name
	// s.string=city
	// s.int=age
	// return &s

	//等价于下面代码
	// return &Student{Name: name, string: city, int: age}

	s := Student{
   name, city, age}

	return &s
}

func main() {
   
	var s Student
	s.Name = "w"
	s.string = "1234"
	s.int = 34
	sPtr := &s
	fmt.Println(sPtr.Name)
	sPtr.say()

	s2 := *sPtr //指针解析
	s2.say()

	fmt.Println(sPtr)
	fmt.Println(s2)
	fmt.Printf("%p", sPtr)
}
/*输出结果
w
&{w 1234 34}
{w 1234 34}
0xc000114480
*/

8.2.3 方法接收指针

type user struct {
   
	id         int
	score      float32
	enrollment time.Time
	name, addr string
}

func hello(u user, man string) {
   
	u.name = "杰克"
	fmt.Println("hi " + man + ",my name is " + u.name)
}

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

func main() {
   
	u := user{
   name: "aaa"}
	hello(u, "wu")
	fmt.Printf("my name is %s\n", u.name)
	hello2(&u, "a")
	fmt.Printf("my name is %s\n", u.name)
}
/*
hi wu,my name is 杰克
my name is aaa
hi a,my name is 杰克
my name is 杰克
*/

成员函数调用也可以使用结构体指针达到这个效果

type user struct {
   
	id         int
	score      float32
	enrollment time.Time
	name, addr string
}

func (u user) hello(man string) {
   
	u.name = "杰克"
	fmt.Println("hi " + man + ",my name is " + u.name)
}

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

func main() {
   
	u := user{
   name: "aaa"}
	u.hello("wu")
	fmt.Printf("my name is %s\n", u.name)
	u.hello2("a")
	fmt.Printf("my name is %s\n", u.name)
}
/*
hi wu,my name is 杰克
my name is aaa
hi a,my name is 杰克
my name is 杰克
*/

结构体指针解析以后就相当于是拷贝,修改解析以后的结构体不影响原结构体

	var s Student
	s.Name = "w"
	s.string = "1234"
	s.int = 34
	// sPtr := &s
	// s2 :=*sPtr
	s2 :=s//等价于注释的两行代码
	s2.string ="sh"
	fmt.Println(s.string)
/*输出结果
w
*/

8.3 结构体嵌套

type user struct {
   
	name string
	sex byte
}
type paper struct {
   
	name string
	auther user //结构体嵌套
}
p := new(paper)
p.name= "论文标题"
p.authername= "作者姓名"
p.auther.sex = 0
//匿名字段,可用数据类型当字段名
type vedio struct {
   
	length int
	name string
	user//匿名字段
}

字段名冲突

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

8.4 深拷贝与浅拷贝

  • 深拷贝,拷贝的是值,比如Vedio. Length
  • 浅拷贝,拷贝的是指针,比如Vedio.Author
  • 深拷贝开辟了新的内存空间,修改操作不影响原先的内存
  • 浅拷贝指向的还是原来的内存空间,修改操作直接作用在原内存空间上

浅拷贝

type user struct {
   
	id         int
	score      float32
	enrollment time.Time
	name, addr string
}

func updateSlice1(arr *[]user) {
   //user[]切片的地址(指针类型)
	(*arr)[0].name = "123"
}
func updateSlice2(arr []user) {
   //user[]切片
	arr[0].name = "456"
}
func updateSlice3(arr []*user) {
   //user[]切片的每个元素都是user地址元素(指针类型)
	arr[0].name = "789"
}
func test() {
   
	arr := []user{
   {
   name: "wu"}}
	updateSlice1(&arr)
	fmt.Println(arr[0].name)
	updateSlice2(arr)
	fmt.Println(arr[0].name)
	brr := []*user{
   {
   name: "wu"}}
	updateSlice3(brr)
	fmt.Println(brr[0].name)
}

func main() {
   
	test()
}
//三种方式都是浅拷贝,都改变了原来的值
/*输出结果
123
456
789
*/

深拷贝
有的时候我们需要进行深拷贝,深拷贝就是对新对象的修改不会引起旧对象的改变。这时候我们就可以使用copy()函数来进行深拷贝。

func main() {
   
	a:=make([] int,2,10)
	b:=make([] int,2,10)
	// 切片是引用的类型
	copy(b,a)
	b[0]=1
	b[1]=2
	// a b 的值被同时修改
	fmt.Println(a)
	fmt.Println(b)
}
/*输出结果,未改变原来的值
[0 0]
[1 2]
*/

四、 流程控制语句

1. if

1.1 基本语法

if 5>9 {
   
	fmt.Println("5>9")
}
  • 如果逻辑表达式成立,就会执行{}里的内容
  • 逻辑表达式不需要加()
  • “{”必须紧跟在逻辑表达式后面,不能另起一行

1.2 复杂逻辑表达式

if c,d,e:=5, 9, 2;c<d&&(c>e||c>3){
   //初始化多个局部变量。复杂的逻辑表达式
	fmt.Println("fit")
}
  • 逻辑表达中可以含有变量或常量
  • if句子中允许包含1个(仅1个)分号,在分号前初始化些局部变量(即只在if块内可见)

1.3 if-else

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

1.4 if表达式嵌套

if (true) {
   
	if (true) {
   
		if (true) {
   
			if (true) {
   
				if (true) {
   
				}
			}
		}
	}
}

注意:最好不要超过三层嵌套,不然自己也看不懂

2. switch

2.1 基本语法

color := "black"
switch color {
   
case "green":	//相当于if color== "green"
	fmt.Println("go")
case "red" :	//相当于else if color== "red"
	fmt.Println("stop")
default:	//相当于else
	fmt.Printf("invalid traffic signal: %s\n", strings.ToUpper(color))
}

2.2 定义规则

  • switch-case-default可能模拟if-else if-else,但只能实现相等判断
  • switch和case后面可以跟常量、变量或函数表达式,只要它们表示的数据类型相同就行
  • case后面可以跟多个值,只要有一个值满足就行

2.3 空switch

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

2.4 switch Type

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还是interface{}类型
	fmt.Printf("number is inerface %v\n", value)
}

2.5 fallthrough

  • 从上往下,只要找到成立的case,就不再执行后面的case了。所以为提高性能,把大概率会满足的情况往前放
  • case里如果带了fallthrough,则执行完本case还会去判断下一个case是否满足
  • 在switch type语句的case子句中不能使用falthrough
func goo1(age int) {
   
	switch {
   
	case age < 5:
		fmt.Println("<5")
		fallthrough
	case age > 0:
		fmt.Println(">0")
	case age < 6:
		fmt.Println("<6")

	}
}

func main() {
   
	goo1(4)
}
/*输出结果
<5
>0
*/

2.6 select

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

select {
   
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}

select 语句的语法:

  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
    否则:
    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

3. for循环

3.1 基本语法

arr :=[]int{
   1,2,
GoLang学习笔记主要包括以下几个方面: 1. 语法规则:Go语言要求按照语法规则编写代码,例如变量声明、函数定义、控制结构等。如果程序中违反了语法规则,编译器会报错。 2. 注释:Go语言中的注释有两种形式,分别是行注释和块注释。行注释使用`//`开头,块注释使用`/*`开头,`*/`结尾。注释可以提高代码的可读性。 3. 规范代码的使用:包括正确的缩进和空白、注释风格、运算符两边加空格等。同时,Go语言的代码风格推荐使用行注释进行注释整个方法和语句。 4. 常用数据结构:如数组、切片、字符串、映射(map)等。可以使用for range遍历这些数据结构。 5. 循环结构:Go语言支持常见的循环结构,如for循环、while循环等。 6. 函数:Go语言中的函数使用`func`关键字定义,可以有参数和返回值。函数可以提高代码的重用性。 7. 指针:Go语言中的指针是一种特殊的变量,它存储的是另一个变量的内存地址。指针可以实现动态内存分配和引用类型。 8. 并发编程:Go语言提供了goroutine和channel两个并发编程的基本单位,可以方便地实现多线程和高并发程序。 9. 标准库:Go语言提供了丰富的标准库,涵盖了网络编程、文件操作、加密解密等多个领域,可以帮助开发者快速实现各种功能。 10. 错误处理:Go语言中的错误处理使用`defer`和`panic`两个关键字实现,可以有效地处理程序运行过程中出现的错误。 通过以上内容的学习,可以掌握Go语言的基本语法和编程思想,为进一步学习和应用Go语言打下坚实的基础。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Golang学习笔记](https://blog.csdn.net/weixin_52310067/article/details/129467041)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [golang学习笔记](https://blog.csdn.net/qq_44336275/article/details/111143767)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值