
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
String types(文字列型)
文字列型は、文字列値の集合を表します。文字列値は、(空の可能性がある)バイトの列です。バイト数は文字列の長さと呼ばれ、決して負になりません。文字列は不変です。一度作成されると、文字列の内容を変更することは不可能です。事前宣言された文字列型はstringです。これは定義された型です。
文字列sの長さは、組み込み関数lenを使用して調べることができます。文字列が定数の場合、長さはコンパイル時定数です。文字列のバイトは、0からlen(s)-1までの整数インデックスでアクセスできます。そのような要素のアドレスを取得することは不正です。s[i]が文字列のi番目のバイトである場合、&s[i]は無効です。
解説
文字列型とは何か?
文字列型(string) は、テキストデータを扱うための型です。文字列はバイトの列として内部的に保存されます。
たとえ話: 文字列は「本の1ページ」のようなもので、一度印刷されたら内容を書き換えられません(不変性)。新しい内容にするには、新しいページを作る必要があります。
package main
import "fmt"
func main() {
// 文字列の基本
var message string = "こんにちは"
var name string = "太郎"
fmt.Println(message, name)
// 空文字列
var empty string = ""
fmt.Printf("空文字列: %q\n", empty) // ""
}
1. 文字列の基本的な特徴
バイトの列
文字列は内部的にバイトの配列として保存されます。
package main
import "fmt"
func main() {
str := "Hello"
// 文字列は[]byte(バイトスライス)に変換できる
bytes := []byte(str)
fmt.Printf("文字列: %s\n", str)
fmt.Printf("バイト: %v\n", bytes) // [72 101 108 108 111]
fmt.Printf("16進数: % X\n", bytes) // 48 65 6C 6C 6F
}
長さの取得
package main
import "fmt"
func main() {
// len()は文字列の「バイト数」を返す
str1 := "Hello"
fmt.Println(len(str1)) // 5バイト
// UTF-8では、日本語は1文字3バイト
str2 := "こんにちは"
fmt.Println(len(str2)) // 15バイト(5文字 × 3バイト)
// 空文字列
str3 := ""
fmt.Println(len(str3)) // 0
}
文字数とバイト数の違い
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Hello, 世界!"
// バイト数
byteCount := len(str)
fmt.Println("バイト数:", byteCount) // 14
// 文字数(ルーン数)
runeCount := utf8.RuneCountInString(str)
fmt.Println("文字数:", runeCount) // 9
}
2. 不変性(Immutability)
文字列は一度作成したら変更できません。これが文字列の最も重要な特徴です。
文字列は変更できない
package main
import "fmt"
func main() {
str := "Hello"
// これはエラー!
// str[0] = 'h' // cannot assign to str[0]
// 新しい文字列を作る必要がある
newStr := "h" + str[1:]
fmt.Println(newStr) // hello
// 元の文字列は変わらない
fmt.Println(str) // Hello
}
文字列の連結は新しい文字列を作る
package main
import "fmt"
func main() {
str1 := "Hello"
str2 := "World"
// 連結は新しい文字列を作成する
str3 := str1 + " " + str2
fmt.Println(str3) // Hello World
// str1とstr2は変わらない
fmt.Println(str1, str2) // Hello World
}
なぜ不変性が重要か?
package main
import "fmt"
func main() {
original := "Hello"
// 別の変数に代入
copy := original
// copyを「変更」(実際は新しい文字列を作成)
copy = "Goodbye"
// originalは影響を受けない
fmt.Println("original:", original) // Hello
fmt.Println("copy:", copy) // Goodbye
}
利点:
- スレッドセーフ: 複数のゴルーチンで安全に共有できる
- 効率的: 同じ内容の文字列は1つだけメモリに保存される(場合による)
- 予測可能: 予期しない変更が起きない
3. インデックスアクセス
文字列の各バイトには、インデックスでアクセスできます。
バイト単位のアクセス
package main
import "fmt"
func main() {
str := "Hello"
// 0から始まるインデックス
fmt.Printf("str[0] = %c (バイト値: %d)\n", str[0], str[0]) // H (72)
fmt.Printf("str[1] = %c (バイト値: %d)\n", str[1], str[1]) // e (101)
fmt.Printf("str[4] = %c (バイト値: %d)\n", str[4], str[4]) // o (111)
// 範囲外アクセスはパニック
// fmt.Println(str[10]) // panic: index out of range
}
スライス操作
package main
import "fmt"
func main() {
str := "Hello, World!"
// 部分文字列の取得
sub1 := str[0:5] // "Hello"
sub2 := str[7:12] // "World"
sub3 := str[7:] // "World!" (最後まで)
sub4 := str[:5] // "Hello" (最初から)
fmt.Println(sub1, sub2, sub3, sub4)
}
日本語でのインデックスアクセス(注意が必要)
package main
import "fmt"
func main() {
str := "こんにちは"
// バイト単位でアクセス(推奨されない)
fmt.Printf("%c\n", str[0]) // Ã (文字化け)
// ルーン(文字)単位でアクセス(推奨)
runes := []rune(str)
fmt.Printf("%c\n", runes[0]) // こ
fmt.Printf("%c\n", runes[1]) // ん
fmt.Printf("%c\n", runes[2]) // に
}
4. アドレスの取得不可
文字列の要素のアドレスは取得できません。
package main
func main() {
str := "Hello"
// これはエラー!
// ptr := &str[0] // cannot take the address of str[0]
// バイトスライスに変換すればアドレス取得可能
bytes := []byte(str)
ptr := &bytes[0] // OK
_ = ptr
}
理由: 文字列は不変なので、内部表現へのポインタを渡すと、予期しない変更を招く可能性があるため。
5. 文字列の走査
バイト単位の走査(ASCII専用)
package main
import "fmt"
func main() {
str := "Hello"
// 方法1: for-range
for i, b := range []byte(str) {
fmt.Printf("%d: %c (バイト値: %d)\n", i, b, b)
}
// 方法2: 普通のforループ
for i := 0; i < len(str); i++ {
fmt.Printf("%d: %c\n", i, str[i])
}
}
ルーン(文字)単位の走査(推奨)
package main
import "fmt"
func main() {
str := "Hello, 世界!"
// for-rangeは自動的にルーン単位で走査
for i, r := range str {
fmt.Printf("%d: %c (U+%04X)\n", i, r, r)
}
// 出力:
// 0: H (U+0048)
// 1: e (U+0065)
// 2: l (U+006C)
// 3: l (U+006C)
// 4: o (U+006F)
// 5: , (U+002C)
// 6: (U+0020)
// 7: 世 (U+4E16)
// 10: 界 (U+754C) ← インデックスが10(バイト単位)
// 13: ! (U+0021)
}
6. 文字列の操作
連結
package main
import (
"fmt"
"strings"
)
func main() {
// + 演算子
str1 := "Hello" + " " + "World"
fmt.Println(str1) // Hello World
// strings.Join
parts := []string{"Go", "is", "awesome"}
str2 := strings.Join(parts, " ")
fmt.Println(str2) // Go is awesome
// strings.Builder(効率的)
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" ")
builder.WriteString("World")
str3 := builder.String()
fmt.Println(str3) // Hello World
}
検索・置換
package main
import (
"fmt"
"strings"
)
func main() {
str := "Hello, World!"
// 含まれているか
fmt.Println(strings.Contains(str, "World")) // true
fmt.Println(strings.Contains(str, "Go")) // false
// 先頭・末尾のチェック
fmt.Println(strings.HasPrefix(str, "Hello")) // true
fmt.Println(strings.HasSuffix(str, "!")) // true
// 位置を検索
fmt.Println(strings.Index(str, "World")) // 7
fmt.Println(strings.Index(str, "Go")) // -1 (見つからない)
// 置換
newStr := strings.Replace(str, "World", "Go", 1)
fmt.Println(newStr) // Hello, Go!
// すべて置換
newStr2 := strings.ReplaceAll("aaa", "a", "b")
fmt.Println(newStr2) // bbb
}
分割・結合
package main
import (
"fmt"
"strings"
)
func main() {
// 分割
str := "apple,banana,cherry"
fruits := strings.Split(str, ",")
fmt.Println(fruits) // [apple banana cherry]
// 空白で分割
str2 := "Go is awesome"
words := strings.Fields(str2)
fmt.Println(words) // [Go is awesome]
// 結合
joined := strings.Join(fruits, " and ")
fmt.Println(joined) // apple and banana and cherry
}
トリミング
package main
import (
"fmt"
"strings"
)
func main() {
str := " Hello, World! "
// 両端の空白を削除
trimmed := strings.TrimSpace(str)
fmt.Printf("[%s]\n", trimmed) // [Hello, World!]
// 特定の文字を削除
str2 := "!!!Hello!!!"
trimmed2 := strings.Trim(str2, "!")
fmt.Println(trimmed2) // Hello
// 先頭だけ
trimmed3 := strings.TrimPrefix("Dr. Smith", "Dr. ")
fmt.Println(trimmed3) // Smith
}
7. 文字列と他の型の変換
数値との相互変換
package main
import (
"fmt"
"strconv"
)
func main() {
// 文字列 → 整数
str1 := "123"
i, err := strconv.Atoi(str1)
if err == nil {
fmt.Println(i) // 123
}
// 整数 → 文字列
num := 456
str2 := strconv.Itoa(num)
fmt.Println(str2) // "456"
// 文字列 → 浮動小数点
str3 := "3.14"
f, err := strconv.ParseFloat(str3, 64)
if err == nil {
fmt.Println(f) // 3.14
}
// 浮動小数点 → 文字列
num2 := 3.14159
str4 := strconv.FormatFloat(num2, 'f', 2, 64)
fmt.Println(str4) // "3.14"
}
バイトスライスとの相互変換
package main
import "fmt"
func main() {
// 文字列 → []byte
str := "Hello"
bytes := []byte(str)
fmt.Printf("% X\n", bytes) // 48 65 6C 6C 6F
// []byte → 文字列
bytes2 := []byte{72, 101, 108, 108, 111}
str2 := string(bytes2)
fmt.Println(str2) // Hello
}
ルーンスライスとの相互変換
package main
import "fmt"
func main() {
// 文字列 → []rune
str := "こんにちは"
runes := []rune(str)
fmt.Println(len(runes)) // 5 (文字数)
// 個別にアクセス
for i, r := range runes {
fmt.Printf("%d: %c\n", i, r)
}
// []rune → 文字列
runes2 := []rune{'H', 'e', 'l', 'l', 'o'}
str2 := string(runes2)
fmt.Println(str2) // Hello
}
8. 実用例
例1: バリデーション
package main
import (
"fmt"
"strings"
"unicode"
)
func IsValidEmail(email string) bool {
return strings.Contains(email, "@") && strings.Contains(email, ".")
}
func IsAlphanumeric(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
return false
}
}
return true
}
func main() {
email := "user@example.com"
fmt.Println("有効なメール?", IsValidEmail(email)) // true
username := "user123"
fmt.Println("英数字のみ?", IsAlphanumeric(username)) // true
}
例2: 文字列の整形
package main
import (
"fmt"
"strings"
)
func FormatName(firstName, lastName string) string {
// 両端の空白を削除し、タイトルケースに
first := strings.TrimSpace(firstName)
last := strings.TrimSpace(lastName)
first = strings.Title(strings.ToLower(first))
last = strings.Title(strings.ToLower(last))
return first + " " + last
}
func main() {
name := FormatName(" TARO ", " yamada ")
fmt.Println(name) // Taro Yamada
}
例3: CSVパーサー(簡易版)
package main
import (
"fmt"
"strings"
)
func ParseCSVLine(line string) []string {
return strings.Split(line, ",")
}
func main() {
csv := "太郎,25,東京"
fields := ParseCSVLine(csv)
name := fields[0]
age := fields[1]
city := fields[2]
fmt.Printf("名前: %s, 年齢: %s, 都市: %s\n", name, age, city)
}
例4: テンプレート置換
package main
import (
"fmt"
"strings"
)
func ReplaceTemplate(template string, values map[string]string) string {
result := template
for key, value := range values {
placeholder := "{{" + key + "}}"
result = strings.ReplaceAll(result, placeholder, value)
}
return result
}
func main() {
template := "こんにちは、{{name}}さん。ようこそ{{city}}へ!"
values := map[string]string{
"name": "太郎",
"city": "東京",
}
message := ReplaceTemplate(template, values)
fmt.Println(message)
// こんにちは、太郎さん。ようこそ東京へ!
}
まとめ: 文字列型で覚えておくべきこと
基本的な特徴
- 型名:
string - 不変: 一度作成したら変更できない
- バイトの列: 内部的には[]byteとして保存
- 長さ:
len()でバイト数を取得 - インデックス: 0から
len(s)-1でアクセス可能
文字数とバイト数
str := "Hello, 世界!"
// バイト数
len(str) // 14
// 文字数
utf8.RuneCountInString(str) // 9
走査の方法
// バイト単位(ASCII専用)
for i := 0; i < len(str); i++ {
fmt.Println(str[i])
}
// ルーン(文字)単位(推奨)
for _, r := range str {
fmt.Printf("%c\n", r)
}
実用的なアドバイス
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func main() {
// 1. 文字列は不変
str := "Hello"
// str[0] = 'h' // エラー!
newStr := "h" + str[1:] // 新しい文字列を作る
// 2. 日本語はルーン単位で扱う
text := "こんにちは"
runes := []rune(text)
fmt.Println(len(runes)) // 5文字
// 3. 文字数を数える
count := utf8.RuneCountInString(text)
fmt.Println(count) // 5
// 4. 効率的な連結にはstrings.Builder
var builder strings.Builder
for i := 0; i < 100; i++ {
builder.WriteString("a")
}
result := builder.String()
fmt.Println(newStr, result)
}
文字列型は、テキスト処理の基礎です。不変性を理解し、適切なメソッドを使うことで、効率的で安全なコードが書けます!
おわりに
本日は、Go言語の言語仕様について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント