在 Go 和 Java 中,nil
和 null
都用于表示“空值”,但它们的实现和使用方式有所不同。
以下是 Go 中的 nil
和 Java 中的 null
之间的对比:
nil
在 Go 中,nil
是一个预定义的常量,表示零值。它的行为根据数据类型的不同而有所不同:
nil
表示“没有指向任何地方”。nil
不适用,Go 会使用对应类型的零值(例如 0
、false
、""
)。package main
import "fmt"
func main() {
var ptr *int
var arr []int
var ch chan int
var m map[string]int
fmt.Println(ptr == nil) // true
fmt.Println(arr == nil) // true
fmt.Println(ch == nil) // true
fmt.Println(m == nil) // true
}
null
在 Java 中,null
是一个常量,表示没有对象的引用。它可以赋给任何对象类型的变量,但不能赋给基本数据类型(如 int
、char
等)。对于基本数据类型,Java 会使用默认值(例如 0
、false
、""
)。
public class Main {
public static void main(String[] args) {
Integer num = null;
String str = null;
System.out.println(num == null); // true
System.out.println(str == null); // true
}
}
特性 | Go 中的 nil |
Java 中的 null |
---|---|---|
适用类型 | 指针、切片、映射、通道、接口、函数等 | 任何对象类型(包括类、接口、数组等) |
基本数据类型 | 对于基本数据类型有零值(如 0 、false ) |
对于基本数据类型不适用,使用默认值(如 0 ) |
空值判断 | 可以直接使用 == nil 判断 |
可以直接使用 == null 判断 |
nil
和 null
都表示“没有值”,但 Go 中的 nil
更为广泛,适用于多种类型,包括接口、切片等,而 Java 的 null
只能用于对象类型。nil
,而 Java 的基本类型有默认值,不能为 null
。在 Go 中,基本数据类型(如 int
、float64
、bool
等)不能使用 nil
进行判断。nil
仅适用于指针、切片、映射、通道、接口、函数等引用类型。对于基本数据类型,Go 会使用相应的零值,而不能将其赋值为 nil
。
在 Go 中,基本数据类型有对应的零值,以下是一些常见的零值:
int
:0
float64
:0.0
bool
:false
string
:""
(空字符串)这些零值与 nil
不同,不能通过 nil
来判断。
nil
判断package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Println(i == nil) // 编译错误
fmt.Println(f == nil) // 编译错误
fmt.Println(b == nil) // 编译错误
fmt.Println(s == nil) // 编译错误
}
对于基本数据类型,你可以通过比较它们是否等于零值来进行判断,例如:
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Println(i == 0) // true
fmt.Println(f == 0.0) // true
fmt.Println(b == false) // true
fmt.Println(s == "") // true
}
nil
判断,它们有自己的零值。i == 0
、s == ""
)。nil
只能用于引用类型(指针、切片、映射等)。在 Go 语言中,结构体方法和普通方法(即函数)是两个常见的概念。虽然它们都定义了代码的行为,但它们的实现和使用方式有所不同。下面是它们的区别:
结构体方法是与结构体类型(struct)关联的函数。它们可以访问和修改结构体的字段。通过给结构体定义方法,可以使结构体类型具备一定的行为。
package main
import "fmt"
// 定义一个结构体
type Person struct {
Name string
Age int
}
// 结构体方法:修改结构体的字段
func (p *Person) SetAge(age int) {
p.Age = age
}
// 结构体方法:打印结构体内容
func (p Person) Greet() {
fmt.Println("Hello, my name is", p.Name, "and I am", p.Age, "years old.")
}
func main() {
person := Person{
Name: "John", Age: 25}
person.Greet() // 输出: Hello, my name is John and I am 25 years old.
person.SetAge(30) // 修改结构体的字段
person.Greet() // 输出: Hello, my name is John and I am 30 years old.
}
普通方法(即函数)是独立于任何类型之外的。它不依赖于结构体或其他类型,但它可以作为参数或返回值与其他类型交互。
package main
import "fmt"
// 普通函数:两个数字相加
func Add(a, b int) int {
return a + b
}
func main() {
result := Add(5, 3) // 调用普通函数
fmt.Println("Result of Add:", result) // 输出: Result of Add: 8
}
特性 | 结构体方法 | 普通方法(函数) |
---|---|---|
与类型的关系 | 与特定类型(如结构体)关联 | 独立于任何类型,通常是全局函数 |
接收者 | 有一个接收者,通常是结构体类型或结构体指针(通过接收者访问字段) | 没有接收者 |
访问结构体字段 | 可以访问结构体字段,通过接收者修改字段值 | 不能访问结构体的字段 |
用法 | 用于为结构体添加行为或功能 | 用于实现通用的功能,适用于任何类型 |
// 如果你实现了 *Student 类型的 String 方法,就会自动调用
这段注释出现在 Go 语言的代码中,解释了当使用 fmt.Println(&stu)
这种方式打印结构体时,Go 是如何处理打印逻辑的。我们来详细拆解一下:
fmt.Println()
是如何工作的?在 Go 语言中,fmt
包在打印一个值时会尝试判断该值是否实现了某些接口,最重要的是:
type Stringer interface {
String() string
}
如果某个类型实现了这个接口,fmt
就会自动调用 String()
方法来获取要打印的字符串。
package main
import "fmt"
type Student struct {
Name string
Age int
}
// 给 *Student 类型实现 String 方法
func (s *Student) String() string {
return fmt.Sprintf("Student(Name: %s, Age: %d)", s.Name, s.Age)
}
func main() {
stu := Student{
Name: "Tom", Age: 20}
fmt.Println(&stu) // 自动调用 (*Student).String() 方法
}
// 如果你实现了 *Student 类型的 String 方法,就会自动调用
fmt.Println(&stu)
*Student
表示 Student 类型的指针。
如果你为 *Student
实现了 String() string
方法:
func (s *Student) String() string {
... }
当你使用 fmt.Println(&stu)
时,fmt
会检测 *Student
是否实现了 Stringer
接口。
如果实现了,就会自动调用这个方法,而不是打印默认格式的结构体内容。
如果你实现的是 Student
(非指针)类型的 String()
方法:
func (s Student) String() string {
// ...
}
那么只有在传值时(fmt.Println(stu)
)才会自动调用 String()
方法,fmt.Println(&stu)
不会自动调用。
fmt.Println(&stu)
中的 &stu
是 *Student
类型。*Student
实现了 String()
方法,fmt.Println(&stu)
会自动调用这个方法。fmt
包内部会优先调用类型的 String()
方法来打印内容。(*array)[i][j], (*array)[j][i] = (*array)[j][i], (*array)[i][j]
这是 Go 语言中的 多重赋值(multiple assignment)语法,它允许你在一行中交换两个值,不需要临时变量。
temp := (*array)[i][j]
(*array)[i][j] = (*array)[j][i]
(*array)[j][i] = temp
temp
临时变量a, b := 3, 5
a, b = b, a
fmt.Println(a, b) // 输出:5 3
二维数组转置时,需要把元素 [i][j]
和 [j][i]
的位置互换,原本需要三行代码,现在一行就能搞定。
语法 | 说明 |
---|---|
a, b = b, a |
同时交换两个变量的值 |
x, y, z = 1, 2, 3 |
支持多个变量同时赋值 |
用途 | 交换变量、并行赋值、多返回值接收 |
虽然它们看起来很像,但本质上有区别。下面是详细的 Markdown 格式说明:
Go 中最常见的写法就是函数。
func Add(a int, b int) int {
return a + b
}
Add(1, 2)
方法是绑定到某个**类型(通常是结构体)**的函数。
type Person struct {
Name string
}
// 方法:带有接收者
func (p Person) SayHello() {
fmt.Println("Hello, my name is", p.Name)
}
p Person
,表示这个方法是 Person
类型“专属”的p.SayHello()
Person
)或指针类型(如 *Person
)作为接收者项目 | 函数 Function | 方法 Method |
---|---|---|
是否有接收者 | ❌ 无接收者 | ✅ 有接收者((x Type) ) |
调用方式 | 函数名(参数) |
对象.方法名() |
所属 | 属于包(package) | 属于某个类型(type) |
用途 | 通用计算、工具类逻辑 | 操作特定结构体的数据和行为 |
// 普通函数
func SayHi(name string) {
fmt.Println("Hi,", name)
}
// 方法
type Cat struct {
Name string
}
func (c Cat) Meow() {
fmt.Println(c.Name, "says Meow~")
}
/*
方法与函数的区别如下:
1. 方法(Method)是与某个类型(通常是结构体)绑定的函数。
它有一个接收者(receiver),定义格式为:
func (接收者名 接收者类型) 方法名(参数...) 返回值 {...}
可以通过接收者调用方法,如:instance.MethodName()
2. 函数(Function)是独立的,**没有接收者**,调用方式为:
FunctionName(参数...),不能通过接收者的方式调用。
3. 方法的接收者可以是值类型或指针类型:
- 值接收者:会复制调用者,不影响原值。
- 指针接收者:传递地址,可修改原值。
4. 方法调用时,Go 支持自动取地址或解引用(语法糖)。
如:值对象也可以调用指针接收者方法,反之亦然。
5. 函数的参数必须严格匹配调用时传入的参数类型,
否则会编译报错,不存在自动取地址或解引用。
总结:
- 方法 = 函数 + 接收者。
- 方法更适合面向对象封装,函数更适合工具类的逻辑。
*/
在 Go 语言中,函数调用和方法调用在底层传参机制上,其实是一样的——都是值传递,区别在于你传的是值还是地址。
方式 | 本质 | 修改是否影响原始变量 | 常用于 |
---|---|---|---|
值拷贝 | 传入的是数据的副本 | ❌ 不影响原始变量 | 小结构体、只读操作 |
指针传递 | 传入的是内存地址 | ✅ 可以影响原始变量 | 修改操作、大结构体 |
type Person struct {
Name string
}
// 函数:值传递
func ChangeNameByValue(p Person) {
p.Name = "张三"
}
// 函数:指针传递
func ChangeNameByPointer(p *Person) {
p.Name = "李四"
}
func main() {
person := Person{
Name: "原名"}
ChangeNameByValue(person)
fmt.Println("值传递结果:", person.Name) // 原名
ChangeNameByPointer(&person)
fmt.Println("指针传递结果:", person.Name) // 李四
}
在编译期间,Go 会把方法转换成函数调用形式,比如:
func (p Person) Hello() {
}
等价于:
func Person_Hello(p Person) {
}
如果是指针接收者:
func (p *Person) Hello() {
}
等价于:
func Person_Hello(p *Person) {
}
type Data struct {
Val int
}
func (d Data) ByValue() {
d.Val = 100
fmt.Println("ByValue 中的值:", d.Val)
}
func (d *Data) ByPointer() {
d.Val = 200
fmt.Println("ByPointer 中的值:", d.Val)
}
func main() {
d := Data{
Val: 10}
d.ByValue()
fmt.Println("main中值:", d.Val) // 10
d.ByPointer()
fmt.Println("main中值:", d.Val) // 200
}
Go 会帮你做这些自动转换:
d.ByPointer()
← 自动加 &
,等价于 (&d).ByPointer()
(&d).ByValue()
← 自动解引用,等价于 d.ByValue()
只要方法接收者允许,这些转换会自动进行。
对比点 | 值接收者 / 值传递 | 指针接收者 / 指针传递 |
---|---|---|
是否拷贝结构体 | ✅ 会 | ❌ 不会,传地址 |
是否修改原对象 | ❌ 否 | ✅ 可以 |
性能开销 | 拷贝大对象开销高 | 传指针更高效 |
自动转换支持 | ✅ 自动加/解引用支持 | ✅ 自动加/解引用支持 |
string
的常用方法(Markdown 格式)在 Go 中,将基本数据类型转换为 string
是开发中的常见需求,以下是常用类型转换方式的总结:
int
转 string
strconv.Itoa
import "strconv"
i := 123
str := strconv.Itoa(i)
fmt.Println(str) // "123"
fmt.Sprintf
str := fmt.Sprintf("%d", i)
float64 / float32
转 string
strconv.FormatFloat
f := 3.14159
str := strconv.