当前位置: 首页 > 工具软件 > go-quote > 使用案例 >

Go语言---strconv包

孔磊
2023-12-01

包strconv主要实现对字符串和基本数据类型之间的转换。基本数据类型包括:布尔、整型(包括有/无符号、二进制、八进制、十进制和十六进制)和浮点型等。

-------------------------------------------------------------------------------------------

strconv 包转换错误处理

字符串转换过程中可能出错,因此strconv 包定义了两个 error 类型的变量:ErrRange 和 ErrSyntax。其中,ErrRange 表示值超过了类型能表示的最大范围,比如将 "128" 转为 int8 就会返回这个错误;ErrSyntax 表示语法错误,比如将 "" 转为 int 类型会返回这个错误。

// ErrRange indicates that a value is out of range for the target type.
var ErrRange = errors.New("value out of range")
  
// ErrSyntax indicates that a value does not have the right syntax for the target type.
var ErrSyntax = errors.New("invalid syntax")

然而,在返回错误的时候,不是直接将上面的变量值返回,而是通过构造一个 NumError 类型的 error 对象返回。NumError 结构的定义如下:

// A NumError records a failed conversion.    包外可以直接对字段进行访问
  type NumError struct {
  	Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
  	Num  string // the input
  	Err  error  // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.)
  }

该结构记录了转换过程中发生的错误信息。该结构不仅包含了一个 error 类型的成员,记录具体的错误信息,而且它自己也实现了 error 接口:

func (e *NumError) Error() string {
  	return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error()
  }

包中定义几个函数,用于构造 NumError 对象:

func syntaxError(fn, str string) *NumError {
  	return &NumError{fn, str, ErrSyntax}
  }
  
  func rangeError(fn, str string) *NumError {
  	return &NumError{fn, str, ErrRange}
  }
  
  func baseError(fn, str string, base int) *NumError {
  	return &NumError{fn, str, errors.New("invalid base " + Itoa(base))}
  }
  
  func bitSizeError(fn, str string, bitSize int) *NumError {
  	return &NumError{fn, str, errors.New("invalid bit size " + Itoa(bitSize))}
  }

在遇到 ErrSyntax 或 ErrRange 错误时,通过上面的函数构造 NumError 对象。

程序示例:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	str := "Not a number"
	if _, err := strconv.ParseFloat(str, 64); err != nil {
		e := err.(*strconv.NumError)
		fmt.Println("Func:", e.Func)
		fmt.Println("Num:", e.Num)
		fmt.Println("Err:", e.Err)
		fmt.Println(err)
	}

}

输出结果:

Func: ParseFloat
Num: Not a number
Err: invalid syntax
strconv.ParseFloat: parsing "Not a number": invalid syntax

-------------------------------------------------------------------------------------------

字符串和整型变量之间的转换

字符串转为整型

func ParseInt(s string, base int, bitSize int) (i int64, err error)
func ParseUint(s string, base int, bitSize int) (n uint64, err error)
func Atoi(s string) (i int, err error)
其中,Atoi 是 ParseInt 的便捷版,内部通过调用 ParseInt(s, 10, 0) 来实现的;ParseInt 转为有符号整型;ParseUint 转为无符号整型,着重介绍 ParseInt。

参数 base 代表字符串按照给定的进制进行解释。一般的,base 的取值为 2~36,如果 base 的值为 0,则会根据字符串的前缀来确定 base 的值:"0x" 表示 16 进制; "0" 表示 8 进制;否则就是 10 进制。

参数 bitSize 表示的是整数取值范围,或者说整数的具体类型。取值 0、8、16、32 和 64 分别代表 int、int8、int16、int32 和 int64。

问题:下面的代码 n 和 err 的值分别是什么?

n, err := strconv.ParseInt("128", 10, 8)

在 ParseInt/ParseUint 的实现中,如果字符串表示的整数超过了 bitSize 参数能够表示的范围,则会返回 ErrRange,同时会返回 bitSize 能够表示的最大或最小值。因此,这里的 n 是 127。

另外,ParseInt 返回的是 int64,这是为了能够容纳所有的整型,在实际使用中,可以根据传递的 bitSize,然后将结果转为实际需要的类型。

转换的基本原理(以 "128" 转 为 10 进制 int 为例):

s := "128"
n := 0
for i := 0; i < len(s); i++ {
    n = 10 * n  + s[i]     // base
}    //在循环处理的过程中,会检查数据的有效性和是否越界等。

程序示例

ParseInt函数

package main

import (
	"fmt"
	"strconv"
)

func main() {
	v32 := "-354634382"
	if s, err := strconv.ParseInt(v32, 10, 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseInt(v32, 16, 32); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

	v64 := "-3546343826724305832"
	if s, err := strconv.ParseInt(v64, 10, 64); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}
	if s, err := strconv.ParseInt(v64, 16, 64); err == nil {
		fmt.Printf("%T, %v\n", s, s)
	}

}  //输出结果:int64, -354634382     int64, -3546343826724305832

Atoi函数

package main

import (
	"fmt"
	"strconv"
)

func main() {
	v := "10"
	if s, err := strconv.Atoi(v); err == nil {
		fmt.Printf("%T, %v", s, s)
	}
}    //输出结果:  int,10

整型转为字符串类型

在Go语言中,你需要将整型转为字符串类型,然后才能进行连接。这个时候,strconv 包中的整型转字符串的相关函数就派上用场了。这些函数签名如下:

func FormatUint(i uint64, base int) string    // 无符号整型转字符串
func FormatInt(i int64, base int) string    // 有符号整型转字符串
func Itoa(i int) string

其中,Itoa 内部直接调用 FormatInt(i, 10) 实现的。base 参数可以取 2~36(0-9,a-z)。

转换的基本原理(以 10 进制的 127 转 string 为例):

const digits = "0123456789abcdefghijklmnopqrstuvwxyz"
u := uint64(127)
var a [65]byte    //0-64位
i := len(a)
b := uint64(base)  //进制数
for u >= b {
    i--
    a[i] = digits[uintptr(u%b)]  //取最低位
    u /= b       //右移
} 
i--
a[i] = digits[uintptr(u)]
return string(a[1:])

即将整数每一位数字对应到相应的字符,存入字符数组中,最后字符数组转为字符串即为结果。

标准库另外提供两个函数是:比较简单,官网也有测试例程

func AppendInt(dst []byte, i int64, base int) []byte
func AppendUint(dst []byte, i uint64, base int) []byte

这两个函数不是将整数转为字符串,而是将整数转为字符数组 append 到目标字符数组中。(最终,我们也可以通过返回的 []byte 得到字符串)

注意点:区别sprintf和itoa

除了使用上述方法将整数转为字符串外,经常见到有人使用 fmt 包来做这件事。如:

v := fmt.Sprintf("%d", 123)
fmt.Printf("%s", v)

那么,这两种方式我们该怎么选择呢?我们主要来考察一下性能。

startTime := time.Now()
for i := 0; i < 10000; i++ {
    fmt.Sprintf("%d", i)
}   
fmt.Println(time.Now().Sub(startTime))

startTime := time.Now()
for i := 0; i < 10000; i++ {
    strconv.Itoa(i)
}   
fmt.Println(time.Now().Sub(startTime))  //输出结果:3.0002ms   1ms
可见时间性能相差3倍,Sprintf 性能差些可以预见,因为它接收的是 interface,需要进行反射等操作。建议使用 strconv 包中的方法进行转换。

注意:别想着通过 string(65) 这种方式将整数转为字符串,这样实际上得到的会是 ASCCII 值为 65 的字符,即 'A'。

-------------------------------------------------------------------------------------------

字符串和布尔值之间的转换

// 接受 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False 等字符串;
// 其他形式的字符串会返回错误
func ParseBool(str string) (value bool, err error)
// 直接返回 "true" 或 "false"
func FormatBool(b bool) string
// 将 "true" 或 "false" append 到 dst 中
// 这里用了一个 append 函数对于字符串的特殊形式:append(dst, "true"...)
func AppendBool(dst []byte, b bool)
-------------------------------------------------------------------------------------------

字符串和浮点数之间的转换

// FormatFloat 将浮点数 f 转换为字符串形式
// f:要转换的浮点数
// fmt:格式标记(b、e、E、f、g、G)
// prec:精度(数字部分的长度,不包括指数部分)
// bitSize:指定浮点类型(32:float32、64:float64),结果会据此进行舍入。
//
// 格式标记:
// 'b' (-ddddp±ddd,二进制指数)
// 'e' (-d.dddde±dd,十进制指数)
// 'E' (-d.ddddE±dd,十进制指数)
// 'f' (-ddd.dddd,没有指数)
// 'g' ('e':大指数,'f':其它情况)
// 'G' ('E':大指数,'f':其它情况)
//
// 如果格式标记为 'e','E'和'f',则 prec 表示小数点后的数字位数
// 如果格式标记为 'g','G',则 prec 表示总的数字位数(整数部分+小数部分)
// 参考格式化输入输出中的旗标和精度说明
func FormatFloat(f float64, fmt byte, prec, bitSize int) string


// 将字符串解析为浮点数,使用 IEEE754 规范进行舍入。
// bigSize 取值有 32 和 64 两种,表示转换结果的精度。 
// 如果有语法错误,则 err.Error = ErrSyntax
// 如果结果超出范围,则返回 ±Inf,err.Error = ErrRange
func ParseFloat(s string, bitSize int) (float64, error)

func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int)

同样的,基于性能的考虑,应该使用 FormatFloat 而不是 fmt.Sprintf。

程序示例:

func main() {
	v := 3.1415926535

	s32 := strconv.FormatFloat(v, 'E', -1, 32)
	fmt.Printf("%T, %v\n", s32, s32)

	s64 := strconv.FormatFloat(v, 'E', -1, 64)
	fmt.Printf("%T, %v\n", s64, s64)

}   //输出结果: string, 3.1415927E+00
                ing, 3.1415926535E+00
func main() {
	s := "0.12345678901234567890"

	f, err := strconv.ParseFloat(s, 32)
	fmt.Println(f, err)                // 0.123456789104328156
	fmt.Println(float32(f), err)       // 0.123456789

	f, err = strconv.ParseFloat(s, 64)
	fmt.Println(f, err)                // 0.12345678901234568
}
-------------------------------------------------------------------------------------------

其他导出的函数

如果要输出这样一句话:This is "studygolang.com" website. 该如何做?

fmt.Println(`This is "studygolang.com" website`)

如果没有 `` 符号,该怎么做?转义:

fmt.Println("This is \"studygolang.com\" website")

strconv 包还提供了函数这做件事(Quote 函数)。我们称 "studygolang.com" 这种用双引号引起来的字符串为 Go 语言字面值字符串(Go string literal)。

fmt.Println("This is", strconv.Quote("studygolang.com"), "website")

Quote函数族

// 将 s 转换为双引号字符串
func Quote(s string) string
// 将 r 转换为单引号字符
func QuoteRune(r rune) string
// 功能同上,非 ASCII 字符和不可打印字符会被转义
func QuoteRuneToASCII(r rune) string
// 功能同上,非图形字符会被转义
func QuoteRuneToGraphic(r rune) string
// 功能同上,非 ASCII 字符和不可打印字符会被转义
func QuoteToASCII(s string) string
// 功能同上,非图形字符会被转义
func QuoteToGraphic(s string) string
// Unquote 将“带引号的字符串” s 转换为常规的字符串(不带引号和转义字符)
// s 可以是“单引号”、“双引号”或“反引号”引起来的字符串(包括引号本身)
// 如果 s 是单引号引起来的字符串,则返回该该字符串代表的字符
func Unquote(s string) (string, error)
// UnquoteChar 将带引号字符串(不包含首尾的引号)中的第一个字符“取消转义”并解码
//
// s    :带引号字符串(不包含首尾的引号)
// quote:字符串使用的“引号符”(用于对字符串中的引号符“取消转义”)
//
// value    :解码后的字符
// multibyte:value 是否为多字节字符
// tail     :字符串 s 解码后的剩余部分
// error    :返回 s 中是否存在语法错误
//
// 参数 quote 为“引号符”
// 如果设置为单引号,则 s 中允许出现 \'、" 字符,不允许出现单独的 ' 字符
// 如果设置为双引号,则 s 中允许出现 \"、' 字符,不允许出现单独的 " 字符
// 如果设置为 0,则不允许出现 \' 或 \" 字符,但可以出现单独的 ' 或 " 字符
func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error)

程序示例:(更具体的详细见官网例程,感觉这个包用的比较少,这个地方等用到再详细看)

// 示例
func main() {
	s := "Hello\t世界!\n"
	fmt.Println(s)                         // Hello	世界!(换行)
	fmt.Println(strconv.Quote(s))          // "Hello\t世界!\n"
	fmt.Println(strconv.QuoteToASCII(s))   // "Hello\t\u4e16\u754c\uff01\n"
	fmt.Println(strconv.QuoteToGraphic(s)) // "Hello\t世界!\n"
}
// 将各种类型转换为带引号字符串后追加到 dst 尾部。
func AppendQuote(dst []byte, s string) []byte
func AppendQuoteRune(dst []byte, r rune) []byte
func AppendQuoteRuneToASCII(dst []byte, r rune) []byte
func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte
func AppendQuoteToASCII(dst []byte, s string) []byte
func AppendQuoteToGraphic(dst []byte, s string) []byte
// 判断 r 是否为可打印字符
// 可否打印并不是你想象的那样,比如空格可以打印,而\t则不能打印
func IsPrint(r rune) bool

// 判断 r 是否为 Unicode 定义的图形字符。
func IsGraphic(r rune) bool

// 判断字符串是否可以不被修改的表示为一个单行的反引号字符串。
// 字符串中不能含有控制字符(除了 \t)和“反引号”字符,否则返回 false
func CanBackquote(s string) bool
程序例程:
package main

import (
	"fmt"
	"strconv"
)

func main() {
	fmt.Println(strconv.CanBackquote("Fran & Freddie's Diner ☺"))
	fmt.Println(strconv.CanBackquote("`can't backquote this`"))    //输出结果 : true   false
}
-------------------------------------------------------------------------------------------
参考博客:http://www.cnblogs.com/golove/p/3262925.html
                  https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter02/02.3.html

                 https://golang.org/pkg/strconv/

前前后后已经看了好几个标准库了,感觉大同小异,看起来不费力了,最关键的是官网给的例程比较详细。

 类似资料: