二 Go的基本语法

1. 基本类型

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128

2. 三种声明变量

2.1 标准格式

var name type

其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型

2.2 批量格式

var (
    a int
    b string
    c []float32
    d func() bool
    e struct {
        x int
    }
) 

2.3 简短格式

名字 := 表达式
i, j := 0, 1
需要注意的是,简短模式(short variable declaration)有以下限制:
定义变量,同时显式初始化。 //相当于赋值,可以不用再声明获取到的变量的类型
不能提供数据类型。 // 因为赋值直接会有类型了,所以不用提供数据类型
只能用在函数内部。 //全局定义还是得用var

示例:

func main() {
   x:=100
   a,s:=1, "abc"
}

3. 变量初始化

3.1 标准格式

var 变量名 类型 = 表达式
var hp int = 100

上面代码中,100 和 int 同为 int 类型,int 可以认为是冗余信息,因此可以进一步简化初始化的写法

3.2 编译器推导类型的格式

var defence = 
var damageRate float32 = 0.17
var damage = float32(attack-defence) * damageRate
fmt.Println(damage)

代码说明:

  • 第 1 和 2 行,右值为整型,attack 和 defence 变量的类型为 int。
  • 第 3 行,表达式的右值中使用了 0.17。由于Go语言和C语言一样,编译器会尽量提高精确度,以避免计算中的精度损失。所以这里如果不指定 damageRate 变量的类型,Go语言编译器会将 damageRate 类型推导为 float64,我们这里不需要 float64 的精度,所以需要强制指定类型为 float32。
  • 第 4 行,将 attack 和 defence 相减后的数值结果依然为整型,使用 float32() 将结果转换为 float32 类型,再与 float32 类型的 damageRate 相乘后,damage 类型也是 float32 类型

3.3 短变量声明并初始化

如果 hp 已经被声明过,但依然使用:=时编译器会报错
// 声明 hp 变量
var hp int
// 再次声明并赋值
hp := 10

示例:

conn, err := net.Dial(“tcp”,“127.0.0.1:8080”) net.Dial
提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(err)

4. 多变量同时赋值

var b = 200
b, a = a, b
fmt.Println(a, b)

5. 变量作用域

5.1 局部变量

import (
    "fmt"
)
func main() {
    //声明局部变量 a 和 b 并赋值
    var a int = 3
    var b int = 4
    //声明局部变量 c 并计算 a 和 b 的和
    c := a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

5.2 全局变量

import "fmt"
//声明全局变量
var c int
func main() {
    //声明局部变量
    var a, b int
    //初始化参数
    a = 3
    b = 4
    c = a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

5.3 形式参数

import (
    "fmt"
)
//全局变量 a
var a int = 13
func main() {
    //局部变量 a 和 b
    var a int = 3
    var b int = 4
    fmt.Printf("main() 函数中 a = %d\n", a)
    fmt.Printf("main() 函数中 b = %d\n", b)
    c := sum(a, b)
    fmt.Printf("main() 函数中 c = %d\n", c)
}
func sum(a, b int) int {
    fmt.Printf("sum() 函数中 a = %d\n", a)
    fmt.Printf("sum() 函数中 b = %d\n", b)
    num := a + b
    return num
}

6. 格式化输出

import (
    "fmt"
    "math"
)
func main() {
    fmt.Printf("%f\n", math.Pi)
    fmt.Printf("%.2f\n", math.Pi)
}

通用占位符:

  • %v 默认使用,可通用
  • %T输出类型
  • %%输出百分号

布尔型:

  • %t

宽度标识符具体见下图:

n := 12.34
fmt.Printf("%f\n", n)
fmt.Printf("%9f\n", n)
fmt.Printf("%.2f\n", n)
fmt.Printf("%9.2f\n", n)
fmt.Printf("%9.f\n", n)

输出:
12.340000
12.340000
12.34
    12.34
       12

7. 整形、浮点、布尔值结合

package main
import "fmt"
func main() {
   i := 0
   var c float32 = 0.2
   b := float32(sum(3, 4))
   if b > 7 {
      i = 1
   }
   fmt.Printf("当前c的值为:%.2f\n", c)
   fmt.Printf("当前i的值为:%d\n", i)
   b2 := isBool(2)
   fmt.Printf("b2的值为: %t\n", b2)
}
func sum(a, b int) int {
   return a + b
}
func isBool(i int) bool { return i != 0 }

8. 字符串

8.1 常用符号

\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\:反斜杠自身

8.2 定义多行字符串

使用 `符号来定义
内部字符串以原始文字输出,转义符号失效

package main
import "fmt"
func main() {
   const str1 = `第一行
第二行
第三行
\r\n
`
   fmt.Printf("str1:%v", str1)
}

8.3 字符串方法

8.3.1 字符串长度

  1. 以字节长度计算
package main
import "fmt"
func main() {
   tip1 := "gen ji is a ninja"
   fmt.Println(len(tip1))
   tip2 := "忍者"
   fmt.Println(len(tip2))
}

输出:
17  // 纯英文与空格只占用一个长度
6  //一个汉字占用三个长度
  1. 按照字符个数计算(计算UTF8中的字符个数,需要用到RuneCountInString() 函数)
package main
import (
   "fmt"
   "unicode/utf8"
)
func main() {
   fmt.Println(utf8.RuneCountInString("龙龟冲!"))
   fmt.Println(utf8.RuneCountInString("龙龟, Fight!"))
}

8.4 获取字符串元素

ASCii码输出

theme := "狙击 start"
for i := 0; i < len(theme); i++ {
    fmt.Printf("ascii: %c  %d\n", theme[i], theme[i])
}

Unicode编码输出

package main
import "fmt"
func main() {
   theme := "狙击 start"
   for i, s := range theme {
      fmt.Printf("%d Ascii: %c %d\n", i, s, s)
   }
}

输出:
0 Ascii:29401
3 Ascii:20987
6 Ascii:   32
7 Ascii: s 115
8 Ascii: t 116
9 Ascii: a 97
10 Ascii: r 114
11 Ascii: t 116

8.5 字符串值获取

tracer := "死神来了, 死神bye bye"
comma := strings.Index(tracer, ", ")
pos := strings.Index(tracer[comma:], "死神")  //等价于从", 死神bye bye"字符串开始查找
fmt.Println(comma, pos, tracer[comma+pos:])

输出:
12 3 死神bye bye

8.6 字符串的拼接

package main
import (
   "bytes"
   "fmt"
)
func main() {
   a := "今天"
   b := "不错"
   var stringBuffer bytes.Buffer
   stringBuffer.WriteString(a)
   stringBuffer.WriteString(b) //这种拼接方式比+号高效
   fmt.Println(stringBuffer.String())
}

9. 类型转换

精度丢失问题

package main
import (
   "fmt"
   "math"
)
func main() {
   // 输出各数值范围
   fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
   fmt.Println("int16 range:", math.MinInt16, math.MaxInt16)
   fmt.Println("int32 range:", math.MinInt32, math.MaxInt32)
   fmt.Println("int64 range:", math.MinInt64, math.MaxInt64)
   // 初始化一个32位整型值
   var a int32 = 1047483647
   // 输出变量的十六进制形式和十进制值
   fmt.Printf("int32: 0x%x %d\n", a, a)
   // 将a变量数值转换为十六进制, 发生数值截断
   b := int16(a)
   // 输出变量的十六进制形式和十进制值
   fmt.Printf("int16: 0x%x %d\n", b, b)
   // 将常量保存为float32类型
   var c float32 = math.Pi
   // 转换为int类型, 浮点发生精度丢失
   fmt.Println(int(c))
}

10. 指针

10.1 指针取值

当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值,代码如下

package main
import (
    "fmt"
)
func main() {
    // 准备一个字符串类型
    var house = "Malibu Point 10880, 90265"
    // 对字符串取地址, ptr类型为*string
    ptr := &house
    // 打印ptr的类型
    fmt.Printf("ptr type: %T\n", ptr)
    // 打印ptr的指针地址
    fmt.Printf("address: %p\n", ptr)
    // 对指针进行取值操作
    value := *ptr
    // 取值后的类型
    fmt.Printf("value type: %T\n", value)
    // 指针取值后就是指向变量的值
    fmt.Printf("value: %s\n", value)
}

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值

package main
import "fmt"
func swap(a, b *int) {
   fmt.Println(a)
   fmt.Println(b)
   *b, *a = *a, *b
}
func main() {
   x, y := 1, 2
   swap(&x, &y)
   fmt.Println(x, y)
}

输出:
21
package main
import "fmt"
func swap(a, b *int) {
    b, a = a, b
}
func main() {
    x, y := 1, 2
    swap(&x, &y)
    fmt.Println(x, y)
}

输出12

10.2 接收启动参数

Go语言内置的 flag 包实现了对命令行参数的解析,flag 包使得开发命令行工具更为简单
下面的代码通过提前定义一些命令行指令和对应的变量,并在运行时输入对应的参数,经过 flag 包的解析后即可获取命令行的数据

package main
// 导入系统包
import (
   "flag"
   "fmt"
)
// 定义命令行参数
var mode = flag.String("mode", "", "process mode")
func main() {
   // 解析命令行参数
   fmt.Printf("%T\n", mode)
   flag.Parse()
   // 输出命令行参数
   fmt.Println(*mode)
}

将这段代码命名为 main.go,然后使用如下命令行运行: go run main.go --mode=fast 命令行输出结果如下:
fast

代码说明如下:
flag定义的都是指针类型的变量
第 10 行,通过 flag.String,定义一个 mode 变量,这个变量的类型是 *string。后面 3 个参数分别如下:
参数名称:在命令行输入参数时,使用这个名称。
参数值的默认值:与 flag 所使用的函数创建变量类型对应,String 对应字符串、Int 对应整型、Bool 对应布尔型等。
参数说明:使用 -help 时,会出现在说明中。
第 15 行,解析命令行参数,并将结果写入到变量 mode 中。
第 18 行,打印 mode 指针所指向的变量。

二 Go的基本语法_第1张图片

10.3 创建指针方法

new(类型)

package main
import "fmt"
func main() {
   a := new(string)
   fmt.Printf("%T\n", a)  //创建了一个指针类型
   *a = "今天天气不错"
   fmt.Println(*a)
}

输出:
*string
今天天气不错

11. Go变量的生命周期

11.1 变量种类

全局变量:它的生命周期和整个程序的运行周期是一致的;
局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。

堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);

栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }中定义的局部变量。

11.2 变量逃逸

var global *int
func f() {
    var x int
    x = 1
    global = &x
}
func g() {
    y := new(int)
    *y = 1
}
  • 上述代码中,函数 f 里的变量 x 必须在堆上分配,因为它在函数退出后依然可以通过包一级的 global
    变量找到,虽然它是在函数内部定义的。用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了。
  • 相反,当函数 g 返回时,变量 *y 不再被使用,也就是说可以马上被回收的。因此,*y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配
    *y 的存储空间,也可以选择在堆上分配,然后由Go语言的 GC(垃圾回收机制)回收这个变量的内存空间。
  • 简而言之就是f函数中的x变量虽然是在局部被定义的,但是因为他的值以指针的形式被另一个变量接收了,所以在外部全局中其实可以使用到他的指针地址,所以在Go术语中这种现象称之为逃逸
  • 然而在g函数中,并没有外部变量去接收y的指针地址,所以当函数结束时就可以把变量的内存空间通过GC加以回收
  • 在实际的开发中,并不需要刻意的实现变量的逃逸行为,因为逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响

12. 常量

Go语言中的常量使用关键字 const
定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式

在Go语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = “abc”
  • 隐式类型定义: const b = “abc”

常量的值必须是能够在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。

  • 正确的做法:const c1 = 2/3
  • 错误的做法:const c2 = getNumber() // 引发构建错误: getNumber() 用做值

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:

const (
    a = 1
    b
    c = 2
    d
)
fmt.Println(a, b, c, d) // "1 1 2 2"

常量生成器

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量

package main
import "fmt"
func main() {
   type Weekday int
   const (
      Sunday Weekday = iota
      Monday
      Tuesday
      Wednesday
      Thursday
      Friday
      Saturday
   )
   fmt.Println(Sunday)
   fmt.Println(Monday)
   fmt.Println(Tuesday)
   fmt.Println(Wednesday)
   fmt.Println(Thursday)
   fmt.Println(Friday)
   fmt.Println(Saturday)
}

输出:
0
1
2
3
4
5
6

13. 关键字与标识符

Go语言的词法元素包括 5
种,分别是标识符(identifier)、关键字(keyword)、操作符(operator)、分隔符(delimiter)、字面量(literal),它们是组成Go语言代码和程序的最基本单位。

13.1 关键字

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

PS:坑爹的表格真难用,该优化了啊!

13.2 标识符

标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列,标识符由若干个字母、下划线_、和数字组成,且第一个字符必须是字母

标识符的命名需要遵守以下规则:

  • 由 26 个英文字母、0~9、_组成;
  • 不能以数字开头,例如 var 1num int 是错误的;
  • Go语言中严格区分大小写;
  • 标识符不能包含空格;
  • 不能以系统保留关键字作为标识符,比如 break,if 等等。

命名标识符时还需要注意以下几点:

  • 标识符的命名要尽量采取简短且有意义;
  • 不能和标准库中的包名重复;
  • 为变量、函数、常量命名时采用驼峰命名法,例如 stuName、getVal;

在Go语言中还存在着一些特殊的标识符,叫做预定义标识符,如下表所示:
二 Go的基本语法_第2张图片

预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用。

14 运算符优先级

Go语言运算符优先级和结合性一览表**
二 Go的基本语法_第3张图片

15. Go的字符串与数值类型的转换**

package main
import (
   "fmt"
   "strconv"
)
func main() {
   value1, err1 := strconv.Atoi("哈哈") // Ascii To Int
   fmt.Println(value1)
   fmt.Println(err1)
   var x = 4
   value2 := strconv.Itoa(x) // 数字转字符串是不会出err
   fmt.Println(value2)

}

你可能感兴趣的:(Go,golang,java,c++)