go 学习 ,二:基本语法

一、数据类型

    • 布尔型:布尔型的值只可以是常量 true 或者 false。eg:var bo bool = true。布尔型无法参与数值运算,也无法与其他类型进行转换
    • 数字类型:整型 int 、浮点型 float32、float64。
    • 字符串类型:字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。

派生类型:

    • 指针类型(Pointer)
    • 数组类型
    • 结构化类型(struct)
    • Channel 类型
    • 函数类型
    • 切片类型
    • 接口类型(interface)
    • Map 类型

a、数字类型

  • 整数

    • 无符号整数类型:

      • uint8:无符号 8 位整型 (0 到 255)

      • uint16:无符号 16 位整型 (0 到 65535)
      • uint32:无符号 32 位整型 (0 到 4294967295)
      • uint64:无符号 64 位整型 (0 到 18446744073709551615)
    • 有符号整数类型:
      • int8:有符号 8 位整型 (-128 到 127)
      • int16:有符号 16 位整型 (-32768 到 32767)
      • int32:有符号 32 位整型 (-2147483648 到 2147483647)
      • int64:有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

    注:

      int 表示有符号整数,uint 表示无符号整数;int 和 uint 所能表示的整数大小根据计算机硬件和编译器不同,会在 32bit 或 64bit 之间变化。

      Unicode 字符的 rune 类型和 int32 类型是等价的,byte 和 uint8 也是等价类型。

      另外还有 无符号的整数类型 uintptr,它没有指定具体的 bit 大小但足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

  • 浮点数

    • float32:常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;最小值为 1.4e-45 ;可提供大约 6 个十进制数的精度。
    • float64:常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;最小值为 4.9e-324 ;可提供大约 15 个十进制数的精度,应优先使用 float64 类型
  • 复数

    • complex64(32 位实数和虚数)
    • complex128(64 位实数和虚数)

    复数使用 re+imi 来表示,其中 re 代表实数部分,im 代表虚数部分,i 代表根号负 1;eg:-1+0i

    内建的 real 和 imag 函数分别返回复数的实部和虚部

b、字符串类型

  • 转义字符:
    • \n:换行符
    • \r:回车符
    • \t:tab 键
    • \u 或 \U:Unicode 字符
    • \\:反斜杠自身
  • 索引访问:
    • 字符串 str 的第 1 个字节:str[0]
    • 第 i 个字节:str[i - 1]
    • 最后 1 个字节:str[len(str)-1]

  注意:获取字符串中某个字节的地址属于非法行为,例如 &str[i]。

  • 字符串拼接符:”+“ 直接连接字符串
  • 定义多行字符串: “ `` ”
  • len(): 表示字符串的 ASCII 字符个数或字节长度 ,返回一个 int 整型
  • 字符串遍历:
    • ASCII 字符串遍历直接使用下标。
    • Unicode 字符串遍历用 for range。
  • 字符串截取使用类似python的切片存在,利用索引截取:s = str[start:end]
  • 格式化操作符:
    • %v 按值的本来值输出
    • %+v 在 %v 基础上,对结构体字段名和值进行展开
    • %#v 输出 Go 语言语法格式的值
    • %T 输出 Go 语言语法格式的类型和值
    • %% 输出 % 本体
    • %b 整型以二进制方式显示
    • %o 整型以八进制方式显示
    • %d 整型以十进制方式显示

    • %x 整型以十六进制方式显示

    • %X 整型以十六进制、字母大写方式显示

    • %U Unicode 字符

    • %f 浮点数

    • %p 指针,十六进制方式显示

c、字符类型

  • uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。byte 类型是 uint8 的别名
  • rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。
  • Unicode包的内置函数:
    • 判断是否为字母:unicode.IsLetter(ch)
    • 判断是否为数字:unicode.IsDigit(ch)
    • 判断是否为空白符号:unicode.IsSpace(ch)

二、变量 & 常量

变量

// 第一种声明变量方式: 指定变量类型,进行初始化或使用默认值
var boolean1 bool       // 声明单个变量
var num1, num2 int     // 声明多个相同类型的变量

​
// 第二种声明变量方式: 
var num3 int = 5
var num3 = 5       // 系统根据 值 自行判定变量类型


// 第三种声明变量方式: 省略 var,若 := 左侧未声明新的变量,则产生编译错误
str1 := "u_u"
str1, str2, str3 := "-_-", "^_^", "8_8"

注:

  • 变量命名遵循小驼峰命名法
  • := 只能用于函数内部定义变量,且使用 := 时必须确保此变量在此之前从未声明使用过,var 一般用于定义全局变量
  • _ 是一个特殊的变量名,任何赋予它的值都会被丢弃
  • 声明的局部变量必须在其代码块中使用,全局变量可不必
  • reflect.TypeOf(variable):可获取变量 variable 的类型

常量

// 定义语法:  const name [type] = value

const pi = 3.14159      // const 定义常量时,必须要有初始值

const Pi float32 = 3.1415926

const (
    e  = 2.7182818
    pi = 3.1415926
)

// 常量使用关键字 const 定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。
// 常量的值必须是能够在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。

iota 枚举

// iota关键字通常用来声明enum的时候用,它默认开始值是0,每调用一次+1.

const (
    first = iota
    second
    third
    fourth = "power"
    fifth
    sixth = 22
    seventh
    eighth = iota
    ninth
    tenth
)

// [Output]: 0 1 2 power power 22 22 7 8 9

三、运算符

算术运算符描述
+相加
-相减
*相乘
/相除
%求余
++自增
--自减
关系运算符描述
==检查两个值是否相等,如果相等返回 True 否则返回 False。
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。
>检查左边值是否大于右边值,如果是返回 True 否则返回 False。
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。
逻辑运算符描述
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。
||逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。
!逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。
赋值运算符描述
=简单的赋值运算符,将一个表达式的值赋给一个左值
+=相加后再赋值
-=相减后再赋值
*=相乘后再赋值
/=相除后再赋值
%=求余后再赋值
<<=左移后赋值
>>=右移后赋值
&=按位与后赋值
^=按位异或后赋值
|=按位或后赋值
其他运算符描述
&返回变量存储地址
*指针变量。

关键字

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

标识符 

appendboolbytecapclosecomplexcomplex64complex128uint16
copyfalsefloat32float64imagintint8int16uint32
int32int64iotalenmakenewnilpanicuint64
printprintlnrealrecoverstringtrueuintuint8uintptr

四、分支循环语句

分支语句

// if 语句
if condition {        // condition 为 布尔类型
  // do something   
} 



// if-else 语句
if condition {      // if 为真即成立,执行 if代码块;否则执行 else代码块
  // do something 
} else { 
  // do something 
} 


// if-else if-else 语句
if condition1 {
    // do something
} else if condition2 {
    // do something else
} else {
    // catch-all or default
}


// if 的特殊姿势
if err := Connect(); err != nil {      // Connect() 为带有返回的函数,此条件先获取函数的返回值,再根据这个返回值进行判断
  fmt.Println(err) 
  return 
} 


// switch 语句
var a = "hello"
switch a {
case "hello":
    fmt.Println(1)
case "world":
    fmt.Println(2)
default:
    fmt.Println(0)
} 

 // 一分支多值

var a = "mum"
switch a {
case "mum", "daddy":
    fmt.Println("family")
}

// 分支表达式
var r int = 11
switch {
case r > 10 && r < 20:
    fmt.Println(r)
}
注意:
每个 switch 只能有一个 default 分支
  switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。

  switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。

switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。


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

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

Go 没有三目运算符,所以不支持 ?: 形式的条件判断

循环语句

// for 循环
for index, elem := range nums {
  // do something
}
 
for {      // 死循环
  // do something 
}


// break & continue 语句 
for _, elem := range nums {
  switch elem{
  case "":
    contiue
  case "bye", "quit":
    break
  default:
    fmt.Println("Hello World")
  } 
}

 
// goto 语句: 无条件地转移到代码中 goto语句 指定的标签中 执行标签内代码
func main() {

    for x := 0; x < 10; x++ {

        for y := 0; y < 10; y++ {

            if y == 2 {
                // 跳转到标签
                goto breakHere
            }

        }
    }

    // 手动返回, 避免执行进入标签
    return

    // 标签
breakHere:
    fmt.Println("done")
}

五、数组

  • go 的数组内所有元素都是同一类型、是固定长度的有序集合
  • 声明语法: var aryName [arySize]elemType
  • 相同类型的两个数组支持 “!=” 和 “==” 比较,但不能比较两个数组的大小
  • 数组的指针:*[3]int; 指针数组:[2]*int
// 声明数组
var ary[2] int

// 数组赋值
ary[0] = 11

// 初始化数组
var balanceSize = [5] float32{100, 2.0, 3.1, 5.5, 6.2}
var balanceSize = [...]float32{100, 2.0, 3.1, 5.5, 6.2}  // 可省略长度,`...`的方式,会自动根据元素个数来计算长度
// 声明多维数组并赋值
var secondly [2][3] int
for k:=0; k<len(secondly); k++ {
    for m:=0; m<len(secondly[k]); m++ {
        secondly[k][m] = k + m + 100
    }
}

// 初始化多维数组
var secondlyArray = [2][3] int{
    {1, 2, 3},
    {4, 5, 6},
}

// 索引访问数组元素
fmt.Println(balanceSize[0])

// 索引设置数组元素值
balanceSize[0] = 22.2

// 返回数组长度
fmt.Println(len(balanceSize))

六、切片

  • 切片的长度是可变的,声明时无需指定切片长度,可以使用数组的方式设置、访问元素、len 获取切片的长度
  • 额外的操作:
    • append() 追加元素,返回一个拥有新元素的新切片,append不会改变原切片,而是生成了一个新切片。
    • 取切片操作:slice[start:end];包含 start,而不包含end
    • 也可创建多维切片,拥有以上所有操作
  • 数组和切片的定义方式的区别在于[]之中是否有固定长度或者推断长度标志符...
// 声明切片: var sliceName []sliceType
var sliceTest [] int        // 第一种方式
sliceTest := make([]string, 3)    // 创建一个长度为3,存储字符串的切片


// 追加元素
sliceTest = append(sliceTest, "power")        
sliceTest = append(sliceTest, "top", "one")


// 取切片
newSlice := sliceTest[:2]    // 取前两个元素
newSlice := sliceTest[:]      // 取所有元素
newSlice := sliceTest[1:5]  // 取索引为1的元素到索引为4的元素


// 同时声明和初始化一个切片
t := []string{"g", "h", "i"}


// 创建二维切片
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
    innerLen := i + 1
    twoD[i] = make([]int, innerLen)
    for j := 0; j < innerLen; j++ {
        twoD[i][j] = i + j
    }
}
fmt.Println("2d: ", twoD)
}


// copy
var nilSlice []int
nilSlice = append(nilSlice, 3, 5, 21, 31, 18)   
copySlice := make([]int, len(nilSlice), (cap(nilSlice))*2)
copy(copySlice, nilSlice)

七、字典 map

// 声明 map :var variable map[keyType]valueType       
var mapVariable map[string]string        // 默认为 map[] nil


// make函数 定义map
mapVariable := make(map[string]string)    


// make 定义并初始化
test := map[string]float32{"a": 1, "b": 2, "c": 3}

// map 是一种引用,两个map指向同一底层,一个修改,另一个也变化
otherTest := test
otherTest["c"] = 6
fmt.Println("modify otherTest: ", otherTest)
fmt.Println("modify test: ", test)

// [Output]:modify otherTest:  map[a:1 b:2 c:6]
// [Output]:modify test:  map[a:1 b:2 c:6]


// 添加键值
mapVariable["s"] = "\u5bb6\u8431"
mapVariable["h"] = "\u99a5\u7504"
mapVariable["e"] = "\u5609\u6866"


// 遍历取出键值
for key, value := range mapVariable{
    fmt.Println(key, value)
}


// 判断是否存在某键
name, ok := mapVariable["s"]        // name:若key存在返回所对应的value,ok:此key是否存在(bool型)


// 删除键值
delete(map_variable1, "she")    // 若key不存在,则忽略
// key 对应 多个 value:切片

 mp1 := make(map[int][]int)

 mp2 := make(map[int]*[]int)

八、make & new

make

  • make用于内建类型(map、slice、channel)的内存分配
  • make(T,args)与new(T)有着不同的功能,make只能创建slice,map,channel,并且返回一个有初始值(非零)的T类型,而不是*T

new

  • new用于各种类型的内存分配【new返回指针
  • new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值(GO语言的术语:返回了一个指针,指向新分配的类型T的零值)

ending

  • 使用 new 创建的某个类型的对象,返回的是 内存地址;使用 make 创建的是三种类型之一的对象,返回的是带有初始值的value
  • new 负责分配内存,new(T) 返回 *T:代表的是 T类型的指针,是一个指向零值的 T 类型指针
  • make 负责初始化值,make(T) 返回初始化后的 T ,而非指针,make 仅适用于slice,map 和channel
// make & new 的区别

// make
slices := make([]int, 2)
fmt.Println("slices: ", slices)

maps := make(map[int]int)
fmt.Println("maps: ", maps)
// 【Output】:slices:  [0 0]
// 【Output】:maps:  map[]



// 声明结构体
type Info struct {
}


// new
num := new(int)
fmt.Println("num: ", num)

structVariable := new(Info)
fmt.Println("structVariable", structVariable)

//【Output】:num:  0xc00000a120
//【Output】:structVariable &{}




// make & new 区别的实例
type Foo struct {        // 声明结构体
   weight float64
   age int
}

// 声明
var foo1 Foo
fmt.Println("foo1: ", foo1)
foo1.age = 18
fmt.Println("foo1: ", foo1)

fmt.Println()

// 定义
foo2 := Foo{}
fmt.Println("foo2: ", foo2)
foo2.age = 19
fmt.Println("foo2: ", foo2)

fmt.Println()

// make 只能创建 channel、slice、map

// & 取址
foo3 := &Foo{}
fmt.Println("foo3: ", foo3)
fmt.Println("foo3: ", *foo3)
foo3.age = 20
fmt.Println("foo3: ", foo3)

fmt.Println()

// new
foo4 := new(Foo)
fmt.Println("foo4: ", foo4)
fmt.Println("foo4: ", *foo4)
foo4.age = 21
fmt.Println("foo4: ", foo4)

fmt.Println()

var foo5 *Foo = &Foo{}
fmt.Println("foo5: ", foo5)
fmt.Println("foo5: ", *foo5)
foo5.age = 22
fmt.Println("foo5: ", foo5)


// 以下是输出结果
foo1: {0 0}
foo1: {0 18}

foo2: {0 0}
foo2: {0 19}

foo3: &{0 0}
foo3: {0 0}
foo3: &{0 20}

foo4: &{0 0}
foo4: &{0 21}

foo5: &{0 0}
foo5: &{0 22}




注:foo1 & foo2 为相同的类型,输出的都是 结构体Foo类型的值 ==> {0 0} 由于没有给字段赋值,输出的都是初始值
   foo3 & foo4 & foo5 为相同的类型,输出的都是 结构体Foo类型的内存地址(指针) ==> &{0 0}  只有在前面加了 * 之后才是取到了他的值:*foo3、*foo4、*foo5