11. Customizing Input and Output

11.1. Regularity and irregularity

프로그래머의 입장에서 입출력을 정규화하는 것이 효율적이지만 사용자의 선호도는 그렇지 않을 수 있으므로 균형을 맞추는 것이 중요하다.

11.2. Output formatting

사람에 따라 출력의 작은 차이는 큰 의미 차이가 될 수 있다. 이를 위해 출력을 균일하게 형식화하는 것은 중요하다.

11.2.1. Integer output

정수형 출력은 8진, 10진, 16진일 수 있는데 보통은 10진을 많이 쓴다. 8진, 16진은 std::oct, std::hex를 쓰면 된다.

std::cout << 1234 << '\t' << std::hex << 1234 << '\t' << std::oct << 1234 << '\n';
std::cout << std::showbase << std::dec;          // show bases
std::cout << 1234 << '\t' << std::hex << 1234 << '\t' << std::oct << 1234 << '\n';

std::showbase는 0, 0x등을 보여주면서 출력한다. std::noshowbase는 이를 되돌린다.

11.2.2. Integer input

std::oct, std::hex 등의 스트림 조정자들은 입력 스트림에도 적용된다. .unsetf() 함수를 이용해서 스트림 조정자 없이 0, 0x 접두사를 붙여서 입력시키는 것도 가능하다.

int a = b = c = d = 0;
std::cin >> a >> std::hex >> b >> std::oct >> c >> d;
std::cout << a << '\t' << b << '\t' << c << '\t' << d << '\n';

std::cin.unsetf(std::ios::dec);  // don’t assume decimal (so that 0x can mean hex)
std::cin.unsetf(std::ios::oct);   // don’t assume octal (so that 12 can mean twelve)
std::cin.unsetf(std::ios::hex);  // don’t assume hexadecimal (so that 12 can mean twelve)
std::cin >>a >> b >> c >> d;

11.2.3. Floating-point output

부동 소수점의 경우엔 std::fixed, std::scientific의 스트림 조정자를 사용한다.

std::cout << 1234.56789 << '\t'
          << std::fixed << 1234.56789 << '\t'
          << std::scientific << 1234.56789 << '\n';
std::cout << 1234.56789 << '\n';                               // floating format “sticks”
std::cout << std::defaultfloat << 1234.56789 << '\t'      // the default format for floating-point output
          << std::fixed << 1234.56789 << '\t'
          << std::scientific << 1234.56789 << '\n';

11.2.4. Precision

유효 숫자 개수는 std::setprecision을 이용한다.

std::cout << 1234.56789 << '\t'
          << std::fixed << 1234.56789 << '\t'
          << std::scientific << 1234.56789 << '\n';
std::cout << std::defaultfloat << std::setprecision(5)
          << 1234.56789 << '\t'
          << std::fixed << 1234.56789 << '\t'
          << std::scientific << 1234.56789 << '\n';
std::cout << std::defaultfloat << std::setprecision(8)
          << 1234.56789 << '\t'
          << std::fixed << 1234.56789 << '\t'
          << std::scientific << 1234.56789 << '\n';

11.2.5. Fields

출력 필드의 너비를 결정할 때는 std::setw를 이용한다. 이는 바로 직후 출력 한 번에 한해 적용됨을 유의하라.

std::cout << 12345 <<'|'<< std::setw(4) << 12345 << '|'
          << std::setw(8) << 12345 << '|' << 12345 << "|\n";
std::cout << 1234.5 <<'|'<< std::setw(4) << 1234.5 << '|'
          << std::setw(8) << 1234.5 << '|' << 1234.5 << "|\n";
std::cout << "asdfg" <<'|'<< std::setw(4) << "asdfg" << '|'
          << std::setw(8) << "asdfg" << '|' << "asdfg" << "|\n";

11.3. File opening and positioning

C++에서 파일 입출력은 바이트 스트림으로 이루어진다.

11.3.1. File open modes

파일 입출력 스트림으론 std::ifstream, std::ofstream을 사용한다. 파일을 여는 모드는 std::ios_base::app (덧붙임), ate (파일 끝에서 시작), binary (바이너리 파일로 염 – 시스템별 동작 변화에 유의하라) in (기본 입력), out (기본 출력), trunc (파일 길이를 자름) 등이 있다. 스트림 생성의 성공은 스트림에 !로 검사한다.

std::ofstream of1 {name1};                                   // defaults to ios_base::out
std::ifstream if1 {name2};                                      // defaults to ios_base::in

std::ofstream ofs {name, std::ios_base::app};        // ofstreams by default include                                                                                 std::ios_base::out
std::fstream fs {"myfile", std::ios_base::in | std::ios_base::out};            // both in and out

std::ofstream ofs {"no-such-file"};                      // create new file called no-such-file
std::ifstream ifs {"no-file-of-this-name"};          // error: ifs will not be good()
if (!ifs) std::cout << "oops"

파일 출력 스트림은 파일이 존재하지 않을 시 파일을 생성하지만, 입력 스트림은 그러지 않는다.

11.3.2. Binary files

기본적으로 ifstream과 ofstream은 파일을 바이트가 아닌 문자로 받거나 출력하지만 직접 바이트를 다루게 할 수도 있다. std::ios_base::binary를 쓰면 된다.

int main() {
    // open an istream for binary input from a file:
    std::cout << "Please enter input file name\n";
    std::string iname;
    std::cin >> iname;
    std::ifstream ifs {iname, std::ios_base::binary};             // note: stream mode
    // binary tells the stream not to try anything clever with the bytes
    if (!ifs) throw std::runtime_error("can't open input file ", iname);

    // open an ostream for binary output to a file:
    std::cout << "Please enter output file name\n";
    std::string oname;
    std::cin >> oname;
    std::ofstream ofs {oname, std::ios_base::binary};        // note: stream mode
    // binary tells the stream not to try anything clever with the bytes
    if (!ofs) throw std::runtime_error("can't open output file ", oname);

    std::vector<int> v;

    // read from binary file:
    for(int x; ifs.read(std::as_bytes(std::span(&x, 1)), sizeof(int)); )   // note: reading bytes
        v.push_back(x);

    // . . . do something with v . . .

    // write to binary file:
    for(int x : v)
        ofs.write(std::as_bytes(std::span(&x, 1)), sizeof(int));         // note: writing bytes
    return 0;
}

바이너리 파일 입출력은 코드가 지저분해지고 버그의 위험이 높다. 정말 필요할 때가 아니라면 문자 파일 입출력을 사용하라.

11.3.3. Positioning in files

웬만하면 파일을 쓸 때 중간에 쓰지 말고 파일 끝에 출력하라. 파일 중간에 써서 수정하는 것보다는 아예 파일을 새로 만드는 것이 낫다. 하지만 꼭 필요하다면 .seekg 함수를 사용한다.

std::fstream fs {name};        // open for input and output
if (!fs) throw std::runtime_error("can't open ", name);

fs.seekg(5);           // move reading position (g for “get”) to 5 (the 6th character)
char ch;
fs >> ch;                  // read and increment reading position
cout << "character[5] is " << ch << ' (' << int(ch) << ")\n";

fs.seekp(1);           // move writing position (p for “put”) to 1
fs << 'y';                  // write and increment writing position

주의할 점은 seekg나 seekp로 잘못된 위치를 가리키게 될 경우의 행동은 정의되어 있지 않다. 런타임 에러 체킹이 없으므로 이를 보장하는 것은 프로그래머의 몫이다.

11.4. String streams

std::string을 처리하는 입출력 스트림은 std::istringstream, std::ostringstream이 있다. 이는 주로 문자열 처리와 입출력을 분리하고 싶을 때 쓰인다.

double str_to_double(const std::string& s) { // if possible, convert characters in s to floating-point value
    std::istringstream is {s};                // make a stream so that we can read from s
    double d = 0.0;
    is >> d;
    if (!is) std::runtime_error("double format error: ",s);
    return d;
}

double d1 = str_to_double("12.4");                                 // testing
double d2 = str_to_double("1.34e–3");
double d3 = str_to_double("twelve point three");       // will call error()

int seq_no = get_next_number();                 // get the number of a log file
std::ostringstream name;
name << "myfile" << seq_no << ".log";    // e.g., myfile17.log
std::ofstream logfile{name.str()};                       // e.g., open myfile17.log

11.5. Line-oriented input

std::istream은 기본적으로 공백 문자가 나타나면 입력을 끊는다. 개행 문자에서 끊고 싶으면 std::getline을 쓴다.

std::string command;
std::getline(std::cin, command);                         // read the line

std::stringstream ss {command};
std::vector<std::string> words;
for (std::string s; ss >> s; )
    words.push_back(s);     // extract the individual words

11.6. Character classification

std::isspace()는 문자가 공백인지를, std::isdigit()은 자릿수인지를, std::isalpha()는 해당 로케일에서의 알파벳인지를 체크한다. std::tolower()는 소문자로 변환하고, std::toupper()는 대문자로 변환한다. 독일어 같은 언어에서는 std::toupper()가 제대로 동작하지 않을 수 있음을 주의하라.

for (char ch; std::cin.get(ch); ) {
    if (std::isspace(ch)) {        // if ch is whitespace
        // do nothing (i.e., skip whitespace)
    }
    if (std::isdigit(ch)) {
        // read a number
    } else if (std::isalpha(ch)) {
        // read an identifier
    } else {
        // deal with operators
    }
}

11.7. Using nonstandard separators

std::istream은 공백 문자 이외의 다른 문자를 공백으로 선언하는 방법을 제공하지 않는다. 이에 대처하려면 다음과 같은 커스텀 클래스를 정의하는 수밖에 없다.

class Punct_stream {          // like an istream, but the user can add to the set of whitespace characters
public:
    Punct_stream(std::istream& is) : source{is}, sensitive{true} { }
    void whitespace(const std::string& s) { white = s; }            // make s the whitespace set
    void add_white(char c) { white += c; }    // add to the whitespace set
    bool is_whitespace(char c);                      // is c in the whitespace set?
    void case_sensitive(bool b) { sensitive = b; }
    bool is_case_sensitive() { return sensitive; }

    Punct_stream& operator>>(std::string& s);
    operator bool();
private:
    std::istream& source;               // character source
    std::istringstream buffer;         // we let buffer do our formatting
    std::string white;                        // characters considered “whitespace”
    bool sensitive;                    // is the stream case-sensitive?
};

Punct_stream ps {std::cin};           // ps reads from cin
ps.whitespace(";:.");             // semicolon, colon, and dot are also whitespace
ps.case_sensitive(false);        // not case-sensitive

Punct_stream& Punct_stream::operator>>(std::string& s) {
    while (!(buffer >> s)) {                                  // try to read from buffer
        if (buffer.bad() || !source.good()) return *this;
        buffer.clear();

        std::string line;
        std::getline(source, line);                       // get a line from source

        // do character replacement as needed:
        for (char& ch : line)
            if (is_whitespace(ch)) {
                ch = ' '; // to space
            } else if (!sensitive) {
                ch = std::tolower(ch); // to lowrecase
            }
        buffer.str(line);                               // put string into stream
    }
    return *this;
}

bool Punct_stream::is_whitespace(char c) {
    return white.find_first_of(c) != std::string::npos;
}

Punct_stream::operator bool() {
    return !(source.fail() || source.bad()) && source.good();
}

이것은 std::istream처럼 동작하지만 std::istream을 상속하지는 않기 때문에 완전한 커스텀 입력 스트림처럼 동작시킬 수는 없다. std::istream을 상속시켜 동작하게 하는 법은 나중에 배운다.

11.8. And there is so much more

입출력 스트림에는 더 많은 디테일이 있다. 이에 대해서는 더 고급 서적을 참조하라.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중