Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24129 人已学习
课程目录
已完结 54 讲
0/4登录后,你可以任选4讲全文学习。
开篇词+学习路线 (3讲)
开篇词 | 跟着学,你也能成为Go语言高手
免费
预习篇 | 写给0基础入门的Go语言学习者
50 | 学习专栏的正确姿势
模块一:Go语言基础知识 (6讲)
01 | 工作区和GOPATH
02 | 命令源码文件
03 | 库源码文件
04 | 程序实体的那些事儿(上)
05 | 程序实体的那些事儿(中)
06 | 程序实体的那些事儿 (下)
模块二:Go语言进阶技术 (16讲)
07 | 数组和切片
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)
23 | 测试的基本规则和流程 (上)
24 | 测试的基本规则和流程(下)
25 | 更多的测试手法
26 | sync.Mutex与sync.RWMutex
27 | 条件变量sync.Cond (上)
28 | 条件变量sync.Cond (下)
29 | 原子操作(上)
30 | 原子操作(下)
31 | sync.WaitGroup和sync.Once
32 | context.Context类型
33 | 临时对象池sync.Pool
34 | 并发安全字典sync.Map (上)
35 | 并发安全字典sync.Map (下)
36 | unicode与字符编码
37 | strings包与字符串操作
38 | bytes包与字节串操作(上)
39 | bytes包与字节串操作(下)
40 | io包中的接口和工具 (上)
41 | io包中的接口和工具 (下)
42 | bufio包中的数据类型 (上)
43 | bufio包中的数据类型(下)
44 | 使用os包中的API (上)
45 | 使用os包中的API (下)
46 | 访问网络服务
47 | 基于HTTP协议的网络服务
48 | 程序性能分析基础(上)
49 | 程序性能分析基础(下)
尾声与思考题答案 (2讲)
尾声 | 愿你披荆斩棘,所向无敌
新年彩蛋 | 完整版思考题答案
Go语言核心36讲
登录|注册

04 | 程序实体的那些事儿(上)

郝林 2018-08-17
我已经为你打开了 Go 语言编程之门,并向你展示了“程序从初建到拆分,再到模块化”的基本演化路径。
一个编程老手让程序完成基本演化,可能也就需要几十分钟甚至十几分钟,因为他们一开始就会把车开到模块化编程的道路上。我相信,等你真正理解了这个过程之后,也会驾轻就熟的。
上述套路是通用的,不是只适用于 Go 语言。但从本篇开始,我会开始向你介绍 Go 语言中的各种特性以及相应的编程方法和思想。
我在讲解那两种源码文件基本编写方法的时候,声明和使用了一些程序实体。你也许已经若有所觉,也许还在云里雾里。没关系,我现在就与你一起梳理这方面的重点。
还记得吗?Go 语言中的程序实体包括变量、常量、函数、结构体和接口。 Go 语言是静态类型的编程语言,所以我们在声明变量或常量的时候,都需要指定它们的类型,或者给予足够的信息,这样才可以让 Go 语言能够推导出它们的类型。
在 Go 语言中,变量的类型可以是其预定义的那些类型,也可以是程序自定义的函数、结构体或接口。常量的合法类型不多,只能是那些 Go 语言预定义的基本类型。它的声明方式也更简单一些。
好了,下面这个简单的问题你需要了解一下。

问题:声明变量有几种方式?

先看段代码。
package main
import (
"flag"
"fmt"
)
func main() {
var name string // [1]
flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2]
flag.Parse()
fmt.Printf("Hello, %v!\n", name)
}
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(32)

  • Shawn 置顶
    当前变量覆盖外层变量
    2018-08-17
    31
  • 陈悬高
    所谓“变量的重声明”容易引发歧义,而且也不容易理解。如果没有为变量分配一块新的内存区域,那么用声明是不恰当的。在《Go 语言圣经》一书中将短声明的这种特性称为赋值。个人总结如下:

    在使用短变量声明的时候,你可能想要同时对一个已有的变量赋值,类似使用 `=` 进行多重赋值那样(如 `i, j = 2, 3`)。所以,Go 为短声明语法提供了一个语法糖(或者叫便利措施):短变量声明不需要声明所有在左边的变量。如果多个变量在同一个词法块中声明,那么对于这些变量,短声明的行为等同于*赋值*。

    比如,在下面的代码中,第一条语句声明了 `in` 和 `err`。第二条语句仅声明了 `out`,但向已有的 `err` 变量进行赋值。

    ```
    in, err := os.Open(infile)
    // ...
    out, err := os.Create(outfile)
    ```

    但是这种行为需要一些前提条件:

    * 要赋值的变量必须声明在同一个词法块中。

    如果两个变量位于不同的词法块中,短声明语法表示的仍然是“声明”而非“赋值”。此时它们就是重名的变量了,而且内层变量会“覆盖”外部变量。

    * 必须至少声明一个新变量,否则代码将不能编译通过。

    原因很简单,如果不用声明新变量而仅仅是为了赋值,那么直接使用赋值符 `=` 即可:

    ```
    f, err := os.Open(infile)
    // ...
    // f, err := os.Create(outfile) // 编译错误:没有新变量
    f, err = os.Create(outfile) // 使用普通的赋值语句即可
    ```
    2018-10-06
    84
  • Andy Chen
    “你可以随意改变getTheFlag函数的内部实现及其返回结果的类型,而不用修改main函数中的任何代码。”这个说法只在你给定的例子下面成立,事实上main函数的代码已经假设getTheFlag会返回字符串,因为它在用返回值,如果getTheFlag一开始是返回某种结构体指针,main使用了这个指针指向的一系列成员,然后你再改getTheFlag返回类型看看。类型推断已经深入大多数语言,包括c++,C#,等等,但它没办法解决所谓的使用者不需要改变任何代码就能进行重构

    作者回复: 重构有很多种,有大有小啊。

    2018-08-17
    11
  • 玉明
    应该是没影响的,不同栈上的变量
    2018-08-17
    9
  • xiaolonghuster
    按照老师的demo,不能获取命令参数,只能得到默认值,改成下面这样可以:我用的是1.10.3版本,是不是版本问题

    func main() {

    var name = getTheFlag()

    flag.Parse()
    fmt.Printf("Hello, %v!\n", *name)
    }

    func getTheFlag() *string {

    return flag.String("name", "everybody", "The greeting object.")
    }

    作者回复: 这里确实是写错了,你改的很对,谢谢指正!我想邀请你进入本专栏的微信讨论群。你知道入群方法吗?

    2018-08-17
    2
    7
  • javaadu
    go支持类型推断;
    两种变量定义方式:var完整方式、:=短变量定义;
    重声明只可以在短变量定义中出现,并且是在多个变量声明中出现(给新变量赋值,给旧变量赋新值)
    2018-08-17
    6
  • Jevade
    string 是值传递,所以调用flag.String函数的时候,本身会构造一个string
    var name = *flag.String("name", "everyone", "The greeting object.")
    传递给name的是一个string的副本,和原本的string只是值一样,地址并不同,后面采用flag.Parse也就不会改变name,因此name还是默认值。
    不知道这样理解对不对?
    2018-11-22
    4
  • 冰激凌的眼泪
    var name = *flag.String("name", "everyone", "The greeting object.")
    这一句是不是导致name是个副本,parse后也不会变?请郝老师确认一下

    作者回复: 已经修正了。

    2018-08-18
    3
  • lfn
    变量解析由内向外,内层找不到才回去外层找。
    2018-08-17
    2
  • 钟鑫
    对于变量重声明我有一点疑问,我程序中尝试了,对于重声明的变量其地址是一样的,这个还算重声明吗?

    作者回复: 当然算,这是两码事。

    2019-11-13
    1
  • 🐶
    作用域不同,当前变量会覆盖外层作用域的变量
    2019-08-08
    1
  • 小苹果
    老师,puzzlers\article4\q1\demo7.go里面有个隐式错误。
    var name = *flag.String("name", "everyone", "The greeting object.")
    这种方法输出的结果永远是 Hello, everyone!,不论 -name 后面是什么。
    正确的应该是:
    func main() {
    // 方式1。
    var name = flag.String("name", "everyone", "The greeting object.")
    flag.Parse()
    fmt.Printf("Hello, %v!\n", *name)
    }

    作者回复: 嗯,看到了,这确实是个问题,当初是为了不改动其他代码。我想想怎么调整一下。谢谢。

    2019-05-04
    1
  • liyinda0000
    在article4/q1/demo7.go中在使用{方式1}中,发现name传参未能正常打印,我使用的go版本1.9.4
        解决办法: 13行 *flag.String改成flag.String;19行 fmt.Printf("Hello, %v!\n", name) 将name改成*name
        问题思考:应该是文中未深入探讨‘指针变量’的问题,flag.String()得到的是指针变量,*代表取值符,*name将指针变量中的值取出(运行&name发现为内存地址,&代表取地址符),上述问题的出现原因请您指点?另如何加入微信学习群,望早日加入组织,哈哈

    作者回复: 微信学习群已经满了,不过可以去开发者头条App搜索GoHackers并加入组织。这个组织是我个人在几年前发起的。

    2018-09-07
    1
  • charlesgogo01
    name := *flag.string()这儿为啥会有*,本来返回应该是个变量值,这样是传递地址吗?

    作者回复: Go里面没有传递地址这种说法。*在这里是取值操作符。

    2018-09-03
    1
  • 慢熊胖胖跑
    由于go是值传递,因此即使传入重名变量,一般在代码块中变量可以正常使用,但是值得改变不会引起变化,因为变量传入后,代码块中赋予了新的地址。 除非如同case3一样中传入变量的指针,然后才会使用相同的变量地址,修改变量的值。func reusevarnam1(var1 int) {    var1 = 3 + var1    fmt.Printf("Inside reusevarnam1,var1 is %d, address is %s\n", var1, &var1)}
    func reusevarnam2(var2 int) {    for var2 := 1; var2 < 3; var2++ {        fmt.Println("reusevarnam2 ...")        fmt.Printf("Inside reusevarnam2,var2 is %d, address is %s\n", var2, &var2)    }    fmt.Println("reusevarnam2")}
    func reusevarnam3(var3 *int) {    *var3 = *var3 + 100    fmt.Printf("Inside reusevarnam2,var3 is %d, address is %s\n", *var3, var3)}

    作者回复: 你这一堆❀是啥?:)

    2018-08-18
    1
  • 公众号「后端进阶」
    类型推断只能在局部生效,相当于Java的局部变量,而在函数体外的声明变量相当于Java的实例变量
    2018-08-17
    1
  • dlili
    :=短变量声明可以理解成var声明后再赋值,可以运行下边的程序理解

    ```

    func main() {
    err := test() //声明并赋值一个error类型的变量
    fmt.Printf("%v:%s\n",&err,err.Error())
    if true{
    err := test() //在if代码块中声明并赋值一个error类型的变量,与if外的不同,这是一个新的变量
    fmt.Printf("%v:%s\n",&err,err.Error())
    }
    if true{
    err := "error" //在if代码块中声明并赋值一个error类型的变量,与if外的不同,这是一个新的变量
    fmt.Printf("%v:%s\n",&err,err)
    }
    //err := errors.New("error") // 编译错误,此变量已经声明过
    err = errors.New("error") // 重新赋值
    fmt.Printf("%v:%s\n",&err,err.Error())
    }

    func test() (err error) {
    return errors.New("error")
    }
    ```
    2019-11-28
  • sun🍏🍌🍒🍅🌶🍎
    简单事情说的复杂化 讲解的时候能不能举例
    2019-09-19
  • w
    那个方便重构的例子,与其说是类型推断带来的方便,感觉更像是接口给带来的。不知道是不是我自己的错觉。

    作者回复: 在这个小例子里没有接口的事啊。当然你要说接口有利于重构,话也没错。

    2019-09-05
    1
  • Sky
    对变量的重声明的解释有点难以理解
    2019-06-04
收起评论
32
返回
顶部