searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享

Go-基本结构与数据类型

2023-11-28 02:07:31
3
0

Go语言基础之基本结构与数据类型

1. 标识符与关键词

1.1 标识符

标识符表示具有特殊意义的字符,比如变量名、常量名、函数名。Go语言中标识符可以由数字、字符以及_组成。

例如:abc _abc _ ,但是开头只能由字符和_组成,例如_abc

1.2 关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。不建议作为变量名。

Go语言中的关键词

    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

Go语言中的保留字

    Constants:    true  false  iota  nil

      Types:   int int8 int16 int32 int64  
                uint uint8 uint16 uint32 uint64 uintptr
                float32 float64 complex128 complex64
                bool byte rune string error

  Functions:   make len cap new append copy close delete
                complex real imag
                panic recover

2. 变量

常见变量的数据类型有:整型、浮点型、布尔型等。

Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

2.1 变量声明

标准声明

Go语言中的声明方式:

var 变量名 变量类型

变量声明以关键字var开头,变量类型放在变量的后面,行尾无需分号。 举个例子:

var name stringvar age intvar isOk bool

批量声明

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

var (
    a string
    b int
    c bool
    d float32
)
 

变量的初始化

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如:

  1. 整型和浮点型变量的默认值为0

  2. 字符串变量的默认值为空字符串

  3. 布尔型变量默认为false

  4. 切片、函数、指针变量的默认为nil

当然也可以在声明变量的时候实现初始化,标准格式如下:

var 变量名 变量类型 = 表达式

举例说明:

var name string = 'zhangsan'
var age int = 18
 

还可以初始化多个变量:

var name age = 'zhangsan', 18
 

不难看出,Go能够自动推导变量的类型,并完成初始化

【思考】Go如何确定变量类型,比如说创建的age变量推导得到的是int还是uint?

在类型推导过程中,编译器会尽力找到一个最适合的类型。

  1. 如果赋值表达式中的字面量没有指定类型,例如age := 18,那么编译器会根据字面量的大小来推导出一个适合的整数类型,通常是int类型。

  2. 如果赋值表达式中的字面量明确指定了类型,例如age := int64(18),那么编译器会直接使用指定的类型,不进行其他推导。

  3. 如果赋值表达式中的字面量是无符号整数,例如age := uint(18),则编译器会推导出一个无符号整数类型,如uintuint32等。

需要注意的是,对于没有指定类型的整数常量,编译器根据具体的平台或编译器实现可能会有所不同,但通常都会选择一个适合范围的默认整数类型。

短变量声明

在函数内部,可以使用更简略的 := 方式声明并初始化变量。

package main
​
import (
  "fmt"
)
// 全局变量m
var m = 100
​
func main() {
  n := 10
  m := 200 // 此处声明局部变量m
  fmt.Println(m, n)
}
 

匿名变量声明

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

func foo() (int, string) {
  return 18, "zhangsan"
}
func main() {
  x, _ := foo()
  _, y := foo()
  fmt.Println("x=", x)
  fmt.Println("y=", y)
}
 

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。

【注意】

  1. 函数外的每个语句都必须以关键字开始(var、const、func等)

  2. :=不能使用在函数外。

  3. _多用于占位,表示忽略值。

3. 常量

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。

const pi = 3.1415
const e = 2.7182
 

声明了pie这两个常量之后,在整个程序运行期间它们的值都不能再发生变化了。

【注意】

在Go语言中,常量的命名风格没有硬性规定要求全部大写。与Java不同,Go语言常量的命名风格通常使用驼峰命名法,即首字母小写,后续单词首字母大写。

虽然在Go语言中没有强制要求全部大写来表示常量,但在惯例中,常量的命名会遵循一些约定,以提高代码的可读性和维护性。通常使用驼峰命名法。

常见的约定包括:

  1. 如果常量是公开的(即需要在包外部可见),可以使用首字母大写的命名,类似于Java中的公有常量。

  2. 如果常量仅在包内部使用,则可以使用首字母小写的命名,类似于Java中的私有常量。

以下是一个示例,展示了Go语言中的常量命名规范:

package main
​
import "fmt"
​
const (
    pi      = 3.14159  // 私有常量
    MaxAge  = 100      // 公有常量
    weekday = "Monday" // 公有常量
)
​
func main() {
    fmt.Println("pi:", pi)
    fmt.Println("MaxAge:", MaxAge)
    fmt.Println("weekday:", weekday)
}
 

上述示例中,我们定义了三个常量:piMaxAgeweekdaypi以小写字母开头,表示为一个私有常量;MaxAgeweekday以大写字母开头,表示为公有常量(可被包外部访问)。多个变量可以一起声明。

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:

const (
    n1 = 100
    n2 // 100
    n3 // 100
)
 

3.1 iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

iota在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

举个例子:

const (
    n1 = iota //0
    n2        //1
    n3        //2
    n4        //3
  )
在开发过程中,要考虑将第一位变量初始值设为iota+1,这样就能避免出现常亮值为0的情况,尽可能赋予其含义。

3.1.1 常见iota示例

使用_跳过某些值

const (
    n1 = iota //0
    n2        //1
    _
    n4        //3
  )

iota声明中间插队

const (
    n1 = iota //0
    n2 = 100  //100
    n3 = iota //2
    n4        //3
  )
  const n5 = iota //0
定义数量级 (这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)
const (
    _  = iota
    KB = 1 << (10 * iota)
    MB = 1 << (10 * iota)
    GB = 1 << (10 * iota)
    TB = 1 << (10 * iota)
    PB = 1 << (10 * iota)
  )

多个iota定义在一行

const (
    a, b = iota + 1, iota + 2 //1,2
    c, d                      //2,3
    e, f                      //3,4
  )

4. 基本类型与运算符

4.1 整型int和浮点数float

Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。

Go 也有基于架构的类型,例如:intuintuintptr

这些类型的长度都是根据运行程序所在的操作系统类型所决定的:

  • intuint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。

  • uintptr 的长度被设定为足够存放一个指针即可。

Go 语言中没有 float 类型。(Go语言中只有 float32float64)没有 double 类型。

整数:

  • 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)

无符号整数:

  • uint8(0 -> 255)

  • uint16(0 -> 65,535)

  • uint32(0 -> 4,294,967,295)

  • uint64(0 -> 18,446,744,073,709,551,615)

浮点型(IEEE-754 标准):

  • float32(+- 1e-45 -> +- 3.4 * 1e38)

  • float64(+- 5 1e-324 -> 107 1e308)

int 型是计算最快的一种类型。

整型的零值为 0,浮点型的零值为 0.0

float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。

Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):

package main
func main() {
    var a int
    var b int32
    a = 15
    b = a + a     // 编译错误
    b = b + 5    // 因为 5 是常量,所以可以通过编译
}

将得到编译错误 cannot use a + a (type int) as type int32 in assignment

同样地,int16 也不能够被隐式转换为 int32

可以通过显示定义的方式实现类型转换:

package main
import "fmt"
func main() {
    var n int16 = 34
    var m int32
    // compiler error: cannot use n (type int16) as type int32 in assignment
    //m = n
    m = int32(n)
    fmt.Printf("32 bit int is: %d\n", m)
    fmt.Printf("16 bit int is: %d\n", n)
}

格式化说明符

在格式化字符串里,%d 用于格式化整数(%x%X 用于格式化 16 进制表示的数字),%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法),%0nd 用于规定输出长度为 n 的整数,其中开头的数字 0 是必须的。

%n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00

数字值转换

当进行类似 a32bitInt = int32(a32Float) 的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 int 型转换为 int8


func Uint8FromInt(n int) (uint8, error) {
 if 0 <= n && n <= math.MaxUint8 {
   return uint(8), nil
}
 return 0, fmt.Errorf("%d is out of the uint8 range", n)
}

【注意】如果你实际存的数字超出你要转换到的类型的取值范围的话,则会引发 panic

【思考】intint64 是相同的类型吗?

int 类型的大小取决于执行该程序的平台。在 32 位平台上,它通常为 32 位(4 字节),而在 64 位平台上,它通常为 64 位(8 字节)。

int64 类型始终是一个长度为 64 位的整数类型。

4.2 复数

Go 拥有以下复数类型:

complex64 (32 位实数和虚数)complex128 (64 位实数和虚数)

复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。

示例:

var c1 complex64 = 5 + 10ifmt.Printf("The value is: %v", c1)// 输出: 5 + 10i

如果 reim 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:

c = complex(re, im)

函数 real(c)imag(c) 可以分别获得相应的实数和虚数部分。

在使用格式化说明符时,可以使用 %v 来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f

复数支持和其它数字类型一样的运算。当你使用等号 == 或者不等号 != 对复数进行比较运算时,注意对精确度的把握。

cmath 包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。

4.3 位运算

位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。

%b 是用于表示位的格式化标识符。

二元运算符

  • 按位与 &

    对应位置上的值经过和运算结果,具体参见和运算符(第 4.5.1 节),并将 T (true) 替换为 1,将 F (false) 替换为 0

    1 & 1 -> 1  1 & 0 -> 0  0 & 1 -> 0  0 & 0 -> 0
  • 按位或 |

    对应位置上的值经过或运算结果,具体参见或运算符(第 4.5.1 节),并将 T (true) 替换为 1,将 F (false) 替换为 0

    1 | 1 -> 1  1 | 0 -> 1  0 | 1 -> 1  0 | 0 -> 0
  • 按位异或 ^

    对应位置上的值根据以下规则组合:

    1 ^ 1 -> 0  1 ^ 0 -> 1  0 ^ 1 -> 1  0 ^ 0 -> 0
  • 位清除 &^:将指定位置上的值设置为 0

 

package main  
import "fmt"  
func main() {      
  var x uint8 = 15 // 00001111  
  var y uint8 = 4 // 00000100     
  fmt.Printf("%08b\n", x &^ y);  // 00001011  
}

一元运算符

  • 按位补足 ^

    该运算符与异或运算符一同使用,即 m^x,对于无符号 x 使用 “全部位设置为 1” 的规则,对于有符号 x 时使用 m=-1。例如:

    ^10 = -01 ^ 10 = -11
  • 位左移 <<

    • 用法:bitP << n

    • bitP 的位向左移动 n 位,右侧空白部分使用 0 填充;如果 n 等于 2,则结果是 2 的相应倍数,即 2 的 n 次方。例如:

      1 << 10 // 等于 1 KB  1 << 20 // 等于 1 MB  1 << 30 // 等于 1 GB
  • 位右移 >>

    • 用法:bitP >> n

    • bitP 的位向右移动 n 位,左侧空白部分使用 0 填充;如果 n 等于 2,则结果是当前值除以 2 的 n 次方。

 

4.4 运算符

算数运算符常见可用于整数和浮点数的二元运算符有 +-*/

(相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符 + 的重载,但 Go 本身不允许开发者进行自定义的运算符重载)

/ 对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2

取余运算符只能作用于整数9 % 4 -> 1

【注意】整数除以 0 可能导致程序崩溃,浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf 表示。

由上至下代表优先级由高到低:

优先级     运算符
7         ^ !
6         * / % << >> & &^
5         + - | ^
4         == != < <= >= >
3         <-
2         &&
1         ||

<- 是 Go 语言中的一个特殊运算符,用于通道操作。

在 Go 中,通道(Channel)是用于在 Goroutine 之间进行通信和同步的一种机制。<- 运算符用于从通道中接收数据(消费者操作)或将数据发送到通道中(生产者操作)。

下面是 <- 运算符在两种不同的情况下的使用示例:

  1. 从通道接收数据:

data := <-channel

这个运算符将会阻塞当前 Goroutine,并等待直到从通道 channel 中接收到数据。一旦数据被接收到,它将被赋值给变量 data

  1. 向通道发送数据:

channel <- data

这个运算符将会阻塞当前 Goroutine,并等待直到将数据 data 发送到通道 channel 中。

需要注意的是,<- 运算符的左侧或右侧必须是一个通道类型。

这是一个简单的示例,演示了如何使用 <- 运算符进行通道操作

package main
​
import "fmt"
​
func main() {
    // 创建一个整数类型的通道
    ch := make(chan int)
​
    // 启动一个 Goroutine,将数字发送到通道
    go func() {
        ch <- 10 // 发送数字 10 到通道
    }()
​
    // 从通道接收数据并输出
    result := <-ch
    fmt.Println(result) // 输出:10
}

4.5 字符类型

严格来说,这并不是 Go 语言的一个类型,字符只是整数的特殊用例。byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = 'A';字符使用单引号括起来。

在 ASCII 码表中,'A' 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的:

var ch byte = 65 或 var ch byte = '\x41'

\x 总是紧跟着长度为 2 的 16 进制数)

另外一种可能的写法是 \ 后面紧跟着长度为 3 的 8 进制数,例如:\377

不过 Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune 也是 Go 当中的一个类型,并且是 int32 的别名。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U

因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则总是紧跟着长度为 4 的 16 进制数,前缀 \U 紧跟着长度为 8 的 16 进制数。

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
输出:
65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234

格式化说明符 %c 用于表示字符;当和字符配合使用时,%v%d 会输出用于表示该字符的整数;%U 输出格式为 U+hhhh 的字符串

unicode 包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)

  • 判断是否为数字:unicode.IsDigit(ch)

  • 判断是否为空白符号:unicode.IsSpace(ch)

这些函数返回单个布尔值。包 utf8 拥有更多与 rune 类型相关的函数

5. 字符串

字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。

Go 中的字符串里面的字符也可能根据需要占用 1 至 4 个字节,这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。

Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组

字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0]

  • i 个字节:str[i - 1]

  • 最后 1 个字节:str[len(str)-1]

需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。

【注意】获取字符串中某个字节的地址的行为是非法的,例如:&str[i]

字符串拼接符 +

两个字符串 s1s2 可以通过 s := s1 + s2 拼接在一起。

两个字符串 s1s2 可以通过 s := s1 + s2 拼接在一起。

s2 追加在 s1 尾部并生成一个新的字符串 s

你可以通过以下方式来对代码中多行的字符串进行拼接:

str := "Beginning of the string " +
    "second part of the string"

由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行。

【思考】Java拼接字符串有StringBuilder/StringBuffer 。Go有没有更好的拼接方法?

在循环中进行字符串拼接时,使用 + 运算符可能会导致性能问题,因为每次拼接都会创建一个新的字符串对象。当循环次数较多时,这样的创建和销毁字符串的操作会非常耗费时间和内存。

在 Go 语言中,更好的办法是使用 strings.Builder 类型来进行字符串拼接。strings.Builder 类型是一个用于高效构建字符串的缓冲区。

下面是使用 strings.Builder 进行字符串拼接的示例

package main
​
import (
  "fmt"
  "strings"
)
​
func main() {
  var builder strings.Builder
​
  for i := 0; i < 10; i++ {
    builder.WriteString("String ")
    builder.WriteString(fmt.Sprint(i))
    builder.WriteString(" ")
  }
​
  result := builder.String()
  fmt.Println(result)
}

使用 strings.Builder 的好处是,它使用了内部缓冲区来保存字符串的中间结果,避免了多次创建和销毁字符串对象。这样可以大大提高字符串拼接的效率和性能。

strings.Join() 函数用于将字符串切片连接起来,它接受一个字符串切片作为输入,并返回一个由切片中的元素连接而成的字符串。相比简单地使用 + 运算符,strings.Join() 函数能够更高效地进行字符串拼接,尤其在处理大量字符串时。这是因为 strings.Join() 函数通过一次内存分配来构建结果字符串,避免了反复创建和销毁字符串对象。

下面是使用 strings.Join() 函数进行字符串拼接的示例

package main
​
import (
  "fmt"
  "strings"
)
​
func main() {
  strSlice := []string{"Hello", "World", "!"}
  result := strings.Join(strSlice, " ")
  fmt.Println(result)
}
另一个常用的字符串拼接方法是使用 bytes.Buffer 类型。bytes.Buffer 提供了一个缓冲区,可以通过 WriteString() 方法将字符串逐步写入缓冲区,最后通过 String() 方法将缓冲区的内容转换为最终的字符串。

下面是使用 bytes.Buffer 进行字符串拼接的示例:

package main
​
import (
  "bytes"
  "fmt"
)
​
func main() {
  var buffer bytes.Buffer
​
  for i := 0; i < 10; i++ {
    buffer.WriteString(fmt.Sprintf("String %d ", i))
  }
​
  result := buffer.String()
  fmt.Println(result)
}

总结来说,strings.Join() 函数适用于将字符串切片连接起来,而 bytes.Buffer 适用于需要逐步构建字符串的场景。strings.Builder 是 Go 1.10 版本引入的新类型,它是在 bytes.Buffer 的基础上进一步优化的,性能更好。

 

6. strings 和 strconv

strings

Go 中使用 strings 包来完成对字符串的主要操作。

方法 描述 用例
前缀 HasPrefix() 判断字符串 s 是否以 prefix 开头: strings.HasPrefix(s, prefix string) bool
后缀 HasSuffix() 判断字符串 s 是否以 suffix 结尾: strings.HasSuffix(s, suffix string) bool
包含关系 Contains() 判断字符串 s 是否包含 substr strings.Contains(s, substr string) bool
索引 Index() 返回字符串 str 在字符串 s 中的索引 strings.Index(s, str string) int
  LastIndex() 返回字符串 str 在字符串 s 中最后出现位置的索引 strings.LastIndex(s, str string) int
  IndexRune()查询非 ASCII 编码的字符在父字符串中的位置 strings.IndexRune(s string, r rune) int
字符串替换 Replace() 用于将字符串 str 中的前 n 个字符串 old 替换为字符串 new,并返回一个新的字符串。如果 n = -1 则替换所有字符串 old 为字符串 new strings.Replace(str, old, new string, n int) string
统计 Count() 用于计算字符串 str 在字符串 s 中出现的非重叠次数 strings.Count(s, str string) int
  Repeat() 用于重复 count 次字符串 s 并返回一个新的字符串 strings.Repeat(s, count int) string
修改字符串大小写 ToLower() 将字符串中的 Unicode 字符全部转换为相应的小写字符 strings.ToLower(s) string
  ToUpper() 将字符串中的 Unicode 字符全部转换为相应的大写字符: strings.ToUpper(s) string
修剪字符串 剔除字符串开头和结尾的空白符号;将开头和结尾的 cut 去除掉;只想剔除开头或者结尾的字符串 strings.TrimSpace(s) strings.Trim(s, "cut") TrimLeft() 或者 TrimRight()
分割字符串 利用 1 个或多个空白符号来作为动态长度的分隔符将字符串分割成若干小块,并返回一个 slice,如果字符串只包含空白符号,则返回一个长度为 0 的 slice。 strings.Fields(s)
  定义分割符号来对指定字符串进行分割,同样返回 slice。 strings.Split(s, sep)
拼接 slice 到字符串 Join() 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串: strings.Join(sl []string, sep string) string
从字符串中读取内容 函数 strings.NewReader(str) 用于生成一个 Reader 并读取字符串中的内容,然后返回指向该 Reader 的指针,从其它类型读取内容的函数还有: strings.NewReader(str)
  Read()[]byte 中读取内容。 ReadByte()ReadRune() 从字符串中读取下一个 byte 或者 rune Read() ReadByte()ReadRune()
     

strconv

与字符串相关的类型转换都是通过 strconv 包实现的。

该包包含了一些变量用于获取程序运行的操作系统平台下 int 类型所占的位数,如:strconv.IntSize

任何类型 T 转换为字符串总是成功的。

数字类型转换到字符串

  • strconv.Itoa(i int) string 返回数字 i 所表示的字符串类型的十进制数。

  • strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string 将 64 位浮点型的数字转换为字符串,其中 fmt 表示格式(其值可以是 'b''e''f''g'),prec 表示精度,bitSize 则使用 32 表示 float32,用 64 表示 float64

将字符串转换为其它类型 tp 并不总是可能的,可能会在运行时抛出错误 parsing "…": invalid argument

  • strconv.Atoi(s string) (i int, err error) 将字符串转换为 int 型。

  • strconv.ParseFloat(s string, bitSize int) (f float64, err error) 将字符串转换为 float64 型。

利用多返回值的特性,这些函数会返回 2 个值,第 1 个是转换后的结果(如果转换成功),第 2 个是可能出现的错误。

7. 时间和日期

time 包为我们提供了一个数据类型 time.Time(作为值使用)以及显示和测量时间和日期的功能函数。

  • time.Now()

  • t.Day()

  • t.Minute()

自定义时间格式化字符串,例如: fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) 将会输出 21.07.2011

Duration 类型表示两个连续时刻所相差的纳秒数,类型为 int64Location 类型映射某个时区的时间,UTC 表示通用协调世界时间。

包中的一个预定义函数 func (t Time) Format(layout string) string 可以根据一个格式化字符串来将一个时间 t 转换为相应格式的字符串,你可以使用一些预定义的格式,如:time.ANSICtime.RFC822

一般的格式化设计是通过对于一个标准时间的格式化描述来展现的,这听起来很奇怪(02 Jan 2006 15:04 是 Go 语言的诞生时间且自定义格式化时必须以此时间为基准),但看下面这个例子你就会一目了然:

fmt.Println(t.Format("02 Jan 2006 15:04"))

输出:

 21 Jul 2011 10:31

8. 指针

不像 Java 和 .NET,Go 语言为程序员提供了控制数据结构的指针的能力;但是,你不能进行指针运算。

程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b08200xf84001d7f0

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址

var i1 = 5fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)

这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 i1:此处使用 *int 表示。如果我们想调用指针 intP,我们可以这样声明它:

var intP *int

然后使用 intP = &i1 是合法的,此时 intP 指向 i1

(指针的格式化标识符为 %p

intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变量 i1

一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。

当一个指针被定义后没有分配到任何变量时,它的值为 nil

一个指针变量通常缩写为 ptr

【注意】你不能获取字面量或常量的地址,例如:

const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10

所以说,Go 语言和 C、C++ 以及 D 语言这些低级(系统)语言一样,都有指针的概念。但是对于经常导致 C 语言内存泄漏继而程序崩溃的指针运算(所谓的指针算法,如:pointer+2,移动指针指向字符串的字节数或数组的某个位置)是不被允许的。Go 语言中的指针保证了内存安全,更像是 Java、C# 和 VB.NET 中的引用。

因此 p++ 在 Go 语言的代码中是不合法的。

 

0条评论
0 / 1000
x****n
2文章数
0粉丝数
x****n
2 文章 | 0 粉丝
x****n
2文章数
0粉丝数
x****n
2 文章 | 0 粉丝

Go-基本结构与数据类型

2023-11-28 02:07:31
3
0

Go语言基础之基本结构与数据类型

1. 标识符与关键词

1.1 标识符

标识符表示具有特殊意义的字符,比如变量名、常量名、函数名。Go语言中标识符可以由数字、字符以及_组成。

例如:abc _abc _ ,但是开头只能由字符和_组成,例如_abc

1.2 关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。不建议作为变量名。

Go语言中的关键词

    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

Go语言中的保留字

    Constants:    true  false  iota  nil

      Types:   int int8 int16 int32 int64  
                uint uint8 uint16 uint32 uint64 uintptr
                float32 float64 complex128 complex64
                bool byte rune string error

  Functions:   make len cap new append copy close delete
                complex real imag
                panic recover

2. 变量

常见变量的数据类型有:整型、浮点型、布尔型等。

Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

2.1 变量声明

标准声明

Go语言中的声明方式:

var 变量名 变量类型

变量声明以关键字var开头,变量类型放在变量的后面,行尾无需分号。 举个例子:

var name stringvar age intvar isOk bool

批量声明

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

var (
    a string
    b int
    c bool
    d float32
)
 

变量的初始化

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如:

  1. 整型和浮点型变量的默认值为0

  2. 字符串变量的默认值为空字符串

  3. 布尔型变量默认为false

  4. 切片、函数、指针变量的默认为nil

当然也可以在声明变量的时候实现初始化,标准格式如下:

var 变量名 变量类型 = 表达式

举例说明:

var name string = 'zhangsan'
var age int = 18
 

还可以初始化多个变量:

var name age = 'zhangsan', 18
 

不难看出,Go能够自动推导变量的类型,并完成初始化

【思考】Go如何确定变量类型,比如说创建的age变量推导得到的是int还是uint?

在类型推导过程中,编译器会尽力找到一个最适合的类型。

  1. 如果赋值表达式中的字面量没有指定类型,例如age := 18,那么编译器会根据字面量的大小来推导出一个适合的整数类型,通常是int类型。

  2. 如果赋值表达式中的字面量明确指定了类型,例如age := int64(18),那么编译器会直接使用指定的类型,不进行其他推导。

  3. 如果赋值表达式中的字面量是无符号整数,例如age := uint(18),则编译器会推导出一个无符号整数类型,如uintuint32等。

需要注意的是,对于没有指定类型的整数常量,编译器根据具体的平台或编译器实现可能会有所不同,但通常都会选择一个适合范围的默认整数类型。

短变量声明

在函数内部,可以使用更简略的 := 方式声明并初始化变量。

package main
​
import (
  "fmt"
)
// 全局变量m
var m = 100
​
func main() {
  n := 10
  m := 200 // 此处声明局部变量m
  fmt.Println(m, n)
}
 

匿名变量声明

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

func foo() (int, string) {
  return 18, "zhangsan"
}
func main() {
  x, _ := foo()
  _, y := foo()
  fmt.Println("x=", x)
  fmt.Println("y=", y)
}
 

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。

【注意】

  1. 函数外的每个语句都必须以关键字开始(var、const、func等)

  2. :=不能使用在函数外。

  3. _多用于占位,表示忽略值。

3. 常量

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。

const pi = 3.1415
const e = 2.7182
 

声明了pie这两个常量之后,在整个程序运行期间它们的值都不能再发生变化了。

【注意】

在Go语言中,常量的命名风格没有硬性规定要求全部大写。与Java不同,Go语言常量的命名风格通常使用驼峰命名法,即首字母小写,后续单词首字母大写。

虽然在Go语言中没有强制要求全部大写来表示常量,但在惯例中,常量的命名会遵循一些约定,以提高代码的可读性和维护性。通常使用驼峰命名法。

常见的约定包括:

  1. 如果常量是公开的(即需要在包外部可见),可以使用首字母大写的命名,类似于Java中的公有常量。

  2. 如果常量仅在包内部使用,则可以使用首字母小写的命名,类似于Java中的私有常量。

以下是一个示例,展示了Go语言中的常量命名规范:

package main
​
import "fmt"
​
const (
    pi      = 3.14159  // 私有常量
    MaxAge  = 100      // 公有常量
    weekday = "Monday" // 公有常量
)
​
func main() {
    fmt.Println("pi:", pi)
    fmt.Println("MaxAge:", MaxAge)
    fmt.Println("weekday:", weekday)
}
 

上述示例中,我们定义了三个常量:piMaxAgeweekdaypi以小写字母开头,表示为一个私有常量;MaxAgeweekday以大写字母开头,表示为公有常量(可被包外部访问)。多个变量可以一起声明。

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:

const (
    n1 = 100
    n2 // 100
    n3 // 100
)
 

3.1 iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

iota在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

举个例子:

const (
    n1 = iota //0
    n2        //1
    n3        //2
    n4        //3
  )
在开发过程中,要考虑将第一位变量初始值设为iota+1,这样就能避免出现常亮值为0的情况,尽可能赋予其含义。

3.1.1 常见iota示例

使用_跳过某些值

const (
    n1 = iota //0
    n2        //1
    _
    n4        //3
  )

iota声明中间插队

const (
    n1 = iota //0
    n2 = 100  //100
    n3 = iota //2
    n4        //3
  )
  const n5 = iota //0
定义数量级 (这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)
const (
    _  = iota
    KB = 1 << (10 * iota)
    MB = 1 << (10 * iota)
    GB = 1 << (10 * iota)
    TB = 1 << (10 * iota)
    PB = 1 << (10 * iota)
  )

多个iota定义在一行

const (
    a, b = iota + 1, iota + 2 //1,2
    c, d                      //2,3
    e, f                      //3,4
  )

4. 基本类型与运算符

4.1 整型int和浮点数float

Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。

Go 也有基于架构的类型,例如:intuintuintptr

这些类型的长度都是根据运行程序所在的操作系统类型所决定的:

  • intuint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。

  • uintptr 的长度被设定为足够存放一个指针即可。

Go 语言中没有 float 类型。(Go语言中只有 float32float64)没有 double 类型。

整数:

  • 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)

无符号整数:

  • uint8(0 -> 255)

  • uint16(0 -> 65,535)

  • uint32(0 -> 4,294,967,295)

  • uint64(0 -> 18,446,744,073,709,551,615)

浮点型(IEEE-754 标准):

  • float32(+- 1e-45 -> +- 3.4 * 1e38)

  • float64(+- 5 1e-324 -> 107 1e308)

int 型是计算最快的一种类型。

整型的零值为 0,浮点型的零值为 0.0

float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。

Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):

package main
func main() {
    var a int
    var b int32
    a = 15
    b = a + a     // 编译错误
    b = b + 5    // 因为 5 是常量,所以可以通过编译
}

将得到编译错误 cannot use a + a (type int) as type int32 in assignment

同样地,int16 也不能够被隐式转换为 int32

可以通过显示定义的方式实现类型转换:

package main
import "fmt"
func main() {
    var n int16 = 34
    var m int32
    // compiler error: cannot use n (type int16) as type int32 in assignment
    //m = n
    m = int32(n)
    fmt.Printf("32 bit int is: %d\n", m)
    fmt.Printf("16 bit int is: %d\n", n)
}

格式化说明符

在格式化字符串里,%d 用于格式化整数(%x%X 用于格式化 16 进制表示的数字),%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法),%0nd 用于规定输出长度为 n 的整数,其中开头的数字 0 是必须的。

%n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00

数字值转换

当进行类似 a32bitInt = int32(a32Float) 的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 int 型转换为 int8


func Uint8FromInt(n int) (uint8, error) {
 if 0 <= n && n <= math.MaxUint8 {
   return uint(8), nil
}
 return 0, fmt.Errorf("%d is out of the uint8 range", n)
}

【注意】如果你实际存的数字超出你要转换到的类型的取值范围的话,则会引发 panic

【思考】intint64 是相同的类型吗?

int 类型的大小取决于执行该程序的平台。在 32 位平台上,它通常为 32 位(4 字节),而在 64 位平台上,它通常为 64 位(8 字节)。

int64 类型始终是一个长度为 64 位的整数类型。

4.2 复数

Go 拥有以下复数类型:

complex64 (32 位实数和虚数)complex128 (64 位实数和虚数)

复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。

示例:

var c1 complex64 = 5 + 10ifmt.Printf("The value is: %v", c1)// 输出: 5 + 10i

如果 reim 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:

c = complex(re, im)

函数 real(c)imag(c) 可以分别获得相应的实数和虚数部分。

在使用格式化说明符时,可以使用 %v 来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f

复数支持和其它数字类型一样的运算。当你使用等号 == 或者不等号 != 对复数进行比较运算时,注意对精确度的把握。

cmath 包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。

4.3 位运算

位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。

%b 是用于表示位的格式化标识符。

二元运算符

  • 按位与 &

    对应位置上的值经过和运算结果,具体参见和运算符(第 4.5.1 节),并将 T (true) 替换为 1,将 F (false) 替换为 0

    1 & 1 -> 1  1 & 0 -> 0  0 & 1 -> 0  0 & 0 -> 0
  • 按位或 |

    对应位置上的值经过或运算结果,具体参见或运算符(第 4.5.1 节),并将 T (true) 替换为 1,将 F (false) 替换为 0

    1 | 1 -> 1  1 | 0 -> 1  0 | 1 -> 1  0 | 0 -> 0
  • 按位异或 ^

    对应位置上的值根据以下规则组合:

    1 ^ 1 -> 0  1 ^ 0 -> 1  0 ^ 1 -> 1  0 ^ 0 -> 0
  • 位清除 &^:将指定位置上的值设置为 0

 

package main  
import "fmt"  
func main() {      
  var x uint8 = 15 // 00001111  
  var y uint8 = 4 // 00000100     
  fmt.Printf("%08b\n", x &^ y);  // 00001011  
}

一元运算符

  • 按位补足 ^

    该运算符与异或运算符一同使用,即 m^x,对于无符号 x 使用 “全部位设置为 1” 的规则,对于有符号 x 时使用 m=-1。例如:

    ^10 = -01 ^ 10 = -11
  • 位左移 <<

    • 用法:bitP << n

    • bitP 的位向左移动 n 位,右侧空白部分使用 0 填充;如果 n 等于 2,则结果是 2 的相应倍数,即 2 的 n 次方。例如:

      1 << 10 // 等于 1 KB  1 << 20 // 等于 1 MB  1 << 30 // 等于 1 GB
  • 位右移 >>

    • 用法:bitP >> n

    • bitP 的位向右移动 n 位,左侧空白部分使用 0 填充;如果 n 等于 2,则结果是当前值除以 2 的 n 次方。

 

4.4 运算符

算数运算符常见可用于整数和浮点数的二元运算符有 +-*/

(相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符 + 的重载,但 Go 本身不允许开发者进行自定义的运算符重载)

/ 对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2

取余运算符只能作用于整数9 % 4 -> 1

【注意】整数除以 0 可能导致程序崩溃,浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf 表示。

由上至下代表优先级由高到低:

优先级     运算符
7         ^ !
6         * / % << >> & &^
5         + - | ^
4         == != < <= >= >
3         <-
2         &&
1         ||

<- 是 Go 语言中的一个特殊运算符,用于通道操作。

在 Go 中,通道(Channel)是用于在 Goroutine 之间进行通信和同步的一种机制。<- 运算符用于从通道中接收数据(消费者操作)或将数据发送到通道中(生产者操作)。

下面是 <- 运算符在两种不同的情况下的使用示例:

  1. 从通道接收数据:

data := <-channel

这个运算符将会阻塞当前 Goroutine,并等待直到从通道 channel 中接收到数据。一旦数据被接收到,它将被赋值给变量 data

  1. 向通道发送数据:

channel <- data

这个运算符将会阻塞当前 Goroutine,并等待直到将数据 data 发送到通道 channel 中。

需要注意的是,<- 运算符的左侧或右侧必须是一个通道类型。

这是一个简单的示例,演示了如何使用 <- 运算符进行通道操作

package main
​
import "fmt"
​
func main() {
    // 创建一个整数类型的通道
    ch := make(chan int)
​
    // 启动一个 Goroutine,将数字发送到通道
    go func() {
        ch <- 10 // 发送数字 10 到通道
    }()
​
    // 从通道接收数据并输出
    result := <-ch
    fmt.Println(result) // 输出:10
}

4.5 字符类型

严格来说,这并不是 Go 语言的一个类型,字符只是整数的特殊用例。byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = 'A';字符使用单引号括起来。

在 ASCII 码表中,'A' 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的:

var ch byte = 65 或 var ch byte = '\x41'

\x 总是紧跟着长度为 2 的 16 进制数)

另外一种可能的写法是 \ 后面紧跟着长度为 3 的 8 进制数,例如:\377

不过 Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune 也是 Go 当中的一个类型,并且是 int32 的别名。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U

因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则总是紧跟着长度为 4 的 16 进制数,前缀 \U 紧跟着长度为 8 的 16 进制数。

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
输出:
65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234

格式化说明符 %c 用于表示字符;当和字符配合使用时,%v%d 会输出用于表示该字符的整数;%U 输出格式为 U+hhhh 的字符串

unicode 包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)

  • 判断是否为数字:unicode.IsDigit(ch)

  • 判断是否为空白符号:unicode.IsSpace(ch)

这些函数返回单个布尔值。包 utf8 拥有更多与 rune 类型相关的函数

5. 字符串

字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。

Go 中的字符串里面的字符也可能根据需要占用 1 至 4 个字节,这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。

Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组

字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0]

  • i 个字节:str[i - 1]

  • 最后 1 个字节:str[len(str)-1]

需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。

【注意】获取字符串中某个字节的地址的行为是非法的,例如:&str[i]

字符串拼接符 +

两个字符串 s1s2 可以通过 s := s1 + s2 拼接在一起。

两个字符串 s1s2 可以通过 s := s1 + s2 拼接在一起。

s2 追加在 s1 尾部并生成一个新的字符串 s

你可以通过以下方式来对代码中多行的字符串进行拼接:

str := "Beginning of the string " +
    "second part of the string"

由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行。

【思考】Java拼接字符串有StringBuilder/StringBuffer 。Go有没有更好的拼接方法?

在循环中进行字符串拼接时,使用 + 运算符可能会导致性能问题,因为每次拼接都会创建一个新的字符串对象。当循环次数较多时,这样的创建和销毁字符串的操作会非常耗费时间和内存。

在 Go 语言中,更好的办法是使用 strings.Builder 类型来进行字符串拼接。strings.Builder 类型是一个用于高效构建字符串的缓冲区。

下面是使用 strings.Builder 进行字符串拼接的示例

package main
​
import (
  "fmt"
  "strings"
)
​
func main() {
  var builder strings.Builder
​
  for i := 0; i < 10; i++ {
    builder.WriteString("String ")
    builder.WriteString(fmt.Sprint(i))
    builder.WriteString(" ")
  }
​
  result := builder.String()
  fmt.Println(result)
}

使用 strings.Builder 的好处是,它使用了内部缓冲区来保存字符串的中间结果,避免了多次创建和销毁字符串对象。这样可以大大提高字符串拼接的效率和性能。

strings.Join() 函数用于将字符串切片连接起来,它接受一个字符串切片作为输入,并返回一个由切片中的元素连接而成的字符串。相比简单地使用 + 运算符,strings.Join() 函数能够更高效地进行字符串拼接,尤其在处理大量字符串时。这是因为 strings.Join() 函数通过一次内存分配来构建结果字符串,避免了反复创建和销毁字符串对象。

下面是使用 strings.Join() 函数进行字符串拼接的示例

package main
​
import (
  "fmt"
  "strings"
)
​
func main() {
  strSlice := []string{"Hello", "World", "!"}
  result := strings.Join(strSlice, " ")
  fmt.Println(result)
}
另一个常用的字符串拼接方法是使用 bytes.Buffer 类型。bytes.Buffer 提供了一个缓冲区,可以通过 WriteString() 方法将字符串逐步写入缓冲区,最后通过 String() 方法将缓冲区的内容转换为最终的字符串。

下面是使用 bytes.Buffer 进行字符串拼接的示例:

package main
​
import (
  "bytes"
  "fmt"
)
​
func main() {
  var buffer bytes.Buffer
​
  for i := 0; i < 10; i++ {
    buffer.WriteString(fmt.Sprintf("String %d ", i))
  }
​
  result := buffer.String()
  fmt.Println(result)
}

总结来说,strings.Join() 函数适用于将字符串切片连接起来,而 bytes.Buffer 适用于需要逐步构建字符串的场景。strings.Builder 是 Go 1.10 版本引入的新类型,它是在 bytes.Buffer 的基础上进一步优化的,性能更好。

 

6. strings 和 strconv

strings

Go 中使用 strings 包来完成对字符串的主要操作。

方法 描述 用例
前缀 HasPrefix() 判断字符串 s 是否以 prefix 开头: strings.HasPrefix(s, prefix string) bool
后缀 HasSuffix() 判断字符串 s 是否以 suffix 结尾: strings.HasSuffix(s, suffix string) bool
包含关系 Contains() 判断字符串 s 是否包含 substr strings.Contains(s, substr string) bool
索引 Index() 返回字符串 str 在字符串 s 中的索引 strings.Index(s, str string) int
  LastIndex() 返回字符串 str 在字符串 s 中最后出现位置的索引 strings.LastIndex(s, str string) int
  IndexRune()查询非 ASCII 编码的字符在父字符串中的位置 strings.IndexRune(s string, r rune) int
字符串替换 Replace() 用于将字符串 str 中的前 n 个字符串 old 替换为字符串 new,并返回一个新的字符串。如果 n = -1 则替换所有字符串 old 为字符串 new strings.Replace(str, old, new string, n int) string
统计 Count() 用于计算字符串 str 在字符串 s 中出现的非重叠次数 strings.Count(s, str string) int
  Repeat() 用于重复 count 次字符串 s 并返回一个新的字符串 strings.Repeat(s, count int) string
修改字符串大小写 ToLower() 将字符串中的 Unicode 字符全部转换为相应的小写字符 strings.ToLower(s) string
  ToUpper() 将字符串中的 Unicode 字符全部转换为相应的大写字符: strings.ToUpper(s) string
修剪字符串 剔除字符串开头和结尾的空白符号;将开头和结尾的 cut 去除掉;只想剔除开头或者结尾的字符串 strings.TrimSpace(s) strings.Trim(s, "cut") TrimLeft() 或者 TrimRight()
分割字符串 利用 1 个或多个空白符号来作为动态长度的分隔符将字符串分割成若干小块,并返回一个 slice,如果字符串只包含空白符号,则返回一个长度为 0 的 slice。 strings.Fields(s)
  定义分割符号来对指定字符串进行分割,同样返回 slice。 strings.Split(s, sep)
拼接 slice 到字符串 Join() 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串: strings.Join(sl []string, sep string) string
从字符串中读取内容 函数 strings.NewReader(str) 用于生成一个 Reader 并读取字符串中的内容,然后返回指向该 Reader 的指针,从其它类型读取内容的函数还有: strings.NewReader(str)
  Read()[]byte 中读取内容。 ReadByte()ReadRune() 从字符串中读取下一个 byte 或者 rune Read() ReadByte()ReadRune()
     

strconv

与字符串相关的类型转换都是通过 strconv 包实现的。

该包包含了一些变量用于获取程序运行的操作系统平台下 int 类型所占的位数,如:strconv.IntSize

任何类型 T 转换为字符串总是成功的。

数字类型转换到字符串

  • strconv.Itoa(i int) string 返回数字 i 所表示的字符串类型的十进制数。

  • strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string 将 64 位浮点型的数字转换为字符串,其中 fmt 表示格式(其值可以是 'b''e''f''g'),prec 表示精度,bitSize 则使用 32 表示 float32,用 64 表示 float64

将字符串转换为其它类型 tp 并不总是可能的,可能会在运行时抛出错误 parsing "…": invalid argument

  • strconv.Atoi(s string) (i int, err error) 将字符串转换为 int 型。

  • strconv.ParseFloat(s string, bitSize int) (f float64, err error) 将字符串转换为 float64 型。

利用多返回值的特性,这些函数会返回 2 个值,第 1 个是转换后的结果(如果转换成功),第 2 个是可能出现的错误。

7. 时间和日期

time 包为我们提供了一个数据类型 time.Time(作为值使用)以及显示和测量时间和日期的功能函数。

  • time.Now()

  • t.Day()

  • t.Minute()

自定义时间格式化字符串,例如: fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) 将会输出 21.07.2011

Duration 类型表示两个连续时刻所相差的纳秒数,类型为 int64Location 类型映射某个时区的时间,UTC 表示通用协调世界时间。

包中的一个预定义函数 func (t Time) Format(layout string) string 可以根据一个格式化字符串来将一个时间 t 转换为相应格式的字符串,你可以使用一些预定义的格式,如:time.ANSICtime.RFC822

一般的格式化设计是通过对于一个标准时间的格式化描述来展现的,这听起来很奇怪(02 Jan 2006 15:04 是 Go 语言的诞生时间且自定义格式化时必须以此时间为基准),但看下面这个例子你就会一目了然:

fmt.Println(t.Format("02 Jan 2006 15:04"))

输出:

 21 Jul 2011 10:31

8. 指针

不像 Java 和 .NET,Go 语言为程序员提供了控制数据结构的指针的能力;但是,你不能进行指针运算。

程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b08200xf84001d7f0

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址

var i1 = 5fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)

这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 i1:此处使用 *int 表示。如果我们想调用指针 intP,我们可以这样声明它:

var intP *int

然后使用 intP = &i1 是合法的,此时 intP 指向 i1

(指针的格式化标识符为 %p

intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变量 i1

一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。

当一个指针被定义后没有分配到任何变量时,它的值为 nil

一个指针变量通常缩写为 ptr

【注意】你不能获取字面量或常量的地址,例如:

const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10

所以说,Go 语言和 C、C++ 以及 D 语言这些低级(系统)语言一样,都有指针的概念。但是对于经常导致 C 语言内存泄漏继而程序崩溃的指针运算(所谓的指针算法,如:pointer+2,移动指针指向字符串的字节数或数组的某个位置)是不被允许的。Go 语言中的指针保证了内存安全,更像是 Java、C# 和 VB.NET 中的引用。

因此 p++ 在 Go 语言的代码中是不合法的。

 

文章来自个人专栏
Go语言基础新手入门
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0