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、字符串类型

  • 转义字符:
    • :换行符
    • :回车符
    • :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。

  

赋值运算符描述
= 简单的赋值运算符,将一个表达式的值赋给一个左值
+= 相加后再赋值
-= 相减后再赋值
*= 相乘后再赋值
/= 相除后再赋值
%= 求余后再赋值
<<= 左移后赋值
>>= 右移后赋值
&= 按位与后赋值
^= 按位异或后赋值
|= 按位或后赋值
其他运算符描述
& 返回变量存储地址
* 指针变量。

关键字

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

标识符 

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

四、分支循环语句

分支语句

// 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 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 breakswitch 默认情况下 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"] = "u5bb6u8431"
mapVariable["h"] = "u99a5u7504"
mapVariable["e"] = "u5609u6866"


// 遍历取出键值
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
原文地址:https://www.cnblogs.com/hsmwlyl/p/11767094.html