1. 문자열 만들기


  1. 큰 따음표는 특수문자 처리해주나, backtick은 처리하지 않음
  2. 큰 따음표는 한줄에 모든 문자열을 작성해야하지만, 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 타입

문자열은 최대한 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))
}