字符串
- Go 語(yǔ)言中的字符串是一個(gè)字節(jié)切片。把內(nèi)容放在雙引號(hào)""之間飞袋,我們可以創(chuàng)建一個(gè)字符串瞻坝。
- 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)
}
如果換成這個(gè)字符串:Se?or,進(jìn)行切片遍歷仰泻,可以發(fā)現(xiàn)輸出的字節(jié)是不對(duì)的:S e ? ± o r荆陆。
這是因?yàn)??
的 Unicode 代碼點(diǎn)(Code Point)是 U+00F1
。它的 UTF-8 編碼占用了 c3 和 b1 兩個(gè)字節(jié)集侯。它的 UTF-8 編碼占用了兩個(gè)字節(jié) c3 和 b1被啼。而我們打印字符時(shí)帜消,卻假定每個(gè)字符的編碼只會(huì)占用一個(gè)字節(jié),這是錯(cuò)誤的浓体。在 UTF-8 編碼中泡挺,一個(gè)代碼點(diǎn)可能會(huì)占用超過(guò)一個(gè)字節(jié)的空間。那么我們?cè)撛趺崔k呢命浴?rune 能幫我們解決這個(gè)難題粘衬。
rune 是 Go 語(yǔ)言的內(nèi)建類型,它也是 int32 的別稱咳促。在 Go 語(yǔ)言中稚新,rune 表示一個(gè)代碼點(diǎn)。代碼點(diǎn)無(wú)論占用多少個(gè)字節(jié)跪腹,都可以用一個(gè) rune 來(lái)表示褂删。讓我們修改一下上面的程序,用 rune 來(lái)打印字符冲茸。
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)化為一個(gè) 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é)位置。
字符串的長(zhǎng)度
utf8 package 包中的 func RuneCountInString(s string) (n int)
方法用來(lái)獲取字符串的長(zhǎng)度逗栽。這個(gè)方法傳入一個(gè)字符串參數(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 中的字符串是不可變的。一旦一個(gè)字符串被創(chuàng)建彼宠,那么它將無(wú)法被修改鳄虱。
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)化為一個(gè) rune 切片凭峡。然后這個(gè)切片可以進(jìn)行任何想要的改變拙已,然后再轉(zhuǎn)化為一個(gè)字符串。
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é)過(guò)C語(yǔ)言的知道摧冀,指針是一種存變量?jī)?nèi)存地址的變量倍踪。
如上圖所示,變量 b 的值為 156索昂,而 b 的內(nèi)存地址為 0x1040a124建车。變量 a 存儲(chǔ)了 b 的地址。我們就稱 a 指向了 b楼镐。
指針的聲明
例如:指針變量的類型為 *T癞志,該指針指向一個(gè) 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 解引用的語(yǔ)法是 *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] 的簡(jiǎn)寫形式,因此上面代碼中的 (arr)[0] 可以替換為 arr[0]描睦。下面我們用簡(jiǎn)寫形式重寫以上代碼膊存。
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ù)傳遞一個(gè)數(shù)組指針參數(shù),并在函數(shù)內(nèi)修改數(shù)組忱叭。盡管它是有效的隔崎,但卻不是 Go 語(yǔ)言慣用的實(shí)現(xiàn)方式。我們最好使用切片來(lái)處理韵丑。
接下來(lái)我們用切片來(lái)重寫之前的代碼:
package main
import (
"fmt"
)
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
我們將一個(gè)切片傳遞給了 modify 函數(shù)爵卒。在 modify 函數(shù)中,我們把切片的第一個(gè)元素修改為 90撵彻。程序也會(huì)輸出 [90 90 91]钓株。所以別再傳遞數(shù)組指針了,而是使用切片吧陌僵。上面的代碼更加簡(jiǎn)潔轴合,也更符合 Go 語(yǔ)言的習(xí)慣。
Go 不支持指針運(yùn)算
Go 并不支持其他語(yǔ)言(例如 C)中的指針運(yùn)算碗短。
package main
func main() {
b := [...]int{109, 110, 111}
p := &b
p++
}
上面的程序會(huì)拋出編譯錯(cuò)誤:main.go:6: invalid operation: p++ (non-numeric type *[3]int)
受葛。
結(jié)構(gòu)體
什么是結(jié)構(gòu)體?
結(jié)構(gòu)體是用戶定義的類型偎谁,表示若干個(gè)字段(Field)的集合奔坟。有時(shí)應(yīng)該把數(shù)據(jù)整合在一起,而不是讓這些數(shù)據(jù)沒(méi)有聯(lián)系搭盾。這種情況下可以使用結(jié)構(gòu)體咳秉。
聲明結(jié)構(gòu)體
例如,一個(gè)職員有 firstName鸯隅、lastName 和 age 三個(gè)屬性澜建,而把這些屬性組合在一個(gè)結(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)體時(shí)也可以不用聲明一個(gè)新類型跟畅,這樣的結(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)體,第一種無(wú)需順序?qū)?yīng)聲明時(shí)結(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)體是匿名的,是因?yàn)樗皇莿?chuàng)建一個(gè)新的結(jié)構(gòu)體變量 em3睹耐,而沒(méi)有定義任何結(jié)構(gòu)體類型辐赞。
結(jié)構(gòu)體的零值(Zero Value)
當(dāng)定義好的結(jié)構(gòu)體并沒(méi)有被顯式地初始化時(shí),該結(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)然還可以為某些字段指定初始值,而忽略其他字段窖梁。這樣赘风,忽略的字段名會(huì)賦值為零值。
訪問(wèn)結(jié)構(gòu)體的字段
點(diǎn)號(hào)操作符 . 用于訪問(wèn)結(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邀窃,以后再給各個(gè)字段賦值。
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 語(yǔ)言允許我們?cè)谠L問(wèn) firstName 字段時(shí)蛔翅,可以使用 emp8.firstName 來(lái)代替顯式的解引用 (*emp8).firstName
匿名字段
當(dāng)我們創(chuàng)建結(jié)構(gòu)體時(shí),字段可以只有類型位谋,而沒(méi)有字段名山析。這樣的字段稱為匿名字段(Anonymous Field)。
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
p := Person{"Naveen", 50}
fmt.Println(p)
}
雖然匿名字段沒(méi)有名稱掏父,但其實(shí)匿名字段的名稱就是為它的類型笋轨。比如在上面的 Person 結(jié)構(gòu)體里,雖說(shuō)字段是匿名的赊淑,但 Go 默認(rèn)這些字段名是它們各自的類型爵政。所以 Person 結(jié)構(gòu)體有兩個(gè)名為 string 和 int 的字段。
可以這樣操作:
func main() {
var p1 Person
p1.string = "naveen"
p1.int = 50
fmt.Println(p1)
}
嵌套結(jié)構(gòu)體(Nested Structs)
結(jié)構(gòu)體的字段有可能也是一個(gè)結(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也是一個(gè)結(jié)構(gòu)體饱岸,而Address結(jié)構(gòu)體中有兩個(gè)字段掺出,訪問(wèn)這兩個(gè)字段就像在 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)體名稱
以大寫字母開(kāi)頭
汤锨,則它是其他包可以訪問(wèn)的導(dǎo)出類型(Exported Type)。同樣百框,如果結(jié)構(gòu)體里的字段首字母大寫
闲礼,它也能被其他包訪問(wèn)到。
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)
}
如果訪問(wèn)未導(dǎo)出的字段 model,編譯器會(huì)提示錯(cuò)誤柬泽。
結(jié)構(gòu)體相等性(Structs Equality)
結(jié)構(gòu)體是值類型慎菲。如果它的每一個(gè)字段都是可比較的,則該結(jié)構(gòu)體也是可比較的聂抢。如果兩個(gè)結(jié)構(gòu)體變量的對(duì)應(yīng)字段相等钧嘶,則這兩個(gè)變量也是相等的棠众。
如果結(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 包含一個(gè) map 類型的字段闸拿。由于 map 類型是不可比較的空盼,因此 image1 和 image2 也不可比較。如果運(yùn)行該程序新荤,編譯器會(huì)報(bào)錯(cuò):main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)揽趾。
如果以上文章對(duì)你有幫助,記得點(diǎn)贊加關(guān)注作者苛骨,后續(xù)持續(xù)更新哦~~~~