문자열은 문자열의 집합이다. 문자열의 타입명은 string이다.
문자열 큰따옴표나 백퀴트로 묶어서 표시한다.
따옴표("") : 큰따옴표로 묶으면 특수 문자가 동작한다.
백쿼트(~표시 아래) : 백쿼트로 묶으면 특수 문자가 동작하지 않는다. 여러 줄에 걸쳐서 문자열 출력이 가능하다.
str1 := "Hello \t 'world' \n"
fmt.Println(str1)
str2 := `Hello \t 'world' \n Testing back qoute`
fmt.Println(str2)
-----------------------------------------------
Hello 'world'
Hello \t 'world' \n Testing back qoute
rune 타입으로 한 문자 담기
문자 하나를 표현하는데 rune 타입을 사용한다.
Go는 UTF-8 문자코드를 표준 문자코드로 사용하는데 UTF-8은 1~3바이트 크기이기 때문에 UTF-8문자값을 가지려면 3바이트가 필요하다.
하지만, Go 언어 기본 타입에서 3바이트 정수 타입은 제공되지 않기 때문에 rune 타입은 4바이트 정수 타입인 int32의 별칭 타입이다.
문자 한 개는 작은따옴표로 묶어서 표시한다.
var char rune = '한'
fmt.Printf("%T\n", char) // char 타입 출력
fmt.Println(char) // char값 출력
fmt.Printf("%c\n", char) // 문자 출력
----------------------------------------
int32
54620
한
len()으로 문자열 크기 알아내기
len() 내장 함수를 이용해 문자열 크기를 알 수 있다.
이때 크기는 문자 수가 아니라 문자열이 차지하는 메모리 크기이다.
str1 := "가나다라마"
str2 := "abcde"
fmt.Println(len(str1)) // 15
fmt.Println(len(str2)) // 5
위의 예시에서 str1, str2 두 문자열 둘 다 글자 수는 5개지만, 한글 문자열 크기는 15이다.
UTF-8에서 한글은 글자당 3바이트를 차지하기 때문에 총 3 x 5를 하면 15바이트가 나오게 된다.
UTF-8에서 영문자는 글자당 1바이트라서 총 5바이트가 된다.
[]rune 타입 변환으로 글자 수 알아내기
string 타입, rune 슬라이스 타입은 []rune 타입은 상호 변환이 가능하다.
슬라이스는 길이가 변할 수 있는 배열이다.
타입 변환을 할 경우 rune 배열의 각 요소에는 문자열의 각 글자가 대입된다.
이를 통해서 문자열의 글자 수를 알 수 있다.
string 타입은 연속된 바이트 메모리라면, []rune 타입은 글자들의 배열로 이루어져 있다.
str := "Hello 월드"
runes := []rune(str)
fmt.Println(str)
fmt.Println("len(str):", len(str))
fmt.Println(runes)
fmt.Println("len(runes):", len(runes))
--------------------------------------
Hello 월드
len(str): 12
[72 101 108 108 111 32 50900 46300]
len(runes): 8
문자열 순회
문자열 순회하는 방법은 총 3가지가 있다.
- 인덱스를 사용한 바이트 단위 순회
- []rune으로 타입 변환 후 한 글자씩 순회
- range 키워드를 이용한 한 글자씩 순회
인덱스를 사용한 바이트 단위 순회
len은 문자열의 글자 개수가 아닌 바이트 크기를 반환했다.
영문과 공백 문자는 제대로 출력했지만, 한글은 깨졌다.
str[i]와 같이 인덱스로 접근하면 요소의 타입은 uint8 즉 byte이다.
그래서 1 바이트 크기인 영문자는 잘 표시되는데 3바이트 크기인 한글은 깨져서 출력된 것이다.
str := "Hello 월드"
for i := 0; i < len(str); i++ {
fmt.Printf("타입명: %T 값: %d 문자값: %c\n", str[i], str[i], str[i])
}
--------------------------------------------------------------------
타입명: uint8 값: 72 문자값: H
타입명: uint8 값: 101 문자값: e
타입명: uint8 값: 108 문자값: l
타입명: uint8 값: 108 문자값: l
타입명: uint8 값: 111 문자값: o
타입명: uint8 값: 32 문자값:
타입명: uint8 값: 236 문자값: ì
타입명: uint8 값: 155 문자값:
입명: uint8 값: 148 문자값:
타입명: uint8 값: 235 문자값: ë
타입명: uint8 값: 147 문자값:
타입명: uint8 값: 156 문자값:
[]rune으로 타입 변환 후 한 글자씩 순회하기
str 문자열을 []rune으로 타입 변환한 다음에 runes 변수에 대입했다.
len()은 문자열 글자 개수를 반환한다. 변환한 runes 배열은 for문을 이용해면 각 글자를 돌면서 순회한다.
str := "Hello 월드"
runes := []rune(str)
for i := 0; i < len(runes); i++ {
fmt.Printf("타입명: %T 값: %d 문자값: %c\n", runes[i], runes[i], runes[i])
}
--------------------------------------------------------------------------
타입명: int32 값: 72 문자값: H
타입명: int32 값: 101 문자값: e
타입명: int32 값: 108 문자값: l
타입명: int32 값: 108 문자값: l
타입명: int32 값: 111 문자값: o
타입명: int32 값: 32 문자값:
타입명: int32 값: 50900 문자값: 월
타입명: int32 값: 46300 문자값: 드
[]rune으로 변환하는 과정에서 별도의 배열을 할당하므로 불필요한 메모리를 사용하게 된다. range 키워드를 사용해 순회하면 이를 방지할 수 있다.
range 키워드를 이용해 한 글자씩 순회하기
range를 이용하면 추가 메모리 할당 없이 문자열을 한 글자씩 순회할 수 있어서 불필요한 메모리 낭비를 없앨 수 있다.
str := "Hello 월드"
for _, v := range str {
fmt.Printf("타입명: %T 값: %d 문자값: %c\n", v, v, v)
}
--------------------------------------------------------------------------
타입명: int32 값: 72 문자값: H
타입명: int32 값: 101 문자값: e
타입명: int32 값: 108 문자값: l
타입명: int32 값: 108 문자값: l
타입명: int32 값: 111 문자값: o
타입명: int32 값: 32 문자값:
타입명: int32 값: 50900 문자값: 월
타입명: int32 값: 46300 문자값: 드
문자열 합치기
문자열 간의 연산을 알아보자
문자열은 +와 +=의 연산을 사용해서 문자열을 이을 수 있다.
문자열 비교하기
연산자 ==, !=을 이용해서 문자열이 같은지 같지 않은지를 비교한다.
문자열 대소 비교하기 : >, <, <=, >=
>, <, <=, >=을 이용해 문자열 간 대소를 비교한다.
문자열 대소 비교는 첫 글자부터 하나씩 값을 비교해서 유니코드 값이 다를 경우 대소를 반환한다.
// 문자열 합치기
str1 := "Hello"
str2 := "world"
str3 := str1 + " " + str2
fmt.Println(str3)
// 문자열 비교하기
str4 := "world"
fmt.Println("str1 == str4 :", str1 == str4)
fmt.Println("str2 == str4 :", str2 == str4)
// 문자열 대소 비교하기
str5 := "aaa"
str6 := "aab"
fmt.Println("str5 < str6 :", str5 < str6)
Hello world
str1 == str4 : false
str2 == str4 : true
str5 < str6 : true
문자열 구조
string 타입은 Go언어에서 제공하는 내장 타입으로 그 내부 구현은 감춰져 있지만, reflect 패키지 안의 StringHeader 구조체를 통해서 내부 구현을 볼 수 있다.
type StringHeader struct{
Data uintptr
Len int
}
string 필드가 2개인 구조체이다. 첫번째 필드 Data는 문자열의 데이터가 있는 메모리 주소를 나타내는 포인터이다.
두 번째 필드 Len은 int 타입으로 문자열의 길이를 나타낸다.
string 변수끼리 대입하면 문자열 데이터를 복사하는 것이 아니라 각 필드 Data 포인터 값과 Len값이 복사된다. 문자열을 복사할 때 문자열 전체가 복사되어서 긴 문자열의 경우 메모리나 성능 문제가 생기지 않을까 걱정할 필요가 없다.
문자열은 불변이다
문자열은 불변(immutable)이다. 즉, string 타입이 가리키는 문자열의 일부만 변경할 수 없다.
- 문자열 전체를 변경하는 것은 가능하다.
str의 Data 포인터 값을 "How are you?" 문자열이 있는 메모리 주소로 Data 포인터 값을 변경하고,
Len 값도 문자열 길이에 맞게 변경한다. - 문자열은 불변이기 때문에 문자열의 일부는 바꿀 수 없다.
var str string = "Hello World"
str = "How are you?" // 1.전체 바꾸기는 가능
str[2] = 'a' // Error 일부 바꾸기는 불가능
'Programming language > Golang' 카테고리의 다른 글
[Go] Map 의 특정 key나 value 값으로 정렬하기 (0) | 2022.02.23 |
---|---|
[Go] ch16 패키지 (0) | 2022.01.18 |
[Go] ch14 포인터 (0) | 2022.01.15 |
[Go] ch13 구조체 (0) | 2022.01.15 |
[Go] 12 배열 (0) | 2022.01.15 |