[Tucker의 Go 언어 프로그래밍] 15장 문자열

Coen·2023년 10월 12일
1

tucker-go

목록 보기
12/18
post-thumbnail
post-custom-banner

이 글은 골든래빗 《Tucker의 Go 언어 프로그래밍》의 15장 써머리입니다.

15장 문자열

15.1 문자열

  • string은 큰따옴표나 백쿼트(`)로 표시한다.
  • 백쿼트(`)로 묶으면 특수문자가 동작하지 않는다.
fmt.Println("Hello\t\tWorld") //Hello		World
fmt.Println(`Hello\t\tWorld`) //Hello\t\tWorld
  • 백쿼트는 여러줄을 표현할 수 있다.
fmt.Println("한줄\n내려진다")
fmt.Println(`한줄
내려진다`)

//출력결과는 둘 다 아래와 같다.

한줄
내려진다

15.1.1 UTF-8 문자코드

  • Golang은 UTF-8 문자코드를 표준 문자코드로 사용한다.
  • Golang 창시자 세명 중 두명이 만든 문자코드다.
  • UTF-16은 한 문자에 2바이트를 고정해서 사용하는데, UTF-8은 자주 사용되는 영문자, 숫자, 일부 특수 문자를 1바이트로 표현해 UTF-16에 비해 크기 절약이 가능.

15.1.2 rune 타입으로 한 문자 담기

  • 문자 하나를 표현하는데 rune 타입을 사용한다.
  • UTF-8은 한 글자가 1~3바이트인데, golang기본 타입에서 3바이트 정수 타입이 제공되지 않기 때문에 rune타입은 4바이트 정수 타입인 int32 타입의 별칭 타입이다.

type rune int32 라고 보면 된다!

  • 문자 한 개는 작은 따옴표로 묶어 표시한다.
var char rune = '한'

fmt.Printf("%T\n", char) //char 타입 출력: int32
fmt.Println(char)        //char 값 출력: 54620
fmt.Printf("%c\n", char) //문자 출력: 한

15.1.3 len()으로 문자열 크기 알아내기

  • len() 내장함수를 이용해 문자열의 크기(메모리의 크기)를 알 수 있다.
str1 := "가나다라마"
str2 := "abcde"

fmt.Println(len(str1)) //15
fmt.Println(len(str2)) //5

15.1.5 string 타입을 []byte로 타입변환할 수 있다.

  • string 타입과 []byte 타입은 상호 타입 변환이 가능하다.
  • 메모리는 1바이트 단위로 저장되기 때문에 모든 문자열은 1바이트 배열로 변환이 가능하다.

15.2 문자열 순회

  • 문자열을 순회하는 방법
  1. 인덱스를사용한 바이트 단위 순회
  2. []rune 으로 타입 변환 후 한 글자씩 순회
  3. range 키워드를 이용한 한 글자씩 순회

15.2.1 인덱스를 사용해 바이트 단위 순회하기

func Test_stringIterateByIndex(t *testing.T) {
	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 문자값: 
  • len()은 문자열 글자수가 아닌 바이트 크기를 반환했다.
  • 한글은 3바이트이기 때문에 깨지게 된다.

15.2.2 []rune으로 타입 변환 후 한 글자씩 순회하기

func Test_stringIterateByRuneSlice(t *testing.T) {
	str := "Hello 월드"
	arr := []rune(str)
	for i := 0; i < len(arr); i++ {
		fmt.Printf("타입:%T 값:%d 문자값: %c\n", arr[i], arr[i], arr[i])
	}
}

//출력값
타입:int32 값:72 문자값: H
타입:int32 값:101 문자값: e
타입:int32 값:108 문자값: l
타입:int32 값:108 문자값: l
타입:int32 값:111 문자값: o
타입:int32 값:32 문자값:  
타입:int32 값:50900 문자값: 월
타입:int32 값:46300 문자값: 드
  • 원하는대로 작동하지만 []rune으로 변환하면서 불필요한 메모리를 사용하게 된다.

15.2.3 range 키워드를 이용해 한 글자씩 순회하기

func Test_stringIterateByRange(t *testing.T) {
	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 문자값: 드
  • 한글이 포함된 문자열을 순환할때 가장 이상적으로 사용이 가능해보인다.

15.3 문자열 합치기

  • 문자열은 ++=연산을 사용해 문자열을 합칠 수 있다.

15.3.1 문자열 비교하기

  • 문자열은 == !=를 사용해 문자열의 값 비교가 가능하다.

15.3.2 문자열 대소 비교하기

  • > < >= <= 연산자를 이용해 문자열 대소비교가 가능하다.
  • 첫글자부터 하나씩 값을 비교해 유니코드 값이 다를 경우 대소를 반환한다.

15.4 문자열 구조

15.4.1 string 구조 알아보기

  • string 타입은 Go 언어가 제공하는 내장 타입으로 내부 구현은 감춰져 있다.
  • reflect 패키지 안의 StringHeader 구조체를 통해 내부 구현을 볼 수 있다.
type StringHeader struct {
	Data uintptr
    Len  int
}
  • Data는 문자열 데이터가 있는 메모리 주소를 나타내는 일종의 포인터.
  • Len은 문자열의 길이(byte)를 나타낸다.
func Test_reflectStringHeader(t *testing.T) {
	str := "Hello 월드"
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
	t.Log(strHeader.Data, strHeader.Len) //4309419814 12
}

15.5 문자열은 불변이다.

  • 불변(immutable)이라는 말은 string 타입이 가리키는 문자열의 일부만 변경할 수 없다는 뜻.

15.5.1 문자열 합산

  • 최초 할당, 문자열 변경, 문자열 합산 시 모두 주소값이 변경된다.
func Test_stringImmutable(t *testing.T) {
	str := "Hello 월드"
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
	t.Log(strHeader.Data, strHeader.Len)
	str = "Hello Go"
	t.Log(strHeader.Data, strHeader.Len)
	str += "!"
	t.Log(strHeader.Data, strHeader.Len)
}
  • string 합 연산을 빈번하게 하면 메모리가 낭비된다. string 합 연산을 여러번 사용하는 경우 strings 패키지의 Builder를 이용해 메모리를 세이브 할 수 있다.
func Test_stringsBuilder(t *testing.T) {
	var sb strings.Builder
	sb.WriteString("Hello")
	sb.WriteString(" Go")
	sb.WriteString(" World")
	fmt.Println(sb.String()) //Hello Go World
}

15.5.2 왜 문자열은 불변 원칙을 지키려 할까?

  • 가장 큰 이유는 예기치 못한 버그를 방지하기 위해서이다.
  • string타입이 복사될 때 문자열 전체가 복사되는 것이 아닌 StringHeader의 Data, Len필드값만 복사된다.
  • 여러 변수가 하나의 인스턴스를 가리키고 있다면, 언제 어디에서 문자열이 변경되었는지 알 수 없어 많은 버그를 양산할 수 있다.
profile
백엔드 프로그래머
post-custom-banner

0개의 댓글