[Swift] String and Characters (문자열과 문자)

LEEHAKJIN-VV·2022년 5월 11일
0

Study-Swift 5.6

목록 보기
11/22

참고사이트:
The Swift Programming Language


String and Characters (문자열과 문자)

String은 character의 연속이다. Swift에서 String은 Character의 컬렉션을 포함하여 다양한 방법으로 접근할 수 있다.

문자열 생성및 조작을 위한 구문은 C와 유사한 문자열 literal 구문을 사용하여 가볍고 읽기 쉽다. 문자열 연결은 더하기(+) 연산자로 쉽게 결합할 수 있고, 문자열의 변동성은 다른 타입처럼 상수나 변수 중에서 선택하여 관리된다.

NOTE
Swift의 String은 Foundation 프레임워크의 NSString은 bridged(연결) 되어 있다. 그래서 Foundation을 import 하면 casting없이 String에서 NSString 메소드를 사용할 수 있다.


String Literals (문자열 리터럴)

코드 내에서 이미 정의된 문자열 값을 String literals로 포함할 수 있다. String literal이란 큰따옴표("")로 둘러싸인 문자의 연속이다.

상수 또는 변수에서 string literal을 초깃값으로 사용한다.

let someString = "Some string literal value"

여기서 Swift는 someString 상수 타입을 문자열로 추론한다. 이유는 string literal로 초기화되었기 때문이다.


Multiline String Literals (여러줄 문자열 리터럴)

만약 여러줄의 문자열이 필요한 경우 (""")로 둘러싸인 다중 string literal을 사용하면 된다.

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

다중 문자열 리터럴은 다음 문자열도 1개의 라인에 담는다.

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

다중 문자열 리터럴 내부에 개행문자(엔터)가 포함되어 있으면 이것은 문자열의 값에도 포함된다. 만약 문자열의 값에 포함되지 않게 개행문자를 사용하고 싶다면 백슬래시()를 줄 끝에 사용한다.

let softWrappedQuotation = """
The White Rabbit put on his spectacles.  "Where shall I begin, \
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""

다중 문자열 리터럴의 처음과 끝이 개행문자로 시작하기를 원하면 양끝에 빈줄을 만든다.

let lineBreaks = """

This string starts with a line break.
It also ends with a line break.

"""

들여 쓰기도 가능하다. 들여 쓰기의 기준은 끝나는 지점의 """의 위치이다. 아래 사진을 보면 맨 마지막 줄의 """ 앞에 공백 4칸이 있는 것을 확인할 수 있다. 공백 4칸이 기준이므로 공백 4칸 까지의 공백들은 전부 무시되고 5칸부터의 공백은 문자열에 반영된다.


Initializing an Empty String (빈 문자열 초기화)

빈 문자열을 만드는 방법은 2가지가 있다.

  • 변수에 빈 문자열 값("")을 할당
  • String 인스턴스 초기화

2가지 방법 모두 아래 예제에서 확인할 수 있다. 결과는 모두 동일하다.

var emptyString = ""               // empty string literal
var anotherEmptyString = String()  // initializer syntax
// these two strings are both empty, and are equivalent to each other

문자열이 비어있는지 여부는 isEmpty프로퍼티를 이용하여 확인한다.

if emptyString.isEmpty {
    print("Nothing to see here")
}
// Prints "Nothing to see here"

String Mutability (문자열 수정)

문자열 또한 다른 타입처럼 변수(var)로 선언할 시 수정이 가능하고 상수(let)로 선언할 시 수정이 불가능하다.

var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified

Strings Are Value Types (문자열은 값 타입)

Swift의 문자열은 값 타입이다. 새로운 문자열을 만들고 이를 함수나 메소드에 전달하거나 상수나 변수에 할당될 때 원본 문자열의 값의 복사본이 생성된다. 2가지 경우 모두 원본이 아닌 복사본이 전달되거나 할당된다.

그렇기에 함수나 메소드가 문자열에 전달된 문자열이 수정되지 않는 것을 보장한다. 그리고 값의 복사는 Swift의 컴파일러가 필요할 때만 복사하여 문자열 사용을 최적화한다.


Woring with Characters

문자열의 개별 문자를 for-in loop을 사용해 접근할 수 있다.

for character in "Doctor🤡" {
    print(character)
}
// D
// o
// c
// t
// o
// r
// 🤡

다음과 같이 문자를 상수나 변수로 선언할 수도 있다.

let exclamationMark: Character = "!"

문자열 초기화 메소드의 인수로 문자 배열을 넣어 문자열을 얻을 수 있다.

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// Prints "Cat!🐱"

Concatenating Strings and Characters (문자열과 문자의 결합)

문자열은 더하기(+)연사자를 이용하여 새로운 문자열을 만들 수 있다.

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome : "hello there"

또한 기존의 문자열에 새로운 문자열 값을 추가할 수 있다.

var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

문자값을 문자열 타입의 append()메소드를 이용하여 문자열 변수에 추가할 수 있다.

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"

NOTE
Character(문자)는 단일 문자로 구성되기 때문에 기존 문자 변수에는 문자열이나 문자를 append (추가)할 수 없다.

multile string literals을 사용하여 긴 문자열을 작성하는 경우 모든 줄의 마지막이 개행문자(줄 바꿈)으로 끝나기를 원한다고 하자. 이를 달성하기 위해서 문자열을 합칠 때 왼쪽 문자열(시작 문자열)의 맨 마지막 줄에 개행문자를 추가함으로써 해결할 수 있다.

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree

let goodStart = """
one
two

"""
print(goodStart + end)
// Prints three lines:
// one
// two
// three

String Interpolation (문자열 보간)

문자열 보간은 문자열 리터럴 내부에 상수, 변수, 리터럴 값을 추가하는 것이다. 한 줄 또는 여러 줄에서 문자열 보간을 사용할 수 있다. 문자열 리터럴에 삽입하는 아이템은 백슬래시()와 괄호를 이용하여 삽입한다.

let mutiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message : "3 times 2.5 is 7.5"

Unicode (유니코드)

유니코드는 전 세계의 모든 문자를 컴퓨터에서 읽고 쓸 수 있도록 하는 국제 표준이다. Swift의 문자열과 문자 타입은 유니코드와 완전히 호환된다.

Unicode Scalar Values (유니코드 스칼라)

Swift의 기본 String타입은 유니코드 스칼라 값으로 빌드 된다. 유니코드 스칼라 값은 21비트의 고유한 숫자로 구성되어 있다. 예를 들어 U+0061는 라틴어의 소문자 a를 나타내고 U+1F425는 병아리 🐥 를 나타낸다.

Counting Characters (문자 세기)

문자열에서 문자 값의 개수를 검색할려면 문자열의 count property를 사용한다.

print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters"

Accessing and Modifying a String (문자열 접근과 수정)

문자열의 메소드나 프로퍼티 또는 subscript를 사용하여 문자열을 수정하고 접근할 수 있다.


String Indices (문자열 인덱스)

문자열의 값에는 문자열의 각 문자 위치에 해당하는 index type(색인)인 String.index가 있다. 그리고 Swift에서는 문자열을 정수로 index 하지 않는다.

문자열의 첫 문자의 위치에 접근하려면 startIndex 프로퍼티를 사용해야 한다. endIndex프로퍼티는 문자열의 마지막 문자의 뒤를 가리킨다. 그래서 문자열의 subscript로 endIndex프로퍼티를 사용할 수 없다. 만약 문자열이 비었다면 startIndexendIndex가 동일하다.

문자열에 특정한 인덱스가 주어진다면 그 인덱스의 바로 앞과 뒤를 index(before:)그리고 index(after:)메소드로 접근할 수 있다. 주어진 인덱스로부터 멀리 떨어진 인덱스를 접근하여 인덱스 값을 얻고자 한다면 index(_:offsetBy:) 메소드를 사용하면 된다.

이제 subscript와 메소드를 사용하여 문자열의 특정 인덱스에 접근 하자.

let actor: String = "Tony Stark!"

actor[actor.startIndex]
// T
actor[actor.index(before: actor.endIndex)]
// !
actor[actor.index(after: actor.startIndex)]
// o
let index = actor.index(actor.startIndex, offsetBy: 7)
actor[index]
// a

문자열의 인덱스를 벗어나는 문자를 접근하면 런타임 에러가 발생한다.

actor[actor.endIndex] // error
actor.index(after: actor.endIndex) // error

문자열의 모든 문자의 인덱스에 접근하기 위해서는 indices 프로퍼티를 사용한다.

for index in actor.indices {
    print(" \(actor[index])", terminator: "")
}
// T o n y   S t a r k !

여기서 index는 Int가 아닌 Index의 타입의 인스턴스다.


Inserting and Removing (문자의 삽입과 삭제)

문자열의 특정 위치에 한 개의 문자를 삽입하려면 insert(_:at:) 메소드를 사용하고, 문자가 아닌 다른 문자열은 insert(contentsOf:at:) 메소드를 사용한다.

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome : hello!

welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome : hello there!

마찬가지로, 문자열의 특정 인덱스에 위치하는 한 개의 문자를 제거하려면 remove(at:) 메소드를 사용하고 지정된 범위의 문자열을 제거할려면 removeSubrange(_:)메소드를 사용한다.

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome : hello there

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome : hello

NOTE
위 메소드들은 RangeReplaceableCollection 프로토콜을 따르는 Array, Dictionary, Set 등에서도 동일하게 사용할 수 있다.


Substrings (부분 문자열)

문자로부터 substring(부분 문자열)을 얻을 때 subscript나 prefix(_:) 와 같은 메소드를 사용하는데 이들의 결과는 문자열이 아닌 Substring 인스턴스이다. Swift에서 SubString은 문자열과 거의 동일하게 동작한다. 그러나 Substring을 단기간 사용하는 것이 아닌 장기간 사용한다면 문자열 인스턴스로 바꾸는 것이 좋다.

다음 예제를 보자.

let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"

// Convert the result to a String for long-term storage.
let newString = String(beginning)

위 코드에서 (??) 연산자는 Nil 병합 연산자이다. 이 연산자는 왼쪽 값이 nil이 아니면 왼쪽 값을 상수나 변수에 할당하고 nil 이면 오른쪽 값을 할당한다. greeting 문자열에서 ","문자가 없을 시 firstIndex 메소드는 nil을 반환하고 index는 문자열의 제일 마지막을 가리키므로 beginning은 "Hello, world"가 된다.

장기간 오래 사용할 시 String(문자열)을 사용해야 하는 이유는 메모리 관리 때문이다. SubString은 원본 문자열에서 추출한 문자를 다른 메모리에 값을 저장하고 참조하는 것이 아닌 원본 문자열의 메모리를 참조해 사용한다. 다음 그림을 보자.

그림에서 확인할 수 있듯이 Substring을 계속 이용하면 원본 문자열은 메모리에 계속 남게 된다. 즉 사용하지 않는 문자열이 계속 메모리에 남게 되어 성능 최적화에 문제가 생기는 것이다. 이를 방지하고자 Substring을 오래 사용하는 경우 String 인스턴스로 만들어 사용하자.

NOTE
String과 Substring 모두 StringProtocol을 따른다. 그래서 문자 조작에 필요한 메소드를 사용할 수 있다.


Comparing Strings (문자열 비교)

Swift는 문자열과 문자, 접두사(첫 문자), 접미사(끝 문자)가 같은지 비교할 수 있다.

String and Character Equality (문자열과 문자 비교)

문자열과 문자 비교에는 ==!=을 사용한다.

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

유니코드는 결합된 문자열을 갖고 비교한다.

// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"

// "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

같은 유니코드 문자여도 유니코드가 다르면 다른 문자로 판별한다.

let latinCapitalLetterA: Character = "\u{41}"

let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters are not equivalent.")
}
// Prints "These two characters are not equivalent."

NOTE
Swift에서 문자열과 문자의 비교는 언어를 고려하지 않는다. 즉 언어가 달라도 같은 문자이면 동일한 문자로 취급한다.


Prefix and Suffix Equality (접두사와 접미사 비교)

문자열의 특정 접두사 또는 접미사가 있는지 확인하려면 문자열의 hasPrefix(_:) 그리고 hasSuffix(_:) 메소드를 사용한다. 두 메소드 모두 인수의 타입이 문자열이고 반환값은 Bool형이다.

let romeoAndJuliet = [
    "Act 1 Scene 1: Verona, A public place",
    "Act 1 Scene 2: Capulet's mansion",
    "Act 1 Scene 3: A room in Capulet's mansion",
    "Act 1 Scene 4: A street outside Capulet's mansion",
    "Act 1 Scene 5: The Great Hall in Capulet's mansion",
    "Act 2 Scene 1: Outside Capulet's mansion",
    "Act 2 Scene 2: Capulet's orchard",
    "Act 2 Scene 3: Outside Friar Lawrence's cell",
    "Act 2 Scene 4: A street in Verona",
    "Act 2 Scene 5: Capulet's mansion",
    "Act 2 Scene 6: Friar Lawrence's cell"
]

다음 코드는 문자열 배열remeoAndJuliet에서 접두어 Act 1이 몇개 들어있는지 확인한다.

var act1SceneCount = 0
for scene in remeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("There are \(act1SceneCount) scenes in Act 1")
// There are 5 scenes in Act 1

다음은 문자열 배열에서 접미어 Capulet's mansionFriar Lawrences' cell의 개수를 확인한다.

var mansionCount = 0
var cellCount = 0
for scene in remeoAndJuliet {
    if scene.hasSuffix("Capulet's mansion") {
        mansionCount += 1
    } else if scene.hasSuffix("Friar Lawrence's cell") {
        cellCount += 1
    }
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 6 mansion scenes; 2 cell scenes

0개의 댓글