자바스크립트를 활성화 해주세요

Tucker의 Go 언어 프로그래밍 15장 요약

 ·  ☕ 6 min read

Tucker의 Go 언어 프로그래밍스터디 요약 노트입니다.

15장. 문자열

문자열의 Go 언어에서의 특징만 확인해보자.

  1. backquote(`)으로 문자열을 감싸면 줄바꿈을 포함한 모든 문자열을 원형으로 작성할 수 있다.
  2. 기본적으로 UTF-8로 문자열이 인코딩된다.
  3. range로 각 문자를 rune 단위로 순회할 수 있다.
  4. 연산자(+=, ==, !=, <=)를 통해 문자열을 합치거나, 비교할 수 있다.
  5. 문자열의 내용은 불변이다.

여기서 UTF-8 인코딩에 관한 확인은 ch04에서 이미 실험한 바 있으므로 변환 과정은 생략하도록 하겠다.

문자열 순회하기

문자열을 순회하는 방법에는 세가지 방법이 존재한다.

  1. 문자열을 바로 배열 취급하여 byte 단위로 순회
  2. 문자열을 []rune으로 타입 변환하여 배열 순회
  3. rangerune 단위로 순회
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "fmt"

func main() {
	str := `backquote로 감싸진 string은
그대로 줄바꿈이 되지만 \n은 동작하지 않습니다.`

	fmt.Println(str)
	for i := 0; i < len(str); i++ {
		fmt.Printf("Type: %T	Value: %d	Char: %c\n",
			str[i], str[i], str[i])
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import "fmt"

func main() {
	str := `backquote로 감싸진 string은
그대로 줄바꿈이 되지만 \n은 동작하지 않습니다.`
	arr := []rune(str)

	fmt.Println(arr)
	for i := 0; i < len(arr); i++ {
		fmt.Printf("Type: %T	Value: %d	Char: %c\n",
			arr[i], arr[i], arr[i])
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "fmt"

func main() {
	str := `backquote로 감싸진 string은
그대로 줄바꿈이 되지만 \n은 동작하지 않습니다.`

	fmt.Println(str)
	for _, v := range str {
		fmt.Printf("Type: %T	Value: %d	Char: %c\n",
			v, v, v)
	}
}
$ ./go_string_traversal_index
backquote로 감싸진 string은
그대로 줄바꿈이 되지만 \n은 동작하지 않습니다.
Type: uint8	Value: 98	Char: b
Type: uint8	Value: 97	Char: a
Type: uint8	Value: 99	Char: c
Type: uint8	Value: 107	Char: k
Type: uint8	Value: 113	Char: q
Type: uint8	Value: 117	Char: u
Type: uint8	Value: 111	Char: o
Type: uint8	Value: 116	Char: t
Type: uint8	Value: 101	Char: e
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 161	Char: ¡
Type: uint8	Value: 156	Char: œ
Type: uint8	Value: 32	Char:
Type: uint8	Value: 234	Char: ê
Type: uint8	Value: 176	Char: °
Type: uint8	Value: 144	Char: 
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 139	Char: ‹
Type: uint8	Value: 184	Char: ¸
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 167	Char: §
Type: uint8	Value: 132	Char: „
Type: uint8	Value: 32	Char:
Type: uint8	Value: 115	Char: s
Type: uint8	Value: 116	Char: t
Type: uint8	Value: 114	Char: r
Type: uint8	Value: 105	Char: i
Type: uint8	Value: 110	Char: n
Type: uint8	Value: 103	Char: g
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 157	Char: 
Type: uint8	Value: 128	Char: €
Type: uint8	Value: 10	Char:

Type: uint8	Value: 234	Char: ê
Type: uint8	Value: 183	Char: ·
Type: uint8	Value: 184	Char: ¸
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 140	Char: Œ
Type: uint8	Value: 128	Char: €
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 161	Char: ¡
Type: uint8	Value: 156	Char: œ
Type: uint8	Value: 32	Char:
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 164	Char: ¤
Type: uint8	Value: 132	Char: „
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 176	Char: °
Type: uint8	Value: 148	Char: ”
Type: uint8	Value: 234	Char: ê
Type: uint8	Value: 191	Char: ¿
Type: uint8	Value: 136	Char: ˆ
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 157	Char: 
Type: uint8	Value: 180	Char: ´
Type: uint8	Value: 32	Char:
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 144	Char: 
Type: uint8	Value: 152	Char: ˜
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 167	Char: §
Type: uint8	Value: 128	Char: €
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 167	Char: §
Type: uint8	Value: 140	Char: Œ
Type: uint8	Value: 32	Char:
Type: uint8	Value: 92	Char: \
Type: uint8	Value: 110	Char: n
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 157	Char: 
Type: uint8	Value: 128	Char: €
Type: uint8	Value: 32	Char:
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 143	Char: 
Type: uint8	Value: 153	Char: ™
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 158	Char: ž
Type: uint8	Value: 145	Char: ‘
Type: uint8	Value: 237	Char: í
Type: uint8	Value: 149	Char: •
Type: uint8	Value: 152	Char: ˜
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 167	Char: §
Type: uint8	Value: 128	Char: €
Type: uint8	Value: 32	Char:
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 149	Char: •
Type: uint8	Value: 138	Char: Š
Type: uint8	Value: 236	Char: ì
Type: uint8	Value: 138	Char: Š
Type: uint8	Value: 181	Char: µ
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 139	Char: ‹
Type: uint8	Value: 136	Char: ˆ
Type: uint8	Value: 235	Char: ë
Type: uint8	Value: 139	Char: ‹
Type: uint8	Value: 164	Char: ¤
Type: uint8	Value: 46	Char: .

$ ./go_string_traversal_rune_array
[98 97 99 107 113 117 111 116 101 47196 32 44048 49912 51652 32 115 116 114 105 110 103 51008 10 44536 45824 47196 32 51460 48148 45000 51060 32 46104 51648 47564 32 92 110 51008 32 46041 51089 54616 51648 32 50506 49845 45768 45796 46]
Type: int32	Value: 98	Char: b
Type: int32	Value: 97	Char: a
Type: int32	Value: 99	Char: c
Type: int32	Value: 107	Char: k
Type: int32	Value: 113	Char: q
Type: int32	Value: 117	Char: u
Type: int32	Value: 111	Char: o
Type: int32	Value: 116	Char: t
Type: int32	Value: 101	Char: e
Type: int32	Value: 47196	Char: 로
Type: int32	Value: 32	Char:
Type: int32	Value: 44048	Char: 감
Type: int32	Value: 49912	Char: 싸
Type: int32	Value: 51652	Char: 진
Type: int32	Value: 32	Char:
Type: int32	Value: 115	Char: s
Type: int32	Value: 116	Char: t
Type: int32	Value: 114	Char: r
Type: int32	Value: 105	Char: i
Type: int32	Value: 110	Char: n
Type: int32	Value: 103	Char: g
Type: int32	Value: 51008	Char: 은
Type: int32	Value: 10	Char:

Type: int32	Value: 44536	Char: 그
Type: int32	Value: 45824	Char: 대
Type: int32	Value: 47196	Char: 로
Type: int32	Value: 32	Char:
Type: int32	Value: 51460	Char: 줄
Type: int32	Value: 48148	Char: 바
Type: int32	Value: 45000	Char: 꿈
Type: int32	Value: 51060	Char: 이
Type: int32	Value: 32	Char:
Type: int32	Value: 46104	Char: 되
Type: int32	Value: 51648	Char: 지
Type: int32	Value: 47564	Char: 만
Type: int32	Value: 32	Char:
Type: int32	Value: 92	Char: \
Type: int32	Value: 110	Char: n
Type: int32	Value: 51008	Char: 은
Type: int32	Value: 32	Char:
Type: int32	Value: 46041	Char: 동
Type: int32	Value: 51089	Char: 작
Type: int32	Value: 54616	Char: 하
Type: int32	Value: 51648	Char: 지
Type: int32	Value: 32	Char:
Type: int32	Value: 50506	Char: 않
Type: int32	Value: 49845	Char: 습
Type: int32	Value: 45768	Char: 니
Type: int32	Value: 45796	Char: 다
Type: int32	Value: 46	Char: .

$ ./go_string_traversal_range
backquote로 감싸진 string은
그대로 줄바꿈이 되지만 \n은 동작하지 않습니다.
Type: int32	Value: 98	Char: b
Type: int32	Value: 97	Char: a
Type: int32	Value: 99	Char: c
Type: int32	Value: 107	Char: k
Type: int32	Value: 113	Char: q
Type: int32	Value: 117	Char: u
Type: int32	Value: 111	Char: o
Type: int32	Value: 116	Char: t
Type: int32	Value: 101	Char: e
Type: int32	Value: 47196	Char: 로
Type: int32	Value: 32	Char:
Type: int32	Value: 44048	Char: 감
Type: int32	Value: 49912	Char: 싸
Type: int32	Value: 51652	Char: 진
Type: int32	Value: 32	Char:
Type: int32	Value: 115	Char: s
Type: int32	Value: 116	Char: t
Type: int32	Value: 114	Char: r
Type: int32	Value: 105	Char: i
Type: int32	Value: 110	Char: n
Type: int32	Value: 103	Char: g
Type: int32	Value: 51008	Char: 은
Type: int32	Value: 10	Char:

Type: int32	Value: 44536	Char: 그
Type: int32	Value: 45824	Char: 대
Type: int32	Value: 47196	Char: 로
Type: int32	Value: 32	Char:
Type: int32	Value: 51460	Char: 줄
Type: int32	Value: 48148	Char: 바
Type: int32	Value: 45000	Char: 꿈
Type: int32	Value: 51060	Char: 이
Type: int32	Value: 32	Char:
Type: int32	Value: 46104	Char: 되
Type: int32	Value: 51648	Char: 지
Type: int32	Value: 47564	Char: 만
Type: int32	Value: 32	Char:
Type: int32	Value: 92	Char: \
Type: int32	Value: 110	Char: n
Type: int32	Value: 51008	Char: 은
Type: int32	Value: 32	Char:
Type: int32	Value: 46041	Char: 동
Type: int32	Value: 51089	Char: 작
Type: int32	Value: 54616	Char: 하
Type: int32	Value: 51648	Char: 지
Type: int32	Value: 32	Char:
Type: int32	Value: 50506	Char: 않
Type: int32	Value: 49845	Char: 습
Type: int32	Value: 45768	Char: 니
Type: int32	Value: 45796	Char: 다
Type: int32	Value: 46	Char: .

문자열 연산자

+, +=연산자를 사용하여 문자열을 합칠 수 있다. == !=연산자로 두 문자열이 같은지, 다른지 확인할 수 있으며, >=, <=연산자로 두 문자열을 유니코드 값 기준으로 순서를 비교할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func main() {
	strHello := "hello"
	strWorld := "world"

	strHelloWorld := strHello
	strHelloWorld += strWorld

	strHelloExcl := strHello + " " + strWorld + "!"

	fmt.Println(strHelloWorld)
	fmt.Println(strHelloExcl)

	fmt.Printf("It is %v that \"%s\" is equal with \"%s\"\n",
		strHelloWorld == strHelloExcl, strHelloWorld, strHelloExcl)

	if strHelloWorld < strHelloExcl {
		fmt.Printf("%s comes before %s when strings are sorted in Unicode order\n",
			strHelloWorld, strHelloExcl)
	} else if strHelloWorld > strHelloExcl {
		fmt.Printf("%s comes after %s when strings are sorted in Unicode order\n",
			strHelloWorld, strHelloExcl)
	}
}
$ ./go_string_operators
helloworld
hello world!
It is false that "helloworld" is equal with "hello world!"
helloworld comes after hello world! when strings are sorted in Unicode order

문자열의 구현 방식, 불변성

string의 내부 구현은 실제 문자열 데이터(UTF-8로 인코딩된 []byte, 해당 배열을 가리키는 포인터)와 해당 배열의 길이를 나타내는 길이로 표현하고 있다.

그리고 StringHeader 구조체를 통해 해당 string의 내부를 엿볼수 있다.

문자열을 복사할 때는 메모리 관리를 효율적으로 하기 위해 해당 배열을 가리키는 포인터와 배열의 길이만 복사한다.

그리고 문자열의 데이터는 불변이기 때문에 임의로 코드에서 변경할 수 없다.

임의로 문자열의 데이터를 변경하려면 []byte와 같이 자료형을 변환하고 해당 slice에서 수정해야 한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	str := "Hello world"
	stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
	fmt.Println(stringHeader)

	slice := []byte(str)
	slice[2] = 'a'
	fmt.Printf("%s\n", slice)
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
	fmt.Println(sliceHeader)

	newStr := string(slice)
	fmt.Println(newStr)
	newStrHeader := (*reflect.StringHeader)(unsafe.Pointer(&newStr))
	fmt.Println(newStrHeader)
}
$ ./go_string_internals
&{4343664238 11}
Healo world
&{1374389641488 11 16}
Healo world
&{1374389641520 11}

실행 결과를 보고 예측할 수 있는 점은 아래와 같다.

  1. 초기 사용된 str의 값은 상수 값으로 하드코딩된 값으로 들어간다.
  2. 문자열을 slice로 동적 변환했으므로, 해당 변수는 힙에 할당된다.
  3. 변경된 slice에서 다시 문자열로 변환하면 불변성을 유지해야 하기 때문에 새로운 주소에 해당 내용을 복사한다.

저자 강의


JaeSang Yoo
글쓴이
JaeSang Yoo
The Programmer

목차