豐縣建設(shè)網(wǎng)站做網(wǎng)站遇到的問(wèn)題
鶴壁市浩天電氣有限公司
2026/01/22 10:18:00
豐縣建設(shè)網(wǎng)站,做網(wǎng)站遇到的問(wèn)題,wordpress 單頁(yè)面翻頁(yè),微信商城如何開(kāi)通理解 Go 語(yǔ)言中的字符串、字節(jié)與符文 #x1f4d6;
引言
我們之前的博文詳細(xì)解釋了 Go 語(yǔ)言切片的工作原理#xff0c;并輔以大量示例闡明了其實(shí)現(xiàn)機(jī)制。在此背景下#xff0c;本文將深入探討 Go 語(yǔ)言中的字符串。乍一看#xff0c;字符串似乎是一個(gè)過(guò)于簡(jiǎn)單的話題#…理解 Go 語(yǔ)言中的字符串、字節(jié)與符文 引言我們之前的博文詳細(xì)解釋了 Go 語(yǔ)言切片的工作原理并輔以大量示例闡明了其實(shí)現(xiàn)機(jī)制。在此背景下本文將深入探討 Go 語(yǔ)言中的字符串。乍一看字符串似乎是一個(gè)過(guò)于簡(jiǎn)單的話題不值得專門(mén)撰寫(xiě)一篇博文。然而要真正善用字符串不僅需要理解其工作原理還需要辨清字節(jié)byte、字符character和符文rune之間的區(qū)別理解 Unicode 與 UTF-8 的差異區(qū)分字符串與字符串字面量以及其他更為細(xì)微的差別。理解這個(gè)話題的一種方式是回答一個(gè)常見(jiàn)問(wèn)題“當(dāng)我通過(guò)索引n訪問(wèn) Go 字符串時(shí)為什么我得到的不是第n個(gè)字符”正如您將看到的這個(gè)問(wèn)題將引導(dǎo)我們深入了解現(xiàn)代世界中文本處理的諸多細(xì)節(jié)。Joel Spolsky 的著名博文《每個(gè)軟件開(kāi)發(fā)者都必須絕對(duì)、肯定、知道的 Unicode 和字符集的最少知識(shí) (沒(méi)有借口)》對(duì)其中一些問(wèn)題做了精彩的介紹其觀點(diǎn)也將在本文中得到呼應(yīng)。什么是字符串讓我們從一些基礎(chǔ)知識(shí)開(kāi)始。在 Go 語(yǔ)言中字符串本質(zhì)上是一個(gè)只讀的字節(jié)切片。如果您對(duì)字節(jié)切片是什么或如何工作有任何疑問(wèn)請(qǐng)務(wù)必閱讀我們之前的博文本文將假定您已了解相關(guān)背景。首先要明確的是字符串可以容納任意字節(jié)。它不強(qiáng)制要求包含 Unicode 文本、UTF-8 文本或任何其他預(yù)定義格式。就字符串的內(nèi)容而言它與字節(jié)切片完全等價(jià)。這是一個(gè)字符串字面量稍后會(huì)詳細(xì)介紹它使用xNN表示法來(lái)定義一個(gè)包含一些特殊字節(jié)值的字符串常量。當(dāng)然字節(jié)的十六進(jìn)制值范圍從 00 到 FF包括兩端。Goconst sample xbdxb2x3dxbcx20xe2x8cx98打印字符串由于sample字符串中的某些字節(jié)既不是有效的 ASCII也不是有效的 UTF-8直接打印字符串會(huì)產(chǎn)生混亂的輸出。簡(jiǎn)單的fmt.Println語(yǔ)句Gofmt.Println(sample)會(huì)產(chǎn)生以下亂碼具體外觀因環(huán)境而異 ?為了了解這個(gè)字符串真正包含什么我們需要將其拆解并檢查各個(gè)部分。有幾種方法可以做到這一點(diǎn)。最直接的方法是循環(huán)遍歷其內(nèi)容并單獨(dú)取出字節(jié)就像這個(gè)for循環(huán)一樣Gofor i : 0; i len(sample); i { fmt.Printf(%x , sample[i]) }如前所述對(duì)字符串進(jìn)行索引訪問(wèn)的是單個(gè)字節(jié)而不是字符。我們將在下面詳細(xì)討論這個(gè)話題?,F(xiàn)在讓我們只關(guān)注字節(jié)。這是按字節(jié)循環(huán)的輸出bd b2 3d bc 20 e2 8c 98請(qǐng)注意單個(gè)字節(jié)如何與定義字符串的十六進(jìn)制轉(zhuǎn)義序列相匹配。生成可呈現(xiàn)的混亂字符串輸出的更短方法是使用fmt.Printf的%x十六進(jìn)制格式動(dòng)詞。它只會(huì)將字符串的連續(xù)字節(jié)作為十六進(jìn)制數(shù)字打印出來(lái)每個(gè)字節(jié)兩位。Gofmt.Printf(%x
, sample)將其輸出與上面的進(jìn)行比較bdb23dbc20e28c98一個(gè)巧妙的技巧是在該格式中使用“空格”標(biāo)志在%和x之間加上一個(gè)空格。將此處使用的格式字符串與上面的進(jìn)行比較Gofmt.Printf(% x
, sample)請(qǐng)注意字節(jié)之間如何出現(xiàn)空格使結(jié)果不那么令人感到壓迫bd b2 3d bc 20 e2 8c 98還有更多。%q引用動(dòng)詞將轉(zhuǎn)義字符串中任何不可打印的字節(jié)序列使輸出清晰明了。Gofmt.Printf(%q
, sample)當(dāng)字符串大部分內(nèi)容可理解為文本但有一些需要找出特殊之處時(shí)此技術(shù)非常方便它會(huì)生成xbdxb2xbc ?如果我們瞇著眼睛看可以看到在噪音中埋藏著一個(gè) ASCII 等號(hào)以及一個(gè)普通空格末尾出現(xiàn)了著名的瑞典“興趣點(diǎn)”符號(hào)。該符號(hào)的 Unicode 值為 U2318由空格十六進(jìn)制值20后的字節(jié)e2 8c 98以 UTF-8 編碼。如果我們對(duì)字符串中的奇怪值不熟悉或感到困惑可以使用%q動(dòng)詞的“加號(hào)”標(biāo)志。此標(biāo)志會(huì)使輸出不僅轉(zhuǎn)義不可打印序列還會(huì)轉(zhuǎn)義任何非 ASCII 字節(jié)同時(shí)解釋 UTF-8。結(jié)果是它會(huì)暴露字符串中表示非 ASCII 數(shù)據(jù)的正確格式化 UTF-8 的 Unicode 值Gofmt.Printf(%q
, sample)使用這種格式瑞典符號(hào)的 Unicode 值會(huì)顯示為u轉(zhuǎn)義序列xbdxb2xbc u2318這些打印技術(shù)在調(diào)試字符串內(nèi)容時(shí)很有用并且在接下來(lái)的討論中也會(huì)派上用場(chǎng)。值得指出的是所有這些方法對(duì)于字節(jié)切片和字符串的行為完全相同。以下是我們列出的所有打印選項(xiàng)的完整集合作為一個(gè)完整的程序呈現(xiàn)您可以在瀏覽器中直接運(yùn)行和編輯Gopackage main import fmt func main() { const sample xbdxb2x3dxbcx20xe2x8cx98 fmt.Println(Println:) fmt.Println(sample) fmt.Println(Byte loop:) for i : 0; i len(sample); i { fmt.Printf(%x , sample[i]) } fmt.Printf(
) fmt.Println(Printf with %x:) fmt.Printf(%x
, sample) fmt.Println(Printf with % x:) fmt.Printf(% x
, sample) fmt.Println(Printf with %q:) fmt.Printf(%q
, sample) fmt.Println(Printf with %q:) fmt.Printf(%q
, sample) }UTF-8 與字符串字面量正如我們所見(jiàn)對(duì)字符串進(jìn)行索引訪問(wèn)的是它的字節(jié)而不是它的字符字符串只是一串字節(jié)。這意味著當(dāng)我們?cè)谧址写鎯?chǔ)一個(gè)字符值時(shí)我們存儲(chǔ)的是它的字節(jié)表示。讓我們看一個(gè)更受控的例子了解它是如何發(fā)生的。這是一個(gè)簡(jiǎn)單的程序它以三種不同的方式打印一個(gè)包含單個(gè)字符的字符串常量一次是普通字符串一次是僅 ASCII 引用的字符串一次是十六進(jìn)制的單個(gè)字節(jié)。為了避免任何混淆我們創(chuàng)建一個(gè)用反引號(hào)括起來(lái)的“原始字符串”這樣它只能包含字面文本。普通的字符串用雙引號(hào)括起來(lái)可以包含我們上面展示的轉(zhuǎn)義序列。Gofunc main() { const placeOfInterest ? fmt.Printf(plain string: ) fmt.Printf(%s, placeOfInterest) fmt.Printf(
) fmt.Printf(quoted string: ) fmt.Printf(%q, placeOfInterest) fmt.Printf(
) fmt.Printf(hex bytes: ) for i : 0; i len(placeOfInterest); i { fmt.Printf(%x , placeOfInterest[i]) } fmt.Printf(
) }輸出結(jié)果是plain string: ? quoted string: u2318 hex bytes: e2 8c 98這提醒我們Unicode 字符值 U2318即“興趣點(diǎn)”符號(hào) ?由字節(jié)e2 8c 98表示這些字節(jié)是十六進(jìn)制值 2318 的 UTF-8 編碼。這可能是顯而易見(jiàn)的也可能是微妙的取決于您對(duì) UTF-8 的熟悉程度但值得花點(diǎn)時(shí)間解釋一下字符串的 UTF-8 表示是如何創(chuàng)建的。簡(jiǎn)單的事實(shí)是它是在源代碼編寫(xiě)時(shí)創(chuàng)建的。Go 語(yǔ)言中的源代碼被定義為 UTF-8 文本不允許使用其他表示。這意味著當(dāng)我們?cè)谠创a中寫(xiě)入文本?用于創(chuàng)建程序的文本編輯器會(huì)將符號(hào) ? 的 UTF-8 編碼放入源代碼文本中。當(dāng)我們打印出十六進(jìn)制字節(jié)時(shí)我們只是傾倒了編輯器放入文件中的數(shù)據(jù)。簡(jiǎn)而言之Go 源代碼是 UTF-8因此字符串字面量的源代碼就是 UTF-8 文本。如果該字符串字面量不包含轉(zhuǎn)義序列原始字符串不能包含則構(gòu)建的字符串將完全包含引號(hào)之間的源代碼文本。因此根據(jù)定義和構(gòu)造原始字符串將始終包含其內(nèi)容的有效 UTF-8 表示。同樣除非它包含像上一節(jié)中那樣的破壞 UTF-8 的轉(zhuǎn)義序列否則常規(guī)字符串字面量也始終包含有效的 UTF-8。有些人認(rèn)為 Go 字符串總是 UTF-8但它們不是只有字符串字面量是 UTF-8。正如我們?cè)谏弦还?jié)中所示字符串值可以包含任意字節(jié)正如我們?cè)诒竟?jié)中所示字符串字面量只要它們沒(méi)有字節(jié)級(jí)轉(zhuǎn)義總是包含 UTF-8 文本。總而言之字符串可以包含任意字節(jié)但當(dāng)從字符串字面量構(gòu)建時(shí)這些字節(jié)幾乎總是是 UTF-8。碼點(diǎn)、字符與符文到目前為止我們一直非常小心地使用“字節(jié)”和“字符”這兩個(gè)詞。這部分是因?yàn)樽址鎯?chǔ)的是字節(jié)部分是因?yàn)椤白址钡母拍钣悬c(diǎn)難以定義。Unicode 標(biāo)準(zhǔn)使用術(shù)語(yǔ)“碼點(diǎn)”code point來(lái)指代由單個(gè)值表示的項(xiàng)。碼點(diǎn) U2318十六進(jìn)制值為 2318表示符號(hào) ?。有關(guān)該碼點(diǎn)的更多信息請(qǐng)參閱其 Unicode 頁(yè)面。舉一個(gè)更通俗的例子Unicode 碼點(diǎn) U0061 是小寫(xiě)拉丁字母 Aa。但是小寫(xiě)重音字母 Aà 呢這是一個(gè)字符它也是一個(gè)碼點(diǎn)U00E0但它還有其他表示。例如我們可以使用“組合”重音碼點(diǎn) U0300并將其附加到小寫(xiě)字母 aU0061上從而創(chuàng)建相同的字符 à。通常一個(gè)字符可能由許多不同的碼點(diǎn)序列表示因此也可能由不同的 UTF-8 字節(jié)序列表示。因此計(jì)算中的字符概念是模棱兩可的或者至少是令人困惑的所以我們謹(jǐn)慎使用它。為了使事情可靠存在規(guī)范化技術(shù)可以保證給定的字符始終由相同的碼點(diǎn)表示但這個(gè)主題現(xiàn)在讓我們偏離了話題太遠(yuǎn)。后續(xù)的博文將解釋 Go 庫(kù)如何處理規(guī)范化。“碼點(diǎn)”這個(gè)詞有點(diǎn)拗口所以 Go 引入了一個(gè)更短的術(shù)語(yǔ)來(lái)表示這個(gè)概念符文rune。這個(gè)術(shù)語(yǔ)出現(xiàn)在庫(kù)和源代碼中其含義與“碼點(diǎn)”完全相同但有一個(gè)有趣的補(bǔ)充。Go 語(yǔ)言將rune這個(gè)詞定義為int32類型的別名因此程序可以清楚地表明一個(gè)整數(shù)值表示一個(gè)碼點(diǎn)。此外您可能認(rèn)為的字符常量在 Go 中被稱為符文常量。表達(dá)式?的類型和值是rune整數(shù)值為0x2318??偨Y(jié)一下以下是幾個(gè)要點(diǎn)Go 源代碼始終是 UTF-8。字符串包含任意字節(jié)。字符串字面量如果沒(méi)有字節(jié)級(jí)轉(zhuǎn)義總是包含有效的 UTF-8 序列。這些序列代表 Unicode 碼點(diǎn)稱為符文。Go 不保證字符串中的字符經(jīng)過(guò)規(guī)范化。Range 循環(huán)除了 Go 源代碼是 UTF-8 這一公理細(xì)節(jié)之外Go 對(duì) UTF-8 的特殊處理方式只有一種那就是在對(duì)字符串使用for range循環(huán)時(shí)。我們已經(jīng)看到了常規(guī)for循環(huán)會(huì)發(fā)生什么。相比之下for range循環(huán)在每次迭代時(shí)解碼一個(gè) UTF-8 編碼的符文。每次循環(huán)時(shí)循環(huán)的索引是當(dāng)前符文的起始位置以字節(jié)為單位碼點(diǎn)是其值。這是一個(gè)例子使用了另一個(gè)方便的Printf格式%#U它顯示了碼點(diǎn)的 Unicode 值及其打印表示Goconst nihongo 日本語(yǔ) for index, runeValue : range nihongo { fmt.Printf(%#U starts at byte position %d
, runeValue, index) }輸出顯示了每個(gè)碼點(diǎn)如何占用多個(gè)字節(jié)U65E5 日 starts at byte position 0 U672C 本 starts at byte position 3 U8A9E 語(yǔ) starts at byte position 6標(biāo)準(zhǔn)庫(kù)Go 的標(biāo)準(zhǔn)庫(kù)為解釋 UTF-8 文本提供了強(qiáng)大的支持。如果for range循環(huán)不足以滿足您的需求那么您需要的設(shè)施很可能由庫(kù)中的包提供。最重要的此類包是unicode/utf8它包含用于驗(yàn)證、拆解和重新組裝 UTF-8 字符串的輔助例程。這是一個(gè)與上述for range示例等效的程序但使用該包中的DecodeRuneInString函數(shù)來(lái)完成工作。該函數(shù)的返回值是符文及其在 UTF-8 編碼字節(jié)中的寬度。Goconst nihongo 日本語(yǔ) for i, w : 0, 0; i len(nihongo); i w { runeValue, width : utf8.DecodeRuneInString(nihongo[i:]) fmt.Printf(%#U starts at byte position %d
, runeValue, i) w width }運(yùn)行它您會(huì)看到它執(zhí)行相同的功能。for range循環(huán)和DecodeRuneInString被定義為產(chǎn)生完全相同的迭代序列。查閱unicode/utf8包的文檔以查看它還提供了哪些其他功能。結(jié)論回答開(kāi)頭提出的問(wèn)題字符串由字節(jié)構(gòu)成因此對(duì)其進(jìn)行索引會(huì)得到字節(jié)而不是字符。字符串甚至可能不包含字符。事實(shí)上“字符”的定義是模糊的試圖通過(guò)定義字符串由字符組成來(lái)解決這種模糊性將是一個(gè)錯(cuò)誤。關(guān)于 Unicode、UTF-8 和多語(yǔ)言文本處理的世界還有很多要說(shuō)的但這可以留待另一篇文章。現(xiàn)在我們希望您對(duì) Go 字符串的行為有了更好的理解并且盡管它們可能包含任意字節(jié)但 UTF-8 是其設(shè)計(jì)的核心部分。原文https://go.dev/blog/strings