第4篇:基础数据类型

基础数据类型

  • 整型,用于表示整数。
  • 浮点型,用于表示小数。
  • 复数,用来表示复数。
  • 布尔型,用于表示真/假。
  • 字符串,用于表示文本信息。

复合数据类型

  • 数组,用于表示多个数据(数据集合)
  • 结构体,用于自定义一些数据集合和方法。

引用数据类型

  • 指针,用于表示内存地址的类型。
  • 切片,用于表示多个数据(数据集合)
  • 字典,用于表示键值对集合。
  • 函数,用于将一个语句序列打包为一个单元,然后可以从程序中其它地方多次调用。
  • 通道,channel,是goroutine中的通信机制

接口数据类型

  • 接口,用于约束和泛指数据类型。

虽然从底层而言,所有的数据都是由比特组成,但计算机一般操作的是固定大小的数,如整数、浮点数、比特数组、内存地址等。进一步将这些数组织在一起,就可表达更多的对象,例如数据包、像素点、诗歌,甚至其他任何对象。Go语言提供了丰富的数据组织形式,这依赖于Go语言内置的数据类型。这些内置的数据类型,兼顾了硬件的特性和表达复杂数据结构的便捷性。Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。基础类型包括:整型、浮点型、复数、字符串和布尔型。复合数据类型:数组和结构体,是通过组合简单类型,来表达更加复杂的数据结构。引用类型包括指针、切片、字典、函数、通道,虽然数据种类很多,但它们都是对程序中一个变量或状态的间接引用。这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。本节我们将介绍基础数据类型,以下是Golang中内置的数据类型详细展示。

Golang支持更明确的数字类型命名,支持 Unicode,支持常用数据结构。

类型 长度(字节) 默认值 说明
bool 1 false
byte 1 0 uint8
rune 4 0 Unicode Code Point, int32
int, uint 4或8 0 32 或 64 位
int8, uint8 1 0 -128 ~ 127, 0 ~ 255,byte是uint8 的别名
int16, uint16 2 0 -32768 ~ 32767, 0 ~ 65535
int32, uint32 4 0 -21亿~ 21亿, 0 ~ 42亿,rune是int32 的别名
int64, uint64 8 0
float32 4 0.0
float64 8 0.0
complex64 8
complex128 16
uintptr 4或8 以存储指针的 uint32 或 uint64 整数
array 值类型
struct 值类型
string “” UTF-8 字符串
slice nil 引用类型
map nil 引用类型
channel nil 引用类型
interface nil 接口
function nil 函数

支持八进制、 十六进制,以及科学记数法。标准库 math 定义了各数字类型取值范围。

a, b, c, d := 071, 0x1F, 1e9, math.MinInt16

空指针值 nil,而非C/C++ NULL。

一、整型

Go中的整型分为有符号和无符号两大类,有符号的包含负值,无符号不包含负值。

有符号整型:

  • int8(-128 -> 127)
  • int16(-32768 -> 32767)
  • int32(-2,147,483,648 -> 2,147,483,647)
  • int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
  • int
    • 在 32 位操作系统上使用 32 位(-2,147,483,648 -> 2,147,483,647) 2**32
    • 在 64 位操作系统上使用 64 位(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,80)2**64

无符号整数:

  • uint8(0 -> 255)
  • uint16(0 -> 65,535)
  • uint32(0 -> 4,294,967,295)
  • uint64(0 -> 18,446,744,073,709,551,615)
  • uint
    • 在 32 位操作系统上使用32 位(0 -> 4,294,967,295) 2**32
    • 64 位操作系统上使用 64 位(0 -> 18,446,744,073,709,551,615) 2**64

不同整型可表示的数据范围不同,我们需要根据自己的需求来选择适合的类型。

1.1 整型之间的转换

data := intXXX(其他整型)
var v1 int8 = 10
var v2 int16 = 18
v3 := int16(v1) + v2
fmt.Println(v3, reflect.TypeOf(v3))

注意:

  • 地位转向高位,没问题。

  • 高位转向低位,可能有问题

    var v1 int16 = 130
    v2 := int8(v1)
    fmt.Println(v2)

1.2 整型与字符串的转换

package main

func main() {

    // 整型转换为字符串类型
    /*
        v1 := 19
        result := strconv.Itoa(v1)
        fmt.Println(result, reflect.TypeOf(result))
        var v2 int8 = 17
        data := strconv.Itoa(int(v2))
        fmt.Println(data,reflect.TypeOf(data))
    */

    // 字符串转换为整型:转换后是int类型;可能存在错误
    /*
        v1 := "666"
        result, err := strconv.Atoi(v1)
        if err == nil {
            fmt.Println("转换成功", result,reflect.TypeOf(result))
        } else {
            fmt.Println("转换失败")
        }

    */
}

1.3 进制转换

  • Go代码中:
    • 十进制,整型的方式存在。
    • 其他进制,是以字符串的形式存在。
  • 整形,10进制数。

十进制转换为其他进制:

package main

import (
    "fmt"
    "reflect"
    "strconv"
)

func main() {
    v1 := 99
    // 整型(十进制)转换为:二进制、八进制、十六进制
    r1 := strconv.FormatInt(int64(v1), 16)
    fmt.Println(r1, reflect.TypeOf(r1))

}

其他进制转换为十进制:

package main

import (
    "fmt"
    "reflect"
    "strconv"
)

func main() {
    // data,要转换的文本
    // 2,把文档当做二进制去转换成 十进制(整型)
    // 16,转换的过程中对结果进行约束
    // 结果:如果转换成功,则将err设置为nil,result则永远以int64的类型返回。
    data := "1001000101"
    result, err := strconv.ParseInt(data, 2, 0)
    fmt.Print(result, err, reflect.TypeOf(result))
}

提醒:通过ParseInt将字符串转换为10进制时,本质上与Atoi是相同的。

练习题:

  • 将十进制 14 用 转换成16进制的字符串。
  • 将 2进制 “10011” 转换成 10进制的整型。
  • 将 2进制 “10011” 转换成 16 进制的字符串。

1.4 常见数学运算

package main

import (
    "fmt"
    "math"
)

func main() {
    // 整型
    fmt.Println(666)
    fmt.Println(6 + 9)
    fmt.Println(6 - 9)
    fmt.Println(6 * 9)
    fmt.Println(16 / 9) // 商
    fmt.Println(16 % 9) // 余数

    fmt.Println(math.Abs(-19))                // 取绝对值
    fmt.Println(math.Floor(3.14))             // 向下取整
    fmt.Println(math.Ceil(3.14))              // 向上取整
    fmt.Println(math.Round(3.3478))           // 就近取整
    fmt.Println(math.Round(3.5478*100) / 100) // 保留小数点后两位
    fmt.Println(math.Mod(11, 3))              // 取余数,同11 % 3
    fmt.Println(math.Pow(2, 5))               // 计算次方,如:2的5次方
    fmt.Println(math.Pow10(2))                // 计算10次方,如:2的10次方
    fmt.Println(math.Max(1, 2))               // 两个值,取较大值
    fmt.Println(math.Min(1, 2))               // 两个值,取较小值
    // ...
}

1.5 指针/nil/声明变量/new

  • 声明变量

    var v1 int 
    v2 := 999

    image-20200608195751136

  • 指针

    var v3 *int    
    v4 := new(int)   

    image-20200608200651520

  • new关键字

    new用于创建内存并进行内部数据的初始化,并返回一个指针类型。
  • nil

    nilgo语言中的空值。
    var v100 *int
    var v101 *int8

问题:

  • 为什么要有指针?

    为了节省内存,不重复开辟空间去存储数据
  • int*int是两种不同的数据类型,不等

1.6 超大整型

第一步:创建对象

创建超大整型对象

// 第一步:创建一个超大整型的一个对象
var v1 big.Int
var v2 big.Int

// 第二步:在超大整型对象中写入值
v1.SetInt64(9223372036854775807)
fmt.Println(v1)

v2.SetString("92233720368547758089223372036854775808", 10)
fmt.Println(v2)
// 第一步:创建一个超大整型的一个对象
v3 := new(big.Int)
v4 := new(big.Int)

// 第二步:在超大整型对象中写入值
v3.SetInt64(9223372036854775807)
fmt.Println(v3)

v4.SetString("92233720368547758089223372036854775808", 10)
fmt.Println(v4)

推荐:使用指针的方式,即:使用new来进行创建和初始化。

第二步:加减乘除

超大对象进行加减乘除

n1 := new(big.Int)
n1.SetInt64(89)

n2 := new(big.Int)
n2.SetInt64(99)

result := new(big.Int)
result.Add(n1, n2)

fmt.Println(result)
n1 := big.NewInt(89)

n2 := big.NewInt(99)

result := new(big.Int)
result.Add(n1, n2)

fmt.Println(result)

其他:

v1 := big.NewInt(11)
v2 := big.NewInt(3)
result := new(big.Int)

// 加
result.Add(v1, v2)
fmt.Println(result)
// 减
result.Sub(v1, v2)
fmt.Println(result)

// 乘
result.Mul(v1, v2)
fmt.Println(result)

// 除(地板除,只能得到商)
result.Div(v1, v2)
fmt.Println(result)

// 除,得商和余数
minder := new(big.Int)
result.DivMod(v1, v2, minder)
fmt.Println(result, minder)
第三步:关于结果
n1 := new(big.Int)
n1.SetString("92233720368547758089223372036854775808", 10)

n2 := new(big.Int)
n2.SetString("11111192233720368547758089223372036854775808", 10)

result := new(big.Int)
result.Add(n1, n2)

fmt.Println(result.String())

最后建议

  • 尽量new方式去初始化并返回一个指针类型的方式。

  • 易错的点(int类型和*int类型)

    var v1 big.Int
    v1.SetString("92233720368547758089223372036854775808", 10)
    
    var v2 big.Int
    v2.SetString("2", 10)
    
    //result := new(big.Int)
    var result big.Int
    result.Add(&v1, &v2)
    fmt.Println(result.String())

二、浮点型

浮点数,计算机中小数的表示方式,如:3.14

Go语言中提供了两种浮点型:

  • float32,用32位(4个字节)来存储浮点型。
  • float64,用64位(8个字节)来存储浮点型。
package main

import "fmt"

func main() {
    var v1 float32
    v1 = 3.14
    v2 := 99.9
    v3 := float64(v1) + v2
    fmt.Println(v1, v2, v3)
}

2.1 非精确

float类型,计算机中小数的非精确的表示方式,如:3.14

package main

import "fmt"

func main() {
    var v1 float32
    v1 = 3.14
    v2 := 99.9
    v3 := float64(v1) + v2
    fmt.Println(v1, v2, v3)

    v4 := 0.1
    v5 := 0.2
    result := v4 + v5
    fmt.Println(result)

    v6 := 0.3
    v7 := 0.2
    data := v6 + v7
    fmt.Println(data)
}
3.14 99.9 103.04000010490418
0.30000000000000004
0.5

2.2 float底层存储原理

var price float32 = 0.29
第一步:浮点型转换为二进制
  • 整数部分,直接转换为二进制(10进制转换为2进制),即:100111

  • 小数部分,让小数部分乘以2,结果小于1则将结果继续乘以2,结果大于1则讲结果-1继续乘以2,结果等于1则结束。

    0.29 * 2 = 0.58       // 小于1,则继续乘
    0.58 * 2 = 1.16          // 大于1,则减1继续乘
    0.16 * 2 = 0.32          // 小于1,则继续乘
    0.32 * 2 = 0.64       // 小于1,则继续乘
    0.64 * 2 = 1.28       // 大于1,则减1继续乘
    0.28 * 2 = 0.56       // 小于1,则继续乘
    0.56 * 2 = 1.12       // 大于1,则减1继续乘
    0.12 * 2 = 0.24       // 小于1,则继续乘    
    0.24 * 2 = 0.48       // 小于1,则继续乘
    0.48 * 2 = 0.96       // 小于1,则继续乘
    0.96 * 2 = 1.92       // 大于1,则减1继续乘
    0.92 * 2 = 1.84       // 大于1,则减1继续乘
    0.84 * 2 = 1.68       // 大于1,则减1继续乘
    0.68 * 2 = 1.36       // 大于1,则减1继续乘
    0.36 * 2 = 0.72       // 小于1,则继续乘
    0.72 * 2 = 1.44       // 大于1,则减1继续乘
    0.44 * 2 = 0.88       // 小于1,则继续乘
    0.88 * 2 = 1.76       // 大于1,则减1继续乘
    0.76 * 2 = 1.52       // 大于1,则减1继续乘
    0.52 * 2 = 1.04       // 大于1,则减1继续乘
    0.04 * 2 = 0.08       // 小于1,则继续乘
    0.08 * 2 = 0.16       // 小于1,则继续乘
    0.16 * 2 = 0.32       // 小于1,则继续乘(与第三行相同,这样会一直循环执行下去)
    ...
    
    将相乘之后等结果的整数部分拼接起来,所以 0.29的 二进制表示:010010100011110101110000101000111...

所以,最终39.29的二进制表示为:100111.010010100011110101110000101000111...

第二步:科学计数法表示
100111.010010100011110101110000101000111...

1.00111010010100011110101110000101000111…∗251.00111010010100011110101110000101000111…∗25

第三步:存储

以float32为例来进行存储,用32位来存储浮点型。

image-20200608235249835

  • sign,用1位来表示浮点数正负,0表示正数;1表示负数。
  • exponent,用8位来表示共有256种(0~255),含正负值(-127 ~ 128)。例如:5想要存储到exponent位的话,需要让 5 + 127 = 132,再讲132转换二进制,存储到exponent。(132的二进制是:01000010)
  • fraction,存储小数点后的所有数据。

float64和float32类似,只是用于表示各部分的位数不同而已,其中:sign=1位exponent=11位fraction=52位,也就意味着可以表示的范围更大了。

2.3 decimal

Go语言内部没有decimal。

第三方包,则需要在本地的Go环境中先安装再使用。第三方包源码地址:https://github.com/shopspring/decimal

第一步:安装第三发的包
go get github.com/shopspring/decimal 

命令执行完成之后,在 $GOPATH/src的目录下就会出现 github/shopspring/decimal的目录,这就是第三方模块安排的位置。

第二步:使用decimal包
package main

import (
    "fmt"
    "github.com/shopspring/decimal"
)

func main() {

    var v1 = decimal.NewFromFloat(0.0000000000019)
    var v2 = decimal.NewFromFloat(0.29)

    var v3 = v1.Add(v2)

    var v4 = v1.Sub(v2)

    var v5 = v1.Mul(v2)

    var v6 = v1.Div(v2)

    fmt.Println(v3, v4, v5, v6) // 输出:0.2900000000019(也可以调用String方法)


    var price = decimal.NewFromFloat(3.4626)
    var data1 = price.Round(1)         // 保留小数点后1位(四舍五入)
    var data2 = price.Truncate(1)    // 保留小数点后1位
    fmt.Println(data1, data2) // 输出:3.5  3.4
}

三、复数

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的realimag函数分别返回复数的实部和虚部:

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0:

fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1

在常量算术规则下,一个复数常量可以加到另一个普通数值常量(整数或浮点数、实部或虚部),我们可以用自然的方式书写复数,就像1+2i或与之等价的写法2i+1。上面x和y的声明语句还可以简化:

x := 1 + 2i
y := 3 + 4i

复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的(译注:浮点数的相等比较是危险的,需要特别小心处理精度问题)。

math/cmplx包提供了复数处理的许多函数,例如求复数的平方根函数和求幂函数。

fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)"

四、布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)false(假)两个值。if和for语句的条件部分都是布尔类型的值,并且==和<等比较操作也会产生布尔型的值。一元操作符!对应逻辑非操作,因此!true的值为false,更罗嗦的说法是(!true==false)==true,虽然表达方式不一样,不过我们一般会采用简洁的布尔表达式,就像用x来表示x==true

布尔值可以和&&(AND)和||(OR)操作符结合,并且有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值,因此下面的表达式总是安全的:

s != "" && s[0] == 'x'

其中s[0]操作如果应用于空字符串将会导致panic异常。

因为&&的优先级比||高(助记:&&对应逻辑乘法,||对应逻辑加法,乘法比加法优先级要高),下面形式的布尔表达式是不需要加小括弧的:

if 'a' <= c && c <= 'z' ||
    'A' <= c && c <= 'Z' ||
    '0' <= c && c <= '9' {
    // ...ASCII letter or digit...
}

布尔值并不会隐式转换为数字值0或1,反之亦然。必须使用一个显式的if语句辅助转换:

i := 0
if b {
    i = 1
}

如果需要经常做类似的转换, 包装成一个函数会更方便:

// btoi returns 1 if b is true and 0 if false.
func btoi(b bool) int {
    if b {
        return 1
    }
    return 0
}

数字到布尔型的逆转换则非常简单, 不过为了保持对称, 我们也可以包装一个函数:

// itob reports whether i is non-zero.
func itob(i int) bool { return i != 0 }

注意:

  • 布尔类型变量的默认值为false。
  • Go 语言中不允许将整型强制转换为布尔型.
  • 布尔型无法参与数值运算,也无法与其他类型进行转换。

示例1

package main

import "fmt"

func main() {    
    // 布尔类型,真假
    fmt.Println(1 > 2) // false  假
    fmt.Println(1 < 2) // true   真
    fmt.Println(1 == 2)
    fmt.Println(1 >= 2)
    fmt.Println("Michael" == "666")

    if 2 > 1 {
        fmt.Println("反者道之动")
    } else {
        fmt.Println("孙子")
    }
}

示例2

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 字符串 转换 布尔类型
    // true:"1", "t", "T", "true", "TRUE", "True"
    // false:"0", "f", "F", "false", "FALSE", "False"
    //false,err错误信息
    v1, err := strconv.ParseBool("t")
    fmt.Println(v1, err)

    // 布尔类型 转换 字符串
    v2 := strconv.FormatBool(false)
    fmt.Println(v2)
}

五、字符串

Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为双引号(“)中的内容,可以在Go语言的源码中直接添加非ASCII码字符,例如:

s1 := "hello"
s2 := "你好"

1. 字符串转义符

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。

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

举个例子,我们要打印一个Windows平台下的一个文件路径:

package main
import (
    "fmt"
)
func main() {
    fmt.Println("str := \"c:\\pprof\\main.exe\"")
}

2. 多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符:

s1 := `第一行
第二行
第三行
`
fmt.Println(s1)

反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。

3. 字符串的底层存储

计算机中所有的操作和数据最终都是二进制,即:1000100001011…

Go语言中的字符串是utf-8编码的序列。

package main

import "fmt"

func main() {
    // unicode字符集:文字 -> 码点(ucs4, 4个字节表示)
    // utf-8编码,对unicode字符集的码点进行编码最终得到:1000100001
    var name string = "张亚飞"
    fmt.Printf(name)
}

课上代码示例:

package main

import (
    "fmt"
    "strconv"
    "unicode/utf8"
)

func main() {
    // 1. 本质是utf-8编码的序列
    var name string = "张亚飞"

    // 张 => 11100101 10111100 10100000
    fmt.Println(name[0], strconv.FormatInt(int64(name[0]), 2))  // 229 11100101
    fmt.Println(name[1], strconv.FormatInt(int64(name[1]), 2))  // 188 10111100
    fmt.Println(name[2], strconv.FormatInt(int64(name[2]), 2))  // 160 10100000

    // 亚 => 11100100 10111010 10011010
    fmt.Println(name[3], strconv.FormatInt(int64(name[3]), 2))  // 228 11100100
    fmt.Println(name[4], strconv.FormatInt(int64(name[4]), 2))  // 186 10111010
    fmt.Println(name[5], strconv.FormatInt(int64(name[5]), 2))  // 154 10011010

    // 飞 => 11101001 10100011 10011110
    fmt.Println(name[6], strconv.FormatInt(int64(name[6]), 2))  // 233 11101001
    fmt.Println(name[7], strconv.FormatInt(int64(name[7]), 2))  // 163 10100011
    fmt.Println(name[8], strconv.FormatInt(int64(name[8]), 2))  // 158 10011110

    // 2. 获取字符串的长度:9(字节长度)
    fmt.Println(len(name))  // 9

    // 3. 字符串转换为一个"字节集合"
    byteSet := []byte(name)
    fmt.Println(byteSet) //[229 188 160 228 186 154 233 163 158]

    // 4. 字节的集合转换为字符串
    byteList := []byte{229,188,160,228,186,154,233,163,158}
    targetString := string(byteList)
    fmt.Println(targetString)  // 张亚飞

    // 5. 将字符串转换为 unicode字符集码点的集合 
    tempSet := []rune(name)
    fmt.Println(tempSet) // [24352 20122 39134]
    fmt.Println(tempSet[0], strconv.FormatInt(int64(tempSet[0]), 16))  // 24352 5f20
    fmt.Println(tempSet[1], strconv.FormatInt(int64(tempSet[1]), 16))   // 20122 4e9a
    fmt.Println(tempSet[2], strconv.FormatInt(int64(tempSet[2]), 16))  // 39134 98de

    // 6. "rune集合" 转换 为字符串
    runeList := []rune{24352, 20122, 39134}
    targetName := string(runeList)
    fmt.Println(targetName)  // 张亚飞

    // 7. 长度的处理(获取字符长度)
    runeLength := utf8.RuneCountInString(name)
    fmt.Println(runeLength)   // 3
}

3. 字符串的常用操作

  • len(string):求字符串长度
  • strings.HasPrefix(s string, prefix string) bool:判断字符串s是否以prefix开头 。
  • strings.HasSuffix(s string, suffix string) bool:判断字符串s是否以suffix结尾。
  • strings.Contains(s string, sunstr string) bool:判断字符串s是否包含子字符串substr
  • strings.Index(s string, str string) int:判断str在s中首次出现的位置,如果没有出现,则返回-1
  • strings.LastIndex(s string, str string) int:判断str在s中最后出现的位置,如果没有出现,则返回-1
  • strings.Replace(str string, old string, new string, n int) string:字符串替换
  • strings.Count(str string, substr string)int:字符串计数
  • strings.Repeat(str string, count int)string:重复count次str
  • strings.ToLower(str string)string:转为小写
  • strings.ToUpper(str string)string:转为大写
  • strings.TrimSpace(str string):去掉字符串首尾空白字符
  • strings.Trim(str string, cut string):去掉字符串首尾cut字符
  • strings.TrimLeft(str string, cut string):去掉字符串首cut字符
  • strings.TrimRight(str string, cut string):去掉字符串首cut字符
  • strings.Field(str string):返回str空格分隔的所有子串的slice
  • strings.Split(str string, split string):返回str split分隔的所有子串的slice
  • strings.Join(s1 []string, sep string):用sep把s1中的所有元素链接起来
  • strings.Itoa(i int):把一个整数i转成字符串
  • strings.Atoi(str string)(int, error):把一个字符串转成整数

示例

package main

import (
    "fmt"
    "strconv"
    "strings"
    "unicode/utf8"
)

func main() {
    var name = "张亚飞"
    fmt.Println( len(name) )                    // 获取 字节 长度,输出:9
    fmt.Println( utf8.RuneCountInString(name) ) // 获取字符长度,输出:3

    res := strings.HasPrefix(name, "张")
    fmt.Println(res) // 输出:true

    res = strings.HasSuffix(name, "飞")
    fmt.Println(res) // 输出:true

    name = "老子为什么写道德经"
    res = strings.Contains(name, "老子")
    fmt.Println(res) // 输出:true

    str := "   hello world   \n"

    result := strings.ToLower(str)
    fmt.Println("tolower:", result)  // tolower:    hello world 

    result = strings.ToUpper(str)
    fmt.Println("toupper:", result) // toupper:    HELLO WORLD 

    str2 := strings.Replace(str, "world", "you", 1)
    fmt.Println("replace:", str2)  // replace:    hello you 

    split_result := strings.Fields(str)
    for i := 0; i < len(split_result); i++ {
        fmt.Println(split_result[i])
    }
    /*
    hello
    world
       he
     */
    split_result = strings.Split(str, "l")
    for i := 0; i < len(split_result); i++ {
        fmt.Println(split_result[i])
    }
    // o wor
    // d
    str3 := strings.Join(split_result, "l")
    fmt.Println("join:", str3) // join:    hello world  

    count := strings.Count(str, "l")
    fmt.Println("count:", count)  // count: 3

    result = strings.Repeat(str, 3)
    fmt.Println("repeat:", result)
    /*
    repeat:    hello world
       hello world
       hello world
    */
    result = strings.TrimSpace(str)
    fmt.Println("trimspace:", result)  // trimspace: hello world

    result = strings.Trim(str, " \n\r")
    fmt.Println("trim", result)   // trim hello world

    result = strings.TrimLeft(str, " \n\r")
    fmt.Println("trimleft:", result)  // trimleft: hello world

    result = strings.TrimRight(str, " \n\r")
    fmt.Println("trimright:", result)   // trimright:    hello world

    str2 = strconv.Itoa(1000)
    fmt.Println("Itoa:", str2)  // Itoa: 1000

    num, err := strconv.Atoi(str2)
    if err != nil {
        fmt.Println("can not convert str to int", err)
    }
    fmt.Println("number is:", num)  // number is: 1000
}

补充:字符串拼接

可以使用 + 让两个字符串进行拼接,但这样的拼接效率会非常的低,不建议使用,建议大家使用以下的方式:

package main

import (
    "bytes"
    "fmt"
    "strings"
)

func main() {
    // 不建议
    message := "我爱" + "北京天安门"
    fmt.Println(message)

    // 建议:效率高一些
    dataList := []string{"我爱", "北京天安门"}
    result := strings.Join(dataList, "")
    fmt.Println(result) // 我爱北京天安门

    // 建议:效率更高一些(go 1.10之前)
    var buffer bytes.Buffer
    buffer.WriteString("我爱")
    buffer.WriteString("北京")
    buffer.WriteString("天安门")
    data := buffer.String()
    fmt.Print(data)

    // 建议:效率更更更更高一些(go 1.10之后)
    var builder strings.Builder
    builder.WriteString("我爱")
    builder.WriteString("北京")
    builder.WriteString("天安门")
    value := builder.String()
    fmt.Print(value)
}

5. byte和rune类型

组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:

var a := '中'
var b := 'x'

Go 语言的字符有以下两种:

uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
rune类型,代表一个 UTF-8字符。

当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理。

// 遍历字符串
func traversalString() {
    s := "pprof.cn博客"
    for i := 0; i < len(s); i++ { //byte
        fmt.Printf("%v(%c) ", s[i], s[i])
    }
    fmt.Println()
    for _, r := range s { //rune
        fmt.Printf("%v(%c) ", r, r)
    }
    fmt.Println()
}

输出:

112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 229(å) 141() 154() 229(å) 174(®) 162(¢)
112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 21338(博) 23458(客)

因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    // 数字转字符串
    v1 := string(65)
    fmt.Println(v1) // A

    v2 := string(24352)
    fmt.Println(v2) // 张

    // 字符串转数字
    v3, size := utf8.DecodeRuneInString("A")
    fmt.Println(v3, size) // 65    1

    v4, size := utf8.DecodeRuneInString("张")
    fmt.Println(v4, size) // 24352 3

}

6. 索引切片和循环

package main

import "fmt"

func main() {
    var name string = "张亚飞"

    // 1. 索引获取字节
    v1 := name[0]
    fmt.Println(v1)  // 229

    // 2. 切片获取字节区间
    v2 := name[0:3]
    fmt.Println(v2)  // 张

    // 3. 手动循环获取所有字节
    /*
        0 229
        1 188
        2 160
        3 228
        4 186
        5 154
        6 233
        7 163
        8 158
    */
    for i := 0; i < len(name); i++ {
        fmt.Println(i, name[i])
    }
    // 4. for range 循环获取所有字符
    /*
    0 24352 张
    3 20122 亚
    6 39134 飞
    */
    for index, item := range name {
        fmt.Println(index, item, string(item))
    }
    // 5.转换成rune集合 [24352 20122 39134]
    dataList := []rune(name)
    fmt.Println(dataList[0], string(dataList[0]))  // 24352 张
}

7. 修改字符串

要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

func changeString() {
    s1 := "hello"
    // 强制类型转换
    byteS1 := []byte(s1)
    byteS1[0] = 'H'
    fmt.Println(string(byteS1))

    s2 := "博客"
    runeS2 := []rune(s2)
    runeS2[0] = '狗'
    fmt.Println(string(runeS2))
}

六、类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。

基本数据类型相互转换'

强制类型转换的基本语法如下:

T(表达式)

其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等。

可以直接转换的数据类型

// 基本数据类型的相互转换
package main

import (
    // 如果一个包没有被使用过,但又不想去掉,可在包名前加"_"表示忽略
    // 比如:_ "unsafe"
    "fmt"
    _ "unsafe"
)

func main() {
    // n1为int类型
    var n1 int = 100
    // n2为int64类型,将n1赋值给n2的时候,需要将n1显式的转换为int64类型
    var n2 int64 = int64(n1)
    // 同上,需要将n1显式转换为int8类型
    var n3 int8 = int8(n1)
    // 同上,需要将n1显式转换为float32类型
    var f1 float32 = float32(n1)
    // 格式化输出结果
    // 可以看出不管将n1显式转换为什么类型,最后n1自身的类型仍然不变还是int类型
    fmt.Printf("n1 = %v, n2 = %v, n3 = %v, f1 = %v, 最后n1的类型为%T\n", n1, n2, n3, f1, n1) // n1 = 100, n2 = 100, n3 = 100, f1 = 100, 最后n1的类型为int

    // n4为int32类型
    var n4 int32 = 1000
    // n5为int8类型,将n4赋值给n5,需要将n4从
    // int32显式转换为int8,此时n4的值为1000大
    // 于int8的最大值127,此时会按溢出处理,最终
    // 输出的结果会与之前的不一样,输出-24
    var n5 int8 = int8(n4)
    // 格式化输出结果
    fmt.Printf("n4 = %v, n5 = %v\n", n4, n5) // n4 = 1000, n5 = -24

    // 练习1
    var t1 int32 = 12
    var t2 int64
    var t3 int8
    // t1 + 50 是int32类型
    // 需要将t1转换为int64
    t2 = int64(t1) + 50
    // t1 + 50 是int32类型
    // 需要将t1转换为int8
    t3 = int8(t1) + 50
    // 格式化输出结果
    fmt.Printf("t1 = %v, t2 = %v, t3 = %v\n", t1, t2, t3) // t1 = 12, t2 = 62, t3 = 62

    // 练习2
    var t4 int32 = 15
    var t5 int8
    var t6 int8
    // 输出结果会按溢出处理,但是编译时不会报错
    t5 = int8(t4) + 127
    // 报错:constant 128 overflows int8,编译无法通过
    // t6 = int8(t4) + 128
    fmt.Printf("t4 = %v, t5 = %v, t6 = %v\n", t4, t5, t6) // t4 = 15, t5 = -114, t6 = 0
}

先需要间接转换的数据类型

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
    "strconv"
)

func main() {
    // string到int
    n10, _ := strconv.Atoi("12")
    fmt.Println(reflect.TypeOf(n10))
    // string到int64
    n64, _ := strconv.ParseInt("12", 10, 64)
    fmt.Println(reflect.TypeOf(n64))
    // string to float32、float64
    f_32, _ := strconv.ParseFloat("3.1415", 32)
    f_64, _ := strconv.ParseFloat("3.1415", 64)
    fmt.Printf("%T\n", f_32)  // float64
    fmt.Printf("%T\n", f_64)

    // int到string
    s1 := strconv.Itoa(11)
    fmt.Println(reflect.TypeOf(s1))
    // int64到string
    s2 := strconv.FormatInt(11,10)
    fmt.Println(reflect.TypeOf(s2))
    // uint到string
    s3 := strconv.Itoa(uint(11))
    fmt.Println(reflect.TypeOf(s3))
    // float32/64 to string
    s4 := strconv.FormatFloat(3.1415, 'E', -1, 32)
    s5 := strconv.FormatFloat(3.1415, 'E', -1, 64)
    fmt.Println(reflect.TypeOf(s4))
    fmt.Println(reflect.TypeOf(s5))
    // byte -> string
    bytea := byte(1)
    s6 := strconv.Itoa(int(bytea))
    fmt.Println(reflect.TypeOf(s6))

    // int和int64
    n_10_64 := int64(11)
    fmt.Println(reflect.TypeOf(n_10_64))
    // int64 to int
    n_64_10 := int(n_10_64)
    fmt.Println(reflect.TypeOf(n_64_10))
}

文章作者: 张亚飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张亚飞 !
评论
 上一篇
第5篇:复合数据类型之数组 第5篇:复合数据类型之数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩的动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组。
2020-12-27
下一篇 
第3篇:条件语句和循环语句 第3篇:条件语句和循环语句
条件语句一、条件语句if条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。Go 语言提供了以下几种条件判断语句。 1. 基本条件语句if 条件表
2020-12-26
  目录