GO 从 0 到 1 系列:1 变量+常量+作用域

[TOC]

1 程序结构

Go 源文件以 package 声明开头,说明源文件所属的包,接着使用 import 导入依赖的包,其次为包级别的变量、常量、类型和函数的声明和赋值。函数中可定义局部的变量、常量

2 基本组成元素

2.1 标识符

标识符是编程所使用的名字,用于给变量、常量、函数、类型、接口、包名等进行命名,以建立名称和使用之间的关系,Go 语言标识符的命名规则:

必须满足:

  1. 只能由非空字母(Unicode)、数字、下划线 _ 组成
  2. 只能以字母或下划线开头
  3. 不能使用 Go 关键字
  4. 避免使用 Go 语言预定义标识符
  5. 建议使用驼峰式
  6. 标识符区分大小写

Go 语言提供一些预先定义得标识符用来表示内置得常量、类型、函数,在自定义标识符时应该避免使用:

  • 内置常量:true、false、nil、iota
  • 内置类型:bool、byte、rune、int、int8、int16、int32、uint、uint8、uint16、uint32、uint64、uintptr、float32、float64、complex64、complex128、string、error
  • 内置函数:make、len、cap、new、append、copy、close、delete、complex、real、imag、panic、recover
  • 空白标识符:_

2.2 关键字

关键字用于特定的语法结构,Go 语言定义 25 个关键字:

  • 用于声明:import、package
  • 实体声明和定义:chan、const、func、interface、map、struct、type、var
  • 流程控制:break、case、continue、default、defer、else、fallthrough、for、go、goto、if、range、return、select、switch

2.3 字面量

字面量是值的表示方法,常用与对变量/常量进行初始化,主要分为:

  • 标识基础数据类型值的字面量,例如:0, 1.1, true,3 + 4i, 'a', "我爱中国"
  • 构造自定义的复合数据类型的类型字面量,例如:type Interval int
  • 用于表示符合数据类型值的复合字面量,用来构造array、slice、map、struct的值,例如:{1,2,3]

2.4 操作算数运算符

  • 算术运算符:+、-、*、/、%、++、--

    运算符 描述
    + 相加
    相减
    * 相乘
    / 相除
    % 求余

    示例:

    package main
    
    import "fmt"
    
    func act() {
    var a int = 4
    var b int = 8
    var c int = a + b
    var d int = b - a
    var e int = a * b
    var f int = b / a
    var g int = b % a
    fmt.Println(c, d, e, f, g)
    }
    
    func main() {
    act()
    }
    

    输出:

    [15:05:57 root@go day1]#go run main.go 
    12 4 32 2 0
  • 关系运算符:

    运算符 描述
    == 检查两个值是否相等,如果相等返回 True 否则返回 False
    != 检查两个值是否不相等,如果不相等返回 True 否则返回 False
    > 检查左边值是否大于右边值,如果是返回 True 否则返回 False
    >= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False
    < 检查左边值是否小于右边值,如果是返回 True 否则返回 False
    <= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False

    示例:

    package main
    
    import "fmt"
    
    func rel() {
    var a int = 8
    var b int = 3
    
    fmt.Println(a > b)
    fmt.Println(a < b)
    fmt.Println(a != b)
    fmt.Println(a >= b)
    fmt.Println(a <= b)
    }
    
    func main() {
    rel()
    }
    

    输出:

    [15:05:58 root@go day1]#go run main.go 
    true
    false
    true
    true
    false
  • 逻辑运算符

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

    示例:

    package main
    
    import "fmt"
    
    func rel() {
    var a int = 8
    var b int = 3
    var c int = 5
    fmt.Println(a > b && c > b) // a 大于 b 并且 c 大于 b 这里为 true
    fmt.Println(a < b || b < c) // a 小于 b 为假但是这里为 || 所以 b < c 这里为 true
    fmt.Println(!(a > b))       // a 大于 b 为 true 取反之后为 false
    }
    
    func main() {
    rel()
    }
    

    输出:

    [15:07:58 root@go day1]#go run main.go 
    true
    true
    false
  • 位运算符:&、|、^、<<、>>、&^

    运算符 描述
    & 参与运算的两数各对应的二进位相与(两位均为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 bit_op() {
    fmt.Printf("%b\n", 5&6)  // 101 & 110 == 100
    fmt.Printf("%b\n", 5|6)  // 101 | 110 == 111
    fmt.Printf("%b\n", 5^6)  // 101 ^ 110 == 011
    fmt.Printf("%b\n", 5>>1) // 101 >> 1 = 10 可以看到 5 的二进制为 101 向右移 1 位,就变成了 10
    fmt.Printf("%b\n", 5<<1) // 101 << 1 = 1010 可以看到 5 的二进制为 101 向右移 1 位,就变成了 1010
    }
    
    func main() {
    bit_op()
    }
    

    输出:

    [15:27:52 root@go day1]#go run main.go 
    100
    111
    11
    10
    1010
  • 赋值运算符:=、+=、—=、/=、%=、&=、|=、”=、<<=、>>=

    运算符 描述
    = 简单的赋值运算符,将一个表达式的值赋给一个左值
    += 相加后再赋值
    -= 相减后再赋值
    *= 相乘后再赋值
    /= 相除后再赋值
    %= 求余后再赋值
    <<= 左移后赋值
    >>= 右移后赋值
    &= 按位与后赋值
    |= 按位或后赋值
    ^= 按位异或后赋值

    示例:

    // 这里我只演示一下几种赋值运算,其他的以此类推
    package main
    
    import "fmt"
    
    func main() {
    a := 5
    a += 1 // a += 1 = 5 + 1 = 6
    b := 7
    b *= a // b *= a = 7 * 6 = 42
    fmt.Println(a, b)
    
    b ^= a // 42 ^ 6 = 101010 ^ 110 = 101100 = 44
    fmt.Printf("二进制:%b\n十进制:%d\n", b, b)
    }
    

    输出:

    [17:29:01 root@go day1]#go run main.go 
    6 42
    二进制:101100
    十进制:44
  • 其他运算符:&(单目)、*(单目)、.(点)、-(单目)、... 、<-

2.5 分割符

小括号(),中括号[],大括号{},分号;,逗号,

2.6 保留字

常量:truefalseiotanil

数据类型:

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

3 声明

声明语言用于定义程序的各种实体对象,主要有:

  • 声明变量的var
  • 声明常量的const
  • 声明函数的func
  • 声明类型的type

4 变量、常量、字面量

4.1 变量

类型 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

变量常用语法:

变量是指对一块存储空间定义名称,通过名称对存储空间的内容进行访问或修改,使用 var 进行变量声明,常用的语法为:

  • var变量名变量类型=值
    定义变量并进行初始化,

    例如: var name string = "silence"

  • var变量名变量类型
    定义变量使用零值进行初始化,

    例如: var age int

  • var 变量名 = 值
    定义变量,变量类型通过值类型进行推导,

    例如:var isBoy = true

  • var变量名1,变量名2,…,变量名 n 变量类型

    定义多个相同类型的变量并使用零值进行初始化

    例如: var prefix,suffix string

  • var变量名1,变量名2 ,…,变量名n变量类型 = 值1,值2,…,值n

    定义多个变量并使用对应的值进行初始化,变量的类型使用值类型进行推导,类型可不相同,

    例如: var name,age = "silence",30

标准定义:

var name string 
var age int 
var isOk bool

// 声明变量同时并赋值
var a string="china"
var a,b int=3,7     // 给 a 赋值 3 b 赋值 7
var a="china"         // 类型推导,a 默认为  string 类型
var a,b="china",7     // 类型推导给 a 赋值 china 并且为 string,给 b 赋值为 7 为 int 类型

批量定义:

var (
    变量名1 变量类型1 = 值1
    变量名2 变量类型2 = 值2
)
  • 定义多个变量并进行初始化,批量赋值中变量类型可省略

例如:

var (
    name string = "silenge"
    age int = 30
)

初始化表达式可以使用字面量、任何表达式、函数

初始化表达式可以使用字面量、任何表达式、函数

范例代码:

package main

import (
    "fmt"
)

// 我是注释
/*
   我是块注释
*/

// 全局变量
// var 定义变量
// var 标识符(变量名称) 类型(变量得类型)
// "zz" 等于给 Name 赋值
var Name string = "zz"

func main() {
    // 局部变量
    // 10 等于给 Age 赋值
    var Age int = 10

    fmt.Println(Name, Age)
}

输出:

go 的局部变量声明了必须使用,不然会报:weight declared but not used

如下代码:


报错:

但是全局变量定义不使用就不会报错,如下图代码:


成功执行该代码:

当然我们还可以将类型忽略掉,由此又出了类型推导

范例代码:

package main

import (
    "fmt"
)

// 全局变量不能使用类型推导
var Name string = "zz"

func main() {

    // 类型推导也可以叫做短声明,为 int 类型
    Age := 10
    weight := 189

    fmt.Println(weight, Age)
    fmt.Printf("age type = %T\nweight type = %T", Age, weight)
}

通过输出我们会发现 age weight 类型推导为了 int 类型

![image-20210529231244575](1 程序结构.assets\image-20210529231244575.png)

4.1.1 同时定义多个变量

我们在定义变量的时候可以同时定义多个变量并且赋值

范例代码:

package main

import (
    "fmt"
)

func main() {
    // 这里同时定义了 age, name, height 三个不同的变量,并且同时赋值了三个不同的类型
    age, name, height := 18, "zzz", 13.4
    fmt.Printf("age type = %T\nweight type = %T\nheight = %T", age, name, height)
}

4.1.2 表达式定义范例

初始化表达式可以使用字面量、任何表达式、函数

范例代码:

package main

import (
    "fmt"
)

func main() {

    // 这种称为表达式
    age := 12 + 1
    fmt.Println(age)
}

4.1.3 简短声明

在函数内可以通过简短声明语句声明并初始化变量,可通过简短声明同时声明和初始化多个变量需要注意操作符左侧的变量至少有一个未定义过

注意:

全局变量不能通过短声明来定义变量

4.1.4 赋值

赋值等于更改变量的值

范例代码:

package main

import (
    "fmt"
)

func main() {
    // 定义 age 变量并且赋值为 12
    var age = 12
    fmt.Println("第一次赋值", age)

    // 重新对 age 赋值 20
    age = 20
    fmt.Println("第二次赋值", age)

    // 最后对 age 赋值为 100
    age = 100
    fmt.Println("最终赋值", age)
}


最后输出的值是最后一次赋值的值

也可以一次同时赋值多个变量

4.1.5 变量总结

变量:

  1. 变量必须先定义再使用

  2. 变量都是有类型的指定,未指定通过值推导

  3. 定义变量的几种方式:

    // 1 种
    var name type 
    
    // 2 种
    var name type = value
    
    // 3 种
    var name = value
    
    // 4 种:只能在函数内部使用,不能在全局变量使用
    name := value
  4. 标识符在局部内只能定义一次,一个 {} 就为一个局部

  5. 变量可以在后期更改它的值的

4.1.6 常量

常量可以声明全局的也可以声明为局部的,常量定义以后就不能更改他的值

常量必须在定义的时候就需要赋值,因为后期不能更改该常量

而且常量这一块不能定义函数

常量用于定义不可被修改的的值,需要在编译过程中进行计算,只能为基础的数据类型布尔数值、字符串,使用const进行常量声明,

常用语法:

  1. const 常量名 类型 = 值

    定义常量并初始化,

    例如:const pi float64 = 3.14

  2. const 常量名 = 值

    定义常量,类型通过值类型进行推导

    例如:const e = 2.177

  3. 批量定义

    const(
    常量名1 类型1 = 值1
    常量名2 类型2 = 值2
    )
    
    // 定义多个常量并进行初始化,批量赋值中类型可以省略,并且除了第一个常量值外其
  4. 常量可同时省略类型和值,表示使用前一个常量的初始化表达式

    例如:

    const (
    name string = "silence"
    age int = 30
    )
    
    const (
    name string = "silence"
    desc
    )
  5. 常量之间的运算,类型转换,以及对常量调用函数len、cap、real、imag、complex、unsafe.Sizeof 得到的结果依然为常量

范例代码:

package main

import (
    "fmt"
)

// 全局定义常量
const statusNew int = 1
const statusDelete int = 2

func main() {
    // 局部定义常量
    const Monday = "星期一"
    const Tuesday = "星期二"
    fmt.Println(statusNew, statusDelete, Monday, Tuesday)
}

常量定义后不能被赋值了,否则报错


输出:

报错无法给 Tuesday 常量进行分配

4.2 常量

常量在定义时必须赋值,且程序运行期间其值不能改变

伪代码 1 :

const PI float32=3.14
const(
    PI=3.14
    E=2.71
)

伪代码 2 :

const(
    a=100
    b   //100,跟上一行的值相同
    c   //100,跟上一行的值相同
)

4.2.1 多常量定义

范例代码:

package main

import (
    "fmt"
)

// 全局定义常量
const statusNew int = 1
const statusDelete int = 2

func main() {
    // 多常量定义,局部定义常量
    const (
        Monday  = "星期一"
        Tuesday = "星期二"
    )
    fmt.Println(statusNew, statusDelete, Monday, Tuesday)
}

如果常量里面被小括号括起来了,除了第一个常量被初始化值了,后面的常量没有初始化值,会自动继承第一个常量的初始化值

范例代码:

package main

import (
    "fmt"
)

// 全局定义常量
const (
    statusNew int = 1
    statusDelete
)

func main() {
    // 局部定义常量
    const (
        Monday  = "星期一"
        Tuesday = "星期二"
        yyds    // 如果在同一个小括号内,若为赋值则使用最近的一个已赋值常量对应的值进行初始化
    )
    fmt.Println(statusNew, statusDelete, Monday, Tuesday, yyds)
}

会发现这里 yyds也被赋值为了 星期二,statusDelete 赋值为了 1

4.2.2 枚举(iota)

伪代码:

const(
    a=iota  //0
    b       //1
    c       //2
    d       //3
)

const(
    a=iota  //0
    b       //1
    _       //2
    d       //3
)

const(
    a=iota  //0
    b=30    
    c=iota  //2
    d       //3
)

const(
    _=iota              // iota =0
    KB=1<<(10* iota)  // iota =1
    MB=1<<(10* iota)  // iota =2
    GB=1<<(10* iota)  // iota =3
    TB=1<<(10* iota)  // iota =4
)

const(
     a,b = iota+1, iota+2   //iota = 0 , a = iota+1=0+1 , b = iota+2=0+2 , a,b = 1,2 
     c,d                    //iota = 1 , c = iota+1=1+1 , d = iota+2=1+2 , c,d = 2,3  
     e,f                    //iota = 2 , e = iota+1=2+1 , f = iota+2=2+2 , e,f = 3,4 
)

枚举:

有一个值是有一个范围的,而且每一个值都对应一个类型,并且这些值都是固定的

范例代码:

package main

import (
    "fmt"
)

func main() {
    // 枚举值
    // 这里就类似于 status 的枚举,并且对应的值是 1,2,3
    // 分别对应的是 New Complete Delete
    const (

        // iota 在小括号内,初始化为 0 ,每次调用一次 + 1
        // 如果在同一个小括号内,若为赋值则使用最近的一个已赋值常量对应的值进行初始化
        // 所以这里都会调用 statusNew 的值 iota
        statusNew      = iota // 0
        statusComplete        // 1
        statusDelete          // 2
    )

    fmt.Println(statusNew, statusComplete, statusDelete)
}

4.2.3 枚举时赋值

枚举的同时也支持赋值

package main

import (
    "fmt"
)

func main() {
    // 枚举值
    // 这里就类似于 status 的枚举,并且对应的值是 1,2,3
    // 分别对应的是 New Complete Delete
    const (

        // iota 在小括号内,初始化为 0 ,每次调用一次 + 1
        // 如果在同一个小括号内,若为赋值则使用最近的一个已赋值常量对应的值进行初始化
        // 所以这里都会调用 statusNew 的值 iota

        // 这里再定义枚举的同时 + 10
        statusNew      = iota + 10 // 10
        statusComplete             // 11
        statusDelete               // 12
    )

    fmt.Println(statusNew, statusComplete, statusDelete)
}

iota 乘法的用法范例

package main

import (
    "fmt"
)

func main() {

    const (
        // 由于 iota 每次 + 1 所以这里乘以 10
        statusNew      = iota * 10 // 0
        statusComplete             // 10
        statusDelete               // 20
    )

    fmt.Println(statusNew, statusComplete, statusDelete)
}

4.3 字面量

字面量:没有出现变量名,直接出现了值。基础类型的字面量相当于是常量

fmt.Printf("%t\n", 04 == 4.00) //用到了整型字面量和浮点型字面量
fmt.Printf("%v\n", .4i) //虚数字面量 0.4i
fmt.Printf("%t\n", '\u4f17' == '众') //Unicode和rune字面量
fmt.Printf("Hello\nWorld\n!\n") //字符串字面量

5 作用域

作用域是限制变量的可以使用范围go 语言使用大括号显示的标识作用域范围,大括号内包含一连串的语句,叫做语句块。语句块可以嵌套,语句块内定义的变量不能在语句块外使用

全局域中只能定义变量不能调用变量

常见隐式语句块:

  • 全语句块
  • 包语句块
  • 文件语句块
  • if、switch、for、select、case 语句块

作用域内定义变量只能被声明一次且变量必须使用,否则编译错误。在不同作用域可定义相同的变量,此时父部变量将被覆盖

作用域原理图:


我们在一个 package 作用域中定义了 Name ,然后再 main 函数作用域中也定义了 Name age 。然后又开启了一个 {var age int = 33 fmt.Println(Name, age)},这时候 age 就是 33 ,但是由于 Name 不在该{} 作用域中,所以就去找 main 作用域是否有 Name 变量,假如 main 也没有就会直接找到 Package 中是否有 Name

如果当前作用域没有对应的变量,就会找父级的作用域

范例代码:

package main

import (
    "fmt"
)

// 该 Name 变量作用域在全局
var Name string = "zz"

func main() {

    // 作用域用来说明标识符的适用范围 {}
    // 该 Name 变量作用域在 main 函数中
    var Name string = "gg"
    var age int = 30
    fmt.Println(Name, age)
}

输出发现只会将最后一次的 name 赋值 gg 输出

作用域细节:

当我们在局部作用域定义了一个和全局作用域相同的变量时,如果先执行的话就是先执行全局作用域的变量

package main

import (
    "fmt"
)

// 该 Name 变量作用域在全局
var Name string = "zz"

func main() {
    // 这里输出的时候是使用的全局变量 Name
    fmt.Println(Name)

    // 该 Name 变量作用域在 main 函数中
    var Name string = "gg"
    var age int = 30
    fmt.Println(Name, age)
}

一个大括号就是一个作用域:

package main

import (
    "fmt"
)

// 该 Name 变量作用域在全局
var Name string = "zz"

func main() {
    // 这里输出的时候是使用的全局变量 Name
    fmt.Println(Name)

    // 作用域用来说明标识符的适用范围 {}
    // 该 Name 变量作用域在 main 函数中
    var Name string = "gg"
    var age int = 30

    // 一个大括号就是一个作用域,这里将 age 定义为了 33 ,并且输出的时候只会输出该作用域的结果
    {
        var age int = 33 // 这里是重新声明了 age
        fmt.Println(age)
    }

    fmt.Println(Name, age)

}

通过输出,我们会发现在 {} 中定义的 age 并不会覆盖掉 main 函数中的 age 变量的值

在子作用域中赋值变量,会修改父作用域的该变量值

范例:

package main

import (
    "fmt"
)

// 该 Name 变量作用域在全局
var Name string = "zz"

func main() {
    fmt.Println(Name)

    var Name string = "gg"    // 被重新赋值为了 YY
    var age int = 30

    {
        var age int = 33    // 这里是重新声明了 age
        fmt.Println(age)    
        Name = "yy"   // 将 Name 变量重新赋值为了 yy
    }

    fmt.Println(Name, age)

}

我们可以看到,输出的时候并没有输出 gg ,而是将父作用域的 Name 变量重新修改为了 yy。

总结:

作用域会先在同级的里面找,如果同级里面没有就会从父作用域去找,所以这里将 gg 修改为了 yy

7 注释

Go 支持两种注释方式,行注释和块注释:

行注释:以 // 开头,例如://我是行注释

块注释:以 /* 开头, */ 结尾,例如:/*我是块注释*/

8 问题追踪和排查

最基本的问题跟踪方式为打印日志,我们可以fmt包中提供的Println、Print、Printf 函数用于将信息打印到控制台,帮助我们进行问题调试

我们一般先是将代码分成一半来 fmt.Println() 来观察结果,判断代码前一半有问题还是没有问题,类似于二分查找,逐步缩小我们的代码范围。

9 输出&输入

输出的话通过 fmt 包中的 Println、Print、Printf 即可

输入范例:

package main

import (
    "fmt"
)

func main() {
    var name string
    fmt.Println("请输入名字:")
    fmt.Scan(&name)             // 取 name 变量地址
    fmt.Println("你输入的内容为:", name)
}

评论

  1. 693372
    3年前
    2021-6-11 3:21:34

    元哥又开始表演了 {{chan}}

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
下一篇