1. 문자열 만들기
- 큰 따음표는 특수문자 처리해주나, backtick은 처리하지 않음
- 큰 따음표는 한줄에 모든 문자열을 작성해야하지만, backtick은 여러 줄이 가능
func main() {
poet1 := "죽는 날까지 하늘을 우러러 \n한점 부끄럼이 없기를,\n잎새에 이는 바람에도\n나는 괴로워했다.\n"
poet2 := `죽는날까지 하 늘우러러
한점 부끄럼이 없기를,
잎새에 이는 바람에도
나는 괴로워했따.`
fmt.Println(poet1)
fmt.Println(poet2)
}
2. 문자열 개수 세기
- rune
- 한 문자열을 담는데 사용되는 타입 (UTF-8)
- UTF-8은 한 글자당 1~3 바이트를 사용
- 따라서 4byte인 rune 사용
- type rune int32 // 4byte 타입
- 한 문자열을 담는데 사용되는 타입 (UTF-8)
문자열은 최대한 byte을 압축하여 저장 글자수를 알기 위해서는 []rune으로 변환하여 저장해야한다.
// []rune -> string으로 변환 가능
func main() {
str := "Hello World"
runes := []rune{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100}
fmt.Println(str)
fmt.Println(string(runes))
}
// string -> []rune 변환 가능
func main() {
str := "Hello 월드"
runes := []rune(str)
fmt.Printf("len(str) = %d\n", len(str))
fmt.Printf("len(runes) = %d\n", len(runes))
}
- string <-> []byte도 가능하며, 이는 interface에서 다룬다.
3. 문자열 iteration
문자열을 index로 iteration하면 한국어는 잘 못 나올 수 있다.
func main() {
str := "Hello 월드!"
for i := 0; i < len(str); i++ {
fmt.Printf(" 타입:%T 값: %d 문자열:%c\n", str[i], str[i], str[i])
}
}
/*
go run ex13.7.go
타입: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 문자열:
타입:uint8 값: 33 문자열:!
*/
// []rune 타입이나 iteration을 돌면 정상적으로 나온다.
// []rune은 자명하므로 iteration만 보인다.
func main() {
str := "Hello 월드!"
for _, v := range str {
fmt.Printf(" 타입: %T, 값: %d, 문자: %c\n", v, v, v)
}
}
/*
go run ex13.9.go
타입: int32, 값: 72, 문자: H
타입: int32, 값: 101, 문자: e
타입: int32, 값: 108, 문자: l
타입: int32, 값: 108, 문자: l
타입: int32, 값: 111, 문자: o
타입: int32, 값: 32, 문자:
타입: int32, 값: 50900, 문자: 월
타입: int32, 값: 46300, 문자: 드
타입: int32, 값: 33, 문자: !
*/
4. 문자열 비교
- == : 같다
- != : 다르다
, >= : 크다, 크거나 같다
- <, <= : 작다 작거나 같다.
사전순으로 비교됨. 다른 언어들과 비슷하므로 별도의 설명없이 지나간다.
5. String 내부 구조 알아보기
- string type은 go에서 내부적으로 구현하여 감춰져있으나, StringHeader을 통해 살펴볼 수 있으며 아래 와같다.
type StringHeader struct {
Data uintptr
Len int
}
// Data가 포인터를 가르키고, Len이 문자열의 길이를 나타냄 (Byte단위)
// 따라서 아래와 같이 Copy할 대는 문자열이 복사되지 않고 포인터와 길이만 복사됨.
var str1 := "Hello World"
var str2 := str1
//str1, str2가 가르키는 문자열은 동일한 메모리 위치
// 따라서 아래와같이 구현하면...
func main() {
str1 := "Hello World!"
str2 := str1
// In go, strings cannot be conveted StringHeader.
// So first str1 is converted to unsafe Pointer type, and converted to StringHeader Pointer Type
stringHeader1 := (*StringHeader)(unsafe.Pointer(&str1))
stringHeader2 := (*StringHeader)(unsafe.Pointer(&str2))
fmt.Println(stringHeader1)
fmt.Println(stringHeader2)
}
/*
go run ex13.13.go
&{5003419 12}
&{5003419 12}
*/
6. 문자열은 불변이다.
문자열은 중간 index의 값을 변경할 수 없다. (전체 문자열을 변경하는 것은 괜찮다.)
만약 중간 값을 바꾸고 싶다면, []byte로 변경한다음에 index로 접근하여 수정하면 된다.
따라서, 문자열을 자주 + 등으로 덧붙이거나, 변경하게 되면 많은 비효율을 양산하게 됩니다. (새로운 문자열이 메모리에 계속 할당되고 사라지고 합니다.) => 이럴 때는 StringBuilder을 사용하여 효율적으로 구현합니다.
func ToUpper1(str string) string {
var rst string
for _, c := range str {
if c >= 'a' && c <= 'z' {
// Inefficient code
rst += string('A' + (c - 'a'))
} else {
rst += string(c)
}
}
return rst
}
func ToUpper2(str string) string {
var builder strings.Builder
for _, c := range str {
if c >= 'a' && c <= 'z' {
builder.WriteRune('A' + (c - 'a'))
} else {
builder.WriteRune(c)
}
}
return builder.String()
}
func main() {
var str string = "Hello World!"
fmt.Println(ToUpper1(str))
fmt.Println(ToUpper2(str))
}