Programming language/Golang

[Go] ch15 문자열

chaenii 2022. 1. 18. 22:28

문자열은 문자열의 집합이다. 문자열의 타입명은 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가지가 있다.

  1. 인덱스를 사용한 바이트 단위 순회
  2. []rune으로 타입 변환 후 한 글자씩 순회
  3. 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 타입이 가리키는 문자열의 일부만 변경할 수 없다.

  1. 문자열 전체를 변경하는 것은 가능하다.
    str의 Data 포인터 값을 "How are you?" 문자열이 있는 메모리 주소로 Data 포인터 값을 변경하고,
    Len 값도 문자열 길이에 맞게 변경한다.
  2. 문자열은 불변이기 때문에 문자열의 일부는 바꿀 수 없다.
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