首页 > 其他分享 >Goalng:基础复习一遍过

Goalng:基础复习一遍过

时间:2023-04-04 14:56:27浏览次数:57  
标签:复习 Goalng int fmt 一遍 Println func go main

Go(又称Golang)是Google开发的一种静态强类型编译型并发型,并具有垃圾回收功能的编程语言。

 

 

剖析 Hello world 

 

新建文件 main.go 写入以下内容:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

其中,packge main  的作用是声明了 main.go 这个go文件所在的包,Go语言中使用包来组织代码,一般一个文件夹就是一个包,包内可以暴露类型或者方法供其他包使用

import "fmt" fmt 是 Go 语言中的一个标准库/包,用于处理标准的输入输出

func main:main 函数是整个程序的入口,main函数所在的包名也必须是main

fmt.Println:调用fmt包的 Println 方法,打印出 ”Hello World!“

 

执行 go run main.go 或者 go run. 运行该程序就会输出 Hello World!

而 go run main.go 其实有两步:

  • go build main.go: 编译成二进制可执行程序
  • ./main: 执行该程序

 

变量与数据类型

变量的声明

Go语言是静态类型的,变量声明时必须明确变量的类型。Go语言在变量声明时,变量的类型写在变量的后面

如:

var a int = 1
var a int // int类型默认为0
var a = 1 //会自动根据1的类型进行类型推断
a := 1 // 简洁模式

Golang 中的基本简单类型

空值:nil

整型类型: int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, …

浮点数类型:float32, float64

字节类型:byte (等价于uint8)

字符串类型:string

布尔值类型:boolean,(true 或 false)

 

字符串

在 Go 语言中,字符串使用 UTF8 编码

,UTF8 的好处在于,如果基本是英文,每个字符占 1 byte,和 ASCII 编码是一样的,非常节省空间,如果是中文,一般占3字节。包含中文的字符串的处理方式与纯 ASCII 码构成的字符串有点区别。

package main

import (
    "fmt"
    "reflect"
)
func main() {
    str1 := "Golang"
    str2 := "Go语言"
    fmt.Println(reflect.TypeOf(str2[2]).Kind()) // uint8
    fmt.Println(str1[2], string(str1[2]))       // 108 l
    fmt.Printf("%d %c\n", str2[2], str2[2])     // 232 è
    fmt.Println("len(str2):", len(str2))       // len(str2): 8
}
  • reflect.TypeOf().Kind() 可以知道某个变量的类型,我们可以看到,字符串是以 byte 数组形式保存的,类型是 uint8,占1个 byte,打印时需要用 string 进行类型转换,否则打印的是编码值。
  • 因为字符串是以 byte 数组的形式存储的,所以,str2[2] 的值并不等于。str2 的长度 len(str2) 也不是 4,而是 8( Go 占 2 byte,语言占 6 byte)。

将 string 转为 rune 数组就可以正确处理中文

str2 := "Go语言"
runeArr := []rune(str2)
fmt.Println(reflect.TypeOf(runeArr[2]).Kind()) // int32
fmt.Println(runeArr[2], string(runeArr[2]))    // 35821 语
fmt.Println("len(runeArr):", len(runeArr))    // len(runeArr): 4

转换成 []rune 类型后,字符串中的每个字符,无论占多少个字节都用 int32 来表示

 

数组与切片

声明与初始化:

var arr [5]int     // 一维
var arr2 [5][5]int // 二维 

var arr = [5]int{1, 2, 3, 4, 5}
// 或 arr := [5]int{1, 2, 3, 4, 5}

下标索引的使用

arr := [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
    arr[i] += 100
}
fmt.Println(arr)  // [101 102 103 104 105]

数组的长度不能改变,如果想拼接2个数组,或是获取子数组,需要使用切片。切片是数组的抽象。

切片使用数组作为底层结构。

切片包含三个组件:容量,长度和指向底层数组的指针,切片可以随时进行扩展

声明与初始化

slice1 := make([]float32, 0) // 长度为0的切片
slice2 := make([]float32, 3, 5) // [0 0 0] 长度为3容量为5的切片
fmt.Println(len(slice2), cap(slice2)) // 3 5

使用

// 添加元素,切片容量可以根据需要自动扩展
slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4]
fmt.Println(len(slice2), cap(slice2)) // 7 12
// 子切片 [start, end)
sub1 := slice2[3:] // [1 2 3 4]
sub2 := slice2[:3] // [0 0 0]
sub3 := slice2[1:4] // [0 0 1]
// 合并切片
combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0]
  • 声明切片时可以为切片设置容量大小,为切片预分配空间。在实际使用的过程中,如果容量不够,切片容量会自动扩展。
  • sub2... 是切片解构的写法,将切片解构为 N 个独立的元素。

 

字典

map 类似于 java 的 HashMap,Python的字典(dict),是一种存储键值对(Key-Value)的数据解构。使用方式和其他语言几乎没有区别。

声明与使用

// 仅声明
m1 := make(map[string]int)
// 声明时初始化
m2 := map[string]string{
    "Sam": "Male",
    "Alice": "Female",
}
// 赋值/修改
m1["Tom"] = 18

 

指针

指针即某个值的地址,类型定义时使用符号*,对一个已经存在的变量,使用 & 获取该变量的地址。

str := "Golang"
var p *string = &str // p 是指向 str 的指针
*p = "Hello"
fmt.Println(str) // Hello 修改了 p,str 的值也发生了改变

一般来说,指针通常在函数传递参数,或者给某个类型定义新的方法时使用。Go 语言中,参数是按值传递的,如果不使用指针,函数内部将会拷贝一份参数的副本,对参数的修改并不会影响到外部变量的值。如果参数使用指针,对参数的传递将会影响到外部变量。

func add(num int) {
    num += 1
}

func realAdd(num *int) {
    *num += 1
}

func main() {
    num := 100
    add(num)
    fmt.Println(num)  // 100,num 没有变化

    realAdd(&num)
    fmt.Println(num)  // 101,指针传递,num 被修改
}
4 流程

 

 

流程控制

if条件

age := 18
if age < 18 {
    fmt.Printf("Kid")
} else {
    fmt.Printf("Adult")
}

// 可以简写为:
if age := 18; age < 18 {
    fmt.Printf("Kid")
} else {
    fmt.Printf("Adult")
}

switch选择

type Gender int8
const (
    MALE   Gender = 1
    FEMALE Gender = 2
)

gender := MALE

switch gender {
case FEMALE:
    fmt.Println("female")
case MALE:
    fmt.Println("male")
default:
    fmt.Println("unknown")
}
// male
  • 在这里,使用了type 关键字定义了一个新的类型 Gender。
  • 使用 const 定义了 MALE 和 FEMALE 2 个常量,Go 语言中没有枚举(enum)的概念,一般可以用常量的方式来模拟枚举。
  • 和其他语言不同的地方在于,Go 语言的 switch 不需要 break,匹配到某个 case,执行完该 case 定义的行为后,默认不会继续往下执行。如果需要继续往下执行,需要使用 fallthrough,例如:
switch gender {
case FEMALE:
    fmt.Println("female")
    fallthrough
case MALE:
    fmt.Println("male")
    fallthrough
default:
    fmt.Println("unknown")
}
// 输出结果
// male
// unknown

 

for循环

一个简单的累加的例子,break 和 continue 的用法与其他语言没有区别。

sum := 0
for i := 0; i < 10; i++ {
    if sum > 50 {
        break
    }
    sum += i
}

对数组(arr)、切片(slice)、字典(map) 使用 for range 遍历:

nums := []int{10, 20, 30, 40}
for i, num := range nums {
    fmt.Println(i, num)
}
// 0 10
// 1 20
// 2 30
// 3 40
m2 := map[string]string{
    "Sam":   "Male",
    "Alice": "Female",
}

for key, value := range m2 {
    fmt.Println(key, value)
}
// Sam Male
// Alice Female

函数(functions)

一个典型的函数定义如下,使用关键字 func,参数可以有多个,返回值也支持有多个。特别地,package main 中的func main() 约定为可执行程序的入口。

func funcName(param1 Type1, param2 Type2, ...) (return1 Type3, ...) {
    // body
}

例如,实现2个数的加法(一个返回值)和除法(多个返回值):

func add(num1 int, num2 int) int {
    return num1 + num2
}

func div(num1 int, num2 int) (int, int) {
    return num1 / num2, num1 % num2
}
func main() {
    quo, rem := div(100, 17)
    fmt.Println(quo, rem)     // 5 15
    fmt.Println(add(100, 17)) // 117
}

也可以给返回值命名,简化 return,例如 add 函数可以改写为

func add(num1 int, num2 int) (ans int) {
    ans = num1 + num2
    return
}

错误处理(error handling)

如果函数实现过程中,如果出现不能处理的错误,可以返回给调用者处理。比如我们调用标准库函数os.Open读取文件,os.Open 有2个返回值,第一个是 *File,第二个是 error, 如果调用成功,error 的值是 nil,如果调用失败,例如文件不存在,我们可以通过 error 知道具体的错误信息。

import (
    "fmt"
    "os"
)

func main() {
    _, err := os.Open("filename.txt")
    if err != nil {
        fmt.Println(err)
    }
}

// open filename.txt: no such file or directory

可以通过 errorw.New 返回自定义的错误

import (
    "errors"
    "fmt"
)

func hello(name string) error {
    if len(name) == 0 {
        return errors.New("error: name is null")
    }
    fmt.Println("Hello,", name)
    return nil
}

func main() {
    if err := hello(""); err != nil {
        fmt.Println(err)
    }
}
// error: name is null

error 往往是能预知的错误,但是也可能出现一些不可预知的错误,例如数组越界,这种错误可能会导致程序非正常退出,在 Go 语言中称之为 panic。

func get(index int) int {
    arr := [3]int{2, 3, 4}
    return arr[index]
}

func main() {
    fmt.Println(get(5))
    fmt.Println("finished")
}
$ go run .
panic: runtime error: index out of range [5] with length 3
goroutine 1 [running]:
exit status 2

在 Python、Java 等语言中有 try...catch 机制,在 try 中捕获各种类型的异常,在 catch 中定义异常处理的行为。Go 语言也提供了类似的机制 defer 和 recover
func get(index int) (ret int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Some error happened!", r)
            ret = -1
        }
    }()
    arr := [3]int{2, 3, 4}
    return arr[index]
}

func main() {
    fmt.Println(get(5))
    fmt.Println("finished")
}

结构体,方法和接口

结构体类似于其他语言中的 class,可以在结构体中定义多个字段,为结构体实现方法,实例化等。接下来我们定义一个结构体 Student,并为 Student 添加 name,age 字段,并实现 hello() 方法

type Student struct {
    name string
    age  int
}

func (stu *Student) hello(person string) string {
    return fmt.Sprintf("hello %s, I am %s", person, stu.name)
}

func main() {
    stu := &Student{
        name: "Tom",
    }
    msg := stu.hello("Jack")
    fmt.Println(msg) // hello Jack, I am Tom
}
  • 使用 Student{field: value, ...}的形式创建 Student 的实例,字段不需要每个都赋值,没有显性赋值的变量将被赋予默认值,例如 age 将被赋予默认值 0。
  • 实现方法与实现函数的区别在于,func 和函数名hello 之间,加上该方法对应的实例名 stu 及其类型 *Student,可以通过实例名访问该实例的字段name和其他方法了。
  • 调用方法通过 实例名.方法名(参数) 的方式。

除此之外,还可以使用 new 实例化:

func main() {
    stu2 := new(Student)
    fmt.Println(stu2.hello("Alice")) // hello Alice, I am  , name 被赋予默认值""
}

接口(interfaces)

一般而言,接口定义了一组方法的集合,接口不能被实例化,一个类型可以实现多个接口。

举一个简单的例子,定义一个接口 Person和对应的方法 getName() 和 getAge()

type Person interface {
    getName() string
}

type Student struct {
    name string
    age  int
}

func (stu *Student) getName() string {
    return stu.name
}

type Worker struct {
    name   string
    gender string
}

func (w *Worker) getName() string {
    return w.name
}

func main() {
    var p Person = &Student{
        name: "Tom",
        age:  18,
    }

    fmt.Println(p.getName()) // Tom
}
  • Go 语言中,并不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可。
  • 实例化 Student后,强制类型转换为接口类型 Person。

在上面的例子中,我们在 main 函数中尝试将 Student 实例类型转换为 Person,如果 Student 没有完全实现 Person 的方法,比如我们将 (*Student).getName() 删掉,编译时会出现如下报错信息。

*Student does not implement Person (missing getName method)
但是删除 (*Worker).getName() 程序并不会报错,因为我们并没有在 main 函数中使用。这种情况下我们如何确保某个类型实现了某个接口的所有方法呢?一般可以使用下面的方法进行检测,如果实现不完整,编译期将会报错。
var _ Person = (*Student)(nil)
var _ Person = (*Worker)(nil)
  • 将空值 nil 转换为 *Student 类型,再转换为 Person 接口,如果转换失败,说明 Student 并没有实现 Person 接口的所有方法。
  • Worker 同上。

实例可以强制类型转换为接口,接口也可以强制类型转换为实例。

func main() {
    var p Person = &Student{
        name: "Tom",
        age:  18,
    }

    stu := p.(*Student) // 接口转为实例
    fmt.Println(stu.getAge())
}

空接口

如果定义了一个没有任何方法的空接口,那么这个接口可以表示任意类型。例如

func main() {
    m := make(map[string]interface{})
    m["name"] = "Tom"
    m["age"] = 18
    m["scores"] = [3]int{98, 99, 85}
    fmt.Println(m) // map[age:18 name:Tom scores:[98 99 85]]
}

并发编程(goroutine)

Go 语言提供了 sync 和 channel 两种方式支持协程(goroutine)的并发。

例如我们希望并发下载 N 个资源,多个并发协程之间不需要通信,那么就可以使用 sync.WaitGroup,等待所有并发协程执行结束。

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func download(url string) {
    fmt.Println("start to download", url)
    time.Sleep(time.Second) // 模拟耗时操作
    wg.Done()
}

func main() {
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go download("a.com/" + string(i+'0'))
    }
    wg.Wait()
    fmt.Println("Done!")
}
  • wg.Add(1):为 wg 添加一个计数,wg.Done(),减去一个计数。
  • go download():启动新的协程并发执行 download 函数。
  • wg.Wait():等待所有的协程执行结束。
$  time go run .
start to download a.com/2
start to download a.com/0
start to download a.com/1
Done!

可以看到串行需要 3s 的下载操作,并发后,只需要 1s。
real 0m1.563s

使用 channel 信道,可以在协程之间传递消息。阻塞等待并发协程返回消息。
var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道

func download(url string) {
    fmt.Println("start to download", url)
    time.Sleep(time.Second)
    ch <- url // 将 url 发送给信道
}

func main() {
    for i := 0; i < 3; i++ {
        go download("a.com/" + string(i+'0'))
    }
    for i := 0; i < 3; i++ {
        msg := <-ch // 等待信道返回消息。
        fmt.Println("finish", msg)
    }
    fmt.Println("Done!")
}
$ time go run .
start to download a.com/2
start to download a.com/0
start to download a.com/1
finish a.com/2
finish a.com/1
finish a.com/0
Done!

real 0m1.528s

单元测试

假设我们希望测试 package main 下 calc.go 中的函数,要只需要新建 calc_test.go 文件,在calc_test.go中新建测试用例即可。

 
// calc.go
package main

func add(num1 int, num2 int) int {
    return num1 + num2
}
// calc_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    if ans := add(1, 2); ans != 3 {
        t.Error("add(1, 2) should be equal to 3")
    }
}

运行 go test,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v参数。

$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok example 0.040s

包(Package)和模块(Modules)

一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。

比如我们新建一个文件 calc.go, main.go 平级,分别定义 add 和 main 方法。

// calc.go
package main

func add(num1 int, num2 int) int {
    return num1 + num2
}
// main.go
package main

import "fmt"

func main() {
    fmt.Println(add(3, 5)) // 8
}

运行 go run main.go,会报错,add 未定义:

./main.go:6:14: undefined: add


因为 go run main.go 仅编译 main.go 一个文件,所以命令需要换成
$ go run main.go calc.go
8


$ go run .
8

Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。


Go Modules 是 Go 1.11 版本之后引入的,Go 1.11 之前使用 $GOPATH 机制。Go Modules 可以算作是较为完善的包管理工具。同时支持代理,国内也能享受高速的第三方包镜像服务。接下来简单介绍 go mod 的使用。Go Modules 在 1.13 版本仍是可选使用的,环境变量 GO111MODULE 的值默认为 AUTO,强制使用 Go Modules 进行依赖管理,可以将 GO111MODULE 设置为 ON。
在一个空文件夹下,初始化一个 Module
$ go mod init example
go: creating new go.mod: module example
 

此时,在当前文件夹下生成了go.mod,这个文件记录当前模块的模块名以及所有依赖包的版本。

接着,我们在当前目录下新建文件 main.go,添加如下代码:

package main

import (
    "fmt"

    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())  // Ahoy, world!
}
运行 go run .,将会自动触发第三方包 rsc.io/quote的下载,具体的版本信息也记录在了go.mod中:
module example

go 1.13

require rsc.io/quote v3.1.0+incompatible
我们在当前目录,添加一个子 package calc,代码目录如下
demo/
   |--calc/
      |--calc.go
   |--main.go

在 calc.go 中写入

package calc

func Add(num1 int, num2 int) int {
    return num1 + num2
}

在 package main 中如何使用 package cal 中的 Add 函数呢?import 模块名/子目录名 即可,修改后的 main 函数如下:

package main

import (
    "fmt"
    "example/calc"

    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
    fmt.Println(calc.Add(10, 3))
}
$ go run .
Ahoy, world!
13




以上
 

标签:复习,Goalng,int,fmt,一遍,Println,func,go,main
From: https://www.cnblogs.com/slowlydance2me/p/17286403.html

相关文章

  • 02142数据结构导论复习笔记
    第一章概论概论⭐⭐数据结构:计算机组织数据和存储数据的方式。数据结构:指一组相互之间存在一种或多种特定关系的数据的组织方式和它们在计算机内的存储方式,以及定义在该组数据上的一组操作。引言⭐⭐算法+数据结构=程序数据、数据元素和数据项⭐⭐⭐数据:所有被计......
  • 复习
    Markdowm复习标题三级标题四级标题 字体Hello,WordHello,wordHello,WordHello,Word 引用小杨姐姐前面加> 分割线第一种三个减号--- 第二种三个***也是 图片 要用英文的![]() 超链接跳转到小杨姐姐的空间 英文[]() 列表......
  • Spring源码复习
    Bean的生命周期 ApplicationContextCentralinterfacetoprovideconfigurationforanapplication.*Thisisread-onlywhiletheapplicationisrunning,butmaybe*reloadediftheimplementationsupportsthis.**<p>AnApplicationContextprovides:*<ul......
  • C#复习笔记-委托
       委托是一种引用类型,委托定义了了一类可以被委托实例调用的方法。它定义了方法的返回值类型和参数类型。定义了一个名为FeedBack的委托,返回一个int类型的值,带有一个int类型的参数。可以将任何类型或者结构中与委托类型匹配的方法传递给委托,可以是静态方法也可以是实例方法......
  • 新概念2册L75笔记(复习一般过去时&系动词:变化)
    L75SOS单词理解语法理解一般过去时功能:发生在过去的事情;礼貌委婉。关键词:过去具体时间(yesterday/ago/last…)课文理解......
  • C 语言程序设计复习
    第一章程序设计和C语言计算机程序一组计算机能够识别和执行的指令计算机语言机器语言计算机只能识别由0和1组成的指令能够别计算机识别和接受的二进制代码成为机器指令机器指令的集合就是机器语言符号语言(汇编语言)计算机不能直接识别和执行,需要汇编程序将其转换为机......
  • 进制转化复习( 万能的a进制转化为b进制)
    进制转化复习十进制转化为十六进制Description十六进制数是在程序设计时经常要使用到的一种整数的表示方式。它有0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F共16个符号,分别表示十进制数的0至15。十六进制的计数方法是满16进1,所以十进制数16在十六进制中是10,而十进制的17在十六进制中......
  • 线段树模板复习
    建树voidbuild(intl,intr,intrt){ if(l==r) { t[rt]=a[l]; return; } intmid=(l+r)>>1; build(l,mid,rt<<1); build(mid+1,r,(rt<<1)|1); t[rt]=t[rt<<1]+t[(rt<<1)|1];}标记下传voidpushdown(intl,intr,intrt){ if(lazy......
  • CSS总复习(二)一些属性介绍
    CSS字体、文本样式指定字体不能指望用户的机器上一定安装了你想使用的字体。解决这个问题的方法是使用Web字体,我们可以直接下载Web字体并使用在自己的页面上,而不需要用户做什么。使用@font-face指定Web字体,如:@font-face{font-family:'MyFont';font-style:normal;......
  • CSS总复习(一)基础
    CSS是什么CSS(层叠样式表)用来规定HTML文档的呈现形式(外观和格式编排)。CSS样式由一条或多条以分号隔开的样式声明组成。每条声明包含着一个CSS属性和该属性的值,二者以冒号分隔。PS:书中有句话很nice:“笨蛋才会为完美的CSS方案纠缠不休”。因为一种效果的实现方式可能有很多种,你只要......