当前位置: 首页 > news >正文

Go基础:Go基本数据类型详解

文章目录

    • 一、基本数据类型概述
      • 1.1 基本数据类型的好处
      • 1.1 基本数据类型的对比和总结
    • 二、数字类型
      • 2.1 整数类型
      • 2.2 浮点类型
      • 2.3 复数类型
    • 三、布尔类型
    • 四、字符串类型
      • 4.1 核心概念
      • 4.2 案例代码
    • 五、类型转换
    • 六、类型别名与类型定义
      • 6.1 类型别名
      • 6.2 类型定义
      • 6.3 案例代码

一、基本数据类型概述

1.1 基本数据类型的好处

Go 语言作为一门静态类型、编译型的语言,其类型系统非常严谨和高效。理解基本数据类型是掌握 Go 编程的基石。在 Go 中,每个变量都有一个静态类型,这个类型在编译时就确定了,并且不能在运行时更改。这种设计带来了诸多好处:

  • 性能:编译器可以生成高效的机器码,因为类型信息是已知的。
  • 安全性:许多类型相关的错误可以在编译阶段就被发现,避免了运行时的崩溃。
  • 可读性:明确的类型让代码的意图更加清晰,便于维护。

Go 的基本数据类型可以分为:数字类型(整数、浮点、复数)、布尔类型字符串类型

1.1 基本数据类型的对比和总结

类型类别类型关键点与最佳实践
整数int, uint首选。除非有特殊需求(如内存优化、二进制协议、特定硬件交互),否则应使用 int
int64, uint64用于处理大数,例如 Unix 时间戳、文件大小、数据库 ID 等。
byte (uint8)用于表示原始数据字节,如文件读写、网络通信。
rune (int32)处理所有文本时必须使用。当你需要遍历字符串中的每个字符(尤其是非ASCII字符)时,使用 for...range[]rune
浮点float64默认选择。精度更高,性能与 float32 相当,适用于几乎所有场景。
float32仅在内存占用是关键瓶颈且精度要求不高时使用(例如,机器学习中的某些模型权重)。
复数complex128默认选择,用于科学和工程计算。
布尔bool只能是 truefalse。在条件判断中,必须使用布尔表达式,禁止使用 if value 这样的“真值”判断。
字符串string理解其只读UTF-8特性。用 len() 获取字节长度,用 utf8.RuneCountInString() 获取字符数量。大量拼接用 strings.Builder
转换显式转换 T(v)永远记住 Go 没有隐式转换。不同类型间操作必须手动转换。数值和字符串转换使用 strconv 包。
定义type NewType Old用于创建具有特定行为(方法)的新类型,增强类型安全。
别名type Alias = Old用于重构或兼容,不创建新类型。

二、数字类型

2.1 整数类型

整数类型用于表示没有小数部分的数值。Go 提供了丰富的整数类型,分为有符号(可以表示正数、负数和零)和无符号(只能表示非负数)两大类。

1、有符号整数

类型大小(字节)取值范围描述
int81-128 到 1278位有符号整数
int162-32,768 到 32,76716位有符号整数
int324-2,147,483,648 到 2,147,483,64732位有符号整数
int648-9,223,372,036,854,775,808 到 9,223,372,036,854,775,80764位有符号整数
int平台相关32位系统上是 int32,64位系统上是 int64最常用的通用整数类型,大小与操作系统位数一致

2、无符号整数

类型大小(字节)取值范围描述
uint810 到 2558位无符号整数,常用于表示RGB颜色值
uint1620 到 65,53516位无符号整数
uint3240 到 4,294,967,29532位无符号整数
uint6480 到 18,446,744,073,709,551,61564位无符号整数
uint平台相关32位系统上是 uint32,64位系统上是 uint64最常用的通用无符号整数类型

3、特殊整数类型

  • byte: uint8别名,用于强调数据是字节(例如,处理二进制数据或 ASCII 字符)。byteuint8 可以互换使用。
  • rune: int32别名,用于表示一个 Unicode 码点。在处理多字节字符(如中文)时,必须使用 rune

3、案例代码

package main
import ("fmt""unsafe" // 用于查看变量占用的字节数
)
func main() {// 1. 声明和初始化不同大小的整数var a int8 = 127var b int16 = -32768var c uint32 = 4294967295var d uint64 = 18446744073709551615fmt.Printf("a (int8): %d, 大小: %d 字节\n", a, unsafe.Sizeof(a))fmt.Printf("b (int16): %d, 大小: %d 字节\n", b, unsafe.Sizeof(b))fmt.Printf("c (uint32): %d, 大小: %d 字节\n", c, unsafe.Sizeof(c))fmt.Printf("d (uint64): %d, 大小: %d 字节\n", d, unsafe.Sizeof(d))// 2. 溢出问题// a = a + 1 // 编译错误!constant 128 overflows int8// Go 在编译时会检查字面量的溢出,但对于变量间的运算,可能会发生“回绕”var e int8 = 127e++fmt.Printf("e (int8) 溢出后: %d\n", e) // 输出 -128,这是预期的二进制补码行为// 3. int 和 uint 的大小var f intvar g uintfmt.Printf("f (int) 大小: %d 字节\n", unsafe.Sizeof(f)) // 在64位系统上输出 8fmt.Printf("g (uint) 大小: %d 字节\n", unsafe.Sizeof(g)) // 在64位系统上输出 8// 4. byte 和 runevar h byte = 'A' // 'A' 的 ASCII 码是 65var i rune = '世' // '世' 的 Unicode 码点是 U+4E16fmt.Printf("h (byte): %d, 字符: %c\n", h, h)fmt.Printf("i (rune): %d, 字符: %c, 大小: %d 字节\n", i, i, unsafe.Sizeof(i))// 5. 数字字面量// Go 支持多种进制表示j := 42        // 十进制k := 0b101010  // 二进制,以 0b 开头l := 052       // 八进制,以 0 开头m := 0x2A      // 十六进制,以 0x 开头fmt.Printf("十进制: %d, 二进制: %b, 八进制: %o, 十六进制: %x\n", j, k, l, m)// 为了增加可读性,可以使用下划线 _ 分隔数字n := 1_000_000fmt.Printf("带下划线的数字: %d\n", n)
}

2.2 浮点类型

浮点类型用于表示带有小数部分的实数。Go 提供了两种浮点类型,遵循 IEEE 754 标准。

类型大小(字节)描述
float32432位浮点数,精度约6-7位小数
float64864位浮点数,精度约15-16位小数

1、重要提示

  • 默认类型:在 Go 中,如果你声明一个浮点数变量而不指定类型(如 f := 3.14),它默认是 float64。这是因为 float64 在现代 CPU 上计算速度和 float32 几乎一样快,但精度更高,是科学计算和大多数应用的首选。
  • 精度问题:和所有浮点数实现一样,Go 的浮点数也存在精度损失。例如,0.1 在计算机中无法被精确表示。

2、案例代码

package main
import ("fmt""math"
)
func main() {// 1. 声明和初始化var pi_float32 float32 = 3.1415926535var pi_float64 float64 = 3.14159265358979323846fmt.Printf("pi_float32: %.10f\n", pi_float32) // 输出: 3.1415927410 (精度损失)fmt.Printf("pi_float64: %.20f\n", pi_float64) // 输出: 3.14159265358979311600 (精度更高)// 2. 默认类型是 float64gravity := 9.81fmt.Printf("gravity 的类型是: %T\n", gravity) // 输出: gravity 的类型是: float64// 3. 浮点数精度问题a := 0.1b := 0.2c := a + bfmt.Printf("0.1 + 0.2 = %.18f\n", c) // 输出: 0.1 + 0.2 = 0.300000000000000044// 因此,永远不要用 == 来比较两个浮点数是否相等!// if c == 0.3 { ... } // 错误的做法// 正确的做法是判断它们之间的差值是否在一个极小的范围内(称为“epsilon”或“容差”)const epsilon = 1e-9if math.Abs(c-0.3) < epsilon {fmt.Println("c 约等于 0.3")}// 4. 科学计数法avogadro := 6.022e23 // 6.022 * 10^23planck := 6.626e-34  // 6.626 * 10^-34fmt.Printf("阿伏伽德罗常数: %e\n", avogadro)fmt.Printf("普朗克常数: %e\n", planck)// 5. math 包中的特殊值fmt.Printf("正无穷大: %f\n", math.Inf(1))fmt.Printf("负无穷大: %f\n", math.Inf(-1))fmt.Printf("非数值: %f\n", math.NaN())
}

2.3 复数类型

Go 是为数不多的在语言层面原生支持复数类型的通用编程语言之一。这对于科学计算、信号处理等领域非常方便。

类型大小(字节)描述
complex648实部和虚部都是 float32
complex12816实部和虚部都是 float64

1、 创建和使用

  • 可以使用内置的 complex() 函数创建复数:complex(realPart, imaginaryPart)
  • 也可以直接使用字面量:real + imagi

2、案例代码

package main
import ("fmt"
)
func main() {// 1. 使用 complex() 函数创建// 默认情况下,如果参数是浮点数字面量,会创建 complex128c1 := complex(3.0, 4.0)fmt.Printf("c1: %v, 类型: %T\n", c1, c1) // 输出: c1: (3+4i), 类型: complex128// 创建 complex64c2 := complex(float32(1.5), float32(2.5))fmt.Printf("c2: %v, 类型: %T\n", c2, c2) // 输出: c2: (1.5+2.5i), 类型: complex64// 2. 使用字面量创建c3 := 5 + 6ifmt.Printf("c3: %v, 类型: %T\n", c3, c3) // 输出: c3: (5+6i), 类型: complex128// 3. 提取实部和虚部// 使用内置的 real() 和 imag() 函数realPart := real(c1)imagPart := imag(c1)fmt.Printf("c1 的实部: %f, 虚部: %f\n", realPart, imagPart) // 输出: c1 的实部: 3.000000, 虚部: 4.000000// 4. 复数运算c4 := 1 + 2ic5 := 3 + 4isum := c4 + c5product := c4 * c5fmt.Printf("c4 + c5 = %v\n", sum)      // 输出: c4 + c5 = (4+6i)fmt.Printf("c4 * c5 = %v\n", product)  // 输出: c4 * c5 = (-5+10i)
}

三、布尔类型

布尔类型 bool 非常简单,它只有两个可能的值:truefalse。它主要用于逻辑判断和条件控制。

1、案例代码

package main
import "fmt"
func main() {// 1. 声明和初始化var isReady bool = truevar hasError bool = false// 类型推断isLoggedIn := truefmt.Printf("isReady: %t\n", isReady)fmt.Printf("hasError: %t\n", hasError)fmt.Printf("isLoggedIn: %t, 类型: %T\n", isLoggedIn, isLoggedIn)// 2. 逻辑运算符// && (与), || (或), ! (非)a := trueb := falsefmt.Printf("a && b: %t\n", a && b) // falsefmt.Printf("a || b: %t\n", a || b) // truefmt.Printf("!a: %t\n", !a)         // false// 3. 在条件语句中使用age := 20if age >= 18 {fmt.Println("你是成年人。")} else {fmt.Println("你是未成年人。")}// 4. 重要:Go 中没有“真值”或“假值”的概念// 在很多语言中,0, "", nil 等在布尔上下文中被视为 false。// 但在 Go 中,这是不允许的。你必须使用明确的 bool 值。// var num int = 0// if num { // 编译错误:non-bool num (type int) used as if condition// }// 正确的做法是进行显式比较var num int = 0if num == 0 {fmt.Println("num 等于 0")}
}

四、字符串类型

字符串是 Go 中最重要、最常用的数据类型之一。理解其底层实现对于编写高效、正确的 Go 代码至关重要。

Go 语言中的字符串可以表示为任意的数据,比如以下代码,在 Go 语言中,字符串通过类型 string 声明:

var s1 string = "Hello"
var s2 string = "世界"
fmt.Println("s1 is",s1,",s2 is",s2)
fmt.Println("s1+s2=",s1+s2)  // Hello世界

运行程序就可以看到打印的字符串结果。由于 s1 表示字符串“Hello”,s2 表示字符串“世界”,在终端输入 go run ch02/main.go 后,就可以打印出它们连接起来的结果“Hello世界。

4.1 核心概念

1、本质:一个 string 在 Go 中是一个只读的字节切片[]byte)。这意味着字符串的内容一旦创建就不能被修改。
2、编码:Go 的字符串默认使用 UTF-8 编码。UTF-8 是一种可变长度的编码,可以表示世界上所有的 Unicode 字符。一个英文字符占用1个字节,一个中文字符通常占用3个字节。

  • len() vs utf8.RuneCountInString():
    • len(s): 返回字符串占用的字节数
    • utf8.RuneCountInString(s): 返回字符串中Unicode 字符(rune)的数量
  • 对于纯 ASCII 字符串,两者结果相同。对于包含多字节字符的字符串,后者才是我们通常理解的“长度”。

4.2 案例代码

package main
import ("fmt""unicode/utf8"
)
func main() {// 1. 声明和初始化s1 := "Hello, Go!" // 双引号,可以包含转义字符s2 := `这是一个
多行字符串` // 反引号,原生字符串,不处理转义符,可跨行fmt.Println(s1)fmt.Println(s2)// 2. 字符串的本质是字节切片s3 := "世界"fmt.Printf("字符串 s3: %s\n", s3)fmt.Printf("s3 的字节长度 (len): %d\n", len(s3)) // 输出: 6,因为 "世" 和 "界" 各占3个字节// 3. 遍历字符串// 错误的遍历方式:按字节遍历,会破坏多字节字符fmt.Println("--- 按字节遍历 (错误) ---")for i := 0; i < len(s3); i++ {fmt.Printf("%d: %x\n", i, s3[i])}// 正确的遍历方式:使用 for...range,它会自动按 rune 解码fmt.Println("--- 按 rune 遍历 (正确) ---")for i, r := range s3 {fmt.Printf("%d: %c (码点: %U)\n", i, r, r)}// 注意:这里的 i 是字符起始字节的索引,不是连续的 0, 1, 2...// 4. 获取字符数量(rune 数量)count := utf8.RuneCountInString(s3)fmt.Printf("s3 的字符数量: %d\n", count) // 输出: 2// 5. 字符串的不可变性// s3[0] = 'A' // 编译错误!cannot assign to s3[0]// 要修改字符串,必须先将其转换为可变的类型(如 []rune),修改后再转回 stringoriginal := "hello"runes := []rune(original)runes[0] = 'H'modified := string(runes)fmt.Printf("原始字符串: %s, 修改后: %s\n", original, modified)// 6. 字符串拼接str1 := "Go"str2 := "语言"result := str1 + str2fmt.Printf("拼接结果: %s\n", result) // Go语言// 对于大量拼接,使用 strings.Builder 更高效var builder strings.Builderfor i := 0; i < 5; i++ {builder.WriteString(str1)}fmt.Printf("高效拼接结果: %s\n", builder.String())
}

五、类型转换

Go 语言是强类型语言,并且不支持隐式类型转换。不同类型的变量之间赋值或运算,必须进行显式类型转换。这个规则虽然严格,但能有效避免很多因类型不匹配导致的潜在 bug。

1、语法

T(v)
// 将值 v 转换为类型 T

以字符串和数字互转这种最常见的情况为例,如下面的代码所示:

i2s:=strconv.Itoa(i)
s2i,err:=strconv.Atoi(i2s)
fmt.Println(i2s,s2i,err)

通过包 strconv 的 Itoa 函数可以把一个 int 类型转为 string,Atoi 函数则用来把 string 转为 int。

同理对于浮点数、布尔型,Go 语言提供了 strconv.ParseFloat、strconv.ParseBool、strconv.FormatFloat 和 strconv.FormatBool 进行互转。

对于数字类型之间,可以通过强制转换的方式,如以下代码所示:

i2f:=float64(i)
f2i:=int(f64)
fmt.Println(i2f,f2i)

这种使用方式比简单,采用“类型(要转换的变量)”格式即可。采用强制转换的方式转换数字类型,可能会丢失一些精度,比如浮点型转为整型时,小数点部分会全部丢失,你可以自己运行上述示例,验证结果。把变量转换为相应的类型后,就可以对相同类型的变量进行各种表达式运算和赋值了。

2、案例代码

package main
import "fmt"
func main() {var i int = 42var f float64 = 3.14// int -> float64f_int := float64(i)fmt.Printf("int %d 转换为 float64: %f\n", i, f_int)// float64 -> int (会截断小数部分,不是四舍五入)i_float := int(f)fmt.Printf("float64 %f 转换为 int: %d\n", f, i_float)// 不同类型之间的运算必须先转换// var sum int = i + f // 编译错误:invalid operation: mismatched types int and float64var sum float64 = float64(i) + ffmt.Printf("int + float64 的和: %f\n", sum)// 数值类型和 string 之间的转换// 不能直接转换!// var s string = string(42) // 这不会得到 "42",而是得到 '*' (ASCII 码为 42 的字符)// fmt.Println(s)// 正确的转换方式是使用 strconv 包import "strconv"num := 123strFromInt := strconv.Itoa(num) // Itoa = Integer to ASCIIfmt.Printf("int %d 转换为 string: \"%s\"\n", num, strFromInt)str := "456"intFromStr, err := strconv.Atoi(str) // Atoi = ASCII to Integerif err != nil {fmt.Println("转换失败:", err)} else {fmt.Printf("string \"%s\" 转换为 int: %d\n", str, intFromStr)}// float 和 string 的转换pi_str := "3.14159"pi_float, err := strconv.ParseFloat(pi_str, 64)if err != nil {fmt.Println("转换失败:", err)} else {fmt.Printf("string \"%s\" 转换为 float64: %f\n", pi_str, pi_float)}
}

六、类型别名与类型定义

Go 提供了两种创建新类型名称的方式,它们有本质区别。

6.1 类型别名

使用 = 号,它只是为现有类型创建了一个别名。别名和原类型是完全相同的,可以互相赋值。

type MyInt = int // MyInt 是 int 的一个别名
  • 用途:主要用于大型代码库重构或兼容性处理。当你想给一个类型起一个更有意义的名字,但又不想让它成为一个全新的类型时,可以使用别名。

6.2 类型定义

不使用 = 号,它创建了一个全新的类型。新类型和原类型虽然底层结构相同,但它们是两种不同的类型,不能直接互相赋值。

type MyInt int // MyInt 是一个基于 int 的新类型
  • 用途:用于增强代码的类型安全。你可以为新类型定义自己的方法,从而赋予它独特的行为。

6.3 案例代码

package main
import "fmt"
// 类型别名
type AliasInt = int
// 类型定义
type DefinedInt int
// 为新定义的类型添加方法
func (d DefinedInt) print() {fmt.Println("这是一个 DefinedInt 类型的值:", d)
}
func main() {var i int = 100var a AliasIntvar d DefinedInt// 1. 类型别名可以和原类型直接赋值a = i // OKi = a // OKfmt.Printf("int 和 AliasInt 可以互转: i=%d, a=%d\n", i, a)// 2. 类型定义不能和原类型直接赋值// d = i // 编译错误!cannot use i (type int) as type DefinedInt in assignment// 必须进行显式转换d = DefinedInt(i)fmt.Printf("int 转换为 DefinedInt: d=%d\n", d)// 3. 新类型可以有自己的方法d.print() // 调用 DefinedInt 的方法// 4. 类型别名没有引入新类型,所以它没有自己的方法// a.print() // 编译错误!a.print undefined (type AliasInt has no field or method print)
}

掌握这些基本数据类型及其特性,是编写出健壮、高效且符合 Go 语言设计哲学的代码的第一步。

http://www.dtcms.com/a/393015.html

相关文章:

  • 项目管理(一)
  • 【STM8L101 执行函数FLASH_ProgramBlock出现问题】
  • ​​[硬件电路-278]:双向双电源电平转换收发器74AXP2T45DCH功能概述、管脚定义
  • 音视频同步的原理和实现方式
  • BUG调试案例十八:TPS5430输出震荡问题案例
  • Python读取Excel文件里面指定列中的指定范围行
  • C语言入门教程 | 阶段二:控制结构详解(条件语句与 switch 语句)
  • Linux 4.x hook系统调用的问题
  • 了解 Highcharts 响应式功能:构建适配各种屏幕的图表界面
  • 逻辑分析仪解码脚本实例解析——UART
  • 垃圾回收中的STW是什么?
  • redis未授权漏洞扫描器
  • LTE/EPC 架构
  • ANSYS学习
  • 【python】安装jieba库
  • tyza66的博客:专注软件开发、全栈开发与开源项目的技术分享
  • Redis最佳实践——购物车优化详解
  • Netty从0到1系列之Netty内存管理【下】
  • 【使用函数求余弦COS函数的近似值】2022-11-27
  • 前端违规页面车主信息优化说明
  • 成功安装了 Anaconda3。要启动它,您有以下几种主要方式:方式一:通过“开始菜单”启动(最直接的方法)1. 点击您电脑屏幕左下角的 “开始菜单”(Win
  • flex布局实现导航栏横向滚动切换
  • 改进过程缺乏数据驱动会带来哪些后果
  • 实验1.1点亮led灯
  • 林粒粒的视频笔记13-数据清洗
  • Java进阶教程,全面剖析Java多线程编程,线程出让,笔记09
  • 大模型微调之 用LoRA微调Llama2(附代码)——李宏毅2025大模型作业5笔记-上
  • Matplotlib地理数据可视化技术详解:Cartopy与Basemap实战指南
  • wordpress 图片不显示 后台无法登陆的问题之一
  • TFS-2023《Local-Global Fuzzy Clustering With Anchor Graph》