结构体中,为什么不能嵌入非基础类型
问题:
为什么data可以这样声明,data1不能
type data struct {
*int
string
//string
}
type a *int
type data1 struct {
a
}
你的问题涉及到 Go 中匿名字段(embedded fields)和类型定义的规则。我来详细解释为什么 data 可以这样声明,而 data1 不行。
代码分析
1. data 的声明
type data struct {
*int // 匿名字段,类型是 *int,字段名是 int
string // 匿名字段,类型是 string,字段名是 string
}
- 这里
data是一个结构体类型,包含两个匿名字段:*int:一个指向int的指针类型,字段名是int。string:一个字符串类型,字段名是string。
- 在 Go 中,结构体允许直接使用基本类型(如
int、string、*int等)或命名类型(如time.Time)作为匿名字段。这是 Go 的语法规则,匿名字段的类型可以是任何有效类型(包括指针类型),字段名会自动取类型的简单名称。 - 因此,
data的声明是合法的。
2. data1 的声明
type a *int
type data1 struct {
a // 匿名字段,类型是 a,字段名是 a
}
- 这里你定义了一个新类型
a,它是*int的别名(type a *int)。 - 然后在
data1中将a作为匿名字段。 - 表面上看,这似乎和
data类似,但问题在于 Go 的语法和语义规则对匿名字段的初始化和使用有一些限制。
为什么 data1 不能像 data 那样声明并使用?
问题并不在于 data1 的声明本身,而是后续的使用方式可能会导致困惑。让我们逐步分析:
1. 声明本身是合法的
type data1 struct { a }是合法的语法。- 在
data1中,a是一个匿名字段:- 类型是
a(也就是*int的别名)。 - 字段名是
a(取自类型名a)。
- 类型是
- 从语法上看,
data1的定义没有问题,和data类似。
2. 使用时的差异
你的问题可能是隐含在“这样声明”之后的使用方式上。假设你尝试像 data 那样初始化和操作 data1,会出现一些不同:
示例:data 的使用
x := 1
d := data{
string: "hello",
}
d.int = &x // 直接通过字段名 int 赋值
fmt.Println(*d.int, d.string) // 输出:1 hello
d.int是*int类型的字段,可以直接赋值为&x。- 匿名字段
*int的字段名是int,访问和赋值都很直观。
示例:data1 的使用
x := 1
d1 := data1{} // 默认初始化,a 字段为 nil
d1.a = &x // 通过字段名 a 赋值
fmt.Println(*d1.a) // 输出:1
d1.a是a类型的字段(a是*int的别名),可以赋值为&x。- 这看起来是可行的,但问题在于:如果你期望
data1的字段名像data那样是int(而不是a),那是不可能的。
3. 字段名的差异
- 在
data中,*int的字段名是int,因为*int是一个基本类型,Go 使用它的简单名称int作为字段名。 - 在
data1中,a是一个用户定义的类型(*int的别名),它的字段名是a,而不是int。 - 如果你尝试用
d1.int = &x来赋值,会报错:
因为d1.int undefined (type data1 has no field or method int)data1中没有字段名为int的成员,只有字段名a。
根本原因:匿名字段的字段名规则
Go 中匿名字段的字段名取决于类型的名称:
- 对于基本类型(如
*int、string),字段名是类型的简单名称(int、string)。 - 对于用户定义的类型(如
type a *int),字段名是用户定义的类型名(a)。
对比
| 结构体 | 字段类型 | 字段名 | 可访问方式 |
|---|---|---|---|
data |
*int |
int |
d.int |
data |
string |
string |
d.string |
data1 |
a |
a |
d1.a |
data的字段名直接来源于基本类型(*int->int)。data1的字段名来源于用户定义的类型名(a->a),而不是a底层类型*int的名称。
假设你的困惑来源
你可能觉得 data1 应该也能像 data 那样用 d1.int 访问字段,但实际上:
data的*int是直接内嵌的基本类型,字段名是int。data1的a是一个用户定义类型,字段名是a,不会“继承”底层类型*int的名称。
如果改为:
type data1 struct {
*int // 直接用 *int,而不是 a
}
那么 data1 就可以像 data 那样用 d1.int 访问字段,因为字段名会是 int。
为什么 data1 的声明“不能这样”?
从你的问题来看,可能指的是以下情况:
- 字段名不一致的期望:
- 你可能希望
data1的字段名是int(像data那样),但由于a是用户定义类型,字段名变成了a。
- 你可能希望
- 语义预期不同:
- 如果你想让
data1的行为完全模仿data,直接用*int作为匿名字段就够了,而不需要定义type a *int。
- 如果你想让
合法但不一致的例子
type a *int
type data1 struct {
a
}
x := 1
d1 := data1{a: &x} // 合法,但字段名是 a,不是 int
fmt.Println(*d1.a) // 输出:1
- 这种声明是合法的,但字段名是
a,不像data那样是int。
总结
data可以这样声明:因为它直接使用基本类型*int和string作为匿名字段,字段名分别是int和string,符合 Go 的规则。data1不能“这样”声明的真正含义:如果你期望data1的字段名是int而不是a,那是因为a是一个用户定义类型,字段名取自类型名a,而不是底层类型*int的名称。- ** 除接口指针和多级指针以外的任何命名类型都可作为匿名字段
如果你的本意是让 data1 的字段名也是 int,你应该直接用 *int 而不是 a:
type data1 struct {
*int // 字段名是 int
}
这样 data1 和 data 的行为就一致了。
希望这个解释解决了你的疑问!如果还有其他问题,欢迎继续讨论。

浙公网安备 33010602011771号