자르고 찾는 것은 전부 새 문자열이다
Esc

    자르고 찾는 것은 전부 새 문자열이다

    –C#기초

    앞에서 이렇게 적었습니다.

    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 의 둘째 인자는 개수다

    가장 많이 헷갈리는 자리입니다. 두 번째 숫자는 끝 번호가 아니라 개수입니다.

    H e l l o , ␣ ! W o r l d 0 1 2 3 4 5 6 7 8 9 10 11 12 7번부터 다섯 칸 text.Substring(7, 5)
    둘째 인자 5는 마지막 번호가 아니라 가져올 칸의 개수입니다.
    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 은 배열을 돌려준다. 구분자는 사라지고, 붙어 있으면 빈 칸이 생긴다.