문자는 숫자로 저장된다
Esc

    문자는 숫자로 저장된다

    –C#기초

    앞에서 이런 걸 봤습니다.

    int  n = 65;
    char c = (char)65;
    
    Console.WriteLine(n);   // 65
    Console.WriteLine(c);   // A

    같은 비트를 int 로 읽으면 65고 char 로 읽으면 A 입니다.

    그런데 왜 하필 65일까요. 누가 정한 걸까요.

    컴퓨터는 글자를 모른다

    지금까지 본 대로 메모리에는 0과 1밖에 없습니다. 그러니 글자를 저장하려면 방법은 하나뿐입니다.

    글자마다 번호를 붙여두고, 그 번호를 저장합니다.

    A 를 저장하는 게 아니라 A 의 번호인 65를 저장합니다. 화면에 그릴 때 다시 번호를 보고 A 모양을 찾아 그립니다.

    문제는 누가 그 번호를 정하느냐입니다. 회사마다 다르게 정하면 다른 컴퓨터에서 글이 깨집니다. 그래서 표준이 필요했고, 그게 아스키코드입니다.

    아스키코드 (ASCII)

    1960년대에 만들어진 표입니다. 7비트로 128개의 글자에 번호를 붙였습니다. 영어 알파벳과 숫자, 기호, 그리고 몇 가지 제어 신호가 전부입니다.

    번호 무엇 예시
    0 ~ 31 제어 문자 (화면에 안 보임) 줄바꿈, 탭
    32 공백
    48 ~ 57 숫자 문자 '0' ~ '9'
    65 ~ 90 대문자 'A' ~ 'Z'
    97 ~ 122 소문자 'a' ~ 'z'

    'A' 가 65인 것은 그냥 그렇게 정한 것입니다. 계산으로 나오는 수가 아닙니다.

    직접 확인해 보세요. 비트를 눌러 65를 만들면 A 가 뜹니다. 97을 만들면 a 가 뜹니다.

    이 번호에는 규칙이 있습니다

    아무렇게나 붙인 번호가 아닙니다. 순서대로 붙였고, 구간을 나눠 붙였습니다. 그래서 계산이 됩니다.

    첫째, 같은 종류는 이어져 있습니다.

    'A' 부터 'Z' 까지가 65~90으로 끊김 없이 이어집니다. 그래서 범위 검사가 그냥 부등호로 됩니다.

    if (c >= 'a' && c <= 'z')
    {
        Console.WriteLine("소문자입니다");
    }

    둘째, 숫자 문자는 48부터입니다.

    '7' 이라는 문자와 7 이라는 숫자는 다릅니다. '7' 의 번호는 55입니다. 그런데 '0' 이 48이니 빼주면 진짜 숫자가 나옵니다.

    char c = '7';
    
    Console.WriteLine(c - '0');   // 7   (55 - 48)
    Console.WriteLine((int)c);    // 55  (문자의 번호)

    셋째, 대문자와 소문자는 정확히 32 차이입니다.

    'A' 는 65, 'a' 는 97입니다. 이게 우연이 아닙니다.

    'A' = 65 'a' = 97 01 0 000 01 01 1 000 01 12864 32 1684 21
    대문자와 소문자는 비트 하나만 다릅니다. 그 칸의 자릿값이 32입니다.

    비트 하나를 켜고 끄는 것으로 대소문자가 바뀝니다. 표를 만들 때 일부러 이렇게 배치한 것입니다.

    char upper = 'A';
    char lower = (char)(upper + 32);   // 'a'

    실제 코드에서는 이렇게 쓰지 마세요. char.ToLower(c) 를 쓰면 됩니다. 32를 더하는 방식은 아스키 범위의 영어에서만 맞고 한글이나 다른 언어에서는 깨집니다. 여기서는 번호가 어떻게 배치되어 있는지를 보려고 든 예입니다.

    128자로는 모자랐다

    아스키코드는 영어를 위한 표입니다. 한글도 한자도 들어갈 자리가 없습니다.

    그래서 나라마다 자기 방식을 만들었고, 같은 번호가 나라마다 다른 글자를 가리키게 됐습니다. 한글 문서를 다른 컴퓨터에서 열면 깨지던 시절의 이야기입니다.

    이걸 해결하려고 만든 게 유니코드(Unicode) 입니다. 세상의 모든 글자에 번호를 하나씩 붙이자는 계획입니다.

    • 아스키의 128자는 번호를 그대로 물려받았습니다. 'A' 는 유니코드에서도 65입니다
    • 한글 '가' 는 44032번입니다
    • 이모지도 번호가 있습니다

    여기서 용어가 둘로 갈립니다. 헷갈리기 쉬운 자리입니다.

    무엇
    유니코드 글자마다 번호를 정해둔 표
    UTF-8 · UTF-16 그 번호를 바이트로 적는 방법

    번호가 44032라고 해서 메모리에 그 숫자를 어떻게 넣을지가 저절로 정해지지는 않습니다. 그걸 정하는 게 UTF-8 이나 UTF-16 입니다.

    C# 의 char 는 UTF-16 한 조각이고 2바이트입니다. 앞에서 char 가 2바이트였던 이유가 이것입니다. 아스키만 담을 거면 1바이트로 충분한데, 한글과 한자까지 담아야 해서 두 배를 씁니다.

    Console.WriteLine((int)'A');   // 65
    Console.WriteLine((int)'가');   // 44032

    char 는 숫자처럼 계산된다

    번호가 붙어 있으니 char 는 계산이 됩니다.

    char c = 'A';
    
    Console.WriteLine(c + 1);           // 66     int 로 계산됩니다
    Console.WriteLine((char)(c + 1));   // B      다시 char 로 읽으면

    c + 1 의 결과가 char 가 아니라 int 인 게 눈에 걸릴 겁니다. 앞에서 본 대로 연산자는 값을 만드는데, char 끼리 더하면 넘칠 수 있으니 C# 은 결과를 int 로 넓혀서 줍니다. char 로 되돌리려면 직접 캐스팅해야 합니다.

    이 성질로 알파벳을 쭉 찍어볼 수 있습니다.

    for (char c = 'A'; c <= 'Z'; c++)
    {
        Console.Write(c);   // ABCDEFGHIJKLMNOPQRSTUVWXYZ
    }

    'A' 와 "A" 는 다르다

    앞에서 잠깐 봤던 이야기를 여기서 마무리합니다.

    따옴표 담는 것 크기
    char 작은따옴표 'A' 문자 하나 2바이트 고정
    string 큰따옴표 "A" 글자들 글자 수에 따라 다름
    char c = 'A';       // 됩니다
    char c = "A";       // 컴파일 에러
    char c = 'AB';      // 컴파일 에러. 하나만 들어갑니다
    
    string s = "A";     // 됩니다
    string s = "";      // 빈 글도 됩니다

    char 는 한 칸이고 string 은 묶음입니다. 빈 char 는 없지만 빈 string 은 있습니다.

    화면에 안 보이는 문자들

    아스키 표의 0~31번은 모양이 없는 문자입니다. 줄을 바꾸거나 탭으로 띄우라는 신호입니다.

    키보드로 칠 수 없으니 역슬래시로 적습니다. 이걸 이스케이프 문자라고 합니다.

    적는 법 뜻 번호
    \n 줄바꿈 10
    \t 탭 9
    \\ 역슬래시 문자 그 자체 92
    \" 큰따옴표 34
    \' 작은따옴표 39
    Console.WriteLine("첫 줄\n둘째 줄");
    Console.WriteLine("이름\t점수");
    Console.WriteLine("그가 \"안녕\" 이라고 했다");

    역슬래시가 특별한 뜻을 가지므로, 역슬래시 자체를 쓰려면 두 번 써야 합니다. 파일 경로에서 이게 성가십니다.

    string path = "C:\\Users\\Documents";     // 두 번씩
    string path = @"C:\Users\Documents";      // @ 를 붙이면 그대로

    @ 를 앞에 붙이면 역슬래시를 특별하게 보지 않습니다. 경로를 쓸 때 편합니다.

    정리

    • 컴퓨터는 글자를 모른다. 글자마다 번호를 붙여두고 그 번호를 저장한다.
    • 아스키코드는 그 번호를 정한 표다. 7비트로 128자를 담았다.
    • 'A' 는 65, 'a' 는 97, '0' 은 48이다. 같은 종류는 번호가 이어져 있다.
    • 대문자와 소문자는 비트 하나 차이다. 그 자릿값이 32다.
    • 128자로 모자라서 유니코드가 나왔다. 아스키 번호는 그대로 물려받았다.
    • 유니코드는 번호를 정한 표고, UTF-16 은 그 번호를 바이트로 적는 방법이다.
    • char 는 UTF-16 한 조각이라 2바이트다.
    • 'A' 는 char, "A" 는 string 이다. 따옴표가 다르다.