第一章 基础知识

1.1 语言简介

1.1.1 Go 语言的诞生背景

1.1.2 语言特性

  语言组织

  所有的高级语言都使用源代码来表达程序,程序的语法格式千差万别,但表达这些语法的基本概念大同小异

  • 标识符和关键字
  • 变量和常量
  • 运算符
  • 表达式
  • 简单语句
  • 控制结构

  类型系统

  • 动静特性:动态语言还是静态语言
  • 类型强弱:强类型还是弱类型
  • 基本数据类型:包括类型及其支持的运算和操作集合
  • 自定义数据类型:包括类型及其支持的运算和操作集合

  抽象特性

  • 函数:是否支持函数、匿名函数、高阶函数、闭包等
  • 面向对象:是否支持面向对象
  • 多态:如何支持多态
  • 接口:是否支持接口,以及接口实现模式

  元编程特性

  • 泛型:是否支持泛型
  • 反射:是否支持反射,反射的能力

  运行和跨平台语言特性

  • 编译模式:是编译成可执行程序,还是编译成中间代码,还是解释器解释执行
  • 运行模式:直接由 OS 加载运行,还是由虚拟机加载执行
  • 内存管理:否原生支持并发,还是库支持
  • 交叉编译:是否支持交叉编译
  • 跨平台支持:是否支持多个平台

  语言软实力特性

  • 库:标准库和第三方库是否丰富、好用、高效
  • 框架:是否有非常出众的框架
  • 语言自身兼容性:语言规范是否经常变换,语言新版本向前兼容性
  • 语言影响力:是否有商业公司支持,社区的活跃性,是否是著名项目

1.1.3 Go 语言的特性

特性集合特性项GoCJava
基础语法关键字和保留字25个ANSI 32个大于48个
-控制结构支持顺序、循环、分支支持顺序、循环、分支支持顺序、循环、分支
类型系统动、静特性静态语言,支持运行时动态类型静态语言静态语言
-强、弱特性强类型弱类型强类型
-隐式类型推导支持
-类型安全类型安全非类型安全类型安全
-自定义数据类型支持type自定义struct通过类/接口实现自定义类型和行为
抽象函数支持支持支持
-面向对象支持类型组合支持面向对象struct内嵌函数指针支持类/接口
-接口Duck模型void *间接支持显式声明
-多态通过接口支持void *间接支持接口及继承关系支持
元编程泛型支持
-反射支持
平台和运行模式编译模式编译成可执行程序编译成可执行程序编译成中间字节码
-运行模式直接运行直接运行虚拟机加载执行
-内存管理支持自动垃圾回收手动管理支持自动垃圾回收
-并发支持协程(语言原生支持)OS线程(库支持协程)Java线程(JVM内部映射到OS线程)
-交叉编译支持支持中间代码无交叉编译必要
-跨平台支持支持原生跨平台
语言软实力标准库和第三方库丰富,发展很快很丰富很丰富
-框架丰富,发展很快很丰富很丰富
-语法兼容性向前兼容性好向前兼容性好向前兼容性好
-影响力社区活跃,Google背书40多年宝刀未老社区活跃,近几年受Oracle控制
-应用领域云计算基础设施软件、中间件、区块链OS及系统软件企业级应用/大数据/移动端

1.1.4 总结

  所谓技能,就是把已掌握的知识抽象成解决问题的认知模型,这些认知模型能够指导我们解决某个领域和相似领域的问题。

1.2 初识 Go 程序

  第一个 Go 程序

// hello.go
package main

import "fmt"

func mian() {
    fmt.Printf("Hello, world. 你好,世界!\n")
}

  程序解读

  • 第 1 行定义一个包,包名为 main,main 是可执行程序的包名,所有的 Go 源程序文件头部必须有一个包声明语句,Go 通过包来管理命名空间
  • 第 3 行 import 引用一个外部包 fmt,可以是标准库的包,也可以是第三方或自定义的包, fmt 是标准输入/输出包
  • 第 5 行使用 func 关键字声明定义一个函数,函数名为 main,main 代表 Go 程序入口函数。
  • 第 6 行调用 fmt 包里面的 Printf 函数,函数实参是一个字符串字面量,在标准输出里面打印一句话“Hello, world. 你好,世界!”,\n 是一个转义符,表示换行。

  Go 源代码的特征解读

  • 源程序以 .go 为后缀。
  • 源程序默认为 UTF-8 编码。
  • 标识符区分大小写。
  • 语句结尾的分号可以省略。
  • 函数以 func 开通,函数体开头的“{”必须在函数头所在的行尾部,不能单独起一行。
  • 字符串字面量使用“""”(双引号)括起来。
  • 调用包里面的方法通过点“.”访问符。
  • main 函数所在的包名必须是 mian。

  编译运行

go build hello.go

// 运行
./hello
Hello, world. 你好,世界!

1.3 Go 词法单元

  • token
  • 关键字
  • 标识符
  • 操作符
  • 分隔符
  • 字面量

1.3.1 token

  token 是构成源程序的基本不可再分割的单元。编译器编译源程序的第一步就是讲源程序分割成一个个独立的 token,这个过程就是词法分析。

  Go 语言的 token 分为关键字、标识符、操作符、分隔符和字面量等。

  • token

    • identifers(标识符)
      • Predeclard identifers(预声明标识符)
        • keywords(关键字)
        • 其他预声明标识符
          • 内置数据类型标识符
          • 常量值标识符
          • 空白标识符
          • 内置函数
      • 用户自定义标识符
        • 包名
        • 变量名
        • 常量名
        • 函数名
        • 自定义类型名
        • 方法名
    • operators and delimiters(操作符和分隔符)
      • 操作符 (不仅起到分割 token 的作用,本身也是 token)
      • 纯分隔符(仅仅起到分割 token 的作用,本身不是 token)
        • 空格
        • 制表符
        • 回车
        • 换行
    • literals(字面常量)
  • 操作符:即是分隔符,也是 token

// “:=”和“+”即是分隔符,也是 token
sum:=a+b
  • 纯分隔符:空格、制表符、换行符和回车符

1.3.2 标识符

  编程语言的标识符用来标识变量、类型、常量等语法对象的符号名称,在语法分析时作为一个 token 存在。

  • 预定义标识符(语言设计者预留的标识符)(65 个)
    • 关键字(keywords)(25 个)
      • 引导程序结构的 8 个关键字
      • 声明复合数据结构的 4 个关键字
        • struct
        • interface
        • map
        • chan
      • 控制程序结构的 13 个关键字
    • 内置数据类型标识符(20 个)
      • 数值(16 个)
        • 整形(12 个)
        • 浮点型(2 个)
          • float32
          • float64
        • 复数型(2 个)
          • complex64
          • complex128
      • 字符和字符串型(2 个)
        • rune
        • string
      • 接口型(1 个)
        • error
      • 布尔型(1 个)
        • bool
    • 内置函数(15 个)
    • 常量标识符(4 个)
      • true false
      • iota
      • nil
    • 空白标识符(1 个)
      • _
  • 用户自定义标识符(编程者自定义的标识符)
    • 包名
    • 变量名
    • 常量名
    • 函数名
    • 自定义类型名
    • 方法名

  用户自定义标识符不应该使用预定义标识符,导致歧义,影响代码可读性

  Go 的标识符构成规则:开头一个字符必须是字母或下划线,后面跟任意多个字符、数字或下划线,并且区分大小写,Unicode字符也可以作为标识符的构成,但是一般不推荐这么使用。

  Go 语言预声明的标识符包括关键字、内置数据类型标识符、常量值标识符、内置函数和空白标识符。

  关键字(keywords)(25个)

  编程语言里面的关键字是指语言设计者保留的特定语法含义的标识符,这些关键字有自己独特的用途和语法含义,它们一般用来控制程序结构,每个关键字都代表不同语言的语法糖。

  Go 语言关键字

break
default
func
interface
select
case
defer
go
map
struct
const
fallthrough
if
range
type
continue
for
import
return
var
  • 引导程序结构的8个关键字
package // 定义包名关键字
import  // 导入包名关键字
const   // 常量声明关键字
var     // 常量声明关键字
func    // 函数声明关键字
defer   // 延迟执行关键字
go      // 并发语法糖关键字
return  // 函数返回关键字
  • 声明复合数据结构的4个关键字
struct      // 定义结构类型关键字
interface   // 定义接口类型关键字
map         // 声明或创建map类型关键字
chan        // 声明或创建通道类型关键字
  • 控制程序结构的13个关键字
if else                                     // if else 语句关键字
for range break continue                    // for循环使用的关键字
switch select type case default fallthrough // switch 和 select 语句使用的关键字
goto                                        // goto跳转语句关键字

  内置数据类型标识符(20个)

数值(16)个
    整型(12个)
        byte int int8 int16 int32 int64
        uint uint8 uint16 uint32 uint64 uintptr
    
    浮点型(2个)
        float32 float64
    
    复数型(2个)
        complex64 complex128
    
字符和字符串型(2个)
    string rune
    
接口型(1个)
    error

布尔型(1个)
    bool

  Go 是一种强类型静态编译型语言,在定义变量和常量是需要显式地指出数据类型,当然 Go 也支持自动类型推导,在声明初始化内置类型变量时,Go 可以自动地进行类型推导。但是,在定义新类型或函数时,必须显式地带上类型标识符。

  内置函数(15个)

make
new
len
cap
append
copy
delete
panic
recover
close
complex
real
image
print
println

  内置函数也是高级语言的一种语法糖,由于其是语言内置的,不需要用import引入,内置函数具有全局可见性。

  常量标识符(4个)

true false  // true 和 false 表示 bool 类型的两个常量值:真和假
iota        // 用在连续的枚举类型的声明中
nil         // 指针/引用型的变量的默认值就是nil

  空白标识符(1个)

_

  空白标识符有特殊的含义,用来声明一个匿名的变量,该变量在赋值表达式的左端,空白标识符引用通常被用作占位,比如忽略函数多个返回值中的一个和强制编译器做类型检查。

1.3.3 操作符( operators )和分隔符( delimiters )

  操作符就是语言所使用的符号集合,包括运算符、显式的分隔符,以及其他语法辅助富豪。
  Go 语言一共使用了 47 个操作符:

+   &   +=  &=  &&  ==  !=  (   )
-   |   -=  |=  ||  <   <=  [   ]
*   ^   *=  ^=  <-  >   >=  {   }
/   <<  /=  <<= ++  =   :=  ,   ;
%   >>  %=  >>= --  !   ... .   :
    &^      &^=
  • 算术运算符(5个)
+ - * / %
  • 位运算符(6个)
& | ^ &^ >> <<
  • 赋值和赋值复核运算符(13个)
:= = += -= *= /= %= &= |= ^= &^= >>= <<=
  • 比较运算符(6个)
> >= < <= == !=
  • 括号(6个)
( ) [ ] { }
  • 逻辑运算符(3个)
&& || !
  • 自增自减(2个)
++ --

注意: Go 语言里面自增、自减操作符是语句而不是表达式。

  • 其他运算符(6个)
: , ; . ... <-

1.3.4 字面常量

  编程语言源程序中表示固定值的符号叫作字面常量,简称字面量。

  Go 的字面量可以出现在两个地方:

  • 用于常量和变量的初始化
  • 用在表达式里或作为函数调用实参

  字面量有如下几类:

  • 整形字面量( Integer literals )
42
0600
0xBadFace
1701411
  • 浮点型字面量( Floating-point Literals )
0.
72.43
072.43
1.e+0
1E6
.25
  • 复数类型字面量( Imaginary Literals )
0i
1.e+0i
1E6i
.25i
  • 字符型字面量( Rune Literals )
    Go 的源码采用的是 UTF-8 的编码方式, UTF-8 的字符占用字节数可以有 1~4 个字节, Rune 字符常量也有多种表现形式,但是使用“’’”(单引号)将其括住。
'a'
'本'
'\t'
'\000'
'\x07'
  • 字符串字面量( String Literals )
"\n"
"\""
"Hello, world!\n"
"中国人"

1.3.5 总结

   Go 源程序的基本构成

  • 关键字引导程序的基本结构
  • 内置类型标识符辅助声明变量和常量
  • 字面量辅助变量和常量的初始化
  • 分隔符帮助 Go 编译器识别各个 token
  • 操作符和变量、关键字一起构成丰富的语法单元

1.4 变量和常量

  高级语言通过一个标识符来绑定一块特定的内存,后续对特定的内存的操作都可以使用该标识符来代替。

  分为两类:

  • 变量:表示指向的内存可以被修改
  • 常量:表示指向的内存不能被修改

1.4.1 变量

  变量:使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址里面存放的内容可以改变

变量声明

  1.显式的完整声明

var varName dataType [ = value ]

  说明

  • 关键字 var 用于变量声明。
  • varName 是变量名标识符。
  • dataType 是基本类型。
  • value 是变量的初始值,初始值可以使字面量,也可以是其他变量名,还可以是表达式;如果不指定初始值,则 Go 默认将该变量的初始值为类型的零值。
var a int = 1
var a int = 2 * 3
var a int = b

  2.短类型声明

varName := value

  说明:

  • := 声明只能出现在函数内(包括在方法内)。
  • 此时 Go 编译器自动进行数据类型推断。

  Go 支持多个类型变量同时声明并赋值。

a, b := 1, "hello"
变量属性
  • 变量名:自定义标识符规则
  • 变量值:
  • 变量存储和生存期:Go 语言提供自动内存管理,编译器使用栈逃逸技术能够自动为变量分配空间:可能在栈上,也可能在堆上。
  • 类型信息:
  • 可见性和作用域:

1.4.2 常量

  常量:使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义常量时指定的类型决定,而且该内存地址里面存放的内容不可以改变

  常量类型:

  • 布尔型
  • 字符串型
  • 数值型
const (
    c0 = iota   // c0 == 0
    c1 = iota   // c1 == 1
    c2 = iota   // c2 == 2
)

const (
    c0 = iota   // c0 == 0
    c1          // c1 == 1
    c2          // c2 == 2
)

const (
    c0 = iota   // c0 == 0
    c1          // c1 == 1
    c2 = 1      // c2 == 1 (iota == 2, unused)
    c2          // c3 == 3
)

// 分开的 const 语句,iota 每次都从 0 开始
const x = iota   // x == 0
const y = iota   // y == 0

1.5 基本数据类型

  Go 语言内置七类基本数据类型(20个具体子类型)

  • 布尔类型: bool
  • 整形: byte int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
  • 浮点型: float32 float64
  • 复数: complex64 complex128
  • 字符: rune
  • 字符串: string
  • 错误类型: error

1.5.1 布尔类型(1 种)

  • 关键字:bool
  • 值:true 和 false
  • 声明的布尔型变量如不指定初始化值,则默认为false。

1.5.2 整型 (12 种)

byte int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
  • 不同类型的整型必须进行强制类型转换
  • 整型支持算术运算和位操作,算术表达式和位操作表达式的结果还是整型

1.5.3 浮点型(2 种)

  • 关键字:float32 float64
  • 浮点数字面量被自动类型推断为 float64 类型。
  • 计算机很难进行浮点数的精确表示和存储,因此浮点数之间不应该使用 == 或 != 进行比较操作,高精度科学计算应该使用 math 标准库。

1.5.4 复数类型(2️种)

  • 关键字:complex64 complex128
  • 内置函数:complex real image
var value1 complex64 = 3.1 + 5i
value2 := 2.1 + 6i

var v = complex(2.1, 3) // 构造一个复数
a := real(v)            // 返回复数实部
b := image(v)           // 返回复数虚部

1.5.5 字符串

  • 字符串是常量,可以通过类似数组的缩影访问其字节单元,但是不能修改某个字节的值
var a = "hello, world"
b := a[0]
a[1] = 'a' // error
  • 字符串转换为切片 []byte(s) 要慎用,尤其是当数据量较大时(没转换一次都需要复制内容)。
a := "hello, world"
b := []byte[a]
  • 字符串尾部不包含 NULL 字符,这一点和 C/C++ 不一样
  • 字符串类型底层实现是一个二元的数据结构,一个是指针指向字节数组的起点,另一个是长度。
// runtime/string.go

type stringStruct struct {
    str unsafe.Pointer  // 指向底层字节数组的指针
    len int             // 字节数组长度
}
  • 基于字符串创建的切片和原字符串指向相同的底层字符数组,一样不能修改,对字符串的切片操作返回的子串仍然是 string,而非 slice
  • 字符串和切片的转换:字符串可以转换为字节数组,也可以转换为 Unicode 的字数组。
a := "hello, 世界!"
b := []byte(a)
c := []rune(a)
  • 字符串的运算
a := "hello"
b := "world"

c := a + b  // 字符串拼接
len(a)      // 内置的 len 函数获取字符串长度

d := "hello, 世界!"

for i := 0; i < len(d); i++ {   // 遍历字节数组
    fmt.Println(d[i])
}

for i, v = range d {        // 遍历 rune 数组
    fmt.Println(i, v)
}

1.5.6 rune 类型

  Go 内置两种字符类型

  • byte 的字节类型( byte 是 uint 的别名)
  • Unicode 编码的字符 rune ( rune 是 int32 的别名 )
  • Go 语言默认的字符编码是 UTF-8,使用 Unicode/UTF-8 标准包进行编码转换

1.6 复合数据类型

  • 指针
  • 数组
  • 切片
  • 字典
  • 通道
  • 结构
  • 接口
* pointerType
[n] elementType
[] elementType
map [keyType]valueType
chan valueType

struct {
    filedName filedType
    filedName filedType
    ...
}

interface {
    method1(inputParams) (returnParams)
    method2(inputParams) (returnParams)
    ...
}

1.6.1 指针

  • 类型声明:*T
  • 支持多级指针:**T
  • 变量名谦加&来获取变量的地址

  1)在赋值语句中

  • *T 出现在“=”左边表示指针声明。
  • *T 出现在“=”右边表示取指针指向的值。
var a = 11
p := &a // *p 和 a 的值都是 11

fmt.Println(a)  // 11
fmt.Println(*p) // 11

  2)结构体指针访问结构体自动仍然使用“.”点操作符,Go 语言没有“->”操作符。

type User struct {
    name string
    age  int
}

andes := User{
    name: "andes",
    age:  18,
}

p := &andes

fmt.Println(p.name) // p.name 通过“.”操作符访问成员变量

  3)Go 不支持指针的运算。

  • Go 由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来更多不便,在 C 和 C++ 里面指针运算很容易出现问题,因此 Go 直接在语言层面禁止指针运算。
    ✅ ❎
a := 11
p := &a

p++ // ❎ 不允许,报 non-numeric type *int 错误  

  4)函数允许返回局部变量的地址。

  • Go 编译器使用“栈逃逸”机制将这种局部变量的空间分配在堆上。
func sum (a, b int) *int {
    sum := a + b
    return &sum // ✅ 允许,sum 会分配在 heap (堆)上
}

1.6.2 数组

  • 类型名:[n]elementType
  • n:数组长度
  • elementType:数组元素类型

  数组初始化

a := [3]int{1, 2, 3}    // 指定长度和初始化字面量
a := [...]int{1, 2, 3}  // 不指定长度,但是由后面的初始化列表数量来确定其长度
a := [3]int{1:1, 2:3}   // 指定总长度,并通过索引值进行初始化,没有指定索引的元素被初始化为类型的零值
a := [...]int{1:1, 2:3} //不指定总长度,通过索引值进行初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值

  数组的特点

  1. 数组创建完长度就固定了,不可以再追加元素
  2. 数组是值类型,数组赋值或作为函数参数都是值拷贝
  3. 数组长度是数组类型的组成部分,[10]int 和 [20]int 表示不同的类型。
  4. 可以根据数组创建切片

  数组相关操作

  1)数组元素访问

a := [...]int{1, 2, 3}
b := a[0]
fmt.Println(b)
for i, v := range a {
	fmt.Println("i=", i, ",v=", v)
}

// 输出结果
1
i= 0 ,v= 1
i= 1 ,v= 2
i= 2 ,v= 3

  2)数组长度

a := [...]int{1, 2, 3}
alengh := len(a)

for i := 0; i < alengh; i++ {
    fmt.Println(a[i])
}

// 输出结果
1
2
3

1.6.3 切片

  是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型

// src/runtime/slice.go

type slice struct {
    array   unsafe.Pointer // 指向数组的指针
    len     int // 切片的元素数量
    cap     int // 底层数组的容量
}

   Go 为切片维护三个元素

  • array:指向底层数组的指针
  • len:切片的元素数量
  • cap:底层数组的容量
array unsafe.Pointer
len=0
cap=0

  1)切片的创建

  • 由数组创建 array[b:e],其中 array 表示数组名; b 表示开始索引,可以不指定,默认是0;e 表示结束索引,可以不指定,默认是 len(array)。 array[b:e] 表示创建一个包含 e-b 个元素的切片,第一个元素是 array[b],最后一个元素是array[e-1]。
	var a0 = [...]int{0, 1, 2, 3, 4, 5, 6}

	s1 := a0[:]
	s2 := a0[0:4]
	s3 := a0[:4]
	s4 := a0[2:]

	fmt.Printf("a0 type is %T, len is %v, cap is %v, value is %v \n", a0, len(a0), cap(a0), a0)
	fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
	fmt.Printf("s2 type is %T, len is %v, cap is %v, value is %v \n", s2, len(s2), cap(s2), s2)
	fmt.Printf("s3 type is %T, len is %v, cap is %v, value is %v \n", s3, len(s3), cap(s3), s3)
	fmt.Printf("s4 type is %T, len is %v, cap is %v, value is %v \n", s4, len(s4), cap(s4), s4)

// 输出结果
a0 type is [7]int, len is 7, cap is 7, value is [0 1 2 3 4 5 6] 
s1 type is []int, len is 7, cap is 7, value is [0 1 2 3 4 5 6] 
s2 type is []int, len is 4, cap is 7, value is [0 1 2 3] 
s3 type is []int, len is 4, cap is 7, value is [0 1 2 3] 
s4 type is []int, len is 5, cap is 5, value is [2 3 4 5 6] 
  • 通过内置函数 make 创建切片:make([]elementType, len, cap),cap 可以不指定,默认等于 len。
    注意:由 make 创建的切片各元素被默认初始化为切片元素类型的零值。
	s1 := make([]int, 10)
	s2 := make([]int, 10, 15)
	
	fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
	fmt.Printf("s2 type is %T, len is %v, cap is %v, value is %v \n", s2, len(s2), cap(s2), s2)

// 输出结果	
s1 type is []int, len is 10, cap is 10, value is [0 0 0 0 0 0 0 0 0 0] 
s2 type is []int, len is 10, cap is 15, value is [0 0 0 0 0 0 0 0 0 0] 

注意: 直接声明切片类型变量是没有意义的。

    var s1 []int
    fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
  
// 输出结果	  
s1 type is []int, len is 0, cap is 0, value is [] 

此时切片 a 的底层的数据结构如下:

array=nil
len=0
cap=0

  2) 切片支持的操作

  • 内置函数 len() 返回切片长度
  • 内置函数 cap() 返回切片底层数组容量
  • 内置函数 append() 对切片追加元素
  • 内置函数 copy() 用于复制一个切片
	a := [...]int{0, 1, 2, 3, 4, 5, 6}
	s1 := make([]int, 2, 4)
	s2 := a[0:3]
	s3 := make([]int, 2, 2)

	fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
	fmt.Printf("s2 type is %T, len is %v, cap is %v, value is %v \n", s2, len(s2), cap(s2), s2)
	fmt.Printf("s3 type is %T, len is %v, cap is %v, value is %v \n", s3, len(s3), cap(s3), s3)
	s1 = append(s1, 1)
	fmt.Println("after append 1 ")
	fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
	s1 = append(s1, s2...) // 底层数组发生扩展
	fmt.Println("after append s2 ")
	fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
	copy(s3, s2) // copy 只会复制 d 和 c 中长度最小的
	fmt.Printf("s3 type is %T, len is %v, cap is %v, value is %v \n", s3, len(s3), cap(s3), s3)

// 输出结果	
s1 type is []int, len is 2, cap is 4, value is [0 0] 
s2 type is []int, len is 3, cap is 7, value is [0 1 2] 
s3 type is []int, len is 2, cap is 2, value is [0 0] 
after append 1 
s1 type is []int, len is 3, cap is 4, value is [0 0 1] 
after append s2 
s1 type is []int, len is 6, cap is 8, value is [0 0 1 0 1 2] 
s3 type is []int, len is 2, cap is 2, value is [0 1] 

  3) 字符串和切片的相关转换

	str := "hello, 世界!"  // 通过字符串字面量初始化一个字符串 str
	s1 := []byte(str)       // 将字符串转换为 []byte 类型切片
	s2 := []rune(str)       // 将字符串转换为 []rune 类型切片

	fmt.Printf("str type is %T, len is %v, value is %v \n", str, len(str), str)
	fmt.Printf("s1 type is %T, len is %v, cap is %v, value is %v \n", s1, len(s1), cap(s1), s1)
	fmt.Printf("s2 type is %T, len is %v, cap is %v, value is %v \n", s2, len(s2), cap(s2), s2)

// 输出结果	
str type is string, len is 16, value is hello, 世界! 
s1 type is []uint8, len is 16, cap is 16, value is [104 101 108 108 111 44 32 228 184 150 231 149 140 239 188 129] 
s2 type is []int32, len is 10, cap is 12, value is [104 101 108 108 111 44 32 19990 30028 65281] 

1.6.4 map

  内置的字典类型

  • 类型格式:map[K]T
  • K 可以是任意可以进行比较的类型
  • T 是值类型
  • map 是一种引用类型

1)map 的创建

  • 使用字面量创建。
ma := map[string]int{"a": 1, "b": 2}
fmt.Println(ma["a"]) // 1
fmt.Println(ma["b"]) // 2
  • 使用内置的 make 函数创建
make(map[K]T)       // map 的容量使用默认值
make(map[K]T, len) // map 的容量使用给定的 len 值

	mp1 := make(map[int]string)
	mp2 := make(map[int]string, 3)
	mp1[1] = "tom"
	mp1[3] = "kitty"
	mp2[1] = "pony"
	mp2[4] = "hello"
	mp2[5] = "hello"
	mp2[6] = "hello"
	mp2[7] = "hello"

	fmt.Printf("mp1 type is %T, len is %v, value is %v \n", mp1, len(mp1), mp1)
	fmt.Printf("mp2 type is %T, len is %v, value is %v \n", mp2, len(mp2), mp2)

// 输出结果	
mp1 type is map[int]string, len is 2, value is map[1:tom 3:kitty] 
mp2 type is map[int]string, len is 5, value is map[1:pony 4:hello 5:hello 6:hello 7:hello] 

2)map 支持的操作

  • map 的单个健值访问格式为 mapName[key],更新某个 key 的值时 mapName[key]放到等号左边,访问某个 key 的值时 mapName[key] 放到等号的右边。
  • 可以使用 range 遍历一个 map 类型变量,但是不保证每次迭代元素的顺序
  • 删除 map 中的某个健值,使用如下语法:delete(mapName, key)。delete 是内置函数,用来删除 map 中的某个键值对。
  • 可以使用内置的 len() 函数返回 map 中的键值对数量。
mp := make(map[int]string)
mp[1] = "tom"
mp[2] = "pony"
mp[3] = "jaky"
mp[1] = "andes"
delete(mp, 3)

fmt.Println(mp[1])
fmt.Println(len(mp))

for k, v := range mp { // range 支持遍历mp,但不保证每次遍历次序是一样的
    fmt.Println("key=", k, "value=", v)
}

// 输出结果	
andes
2
key= 2 value= pony
key= 1 value= andes

注意

  • Go 内置的 map 不是并发安全的,并发安全的 map 可以使用标准包 sync 中的 map
  • 不要直接修改 map value 内某个元素的值,如果向修改 map 的某个健值,则必须整体赋值。
type User struct {
    name string
    age  int
}

ma := make(map[int]User)
andes := User{
    name: "andes",
    age: 18,
}

ma[1] = andes
// ma[1].age = 19 //  ❎ ERROR, 不能通过 map 引用直接修改
andes.age = 19

ma[1] = andes // 必须整体替换 value
fmt.Printf("%v\n", ma)

1.6.5 struct

  Go 中的 struct 类型和 C 类似,表示结构,由多个不同类型元素组合而成。

  • struct 结构中的类型可以使任意类型
  • struct 的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求

  1)struct 类型字面量

struct {
    FieldName   FieldType
    FieldName   FieldType
    FieldName   FieldType
}

  2) 自定义 struct 类型

type TypeName struct {
    FieldName   FieldType
    FieldName   FieldType
    FieldName   FieldType
}

  3) struct 类型变量的初始化

type Person struct {
    Name string
    Age  int
}

type Student struct {
    *Person
    Number int
}

// 按照类型声明顺序,逐个赋值
// 不推荐这种初始化方式,一旦 struct 增加字段,则整个初始化语句会报错
a := Person{"Tom", 21}

p := &Persion{
    Name: "tata",
    Age: 12,
}

s := Student {
    Persion: p,
    Number: 110,
}

1.7 控制结构

  • 程序指令都是线性地存放在存储器上。
  • 程序执行从本质上就是两种模式:顺序和跳转
  • 顺序:按照程序指令在存储器上的存放顺序逐条执行。
  • 跳转:遇到跳转指令就跳转到某处继续线性执行。
    • 无条件跳转:函数调用和 goto 语句
    • 有条件跳转:分支(if、switch、select)和循环(for)

  备注

  Go 的源代码的顺序并不一定是编译后最终可执行程序的指令顺序,涉及语言的运行时和包的加载过程。

1.7.1 if 语句

  特点

  • if 后面的条件判断子句不需要用小括号括起来。
  • {必须放在行尾,和 if 或 if else 放在一起。
  • if 后面可以带一个简单的初始化语句,并以分号分割,该简单语句声明的变量作用域是整个 if 语句块,包括后面的 else if 和 else 分支
  • Go 语言没有条件运算符(a > b ? a : b),这也符合 Go 的设计哲学,只提供一种方法做事情。
  • if 分支语句遇到 return 后直接返回,遇到 break 则跳过 break 下方的 if 语句块。
if x := f(); x < y { // 初始化语句中声明变量 x
    return x
} else if  x > z { // x 在 else if 里面一样可以被访问
    return z
} else {
    return y
}

1.7.2 switch 语句

  switch 语句会根据传入的参数检测并执行符合条件的分支。

   语法特点如下:

  • switch 和 if 语句一样,switch 后面可以带一个可选的简单的初始化语句,该简单语句声明的变量作用域是整个 switch 语句块
  • switch 后面的表达式也是可选的,如果没有表达式,则 case 子句是一个布尔表达式,而不是一个值,此时就相当于多重 if else 语句。
  • switch 条件表达式的值不像 C 语言那样必须限制为整数,可以是任意支持相等比较运算的类型变量。
  • 通过 fallthrough 语句来强制执行下一个 case 语句(不再判断下一个 case 子句的条件是否满足)。
  • switch 支持 default 语句,当所有 case 分支都不符合时,执行 default 语句,并且 default 语句可以放到任意位置,并不影响 switch 的判断逻辑。
  • switch 和 .(type)结合可以进行类型的查询。
switch i := "y"; i {
case "y", "Y": // 多个 case 值使用逗号分隔
    fmt.Println("yes")
    fallthrough // fallthrough 会跳过接下来的 case 条件表达式,直接执行下一个 case yuju

case "n", "N":
    fmt.Println("no")
}

// 输出 
// yes
// no

score := 85
grade := ' '

if score >= 90 {
    grade = 'A'
} else if score >= 80 {
    grade = 'B'
} else if score >= 70 {
    grade = 'C'
} else if score >= 60 {
    grade = 'D'
} else {
    grade = 'E'
}
fmt.Printf("grade=%c\n", grade)

switch {
default:
    grade = 'E'
case score >= 90:
    grade = 'A'
case score >= 80:
    grade = 'B'  
case score >= 70:
    grade = 'C'
case score >= 60:
    grade = 'D'
}

fmt.Printf("grade=%c\n", grade)
// grade = B

1.7.3 for 语句

  三种场景:

  • 类似 C 里面的 for 循环语句
for init; condition; post { }
  • 类似 C 里面的 while 循环语句
for condition { }
  • 类似 C 里面的 while(1)死循环语句
for { }
  • for 访问数组、切片、字符串、map 和通道
// map
for key, value := range map { }
for key := range map { }

// array
for index, value := range arry { }
for index := range arry { }
for _, value := range arry { }

// slice
for index, value := range slice { }
for index := range slice { }
for _, value := range slice { }

// channel
for value := range channle { }

1.7.4 标签和跳转

  标签

  Go 语言使用标签(Lable)来标识一个语句的位置,用于 goto、break、continue 语句的跳转,标签的语法是:

Lable: Statement

  goto

   goto 语句用于函数的内部跳转,需要配合标签一起使用

goto Lable
  • goto 语句只能在函数内部跳转
  • goto 语句不能跳过内部变量声明语句,这些变量在 goto 语句的标签语句处又是可见的。
goto L // ❎ BAD,跳过v := 3 这条语句是不允许的
v := 3
L:
  • goto 语句只能跳到同级作用域或者上层作用域内,不能跳转到内部作用域内。
if n % 2 == 1 {
    goto L1
}
for n > 0 {
    f()
    n--
L1 :
    f()
    n--
}

  break

  用于函数内跳出 for、switch、select 语句的执行

  • 单独使用,用于跳出 break 当前所在的 for、switch、select 语句的执行。
  • 和标签一起使用,用于跳出标签所标识的 for、switch、select 语句的执行,可用于跳出多重循环,但标签和 break 必须在同一个函数内
L1:

for i := 0; ; i++ {
    for j := 0; ; j++ {
        if i >=5 {
            // 跳出 L1 标签所在的 for 循环
            break L1
        }
        if j > 10 {
            // 默认跳出离 break 最近的内层循环
            break
        }
    }
}

  continue

  用于跳出 for 循环的本次迭代,跳到 for 循环的下一次迭代的 post 语句处执行

  • 单独使用,用于跳出 continue 当前的 for 循环的本次迭代。
  • 和标签一起使用,用于跳出标签所标识的 for 语句的本次迭代,但标签和 continue 必须在同一个函数内
L1:

for i := 0; ; i++ {
    for j := 0; ; j++ {
        if i >=5 {
            // 跳到 L1 标签所在的 for 循环 i++ 处执行
            continue L1
        }
        if j > 10 {
            // 默认仅跳到离 break 最近的内层循环 j++ 处执行
            continue
        }
    }
}

return 和函数调用

return 语句也能引发控制流程的跳转,用户函数和方法的推出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值