Doodly IT

[Go 묘공단 스터디] 문자열 본문

프로그래밍/Go

[Go 묘공단 스터디] 문자열

DoodlyKim 2023. 11. 22. 20:38

1) 문자열

-> 문자열은 " "이나 ` 로 표현한다. "은 개행문자들을 반영하지만 ` 은 반영하지않는다.

package main

import "fmt"

func main() {

	poet2 := "Hello World"
	poet1 := `Hello World! 
	         Program to work and not to feel. Not even sure that this is real`

	fmt.Println(poet1)
	fmt.Println(poet2)
}

 

2) UTF-8

Go는 UTF-8을 표준 문자코드로 사용한다. UTF-8이 모든 문자를 2~3바이트로 표현한 것과 달리 자주 사용되는 영문자와 숫자를 1바이트로 표현하고 나머지 문자들을 2~3바이트로 표현한다. UTF-8은 ANSI와 1:1대응이 가능하기 때문에 바로 변환이 가능하다.

 

package main

import "fmt"

func main() {

	var char rune = '한'

	fmt.Println(char)
	fmt.Printf("%c\n", char)

}

 

 

Printf와 Print함수의 차이점은 Printf은 %c처럼 출력의 형태(Format)을 정할 수 있다.

예를 들어 위에 예시 처럼 %c면 char형태로 출력하기 때문에 '한'이라고 출력하게 된다.

하지만 형태를 지정하지 않은 print함수는 54260로 숫자로 나타나게 된다.

 

3) 문자열 길이 

문자열의 길이는 len함수를 쓴다.

package main

import "fmt"

func main() {

	str1 := "Hello World"
	fmt.Printf("len(str1) = %d \n", len(str1))
}

 

 

4) []rune 타입

rune형태의 배열이다.

package main

import "fmt"

func main() {

	str := "Hello World"

	//runes := []rune{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100}
	
	runes := []rune(str)


	fmt.Println(str)
	fmt.Println(runes)
}

 

이때 runes의 결과는 Hello World의 UTF-8의 숫자형태로 나오게 된다.

 

 

5) 문자열 순회

문자열을 순회하는 3가지 방법이 있다.

 

1. 인덱스를 사용해서 바이트 단위로 순회하기

package main

import "fmt"

func main() {

	str := "Hello World 은 안녕세상아"

	for i := 0; i < len(str); i++ {
		fmt.Printf("타입: %T, 값:%d , 문자값: %c \n ", str[i], str[i], str[i])
	}
}

이 때 한글은 2~3바이트여서 str의 기본 단위인 바이트 단위로 순회하면 깨지게 된다.

 

 

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

 

[]rune 각 글자들로 이루어진 배열로 변환하기 때문에 한글이 깨지지 않는다.

package main

import "fmt"

func main() {

	str := "Hello World 은 안녕세상아"
	arr := []rune(str)

	for i := 0; i < len(str); i++ {
		fmt.Printf("타입: %T, 값:%d , 문자값: %c \n ", arr[i], arr[i], arr[i])
	}
}

 

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

package main

import "fmt"

func main() {

	str := "Hello World 은 안녕세상아"

	for _, v := range str {
		fmt.Printf("타입: %T 값: %d 문자값: %c \n", v, v, v)
	}
}

 

 

6) 문자열 연산 & 비교

문자열도  숫자처럼 비교가 가능하다. str의 가장 작은 인덱스에서 부터 연산을 시작한다.

알파벳 순서대로 문자의 정수값은 증가한다.

package main

import "fmt"

func main() {

	str := "HelloWorld"
	str1 := "HelloWorld"
	str2 := "NotHelloWorld"

	fmt.Printf("str and str1 is %v\n", str == str1)
	fmt.Printf("str and str2 is %v\n", str == str2)
	fmt.Printf("str is upper than str2? %v", str < str2)//H는 N보다 더 정수값이 작기 때문에 더 앞서있다. 

}

 

문자열도 합칠 수 있다.

package main

import "fmt"

func main() {

	str := "Hello"
	str1 := "World"

	fmt.Print(str + str1)

}

 

 

7) 문자열은 불변이다.

package main

import "fmt"

func main() {

	str := "HelloWorld"
	str[1] = 'a'
	fmt.Println(str)

}

실제로 위와 같은 문자열에서 1번째 인데스 값만 바꾸려고 할때 컴파일 에러가 일어난다.

그렇다고 문자열을 아예 바꿀 수 없는 것은 아니다.

package main

import "fmt"

func main() {

	str := "HelloWorld"
	var slice []byte = []byte(str)
	slice[1] = 'a'

	fmt.Println(str)
	fmt.Printf("%c", slice)

}

위와 같이 바이트 단위로 변환을 하고 해당 Slice의 값만 바꾸면 된다.

위에 코드 실행 시키면 아래와 같이 e->a로 바뀌게 된다. 

 

근데 코드를 보면 str의 값은 바뀌지 않았다는 것을 알 수 있다. 

string 타입은 실제로 실제 문자가 들어 있는 포인터값과 int형으로 이루어진 자료구조기 때문에 위에 변환을 하면서 해당 포인터값의 데이터를 복사했을 뿐 slice와 str은 별개의 변수이다. 

 

>> string형의 자료구조

type StringHeader struct{

Data uintptr
Len int
}

 

 

이를 확인하기 위해선 아래 코드로 str와 slice의 포인터 값을 얻어서 확인 할 수 있다.

package main

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

func main() {

	str := "HelloWorld"
	var slice []byte = []byte(str)
	slice[1] = 'a'

	stringheader := (*reflect.StringHeader)(unsafe.Pointer(&str))
	sliceheader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))

	fmt.Println(stringheader)
	fmt.Println(sliceheader)

}

 

위 코드를 실행 시키면 아래와 같이 결과가 나온다. 

이렇게 str과 slice는 아예 다른 메모리에 저장이 되어있고 별개인걸 알 수 있다.