GO学习笔记之表达式

简介: GO学习笔记之表达式

保留字

Go语言仅25个保留关键字(keyword),这是最常见的宣传语,虽不是主流语言中最少的,但也确实体现了Go语法规则的简洁性。保留关键字不能用作常量、变量、函数名,以及结构字段等标识符。

break   default    func   interface select
case    defer      go    map   struct
chan    else   goto   package    switch
const   fallthrough if    range      type
continue for   import     return       var

相比在更新版本中不停添加新语言功能,我更喜欢简单的语言设计。某些功能可通过类库扩展,或其他非侵入方式实现,完全没必要为了“方便”让语言变得臃肿。过于丰富的功能特征会随着时间的推移抬升门槛,还会让代码变得日趋“魔幻”,降低一致性和可维护性。

运算符

很久以前,流传“程序=算法+数据”这样的说法。

算法是什么?通俗点说就是“解决问题的过程”。小到加法指令,大到成千上万台服务器组成的分布式计算集群,抛去抽象概念和宏观架构,最终都由最基础的机器指令过程去处理不同层次存储设备里的数据。

学习语言和设计架构不同,我们所关心的就是微观层次,诸如语法规则所映射的机器指令,以及数据存储位置和格式等等。其中,运算符和表达式用来串联数据和指令,算是最基础的算法。

另有一句话:“硬件的方向是物理,软件的结局是数学。”

全部运算符及分隔符列表:

+      &       +=      &=       &&      ==      !=      (      ) 
-      |       -=      |=       ||      <       <=      [      ] 
*      ^       *=      ^=       <-      >       >=      {      } 
/      <<      /=      <<=      ++      =       :=      ,      ; 
%      >>      %=      >>=      --      !       ...     .      : 
       &^              &^=

没有乘幂和绝对值运算符,对应的是标准库math里的Pow、Abs函数实现。

优先级

一元运算符优先级最高,二元则分成五个级别,从高往低分别是:

highest * / % << >> & &^ + - | ^ == != < <= > >= && lowest ||

相同优先级的二元运算符,从左往右依次计算。

二元运算符

除位移操作外,操作数类型必须相同。如果其中一个是无显式类型声明的常量,那么该常量操作数会自动转型。

func main() { 
   const v=20            // 无显式类型声明的常量 
  
   var a byte=10
   b:=v+a                 //v自动转换为byte/uint8类型 
   fmt.Printf("%T, %v\n",b,b) 
   const c float32=1.2
   d:=c+v         //v自动转换为float32类型 
   fmt.Printf("%T, %v\n",d,d) 
}
uint8,30
float32,21.2
func main() { 
   b:=23            //b是有符号int类型变量 
   x:=1<<b     // 无效操作:1<<b(shift count type int,must be unsigned integer) 
   println(x) 
}

位移右操作数必须是无符号整数,或可以转换的无显式类型常量。

如果是非常量位移表达式,那么会优先将无显式类型的常量左操作数转型。

func main() { 
   a:=1.0<<3           // 常量表达式(包括常量展开) 
   fmt.Printf("%T, %v\n",a,a)       //int,8
  
   var s uint=3
   b:=1.0<<s           // 无效操作:1<<s(shift of type float64) 
   fmt.Printf("%T, %v\n",b,b)       // 因为b没有提供类型,那么编译器通过1.0推断, 
                       // 显然无法对浮点数做位移操作 
   var c int32=1.0<<s       // 自动将1.0转换为int32类型 
   fmt.Printf("%T, %v\n",c,c)       //int32,8
}

位运算符

二进制位运算符比较特别的就是“bit clear”,在其他语言里很少见到。

AND          按位与:都为1     a&b  0101&0011=0001
OR           按位或:至少一个1   a|b  0101|0011=0111
XOR          按位亦或:只有一个1     a^b  0101^0011=0110
NOT          按位取反   (一元)        ^a ^0111=1000
AND NOT      按位清除   (bit clear)  a&^b 0110&^1011=0100
LEFT SHIFT    位左移           a<<2 0001<<3=1000
RIGHT SHIFT       位右移           a>>2 1010>>2=0010

位清除(AND NOT)和位亦或(XOR)是不同的。它将左右操作数对应二进制位都为1的重置为0(有些类似位图),以达到一次清除多个标记位的目的。

const( 
   read  byte=1<<iota
   write
   exec
   freeze
) 
  
func main() { 
   a:=read|write|freeze
   b:=read|freeze|exec
   c:=a&^b     // 相当于a^read^freeze,但不包括exec
  
   fmt.Printf("%04b&^ %04b= %04b\n",a,b,c) 
}

自增

自增、自减不再是运算符。只能作为独立语句,不能用于表达式。

指针

不能将内存地址与指针混为一谈。

内存地址是内存中每个字节单元的唯一编号,而指针则是一个实体。指针会分配内存空间,相当于一个专门用来保存地址的整型变量。

p:= &x                x:=100
    -----------------+--------+------\\-------+------+--------- 
    memory ...     |0x1200|     ....      |100 |   ... 
    -----------------+--------+------\\-------+------+--------- 
    address       0x800                  0x1200

取址运算符“&”用于获取对象地址。

指针运算符“”用于间接引用目标对象。
二级指针**T,如包含包名则写成
package.T。

并非所有对象都能进行取地址操作,但变量总是能正确返回(addressable)。指针运算符为左值时,我们可更新目标对象状态;而为右值时则是为了获取目标状态。

func main() { 
   x:=10
  
   var p*int= &x     // 获取地址,保存到指针变量 
    *p+=20           // 用指针间接引用,并更新对象 
  
   println(p, *p)       // 输出指针所存储的地址,以及目标对象 
}
输出
0xc82003df30 30

指针类型支持相等运算符,但不能做加减法运算和类型转换。如果两个指针指向同一地址,或都为nil,那么它们相等。

func main() { 
   x:=10
   p:= &x
  
   p++                    // 无效操作:p++ (non-numeric type*int) 
   var p2*int=p+1  // 无效操作:p+1(mismatched types*int and int) 
  
   p2= &x
   println(p==p2) 
}

可通过unsafe.Pointer将指针转换为uintptr后进行加减法运算,但可能会造成非法访问。

Pointer类似C语言中的void*万能指针,可用来转换指针类型。它能安全持有对象或对象成员,但uintptr不行。后者仅是一种特殊整型,并不引用目标对象,无法阻止垃圾回收器回收对象内存。

指针没有专门指向成员的“->”运算符,统一使用“.”选择表达式。

func main() { 
   a:=struct{ 
       x int
    }{} 
  
   a.x=100
  
   p:= &a
   p.x+=100      // 相当于p->x+=100
  
   println(p.x) 
}

零长度(zero-size)对象的地址是否相等和具体的实现版本有关,不过肯定不等于nil。即便长度为0,可该对象依然是“合法存在”的,拥有合法内存地址,这与nil语义完全不同。

在runtime/malloc.go里有个zerobase全局变量,所有通过mallocgc分配的零长度对象都使用该地址。不过上例中,对象a、b在栈上分配,并未调用mallocgc函数。

func main() { 
   var a,b struct{} 
  
   println(&a, &b) 
   println(&a== &b, &a==nil) 
}

0xc820041f2f 0xc820041f2f

true false

目录
相关文章
|
6月前
|
监控 安全 Java
Go语言学习笔记(一)
Go语言学习笔记(一)
124 1
|
自然语言处理 测试技术 编译器
Go语言实现表达式求值器的秘密都在这里!
Go语言实现表达式求值器的秘密都在这里!
96 0
|
19天前
|
Go
go语言编译时常量表达式
【10月更文挑战第20天】
27 3
|
20天前
|
Go
go语言使用常量和编译时常量表达式
【10月更文挑战第19天】
24 2
Go语言的条件控制语句及循环语句的学习笔记
本文是Go语言的条件控制语句和循环语句的学习笔记,涵盖了if语句、if-else语句、if嵌套语句、switch语句、select语句以及for循环和相关循环控制语句的使用方法。
Go语言的条件控制语句及循环语句的学习笔记
|
2月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
2月前
|
安全 Go C语言
Go常量的定义和使用const,const特性“隐式重复前一个表达式”,以及iota枚举常量的使用
这篇文章介绍了Go语言中使用`const`定义常量的方法,包括常量的特性“隐式重复前一个表达式”,以及如何使用`iota`实现枚举常量的功能。
|
2月前
|
人工智能 算法 搜索推荐
Go学习笔记-代码调
近年来,人工智能技术飞速发展,Cody作为由Sourcegraph开发的一款AI驱动编码助手,应运而生。它不仅提供代码预测与补全,还能深度理解代码上下文,为开发者提供准确建议,提升编码效率和质量。Cody能识别潜在错误并提出修复建议,缩短调试时间,同时进行智能代码审查,帮助优化代码结构和风格。未来,随着AI技术进步,Cody将不断学习优化,成为开发者不可或缺的伙伴,推动编程领域的创新与发展。
33 0
|
3月前
|
存储 缓存 Java
涨姿势啦!Go语言中正则表达式初始化的最佳实践
在Go语言中,正则表达式是处理字符串的强大工具,但其编译过程可能消耗较多性能。本文探讨了正则表达式编译的性能影响因素,包括解析、状态机构建及优化等步骤,并通过示例展示了编译的时间成本。为了优化性能,推荐使用预编译策略,如在包级别初始化正则表达式对象或通过`init`函数进行错误处理。此外,简化正则表达式和分段处理也是有效手段。根据初始化的复杂程度和错误处理需求,开发者可以选择最适合的方法,以提升程序效率与可维护性。
52 0
涨姿势啦!Go语言中正则表达式初始化的最佳实践
|
6月前
|
Java Go 区块链
【Go语言专栏】Go语言中的运算符和表达式
【4月更文挑战第30天】Go语言是Google开发的编程语言,因其简单易学、高性能和出色的并发处理能力而备受关注,常用于云计算、微服务等领域。本文介绍了Go语言中的运算符和表达式,包括算术运算符(如加、减、乘、除、取模)、关系运算符(如等于、不等于、大于、小于等)、逻辑运算符(如逻辑与、或、非)以及位运算符(如按位与、或、异或、移位)。通过示例代码展示了这些运算符的使用方法。
59 1