type
status
date
slug
summary
tags
category
icon
password
Go语言基本语法学习-I
目 录
Go语言特性
自动垃圾回收
以不支持垃圾回收的语言内存管理机制为例,如下面这段C语言代码
C系列编程语言由于不支持自动的内存回收机制,因此在声明并赋值一个内存区域后,需要在不使用该内存的时候及时释放,否则指针会变成“野指针”并且该程序进程所占内存会疯狂增长,导致程序崩溃。到目前为止,内存泄露的最佳解决方案是在语言级别引入自动垃圾回收算法(Garbage Collection,简称GC)。所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。当然,因为需要尽量最小化垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个复杂得多,比如为对象增加年龄属性等,但基本原理都是如此。
丰富的内置类型
Golang 除了支持简单的内置类型,如整型、浮点型、字符型等,还内置了一些高级类型,比如字符串类型、字典类型、数组类型、数组切片类型(动态增长的数组,类似c++中的vector)
函数多返回值
目前的主流语言除了Python外基本上都不支持函数拥有多返回值功能。然而,Golang 可以支持函数拥有多返回值,并且在接收函数返回值时,Go还可以实现像Python类似的使用下划线作为占位符来忽略其他不关心的返回值。
错误处理
Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic 和 recover。整体上而言与C++和Java等语言中的异常捕获机制相比,
Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。这对于代码的阅读者和维护者来说也是一件很好的事情,因为可以避免在层层的代码嵌套中定位业务代码。
匿名函数和闭包
在Go语言中,所有的函数也是值类型,可以作为参数传递。Go语言支持常规的匿名函数和闭包,比如下列代码就定义了一个名为f的匿名函数,开发者可以随意对该匿名函数变量进行传递和调用。
类型与接口
Go语言的类型定义非常接近于C语言中的结构(struct),甚至直接沿用了struct关键字。相比而言,Go语言并没有直接沿袭C++和Java的传统去设计一个超级复杂的类型系统,不支持继承和重载,而只是支持了最基本的类型组合功能。Go语言也不是简单的对面向对象开发语言做减法,它还引入了一个无比强大的“非侵入式”
接口的概念,让开发者从以往对C++和Java开发中的接口管理问题中解脱出来。
上述c++代码首先声明了一个接口,并且在实现类时,将类与接口紧密绑定,接口的修改会影响到所有实现了该接口的类。Go则不需要
并发编程
Go语言引入了goroutine概念,它使得并发编程变得非常简单。通过使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来通信,Go语言让并发编程变得更加轻盈和安全。
goroutine是一种比线程更加轻盈、更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度的规模不下于每秒百万次,这使得我们能够创建大量的goroutine,从而可以很轻松地编写高并发程序,达到我们想要的目的。
反射
反射(reflection)是在Java语言出现后迅速流行起来的一种概念。通过反射,你可以获取对象类型的详细信息,并可动态操作对象。反射最常见的使用场景是做对象的序列化(serialization,有时候也叫Marshal & Unmarshal)。例如,Go语言标准库的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量依赖于反射功能来实现。
语言交互性
在Go代码中,可以按Cgo的特定语法混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。与Java中的JNI不同,Cgo的用法非常简单。
小结
主要是针对Go语言所具备的几点优势进行简单的汇总,后续会针对具体内容进行具体学习。
变量
变量声明
- 声明单个变量: var 变量名 变量类型
- 声明多个变量
变量初始化
变量赋值
匿名变量
常量
字面常量
指的是程序硬编码的常量。Go中的字面常量与其他语言不同,只要该常量在相应类型的值域范围内,就可以作为该类型的常量。例如, 就可以赋值给 int、uint、int32、int64、float32、float64、complex64、complex128等类型的变量。
常量定义
通过const关键字给字面常量定义一个名字, const 常量名 (常量类型) = 字面常量
预定义常量
iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。
枚举
类型
布尔类型
整型
类型名 | 长度(字节) | 值范围 |
int8 | 1 | 1000 0000(-0/-128) 0111 1111(127) 1111 1111(-127) |
int16 | 2 | |
int32 | 4 | |
int64 | 8 | |
uint8 | 1 | (1111 1111) |
uint16 | 2 | |
uint32 | 4 | |
uint64 | 8 | |
int | 平台相关 | 平台相关 |
uint | 平台相关 | 平台相关 |
uintptr | 无符号整型,用于存放一个指针 | 在32位平台下为4字节,64字节平台下是8字节 |
浮点型
浮点型用于表示包含小数点的数据,比如1.234就是一个浮点型数据。Go语言中的浮点类型采用IEEE-754标准的表达方式。Go语言定义了两个类型float32和float64,其中float32等价于C语言的float类型,float64等价于C语言的double类型。
因为浮点数不是一种精确的表达方式,所以像整型那样直接用==来判断两个浮点数是否相等是不可行的,这可能会导致不稳定的结果。
复数类型
复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示虚部(imag)。
字符串
- ASCII码集
ASCII 使用 7 位代码来表示 128 个不同的字符。这些代码被分为 95 个可打印的字符,其中包括 26 个英文字母(A 到 Z,包括大写和小写)、10 个数字(0 到 9),以及各种标点符号和其他符号。还有 33 个不可打印的字符,其中包括控制字符,如回车和换行,以及其他各种用于格式化文本的字符
- UTF-8
UTF-8 扩展了 ASCII 字符集,使用 8 位代码,允许多达 256 个不同的字符。这意味着 UTF-8 可以表示所有可打印的 ASCII 字符,以及不可打印的字符。UTF-8 还包括各种额外的国际字符,如中文字符和阿拉伯字符。
在Go语言中,字符串也是一种基本类型。相比之下, C/C++语言中并不存在原生的字符串类型,通常使用字符数组来表示,并以字符指针来传递。
双引号用来创建可解析的字符串,支持转义,但不能用来引用多行;
反引号用来创建原生的字符串字面量,可能由多行组成,但不支持转义,并且可以包含除反引号外的其他所有字符

字符串遍历
字符类型
在Go语言中支持两个字符类型,一个是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。
数组
数组是Go语言编程中最常用的数据结构之一。顾名思义,数组就是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本
- 数组声明方法
在Go语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内置常量,可以用Go语言的内置函数len()来获取。
- 数组元素遍历
数组切片
从底层实现的角度来看,数组切片实际上仍然使用数组来管理元素,因此它们之间的关系让C++程序员们很容易联想起STL中std::vector和数组的关系。基于数组,数组切片添加了一系列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复复制。

- 创建数组切片
- 数组切片元素遍历
- 动态增减元素
可动态增减元素是数组切片比数组更为强大的功能。与数组相比,数组切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能。数组切片支持Go语言内置的cap()函数和len()函数,cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数。
数组切片会自动处理存储空间不足的问题,如果追加的内容长度超过当前已分配的存储空间(即cap()函数返回的值),数组切片会自动分配一块足够大的内存。
- 数组切片内容赋值
数组切片支持Go语言的另一个内置函数copy(),用于将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。

map类型
在C++/Java中,map一般都以库的方式提供,比如在C++中是STL的std::map<>,在C#中是Dictionary<>,在Java中是Hashmap<>,在这些语言中,如果要使用map,事先要引用相应的库。而在Go中,使用map不需要引入任何库,并且用起来也更加方便。map是一堆键值对的未排序集合。比如以身份证号作为唯一键来标识一个人的信息。
- map变量声明:var map变量名 map[键类型] 值类型
- 创建map
- 创建并初始化
- map赋值
- map元素删除
- map元素查找
运算符
算术运算符
A赋值为20,B赋值为10
运算符 | 描述 | 实例 |
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
-- | 自减 | A-- 输出结果 9 |

逻辑运算符
A赋值为True,B赋值为False
运算符 | 描述 | 实例 |
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |

关系运算符
A赋值为20,B赋值为10
运算符 | 描述 | 实例 |
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |

位运算符
p | q | p & q | p | q | p ^ q |
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假定 A 为60,B 为13
运算符 | 描述 | 实例 |
& | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 | (A & B) 结果为 12, 二进制为 0000 1100 |
| | 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或 | (A | B) 结果为 61, 二进制为 0011 1101 |
^ | 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 | A << 2 结果为 240 ,二进制为 1111 0000 |
>> | 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 | A >> 2 结果为 15 ,二进制为 0000 1111 |
赋值运算符
运算符 | 描述 | 实例 |
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C *= A 等于 C = C * A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
其他运算符
运算符 | 描述 | 实例 |
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
运算符优先级
二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 | 运算符 |
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
运算符注意
不同数据类型不能直接进行相关运算,如下

流程控制
条件语句
关于条件语句,需要注意以下几点:
- 条件语句不需要使用括号将条件包含起来();
- 无论语句体内有几条语句,花括号{}都是必须存在的;
- 左花括号{必须与if或者else处于同一行;
- 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
选择语句
在使用switch结构时,我们需要注意以下几点:
- 左花括号{必须与switch处于同一行;
- 条件表达式不限制为常量或者整数;
- 单个case中,可以出现多个结果选项;
- 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
- 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;
- 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if...else...的逻辑作用等同。
循环语句
与多数语言不同的是,Go语言中的循环语句只支持for关键字,而不支持while和do-while结构。关键字for的基本使用方法与C和C++中非常接近。
使用循环语句时,需要注意的有以下几点。
- 左花括号{必须与for处于同一行。
- Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
- Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更高级的break,可以选择中断哪一个循环
跳转语句
参考文献
注:本文大部分内容来源于《Go语言编程》这本书,非常感谢这本书在学习Go语言方面给我的指导!
注:To be Continued…… 🍊
有关这篇文章的任何问题,欢迎您在底部评论区留言,一起交流~ 🍊