자르고 찾는 것은 전부 새 문자열이다
앞에서 이렇게 적었습니다.
ToUpper·ToLower·Replace·Trim·Substring전부 같습니다. 돌려받지 않으면 아무 일도 일어나지 않습니다.
이제 그 메서드들을 실제로 씁니다. 규칙은 하나뿐입니다. 원본은 그대로 두고 새 문자열을 돌려줍니다.
string text = "Hello, World!";
text.ToUpper(); // 결과를 아무도 안 받았습니다
Console.WriteLine(text); // Hello, World!
string upper = text.ToUpper();
Console.WriteLine(upper); // HELLO, WORLD!
Console.WriteLine(text); // Hello, World! ← 원본은 여전히 그대로
문자열이 불변이라서 그렇습니다. 고칠 방법이 없으니 새로 만드는 것 말고는 할 수 있는 게 없습니다.
이 글에서 다루는 것은 여섯 개입니다. 전부 같은 규칙을 따릅니다.
| 메서드 | 하는 일 | 예 | 결과 |
|---|---|---|---|
Substring(i, n) |
i 번부터 n 개 잘라내기 |
text.Substring(7, 5) |
"World" |
IndexOf(x) |
처음 나오는 칸 번호 | text.IndexOf('W') |
7 |
Contains(x) |
들어 있는지 | text.Contains("World") |
true |
Trim() |
앞뒤 공백 제거 | " hi ".Trim() |
"hi" |
Replace(a, b) |
a 를 전부 b 로 |
text.Replace("World", "C#") |
"Hello, C#!" |
Split(c) |
구분자로 쪼개기 | "a,b,c".Split(',') |
["a", "b", "c"] |
Substring 의 둘째 인자는 개수다
가장 많이 헷갈리는 자리입니다. 두 번째 숫자는 끝 번호가 아니라 개수입니다.
string text = "Hello, World!";
text.Substring(7, 5); // "World" 7번부터 5칸
text.Substring(7); // "World!" 7번부터 끝까지
text.Substring(7, 10); // 예외 7 + 10 이 13을 넘습니다
시작 위치에 개수를 더한 값이 Length 를 넘으면 ArgumentOutOfRangeException 이 납니다.
앞에서 s[5] 가 예외였던 것과 같은 이유입니다. 없는 칸을 달라고 한 것입니다.
끝 번호로 자르는 게 익숙하다면 범위 연산자를 쓸 수 있습니다.
text[7..12]는 7번부터 12번 앞까지입니다. 앞에서 본^1과 같은 문법이고,text[7..]은 7번부터 끝까지,text[..5]는 처음부터 5번 앞까지입니다. 반복문에서i < length를 쓰는 것과 같은 규칙이라 익숙해지면 이쪽이 덜 헷갈립니다.
IndexOf 는 못 찾으면 -1 을 준다
자를 위치를 미리 아는 경우는 드뭅니다. 보통은 찾아서 자릅니다.
int i = email.IndexOf('@');
string domain = email.Substring(i + 1);
이 두 줄에 함정이 있습니다. IndexOf 는 못 찾으면 -1 을 돌려주는데,
그걸 그대로 Substring 에 넘기면 예외가 납니다.
찾은 다음에는 반드시 확인합니다.
두 번째 경우가 특히 위험합니다. i 가 -1 이면 i + 1 은 0 이라
예외도 안 나고 잘못된 값이 조용히 나옵니다. 프로그램은 멀쩡히 도는데
domain 에 주소 전체가 들어가 있습니다.
같은 가족으로 이런 것들이 있습니다.
text.Contains("World"); // true 들어 있는지만
text.StartsWith("Hello"); // true 이걸로 시작하는지
text.EndsWith("!"); // true 이걸로 끝나는지
text.LastIndexOf('l'); // 10 뒤에서부터 찾기
위치가 필요 없고 있는지만 알면 되는 경우에는 Contains 가 낫습니다.
-1 을 확인할 일이 없어서 위 함정을 아예 만들지 않습니다.
Trim 은 앞뒤만, Replace 는 전부
" hi ".Trim(); // "hi" 가운데 공백은 그대로
"the fox".Replace("fox", "cat"); // "the cat"
"a-b-c".Replace("-", ""); // "abc" 빈 문자열로 바꾸면 삭제
Trim 은 앞뒤 공백만 없앱니다. "a b".Trim() 은 "a b" 그대로입니다.
TrimStart 와 TrimEnd 로 한쪽만 다듬을 수도 있습니다.
Replace 는 찾은 것을 전부 바꿉니다. 하나만 바꾸는 옵션은 없습니다.
그리고 대소문자를 구분합니다. "Fox" 를 찾을 때 "fox" 는 안 걸립니다.
string input = Console.ReadLine().Trim();
사용자 입력은 받자마자 Trim 하는 습관을 들이면 좋습니다.
복사해서 붙여 넣은 값에는 앞뒤 공백이 딸려 오는 경우가 많고,
그 공백 때문에 비교가 실패하면 원인을 찾기 어렵습니다.
Split 은 구분자로 쪼갠다
Split 만 돌려주는 것이 다릅니다. 문자열 하나가 아니라 문자열 여러 개,
그러니까 배열입니다.
string s = "Apple,Banana,Cherry";
string[] fruits = s.Split(',');
Console.WriteLine(fruits.Length); // 3
Console.WriteLine(fruits[0]); // Apple
foreach (string f in fruits)
{
Console.WriteLine(f);
}
구분자는 결과에 남지 않습니다. 점선을 따라 종이를 자르면 점선 자체는 버려지는 것과 같습니다. 그래서 조각의 개수는 구분자 개수보다 하나 많습니다.
한 가지 주의할 게 있습니다. 구분자가 붙어 있으면 가운데에 빈 문자열이 생깁니다.
"a,,b".Split(','); // ["a", "", "b"] 길이 3
"a,,b".Split(',', StringSplitOptions.RemoveEmptyEntries); // ["a", "b"]
사용자가 쉼표를 두 번 찍었다고 프로그램이 깨지면 곤란하니,
입력을 쪼갤 때는 RemoveEmptyEntries 를 같이 넘기는 편이 안전합니다.
TrimEntries 를 함께 주면 각 조각의 앞뒤 공백까지 다듬어 줍니다.
반대 방향은 string.Join 입니다.
string joined = string.Join(", ", fruits); // "Apple, Banana, Cherry"
Split 과 Join 이 짝입니다. 쪼개서 손보고 다시 합치는 것이
문자열 처리의 가장 흔한 모양입니다.
정리
- 문자열 메서드는 전부 원본을 두고 새 문자열을 돌려준다. 돌려받아야 한다.
Substring(i, n)의n은 끝 번호가 아니라 개수다.i + n이Length를 넘으면 예외다.- 끝 번호로 쓰고 싶으면 범위 연산자
text[7..12]를 쓴다. IndexOf는 못 찾으면-1이다. 자르기 전에 반드시 확인한다.-1을 그대로 넘기면 예외가 아니라 잘못된 값이 조용히 나올 수 있다.- 있는지만 알면 되는 자리에는
Contains를 쓴다. 확인할-1이 없다. Trim은 앞뒤만,Replace는 전부 바꾸고 대소문자를 구분한다.Split은 배열을 돌려준다. 구분자는 사라지고, 붙어 있으면 빈 칸이 생긴다.