나만의 작은 도서관
[C#] 문법 정리 #2. 문자열 본문
문자열 생성
문자열을 생성하는 방법은 크게 두 가지가 있다. 첫번째는 리터럴 문자를 사용하는 것이고, 두번째는 new 키워드를 통해 생성하는 것이다.
string str1 = "Hello, World!"; // 리터럴 문자열 사용
string str2 = new string('H', 5); // 문자 'H'를 5개로 구성된 문자열 생성
이스케이프 시퀀스(Escape Sequence)
이스케이프 시퀀스(Escape Sequence)는 특수 문자를 문자열에 포함시키기 위해 사용하는 특별한 문자 조합을 의미한다. 리터럴의 일종으로 취급하며 백슬래시를 사용하여 시작한다. 아래는 이스케이프 시퀀스의 일부 예시이다.
이스케이프 시퀀스 | 기능 | 예시 코드 | 결과 |
\n | 새 줄 (줄바꿈) | Console.WriteLine("Hello\nWorld!"); | Hello World! |
\t | 탭 (수평 탭) | Console.WriteLine("A\tB"); | A B |
\\ | 백슬래시 (\) 표시 | Console.WriteLine("\\"); | \ |
\" | 큰따옴표 (") 표시 | Console.WriteLine("\"Hello\""); | Hello" |
\' | 작은따옴표 (') 표시 | Console.WriteLine("It\'s OK"); | It's OK |
\b | 백스페이스 | Console.WriteLine("A\bB"); | B |
\0 | 널 문자 (문자열의 끝을 표시) | - | - |
\u or \x | 유니코드 문자 | Console.WriteLine("\u0041"); | A |
문자열 처리
연결
C#에서 두 문자열의 연결은 "+" 연산자를 통해 연결한다.
string str1 = "Hello";
string str2 = "World!";
string str3 = str1 + " " + str2; // "Hello World!"
분할: string.Split()
C#은 문자열을 분리하는 함수, Split()을 지원한다.(그지같은 C++...) 매개변수로 분리할 문자(separator)를 넣으면 string배열을 반환한다.
string str = "Hello, World!";
string[] words = str.Split(','); // words[0]: "Hello", words[1]: " World"
Split()의 매개변수 타입이 char[]이므로, 분리할 문자를 여러 개 넣을 수도 있다.
char[] seperator = {',', 'e'};
string str = "Hello, World";
string[] output = str.Split(seperator);
foreach (string s in output)
{
Console.WriteLine(s);
}
// [출력 결과]
//H
//llo
// World
검색: string.IndexOf()
IndexOf는 매개변수로 넣은 문자열의 시작 위치를 반환한다. 여러 개 존재했을 시 첫번째 문자열의 위치를 반환한다.
string str = "Hello, World!";
int index = str.IndexOf("World"); // index: 7
만약 특정 문자열이 몇 개 있는지 알고싶다면, 오버로드된 IndexOf()를 통해 알 수 있다.
string str = "hello world, hello universe";
string pattern = "hello";
int index = str.IndexOf(pattern);
int cnt = 0;
while(index != -1)
{
cnt++;
index = str.IndexOf(pattern, index + pattern.Length); // str.IndexOf(문자열, 시작 위치);
}
Console.WriteLine(cnt); // 2
교체: string.Replace()
string.Replace()는 특정 문자열을 모두 교체한 새로운 문자열을 생성한다.
string str = "Hello, World!";
string newStr = str.Replace("World", "Universe"); // Hello, Universe!
변환: Parse() / ToString()
문자열을 숫자로 변환(casting)할 때는 Parse(), 숫자를 문자열로 변환할 때는 ToString()을 사용한다.
string str = "123";
int num = int.Parse(str);
int num = 123;
string str = num.ToString();
대소비교: string.Compare()
두 문자의 대소비교는 string.Compare()를 통해 할 수 있다. 리턴값은 int형이며, -1, 0, 1중 하나로 반환한다. 각각의 의미는 다음과 같다.
- -1 : 첫번째 문자열이 더 작다
- 0: 두 문자열이 같다
- 1: 첫번째 문자열이 더 크다.
대소문자 구분없이 첫번째 문자열에서 두번째 문자열을 뺐을때 나오는 값이라고 생각하면 헷갈리지 않는다.
int result = String.Compare("apple", "banana");
Console.WriteLine(result); // -1 (첫 번째 문자열이 더 작음)
result = String.Compare("banana", "apple");
Console.WriteLine(result); // 1 (첫 번째 문자열이 더 큼)
result = String.Compare("apple", "apple");
Console.WriteLine(result); // 0 (두 문자열이 같음)
주의할 점은 대소문자에 대한 비교 결과이다. String.Compare에 세번째 매개변수를 추가하지 않았다면 기본 우선순위인 사전식으로 대소비교를 한다. 이 때, 사전식은 현재 문화권 정보(CultureInfo.CurrentCulture)를 기준으로 대소문자의 크기를 결정하는데, 대부분의 문화권에서 "A"가 "a"보다 크다고 판단하기 때문에 대게 대문자가 더 크다고 설정되어있다. 따라서 우선순위는 1) 대소문자 구분없이 알파벳순, 2) 대소문자(대문자가 더 큼)가 된다.
result = String.Compare("Hello", "hhhhh");
Console.WriteLine(result); // -1 (첫 번째 문자열이 더 작음)
result = String.Compare("A", "a");
Console.WriteLine(result); // 1 (첫 번째 문자열이 더 큼)
result = String.Compare("a", "B");
Console.WriteLine(result); // -1 (첫 번째 문자열이 더 작음)
result = String.Compare("A", "b");
Console.WriteLine(result); // -1 (첫 번째 문자열이 더 작음)
result = String.Compare("a", "b");
Console.WriteLine(result); // -1 (첫 번째 문자열이 더 작음)
result = String.Compare("A", "a", StringComparison.Ordinal);
Console.WriteLine(result); // -32 (음수)
다른 우선 순위에 대해서 알고싶다면 아래 링크를 참고하면 된다.
https://learn.microsoft.com/ko-kr/dotnet/csharp/how-to/compare-strings
출력 포멧팅
문자열에 변수를 넣고 싶을 때 포멧팅은 유용하게 작용한다. 포멧팅을 하는 방법은 다음과 같다.
문자열 형식화(string.Format() 사용)
string name = "John";
int age = 30;
string message = string.Format("My name is {0} and I'm {1} years old.", name, age);
문자열 보간("$" 리터럴 사용)
string name = "John";
int age = 30;
string message = $"My name is {name} and I'm {age} years old.";
StringBuilder
string이 있음에도 StringBuilder를 사용하는 이유
결론만 말하자면, string은 불변(immutable)이라는 특성을 가지고 있기 때문이다. 불변이라는 특성은 글자그대로 변경되지 않는다는 의미인데, 이 특성을 가지고 있는 대상은 결코 변경될 수 없다. 즉, 한 번 생성된 string은 메모리가 할당된 순간 더이상 그 안의 값을 바꿀 수 없다.
그렇다면 위에서 설명한 메소드들은 어떻게 작동하는 걸까? 사실 내부적으로 string은 기존의 문자열을 변경하는 것이 아닌, 변경된 새로운 문자열을 하나 더 만드는 것이다. 아래 예시를 보자.
string str = "Hello";
str += " World"; // 새로운 문자열 객체 생성
// 최종 메모리: "Hello", "Hello World"
위 코드에서 "Hello"는 수정되지 않고, "Hello World"라는 새로운 문자열 객체가 만들어진다. 즉, 문자열을 변경하는 작업을 할 때마다 메모리를 할당하게 되고, 이런 과정이 반복될수록 메모리와 CPU 자원이 심하게 낭비된다.
이러한 자원 낭비를 하지 않기 위해 불변 특성이 아닌 문자열 타입이 필요했고, 그렇게 나온 타입이 StringBuilder이다.
StringBuilder의 특성
- StringBuilder는 가변(mutable) 객체로 설계되어, 문자열 변경 작업에서 새로운 객체를 생성하지 않고 기존 버퍼를 재사용한다. 이는 문자열 조작 작업이 빈번히 발생했을 때 메모리 할당 및 해제 오버헤드가 크게 증가하는 String의 문제점을 해결하였다.
- StringBuilder는 버퍼 크기를 미리 예약해서 사용하며, 버퍼 크기가 부족하면 동적으로 확장할 수 있다.
주요 메서드
- Append: 문자열을 추가합니다.
- Insert: 특정 위치에 문자열을 삽입합니다.
- Remove: 특정 위치의 문자열을 제거합니다.
- Replace: 문자열의 일부를 다른 문자열로 교체합니다.
- ToString: 최종 결과를 string으로 변환합니다.
예시
StringBuilder sb = new StringBuilder("Hello");
sb.Append(" World"); // 문자열 추가
sb.Insert(6, "C# "); // 특정 위치에 삽입
sb.Replace("World", "Everyone"); // 문자열 교체
sb.Remove(0, 6); // 첫 6글자 삭제
Console.WriteLine(sb.ToString()); // C# Everyone
언제 사용해야 할까?
- 문자열 조작이 많고 반복적일 때 (for, while 등 반복문에서 문자열 연결 작업이 빈번할 때).
- 대량의 문자열 데이터가 처리될 때 (로그 기록, 데이터 합치기 등).
- 메모리 사용량을 최소화하고 성능을 최적화하고 싶을 때.
결론
StringBuilder는 반복적인 문자열 조작을 효율적으로 처리하기위해 설계된 도구이므로, 복잡하고 반복적인 작업에서는 StringBuilder를 사용하는 것이 적절하다. 하지만, 단일 작업이나 간단한 문자열 연결 작업에서 string과 비교했을 때 성능적인 이점이 크지 않으므로 간단한 작업은 string은 사용하는 것이 나을 수 있다.
'Unity > 문법 및 메소드(System)' 카테고리의 다른 글
[C#] 문법 정리 #6. 인터페이스 (0) | 2024.11.21 |
---|---|
[C#] 문법 정리 #5. 상속과 다형성(virtual, abstract 키워드) (0) | 2024.11.21 |
[C#] 문법 정리 #4. 클래스 (0) | 2024.11.21 |
[C#] 문법 정리 #3. 배열과 컬렉션(+제너릭) (1) | 2024.11.21 |
[C#] 문법 정리 #1. 기본 구조과 입출력(+리터럴) (0) | 2024.11.20 |