前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >说说 Go 中的变量(附粗制滥造面试题)

说说 Go 中的变量(附粗制滥造面试题)

作者头像
波罗学
发布2019-12-12 11:13:14
5320
发布2019-12-12 11:13:14
举报
文章被收录于专栏:码神路漫漫码神路漫漫

和其他语言没有区别,Go 中的数据也是两种表示方式,常量和变量,本文先说说变量吧。

为了增加文章的趣味性(多掉些头发),搜集了一些常见的面试题。部分是自己瞎编的,顺便为自己明年可能到来的面试做些准备。

先答题,题目中附有提示,但无解答。带着问题看文章效果或许更好。

面试题

1.1 如下的代码,哪些能正常编译?如果不能正常编译,如何修改?

A.

代码语言:javascript
复制
package main

import (
    "fmt"
)

func get() {
    return 1, 2
}

func main() {
    x, y := get()
    fmt.Println(x)
}
复制代码

考点:定义未使用的局部变量

B.

代码语言:javascript
复制
package main

import (
    "fmt"
)

var (
    x = 1
    y := 10
)

func main() {
    fmt.Println(x)
}
复制代码

考点:简短模式只能定义局部变量

C.

代码语言:javascript
复制
package main

import (
    "fmt"
)

var i int, s string = 1, "3"

func main() {
    fmt.Println(i, s)
}
复制代码

考点:var 定义多个变量

1.2 下面这段代码逻辑是否正确?

代码语言:javascript
复制
package main

import (
    "fmt"
)

var p *int

func foo() (*int, error) {
    var i int = 5
    return &i, nil
}

func bar() {
    // 使用 p
    fmt.Println(*p)
}

func main() {
    p, err := foo()
    if err != nil {
        fmt.Println(err)
        return
    }
    bar()
    fmt.Println(*p)
}
复制代码

考点:变量的作用域问题

注:取自 tonybai 老师的博客,原文地址

1.3 下面哪一行变量简短定义存在已定义变量的赋值行为?

代码语言:javascript
复制
package main

import (
    "fmt"
)

func main() {
    x := 1
    fmt.Println(&x)
    if x, y := 3, 4; true {
	    x = x + y
	    fmt.Println(&x)
    }

    x , y := 5, 6
    x = x + y
    fmt.Println(&x)
}
复制代码

考点:局部变量的作用域与简单模式的退化赋值。

--

题目是不是都非常简单呢?如有问题,可以继续看正文。

什么是变量

变量是可以理解为使用一个名称绑定一个用来存放数据的内存块。

变量,首先是量,即为数据,而后是变,即表示内存块中的数据是可变的。与变量相对的是常量,常即恒常,表示数据不可变。常量的值是在编译期就确定了。

变量的定义

Go 中变量的定义有多种方式,先看一个变量完整的定义组成。如下:

代码语言:javascript
复制
    变量名称  变量类型     变量值
var varName typeName [= Value]

var 是 Go 提供的用于定义变量的关键词,变量的定义语句可出现在函数和包级别中。

语句中核心是三个部分,分别是变量的名称、类型和值。与 C/C++ 不同,Go 的变量类型是在变量名称之后。

定义一个变量:

代码语言:javascript
复制
var i int

var 除了定义单个变量,还可以一次定义多个变量。

代码语言:javascript
复制
// 相同类型简写
var i, j int

// 定义不同类型变量
var (
    i int
    s string
)

初始化

变量定义时可以指定初始值。

代码语言:javascript
复制
var i int = 1
var f float64 = 1.1
var s string = "string"

变量值的可选范围由变量类型决定。Go 是静态语言,变量类型是不可修改的。

代码语言:javascript
复制
var i int
var s string

如果变量定义时,没有指定初始值,将自动初始化为相应的零值(不同类型,零值不同),避免类似 C/C++ 中不可预测的行为。

在 windows 上的 "烫烫烫" 的梗,就是和变量未初始化有关。

如果定义时,指定初始值,则可以省略类型,Go 编译器会自动推导变量类型。

代码语言:javascript
复制
var i = 1

// 同时定义初始化两个不同类型变量
var f, s = 1.1, "string"

简短定义

在函数中,变量的定义有一种简短写法,:=。在初始化值类型明确的情况下,代替 var,实现类似动态语言的效果,懒人神器。

代码语言:javascript
复制
i := 1

变量类型由编译器根据初始化值自动推导。

要注意的是,函数外的每个语句都必须以关键字开始(var, func 等),简短模式不能在函数外使用。

简短模式下,如果语句左边有多个变量,其中包含已定义变量,且必须是位于当前的作用域,则已定义变量会转化为赋值行为。

代码语言:javascript
复制
var x = 1
fmt.Println(&x, x)

x, y := 10, 20
fmt.Println(&x, x)

运行代码将会发现,x 的值修改了,但地址并未改变。

多变量赋值

定义变量时,已经演示了如何同时为多个变量赋初始值。动态语言通常支持这种写法,比如 Pyhon。

代码语言:javascript
复制
x, y := 10, 20
x, y = x+10, y+20

这种语法在简化写法的同时,还有一个比较有用的点,变量交换。

通常,交换变量的写法:

代码语言:javascript
复制
t := x
x = y
y = t

引入一个临时变量实现交换。除此之外,还有两种比较常见的交换算法,不引入临时变量。

代码语言:javascript
复制
x = x + y 
y = x - y
x = x - y

或者

代码语言:javascript
复制
a = a^b
b = b^a
a = a^b

有了多变量同时赋值的特性之后,如下的写法即可完成交换。

代码语言:javascript
复制
x, y = y, x

匿名变量

Go 语言中会将定义但未使用的变量当成错误。但是有一种情况,如果 Go 的函数允许返回多个值,就要定义多个变量接收。

假设,有函数定义如下:

代码语言:javascript
复制
func row() (string, int) {
    return "poloxue", 18
}
复制代码

现在 main 函数将打印第一个返回值,第二个返回值不会使用。

代码语言:javascript
复制
func main() {
    name, age := row()
    fmt.Println(name)
}
复制代码

编译无法通过,提示存在未使用的变量。

这时,可以使用 Go 中提供的匿名变量 _ 接收无用的返回值。

代码语言:javascript
复制
name, _ := row()

匿名变量可以多次使用,不占内存空间。

变量作用域

变量作用域和生命周期不同,生命周期表示变量执行期间的存活时间,而作用域表示变量能有效使用的范围。

除了变量有作用范围,还有诸如常量、函数、类型等都是有作用域的。

Go 的作用域可分为全局和局部,变量也就有全局变量和局部变量。但细究起来,全局和局部变量的说法也不对,Go 中内置的常量、函数、类型才能算是全局。变量的全局只能算包级别,包级别变量支持访问控制,变量名首字母大写,才能在全局可用。

局部会覆盖全局,不同的作用范围可以重新定义同名变量覆盖上一级作用域的变量。

代码语言:javascript
复制
package main

// 全局变量
var i = 1

func printI() {
    // 局部变量覆盖全局变量
    i := 10
    fmt.Println(i)
}

func main() {
    fmt.Println(i)
    printI()
}
复制代码

局部变量有几种情况,分别是函数的参数与返回值,函数体内部定义变量,函数内部语法块等。

函数体内部作用域的例子。

代码语言:javascript
复制
func main() {
	s := "局部变量"
	{
		s := "语法块内部变量"
		fmt.Println(s)
	}
	fmt.Println(s)
}
复制代码

变量作用域是一个很坑的话题,Go 中每个语法块,如 funcifforselectswitch 等,都有一个隐式的作用域。基于它,出现了很多坑死人不偿命的面试题。

一个简单的例子。

代码语言:javascript
复制
func get() int {
    return 1
}

func main() {
    if x := get();  x == 0 {
        fmt.Println(x)
    } else {
        fmt.Println(x)
    }
}
复制代码

else 中也可以使用 x 变量,if 之上有个隐式作用域。

作用域这块还有很多坑,比如与 defer 结合就会产生更多叹为观止(惨绝人寰)的现象。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年12月05日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 面试题
  • 什么是变量
  • 变量的定义
  • 初始化
  • 简短定义
  • 多变量赋值
  • 匿名变量
  • 变量作用域
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档