字符串
- Go 語言中的字符串是一個字節(jié)切片。把內(nèi)容放在雙引號""之間,我們可以創(chuàng)建一個字符串。
- Go 中的字符串是兼容 Unicode 編碼的,并且使用 UTF-8 進(jìn)行編碼。
package main
import (
"fmt"
)
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
如果換成這個字符串:Se?or,進(jìn)行切片遍歷,可以發(fā)現(xiàn)輸出的字節(jié)是不對的:S e ? ± o r。
這是因為 ? 的 Unicode 代碼點(Code Point)是 U+00F1。它的 UTF-8 編碼占用了 c3 和 b1 兩個字節(jié)。它的 UTF-8 編碼占用了兩個字節(jié) c3 和 b1。而我們打印字符時,卻假定每個字符的編碼只會占用一個字節(jié),這是錯誤的。在 UTF-8 編碼中,一個代碼點可能會占用超過一個字節(jié)的空間。那么我們該怎么辦呢?rune 能幫我們解決這個難題。
rune 是 Go 語言的內(nèi)建類型,它也是 int32 的別稱。在 Go 語言中,rune 表示一個代碼點。代碼點無論占用多少個字節(jié),都可以用一個 rune 來表示。讓我們修改一下上面的程序,用 rune 來打印字符。
package main
import (
"fmt"
)
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
runes := []rune(s)
for i:= 0; i < len(runes); i++ {
fmt.Printf("%c ",runes[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n\n")
name = "Se?or"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
在上面代碼的第 14 行,字符串被轉(zhuǎn)化為一個 rune 切片。然后我們循環(huán)打印字符。
字符串的 for range 循環(huán)
func printCharsAndBytes(s string) {
for index, rune := range s {
fmt.Printf("%c starts at byte %d\n", rune, index)
}
}
func main() {
name := "Se?or"
printCharsAndBytes(name)
}
使用 for range 循環(huán)遍歷了字符串。循環(huán)返回的是是當(dāng)前 rune 的字節(jié)位置。
字符串的長度
utf8 package 包中的 func RuneCountInString(s string) (n int) 方法用來獲取字符串的長度。這個方法傳入一個字符串參數(shù)然后返回字符串中的 rune 的數(shù)量。
package main
import (
"fmt"
"unicode/utf8"
)
func length(s string) {
fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
func main() {
word1 := "Se?or"
length(word1)
word2 := "Pets"
length(word2)
}
字符串是不可變的
Go 中的字符串是不可變的。一旦一個字符串被創(chuàng)建,那么它將無法被修改。
package main
import (
"fmt"
)
func mutate(s string)string {
s[0] = 'a'//any valid unicode character within single quote is a rune
return s
}
func main() {
h := "hello"
fmt.Println(mutate(h))
}
為了修改字符串,可以把字符串轉(zhuǎn)化為一個 rune 切片。然后這個切片可以進(jìn)行任何想要的改變,然后再轉(zhuǎn)化為一個字符串。
package main
import (
"fmt"
)
func mutate(s []rune) string {
s[0] = 'a'
return string(s)
}
func main() {
h := "hello"
fmt.Println(mutate([]rune(h)))
}
指針
學(xué)過C語言的知道,指針是一種存變量內(nèi)存地址的變量。

如上圖所示,變量 b 的值為 156,而 b 的內(nèi)存地址為 0x1040a124。變量 a 存儲了 b 的地址。我們就稱 a 指向了 b。
指針的聲明
例如:指針變量的類型為 *T,該指針指向一個 T 類型的變量。
package main
import (
"fmt"
)
func main() {
b := 255
var a *int = &b
fmt.Printf("Type of a is %T\n", a)
fmt.Println("address of b is", a)
}
運(yùn)行結(jié)果:
Type of a is *int
address of b is 0x1040a124
指針的零值(Zero Value)
指針的零值是 nil。
package main
import (
"fmt"
)
func main() {
a := 25
var b *int
if b == nil {
fmt.Println("b is", b)
b = &a
fmt.Println("b after initialization is", b)
}
}
指針的解引用
指針的解引用可以獲取指針?biāo)赶虻淖兞康闹?。?a 解引用的語法是 *a。
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
*a++
fmt.Println("new value of b is", b)
}
上述代碼使用指針的解引用修改了b的值。
向函數(shù)傳遞指針參數(shù)
package main
import (
"fmt"
)
func change(val *int) {
*val = 55
}
func main() {
a := 58
fmt.Println("value of a before function call is",a)
b := &a
change(b)
fmt.Println("value of a after function call is", a)
}
不要向函數(shù)傳遞數(shù)組的指針,而應(yīng)該使用切片
package main
import (
"fmt"
)
func modify(arr *[3]int) {
(*arr)[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
a[x] 是 (a)[x] 的簡寫形式,因此上面代碼中的 (arr)[0] 可以替換為 arr[0]。下面我們用簡寫形式重寫以上代碼。
package main
import (
"fmt"
)
func modify(arr *[3]int) {
arr[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
這種方式向函數(shù)傳遞一個數(shù)組指針參數(shù),并在函數(shù)內(nèi)修改數(shù)組。盡管它是有效的,但卻不是 Go 語言慣用的實現(xiàn)方式。我們最好使用切片來處理。
接下來我們用切片來重寫之前的代碼:
package main
import (
"fmt"
)
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
我們將一個切片傳遞給了 modify 函數(shù)。在 modify 函數(shù)中,我們把切片的第一個元素修改為 90。程序也會輸出 [90 90 91]。所以別再傳遞數(shù)組指針了,而是使用切片吧。上面的代碼更加簡潔,也更符合 Go 語言的習(xí)慣。
Go 不支持指針運(yùn)算
Go 并不支持其他語言(例如 C)中的指針運(yùn)算。
package main
func main() {
b := [...]int{109, 110, 111}
p := &b
p++
}
上面的程序會拋出編譯錯誤:main.go:6: invalid operation: p++ (non-numeric type *[3]int)。
結(jié)構(gòu)體
什么是結(jié)構(gòu)體?
結(jié)構(gòu)體是用戶定義的類型,表示若干個字段(Field)的集合。有時應(yīng)該把數(shù)據(jù)整合在一起,而不是讓這些數(shù)據(jù)沒有聯(lián)系。這種情況下可以使用結(jié)構(gòu)體。
聲明結(jié)構(gòu)體
例如,一個職員有 firstName、lastName 和 age 三個屬性,而把這些屬性組合在一個結(jié)構(gòu)體 employee 中就很合理。
type Employee struct {
firstName string
lastName string
age int
}
還可以這樣聲明:
type Employee struct {
firstName, lastName string
age, salary int
}
上面的結(jié)構(gòu)體 Employee 稱為
命名的結(jié)構(gòu)體(Named Structure)。我們創(chuàng)建了名為 Employee 的新類型,而它可以用于創(chuàng)建 Employee 類型的結(jié)構(gòu)體變量。
聲明結(jié)構(gòu)體時也可以不用聲明一個新類型,這樣的結(jié)構(gòu)體類型稱為 匿名結(jié)構(gòu)體(Anonymous Structure)。
//匿名結(jié)構(gòu)體
var employee struct {
firstName, lastName string
age int
}
創(chuàng)建命名的結(jié)構(gòu)體
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
//creating structure using field names
emp1 := Employee{
firstName: "Sam",
age: 25,
salary: 500,
lastName: "Anderson",
}
//creating structure without using field names
emp2 := Employee{"Thomas", "Paul", 29, 800}
fmt.Println("Employee 1", emp1)
fmt.Println("Employee 2", emp2)
}
上述代碼中兩種方法創(chuàng)建結(jié)構(gòu)體,第一種無需順序?qū)?yīng)聲明時結(jié)構(gòu)體中的字段,第二種必須順序?qū)?yīng),不能亂序。
創(chuàng)建匿名結(jié)構(gòu)體
package main
import (
"fmt"
)
func main() {
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee 3", emp3)
}
之所以稱這種結(jié)構(gòu)體是匿名的,是因為它只是創(chuàng)建一個新的結(jié)構(gòu)體變量 em3,而沒有定義任何結(jié)構(gòu)體類型。
結(jié)構(gòu)體的零值(Zero Value)
當(dāng)定義好的結(jié)構(gòu)體并沒有被顯式地初始化時,該結(jié)構(gòu)體的字段將默認(rèn)賦為零值。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp4 Employee //zero valued structure
fmt.Println("Employee 4", emp4)
}
string 的零值:(""),int的零值:0
當(dāng)然還可以為某些字段指定初始值,而忽略其他字段。這樣,忽略的字段名會賦值為零值。
訪問結(jié)構(gòu)體的字段
點號操作符 . 用于訪問結(jié)構(gòu)體的字段。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp6 := Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", emp6.firstName)
fmt.Println("Last Name:", emp6.lastName)
fmt.Println("Age:", emp6.age)
fmt.Printf("Salary: $%d", emp6.salary)
}
還可以創(chuàng)建零值的 struct,以后再給各個字段賦值。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp7 Employee
emp7.firstName = "Jack"
emp7.lastName = "Adams"
fmt.Println("Employee 7:", emp7)
}
結(jié)構(gòu)體的指針
還可以創(chuàng)建指向結(jié)構(gòu)體的指針。
package main
import (
"fmt"
)
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp8 := &Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", (*emp8).firstName)
fmt.Println("Age:", (*emp8).age)
}
Go 語言允許我們在訪問 firstName 字段時,可以使用 emp8.firstName 來代替顯式的解引用 (*emp8).firstName
匿名字段
當(dāng)我們創(chuàng)建結(jié)構(gòu)體時,字段可以只有類型,而沒有字段名。這樣的字段稱為匿名字段(Anonymous Field)。
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
p := Person{"Naveen", 50}
fmt.Println(p)
}
雖然匿名字段沒有名稱,但其實匿名字段的名稱就是為它的類型。比如在上面的 Person 結(jié)構(gòu)體里,雖說字段是匿名的,但 Go 默認(rèn)這些字段名是它們各自的類型。所以 Person 結(jié)構(gòu)體有兩個名為 string 和 int 的字段。
可以這樣操作:
func main() {
var p1 Person
p1.string = "naveen"
p1.int = 50
fmt.Println(p1)
}
嵌套結(jié)構(gòu)體(Nested Structs)
結(jié)構(gòu)體的字段有可能也是一個結(jié)構(gòu)體。這樣的結(jié)構(gòu)體稱為嵌套結(jié)構(gòu)體。
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.address = Address {
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:",p.age)
fmt.Println("City:",p.address.city)
fmt.Println("State:",p.address.state)
}
提升字段(Promoted Fields)
匿名字段為結(jié)構(gòu)體的字段稱之為提升字段。
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
上述代碼中Person結(jié)構(gòu)體中的匿名字段Address也是一個結(jié)構(gòu)體,而Address結(jié)構(gòu)體中有兩個字段,訪問這兩個字段就像在 Person 里直接聲明的一樣,因此我們稱之為提升字段。
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
導(dǎo)出結(jié)構(gòu)體和字段
如果結(jié)構(gòu)體名稱
以大寫字母開頭,則它是其他包可以訪問的導(dǎo)出類型(Exported Type)。同樣,如果結(jié)構(gòu)體里的字段首字母大寫,它也能被其他包訪問到。
package computer
type Spec struct { //exported struct
Maker string //exported field
model string //unexported field
Price int //exported field
}
package main
import "structs/computer"
import "fmt"
func main() {
var spec computer.Spec
spec.Maker = "apple"
spec.Price = 50000
fmt.Println("Spec:", spec)
}
如果訪問未導(dǎo)出的字段 model,編譯器會提示錯誤。
結(jié)構(gòu)體相等性(Structs Equality)
結(jié)構(gòu)體是值類型。如果它的每一個字段都是可比較的,則該結(jié)構(gòu)體也是可比較的。如果兩個結(jié)構(gòu)體變量的對應(yīng)字段相等,則這兩個變量也是相等的。
如果結(jié)構(gòu)體包含不可比較的字段,則結(jié)構(gòu)體變量也不可比較。
先看可比較的示例:
type name struct {
firstName string
lastName string
}
func main() {
name1 := name{"Steve", "Jobs"}
name2 := name{"Steve", "Jobs"}
if name1 == name2 {
fmt.Println("name1 and name2 are equal")
} else {
fmt.Println("name1 and name2 are not equal")
}
name3 := name{firstName:"Steve", lastName:"Jobs"}
name4 := name{}
name4.firstName = "Steve"
if name3 == name4 {
fmt.Println("name3 and name4 are equal")
} else {
fmt.Println("name3 and name4 are not equal")
}
}
再看不可比較的示例:
package main
import (
"fmt"
)
type image struct {
data map[int]int
}
func main() {
image1 := image{data: map[int]int{
0: 155,
}}
image2 := image{data: map[int]int{
0: 155,
}}
if image1 == image2 {
fmt.Println("image1 and image2 are equal")
}
}
結(jié)構(gòu)體類型 image 包含一個 map 類型的字段。由于 map 類型是不可比較的,因此 image1 和 image2 也不可比較。如果運(yùn)行該程序,編譯器會報錯:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)。
如果以上文章對你有幫助,記得點贊加關(guān)注作者,后續(xù)持續(xù)更新哦~~~~