Go part 2 基础语法

我们两清 提交于 2020-11-20 08:46:26

关键字、标识符

标识符:

是用户或系统定义的有意义单词组合,或单词与数字组合(具体意义有定义者决定)  

标识符以字母下划线开头,大小写敏感,比如:boy,  Boy,  _boy,  _(匿名变量,用来忽略结果)

标识符命名规范:在习惯上,Go语言程序员推荐使用驼峰式命名,当名字有几个单词组成的时优先使用大小写分隔,而不是优先用下划线分隔。因此,在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名,但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtml。

 

关键字:

是 Go 语言提供的有特殊含义的符号,也叫做“保留字”

系统保留关键字:

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

 

常量与变量

常量

常量使用 const 修饰,表示是只读的,不能修改

const 只能修饰 boolean,number(int相关,浮点数,complex)和 string 类型

语法:const identifier [type] = value(type 可省略)

优雅写法:

const(
    name string = "skier"
    age  int = 10
    salary int = 5000 / 2
    // gender boolean = getGender()  // const不能从函数中获取
)

常量因为在编译期确定,所以可以用于数组声明:

const size = 4
var arrayA [size]int

 

变量

声明一个变量:var identifier [type]

// 声明变量并赋值
var a int = 100

// 简写(自动推导类型)
a := 100

优雅写法:

var (
    name string = "johny"
    age int = 10
)

默认值:

  • int        0
  • float     0.0,编译默认推导为 float64
  • bool     false
  • string   ""  
  • slice,函数,指针变量默认为 nil  (slice 默认为 nil,但打印输出是 [],可使用 == nil 进行判空)

 

作用域:从定义变量的代码行开始,一直到直接所属的大括号结束为止(全局变量除外)

  • 在函数部声明的变量叫做局部变量,生命周期仅限于函数内部
  • 在函数部声明的变量叫做全局变量,生命周期作用于整个包,如果首字母大写的话,则可以被其它包导入

在编程中,变量在其实现了变量的功能后,作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助

 

基本数据类型与操作符

数字类型:int8,  int16,  int32,  int64,  uint8,  uint16, uint32,  uint64

bool 类型:ture,  false  (bool 型无法参与数值运算,也无法与其他类型进行转换。)

浮点类型:float32,  float64

字符类型:byte

字符串类型:字符串实现基于 UTF-8 编码

  ""  双引号,定义行字符串

  ``  反引号,定义行字符串(在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出)

    多行字符串一般用于内嵌源码和内嵌数据等

 

类型转换

格式:type(variable)

var a  int = 8
var b int32 = int32(a) 

浮点数转换成 int 类型,精度会丢失

var c float32 = math.Pi
fmt.Println(int(c))

int32 转换成 int16,数据会截断

// 初始化一个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)

结果是:
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759

 

字符串转义符

转移符 含  义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠

 

操作符

数字操作符:+,  -,  *,  /,  %

比较运算符:>,  >=,  <,  <=,  ==,  !=

 

字符串操作

拼接:

var str1 string = "hello"
var str2 string = "world"
var str string = str1 + str2

// var str string = fmt.Sprintf("%s%s", str1, str2)
 
获取长度:var length int = len(str)
 
切片:var substr string = str[:5]  (与python类似)

 

值类型与引用类型

值类型

本质上是原始类型,变量直接储存值,内存通常在中分配,包括 int,  float,  bool,  string 以及数组和 struct(结构体)

对值类型进行操作,一般都会返回一个新创建的值,所以把这些值传递给函数时,其实传递的是一个值的副本

func main() {
	name:="张三"
	fmt.Println(modify(name))
	fmt.Println(name)
}

func modify(s string) string{
	s=s+s
	return s
}

//Output
张三张三
张三

以上是一个操作字符串的例子,通过打印的结果,可以看到,本来 name 的值并没有改变,也就是说,我们传递的是一个副本,并且返回一个新创建的字符串

基本类型因为是值的拷贝,并且在对他进行操作的时候,生成的也是新创建的值,所以这些类型在多线程里是安全的,我们不用担心一个线程的修改影响了另外一个线程的数据

 

引用类型

引用类型与值类型恰恰相反,它的修改可以影响到任何引用到它的变量;变量存储的是地址,这个地址存储最终的值,通常在内存上分配,通过 GC 回收,包括 指针,select,map,chan 等

引用类型之所以可以引用,是因为我们创建引用类型的变量,其实是一个标头值,标头值里包含一个指针,指向底层的数据结构,当我们在函数中传递引用类型时,其实传递的是这个标头值的副本,它所指向的底层结构并没有被复制传递,这也是引用类型传递高效的原因。 

 

流程控制

if else 分支判断

if condition1 {
    block1
} else if condition2 {
    block2   
} else {
    block3
}

 

switch case 语句

func main(){
	var variabel string = "a"
	switch variabel {
		case "a", "b":
			fmt.Println(variabel)
			// fallthrough  // 会执行下一个case的语句块
		case "c":
			fmt.Println(variabel)
		default:
			fmt.Println("default output")
	}
}

case 后边的值可以写多个,是 或 的关系

case 语句块末尾如果加上 fallthrough,会接着执行下一个 case 的语句块

 

for 循环

for i:=0; i<100 ; i++ {
    fmt.Println("hello, world~")
}

 

死循环(for)

func main(){
	for {
		fmt.Println("hello, world~")
		time.Sleep(time.Second)
	}
}

 

加判断的 for 循环

func main(){
	var i int
	for i<100 {
		fmt.Println(i)
		i += 1
	}
}

 

for range 语句

可以使用 for range 遍历数组、切片、字符串、map 及通道(channel)。通过 for range 遍历的返回值有一定的规律:

  • 数组、切片、字符串返回索引和值
  • map 返回键和值
  • 通道(channel)只返回通道内的值

遍历数组:

var arrayA = [3]string{"hammer", "soldier", "mum"}
 
for index, value := range arrayA {
        fmt.Println(index, value)
}
 
运行结果:
0 hammer
1 soldier
2 mum

 

用匿名标识符忽略 index

var arrayA = [3]string{"hammer", "soldier", "mum"}
 
for _, value := range arrayA {
        fmt.Println(value)
}
 
运行结果:
hammer
soldier
mum

 

当然,for 循环中也能够支持:break,  continue 

 

goto 语句

可以通过标签进行代码间的无条件跳转,goto语句可以快速跳出循环 或 实现同样的逻辑 有一定的帮助:

快速跳出循环:

func main(){
    for i:=0; i<=100; i++{
        for j:=0; j<=100; j++{
            if j == 10 {
                // 直接跳转到标签
                goto breakHere
            }
        }
    }

    breakHere:
        fmt.Println("hello world~")
}

运行结果:
hello world~

  

使用 goto 集中处理错误:

err := firstCheckError()
if err != nil {
    goto onExit
}

err = secondCheckError()
if err != nil {
    goto onExit
}

fmt.Println("done")
return

onExit:
    fmt.Println(err)
    exitProcess()

 

函数

Go 语言支持普通函数、匿名函数和闭包

普通函数声明:func 函数名(参数列表)  (返回值列表)  {函数体}

不支持重载,一个源文件内不能有两个相同名称的函数

函数是一等公民,也是一种类型,可以赋值给变量

函数的传参方式

  • 值传递        (基本数据类型都是值传递)
  • 引用传递          (指针,slice,map,chan,interface)

注意:无论是值传递还是引用传递,传递给函数的都是变量的副本,不过,值传递是对值的拷贝,引用传递是地址的拷贝,一般来说,地址拷贝更为高效,而值拷贝取决于拷贝对象的大小,对象越大,则性能越低

 

返回值命名

返回值不需要定义,直接使用(命名的返回值变量的默认值为类型的默认值,即数值为 0,字符串为 "",布尔为 false、指针为 nil)

func calc(a int, b int) (c int) {
	c = a + b
	return c
}

 

可变长参数

可变参数变量是一个包含所有参数的切片

func calc(a int, b int, arg... int) {
	fmt.Println(arg[0])
}

 

defer 的用途

  1. 延迟调用是在 defer 所在函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机
  2. 多个 defer 语句,按先进后出(栈)的顺序执行
  3. defer 语句中的变量,在 defer 声明时就决定了
  4. defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放问题

关闭文件句柄

注意:不能将这一句代码放在第3行空行处,一旦文件打开错误,f将为空,在延迟语句触发时,将触发宕机错误

func read(){
    file err := open(filename)
if err != nil{ return } defer file.Close() }

 

锁资源的释放

func lock(){
    mc.Lock()
    defer mc.Unlock()
}

 

数据库连接的释放

func connect(){
    conn := openDatabase()
    defer conn.Close()
}

 

调用函数

函数在定义后,可以通过调用的方式,让当前代码跳转到被调用的函数中进行执行。调用前的函数局部变量都会被保存起来不会丢失;被调用的函数结束后,恢复到被调用函数的下一行继续执行代码,之前的局部变量也能继续访问

 

递归函数

一个函数在内部调用自己,就叫做递归,下面来举两个递归函数的Demo

递归求阶乘

func calc(n int) int {
	if n <= 1{
		return 1
	}

	return calc(n-1) * n
}


func main(){
	result := calc(5)
	fmt.Println(result)
}

运行结果:
120

 

斐波拉契数

func fab(n int) int {
	if n<=1{
		return 1
	}

	return fab(n-1) + fab(n-2)

}

func main(){
	var n int = 6
	var fabCount int
	for i:=0; i<=n; i++{
		fabCount += fab(i)
	}
	
	fmt.Println(fabCount)
}

运行结果:
33

 

匿名函数

匿名函数没有函数名,只有函数体,可以直接被当做一种类型赋值给函数类型的变量,匿名函数也往往以变量的方式被传递

匿名函数经常被用于实现回调函数、闭包

定义一个匿名函数

func(str string){
    fmt.Println("hello", str)
}("world")

运行结果:
hello world

 

也可以将匿名函数赋值给变量

f := func(str string){
    fmt.Println("hello", str)
}

f("world")

 

匿名函数当做参数

func visit(sliceA []int, f func(int)){
    for _, value := range sliceA {
        f(value)
    }
}

func main(){
    var sliceA []int = []int{1,2,3,4,5}

    f := func(a int){
        fmt.Print(a)
    }
    visit(sliceA, f)
}

运行结果:
12345

 

使用匿名函数实现操作封装

func main(){
    var mapA map[string]func()
    mapA = map[string]func(){
        "fire": func(){
            fmt.Println("chicken fire")
        },
        "run": func(){
            fmt.Println("soldier run")
        },
        "fly": func(){
            fmt.Println("angel fly")
        },
    }
    // fmt.Println(mapA)
    // 接收命令行参数,key,默认值,帮助
    var skill *string = flag.String("skill", "", "skill type")
    flag.Parse()

    f, err := mapA[*skill]
    if err == true {
        f()
    } else {
        fmt.Println("skill not fount")
    }
}

运行效果:
$  go run main.go --skill=fly
angel fly
$  go run main.go --skill=run
soldier run
View Code

 

闭包(Closure)

闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境 也不会被释放或者删除,在闭包中可以继续使用这个自由变量(闭包(Closure)在某些编程语言中也被称为 Lambda 表达式)

简单的说:

闭包 = 函数 + 引用环境

同一个函数与不同的引用环境组合,可以形成不同的实例,如图:

实现一个简单的闭包

func closureFunc(str string) func(){
    wapper := func(){
        fmt.Println("hello", str)
    }
    return wapper
}


func main(){
    f := closureFunc("world~")
    // 调用闭包函数
    f()
}

运行结果:
hello world~

 

累加器的实现(闭包的记忆效应)

func accumulate(num int) func() int {
    wapper := func() int {
        num += 1
        return num
    }
    return wapper
}


func main(){
    accumulator := accumulate(10)
    ret1 := accumulator()
    fmt.Println(ret1)
    ret2 := accumulator()
    fmt.Println(ret2)
}

运行结果:
11
12

 

go 基本程序结构

// 任何一个代码源文件隶属于一个包
package main

//import 关键字,同一个包内的函数可以直接调用,不同包中的函数通过 包名.函数名 的方式调用
import (
    "fmt"
    "/go_dev/test"
)

// 初始化函数
func init(){
    fmt.Println("执行初始化操作")
}

// main 程序入口
func main(){
    fmt.Println("hello, world~")
}

init 函数

每个源文件都可以包含一个 init 函数,会自动被编译器执行

 

包访问控制规则

    1.  包内函数名首字母大写表示此函数/变量是可导出的
    2. 小写表示此函数/变量是私有的,在包外部不能访问

 

程序执行顺序(栈)  *

  1. 导入其它包,初始化 被导入包 内的全局变量,执行 被导入包 内所有源文件的 init 函数
  2. 初始化 main 包内全局变量
  3. 调用 main 包 init 函数
  4. 执行 main 函数

 

练习1:写一个函数,对于一个整数n,求出所有两两相加等于n的组合,比如 n=5

package main

import (
	"fmt"
)

func calc(n int){
	for i:=0; i<=n; i++ {
		num := n - i
		fmt.Printf("%d+%d=%d\n", i, num, n)
	}

}

func main(){
	calc(5)
}

结果:
0+5=5
1+4=5
2+3=5
3+2=5
4+1=5
5+0=5

 

练习2:一个程序包含 add 和 main 两个包,add 包中有两个变量 Name 和 age,在 main 中访问 Name 和 age 变量,打印输出

首先在 go_dev 下新建一个目录 example(go_dev 目录在环境变量 GOPATH 下 src 目录,go编译器根据系统路径会找到 go_dev 目录下的包)

add包下写 add.go

package add

var Name string = "skier"
var age int = 19

main包下写 main.go

package main

import (
        "go_dev/example/add"
        // a "go_dev/example/add"  // 包别名的应用
        "fmt"
)

func main(){
	fmt.Println(add.Name)
	fmt.Println(add.age)  // 不能访问
}

 

作业

1.判断101~200之间有多少个素数,并输出打印

package main

import (
    "fmt"
)

func main(){
    for i:=101; i<=200; i++{
        var flag bool = true
        for j:=2; j<i; j++{
            if (i % j == 0) {
                flag = false
            }
        }
        if (flag == true){
            fmt.Println("素数i:", i)
        }
    }
}
第一题

2.打印出101~999中所有的水仙花数,所谓水仙花数是指一个三位数,其各位数字的立方和等于该数本身,例如153是一个水仙花数,1*1*1 + 5*5*5 + 3*3*3 = 153

package main
  
import (
"fmt"
)

func narcissisticNum(num int) bool {
    var g int = num % 100 % 10 
    var s int = num % 100 / 10
    var b int = num / 100
    //fmt.Println(g, s, b)
    var cube int = g*g*g +  s*s*s + b*b*b
    return num == cube
}

func main(){
    for i:=100; i<=999; i++{
        result := narcissisticNum(i)
        if result == true {
            fmt.Println("水仙花数是:", i)
        }
    }
}
第二题

3.对于一个数n,求n的阶乘之和,即:1! + 2! + ... + n!

package main

import (
    "fmt"
)

func summation(n int) int {
    var result int
    for i:=1; i<=n; i++ {
        var tmpe int = 1
        for j:=1; j<=i; j++{
            tmpe *= j
        }
        result += tmpe
    }
    return result
}

func main(){
    var num int = 5
    var result int = summation(num)
    fmt.Println("sum result:", result)
}
第三题

4.在终端打印 9*9 乘法表

package main

import(
    "fmt"
)

func multiplication(){
    for column:=1; column<=9; column++{
        for row:=1; row<=column; row++{
            fmt.Printf("%d*%d=%d\t", column, row, column*row)
        }
        fmt.Println()
    }
}

func main(){
    multiplication()
}
第四题

5.一个数如果恰好等于它的因子之和,这个数就称之为“完数”,例如:1+2+3=6,在终端输出1000以内的所有完数

package main

import(
    "fmt"
)

func showPerfect(n int){
    for i:=1; i<n; i++{
        var sum int

        for j:=1; j<i; j++{
            if i % j == 0{
                sum += j
            }
        }
        
        if sum == i{
            fmt.Printf("完数:%d \n", i)
        }
    }
}

func main(){
    const num int = 1000
    showPerfect(num)
}
第五题 

6.输入一个字符串,判断其是否为“回文”,回文字符串是指从左到右读,与从右到左读是完全相同的字符串

package main

import (
    "fmt"
)

func isReverse(str string) bool { 
    // 转为字符
    char := []rune(str)

    var length int = len(char)

    for i:=0; i<=length / 2 - 1; i++{
        if char[i] != char[length-1-i]{
            return false    
        }
    } 

    return true
}

func main(){
    var input string
    fmt.Scanf("%s", &input)
    result := isReverse(input)
    fmt.Println("result:", result)
}
第六题

7.输出一行字符串,分别统计其中英文字母,空格,数字和其它符号的个数

package main

import (
    "fmt"
    "bufio"
    "os"
)

func count(str string) (wordCount, spaceCount, numCount, otherCount int) {
    chars := []rune(str)
    for _, value := range chars{
        switch {
        case value >= 'a' && value <= 'z':
            wordCount += 1
        case value >= 'A' && value <= 'Z':
            wordCount += 1
        case value == ' ':
            spaceCount += 1
        case value >= '0' && value <= '9':
            numCount += 1
        default:
            otherCount += 1  
        }
    }
    return
}


func main(){
    reader := bufio.NewReader(os.Stdin)
    // 读取一行的内容
    result, _, error := reader.ReadLine()

    if error == nil{
        wordCount, spaceCount, numCount, otherCount := count(string(result))
        fmt.Printf("wordCount:%d\nspaceCount:%d\nnumCount:%d\notherCount:%d\n", wordCount, spaceCount, numCount, otherCount)
    }
}
第七题

8.计算两个大数相加的和,这两个大数会超过 int64 的表示范围

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!