1.2命令行参数

命令行参数

os包提供了一些与操作系统交互的函数和变量,并且go对其做了一些封装。程序的命令行参数可以从os包的Args变量获取;os包外部使用os.Args访问该变量。

os.Args变量是一个字符串的切片(slice)。学过Python的同学可以很容易理解切片的概念。现在先把切片s当做数组元素序列,序列的成长度动态变化,用s[i]访问单个元素,用s[m:n]获取子序列。序列的元素个数为len(s)。和大多数编程语言类似,区间索引时,Go言里也采用左闭右开形式, 即,区间包括第一个索引元素,不包括最后一个, 因为这样可以简化逻辑。

os.Args的第一个元素,os.Args[0], 是命令本身的名字;其它的元素则是程序启动时传给它的参数。s[m:n]形式的切片表达式,产生从第m个元素到第n-1个元素的切片,下个例子用到的元素包含在os.Args[1:len(os.Args)]切片中。如果省略切片表达式的m或n,会默认传入0或len(s),因此前面的切片可以简写成os.Args[1:]。

下面是Unix里echo命令的一份实现,echo把它的命令行参数打印成一行。程序导入了两个包,用括号把它们括起来写成列表形式, 而没有分开写成独立的import声明。两种形式都合法,列表形式习惯上用得多。包导入顺序并不重要;gofmt工具格式化时按照字母顺序对包名排序。

// Echo1 prints its command-line arguments.
package main

import (
    "fmt"
    "os"
)

func main() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = " "
    }
    fmt.Println(s)
}

注释语句以//开头。对于程序员来说,//之后到行末之间所有的内容都是注释,被编译器忽略。按照惯例,我们在每个包的包声明前添加注释;对于main package,注释包含一句或几句话,从整体角度对程序做个描述。

var声明定义了两个string类型的变量s和sep。变量会在声明时直接初始化。如果变量没有显式初始化,则被隐式地赋予其类型的零值(zero value),数值类型是0,字符串类型是空字符串""。这个例子里,声明把s和sep隐式地初始化成空字符串。第2章再来详细地讲解变量和声明。

对数值类型,Go语言提供了常规的数值和逻辑运算符。而对string类型,+运算符连接字符串。所以表达式:

sep + os.Args[i]

表示连接字符串sep和os.Args。程序中使用的语句:

s += sep + os.Args[i]

是一条赋值语句, 将s的旧值跟sep与os.Args[i]连接后赋值回s,等价于:

s = s + sep + os.Args[i]

运算符+=是赋值运算符(assignment operator),每种数值运算符或逻辑运算符,如+或*,都有对应的赋值运算符。

echo程序可以每循环一次输出一个参数,这个版本却是不断地把新文本追加到末尾来构造字符串。字符串s开始为空,即值为"",每次循环会添加一些文本;第一次迭代之后,还会再插入一个空格,因此循环结束时每个参数中间都有一个空格。这是一种二次加工(quadratic process),当参数数量庞大时,开销很大,但是对于echo,这种情形不大可能出现。本章会介绍echo的若干改进版,下一章解决低效问题。

循环索引变量i在for循环的第一部分中定义。符号:=是短变量声明(short variable declaration)的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。下一章有这方面更多说明。

自增语句i++给i加1;这和i += 1以及i = i + 1都是等价的。对应的还有i--给i减1。它们是语句,而不像C系的其它语言那样是表达式。所以j = i++非法,而且++和--都只能放在变量名后面,因此--i也非法。

Go语言只有for循环这一种循环语句。for循环有多种形式,其中一种如下所示:

for initialization; condition; post {
    // zero or more statements
}

or循环三个部分不需括号包围。大括号强制要求, 左大括号必须和post语句在同一行。

initialization语句是可选的,在循环开始前执行。initalization如果存在,必须是一条简单语句(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。condition是一个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为true则执行循环体语句。post语句在循环体执行结束后执行,之后再次对conditon求值。condition值为false时,循环结束。

for循环的这三个部分每个都可以省略,如果省略initialization和post,分号也可以省略:

// a traditional "while" loop
for condition {
    // ...
}

如果连condition也省略了,像下面这样:

// a traditional infinite loop
for {
    // ...
}

这就变成一个无限循环,尽管如此,还可以用其他方式终止循环, 如一条break或return语句。

for循环的另一种形式, 在某种数据类型的区间(range)上遍历,如字符串或切片。echo的第二版本展示了这种形式:

// Echo2 prints its command-line arguments.
package main

import (
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

每次循环迭代,range产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但range的语法要求, 要处理元素, 必须处理索引。一种思路是把索引赋值给一个临时变量, 如temp, 然后忽略它的值,但Go语言不允许使用无用的局部变量(local variables),因为这会导致编译错误。

Go语言中这种情况的解决方法是用空标识符(blank identifier),即(也就是下划线)。空标识符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。大多数的Go程序员都会像上面这样使用range和写echo程序,因为隐式地而非显示地索引os.Args,容易写对。

echo的这个版本使用一条短变量声明来声明并初始化s和seps,也可以将这两个变量分开声明,声明一个变量有好几种方式,下面这些都等价:

s := ""
var s string
var s = ""
var s string = ""

用哪种不用哪种,为什么呢?第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。第二种形式依赖于字符串的默认初始化零值机制,被初始化为""。第三种形式用得很少,除非同时声明多个变量。第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。

如前文所述,每次循环迭代字符串s的内容都会更新。+=连接原字符串、空格和下个参数,产生新字符串, 并把它赋值给s。s原来的内容已经不再使用,将在适当时机对它进行垃圾回收。

如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用strings包的Join函数:

func main() {
    fmt.Println(strings.Join(os.Args[1:], " "))
}

最后,如果不关心输出格式,只想看看输出值,或许只是为了调试,可以用Println为我们格式化输出。

fmt.Println(os.Args[1:])

这条语句的输出结果跟strings.Join得到的结果很像,只是被放到了一对方括号里。切片都会被打印成这种格式。

练习 1.1: 修改echo程序,使其能够打印os.Args[0],即被执行命令本身的名字。

package main

import (
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    fmt.Println("file:",os.Args[0])
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

练习 1.2: 修改echo程序,使其打印每个参数的索引和值,每个一行。

package main

import (
    "fmt"
    "os"
)

func main() {
    
    for idx, arg := range os.Args[1:] {
       fmt.Println(idx,arg)
    }
    
}

练习 1.3: 做实验测量潜在低效的版本和使用了strings.Join的版本的运行时间差异。(1.6节讲解了部分time包,11.4节展示了如何写标准测试程序,以得到系统性的性能评测。)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,365评论 0 17
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 个人学习批处理的初衷来源于实际工作;在某个迭代版本有个BS(安卓手游模拟器)大需求,从而在测试过程中就重复涉及到...
    Luckykailiu阅读 4,702评论 0 11
  • 今天在大巴车上听了罗辑思维的两段节目,一是爱因斯坦的崛起,二是我为何不信中医,随手记一下。 1,科学共同体:科学共...
    咸鱼茄子阅读 510评论 0 0