1 序


首页图片 在计算机编程中,数据类型是一种基础概念,它定义了数据的特性、存储方式以及可以对其执行的操作。数据类型的正确选择和使用对于编写高效、可靠的代码至关重要。Go语言作为一门现代化、简洁而强大的编程语言,提供了丰富的数据类型,使得开发人员能够更好地控制和操作数据。

本文将带您踏上一段探索Go语言基本数据类型的奇妙之旅。我们将深入探讨Go语言中的布尔型、整数型、浮点型和字符串型等基本数据类型的特性、用法和常见操作。

变量就是承载各种数据类型的容器,变量的定义又离不开关键字,所以我们先看Go的关键字。

2 关键字

Go语言具有一些关键字(Keywords),这些关键字具有特殊的含义,不能作为标识符来使用,以下是Go语言中的关键字列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
break:用于在循环中跳出循环或在switch语句中跳出switch语句。
case:用于在switch语句中分支选择。
chan:用于定义通道类型。
const:用于定义常量。
continue:用于跳过循环中剩余的语句并开始下一次循环。
default:在switch语句中所有case都不匹配时执行的语句块。
defer:用于函数结束前执行一个语句块,常用于资源释放。
else:在if语句中,如果条件不成立时执行的语句块。
fallthrough:在switch语句中,将控制权转移到下一个case语句。
for:用于循环语句。
func:用于定义函数和方法。
go:用于启动一个新的goroutine。
goto:用于无条件跳转到代码中的某个标签。
if:用于条件语句。
import:用于导入其他包。
interface:用于定义接口类型。
map:用于定义映射类型。
package:用于定义包,每个Go文件必须在package定义的包中。
range:用于循环迭代数组、切片、字符串、映射和通道。
return:用于从函数返回一个值。
select:用于同时等待多个通道操作。
struct:用于定义结构体类型。
switch:用于根据不同的条件执行不同的分支语句。
type:用于定义自定义类型。
var:用于定义变量。

这些关键字在Go语言的语法中扮演着重要的角色,用于定义控制流程、定义变量、创建函数等。需要注意的是,除了上面列出的关键字之外,Go语言还有一些保留字(Reserved Words),虽然目前未被使用,但保留用于将来的扩展和功能增强。这些保留字包括nil、true和false等。

3 变量

什么是变量,从数学概念上讲,变量表示没有固定值且可以改变的数,在程序中,变量用来存储各数据类型没有固定值且可以改变的值,你可以可以理解为用于存储值的一种容器,从计算机底层实现角度来看,变量是一段或多段用来存储数据的内存,和C\C++\Java一样,Go是静态强类型语言,因此变量Variable需要明确指定类型,编译器也会检查变量类型的正确性。

3.1 变量定义

变量定义有两种方式,分别是通过var关键字定义和使用:=短变量定义。

3.1.1 var关键字定义变量

变量的定义语法如下:

1
2
3
var 变量名 [类型] = 值

var variableName [type] = variableValue

其中,var是关键字用于定义变量,变量名是你给变量起的名称,类型是变量的数据类型,其中类型是可省略的,表明Go的变量是可以自动推断的,值是各数据类型所表示的值。

以下是一些例子来说明如何定义不同类型的变量:

1
2
3
4
5
6
7
8
9
10
// 定义整数类型变量
var num int // 定义一个int类型的变量num
// 定义字符串类型变量
var str string // 定义一个string类型的变量str
// 定义布尔类型变量
var flag bool // 定义一个bool类型的变量flag
// 定义浮点数类型变量
var score float64 // 定义一个float64类型的变量score
// 定义字符类型变量
var ch rune // 定义一个rune类型的变量ch (用于表示Unicode字符)

注意:以上变量只是定义变量名,没有初始化值,在定义变量时,如果没有显式地指定初始值,那么变量将会被赋予其类型的零值。例如,整数类型变量的零值是0,字符串类型变量的零值是空字符串,布尔类型变量的零值是false。

在定义变量时,可以省略type,由Go语言自动推断类型。例如:

1
2
3
4
5
6
// 定义整数类型变量并赋值
var num = 20
var num int = 20
// 定义字符串类型变量并赋值
var str = "Ratel"
var str string = "Ratel"

我们还可以一次性定义多个变量,且多个变量的数据类型可以不同。例如:

1
2
3
4
5
6
// 一次性定义name和age两个变量,且数据类型不同
var (
name string = "Ratel"
age int = 20
)
fmt.Printf("%T %T", name, age) // Output: string int

3.1.2 :=运算符定义变量

我们还可以使用短变量定义语法来更简洁地定义并初始化变量。短变量定义使用冒号等于符号 := 进行赋值,赋值后,Go会自动进行类型推断。例如:

1
2
3
4
5
6
7
8
9
10
// 定义整数类型变量并赋值
num := 20
// 定义字符串类型变量并赋值
str := "Ratel"
// 定义布尔类型变量并赋值
flag := false
// 定义浮点数类型变量并赋值
score := 0.2315
// 定义字符类型变量并赋值
ch := 'A'

在短变量定义中,我们同样可以一次定义多个变量,且多个变量的数据类型可以不同,Golang会根据值自动推断变量类型。例如:

1
2
3
// 一次性定义name和age两个变量,且数据类型不同
name, age := "Ratel", 20
fmt.Printf("%T %T", name, age) // Output: string int

此时,name会被自动推断为string类型,age则被推断为int类型。

综上所述,使用var关键字可以定义不同类型的变量。同时,使用短变量定义语法也是一种常用的快捷方式来定义并初始化变量。

3.2 作用域

Golang的变量还具有作用域和生命周期。在同一个作用域中,不能使用相同名称的变量。变量的生命周期由变量在内存中的存储时间来决定。变量的生命周期可以是全局、局部或动态分配的。

例如,以下是一个在函数内定义并初始化的变量:

1
2
3
4
func main() {
var name string = "Ratel"
fmt.Println(name)
}

在以上例子中,变量name的作用域为main函数,在main函数外无法访问变量name,变量name的生命周期取决于变量name的创建和销毁时间。

4 常量

常量,顾名思义,与变量相反,常量用来存储各种数据类型有固定值且不可以改变的值。在Go语言中,常量Constant是在程序编译阶段就确定的值,它们在定义时必须赋予一个固定的初值,并且不能被修改。常量的定义和变量的定义有一些差异。

常量的定义语法如下:

1
2
3
const 常量名 [类型] = 值

const constantName [type] = constantValue

其中,const是关键字用于定义常量,常量名是你给常量起的名称,类型是常量的数据类型, 是可以省略的,表达式是常量的初始值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义整数常量
const Pi = 3.14 // 定义一个浮点数类型的常量Pi,并初始化为3.14
const MaxSize int = 100 // 定义一个整数类型的常量MaxSize,并初始化为100
// 定义字符串常量
const Name = "Ratel" // 定义一个字符串类型的常量Name,并初始化为"Ratel"
// 定义布尔常量
const IsDebug = false // 定义一个布尔类型的常量IsDebug,并初始化为false
// 定义多个常量
const (
Monday = 1
Tuesday = 2
Wednesday = 3
Thursday = 4
Friday = 5
Saturday = 6
Sunday = 7
)

这里使用了括号和换行符来分组和格式化多个常量的定义。

值得注意的是,在批量定义常量时,第一个常量的值默认被赋值为0,后续常量的值会根据前面的常量值自动递增。

在Go语言中,常量可以用于各种常见的场景,例如定义数学常量、枚举值、配置参数等。它们具有不可变性,并且可以提高代码的可读性和可维护性。

需要注意的是,Go语言常量的类型是根据初值自动推导得出的,因此在大多数情况下,常量的类型定义是可选的。

综上所述,使用const关键字可以定义不同类型的常量,并为其赋予一个固定的初值。常量在编译时确定,并且不能被修改。

5 基本数据类型


Go语言基本数据类型包括布尔类型、整数类型、浮点类型、复数类型、字符(串)类型五种类型,我们将依次介绍这些数据类型以及它们的操作。

所有的基础类型都是值类型, 这意味着当它们作为参数传递或从函数返回时,它们通过值传递给函数。

  1. 布尔类型(Boolean Type):
  • bool:表示真(true)或假(false)的布尔类型。
  1. 整数类型(Integer Types):
  • int:根据平台可能是32位或64位的有符号整数。
  • uint:根据平台可能是32位或64位的无符号整数。
  • int8、int16、int32、int64:固定大小的有符号整数类型。
  • uint8、uint16、uint32、uint64:固定大小的无符号整数类型。
  • uintptr:用于存储指针的整数类型。
  1. 浮点数类型(Floating-Point Types):
  • float32:IEEE-754 32位浮点数。
  • float64:IEEE-754 64位浮点数。
  1. 复数类型(Complex Types):
  • complex64:包含32位实部和32位虚部的复数类型。
  • complex128:包含64位实部和64位虚部的复数类型。
  1. 字符类型(Character Type):
  • byte:与uint8类型相同,用于表示ASCII字符。
  • rune:与int32类型相同,用于表示Unicode码点。
  1. 字符串类型(String Type):
  • string:表示一系列字符的字符串类型。

5.1 布尔类型

在Go语言中,bool表示布尔类型,它只有两个可能的值:true和false。布尔类型用于表示逻辑条件的真假状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
var b bool // 声明一个布尔变量
b = true // 赋值为true
fmt.Println(b) // 输出: true
// 条件判断
if b {
fmt.Println("b is true")
} else {
fmt.Println("b is false")
}
// 逻辑运算
x := true
y := false
fmt.Println(x && y) // 与运算, 输出: false
fmt.Println(x || y) // 或运算, 输出: true
fmt.Println(!x) // 非运算, 输出: false
}

需要注意的是,在条件判断语句中,只能使用布尔类型的表达式作为条件。不能使用任意其他类型的值进行条件判断。

5.2 整数类型

Go语言同时提供了有符号和无符号的整数类型,其中包括int8、int16、int32和int64四种大小截然不同的有符号整数类型,分别对应8、16、32、64 bit(二进制位)大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。

此外还有两种整数类型int和uint,它们分别对应特定CPU平台的字长(机器字大小),其中int表示有符号整数,应用最为广泛,uint表示无符号整数。实际开发中由于编译器和计算机硬件的不同,int和uint所能表示的整数大小会在32bit或64bit之间变化。

用来表示Unicode字符的rune类型和int32类型是等价的,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样,byte和uint8也是等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。

尽管在某些特定的运行环境下int、uint和uintptr的大小可能相等,但是它们依然是不同的类型,比如int和int32,虽然int类型的大小也可能是32bit,但是在需要把int类型当做int32类型使用的时候必须显示的对类型进行转换,反之亦然。

Go语言中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的取值范围是从-2(n-1) 到 2(n-1)-1。无符号整数的所有bit位都用于表示非负数,取值范围是0到2n-1。例如,int8类型整数的取值范围是从-128到127,而uint8类型整数的取值范围是从0到255。

最后,还有一种无符号的整数类型uintptr,它没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func main() {
var a int = 10
var b int = 5
var c uint = 15
var d uint = 7

sum := a + b // 加法运算
diff := a - b // 减法运算
product := a * b // 乘法运算
quotient := a / b // 除法运算
remainder := a % b // 取模运算

fmt.Println("Sum:", sum) // 输出: Sum: 15
fmt.Println("Difference:", diff) // 输出: Difference: 5
fmt.Println("Product:", product) // 输出: Product: 50
fmt.Println("Quotient:", quotient) // 输出: Quotient: 2
fmt.Println("Remainder:", remainder) // 输出: Remainder: 0

// 无符号整数的运算示例
unsignedSum := c + d // 加法运算
unsignedDiff := c - d // 减法运算
unsignedProduct := c * d // 乘法运算
unsignedQuotient := c / d // 除法运算
unsignedRemainder := c % d // 取模运算

fmt.Println("Unsigned Sum:", unsignedSum) // 输出: Unsigned Sum: 22
fmt.Println("Unsigned Difference:", unsignedDiff) // 输出: Unsigned Difference: 8
fmt.Println("Unsigned Product:", unsignedProduct) // 输出: Unsigned Product: 105
fmt.Println("Unsigned Quotient:", unsignedQuotient) // 输出: Unsigned Quotient: 2
fmt.Println("Unsigned Remainder:", unsignedRemainder) // 输出: Unsigned Remainder: 1
}

5.3 浮点数类型

float32是32位的浮点数类型,它可以表示大约6个小数位的精度。这种类型适用于对内存占用有限且精度要求不高的场景。

1
var num1 float32 = 3.14

float64是64位的浮点数类型,它可以表示大约15个小数位的精度。这种类型适用于需要更高精度的计算或涉及较大数值范围的场景。

1
var num2 float64 = 3.141592653589793

注意,如果没有指定具体的浮点数类型,默认情况下,Go语言会将浮点数值视为float64类型。

1
var num3 = 3.14 //默认为float64类型

简单四则运算。

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
a := float32(3.14)
b := float32(2.5)
sum := a + b // 加法运算
diff := a - b // 减法运算
product := a * b // 乘法运算
quotient := a / b // 除法运算
fmt.Println(sum) // 输出: 5.6400003
fmt.Println(diff) // 输出: 0.6400001
fmt.Println(product) // 输出: 7.8500004
fmt.Println(quotient) // 输出: 1.256
}

5.3.1 注意事项

  • 浮点数不适合用于精确计算,因为它们是基于二进制表示的近似值。在进行计算时可能会出现舍入误差。
  • 不要使用等号(==)来比较两个浮点数是否相等,因为舍入误差可能导致结果不准确。通过定义一个误差范围来判断浮点数是否接近。例如:
    1
    2
    3
    4
    epsilon := 1e-9 // 定义一个小的误差范围
    if math.Abs(f1-f2) < epsilon {
    // 浮点数接近
    }
  • 浮点数运算可能会导致溢出或下溢。务必注意结果是否在所选类型的范围内。
  • 避免在循环中使用浮点数作为循环控制条件,因为舍入误差可能导致无限循环或提前退出循环。
  • 在使用浮点数进行计算时,可以通过引入math包来执行更复杂的数学操作,例如开方、幂等等。
  • 尽量使用float64类型,除非有特别的需求。虽然float32占用的内存更小,但float64提供了更高的精度,并且在大多数情况下,性能影响不大。

总结起来,虽然Go提供了浮点类型进行小数运算,但需要注意浮点数的近似性、舍入误差和比较等问题。如果需要精确计算,应考虑使用整数类型或专门的十进制数库。

5.4 复数类型

在Go语言中,复数类型用于表示复数数值。Go语言中的复数类型是内置的,由complex64和complex128两种类型组成。

complex64类型表示一个复数,其中实部和虚部都是float32类型。complex128类型表示一个复数,其中实部和虚部都是float64类型。

复数有两种定义方式,一种是通过complex(a, b)声明,其中a是实部,b是虚部,例如

1
2
// complex
var c1 complex64 = complex(4, 6)

另外一种是通过a + bi方式声明,其中a是实部,b是虚部,例如:

1
2
// a + bi
var c2 complex64 = 2 + 3i

然后我们看一些关于复数的一些计算操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main {
var c1 complex64 = complex(4, 6)
var c2 complex64 = 2 + 3i
fmt.Println("real(c1) =", real(c1)) // 获取实部,输出: 4
fmt.Println("imag(c1) =", imag(c1)) // 获取虚部,输出: 6
fmt.Println("Mod(c1) =", Mod(c1)) // 获取模长,输出:7.211102550927978
sum := c1 + c2
diff := c1 - c2
product := c1 * c2
quotient := c1 / c2
fmt.Println(sum) // 输出: 6+9i
fmt.Println(diff) // 输出: 2+3i
fmt.Println(product) // 输出: -10+24i
fmt.Println(quotient) // 输出: 2+0i
}

// 计算复数的模
func Mod(c complex64) float64 {
a := real(c) * real(c)
b := imag(c) * imag(c)
return math.Sqrt(float64(a + b))
}

注意精度:在使用复数进行计算时,需要注意浮点数的精度问题。由于浮点数精度的限制,复数计算可能会产生舍入误差。因此,在比较复数是否相等时,不应该直接使用==运算符,而应该使用近似判断方法,如判断实部和虚部的差值是否在允许的误差范围内。

Go语言还提供了math/cmplx包,其中包含一些用于复数计算的函数,如求模、幅角、共轭等。你可以根据需要使用这些函数来进行更复杂的复数计算。

总之,复数类型在Go语言中提供了处理复数数值的能力,可以进行基本的复数计算操作,用于涉及复数计算、信号处理、物理模拟、图像处理和控制系统等领域。但需要注意浮点数精度和比较的问题,以及根据需求使用适当的复数运算函数和库。

5.5 字符类型

字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。

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

  • 一种是byte类型,或者叫uint8型,代表了ASCII码的一个字符。
  • 一种是rune类型,代表一个UTF-8字符,当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型等价于int32类型。它实际上是一个32位的整数类型。rune类型可以用来存储Unicode码点(Unicode code point)。要定义一个字符变量,可以使用单引号将字符括起来。

byte类型是uint8的别名,对于只占用1个字节的传统ASCII编码的字符来说,完全没有问题,例如

1
var ch byte = 'A' //字符使用单引号括起来

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

1
var ch byte = 65var ch byte = '\x41'  //(\x 总是紧跟着长度为2的16进制数)

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

Go语言同样支持Unicode(UTF-8),因此字符同样称为Unicode代码点或者runes,并在内存中使用int来表示。在文档中,一般使用格式U+hhhh来表示,其中h表示一个16进制数。

在书写Unicode字符时,需要在16进制数之前加上前缀\u或者\U。因为Unicode至少占用2个字节,所以我们使用 int16或者int类型来表示。如果需要使用到4字节,则使用\u前缀,如果需要使用到8个字节,则使用\U前缀。

1
2
3
4
5
6
7
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

输出:

1
2
3
4
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)

Unicode与ASCII类似,都是一种字符集。

字符集为每个字符分配一个唯一的ID,我们使用到的所有字符在Unicode字符集中都有一个唯一的 ID,例如上面例子中的 a 在 Unicode 与 ASCII 中的编码都是 97。汉字“你”在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。

UTF-8是编码规则,将Unicode中字符的ID以某种方式进行编码,UTF-8是一种变长编码规则,从1到4个字节不等。编码规则如下:
0xxxxxx表示文字符号0~127,兼容ASCII字符集。
从128到0x10ffff表示其他字符。

根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用3个字节。

广义的Unicode指的是一个标准,它定义了字符集及编码规则,即Unicode字符集和UTF-8、UTF-16编码等。

5.6 字符串类型

Go语言中的字符串类型用string关键字表示。字符串是不可变的序列,可以包含任意Unicode字符。可以使用双引号或反引号将字符串括起来。

在Go中,字符串是一种基本数据类型,其值是Unicode码点(code point)序列,通常被解释为UTF-8编码的字节序列。在Go语言中,字符串的底层数据结构是一个只读的字节数组,也就是一组连续的字节。

一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8字符的一个序列(当字符为ASCII码表上的字符时则占用1个字节,其它字符根据需要占用2-4个字节)。

字符串类型在Go语言中使用双引号(”)或反引号(`)括起来,例如:

1
2
3
str1 := "Hello, World!" // 使用双引号括起来的字符串
str2 := `This is a multiline
string using backticks` // 使用反引号括起来的多行字符串

字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:

1
2
3
4
5
\n:换行符
\r:回车符
\t:TAB键
\u或\U:Unicode字符
\\:反斜杠自身
1
2
3
4
func main() {
var str string = "这是我的\nGo语言教程"
fmt.Println(str)
}

输出结果为:

1
2
这是我的
Go语言教程

以下是一些常见的字符串操作:

1.字符串长度:可以使用内置函数len()获取字符串的长度,即字符的个数。例如:

1
2
3
str := "Hello, World!"
length := len(str) // 获取字符串的长度
fmt.Println(length) // 输出:13

2.字符串索引和切片:可以通过索引访问字符串中的单个字符,索引从0开始。还可以使用切片操作提取子字符串,例如:

1
2
3
str := "Hello, World!"
char := str[0] // 获取第一个字符'H'
substr := str[7:12] // 提取子字符串"World"

3.字符串拼接:可以使用加号(+)运算符将两个字符串连接起来,例如:

1
2
3
str1 := "Hello"
str2 := "World"
result := str1 + " " + str2 // 拼接字符串为"Hello World"

4.字符串比较:可以使用比较运算符(==、!=、<、>、<=、>=)对字符串进行比较,例如:

1
2
3
4
5
6
7
str1 := "Hello"
str2 := "World"
if str1 == str2 {
fmt.Println("Strings are equal")
} else {
fmt.Println("Strings are not equal")
}

5.字符串遍历:可以使用for range循环遍历字符串中的字符。例如:

1
2
3
4
5
str := "Hello, World!"

for _, char := range str {
fmt.Printf("%c ", char)
}

6.其他类型转化成字符串
strconv包是关于字符串转化的工具集。

  • 整数转字符串:使用strconv.Itoa()函数将整数类型转换为字符串。
  • 浮点数转字符串:使用strconv.FormatFloat()函数将浮点数类型转换为字符串。
  • 布尔值转字符串:可以直接使用strconv.FormatBool()将布尔值转换为字符串。
  • 其他类型转字符串:可以使用fmt.Sprintf()函数将其他类型转换为字符串。
1
2
3
4
5
6
7
8
9
10
11
12
import "strconv"

func main() {
// 将整数转换为字符串
str1 := strconv.Itoa(42)
// 将浮点数转换为字符串, 第一个参数是要转换的浮点数,第二个参数是格式,第三个参数是小数点的精度(-1表示不限制),最后一个参数是指定浮点数的位数(32或64)。
str2 := strconv.FormatFloat(num, 'f', -1, 64)
// 将布尔值转换为字符串
str3 := strconv.FormatBool(true)
// 将其他类型转换为字符串,其中%d是格式化字符串中的占位符,用于指定要转换的类型。
str4 := fmt.Sprintf("%d", num)
}

在Go语言中,fmt.Sprintf函数可以使用不同的数据类型的占位符来格式化字符串。以下是一些常见的占位符及其对应的类型:

1
2
3
4
5
%d:表示整数类型(int、int8、int16、int32、int64等)。
%f:表示浮点数类型(float32、float64)。
%s:表示字符串类型(string)。
%t:表示布尔类型(bool)。
%v:表示通用占位符,可以用于任何类型。它会根据值的类型进行适当的格式化。

除了这些常见的占位符之外,还有一些其他的占位符可以用于特定类型的数据。以下是一些额外的占位符:

1
2
3
4
5
6
%b:表示二进制数(整数类型以二进制格式输出)。
%x:表示十六进制数(整数类型以十六进制格式输出)。
%X:表示大写十六进制数(整数类型以大写格式的十六进制输出)。
%o:表示八进制数(整数类型以八进制格式输出)。
%u:表示无符号整数类型(如uint、uintptr等)。
%c:表示字符类型(rune类型,Unicode码点)。

这些占位符可以用于格式化字符串中的特定类型的数据。通过使用适当的占位符,你可以控制数据的输出格式,以满足你的需求。

需要注意的是,以上方法适用于将基本类型转换为字符串。如果需要将自定义类型转换为字符串,可以在自定义类型的方法中实现String()函数来定义其字符串表示形式。

7.字符串转化成其他类型

  • 字符串转整数:使用strconv.Atoi()函数将字符串转换为整数类型。
  • 字符串转浮点数:使用strconv.ParseFloat()函数将字符串转换为浮点数类型。
  • 字符串转布尔值:使用strconv.ParseBool()将字符串转换为布尔类型。
  • 字符串转复数类型:使用strconv.ParseComplex()函数将字符串转换为复数类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import "strconv"

    func main() {
    num1, err := strconv.Atoi("54")
    if err != nil {
    fmt.Println("转换失败:", err)
    return
    }
    fmt.Println("转换结果:", num1)

    num2, err := strconv.ParseFloat("3.14", 64)
    if err != nil {
    fmt.Println("转换失败:", err)
    return
    }
    fmt.Println("转换结果:", num2)

    num3, err := strconv.ParseBool("true")
    if err != nil {
    fmt.Println("转换失败:", err)
    return
    }
    fmt.Println("转换结果:", num3)

    num4, err := strconv.ParseComplex("1+2i", 32)
    if err != nil {
    fmt.Println("转换失败:", err)
    return
    }
    fmt.Println("转换结果:", num4)
    }

    以上这些函数尝试将字符串解析为指定的类型,并返回解析后的值和可能的错误。在使用这些函数时,记得检查返回的错误以确保转换成功。

8.字符串、byte、rune关系和转换

在Go语言中,字符串底层是一个只读的字节数组,也就是[]byte类型,但是因为字符串是只读的,所以需要使用 rune类型来表示Unicode字符,而不是byte类型。因此,我们可以简单地把string类型看成是一个包含了若干个 rune类型的数组,其中每个rune表示一个Unicode字符。

  • []byte 转 string:使用 string() 方法将字节数组转为字符串类型。
  • string 转 []byte:使用 []byte() 方法将字符串类型转为字节数组。
  • string 转 []rune:使用 []rune() 方法将字符串类型转为rune数组。
  • []rune 转 string:使用 string() 方法将rune数组转为字符串类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // []byte 转 string
    bytes := []byte{65, 66, 67, 68}
    str := string(bytes) // "ABCD"
    // string 转 []byte
    str := "ABCD"
    bytes := []byte(str) // [65 66 67 68]
    // string 转 []rune
    str := "ABCD"
    runes := []rune(str) // [65 66 67 68]
    // []rune 转 string
    runes := []rune{65, 66, 67, 68}
    str := string(runes) // "ABCD"

    需要注意的是,虽然Go语言的字符串是Unicode编码的,但是它并不是固定长度的。因为一个 Unicode 字符可能由多个字节组成,所以在Go语言中,使用UTF-8编码来表示 Unicode 字符,一个字符可能由1到4个字节组成。因此,在处理字符串时,需要注意字符的长度和字节的长度并不是一一对应的关系。

另外,strings包中提供了很多关于字符串操作的方法,总而言之,Go语言的字符串类型提供了一系列的操作和函数,可用于处理文本、字符串拼接、搜索和解析等任务。

5.7 引用类型和非引用类型

在Go语言中,有两种类型的数据类型:引用类型和非引用类型。

  • 基本数据类型(如int、float、bool、string等)是非引用类型。这些类型的变量在内存中分配的是实际值的空间。当传递这些变量时,函数会复制实际值而不是变量本身。在处理基本数据类型时,我们使用值传递。

  • 引用类型(如slice、map、channel、interface和函数类型)则是指向底层数据结构的指针的包装器。这些类型的变量在内存中分配的是指向底层数据结构的指针,而不是实际值的空间。在处理引用类型时,我们使用指针传递。

  • 指针类型是一种特殊的引用类型,用于指向变量的内存地址。指针变量保存的是变量的地址,而不是实际值。使用指针类型可以通过指针间接访问变量,也可以在函数中传递指针以通过指针访问原始变量。

6 编码

在计算机中,编码是将一种形式的数据转换为另一种形式的过程。

在Golang中,编码是指将一组字符或字符串转换为一组字节或二进制数据的过程,通常用于将数据在网络或存储介质中进行传输或存储。

Golang中支持的编码方式有多种,包括ASCII、UTF-8、UTF-16、UTF-32等。下面简要介绍几种常见的编码方式:

  • ASCII 编码:ASCII编码使用一个字节来表示一个字符,即采用8位二进制数表示一个字符,共可以表示2^8 = 256 种不同的字符。
  • UTF-8 编码:UTF-8 是一种可变长度的编码方式,使用1~4个字节来表示一个字符。对于ASCII字符,使用一个字节表示;对于中文、韩文等字符,使用3个字节表示;对于某些特殊字符,如表情符号等,需要使用4个字节表示。因此,一个字符串的长度并不等于它所占用的字节数,它由其中的字符数量决定。
  • UTF-16 编码:UTF-16使用2个字节来表示一个字符,对于 ASCII 字符,使用一个字节表示;对于大多数中文、日文、韩文等字符,使用2个字节表示;对于某些特殊字符,需要使用4个字节表示。
  • UTF-32 编码:UTF-32使用4个字节来表示一个字符,无需考虑可变长度的问题,但对于大部分字符而言,会造成空间浪费。
    在Golang中,字符串默认采用UTF-8编码,每个字符占用1~4个字节。
    1
    2
    3
    对于ASCII字符,使用一个字节表示;
    对于中文、韩文等字符,使用3个字节表示;
    对于某些特殊字符,如表情符号等,需要使用4个字节表示。
    Go中还提供了很多用于处理不同编码方式的库和函数,如encoding/json、encoding/base64、unicode/utf8等。这些库和函数可以帮助开发者在不同编码方式之间进行转换和处理。

7 指针

什么是指针?有过C\C++开发经验的同学一定非常熟悉指针,同样在Go语言中也一样,指针是一个变量,它存储了另一个变量的内存地址。因此,指针变量指向的是另一个变量的内存地址,后面我们会说明Go语言中指针和C\C++指针有啥区别。

定义指针变量时,需要在变量名前加上*,表示这是一个指针变量,例如:

1
var p *int

这表示定义了一个名为p的指向整型变量的指针。使用&运算符可以取得一个变量的地址,例如:

1
2
x := 10
p := &x

这表示取得变量x的地址,并将地址赋给指针变量p。
使用指针访问变量时,需要使用*运算符,它表示解引用操作符,例如:

1
2
3
x := 10
p := &x
fmt.Println(*p)

这表示打印出指针变量p指向的变量的值,即变量x的值。

除了定义指针变量和获取变量的地址之外,还可以使用new函数来创建指针变量。例如,创建一个指向整型变量的指针:

1
p := new(int)

这将创建一个新的整型变量,并返回它的地址,然后将地址赋给指针变量p。

指针还可以用于函数参数和返回值,以便在函数调用之间共享数据。

在使用指针时需要小心,因为如果指针指向一个无效的内存地址,程序可能会崩溃或产生不可预测的行为。因此,使用指针时需要确保指针指向的内存地址是有效的。

7.1 指针函数传参

指针可以用于函数参数的传递,当一个函数需要修改实参的值时,可以将实参的地址作为形参传递给函数,通过操作指针来达到修改实参的值的目的。例如:

1
2
3
4
5
6
7
8
9
func modify(str *string) {
*str = "hello, world"
}

func main() {
str := "hello"
modify(&str)
fmt.Println(str) // 输出:hello, world
}

7.2 指针访问结构体字段

通过指针可以更方便地访问结构体中的字段,特别是当结构体很大时,传递指针比较传递整个结构体更高效。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person struct {
Name string
Age int
}

func main() {
p := &Person{
Name: "Alice",
Age: 18,
}
p.Name = "Bob"
fmt.Println(p) // 输出:&{Bob 18}
}

7.3 通过指针动态分配内存

通过指针可以在运行时动态地分配内存,比如使用new()函数创建一个新的变量并返回它的地址。例如:

1
2
3
4
5
func main() {
p := new(int)
*p = 42
fmt.Println(*p) // 输出:42
}

7.4 传递可变参数

在Golang中,可以使用指针传递可变参数。这是因为在使用可变参数时,传递的是一个切片(slice),而切片本身就是指向一个数组的指针。这样的操作,可以避免拷贝大量的数据。

例如,考虑以下函数,它接受一个可变参数并将其打印出来:

1
2
3
4
5
func printArgs(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}

现在,如果要将切片作为参数传递给另一个函数,可以使用指针:

1
2
3
4
5
6
7
8
9
10
func printArgsPtr(args *[]int) {
for _, arg := range *args {
fmt.Println(arg)
}
}

func main() {
args := []int{1, 2, 3}
printArgsPtr(&args)
}

在这个示例中,我们定义了一个新函数printArgsPtr,它使用指针作为参数来接收切片。函数体内,我们使用*args来解引用指针,从而得到实际的切片,并遍历它以打印每个元素。在主函数中,我们创建了一个切片,并将其地址传递给printArgsPtr函数。

需要注意的是,在使用指针传递变长参数时,如果切片为空,则不能传递nil指针,而应该传递一个空的切片。这是因为在Golang中,使用空的切片和nil指针的含义不同,后面会介绍。

除了上述几个方面,指针在一些特定的场景下也非常有用,比如在处理数据结构时,通过指针可以更高效地操作链表、树等数据结构。但需要注意,过度使用指针可能会导致代码难以维护,需要谨慎使用。

8 总结

通过深入学习Go语言基本数据类型,您将能够更好地理解和掌握这些基本数据类型的特性和用法。无论您是初学者还是有经验的开发人员,对基本数据类型的深入理解都是成为一位优秀的Go语言程序员的关键,下一张我们将讲解复合数据类型。