继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Go语言3

holdtom
关注TA
已关注
手记 1845
粉丝 240
获赞 991


strings 包

strings 包实现了用于操作字符串的简单函数。

HasPrefix

strings.HasPrefix(s string, prefix string) bool

判断字符串s是否以prefix开头

HasSuffix

strings.HasSuffix(s string, suffix string) bool

判断字符串s是否以suffix结尾

Index

strings.Index(s string, str string) int

判断str在s中首次出现的位置,如果没有出现,则返回-1

LastIndex

strings.LastIndex(s string, str string) int

判断str在s中最后出现的位置,如果没有出现,则返回-1

Replace

strings.Replace(str string, old string, new string, n int) string

字符串替换,对原始字符串str进行替换,把原始的old替换成new。

最后一个n是替换的次数,如果 n<0 则替换所有。一般就-1。

Count

strings.Count(str string, substr string) int

字符串计数,返回 substr 在 str 里出现的次数。

Repeat

strings.Repeat(str string, count int) string

返回一个新的字符串,由 str 重复 count 次构成。或者这么讲,将 count 个字符串 str 连接成一个新的字符串。

ToLower

strings.ToLower(str string) string

转为小写

ToUpper

strings.ToUpper(str string) string

转为大写

TrimSpace

strings.TrimSpace(s string) string

去掉字符串首尾空白字符,空白字符除了空格至少还包括: “\n\t\r” 。

相当于下面的这个Trim方法的简写:

strings.Trim(s string, " \n\t\r") string

Trim

strings.Trim(s string, cutset string) string

去掉字符串首尾的 cutset 字符串里包含的字符

示例:

package main

import (

    "fmt"

    "strings"

)

func main() {

    fmt.Printf("[%q]", strings.Trim(" !!! Achtung! Achtung! !!! ", "! "))

}

// 上面的cutset包含2个字符: ! 和 空格,

//  所以会把字符串 s 两边的这两个字符都去掉,直到遇到这两个字符以外的字符为止

/* 运行结果:

H:\Go\src\go_dev\day3\strings>go run test_trim.go

["Achtung! Achtung"]

H:\Go\src\go_dev\day3\strings>

*/

如果只需要去掉前面的部分,或者只去掉末尾的部分,还有下面2个方法:

strings.TrimLeft(s string, cutset string) string

strings.TrimRight(s string, cutset string) string

Split

strings.Split(s string, sep string) []string

返回 sep 分隔的所有字符串 s 的子串的slice

如果是用空格分隔的,那么可以用下面的 Fields 方法

strings.Fields(s string) []string

Join

strings.Join(a []string, sep string) string

用 sep 把 a 里的所有元素拼接起来。是上面的Split的逆向操作

strconv 包

strconv 包提供了字符串和基本数据类型之间的转换操作。

Itoa

strconv.Itoa(i int) string

整数转字符串

Atoi

strconv.Atoi(s string) (i int, err error)

字符串转整数。注意,这个返回2个值。成功转换,返回整数,err为空(nil);如果s是空或者无效数字,就会有err,并且返回值是0。

时间和日期类型

time包提供显示和计算时间用的函数。

获取当前时间

获取当前时间用 time.Now() :

package main

import (

    "fmt"

    "time"

)

func main(){

    now := time.Now()

    fmt.Println(now)

    fmt.Printf("%T\n", now)

}

/* 运行结果:

H:\Go\src\go_dev\day3\time>go run now.go

2018-10-07 12:51:52.9706623 +0800 CST m=+0.001999101

time.Time

H:\Go\src\go_dev\day3\time>

*/

这个数据类型是 time.Time

获取日期信息

包括但不只有下面这些方法,全部的方法去官网查吧:

func (t Time) Date() (year int, month Month, day int) {} // 返回时间的日期信息

func (t Time) Year() int {} // 返回年

func (t Time) Month() Month {} // 月

func (t Time) Day() int {} // 日

func (t Time) Weekday() Weekday {} // 星期

func (t Time) ISOWeek() (year, week int) {} // 返回年,星期范围编号

func (t Time) Clock() (hour, min, sec int) {} // 返回时间的时分秒

func (t Time) Hour() int {} // 返回小时

func (t Time) Minute() int {} // 分钟

func (t Time) Second() int {} // 秒

func (t Time) Nanosecond() int {} // 纳秒

func (t Time) YearDay() int {} // 一年中对应的天

func (t Time) Location() *Location {} // 时间的时区

func (t Time) Zone() (name string, offset int) {} // 时间所在时区的规范名和想对UTC 时间偏移量

func (t Time) Unix() int64 {} // 时间转为时间戳

func (t Time) UnixNano() int64 {} // 时间转为时间戳(纳秒)

示例:

package main

import (

    "fmt"

    "time"

)

func main(){

    now := time.Now()

    fmt.Println(now.Year(), now.Month(), now.Day())

}

/* 运行结果

H:\Go\src\go_dev\day3\time>go run time_info.go

2018 October 7

H:\Go\src\go_dev\day3\time>

*/

时间格式化

用(Time) Format格式化

首先,默认提供这么多格式,都定义在time的常量里:

const (

    ANSIC       = "Mon Jan _2 15:04:05 2006"

    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"

    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"

    RFC822      = "02 Jan 06 15:04 MST"

    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone

    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"

    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"

    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone

    RFC3339     = "2006-01-02×××5:04:05Z07:00"

    RFC3339Nano = "2006-01-02×××5:04:05.999999999Z07:00"

    Kitchen     = "3:04PM"

    // Handy time stamps.

    Stamp      = "Jan _2 15:04:05"

    StampMilli = "Jan _2 15:04:05.000"

    StampMicro = "Jan _2 15:04:05.000000"

    StampNano  = "Jan _2 15:04:05.000000000"

)

这些预定义的常量,其实使用的都是一个特定的时间。这个时间是Unix time 1136239445,因为MST是GMT-0700,所以这个指定的时间也可以看做:

01/02 03:04:05PM '06 -0700

选一个上面的常量字符串,或者自己根据这个特定时间照着写一个格式化字符串,调用Format方法进行格式化:

package main

import (

    "fmt"

    "time"

)

func main(){

    now := time.Now()

    fmt_now := now.Format(time.RFC1123Z)

    fmt.Println(fmt_now)

    fmt_str := "2006/1/2 15:04:05 Monday"

    fmt.Println(now.Format(fmt_str))

    fmt.Println(now.Format("01/02 03:04:05PM '06 -0700"))

}

/* 运行结果

H:\Go\src\go_dev\day3\time>go run format.go

Sun, 07 Oct 2018 13:39:27 +0800

2018/10/7 13:39:27 Sunday

10/07 01:39:27PM '18 +0800

H:\Go\src\go_dev\day3\time>

*/

用fmt.Printf格式化

package main

import (

    "fmt"

    "time"

)

func main(){

    now := time.Now()

    fmt.Printf(

        "%02d/%02d/%02d %02d:%02d:%02d %v",

        now.Year(),

        now.Month(),

        now.Day(),

        now.Hour(),

        now.Minute(),

        now.Second(),

        now.Weekday(),

    )

}

时间段

time.Duration 类型代表两个时间点之间经过的纳秒数,可表示的最长时间段约为290年。

涉及到下面这些常量:

const (

    Nanosecond  Duration = 1

    Microsecond          = 1000 * Nanosecond

    Millisecond          = 1000 * Microsecond

    Second               = 1000 * Millisecond

    Minute               = 60 * Second

    Hour                 = 60 * Minute

)

程序运行时间

用time.Now()获取程序运行之前和运行之后的时间。然后用Sub方法计算出这2个时间之间间隔的时间(时间段),最后打印出来。

另外还有个方法,就是直接获取两个时间点的时间戳的纳秒数,然后相减得到的就是运行时间的纳秒数。

package main

import (

    "fmt"

    "time"

)

func test(){

    var sum int

    for i := 1; i <= 100; i++ {

        sum += i

        time.Sleep(2 * time.Nanosecond)  // 每一步都计算后都加上2纳秒的延迟

    }

    fmt.Println(sum)

}

func main(){

    t0 := time.Now()

    test()

    t1 := time.Now()

    fmt.Printf("程序执行的时间是: %v\n", t1.Sub(t0))

    t := t1.Sub(t0)

    fmt.Println("换算成微秒", float64(t) / float64(time.Microsecond), "微秒")

    // 另外一个方法

    nt0 := time.Now().UnixNano()  // 当前时间戳的纳秒数

    test()

    nt1 := time.Now().UnixNano()

    fmt.Println("程序执行时间是:", nt1 - nt0, "纳秒")

}

/* 运行结果

H:\Go\src\go_dev\day3\time>go run sub_time.go

5050

程序执行的时间是: 148.3263ms

换算成微秒 148326.3 微秒

5050

程序执行时间是: 150043400 纳秒

H:\Go\src\go_dev\day3\time>

*/

做单位换算的时候,需要注意数据类型。

参与除法的两个数如果数据类型不同会报错。或者像 n / 1000 这样,n是变量一定是有类型的,1000的数据类型会根据n的类型做类型推导。

如果是int类型相除,得到的结果也是int类型。如果要保留小数,就要浮点类型,比如float64。不能对结果做类型转换(此时小数位已经丢失了),而是要对做参与除法的int类型做类型转换。

指针类型

普通类型,变量存的就是值,也叫值类型。

获取变量的地址,用&。比如:var a int,获取a的地址:&a

指针类型,变量存的是一个地址,这个地址存的才是值

获取指针类型所指向的值,用*。比如:var p *int,获取p指向的值:*p

package main

import "fmt"

func main() {

    var a int=5

    fmt.Println("变量a的地址:", &a)

    var p *int = &a  // 定义指针类型p,p的值就是a的地址

    fmt.Println("通过p拿到a的值:", *p)  // 要获取p指向的值,用*p

    *p = 100  // 通过*p修改值

    fmt.Println("a现在的值是:", a)  // 打印确认,上面改的就是a的值

}

/* 运行结果

H:\Go\src\go_dev\day3\pointer>go run check_pointer.go

变量a的地址: 0xc04204e058

通过p拿到a的值: 5

a现在的值是: 100

H:\Go\src\go_dev\day3\pointer>

*/

流程控制

if / else 分支判断

语法:

// 第一种

if condition1 {

}

// 第二种

if condition1 {

} else {

}

// 第三种

if condition 1 {

} else if condition2 {

} else if condition3 {

} else {

}

switch case 语句

语法:

switch var1 {

    case val1:

    case val2:

    case val3. val4 :

    default:

}

go语言了,case结尾不需要加break,并且语句也不会继续匹配后面的条件。如果需要继续匹配之后的条件,可以添加fallthrough关键字

package main

import "fmt"

func main() {

    fmt.Print("请输入:")

    var choices int

    fmt.Scanf("%d", &choices)

    switch choices {

    case 1:

        fmt.Println("你的选择是一")

    case 2:

        fmt.Println("你的选择是二")

    case 3, 4:

        fmt.Println("你的选择是三或四")

    default:

        fmt.Println("没变对应的选择")

    }

}

switch后面的表达式不是必需的。在此种情况下,整个switch结构与多个 if / else 的逻辑作用等同:

package main

import "fmt"

func main() {

    fmt.Print("请输入分数(1-100):")

    var score int

    fmt.Scanf("%d", &score)

    fmt.Println(score)

    switch {

    case score > 100:

        fmt.Println("分数超出范围")

    case score == 100:

        fmt.Println("你的成绩是:A+满分")

    case score > 90:

        fmt.Println("你的成绩是: A")

    case score > 80:

        fmt.Println("你的成绩是:B")

    case score > 70:

        fmt.Println("你的成绩是:C")

    case score > 60:

        fmt.Println("你的成绩是:D")

    case score > 0:

        fmt.Println("你的成绩是:E")

    default:

        fmt.Println("0分或者输入无效")

    }

}

练习(猜数字程序)

猜数字,写一个程序,随机生成一个0到100的整数n。然后用户在终端输入数字,如果和n相等,则提示用户猜对了。如果不相等,则提示用户,大了还是小了。

package main

import (

    "fmt"

    "math/rand"

    "time"

)

func create_num() int {

    rand.Seed(time.Now().UnixNano())

    return rand.Intn(101)

}

func main() {

    n := create_num()

    //fmt.Println(n)

    var input int

    var flag bool = false

    // 循环结构还没讲,先用for写个死循环

    for {

        fmt.Print("请输入数字(0-100):")

        fmt.Scanf("%d\n", &input)

        switch {

        case input == n:

            fmt.Println("猜对了", n)

            //break  // case里不需要break,似乎也支持写break,不会报错

            flag = true  // 上面的break是switch里的break,无法跳出for循环。等switch结束后判断flag

        case input > n:

            fmt.Println("大了,再小点")

        case input < n:

            fmt.Println("小了,再大点")

        }

        if flag {

            break  // 这里是switch外面,这里的break是跳出for循环的

        }

    }

}

for 语句

写法1

for 初始化语句; 条件判断; 变量修改 {

}

练习

写一个程序,在终端打印如下图形

A

AA

AAA

AAAA

AAAAA

下面有两个实现方法:

package main

import (

    "fmt"

    "strings"

)

// 自己的思路

func print1(tag string, n int) {

    for i := 1; i <= n; i++ {

        fmt.Println(strings.Repeat(tag, i))

    }

}

// 讲师的例子,这段是学for循环,这个方法把for循环用的更透

func print2(tag string, n int) {

    for i := 1; i <= n; i++ {

        for j := 0; j < i; j++ {

            fmt.Print(tag)

        }

        fmt.Println()

    }

}

func main(){

    print1("A", 5)

    print2("B", 6)

}

写法2

for 条件 {

}

// 如果条件时true,就是个死循环

for true {

}

// 上面可以简写,省掉true

for {

}

写法3

for range 用来遍历数组、slice、map、chan

用下面的例子举例了,遍历后获得2个值,i是下标,v是值:

package main

import (

    "fmt"

)

func main() {

    s := "abcdefg"  // 数组什么的还没学,不过字符串也能遍历

    for i, v := range s {

        // fmt.Println(i, v)  // 遍历之后变成字符了

        fmt.Println(i, string(v)) 

    }

}

break: 跳出当前循环

continue :结束本次循环,进入下一次继续

label

for、switch 或 select 语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:)结尾的单词(gofmt 会将后续代码自动移至下一行)。标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母。

在多级嵌套语句中,非常有用。下面例子的continue后面指定了标签,这样就直接作为外层的for循环的continue操作:

package main

import "fmt"

func main() {

LABEL1:

    for i := 0; i <= 5; i++ {

        for j := 0; j <= 5; j++ {

            if j == 4 {

                continue LABEL1

            }

            fmt.Printf("i is: %d, and j is: %d\n", i, j)

        }

    }

}

break 标签的效果和 continue 是一样的,差别只是break和continue的动作。

下面是goto的示例:

package main

func main() {

    i:=0

    HERE:

        print(i)

        i++

        if i==5 {

            return

        }

        goto HERE

}

上面的这种是逆向的使用goto,使用标签和 goto 语句是不被鼓励的。而且总有更加可读的替代方案来实现相同的需求。

如果必须使用 goto ,应当只使用正序的标签(标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现定义新变量的语句,否则会导致编译失败。

函数

声明语法:func 函数名 ([参数列表]) [(返回值列表)] {}

// 没有参数也没有返回值

func test() {

}

// 有参数,没有返回值  

func test(a int, bint) {

}

// 有参数,有一个返回值

func test(a int, b int) int {

}

// 有参数,有多个返回值

func test(a int, b int) (int, int) {

}

// 可以省略前面的参数类型

fun test(a, b int) {

}

函数的特点

Golang函数的特点:

不支持重载,一个包不能有两个名字一样的函数

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

支持匿名函数

多返回值

函数赋值给变量使用的示例

package main

import "fmt"

// 函数也是一种类型,下面用type做一个类型定义

// type关键字,什么要做定义

// add_func,定义的名字是add_func

// func,函数也是一种类型,所以要定义的类型是函数

// 后面是这个函数类型的参数类型和返回值类型

type add_func func(int, int) int

// 写一个函数,函数的类型和上面的自定义类型一样

func add(a, b int) int {

return a + b

}

// 再写一个减法的吧

func sub(a, b int) int {

return a - b

}

// 在写一个函数,第一个参数是函数类型

// 后面的参数是要用来给参数1的函数进行计算的

func operator(op add_func, a, b int) int {

return op(a, b)

}

// 主函数

func main() {

// 第一个变量add,是一个函数

sum := operator(add, 10, 20)

fmt.Println(sum)

var (

    c add_func

    res int

)

// 函数也可以赋值给变量

c = add

res = operator(c, 110, 40)

fmt.Println(res)

c = sub

// 这次调用方法不变,但是变量c变量,换了一个函数进行计算

res = operator(c, 110, 40)

fmt.Println(res)

}

## 参数传递的方式

函数参数传递的方式下面2种:

+ 值传递:各种基本数据类型

+ 引用传递:map、slice(切片)、chan(管道)、指针、interface

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

## 命名返回值的名字

也可以在声明函数的时候给返回值命名:

```go

func add (a, b int) (c int) {

    c = a + b  // 这里变量c不用声明,可以直接使用

    return  // 直接return就好了,也不用说明要返回什么

}

多返回值的函数中,更加方法:

func calc (a, b int) (sum int, avg int) {

    sum = a + b

    avg = sum / 2

    return

}

忽略返回值

使用下划线(_),可以忽略不使用的返回值:

func calc (a, b int) (sum int, avg int) {

    sum = a + b

    avg = sum / 2

    return

}

func main() {

    sum, _ := calc(10, 20)

}

可变参数

在参数类型前加3个点,可以把参数定义为可变参数:

// 0个或多个参数

func sum(arg ...int) int {

}

// 1个或多个参数

func sum(a int, arg ...int) int {

}

// 2个或多个参数

func sum(a int, b int, c ...int) int {

}

变量 arg 是一个 slice (切片)。可以理解为数组,通过 arg[index] 访问所有参数,通过 len(arg) 来判断传递参数的个数

使用可变参数求和,可以传1个或多个int参数:

package main

import "fmt"

func sum(a int, arg ...int) (res int) {

    res = a

    for i := 0; i < len(arg); i++ {

        res += arg[i]

    }

    return

}

func main() {

    var res int

    res = sum(1)

    fmt.Println(res)

    res = sum(1, 2)

    fmt.Println(res)

    res = sum(1, 2, 3)

    fmt.Println(res)

}

defer用途

当函数返回时,会执行defer语句。因此,可以用来做资源清理

多个defer语句,按先进后出的方式执行

defer语句中的变量的值,在defer声明时就决定了

举例说明:

package main

import "fmt"

func main() {

    var i int = 1

    // 下面这句并不立刻执行,而是等函数返回时再执行

    // 但是函数的参数的值,在此时就已经决定了,也就是i=1

    defer fmt.Println(i)

    i = 22

    fmt.Println(i)

}

/* 执行结果

H:\Go\src\go_dev\day3\function>go run defer_demo.go

22

1

H:\Go\src\go_dev\day3\function>

*/

用途

关闭文件句柄

操作前给资源上锁,用defer释放锁

数据库连接释放

以关闭文件句柄举例:

func read () {

    file := open(filename)  // 打开了一个文件

    defer file.Close()  // 紧接着用defer关闭这个文件

    // 之后再写各种文件操作

    // 到文件返回时,defer就会执行,关闭这个文件句柄

}

使用defer不踩坑

函数返回的过程是这样子的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。

defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。

课后作业

一、编写程序,在终端输出九九乘法表

二、一个数如果恰好等于他的因子只和,那么这个数就称为“完数”。例如6=1+2+3。编程找出1000以内的所有完数。

三、输入一个字符串,判断其是否为回文。回文字符串是指从左到右读和从右到左读完全相同的字符串。

四、输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。

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

©著作权归作者所有:来自51CTO博客作者骑士救兵的原创作品,如需转载,请注明出处,否则将追究法律责任


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP