Golang常量无法取地址

Golang常量无法取地址

今天在尝试取常量的地址时出现了报错,会有这样一些场景,嵌套型的struct有些字段是*string或者*int

package main

type Obj struct {
    Prop1  *string
    Prop2  *int
}

func main() {
	obj := Obj{
	    Prop1:  &"a string property",
	    Prop2:  &5,
	}
}
//报错:
Cannot take the address of '"a string property"'
Cannot take the address of '5'

Golang官方的解释是这样的:

For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal. If the evaluation of x would cause a run-time panic, then the evaluation of &x does too.
对于类型为 t 的操作数 x,地址操作 & x 生成类型为 * t 到 x 的指针。操作数必须是可寻址的,即变量、指针间接或片索引操作; 或可寻址结构操作数的字段选择器; 或可寻址数组的数组索引操作。作为可寻址性要求的一个例外,x 也可以是一个(可能是括号中的)复合文本。如果对 x 的求值会引起运行时恐慌,那么对 & x 的求值也会引起恐慌。

不允许使用常量的地址。 可能有以下几个原因:

  1. 常量可能根本没有地址。
  2. 如果可以使用常量值的地址,则可以将**地址(指针)**分配给变量,然后可以通过指针更改该值。
  3. 常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量是无法取地址的【《Go语言学习笔记摘录》】

解决方法

1.声明一个变量,内容和该常量相等,然后对该变量取地址

package main

type Obj struct {
    Prop1  *string
    Prop2  *int
}

func main() {
	var p1 string= "a string property"
	var p2 int = 5
	obj := Obj{
	    Prop1:  &p1,
	    Prop2:  &p2,
	}
}

2.定义一个接受常量并返回其地址的函数

s := func(s string) *string { return &s }
i := func(s int) *int { return &i }

obj := Obj{
    Prop1:  s("a string property"),
    Prop2:  i(5),
    Status: s(statuses.Awesome)
}

之所以可行,是因为在将常量作为参数传递给函数时,会复制该常量成为形参变量,这意味着在函数中创建的指针不是指向常量的地址,而是指向变量【形参】的地址。

Go语言常量特点

参考
Go 语言的常量是一种在源码编译期间被创建的语法元素。这是在说这个元素的值可以像变量那样被初始化,但它的初始化表达式必须是在编译期间可以求出值来的。而且,Go 常量一旦声明并被初始化后,它的值在整个程序的生命周期内便保持不变。这样,我们在并发设计时就不用考虑常量访问的同步,并且被创建并初始化后的常量还可以作为其他常量的初始表达式的一部分。但Go 语言规范规定,Go 常量的类型只局限于 Go 基本数据类型,包括数值类型、字符串类型,以及只有两个取值(true 和 false)的布尔类型。而在C语言中const 关键字修饰的标识符本质上依旧是变量,它甚至无法用作数组变量声明中的初始长度(除非用 GNU 扩展 C)。比如:

const int size = 5;
int a[size] = {1,2,3,4,5}; // size本质不是常量,这将导致编译器错误

1. 无类型常量
Go 语言对类型安全是有严格要求的:即便两个类型拥有着相同的底层类型,但它们仍然是不同的数据类型,不可以被相互比较或混在一个表达式中进行运算。这一要求不仅仅适用于变量,也同样适用于有类型常量(Typed Constant)中

type myInt int
const n myInt = 13
const m int = n + 5 // 编译器报错:cannot use n + 5 (type myInt) as type int in const initializer

func main() {
    var a int = 5
    fmt.Println(a + n) // 编译器报错:invalid operation: a + n (mismatched types int and myInt)
}
//显示转型:
type myInt int
const n myInt = 13
const m int = int(n) + 5  // OK

func main() {
    var a int = 5
    fmt.Println(a + int(n))  // 输出:18
}

上述例子的问题也可以用无类型常量来解决,无类型常量也不是说就真的没有类型,它也有自己的默认类型,不过它的默认类型是根据它的初值形式来决定的。像下面代码中的常量 n 的初值为整数形式,所以它的默认类型为 int,但常量 n 的默认类型 int 与 myInt 并不是同一个类型啊,为什么可以放在一个表达式中计算而没有报编译错误呢?【隐式转型】

type myInt int
const n = 13 //无类型常量

func main() {
    var a myInt = 5
    fmt.Println(a + n)  // 输出:18
}

2. 隐式转型
隐式转型说的就是,对于无类型常量参与的表达式求值,Go 编译器会根据上下文中的类型信息,把无类型常量自动转换为相应的类型后,再参与求值计算,这一转型动作是隐式进行的。但由于转型的对象是一个常量,所以这并不会引发类型安全问题,Go 编译器会保证这一转型的安全性。不过,如果 Go 编译器在做隐式转型时,发现无法将常量转换为目标类型,Go 编译器也会报错:

const m = 1333333333

var k int8 = 1
j := k + m // 编译器报错:constant 1333333333 overflows int8

3. 实现枚举
Go 语言其实并没有原生提供枚举类型。
Go 的 const 语法提供了“隐式重复前一个非空表达式”的机制,比如下面代码:

const ( 
	Apple, Banana = 11, 22 
	Strawberry, Grape 
	Pear, Watermelon 
)

这个代码里,常量定义的后两行并没有被显式地赋予初始值,所以 Go 编译器就为它们自动使用上一行的表达式,也就获得了下面这个等价的代码:


const (
    Apple, Banana = 11, 22
    Strawberry, Grape  = 11, 22 // 使用上一行的初始化表达式
    Pear, Watermelon  = 11, 22 // 使用上一行的初始化表达式
)

Go 在这个特性的基础上又提供了“神器”:iota,iota 是 Go 语言的一个预定义标识符,它表示的是 const 声明块(包括单行声明)中,每个常量所处位置在块中的偏移值(从零开始)。同时,每一行中的 iota 自身也是一个无类型常量,可以像前面我们提到的无类型常量那样,自动参与到不同类型的求值过程中来,不需要我们再对它进行显式转型操作。
Go 标准库中 sync/mutex.go 中的一段基于 iota 的枚举常量的定义:


// $GOROOT/src/sync/mutex.go 
const ( 
    mutexLocked = 1 << iota //1 << 0,也就是 1
    mutexWoken //隐式重复前一个非空表达式:mutexWorken = 1 << iota = 1<<1 = 2
    mutexStarving //隐式重复前一个非空表达式:mutexWorken = 1 << iota = 1<<2 = 4
    mutexWaiterShift = iota //3
    starvationThresholdNs = 1e6 //1e6本身
)

如果一个 Go 源文件中有多个 const 代码块定义的不同枚举,每个 const 代码块中的 iota 也是独立变化的,也就是说,每个 const 代码块都拥有属于自己的 iota

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值