在计算机系统中,所有信息最终都以二进制形式(0 和 1)存储和处理。只有当这些比特被组织并赋予明确的数据类型后,才能正确地进行解析和运算。不同的数据类型,决定了数据的存储方式和可进行的操作。
Go 语言中的数据类型体系主要分为以下四类:

整数就像我们平时数数用的数字,比如1、2、3、100、-5等等。在Go语言里,整数有很多种“规格”,就像衣服有S、M、L、XL一样,不同的规格适合不同的用途。
Go语言提供了有符号和无符号两种整数。有符号整数可以表示正数、负数和零,就像温度计可以显示正温度、负温度和零度;无符号整数只能表示非负数,就像年龄、人数这些永远不会是负数。
Go语言提供了四种不同“大小”的有符号整数:
int8:8位整数,范围是-128到127,就像一个小温度计int16:16位整数,范围更大,能表示-32768到32767int32:32位整数,能表示大约±21亿的数字int64:64位整数,能表示非常大的数字对应的无符号版本是uint8、uint16、uint32、uint64,它们只能表示非负数,但能表示的最大值更大。
还有两个特殊的类型:
int:这是最常用的整数类型,在32位系统上是32位,在64位系统上是64位uint:对应的无符号版本rune是int32的别名,专门用来表示Unicode字符。就像身份证号码虽然也是数字,但它的含义和普通数字不同。
byte是uint8的别名,专门用来表示原始数据。就像面粉虽然也是白色粉末,但它的用途和白糖不同。
有符号整数使用“补码”表示法,就像时钟一样,12点既可以表示中午12点,也可以表示午夜12点,关键看你怎么理解。在计算机里,最高位用来表示正负号,剩下的位表示数值。
举个例子:
|var 温度 int8 = 25 // 25度 var 零下温度 int8 = -10 // -10度
无符号整数就像年龄一样,永远不会是负数:
|var 年龄 uint8 = 25 // 25岁 var 人数 uint16 = 1000 // 1000人
Go语言支持我们熟悉的数学运算:加、减、乘、除、取余。
|var a int = 10 var b int = 3 fmt.Println(a + b) // 13 fmt.Println(a - b) // 7 fmt.Println(a * b) // 30 fmt.Println(a / b) // 3(整数除法,结果也是整数) fmt.Println(a % b) // 1(取余数)
就像水杯装不下太多水会溢出一样,整数也有"溢出"问题。当计算结果超出类型能表示的范围时,就会发生溢出:
|var 小杯子 uint8 = 255 // 最大容量 fmt.Println(小杯子 + 1) // 0(溢出了,就像水杯满了再加一滴水) var 温度计 int8 = 127 // 最高温度 fmt.Println(温度计 + 1) // -128(溢出变成最低温度)
整数之间可以比较大小,就像比较两个人的年龄一样:
|var 小明年龄 int = 18 var 小红年龄 int = 20 fmt.Println(小明年龄 == 小红年龄) // false fmt.Println(小明年龄 < 小红年龄) // true fmt.Println(小明年龄 >= 18) // true
位运算可以让我们像操作积木一样,对数字的每一位进行精确的控制和操作。你可以把一个数字的二进制位想象成一排小开关,通过位运算,我们可以打开、关闭或者切换这些开关,实现诸如按位与、按位或、按位异或等多种操作。这在底层编程、权限控制、数据压缩等场景中非常有用。
|var x uint8 = 60 // 二进制:00111100 var y uint8 = 13 // 二进制:00001101 fmt.Printf("x = %08b\n", x) // 00111100 fmt.Printf("y = %08b\n", y) // 00001101 fmt.Printf("x & y = %08b\n", x & y) // 00001100(按位与) fmt.Printf(
移位运算可以把一个数字的二进制位整体向左或向右"搬家",就像你把一排积木整体往左或往右挪动一样。向左移一位,每一位都往高位方向移动,相当于这个数字乘以2;向右移一位,每一位都往低位方向移动,相当于这个数字除以2(只保留整数部分)。这种操作在需要快速进行乘除2的幂时非常有用。
|var 数字 uint8 = 8 // 二进制:00001000 fmt.Printf("原数字:%08b\n", 数字) // 00001000 fmt.Printf("左移1位:%08b\n", 数字 << 1) // 00010000(相当于乘以2) fmt.Printf("右移1位:%08b\n", 数字 >> 1) // 00000100(相当于除以2)
在Go语言中,不同类型的整数(比如int8、int16、int32、int64等)之间不能直接进行运算。如果你尝试把它们直接相加或者相减,编译器会报错。这就像你不能直接把人民币和美元相加,必须先把它们换算成同一种货币。要让不同类型的整数参与同一个运算,必须先通过类型转换,把它们变成相同的类型后再进行计算。
|var 人民币 int32 = 100 var 美元 int16 = 15 // 这样会报错,类型不匹配 // var 总金额 int = 人民币 + 美元 // 需要先转换类型 var 总金额 int = int(人民币) + int(美元) fmt.Println(总金额) // 115
在Go语言中,我们可以用不同的方式来表示一个数字的字面量。比如,你可以用普通的十进制,也可以用八进制、十六进制,甚至直接用二进制来写一个数字。这些不同的写法让我们在处理不同场景(比如底层编程、位运算或者和硬件打交道时)更加灵活和方便。
|var 十进制 int = 42 // 普通十进制 var 八进制 int = 052 // 以0开头表示八进制 var 十六进制 int = 0x2A // 以0x开头表示十六进制 var 二进制 int = 0b101010 // 以0b开头表示二进制 fmt.Println(十进制) // 42 fmt.Println(八进制) // 42 fmt.Println(十六进制) // 42 fmt.Println
在Go语言中,我们可以通过fmt包灵活地控制数字的输出格式。比如,你可以选择以十进制、八进制、十六进制等不同方式来显示同一个数字,还能决定是否带上进制前缀,甚至可以指定输出的大小写。这让我们在调试、日志记录或者和其他系统交互时,能够更清晰、准确地展示数据。
|数字 := 42 fmt.Printf("十进制:%d\n", 数字) // 42 fmt.Printf("八进制:%o\n", 数字) // 52 fmt.Printf("十六进制:%x\n", 数字) // 2a fmt.Printf("大写十六进制:%X\n", 数字) // 2A fmt.Printf("带前缀的八进制:%#o\n", 数字)
记住,选择合适的整数类型就像选择合适的工具一样重要。用螺丝刀拧螺丝,用扳手拧螺母,每种工具都有它的用途。
浮点数就是我们平时说的“小数”,比如3.14、2.5、-0.001等等。在Go语言里,浮点数就像一把可以测量很精确数值的尺子,既能测量很大的距离,也能测量很小的距离。
Go语言提供了两种"精度"的浮点数:
float32:32位浮点数,精度大约6位十进制数字float64:64位浮点数,精度大约15位十进制数字就像普通尺子只能精确到毫米,而游标卡尺可以精确到0.01毫米一样,float64比float32更精确。在实际编程中,我们通常使用float64,因为它更精确,不容易出现计算误差。
在Go语言中,浮点数的表示范围极其广阔。它们既可以表示非常接近于零的微小数值,也能表示极其巨大的数值。例如,float32类型可以表示大约1.4乘以10的负45次方到3.4乘以10的38次方之间的数,而float64类型的范围则更大,从大约4.9乘以10的负324次方到1.8乘以10的308次方。这种宽广的表示能力让我们能够处理科学计算、工程测量等对数值范围要求极高的场景。
|import "math" fmt.Printf("float32的最大值:%g\n", math.MaxFloat32) // 约3.4e38 fmt.Printf("float64的最大值:%g\n", math.MaxFloat64) // 约1.8e308 fmt.Printf("float32的最小正值:%g\n", math.SmallestNonzeroFloat32) // 约1.4e-45 fmt.Printf("float64的最小正值:%g\n", math.SmallestNonzeroFloat64) // 约4.9e-324
浮点数在计算机中的存储方式决定了它们无法精确表示所有的小数。比如,像0.1、0.2这样的简单小数,在二进制浮点数中其实是无法精确存储的。这类似于我们用十进制表示1/3时,只能写成0.3333...,永远无法完全精确。计算机用有限的位数去近似表示这些小数,因此在进行浮点数运算时,可能会出现微小的误差。这种现象在金融计算、科学计算等对精度要求很高的场景下尤其需要注意。
|var 价格 float32 = 16.78 var 数量 float32 = 3 fmt.Printf("单价:%.2f\n", 价格) // 16.78 fmt.Printf("数量:%.2f\n", 数量) // 3.00 fmt.Printf("总价:%.2f\n", 价格 * 数量) // 50.34 // 但是要注意精度问题 var 分数 float32 =
在Go语言中,浮点数字面量的写法非常灵活。你可以直接写一个带小数点的数字,比如3.5,也可以只写整数部分后面加小数点(如10.),或者只写小数点后面的部分(比如.25)。此外,还可以用科学计数法来表示非常大或非常小的数值,比如1.2e6表示1200000,5.67E-3表示0.00567。这些不同的写法让我们能够根据实际需要,方便地表达各种浮点数。
|var 圆周率 float64 = 3.14159 var 自然对数 float64 = 2.71828 var 科学计数法 float64 = 1.23e-4 // 0.000123 var 大数字 float64 = 6.02e23 // 阿伏伽德罗常数 fmt.Println(圆周率) // 3.14159 fmt.Println(自然对数) // 2.71828 fmt.Println(科学计数法) // 0.000123 fmt.Println
浮点数类型不仅可以进行加法、减法、乘法和除法等基本运算,还支持括号优先级、取余(通过math.Mod函数实现)等更复杂的数学操作。例如,你可以用浮点数计算平均值、增长率、温度变化比例等。在实际编程中,常见的浮点数运算包括:
此外,Go语言的math包还提供了许多针对浮点数的高级数学函数,比如开方(math.Sqrt)、幂运算(math.Pow)、三角函数(math.Sin、math.Cos)等,帮助我们处理更复杂的科学和工程计算。
|var 温度 float64 = 25.5 var 温差 float64 = 3.2 fmt.Printf("当前温度:%.1f°C\n", 温度) // 25.5°C fmt.Printf("温差:%.1f°C\n", 温差) // 3.2°C fmt.Printf("最高温度:%.1f°C\n", 温度 + 温差) // 28.7°C
浮点数类型包含几个特殊的数值,这些特殊值在数学计算和错误处理中非常有用。它们包括正无穷大(+Inf)、负无穷大(-Inf)和非数字(NaN,Not a Number)。 这些特殊值通常在进行某些数学运算时产生,比如除以零或对负数开平方根。理解这些特殊值的含义和产生方式,对于编写健壮的数值计算程序至关重要:
|var 零 float64 = 0.0 var 正无穷 float64 = 1.0 / 0.0 var 负无穷 float64 = -1.0 / 0.0 var 非数字 float64 = 0.0 / 0.0 fmt.Printf("零:%g\n", 零) // 0 fmt.Printf("正无穷:%g\n", 正无穷)
在实际编程中,我们经常需要检查和处理这些特殊值,以确保程序的稳定性和可靠性。Go语言的math包提供了专门的函数来检测这些特殊情况:
math.IsNaN() 检查是否为非数字(NaN)math.IsInf() 检查是否为无穷大,第二个参数为1检查正无穷,为-1检查负无穷,为0检查任意无穷math.IsFinite() 检查是否为有限数值(既不是无穷大也不是NaN)这种检查在处理用户输入、传感器数据或复杂数学计算时特别重要,可以避免程序因异常数值而崩溃:
|import "math" func 检查温度(温度 float64) string { if math.IsNaN(温度) { return "温度传感器故障" } if math.IsInf(温度, 1) { return "温度过高" } if math.IsInf(温度, -1) { return "温度过低" } return
让我们看一个计算圆的面积和周长的例子:
|func 计算圆(半径 float64) (面积, 周长 float64) { const π = 3.14159 面积 = π * 半径 * 半径 周长 = 2 * π * 半径 return } 半径 := 5.0 面积, 周长 := 计算圆(半径) fmt.Printf(
由于浮点数在计算机内部使用二进制表示,而许多十进制小数无法在二进制中精确表示,这会导致微小的舍入误差。
因此,直接使用 == 或 != 比较两个浮点数往往会得到意外的结果。这是所有编程语言中浮点数运算的共同特性,不仅仅是Go语言的问题:
|var a float64 = 0.1 + 0.2 var b float64 = 0.3 fmt.Printf("a = %.20f\n", a) // 0.30000000000000004441 fmt.Printf("b = %.20f\n", b) // 0.29999999999999998890 fmt.Printf("a == b: %t\n", a == b) // false // 正确的比较方法 const
Go语言的math包提供了非常丰富的数学函数库,涵盖了三角函数、对数函数、指数函数、幂函数等各种数学运算。
这些函数经过优化,能够提供高精度的计算结果,是科学计算和工程应用的得力助手。让我们通过一些实际例子来了解常用的数学函数:
|import "math" var 角度 float64 = 45.0 var 弧度 float64 = 角度 * math.Pi / 180.0 fmt.Printf("角度:%.1f度\n", 角度) fmt.Printf("正弦值:%.4f\n", math.Sin(弧度)) // 0.7071 fmt.Printf("余弦值:%.4f\n
在实际编程中,我们需要注意以下几点:
float64:除非有特殊的内存限制,否则使用float64更安全|// 推荐:使用float64 var 身高 float64 = 175.5 var 体重 float64 = 68.2 // 特殊用途才使用float32 var 游戏坐标 float32 = 123.45 // 游戏中的坐标通常不需要很高精度
记住,浮点数就像一把精确的尺子,能测量很大和很小的数值,但要注意它的精度限制,就像尺子也有最小刻度一样。
复数就像数学中的“超级数字”,它们包含两个部分:实部和虚部。在现实生活中,复数主要用于科学计算、信号处理、电气工程等领域。在Go语言中,复数就像一把能够处理复杂数学问题的“瑞士军刀”。
Go语言提供了两种“精度”的复数:
complex64:64位复数,实部和虚部各占32位complex128:128位复数,实部和虚部各占64位就像float64比float32更精确一样,complex128比complex64提供更高的精度,在实际编程中我们通常使用complex128。
在Go语言中,创建复数有多种灵活的方式,每种方式都有其特定的用途和优势。让我们来详细了解这些创建复数的方法:
|// 方法1:使用complex函数 var z1 complex128 = complex(3, 4) // 3 + 4i // 方法2:直接写复数形式 var z2 complex128 = 3 + 4i // 方法3:使用虚数字面量 var z3 complex128 = 3 + 4i fmt.Printf("z1 = %v\n", z1) // (3+4i) fmt.Printf
复数就像一个装着两个数字的盒子,一个叫"实部",一个叫"虚部"。实部是我们熟悉的普通数字,而虚部则是带有虚数单位"i"的部分。想象复数就像一个坐标点,实部是横坐标,虚部是纵坐标。
Go语言为我们提供了两个内置函数来"拆解"复数,让我们能够分别获取这两个重要的组成部分:
|var z complex128 = 3 + 4i 实部 := real(z) // 获取实部 虚部 := imag(z) // 获取虚部 fmt.Printf("复数:%v\n", z) // (3+4i) fmt.Printf("实部:%.1f\n", 实部) // 3.0 fmt.Printf("虚部:%.1f\n", 虚部) // 4.0
复数支持我们熟悉的数学运算,包括加法、减法、乘法和除法。虽然这些运算的符号与实数相同,但背后的计算规则更加复杂和有趣。 让我们通过实际例子来看看这些运算:
|var a complex128 = 1 + 2i var b complex128 = 3 + 4i fmt.Printf("a = %v\n", a) // (1+2i) fmt.Printf("b = %v\n", b) // (3+4i) fmt.Printf("a + b = %v\n", a + b) // (4+6i)
复数的模(绝对值)和共轭是复数理论中的两个核心概念,它们在数学计算和工程应用中都扮演着重要角色。
复数的模:也称为绝对值或幅值,表示复数在复平面上与原点的距离。对于复数 z = a + bi,其模的计算公式是 |z| = √(a² + b²)。模总是一个非负的实数,它反映了复数的"大小"。
复数的共轭:对于复数 z = a + bi,其共轭复数是 z* = a - bi,即实部保持不变,虚部变为相反数。共轭复数在复平面上关于实轴对称,是许多复数运算和证明的基础。
|import "math/cmplx" var z complex128 = 3 + 4i 模 := cmplx.Abs(z) // 计算模 共轭 := cmplx.Conj(z) // 计算共轭 fmt.Printf("复数:%v\n", z) // (3+4i) fmt.Printf("模:%.2f\n", 模) // 5.00
除了常见的代数形式(a + bi),复数还可以用极坐标形式来表示,即通过"模"和"幅角"来描述。模(r)表示复数到原点的距离,幅角(θ)表示复数与实轴正方向的夹角。
极坐标形式通常写作 r·e^(iθ),其中 r = |z|,θ = arg(z)。在Go语言中,我们可以使用 cmplx.Abs 计算模,使用 cmplx.Phase 计算幅角。这样不仅有助于理解复数的几何意义,也方便进行乘法、除法等运算。
|import "math/cmplx" var z complex128 = 1 + 1i 模 := cmplx.Abs(z) 幅角 := cmplx.Phase(z) fmt.Printf("复数:%v\n", z) // (1+1i) fmt.Printf("模:%.4f\n", 模) // 1.4142 fmt.Printf
Go语言的math/cmplx包提供了丰富的复数数学函数,包括常见的三角函数、指数函数、对数函数等。这些函数在处理复数时,会自动处理复数运算的特殊规则,确保结果的正确性。
|import "math/cmplx" var z complex128 = 2 + 3i fmt.Printf("复数:%v\n", z) // (2+3i) fmt.Printf("平方根:%v\n", cmplx.Sqrt(z)) // (1.6741492280355401+0.8959774761298381i) fmt.Printf("指数:%v\n", cmplx.Exp(z)) // (-7.315110094901103+1.0427436562359045i)
在Go语言中,复数类型(如 complex64 和 complex128)支持判断两个复数是否"相等",也就是可以用 == 或 != 运算符来比较它们的实部和虚部是否都相同。例如,3+4i == 3+4i 结果为 true,而 3+4i == 3-4i 结果为 false。
但是,复数不支持像实数那样的大小比较(比如 <、>、<=、>= 这些运算符)。这是因为复数在数学上没有严格的"大小"概念,无法直接判断一个复数比另一个复数"大"或"小"。如果尝试对复数使用这些比较运算符,编译时会报错。
举个例子:
这样设计是为了避免数学上的歧义,也符合复数的基本性质。
|var a complex128 = 3 + 4i var b complex128 = 3 + 4i var c complex128 = 3 - 4i fmt.Printf("a == b: %t\n", a == b) // true fmt.Printf("a == c: %t\n", a == c)
使用fmt包可以控制复数的显示格式:
|var z complex128 = 3.14159 + 2.71828i fmt.Printf("默认格式:%v\n", z) // (3.14159+2.71828i) fmt.Printf("科学计数法:%e\n", z) // (3.141590e+00+2.718280e+00i) fmt.Printf("大写科学计数法:%E\n", z) // (3.141590E+00+2.718280E+00i) fmt.Printf("紧凑格式:%g\n", z) // (3.14159+2.71828i)
类型为bool或布尔值的值只有两个可能的值,true和false。if和for语句中的条件是布尔值,比较运算符如==和<产生布尔结果。
一元运算符!是逻辑否定,所以!true是false,或者可以说,(!true==false)==true,虽然作为风格问题,我们总是简化冗余的布尔表达式如x==true为x。
布尔值可以用&&(AND)和||(OR)运算符组合,它们具有短路行为:如果答案已经由左操作数的值确定,则不评估右操作数,使得安全地写出这样的表达式:
|s != "" && s[0] == 'x'
需要注意的是,如果你尝试对一个空字符串s使用索引操作(比如s[0]),程序会直接发生运行时错误(panic),因为空字符串没有任何字节可供访问。
由于&&的优先级高于||(助记符:&&是布尔乘法,||是布尔加法),这种形式的条件不需要括号:
|if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { // ...ASCII字母或数字... }
没有从布尔值到数值(如0或1)的隐式转换,反之亦然。必须使用显式的if,如:
|i := 0 if b { i = 1 }
如果经常需要这种操作,可能值得写一个转换函数:
|// btoi如果b为true返回1,如果false返回0 func btoi(b bool) int { if b { return 1 } return 0 }
逆操作如此简单,不需要函数,但为了对称性,这里是:
|// itob报告i是否非零 func itob(i int) bool { return i != 0 }
字符串就像我们平时写的文字,比如"你好"、"Hello World"、"123"等等。在Go语言中,字符串就像一串珠子,每个珠子代表一个字符,这些字符按照特定的顺序排列,形成有意义的文本。
字符串是字节的不可变序列,这意味着一旦创建了字符串,就不能修改它的内容。就像刻在石头上的字一样,一旦刻好就不能轻易改变。字符串可以包含任何数据,包括中文、英文、数字、符号等。
|var 问候 string = "你好,世界!" var 英文 string = "Hello, World!" var 数字 string = "12345" var 混合 string = "Hello 你好 123" fmt.Println(问候) // 你好,世界! fmt.Println(英文) // Hello, World! fmt.Println(数字) // 12345 fmt.Println(混合) // Hello 你好 123
在Go语言中,我们可以通过内置的len函数来获取字符串的长度。你可以把字符串想象成一串有序排列的珠子,len函数就像一个计数器,帮你数清楚这串珠子里到底有多少个。需要注意的是,len返回的是字节数,而不是字符数,因为有些字符(比如中文)会占用多个字节。
|var 文本 string = "Hello, 世界" fmt.Printf("字符串:%s\n", 文本) fmt.Printf("字节长度:%d\n", len(文本)) // 13(因为中文字符占多个字节)
在Go语言中,字符串中的每个字符都可以通过一个编号(也叫索引)来访问。这个编号是从0开始的,也就是说,第一个字符的索引是0,第二个字符的索引是1,依此类推。例如,如果你有一个字符串"Hello",那么'H'的索引是0,'e'的索引是1,最后一个'o'的索引是4。通过索引,你可以像查找字典一样,快速定位和获取字符串中的任意一个字符。
|var 文本 string = "Hello" fmt.Printf("第一个字符:%c\n", 文本[0]) // H fmt.Printf("第二个字符:%c\n", 文本[1]) // e fmt.Printf("最后一个字符:%c\n", 文本[len(文本)-1]) // o
在Go语言中,我们可以通过“切片”操作从一个字符串中提取出其中的一部分内容。你可以把字符串想象成一串有序排列的珠子,切片操作就像用剪刀把这串珠子分成你想要的那一段。例如,如果你只想要字符串的前几个字符、中间的一段,或者从某个位置一直到结尾,都可以通过切片来实现。切片的语法是字符串[起始索引:结束索引],其中“起始索引”表示从第几个字符(从0开始)开始,“结束索引”表示到第几个字符为止(但不包括这个位置)。这样,你就可以灵活地获取字符串的任意部分,非常方便。
|var 完整文本 string = "Hello, 世界" // 获取前5个字符 前5个 := 完整文本[:5] fmt.Printf("前5个字符:%s\n", 前5个) // Hello // 获取从第7个字符开始的部分 后半部分 := 完整文本[7:] fmt.Printf("后半部分:%s\n", 后半部分) // 世界 // 获取中间部分(第2到第5个字符) 中间部分 := 完整文本[2:
在Go语言中,如果你有多个字符串,可以通过多种方式把它们拼接成一个更长的新字符串。你可以把每个字符串想象成一段彩色的绳子,连接操作就像把这些绳子一根一根地打结,最终得到一条更长的彩绳。无论是把名字和姓氏合成全名,还是把不同的单词组合成一句话,字符串的连接都非常常用。下面我们会详细介绍几种常见的字符串拼接方法,并举例说明它们的用法。
|var 姓 string = "张" var 名 string = "三" // 方法1:使用+运算符 全名1 := 姓 + 名 fmt.Printf("全名1:%s\n", 全名1) // 张三 // 方法2:使用fmt.Sprintf 全名2 := fmt.Sprintf("%s%s", 姓, 名) fmt.Printf("全名2:%s\n", 全名2)
在Go语言中,字符串之间是可以直接进行比较的。这种比较方式类似于我们查字典时的顺序,也就是“字典序”或“按字母顺序”排列。比如,如果你有两个字符串,Go会逐个比较它们的字符(从左到右),直到遇到不同的字符为止,然后根据它们的Unicode编码值判断大小关系。如果所有字符都相同,则这两个字符串相等。通过这种方式,你可以判断两个字符串是否相等,或者一个字符串是否“排在”另一个字符串前面或后面。
|var 名字1 string = "张三" var 名字2 string = "李四" var 名字3 string = "张三" fmt.Printf("名字1 == 名字2: %t\n", 名字1 == 名字2) // false fmt.Printf("名字1 == 名字3: %t\n", 名字1 == 名字3) // true fmt.Printf("名字1 < 名字2: %t\n", 名字1
在Go语言中,字符串的内容一旦创建就无法被直接更改。也就是说,字符串是“只读”的,每次你对字符串进行看似修改的操作,实际上都是在生成一个全新的字符串,而不是在原有的字符串上进行修改。例如,你不能像操作数组那样通过下标直接替换字符串中的某个字符。如果你需要改变字符串的某一部分,通常的做法是通过拼接、切片等方式,构造出一个新的字符串变量。
|var 文本 string = "Hello" // 这样会报错,不能直接修改字符串 // 文本[0] = 'h' // 编译错误 // 正确的方法是创建新的字符串 新文本 := "h" + 文本[1:] fmt.Printf("原文本:%s\n", 文本) // Hello fmt.Printf("新文本:%s\n", 新文本) // hello
在Go语言中,声明字符串时有几种常见的写法,每种方式适用于不同的场景。你可以根据需要选择合适的方式来表达你的字符串内容:
普通字符串字面量:用双引号 " 括起来,支持常见的转义字符(比如 \n 表示换行,\t 表示制表符等)。适合大多数日常用途。
原始字符串字面量:用反引号 ` 括起来,里面的内容会被原样保留,不会处理转义字符。非常适合写多行文本、正则表达式或包含特殊符号的内容。
多行拼接字符串:通过 + 运算符将多个字符串连接起来,可以让长字符串在代码中分多行书写,提升可读性。
下面我们会通过具体的例子来演示这些写法。
|// 普通字符串 var 普通字符串 string = "Hello, 世界" // 原始字符串(不处理转义字符) var 原始字符串 string = `这是一个原始字符串 可以包含换行符 和反斜杠 \n` // 多行字符串 var 多行字符串 string = "这是第一行" + "这是第二行" + "这是第三行" fmt.Println(普通字符串) fmt.Println(原始字符串) fmt.Println(多行字符串)
在Go语言的字符串中,有些字符本身具有特殊的意义,不能直接写在字符串里。比如换行符、制表符、双引号、反斜杠等。如果你想在字符串中表达这些特殊字符,就需要用“转义字符”来表示。
转义字符通常以反斜杠 \ 开头,后面跟一个特定的字母或符号。例如,\n 表示换行,\t 表示制表符,\\ 表示反斜杠本身,\" 表示双引号。这样可以让你在字符串中灵活地插入各种特殊内容。下面我们会通过例子来详细说明这些用法。
|var 转义字符串 string = "第一行\n第二行\t制表符\\反斜杠\"引号" fmt.Println(转义字符串) // 输出: // 第一行 // 第二行 制表符\反斜杠"引号
Go语言天生支持Unicode编码,这让我们能够轻松地在程序中处理和表示全球各种语言的字符和符号。无论是中文、日文、韩文,还是表情符号、特殊符号,Go都能正确地存储和操作这些内容。 实际上,Go中的字符串底层采用UTF-8编码,这是一种能够兼容ASCII并支持所有Unicode字符的编码方式。因此,你可以在同一个字符串中混合多种语言的文字,而不会出现乱码或丢失信息。 例如,我们可以把英语、中文、日语和表情符号放在一起,Go都能正确识别和处理。
|import "unicode/utf8" var 多语言文本 string = "Hello, 世界, こんにちは, 안녕하세요" fmt.Printf("字符串:%s\n", 多语言文本) fmt.Printf("字节长度:%d\n", len(多语言文本)) fmt.Printf("字符数量:%d\n", utf8.RuneCountInString(多语言文本))
在Go语言中,字符串底层采用UTF-8编码,每个字符(rune)可能由1到4个字节组成。如果我们想要正确地处理和遍历字符串中的每一个Unicode字符(而不是单纯地按字节处理),就需要用到Go提供的专门方法。例如,可以使用for ... range循环来逐个获取字符(rune),而不是逐个字节。此外,标准库中的unicode/utf8包也提供了统计字符数量、解码字符等实用函数。下面我们会通过具体例子来详细演示这些用法。
|import "unicode/utf8" var 文本 string = "Hello, 世界" // 遍历每个字符 for i, r := range 文本 { fmt.Printf("位置%d: 字符%c (Unicode: %d)\n", i, r, r) } // 获取字符数量 字符数量 := utf8.RuneCountInString(文本) fmt.Printf("字符数量:
Go语言为字符串处理提供了大量实用的内置函数和标准库方法,能够帮助我们完成各种常见的字符串操作。比如,你可以轻松地去除字符串两端的空白字符,将字符串全部转换为大写或小写,查找和替换子串,分割字符串为多个部分,判断前缀或后缀,甚至统计某个子串出现的次数。这些功能主要集中在strings标准库中,使用起来非常方便。下面我们通过具体例子来详细介绍这些常用操作的用法。
|import "strings" var 文本 string = " Hello, World! " // 去除首尾空格 清理后 := strings.TrimSpace(文本) fmt.Printf("清理后:'%s'\n", 清理后) // 转换为大写 大写 := strings.ToUpper(文本) fmt.Printf("大写:%s\n", 大写) // 转换为小写
在Go语言中,字符串和字节切片([]byte)之间可以相互转换。字符串本质上是一串只读的字节序列,而字节切片则是可以修改的字节集合。我们可以通过类型转换将字符串转为字节切片,这样就能按字节访问和处理字符串的底层数据;同样,也可以把字节切片直接转换回字符串,生成一个新的字符串值。下面我们会通过具体例子演示如何进行这两种转换,并说明它们在处理UTF-8编码内容时的实际意义。
|var 文本 string = "Hello, 世界" // 字符串转字节切片 字节切片 := []byte(文本) fmt.Printf("字节切片:%v\n", 字节切片) // 字节切片转字符串 新字符串 := string(字节切片) fmt.Printf("新字符串:%s\n", 新字符串) // 处理UTF-8编码 for i := 0; i < len(文本); {
15 / 4 的结果是什么?(假设都是int类型)15 % 4 的结果是什么?0.1 + 0.2 == 0.3 的结果是什么?x != 0 && y/x > 5,如果x=0,由于短路求值,程序会?11. 整数运算练习
编写程序,演示整数运算(除法、取模、位运算)。
|package main import "fmt" func main() { a, b := 15, 4 // 整数除法 fmt.Printf("%d / %d = %d\n", a, b, a/b) // 取模运算 fmt.Printf("%d %% %d =
12. 类型转换练习
修复类型错误,正确进行类型转换。
|package main import "fmt" func main() { var x int32 = 100 var y int16 = 50 // 方法1:将两个操作数转换为相同类型后再运算 var result1 int32 = x + int32(y) fmt.Printf("方法1结果: %d\n", result1) // 方法2:将结果转换为int类型
13. 浮点数运算练习
编写程序计算圆的面积和周长。
|package main import ( "fmt" "math" ) func main() { radius := 5.5 // 计算圆的面积:π * r² area := math.Pi * radius * radius // 计算圆的周长:2 * π * r circumference := 2 * math.Pi * radius
14. 浮点数精度问题
演示浮点数精度问题,并展示正确的比较方法。
|package main import ( "fmt" "math" ) func main() { a := 0.1 + 0.2 b := 0.3 // 直接比较(可能不准确) fmt.Printf("a = %.20f\n", a) fmt.Printf("b = %.20f\n", b)
15. 布尔运算和短路求值
演示布尔运算和短路求值机制。
|package main import "fmt" func main() { // 逻辑运算 fmt.Println("=== 逻辑运算 ===") fmt.Printf("true && false = %t\n", true && false) fmt.Printf("true || false = %t\n", true || false) fmt.Printf(
16. 字符串基本操作
编写程序演示字符串的基本操作。
|package main import ( "fmt" "unicode/utf8" ) func main() { str := "Hello, 世界" // 获取字节长度 byteLen := len(str) fmt.Printf("字节长度: %d\n", byteLen) // 获取字符数量(rune数量) charLen := utf8.RuneCountInString
17. 字符串处理函数
编写程序演示字符串的常用处理操作。
|package main import ( "fmt" "strings" ) func main() { // 1. 将 "hello world" 转换为 "Hello World" str1 := "hello world" words := strings.Fields(str1) for i, word := range words { words[i] = strings.ToUpper(string(word[0]))
18. 字符串构建方法
演示三种不同的字符串构建方法。
|package main import ( "fmt" "strings" ) func main() { name := "张三" age := 25 city := "北京" // 方法1:使用 + 运算符连接 str1 := "姓名: " + name + ", 年龄: " + fmt.Sprintf("%d
输出结果:
|15 / 4 = 3 15 % 4 = 3 1 << 3 = 8 8 >> 2 = 2 12 & 5 = 4 12 | 5 = 13
说明:
%是取模运算符,返回余数<<是左移位运算符,相当于乘以2的n次方>>是右移位运算符,相当于除以2的n次方&是按位与,|是按位或输出结果:
|方法1结果: 150 方法2结果: 150 方法3结果: 150
说明:
T(值),如int32(y)输出结果:
|半径: 5.50 面积: 95.03 周长: 34.56
说明:
math.Pi是Go标准库提供的圆周率常量float64类型%.2f格式化浮点数,保留两位小数输出结果:
|a = 0.30000000000000004441 b = 0.29999999999999998890 a == b: false 使用误差范围比较: true
说明:
==比较浮点数可能不准确math.Abs(a-b) < epsilon输出结果:
|=== 逻辑运算 === true && false = false true || false = true !true = false (true && false) || true = true === 短路求值 === 条件不成立(由于短路求值,不会发生除零错误) === 布尔转换 === intToBool(0) = false intToBool(1) = true intToBool(-1) = true boolToInt(true) = 1 boolToInt(false) = 0
说明:
&&是逻辑与,||是逻辑或,!是逻辑非&&左侧为false时,不计算右侧;||左侧为true时,不计算右侧输出结果:
|字节长度: 13 字符数量: 9 第一个字符: H 最后一个字符: 界 提取'Hello': Hello 提取'世界': 世界
说明:
len()返回字符串的字节长度,不是字符数量utf8.RuneCountInString()返回字符串的字符(rune)数量[start:end]可以提取子字符串输出结果:
|转换结果: Hello World 简单方法: Hello World 'http://example.com' 以 'http://' 开头: true 分割结果: [apple banana orange] 'a' 出现的次数: 5
说明:
strings.HasPrefix()检查字符串是否以指定前缀开头strings.Split()按分隔符分割字符串,返回字符串切片strings.Count()统计子字符串出现的次数strings.Title()将字符串转换为标题格式(首字母大写)输出结果:
|方法1 (+ 运算符): 姓名: 张三, 年龄: 25, 城市: 北京 方法2 (fmt.Sprintf): 姓名: 张三, 年龄: 25, 城市: 北京 方法3 (strings.Join): 姓名: 张三, 年龄: 25, 城市: 北京 方法4 (strings.Builder): 姓名: 张三, 年龄: 25, 城市: 北京
说明:
+运算符简单直接,但效率较低(每次连接都创建新字符串)fmt.Sprintf适合格式化字符串,代码清晰strings.Join适合连接字符串数组或切片strings.Builder适合大量字符串拼接,效率最高