ITPub博客

首页 > 应用开发 > > golang中的程序结构

golang中的程序结构

作者:else 时间:2019-10-28 13:53:55 0 删除 编辑

写在前面

今天来聊一聊golang中程序结构相关的话题,具体包括条件语句、循环、函数和指针等内容。

条件语句

if语句

给定一个自然数v,如果它在0-100之间则返回v,若大于100则返回100,小于0则返回0,使用Go语言实现的代码如下:

package main

import "fmt"

func ifTest(v int) int{

if v >100 { //if的条件里面不需要括号

return 100

}else if v <0 {

return 0

}else {

return v

}

}

func main() {

var a int = ifTest(5)

fmt.Println(a)

}

//运行结果:

5

注意到没有if的条件里面不需要括号,创建的文件中不能包含下划线。func ifTest(v int) int中参数v的前面不需要添加var关键词,后面的int则是该函数的返回结果。

现在来使用Go语言来读取某个文件的信息,如test.txt中的内容,相应的代码如下:

package main

import (

"fmt"

"io/ioutil"

)

func readFieTest(){

const filename = "test.txt"

contents, errorinfo :=ioutil.ReadFile(filename)

//var contents ,errorinfo = ioutil.ReadFile(filename)

if errorinfo != nil {

fmt.Println(errorinfo)

}else{

fmt.Printf("%s\n",contents)

}

}

func main() {

readFieTest()

}

读取文件需要采用ioutil包中的ReadFile函数,查看源码可知该函数一次可以返回两个值:

func ReadFile(filename string) ([]byte, error) {

......

}

if errorinfo != nil中的nil就是无的意思,此处就是产生了错误,可以参考这篇文章了解更多关于nil的信息:理解Go语言的nil 。其实上面那种方式不是很简介,可以使用类似于Java中的三元表达式:

package main

import (

"fmt"

"io/ioutil"

)

func readFieTest(){

const filename = "test.txt"

if contents, errorinfo := ioutil.ReadFile(filename) ;errorinfo != nil {

//先运行前半句后进行判断

fmt.Println(errorinfo)

}else {

fmt.Printf("%s",contents)

}

}

func main() {

readFieTest()

}

发现没有if的条件里可以进行赋值,且if条件里赋值的变量作用域就是这个if语句。接下来聊一聊switch,很多语言中都有switch。

switch语句

switch后面是可以接表达式的(也可以不接),使用Go实现计算某两个整数的加减乘除的功能,相应的代码如下:

package main

import "fmt"

func eval(a, b int,operation string) int {

var result int

switch operation {

case "+":

result = a +b

case "-":

result = a-b

case "*":

result = a*b

case "/":

result = a/b

default:

panic("不支持的运算方式"+operation) //这个panic就是报错,让程序停下来

}

return result

}

func main() {

test:= eval(3,4,"*")

fmt.Println(test)

}

//运行结果:

12

细心的你发现什么奇特之处么?对,里面没有break,因为Go语言中的switch会自动break,除非使用了fallthrough。而在C、C++或者是Java中你要么在后面添加break要么添加continue。

再来举一个例子,用于判断学生成绩情况:当分数小于60,则显示不及格;60-69为及格;70-79为中等;80-89为良好;90-100为优秀。使用Go语言实现的代码如下:

package main

import "fmt"

func scoreTest(score int)string {

var result string = ""

switch {

case score <0 ||score >100 :

panic(fmt.Sprintf("无效的分数:%d",score)) //如果这个条件成立,则程序不再往下执行

case score <60:

result = "不及格"

case score<70:

result = "及格"

case score <80:

result = "中等"

case score <90:

result = "良好"

case score <=100:

result = "优秀"

}

return result

}

func main() {

fmt.Println(

scoreTest(59),

scoreTest(62),

scoreTest(77),

scoreTest(84),

scoreTest(99),

//scoreTest(-99),

)

}

//运行结果:

不及格 及格 中等 良好 优秀

如果程序满足panic的要求,则程序会停止运行。switch后面可以没有表达式

循环语句

for语句

使用Go语言实现求解0-指定数字内的数字之和,如100以内整数的和,相应的代码如下:

package main

import "fmt"

func sumTest(a int) int {

sum := 0

for i :=0;i<=a;i++ {

sum+=i

}

return sum

}

func main() {

fmt.Println(sumTest(100))

}

上面使用了for循环,可以发现这个for循环的格式除了条件中不包含括号以外,其实和Java,JavaScript的代码完全一致。且大家要学会在函数中尽量使用:=的方式替代var来声明变量。

for的条件中不包含括号,且条件中可省略初始条件,结束条件以及递增表达式

再来看一个例子,将整数转换成二进制的表达式,相应的代码如下:

package main

import (

"fmt"

"strconv"

)

func intToBinary(n int)string {

result := ""

for ;n>0;n/=2{ //省略初始条件,相当于while

lsb := n%2

result = strconv.Itoa(lsb) +result

}

return result

}

func main() {

fmt.Println(

intToBinary(5), // 101

intToBinary(13), //1101

intToBinary(121242),

)

}

再来换一种方式读取之前那个test.txt文件中的内容,现在是一行行的进行读取:

//一行行读取

func printFileTest(filename string){

file, err :=os.Open(filename)

if err != nil{

panic(err) //程序停下来去报错

}else{

scanner := bufio.NewScanner(file)

for scanner.Scan(){

// 这里既没有开始条件,也没有递增条件,只有结束条件,此时分号都可以不写,Go语言中没有while

fmt.Println(scanner.Text()) //输出

}

}

}

func main() {

printFileTest("test.txt")

}

在这段代码里面for中既没有开始条件,也没有递增条件,只有结束条件,那么此时的分号都可以不写,记住Go语言中没有while。因为while的功能和for相似,所以Go语言中就没有必要存在while这个关键词了。

当for中什么也不加,则变成了一个死循环,就相当于其他语言中的while true。Go语言中的死循环实现起来非常简单,那是因为后面会经常使用到死循环。

简单总结一下循环语句的特点:1、for和if条件后面没有括号;2、if条件里面也可以定义变量;3、Go语言中没有while;4、switch中不需要定义break,也可以直接switch多个语句。

函数

其实在前面我们就使用了func这个关键词用于定义函数,函数定义的格式为:

func 函数名称(参数名称,参数类型)返回值类型{

......

}

需要说明的是,Go语言的函数可以有多个返回值的,且类型可以不相同:

package main

import "fmt"

//求解两个数的和

func sumTest(a,b int)int{

return a+b

}

//求两个数相除的商及余数

func divTest(a ,b int) (int,int, string) {

return a/b, a%b, "你好"

}

func main() {

fmt.Println(sumTest(3,6))

fmt.Println(divTest(13, 4))

}

//运行结果:

9

3 1 你好

在上面的代码中不知道返回的到底是什么,只知道都是int类型,其实可以像声明变量的方式那样给返回值设置名称:

//求两个数相除的商及余数

func divTest(a ,b int) (q, r int, s string) {

return a/b, a%b, "你好"

}

由于Go语言非常严格,定义的变量一定要使用,如果函数有多个返回值,我们只想取某个值时,那么其余的变量可以使用匿名变量_来接收。尽管Go语言支持返回多个类型值,但是不要乱用,一般返回两个,前者是数据,后者是错误nil,如下图所示。将前面实现两个数的四则运算的相关代码进行改写:

package main

import "fmt"

func calcTest(a, b int,operation string ) (int, error) {

switch operation {

case "+":

return a+b, nil

case "-":

return a-b,nil

case "*":

return a*b,nil

case "/":

return a/b,nil

default:

return 0,fmt.Errorf("不支持的运算操作:%s",operation)

}

}

func main() {

fmt.Println(calcTest(3,5,"+"))

}

//运行结果:

8

上述代码其实还不够完善,在main方法中对正常与否需要进行判断:

func main() {

if result ,err:= calcTest(3,5,"+");err != nil{

//程序运行存在错误

fmt.Println("程序运行存在错误",err)

}else{

fmt.Println(result)

}

}

在Go语言中函数可以返回多个值,且可以给多个值声明名称,但是返回多个值的情况仅仅适用于非常简单的函数,不过取不取名字和调用者无关。

Go语言是函数式编程,函数是一等公民(Python中也是),函数里面的参数,返回值里面都可以包含函数。通过前面求两个数的四则运算这个例子进行改写,实现将函数作为参数:

Go语言没有其他语言中的默认参数、可变参数、函数重载等,只有一个可变参数列表:

//求可变参数列表中参数之和

func dynamicVariable(values ... int)int {

sum :=0

for i:=range values{

sum+=values[i]

}

return sum

}

func main() {

fmt.Println(dynamicVariable(1,2,3,4,5,6))

}

//运行结果:

21

函数小结:1、函数返回值的类型写在最后面;2、函数可以返回多个值;3、函数可作为参数进行使用;4、没有默认参数和可选参数,函数重载等。

指针

大家不要听到指针就害怕,Go语言中的指针和C语言中的指针差别很大(Go语言中的指针不能运算,而C语言中却可以),比C中的指针简单多了。

看到这里就必须谈到一个老生常谈的问题:Go语言中的参数传递是值传递还是引用传递?在C和C++中既可以值传递也可以引用传递。Java和Python绝大部分都是引用传递,除了系统的自建类型以外。那么什么是值传递?什么是引用传递呢?我们通过C++中的一段代码进行了解(C++中使用&表示引用传递):

void pass_by_value(int a){ //值传递

a++;

}

void pass_by_guide(int& a){ //引用传递

a++;

}

int main(){

int a =3;

pass_by_value(a)

printf("值传递以后的值为:%d\n",a);

pass_by_guide(a)

printf("引用传递以后的值为:%d\n",a);

}

//运行结果:

3 4

pass_by_value是值传递,会将a的值从main函数中拷贝一份到pass_by_value函数中,真正作了一份拷贝,拷贝进去的a加了1,那么main函数中的a并没有发生变化,没有动依旧是3。pass_by_guide是引用传递,它不会拷贝,此时main函数中的a和pass_by_guide中的a其实是引用了同一个变量a,因此在pass_by_guide函数中进行了加1操作,自然main函数中的a也会发生变化,因此就变成了4。值传递就是拷贝,原来值不会发生变化;引用传递不会拷贝,会导致原来的值发生变化。

Go语言只有值传递一种方式。Go语言中的参数需要配合其指针来使用,具体分情况:

上面这种就是值传递,两者没有影响。下面是使用到了指针的情况:

左侧是一个int类型名为a的变量,右侧是一个int类型名为aa的指针,通过指针实现相当于引用传递的效果,把a的地址给了你以后,可以修改a的值。这些都是基本数据类型,再来尝试一个自定义类型:

当把左侧的book对象传给右侧的read函数时,一般这个book对象本身通常包含指向data的一个指针,然后拷贝一份到右侧函数中,右侧的book对象也有一个pdata,但是是指向同一个data,其实就是拷贝了同一份指针。因此在Go语言中,自定义类型的时候需要考虑把它作为一个值还是一个指针来用。这里的book其实就是作为一个值来用。

用一个交换两个对象的值这个例子来加深大家的印象:

//交换两个对象的值

func swap(a,b int) {

a,b = b ,a

}

func main() {

a ,b := 1,2

swap(a,b)

fmt.Println(a, b)

}

//运行结果:

1,2

你会发现这个函数没有用,两个数值并没有发生交换,的确是这样的,那是因为这个需要借助于指针来完成:

//交换两个对象的值

func swap(a,b *int) {

*a,*b = *b ,*a //声明指针需要使用*

}

func main() {

a ,b := 1,2

swap(&a,&b) //传递地址需要使用&

fmt.Println(a, b)

}

//运行结果:

2,1

不过这种看起来挺麻烦的,其实之前的代码不是没有起作用,而是没有将结果进行返回,修改一下代码其实是可以的:

func swapTest(a,b int)(int ,int) {

return b ,a

}

func main() {

a ,b := 1,2

a, b = swapTest(a,b)

fmt.Println(a, b)

}

//运行结果:

2,1

这样就通过接收函数的返回值,进而实现交换两个数值的目的。至此程序结构部分就聊到这里,后续介绍内建容器和面向对象,面向接口等方面的知识。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2318/viewspace-3153660/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-07-17

  • 博文量
    271
  • 访问量
    19555