golang中的程序結構

寫在前面

今天來聊一聊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語言中的參數需要配合其指針來使用,具體分情況:


golang中的程序結構


上面這種就是值傳遞,兩者沒有影響。下面是使用到了指針的情況:


golang中的程序結構


左側是一個int類型名為a的變量,右側是一個int類型名為aa的指針,通過指針實現相當於引用傳遞的效果,把a的地址給了你以後,可以修改a的值。這些都是基本數據類型,再來嘗試一個自定義類型:


golang中的程序結構


當把左側的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

這樣就通過接收函數的返回值,進而實現交換兩個數值的目的。至此程序結構部分就聊到這裡,後續介紹內建容器和麵向對象,面向接口等方面的知識。

golang中的程序結構


分享到:


相關文章: