GO學習筆記——GO語言常量與枚舉(6)

看完了變量的定義,再來看常量。

GO語言中常量定義使用關鍵字const,這個關鍵字倒是和C++相像。

常量的定義方法和變量定義差不多,這裏就一筆帶過了。

 

1. 定義一個簡單常量,同樣可以把它定義成局部變量或者是全局變量

const a int = 5

func main() {
	const b int = 5
	fmt.Println(a,b)
}

2.分組定義常量

const (
	a int = 5
	b int = 5
)

func main() {
	fmt.Println(a,b)
}

3.一行定義多個常量

func main() {
	const a,b int = 5,5
	fmt.Println(a,b)
}

4.省略類型的常量定義

func main() {
	const a,b  = 5,5
	fmt.Println(a,b)
	fmt.Println(reflect.TypeOf(a),reflect.TypeOf(b))
}

//輸出結果

5 5
int int

可以看到常量的定義和變量是差不多的,只不過有幾個地方需要注意

  • 常量定義的時候一定要加上const關鍵字。之前說變量定義的時候,如果是局部變量我們可以省略var關鍵字,但是如果在函數體內定義常量,不能省略const關鍵字,很簡單,我們省略了const,那不就和變量一樣了嗎?所以一句話,常量定義一定要加上const關鍵字
  • 常量定義的同時必須賦值,也就是說我們不能夠在常量的定義的時候不可以像變量那樣不賦值,只聲明,從而使用默認零值。常量定義的時候必須同時賦值
  • 因爲必須使用const關鍵字,所以常量也沒有想變量定義那樣的簡化方法,即使用冒號 ':' 定義,因爲使用冒號 ':' 定義,是因爲我們省略了var關鍵字,這邊必須使用const關鍵字,所以也沒有這種定義的方法。
func main() {
	a,b :=  5,5
	fmt.Println(a,b)
}

//上面這就是變量的定義了,不是常量

func main() {
	const a int
	fmt.Println(a)
}

//錯誤提示
.\main.go:9:10: missing value in const declaration

func main() {
	const a,b := 5,5
	fmt.Println(a,b)
}

//錯誤提示
.\main.go:9:8: missing value in const declaration
.\main.go:9:12: syntax error: unexpected := at end of statement

另外,還有一個點需要注意,這個是編譯器方面的推斷類型導致的,看如下代碼

func main() {
	a,b :=  3,4
	var c int
	c = int (math.Sqrt(a*a + b*b))
	fmt.Println(c)
}

程序會報錯,因爲sqrt函數的參數浮點類型,而這裏a和b是變量,編譯器將a和b推斷成了int類型,要變成浮點類型必須強轉

.\main.go:12:25: cannot use a * a + b * b (type int) as type float64 in argument to math.Sqrt

但是如果a和b是常量,就可以了

func main() {
	const a,b  = 3,4
	var c int
	c = int (math.Sqrt(a*a + b*b))
	fmt.Println(c)
}

//輸出結果
5

這是因爲,常量其實本質上做的是一個文本替換,這裏的a和b它既可以是int,也可以是浮點型,編譯器不管這個,編譯器只將a和b出現的地方用3和4代替了,實際上,那句sqrt相當於下面的表達式

c = int (math.Sqrt(25))

這是常量和變量在省略類型時的一點參數,只要注意常量的話它會是一個文本替換,可以作爲任意類型使用,而變量是推斷出具體類型。

5.使用內置表達式來定義廠常量

還可以使用一些內置表達式來定義常量,如:len(),unsafe.Sizeof()等

const name = "pigff"
const length = len(name)
const size = unsafe.Sizeof(name)    //用於求變量大小

func main() {
	fmt.Println(name,length,size)
}

輸出結果

pigff 5 16

注意看,這裏爲什麼Sizeof一個字符串的大小會是16?原因是GO中的sring內部實現由兩部分組成,一部分是指向字符串起始地址的指針,另一部分是字符串的長度,兩部分各是8字節,所以一共16字節。這部分具體會在後面的文章說到,這裏提一下。

和C++做個對比,len其實相當於C++中string類的length接口,而C++中對於字符串求sizeof的大小是根據編譯器決定的,不同平臺下求出的結果也不一樣,vs2017下求出來時28,而Linux下用g++求出來就是8,具體要看平臺,編譯器,以及編譯器版本。

自己實現的函數,不可以這樣用來賦值給常量

func fun(name string) string{
	return name
}
const name = fun("pigff")
func main() {
	fmt.Println(name)
}

程序會報錯

.\main.go:11:7: const initializer fun("pigff") is not a constant

所以只有內置的表達式和函數纔可以這麼定義。


使用常量定義枚舉類型

GO語言中有一個特殊常量——枚舉類型,C++中有enum關鍵字來定義枚舉類型,而GO語言中則沒有這樣的關鍵字,GO中使用一組const常量來表示枚舉類型。

我們知道,枚舉類型的數值是從0開始的,爲了對比,我們先來看一下一段C++代碼。

#include <iostream>
using namespace std;

enum{
    EAST,
    SOUTH,
    WEST,
    NORTH
};

int main(){
    int a[] = {EAST,SOUTH,WEST,NORTH};
    for(int i = 0; i < 4; ++i)
        cout << a[i] << " ";
    cout << endl;
    return 0;
}

我們在main函數中對這幾個枚舉值一一輸出它們的值,結果如下

0 1 2 3

可以看到確實是從0開始的,那麼這樣的枚舉類型在GO語言中是如何實現的呢?


GO使用一組const來表示枚舉類型,並使用了iota這個常量計數器

先試着像C++那樣定義枚舉類型

const (
	EAST
	SOUTH
	WEST
	NORTH
)

func main() {
	fmt.Println(EAST,SOUTH,WEST,NORTH)
}

誠然,因爲這些常量沒有定義,編譯器會報錯

.\main.go:6:2: missing value in const declaration

那麼我們就可以給它們來手動定義

const (
	EAST = 0
	SOUTH = 1
	WEST = 2
	NORTH = 3
)

func main() {
	fmt.Println(EAST,SOUTH,WEST,NORTH)
}

輸出的結果就是預期的

0 1 2 3

那麼如果這個我們想要表達的枚舉類型很多怎麼辦?我們都手動定義嗎,不能像C++那樣從0開始自動增長嗎?

爲了解決這個問題,引入了常量計數器iota,來看下GO語言是怎麼做到自動增長的

const (
	EAST = iota
	SOUTH
	WEST
	NORTH 
)

func main() {
	fmt.Println(EAST,SOUTH,WEST,NORTH)
}

輸出結果和上面一樣

0 1 2 3

好了,下面來具體瞭解一下這個iota到底是個啥


常量計數器iota

iota是GO語言的常量計數器,只可以在常量表達式中使用(給常量賦值)

  • iota在const關鍵字出現時將被重置爲0
  • const中每新增一行常量聲明將使iota計數+1(注意在同一行計數不+1)

上面就是使用iota這個常量計數器的關鍵。

1. iota只可以給常量比表達式使用,其餘地方不可使用

func main() {
	fmt.Println(iota)
}

程序會報錯,以爲iota是一個變量名

.\main.go:13:14: undefined: iota

2.const中每新增一行常量聲明將使iota計數+1(這一般出現在分組定義常量的時候)

func main() {
	const (
		a = iota      //iota爲0,a爲0
		b             //新增了一行常量定義,iota++,b爲1
		c             //新增了一行常量定義,iota++,c爲2
	)
	fmt.Println(a,b,c)
}

輸出結果如下 

0 1 2

3.出現關鍵字const時,iota就會被重置爲0(可能一個程序要聲明多個枚舉類型,就需要這個條件)

func main() {
	const (
		a = iota
		b
		c
	)

	const (
		d = iota
		e
		f
	)
	fmt.Println(a,b,c)
	fmt.Println(d,e,f)
}

輸出結果如下

0 1 2
0 1 2

iota有多種使用方法,一起來看一下


1. 跳值使用法

func main() {
	const (
		a = iota 	//a是0
		b		//b是1
		_		//2被跳過了
		c		//c是3
	)

	fmt.Println(a,b,c)
}

輸出結果

0 1 3

2. 插隊使用法

在a和c之間插入一個不是iota賦值的常量b,這個時候iota還是會+1

func main() {
	const (
		a = iota 	//a是0
		b = 3		//b是3,iota仍舊++
		c = iota	//c是2,不是0,此時iota是2
	)

	fmt.Println(a,b,c)
}

輸出結果

0 3 2

3. 表達式隱式使用法

如果後面的常量沒有賦值,那麼會向上找第一個不爲空的賦值表達式

func main() {
	const (
		a = iota * 2	//a是0,iota++
		b = iota	//b是1,iota++
		c = iota	//c是2
	)

	fmt.Println(a,b,c)
}

這個時候,把b和c的賦值都省略

func main() {
	const (
		a = iota * 2	//a是0,iota++
		b 		//b是2,iota++
		c 		//c是4
	)

	fmt.Println(a,b,c)
}

輸出結果如下

0 2 4

驗證一下,會向上使用第一個非空的賦值表達式

func main() {
	const (
		a = iota * 2	//a是0,iota++
		b = iota * 3	//b是3,iota++
		c 			//c是6,沿用了b的賦值表達式,而不是a的
		d			//d是9,沿用了b的賦值表達式,而不是a的
	)

	fmt.Println(a,b,c,d)
}

輸出結果如下

0 3 6 9 

因此,通過這種方法,可以有多元化的常量賦值,不一定是自增長的,也可以是二倍自增的,這也是GO語言枚舉類型的一個巧妙之處

4.單行使用法

格式一致,沿用之前對應的賦值格式

func main() {
	const (
		a,b = iota,iota + 2    //a爲0,b爲2,iota++
		c,d                    //等價於c = iota,d = iota + 2,所以c爲1,b爲3
	)

	fmt.Println(a,b,c,d)
}

輸出結果

0 2 1 3

注意格式必須一致,格式不一致就會導致出錯的問題(即第一行是兩個變量,後面的也必須是兩個變量)

func main() {
	const (
		a,b = iota,iota + 2
		c,d
		e    //格式不一致
	)

	fmt.Println(a,b,c,d)
}

上面程序格式不一致會出錯

.\main.go:16:3: extra expression in const declaration

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章