goの基本09 - Go言語の文字列型

文字列型は、最新のプログラミング言語で最も一般的に使用されるデータ型の 1 つです。Go 言語の祖先の 1 つである C 言語では、文字列型は明示的に定義されていませんが、文字列リテラル定数または '\0' で終わる文字型 (char) 配列として表されます。

#define GOAUTHERS "Robert Griesemer, Rob Pike, and Ken Thompson"
const char * s = "hello world"
char s[] = "hello gopher"


これにより、C プログラマが文字列を使用するときに次のような問題が発生します。

● タイプの安全性が低い。

● 文字列操作では、常に末尾 '\0' を考慮する必要があります。

● 文字列データは可変です (主に文字配列の形式で定義された文字列型を指します)。

● 文字列の長さの取得にはコストがかかります (O(n) 時間の計算量)。

● 非 ASCII 文字 (漢字など) の組み込み処理はありません。

Go 言語は、C 言語のこの「欠陥」を修正し、文字列型を組み込み、文字列の抽象化を統一しました。

Go言語の文字列型

Go 言語では、コード内に出現する文字列定数、文字列変数、または文字列リテラルのいずれであっても、その型は一律に string に設定されます。

const (
	s = "string constant"
)
func main() {
    
    
	var s1 string = "string variable"
	fmt.Printf("%T\n", s) // string
	fmt.Printf("%T\n", s1) // string
	fmt.Printf("%T\n", "temporary string literal") // string
}

Go の文字列型の設計は、C 言語の文字列設計の経験と教訓を十分に活用し、他の主流言語の文字列型設計のベスト プラクティスを組み合わせたもので、最終的な文字列型は次の機能特性を備えています

(1) 文字列型データは不変です

文字列型識別子が宣言されると、それが定数であっても変数であっても、その識別子によって参照されるデータはプログラムのライフサイクル全体を通じて変更できません。文字列データを変更して、どのような結果が得られるかを見てみましょう。

まず最初の方法を見てみましょう。

func main() {
    
    
// 原始字符串
	var s string = "hello"
	fmt.Println("original string:", s)
	// 切片化后试图改变原字符串
	sl := []byte(s)
	sl[0] = 't'
	fmt.Println("slice:", string(sl))
	fmt.Println("after reslice, the original string is:", 	string(s))
}

このプログラムを実行した結果は次のようになります。


original string: hello
slice: tello
after reslice, the original string is: hello

上の例では、文字列をスライスに変換し、スライスを通じてその内容を変更しようとしましたが、結果は逆効果でした。文字列をスライスした後、Go コンパイラは文字列の基になるストレージを共有するのではなく、スライス変数の基になるストレージを再割り当てするため、スライスへの変更は元の文字列のデータには影響しません。

より「暴力的な」手段で文字列データを「攻撃」してみましょう。

func main() {
    
    
// 原始string
var s string = "hello"
fmt.Println("original string:", s)
// 试图通过unsafe指针改变原始string
modifyString(&s)
fmt.Println(s)

}



func modifyString(s *string) {
    
    
	// 取出第一个8字节的值
	p := (*uintptr)(unsafe.Pointer(s))
	// 获取底层数组的地址
	var array *[5]byte = (*[5]byte)(unsafe.Pointer(*p))
	var len *int = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Sizeof((*uintptr)(nil))))
	for i := 0; i < (*len); i++ {
    
    
		fmt.Printf("%p => %c\n", &((*array)[i]), (*array)[i])
		p1 := &((*array)[i])
		v := (*p1)
		(*p1) = v + 1 //try to change the character
	}
}

安全でないポインタを使用して、実行時に文字列の内部表現構造内のデータ ストレージ ブロックのアドレスをポイントし (詳細については、この記事で後述する説明を参照してください)、その後、ポインタを介してそのメモリに格納されているデータを変更しようとします。 。

このプログラムを実行すると、次の結果が得られます。

original string: hello
0x10d1b9d => h
unexpected fault address 0x10d1b9d
fatal error: fault
[signal SIGBUS: bus error code=0x2 addr=0x10d1b9d pc=0x109b079]

string の基になるデータ ストレージ領域では読み取り専用操作のみを実行できることがわかります。その領域内のデータを変更しようとすると、SIGBUS ランタイム エラーが発生し、「改ざん攻撃」が発生します。文字列データは再び失敗します。

2) ゼロ値が利用可能

Go 文字列型は、「ゼロ値が利用可能」という哲学をサポートしています。Go 文字列では、C 言語のように終了文字「\0」を考慮する必要がないため、ゼロ値は "" で、長さは 0 です。

var s string
fmt.Println(s) // s = ""
fmt.Println(len(s)) // 0


3) 長さを求める時間計算量は O(1) レベル

Go の文字列型データは不変であるため、一度初期値を取得すると、データ部分もその長さも変更されません。Go は、実行時にこの長さを文字列型の内部表現構造のフィールドとして保存します (後述)。このように文字列の長さを取得する操作、つまり len(s) は、ランタイムに格納されている長さの値を実際に読み取るため、非常に低コストの O(1) 操作です。

4) +/+= 演算子による文字列連結のサポート

開発者にとって、+/+= 演算子による文字列の連結は最適な文字列連結操作です。Go 言語はこの操作をサポートしています。

s := "Rob Pike, "
s = s + "Robert Griesemer, "
s += " Ken Thompson"
fmt.Println(s) // Rob Pike, Robert Griesemer, Ken Thompson

5) さまざまな比較演算子をサポート: ==、!=、>=、<=、>、<

func main() {
    
    
	// ==
	s1 := "世界和平"
	s2 := "世界" + "和平"
	fmt.Println(s1 == s2) // true
	// !=
	s1 = "Go"
	s2 = "C"
	fmt.Println(s1 != s2) // true
	// < 和 <=
	s1 = "12345"
	s2 = "23456"
	fmt.Println(s1 < s2) // true
	fmt.Println(s1 <= s2) // true
	// > 和 >=
	s1 = "12345"
	s2 = "123"
	fmt.Println(s1 > s2) // true
	fmt.Println(s1 >= s2) // true
}

Go 文字列は不変であるため、2 つの文字列の長さが異なる場合は、特定の文字列データを比較せずに 2 つの文字列が異なると結論付けることができます。長さが同じ場合は、データ ポインタが基礎となるストレージ データの同じブロックを指しているかどうかをさらに判断する必要があります。それらが同じであれば、2 つの文字列は同等です。異なる場合は、実際のデータ内容をさらに比較する必要があります。

6) 非 ASCII 文字のネイティブ サポートを提供する

Go 言語ソース ファイルでデフォルトで使用される Unicode 文字セット。Unicode 文字セットは現在市場で最も人気のある文字セットであり、主流の非 ASCII 文字 (漢字を含む) のほぼすべてをカバーしています。Go 文字列の各文字は Unicode 文字であり、これらの Unicode 文字は UTF-8 エンコード形式でメモリに保存されます。

例を見てみましょう:

func main() {
    
    
	// 中文字符 Unicode码点 UTF8编码
	// 中 U+4E2D E4B8AD
	// 国 U+56FD E59BBD
	// 欢 U+6B22 E6ACA2
	// 迎 U+8FCE E8BF8E
	// 您 U+60A8 E682A8
	s := "中国欢迎您"
	rs := []rune(s)
	sl := []byte(s)
	for i, v := range rs {
    
    
	var utf8Bytes []byte
	for j := i * 3; j < (i+1)*3; j++ {
    
    
		utf8Bytes = append(utf8Bytes, sl[j])
	}
	fmt.Printf("%s => %X => %X\n", string(v), v, utf8Bytes)
}
}

文字列変数 s に格納されているテキストは、「China Welcomes You」という 5 つの漢字 (非 ASCII 文字カテゴリ) であることがわかります。各漢字に対応する Unicode コード ポイントがここに出力されます (コード ポイント、「中国歓迎」の 2 番目の部分を参照)。出力結果).列)、1 つのルーンは 1 つのコード ポイントに対応します。UTF-8 エンコーディングは、Unicode コード ポイントの文字エンコーディング形式であり、最も一般的に使用されるエンコーディング形式であり、Go のデフォルトの文字エンコーディング形式でもあります。UTF-16 などの他の文字エンコード形式を使用して Unicode コード ポイントをマップすることもできます。

UTF-8 では、ほとんどの中国語文字は 3 バイトを使用して表現されます。[]byte(s) の変換により、 s の基になるストレージの「コピー」を取得できるようになり、それによって各漢字に対応する UTF-8 でエンコードされたバイトが取得されます (出力結果の 3 列目を参照)。

=> 4E2D => E4B8AD
国 => 56FD => E59BBD
欢 => 6B22 => E6ACA2
迎 => 8FCE => E8BF8E
您 => 60A8 => E682A8

7) 複数行の文字列のネイティブサポート

Go 言語は、バッククォートを使用して「表示されているものがそのまま得られる」複数行の文字列を構築するメソッドを直接提供します。

const s = `好雨知时节,当春乃发生。
			随风潜入夜,润物细无声。
			野径云俱黑,江船火独明。
			晓看红湿处,花重锦官城。`
func main() {
    
    
	fmt.Println(s)
}

操作結果:

好雨知时节,当春乃发生。
随风潜入夜,润物细无声。
野径云俱黑,江船火独明。
晓看红湿处,花重锦官城。

おすすめ

転載: blog.csdn.net/hai411741962/article/details/132740171