본문 바로가기
C++/문법 정리

Chapter 01. 입출력 기초

by GameStudy 2022. 4. 28.

1.1 출력

  1.1-1 Hello, world!

    Ex01010101) 

<hide/>

// main.cpp

#include <iostream>

int main()
{
    std::cout << "Hello, world!" << std::endl;

    return 0;
}

 

  1.1-2 std::cout과 std::endl

    Note) iostream

      input output stream의 약어.

      C에서는 stdio.h 파일이 이와 동일한 기능을 함.

      std::cout과 std::endl이 정의되어 있음.

 

    Def) std::cout

      std는 standard를 뜻함. cout은 console out의 의미.

      즉, 표준 출력의 의미. C에서의 printf()와 유사함.

 

    Def) << 연산자(Stream Insertion Operator)

      스트림으로 피연산자를 밀어넣는 연산자.

      추후에 이 연산자의 동작을 바꿔보는 예제를 배움.

 

    Def) std::endl

      endl은 end line의 약어.

      C에서 '\n'(new line)과 유사한 의미.

 

  1.1-3 namespace

    Def) namespace

      변수/함수/클래스 등의 이름 충돌을 피하기 위해서

      사용하는 키워드. 동일한 이름이어도,

      namespace가 다르면 사용 가능함. 

      마치 폴더가 다르면 같은 이름의 .txt이 가능한 것 처럼.

 

    Ex01010301) 

<hide/>

// main.cpp

#include <iostream>

namespace GUnivercity {
    int student_count = 3000;
}

namespace WUnivercity {
    int student_count = 5000;
}

int main()
{
    std::cout << "Hello, " << "namespace!" << std::endl;

    std::cout << "GUnivercity's student count: " << GUnivercity::student_count << std::endl
              << "WUnivercity's student count: " << WUnivercity::student_count << std::endl;

    return 0;
}

 

    Ex01010302)

<hide/>

// hello.h

#pragma once

namespace hello {
    void greet(void);
}
<hide/>

// hello.cpp

#include <iostream>

#include "hello.h"

void hello::greet()
{
    std::cout << "Hello, world!" << std::endl;
}
<hide/>

// hi.h

#pragma once

namespace hi {
    void greet(void);
}
<hide/>

// hi.cpp

#include <iostream>

#include "hi.h"

void hi::greet()
{
    std::cout << "Hi, world!" << std::endl;
}
<hide/>

// main.cpp

#include <iostream>

#include "hello.h"
#include "hi.h"

void greet();

int main()
{
    // :: 연산자는 namespace를 지정해 주는 연산자.
    hello::greet();
    hi::greet();

    // 만약 namespace를 지정하지 않고, ::greet()이라고 하면 전역을 의미함.
    ::greet();

    return 0;
}

void greet()
{
    std::cout << "Hello, global!" << std::endl;
}

 

  1.1-4 using

    Def) using 지시문

      타이핑의 양을 줄이기 위해서 사용하는 지시문.

      C에서는 typedef 키워드와 비슷함. 그 이상의 기능을 겸함.

      ex. typedef long long int int64_t

 

    Ex01010401) 

<hide/>

// main.cpp

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello, using!" << endl;

    return 0;
}

 

  1.1-5 조정자

    Def) 조정자(Manipulator)

      기존 C에서의 형식 지정자(Format specifier)와 유사함.

      

    Ex01010501) 

<hide/>

// main.cpp

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    int num = 15;

    cout << hex << num << endl;
    cout << showbase << num << endl;    // 이미 cout에는 hex가 지정되어 있음.
    cout << noshowbase <<  num << endl;

    return 0;
}

 

    Ex01010502) 

<hide/>

// main.cpp 

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    int num = 123;

    cout << showpos << num << endl;
    cout << noshowpos << num << endl;
    cout << dec << num << endl;
    cout << hex << num << endl;
    cout << oct << num << endl;
    cout << uppercase << hex << num << endl;
    cout << nouppercase << hex << num << endl;
    cout << showbase << oct << num << endl;
    cout << noshowbase << oct << num << endl;
    cout << dec;

    int minus_num = -123;

    // setw()는 iomanip에 정의되어 있음.
    cout << '[' << setw(6) << minus_num << ']' << endl;
    cout << '[' << setw(6) << left << minus_num << ']' << endl;
    cout << '[' << setw(6) << internal << minus_num << ']' << endl;
    cout << '[' << setw(6) << right << minus_num << ']' << endl;
    cout << '[' << setw(6) << setfill('0') << minus_num << ']' << endl;

    float num1 = 123.0f;
    float num2 = 123.456f;

    cout << showpoint << num1 << ' ' << num2 << endl;
    cout << noshowpoint << num1 << ' ' << num2 << endl;
    cout << fixed << num2 << endl;
    cout << scientific << num2 << endl;
    cout << fixed << setprecision(2) << num2 << endl;

    bool bReady = true;

    cout << boolalpha << bReady << endl;
    cout << noboolalpha << bReady << endl;

    return 0;
}

 

    Ex01010503) 

<hide/>

// print.h

#pragma once

namespace print {
    void printMenu();
}
<hide/>

// print.cpp

#include <iostream>

#include "print.h"

using namespace std;

void print::printMenu()
{
    int num = 10;
    float fnum = 3.141592f;
    char ch = 'C';
    char string[] = "Hello, world!";

    cout << num << endl;
    cout << fnum << endl;
    cout << ch << endl
        << string << endl;
}
<hide/>

// main.cpp

#include "print.h"	

int main()
{
    print::printMenu();

    return 0;
}

 

  1.1-6 cout 멤버 메서드

    Note) cout 멤버 메서드 종류

       setf(), unsetf(), width(), fill(), precision() 메서드가 있음.

cout.setf(ios_base::showpos);
cout << num;
cout.width(6);
cout << num;

 

    Note) setf(flag) / unsetf(flag)    

      flag 매개변수에는 아래와 같은 인자를 전달 가능.

      boolalpha / showpos / uppercase / showpos

 

    Note) 그리고 setf(flag, flag)로 사용시에는

      아래와 같은 인자를 전달할 수 있음.

첫 번째 인자 두 번째 인자
dec basefield
oct
hex
fixed floatfield
scientific
left adjustfield
right
internal

 

 

1.2 입력

  1.2-1 입력 스트림

    Def) std::cin

      C의 scanf()와 유사함.

 

    Def) >> 연산자

      입력 스트림으로부터 데이터를 가져옴.

 

    Ex01020101) 

<hide/>

// main.cpp

#include <iostream>

using namespace std;

int main()
{
    int score;
    cout << "Answer your score: ";
    cin >> score;
    cout << "Your score is: " << score << endl;

    float height;
    cout << "What is your height: ";
    cin >> height;
    cout << "Your height is: " << height << endl;

    return 0;
}

 

    Note) scanf()는 위험하다.

      아래와 같은 C 코드는 위험했음. 

      scanf()는 경계검사를 하지 않기 때문.

char firstName[4];
char secondName[5];
scanf("%s", firstName);
scanf("%s", secondName);

 

    Note) 그래서 C에서는 엄청 복잡하게

      fgets() + sscanf() + strlen() + strcpy()를 사용함.

 

    Note) cin도 똑같은 문제가 발생함.

      char[]도 char*와 같기 때문에,

      char 배열의 길이는 모름.

      scanf()와 똑같은 메모리 할당 이슈가 발생함.

      이 문제를 고치기 위해서는 아래와 같이 작성함.

 

    Ex01020101)

<hide/>

// main.cpp

#include <iostream>
#include <iomanip>

using namespace std;

enum { LENGTH = 4 };

int main()
{
    char string[LENGTH] = { 0, };

    cout << "Message: ";
    cin >> setw(LENGTH) >> string;
    /*
        버퍼 사이즈가 LENGTH 밖에 안된다고 적어줌.
        그럼 알아서 3글자만 읽고, 마지막에 '\0'까지 넣어줌.
    */

    cout << string << endl;

    return 0;
}

 

  1.2-2 스트림 상태

    Ex01020201) 

      "    test    123"을 대입하면 어떤 값이 저장될까?

<hide/>

// main.cpp

#include <iostream>

using namespace std;

enum { LENGTH = 16 };

int main()
{
    char string[LENGTH] = { 0, };
    int num;

    cin >> string >> num;
    cout << string << ' ' << num << endl;

    return 0;
}

 

    Note) C++에서는 현재 스트림의 상태가

      어떤 상태인지 체크할 수 있음.

      C에서는 아래와 같이 체크했었음.

if (fgets(line, 10, stdin) != NULL) { ... }

      그러나 위와 같이 작성하면, "NULL이 아니다"의

      의미가 사람마다 다를 수 있음. 

      그래서 C++에서는 아래와 같이 작성함.

if (!cin.eof()) { ... }

 

    Note) C++에는 아래와 같은 istream 상태가 있음.

      namespace: ios_base

      bit flag: goodbit / eofbit / failbit / badbit

      method version: good() / eof() / fail() / bad()

 

    Ex01020202) 

      아래 소스코드의 실행 결과에 아래 입력 값들을 입력해 보자.

      "123abc" / "123" / "abc" / "^Z"(eof)

<hide/>

// main.cpp 

#include <iostream>

using namespace std;

enum { COUNT = 4 };

int main()
{
    int num, loopCount = 4;

    while (loopCount != 0)
    {
        cin >> num;

        cout << "input: " << num
             << " // good bit: " << cin.good()
             << " // eof bit: " << cin.eof()
             << " // fail bit: " << cin.fail()
             << " // bad bit: " << cin.bad() << endl;

        --loopCount;
    }

    return 0;
}

 

  1.2-3 입력 버리기

    Note) 이전 예제에서 우리가 생각하던 대로

      동작하지 않는걸 볼 수 있었음.

      그 이유는 입력 버퍼에 데이터가 남아 있기 때문.

      그리고 입력 스트림 상태가 초기화되지 않았기 때문.

 

    Def) clear()

      스트림을 good state로 돌려 놓아줌.

cin.clear();

 

    Ex01020301)

      아래 소스 코드의 실행 결과에 ctrl + z를 입력 해 보자.

<hide/>

// main.cpp

#include <iostream>

using namespace std;

enum { COUNT = 4 };

int main()
{
    int num;

    cin >> num;

    cout << "input: " << num << boolalpha
         << " // good bit: " << cin.good()
         << " // eof bit: " << cin.eof()
         << " // fail bit: " << cin.fail()
         << " // bad bit: " << cin.bad() << endl;

    cin.clear();
    cout << "input: " << num << boolalpha
         << " // good bit: " << cin.good()
         << " // eof bit: " << cin.eof()
         << " // fail bit: " << cin.fail()
         << " // bad bit: " << cin.bad() << endl;

    return 0;
}

 

    Def) ignore()

      파일 끝에 도달하거나, 지정한 개수 만큼 문자를 입력 버퍼에서 버림.

 

    Ex01020302) 

<hide/>

// main.cpp

#include <iostream>

using namespace std;

enum { COUNT = 4 };

int main()
{
    int num, loopCount = 4;

    while (loopCount != 0)
    {
        cin >> num;

        cout << "input: " << num
             << " // good bit: " << cin.good()
             << " // eof bit: " << cin.eof()
             << " // fail bit: " << cin.fail()
             << " // bad bit: " << cin.bad() << endl;
		
        cin.ignore(LLONG_MAX, '\n');
        // 최대 문자 개수(LLONG_MAX)만큼 버림.
        // 단, 그전에 '\n' 문자를 만나면 곧바로 멈춤.
        cin.clear();

        --loopCount;
    }

    return 0;
}

 

입력값 goodbit eofbit failbit badbit
123abc set unset unset unset
123 set (un)set unset unset
abc unset unset set unset
^Z unset set set unset

 

    Ex01020303) 

<hide/>

// myMath.h

#pragma once

namespace myMath {
    void sum();
}
<hide/>

// myMath.cpp

#include <iostream>

#include "myMath.h"

using namespace std;

void myMath::sum()
{
    int input;
    int sum = 0;

    while (true) {
        cout << "Enter an integer or EOF: ";
        cin >> input;

        if (cin.eof()) {
            cout << "Receiving EOF." << endl;
            break;
        }

        if (cin.fail()) {
            cout << "Invalid input." << endl;
            cin.ignore(LLONG_MAX, '\n');
            cin.clear();

            continue;
        }

        sum += input;

    }

    cin.ignore(LLONG_MAX, '\n');
    cin.clear();    
    cout << "The sum is: " << sum << endl;
}
<hide/>

// main.cpp

#include "myMath.h"

int main()
{
    myMath::sum();

    return 0;
}

 

  1.2-4 get() / getline()

    Def) get()

      '\n' 문자를 만나기 직전까지의 모든 문자를 가져옴.

      단, '\n' 문자는 입력 버퍼에 그대로 남겨둠.

get(firstName, 100);
// 99개 문자를 가져오거나,
// '\n' 문자가 나올 때까지의 문자를 가져와서
// firstName 배열에 배치.

get(firstName, 100, '!')
// 99개 문자를 가져오거나,
// '!' 문자가 나올 때까지의 문자를 가져와서
// firstName 배열에 배치.

 

    Def) getline()

      '\n' 문자를 만나기 직전까지의 모든 문자를 가져옴.

      '\n' 문자는 입력 버퍼에서 꺼냄.

 

     Ex01020401)

<hide/>

// myString.h

#pragma once

namespace myString {
    void reverseString();
}
<hide/>

// myString.cpp

#include <iostream>

#include "myString.h"

using namespace std;

enum { LENGTH = 16 };

void myString::reverseString()
{
    char line[LENGTH] = { 0, };

    cout << "Enter a string to reverse " << endl
        << "or EOF to quit: ";
    cin.getline(line, LENGTH);

    if (cin.fail()) {
        cout << "fail bit is on." << endl;
        cin.clear();

        return;
    }

    char* p = line;
    char* q = line + strlen(line) - 1;

    while (p < q) {
        char temp = *p;
        *p = *q;
        *q = temp;
        ++p;
        --q;
    }

    cout << "Reversed string: " << line << endl;
}
<hide/>

/* main.cpp */

#include "myString.h"

int main()
{
    myString::reverseString();

    return 0;
}

    

 

1.3 새로운 C++ 기능

  1.3-1 초기 C++의 새로운 기능들

    Note) 전혀 사용하지 않는 기능들이 매우 많음.

      현재에는 오직 소수의 기능들만 알면 됨.

      bool 자료형(C++98, 모던 C++)

      참조(Reference)

      개체지향 프로그래밍(Object-Oriented Pro.)

 

    Note) 최신 기준 C++ 표준에도 

      새로운 기능들이 매우 많음.

      C++ 표준은 너무 많은 사람들이 개입함. 

      C++ 표준 위원회도 모든 사람을 만족시키려함.

      그 덕에 이리 많은 기능들이 나왔음.

 

    Note) C++의 진짜 전문가는 사용하지

      말아야 할 기능을 아는 사람.

 

  1.3-2 bool 자료형

    Note) C에서는 '0이거나 0이 아니거나'로

      거짓이거나 참으로 나누었음.

 

    Note) C++에서는 true / false 키워드로

      이를 명확하게 나눔. 이를 bool 자료형이라고 함.

 

  1.3-3 참조(Reference)

    Def) 참조(Reference)

      포인터를 사용하는 좀 더 안전한 방법.

      포인터를 사용하는 부분인데, 주소 연산이 없어도 될때

      참조를 사용하면 좀 더 안전한 코드가 됨.

      그러나 결국 컴퓨터에겐 포인터와 똑같음. 

 

    Note) 참조를 이해하기 위해서는 아래 내용을 살펴봐야함.

      1. 값에 의한 호출.

      2. 참조에 의한 호출

      3. 포인터

 

    Ex01030301)

<hide/>

// swap.h 

#pragma once

namespace swap {
    void swapWithValue(int op1, int op2);
    void swapWithPointer(int* op1, int* op2);
}
<hide/>

// swap.cpp

#include <iostream>

#include "swap.h"

using namespace std;

void swap::swapWithValue(int op1, int op2)
{
    cout << "swapWithValue() has been called." << endl;
    int temp = op1;
    op1 = op2;
    op2 = temp;
}

void swap::swapWithPointer(int* op1, int* op2)
{
    cout << "swapWithPointer() has been called." << endl;
    int temp = *op1;
    *op1 = *op2;
    *op2 = temp;
}
<hide/>

// main.cpp

#include <iostream>

#include "swap.h"

using namespace std;

int main()
{
    int a = 10, b = 20;
    int& ref_of_a = a;
    int& ref_of_b = b;

    cout << "a: " << a << " // b: " << b << endl;
    swap::swapWithValue(a, b);
    cout << "a: " << a << " // b: " << b << endl;
    swap::swapWithPointer(&a, &b);
    cout << "a: " << a << " // b: " << b << endl;

    return 0;
}

 

    Note) 포인터의 문제점

      1. 널 포인터 참조

        참조는 널 포인터를 대입조차 할 수 없음.

      2. 댕글링 포인터

        case 1. 동적할당 후 free() 된 메모리 참조

          애초에 참조 변수에는 lvalue만 대입 가능.

        case 2. 함수 A에서 선언 후, 함수 B에서 동적할당하고 free()

          참조는 반드시 선언과 동시에 초기화 되어야 하기 때문에 안됨.

      즉, 포인터를 안전하게 사용할 수 있는 방법.

 

    Ex01030302) 

<hide/>

// main.cpp

#include <iostream>

using namespace std;

int main()
{
    int a = 10, b = 20;
    int& reference_of_a = a;
    // int& reference_of_NULL = NULL;
    // int& reference;

    cout << "a: " << a << " // b: " << b << " // reference_of_a: " << reference_of_a << endl;
    reference_of_a = b; // 어떤 일이 벌어질까? 이제 b를 참조하게 될까?
    cout << "a: " << a << " // b: " << b << " // reference_of_a: " << reference_of_a << endl;
    reference_of_a += 1;
    cout << "a: " << a << " // b: " << b << " // reference_of_a: " << reference_of_a << endl;

    return 0;
}

 

    Ex01030303) 

<hide/>

// swap.h

#pragma once

namespace swap {
    void swapWithValue(int op1, int op2);
    void swapWithPointer(int* op1, int* op2);
    void swapWithReference(int& op1, int& op2);
}
<hide/>

// swap.cpp

#include <iostream>

#include "swap.h"

using namespace std;

void swap::swapWithValue(int op1, int op2)
{
    cout << "swapWithValue() has been called." << endl;
    int temp = op1;
    op1 = op2;
    op2 = temp;
}

void swap::swapWithPointer(int* op1, int* op2)
{
    cout << "swapWithPointer() has been called." << endl;
    int temp = *op1;
    *op1 = *op2;
    *op2 = temp;
}

void swap::swapWithReference(int& op1, int& op2)
{
    cout << "swapWithReference() has been called." << endl;
    int temp = op1;
    op1 = op2;
    op2 = temp;
}
<hide/>

// main.cpp

#include <iostream>

#include "swap.h"

using namespace std;

int main()
{
    int a = 10, b = 20;
    int& ref_of_a = a;
    int& ref_of_b = b;

    cout << "a: " << a << " // b: " << b << endl;
    swap::swapWithValue(a, b);
    cout << "a: " << a << " // b: " << b << endl;
    swap::swapWithPointer(&a, &b);
    cout << "a: " << a << " // b: " << b << endl;
    swap::swapWithReference(ref_of_a, ref_of_b);
    cout << "a: " << a << " // b: " << b << endl;

    return 0;
}

 

    Note) 참조형을 반환 자료형으로 갖는 함수

      아래 함수는 문제가 없을까? 있다면 왜 있을까?

      함수의 지역변수 주소를 반환하기 때문에, 댕글링 포인터 문제 발생.

      다만 지역변수가 아니라면 상관없음.

int& return_reference()
{
    int temp = 10;
    
    ++temp;

    return temp;
}

 

    Ex01030304)

<hide/>

#include <iostream>

using namespace std;

int& return_reference(int& ref);

int main()
{
    int num1 = 10;
    int& num2 = return_reference(num1);
    
    ++num1;
    ++num2;
    
    cout << "num1 is " << num1 << endl; // 예측 해 보자.
    cout << "num2 is " << num2 << endl; // 예측 해 보자.

    return 0;
}

int& return_reference(int& ref)
{
    ++ref;
    return ref;
}

 

    Ex01030305) 

<hide/>

#include <iostream>

using namespace std;

int& return_reference(int& ref);

int main()
{
    int num1 = 10;
    int num2 = return_reference(num1);
    
    ++num1;
    ++num2;
    
    cout << "num1 is " << num1 << endl; // 예측 해 보자.
    cout << "num2 is " << num2 << endl; // 예측 해 보자.

    return 0;
}

int& return_reference(int& ref)
{
    ++ref;
    return ref;
}

 

    Ex01030306)

<hide/>

#include <iostream>

using namespace std;

int return_reference(int& ref);

int main()
{
    int num1 = 10;
    int num2 = return_reference(num1);
    
    ++num1;
    ++num2;
    
    cout << "num1 is " << num1 << endl; // 예측 해 보자.
    cout << "num2 is " << num2 << endl; // 예측 해 보자.

    return 0;
}

int return_reference(int& ref)
{
    ++ref;
    return ref;
}

 

    Note) Ex01030305와 Ex01030306은 무슨 차이가 있을까?

      Ex01030305의 return_reference() 함수의 반환값은 참조형에 저장이 가능함.

      Ex01030305의 return_reference() 함수의 반환값은 불가능함.

      상수나 다름없기 때문에 참조형에 저장은 컴파일 에러가 남.

 

    Note) 참조형을 매개변수 자료형으로 갖는 함수

      불필요한 값복사가 일어나지 않으면서도 원본에 대한 접근이 가능.

      null 대입이 불가능하므로, 함수 내부에선 언제나 유효한 값을 사용 가능.

      또한 참조형은 주소 연산이 불가능함. 포인터는 주소연산이 가능해서

      소유하지 않은 메모리까지 접근해버릴 수 있음.

 

    Ex01030307)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

void fooV(int b)
{
    // fooV() 함수의 스택 프레임에는 b와 c의 메모리 공간이 생성됨.
    // main() 함수에서 fooV()가 pass by value로 호출되고, b에는 a의 값이 복사됨.
    int c = b + 1;
}

void fooP(int* b)
{
    // fooP() 함수의 스택 프레임에는 b와 c의 메모리 공간이 생성됨.
    // main() 함수에서 fooP()가 pass by pointer로 호출되고, b에는 a의 메모리 주소가 복사됨.
    int c = *b + 1;
}

void fooR(int& b)
{
    // fooP() 함수의 스택 프레임에는 b와 c의 메모리 공간이 생성됨.
    // main() 함수에서 fooR()가 pass by reference로 호출되고, b에는 a의 메모리 주소가 복사됨.
    // 즉, pass by pointer와 pass by reference가 같다. 
    // compile explorer에서 x86-64 gcc 9.3 컴파일러 기준, -O0 플래그를 걸고 보면 어셈블리어 코드도 똑같은걸 알 수 있음.
    int c = b + 1;
}

int main(void)
{
    // main() 함수의 스택 프레임에는 a의 메모리 공간이 생성됨.
    int a = 0;
    fooV(a);
    fooP(&a);
    fooR(a);

    return 0;
}

 

  1.3-4 포인터와 참조에 관한 코딩 표준

    Note) 함수 매개변수로 포인터 Vs. 참조

      포인터가 꼭 필요한 경우에만 매개변수 자료형으로 쓰자.

      포인터를 그대로 노출시키게 되면 원본이 위험할 수 있음.

      즉, 기본값으로 참조를 함수 매개변수로 쓰자.

      원시 자료형도 불필요한 값복사가 이뤄질 수 있으므로 자제.

 

    Note) 만약 아래와 같은 코드가 있다고 가정 해 보자.

      어떤 매개변수가 출력 결과로 쓰일까?

bool TryDivide(int& a, int& b, int& c);

 

    Note) 시도1: 매개변수 이름을 더 잘지어보자.

bool TryDivide(int& result, int& operand1, int& operand2);

      하지만 호출자가 여전히 실수 할 수 있음.

bool success = TryDivide(x, y, result);

 

    Note) 시도2: 읽기전용 매개변수를 상수화하자.

bool TryDivide(int& result, const int& operand1, const int& operand2);

      물론 이렇게 하면 위 시도 1번의 문제점은 해결됨.

      그러나 아래와 같은 것은 막지 못함. 뭐가 출력 결과인지 모름.

bool success = TryDivide(x, y, z);

 

    Note) 시도3: 읽기전용 매개변수는 상수 참조로,

      출력전용 매개변수는 포인터로 하자.

bool TryDivide(int* result, const int& operand1, const int& operand2);

      다만, 이때 result 매개변수로 NULL을 전달할 수 있음.

      이는 assert() 함수로 막아주도록 하자.

bool success = TryDivide(&x, y, z);

 

    Note) 반환자료형에는 참조형을 써야할 때가 있음.

      대표적으로 배열 첨자 연산자.

      아래와 같은 경우가 가능하게끔 하려면 참조형을 써야함.

<hide/>

myArray a;
a[1] = 10;    // 값의 수정.
a[2] = a[1];  // 값의 읽기.

 

1.4 파일 입출력

  1.4-1 fstream

    Note) 파일 관련 스트림

      - ifstream: 파일 입력 스트림

ifstream fin;
fin.open("helloworld.txt"); // 읽기 전용으로 파일 오픈.(파일이 없으면 에러.)

      - ofstream: 파일 출력 스트림

ofstream fout;
fout.open("helloworld.txt"); // 쓰기 전용으로 파일 오픈.(파일이 없으면 만듦.)

      - fstream: 파일 입력 및 출력 스트림

fstream fs;
fs.open("helloworld.txt"); // 읽기와 쓰기 범용으로 파일 오픈.

 

    Note) 파일 스트림에도 << 연산자와

      >> 연산자, 조정자(manipulator) 등을 쓸 수 있음.

 

  1.4-2 open() and close()

    Note) 각 파일 스트림마다 open() 메서드가 있음.

      그리고 open() 메서드에는 다양한 모드 플래그가 존재함.

fin.open("helloworld.txt", ios_base::in | ios_base::binary);

 

    Note) open() 메서드의 모드 플래그

      namespace: ios_base

      in / out / ate / app / trunc / binary

      모드 플래그의 모든 조합이 유효하지는 않음.

C C++
"r" ios_base::in
"w" ios_base::out
ios_base::out | ios_base::trunc
"a" ios_base::out | ios_base::app
"r+" ios_base::in | ios_base::out
"w+" ios_base::in | ios_base::out | ios_base::trunc

 

    Note) close()

      마찬가지로 각 파일 스트림마다 close() 메서드가 있음.

ifstream fin;
fin.close();

 

    Note) 파일 스트림 상태 확인하기

      is_open() 메서드를 통해서 파일이 열려 있는지 확인 가능.

fstream fs;
fs.open("helloWorld.txt");

...

if (fs.is_open())
{
    fs.close();
}

 

    Ex01040201)

<hide/>
// HelloWorld.txt

Hello, world!
<hide/>

// main.cpp

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ifstream fin;
    fin.open("HelloWorld.txt");

    char ch;
    while (true)
    {
        fin.get(ch);
        if (fin.fail())
        {
            break;
        }
        cout << ch;
    }
    cout << endl;

    if (fin.is_open())
    {
        cout << "[System]fin is now open.\n" << endl;
        fin.close();
    }	

    return 0;
}

 

    Note) get(), getline(), >> 연산자

      어떤 스트림(ex. cin, isstream, ...)을 넣어도 

      동일하게 동작함.

fin.get(ch);                // overloading 되어 있어서, 한 단어 또는 한 줄 읽기 가능.
fin.getline(firstName, 20); // 파일에서 문자 20개를 읽음.
getline(fin, line);         // 파일에서 한 줄을 읽음.
fin >> word;                // 파일에서 한 단어를 읽음.

 

    Ex01040202) 파일에서 한 줄씩 읽기

<hide/>

// main.cpp

#include <iostream>
#include <string>    // 추후에 C++ STL을 통해 배움. 지금은 char[] 정도로 생각하자.
#include <fstream>

using namespace std;

int main(void)
{
    ifstream fin;
    fin.open("helloWorld.txt");

    string line;
    while (!fin.eof()) {
        getline(fin, line);
        cout << line << endl;
    }

    if (fin.is_open()) { fin.close(); }

    return 0;
}

 

    Note) 다만, >> 연산자와 getline() 메서드를 

      같이 사용하면 안됨. 입력 버퍼에서 데이터를 

      읽어오는 정책이 서로 상이함.

      >> 연산자는 공백을 꺼내지 않고 놔둠.

      getline()은 공백도 꺼내버림.

 

    Ex01040203) 파일에서 한 데이터씩 읽기

<hide/>

// main.cpp

#include <iostream>
#include <fstream>

using namespace std;

int main(void)
{
    ifstream fin;
    int num;

    fin.open("HelloWorld1.txt"); // 차례대로 HelloWorld1.txt -> HelloWorld2.txt -> HelloWorld3.txt -> HelloWorld4.txt
    while (!fin.eof()) {
        fin >> num;
        cout << num << endl;
    }
    if (fin.is_open()) { fin.close(); }
    
    return 0;
}

    Note) HelloWorld1.txt 

100 200 300

      HelloWorld2.txt

100 200 300(엔터)

 

      HelloWorld3.txt

100 C++ 300

 

      HelloWorld4.txt

100 C++ 200(탭)
300     400

 

    Note) 위 예제들에서 숫자만 적힌 첫 예제는 잘 동작함.

      HelloWorld2.txt부터 안됨.

      제대로 데이터를 읽었을 때만 출력하도록

      fail bit 체크를 해보자.

 

    Ex01040204)

<hide/>

// main.cpp

#include <iostream>
#include <fstream>

using namespace std;

int main(void)
{
    ifstream fin;
    int num;

    fin.open("HelloWorld2.txt"); 
    while (!fin.eof()) 
    {
        fin >> num;
        if (!fin.fail())
        {
            cout << num << endl;
        }
    }
    if (fin.is_open()) { fin.close(); }
    
    return 0;
}

 

    Note) 그러나, HelloWorld3.txt는 해결하지 못함.

      즉, 중간에 유효하지 않는 데이터를 입력 받는 것을

      해결하려면? 한번 버퍼의 데이터를 버려줘야함.

 

    Ex01040205) 

<hide/>

// main.cpp

#include <iostream>
#include <fstream>

using namespace std;

int main(void)
{
    ifstream fin;
    int num;

    fin.open("HelloWorld4.txt"); 
    while (!fin.eof()) 
    {
        fin >> num;

        if (fin.fail())
        {
            fin.clear();
            fin.ignore(LLONG_MAX, ' ');
        }
        else
        {
            cout << num << endl;
        }
    }
    if (fin.is_open()) { fin.close(); }
    
    return 0;
}

 

    Note) HelloWorld4.txt는 어떻게 해결해야 할까?

      탭 때문임. 

 

    Ex01040206)

<hide/>

// main.cpp

#include <iostream>
#include <fstream>

using namespace std;

int main(void)
{
    ifstream fin;
    int num;
    string trash;

    fin.open("HelloWorld4.txt"); 
    while (!fin.eof()) 
    {
        fin >> num;

        if (!fin.fail())
        {
            cout << num << endl;
            continue;
        }

        fin.clear();
        fin >> trash;
    }
    if (fin.is_open()) { fin.close(); }
    
    return 0;
}

 

    Note) 만약 근데 아래와 같이 clear()를 더 늦게한다면?

      C++을 fin >> num; 구문에서 읽다가 failbit이 set.

      그래서 fin >> trash; 구문이 실행됨.

      그러나 failbit이 set된 상태라 파일 위치 표시자가 움직이지 않음.

      fin.clear(); 구문이 실행되어서 failbit이 unset되지만,

      또다시 fin >> num;에서 C++을 읽으려해서 무한 루프.

<hide/>

// main.cpp

...

int main(void)
{
    ...
    while (!fin.eof()) 
    {
        ...

        fin >> trash;
        fin.clear();
    }
    
    ...
}

      HelloWorld5.txt

100 C++(엔터)

 

    Note) 결론은, 입출력 연산이 스트림 상태 비트를

      변경한다는 사실을 꼭 기억해야 함.

      EOF를 잘못 처리하면 무한 루프에 빠지기 쉬움.

      clear()를 쓸 때는 두 번 더 생각하자.

 

    Note) 입출력 처리 문제는 꽤나 자주 등장함.

      C#이나 Java처럼 자신만의 스트림 리더를 만드는 일이 흔함.

 

    Note) 그러나, 처음부터 완벽하게 입출력을 처리하는 코드를

      작성하는 건 거의 불가능함. 양질의 데이터와 집요한 테스트를

      통해서만이 완벽하게 처리하는 코드를 작성하게되는 길.

 

    Note) 훌륭한 테스트 케이스

      아래 테스트 케이스를 키보드 입력과 입력 리디렉션 모두로 확인하자.

// 유효한 입력 뒤에 EOF
100\n200EOF

// 유효한 입력과 뉴라인 뒤에 EOF
100\n200\nEOF

// 유효하지 않은 입력 뒤에 EOF
100\nC++EOF

// 유효하지 않은 입력과 뉴라인 뒤에 EOF
100\nC++\nEOF

// 공백과 탭도 포함할 것인가?
100 C++\t

 

    Note) 파일에 쓰기를 해보자. 

      똑같이 << 연산자 혹은 put() 메서드를 사용하면 됨.

 

    Ex01040207)

<hide/>

// main.cpp 

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int main(void)
{
    ofstream fout;
    fout.open("HelloWorld5.txt");

    string line;
    getline(cin, line);
    if (!cin.fail()) {
        fout << line << endl;
        // endl; 하면 newline + fflush()까지 됨.
    }

    if (fout.is_open()) { fout.close(); }

    return 0;
}

 

    Note) 이진 파일을 읽고 써보자.

 

    Ex01040207) 

<hide/>

// main.cpp 

#include <iostream>
#include <fstream>

using namespace std;

int main(void)
{
    ofstream fout;
    fout.open("HelloWorld6.dat", ios_base::out | ios_base::binary);

    int num;
    cin >> num;
    if (!cin.fail()) {
        fout << num << endl;
    }

    if (fout.is_open()) { fout.close(); }

    return 0;
}

 

    Ex01040208)

<hide/>

// main.cpp 

#include <iostream>
#include <fstream>

using namespace std;

enum { LENGTH = 16 };

int main(void)
{
    ofstream fout;

    fout.open("HelloWorld6.dat", ios_base::out | ios_base::binary);

    if (fout.is_open()) {
        char buffer[LENGTH] = "Parkthy";
        fout.write(buffer, LENGTH);
    }

    fout.close();

    return 0;
}

 

    Ex01040209)

<hide/>

// main.cpp 

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int main(void)
{
    ifstream fin;
    fin.open("HelloWorld6.dat", ios_base::in | ios_base::binary);

    char ch;
    string trash;
    while (!fin.eof())
    {
        if (!fin.fail())
        {
            fin.read((char*)&ch, sizeof(ch));
            cout << ch;
            continue;
        }

        fin.clear();
        fin >> trash;
    }

    if (fin.is_open()) { fin.close(); }

    return 0;
}

 

  1.4-3 파일 안에서의 탐색

    Note) 탐색(seek)의 유형

      1. 절대적 탐색

      2. 상대적 탐색

 

    Note) 절대적 탐색

      특정 위치로의 이동처럼, tellp() / tellg()를

      사용해서 기억해 뒀던 위치로 돌아갈 때 사용함.

 

    Note) 상대적 탐색

      파일 끝에서부터 5바이트 앞의 위치로 이동 같은 것.

 

    Note) 탐색 방향 유형

      ios_base::beg / ios_base::cur / ios_base::end

 

    Def) tellp()

      쓰기 포인터의 위치를 반환함.

ios_base::pos_type pos = fout.tellp();

 

    Def) seekp()

      쓰기 포인터의 위치를 이동함.

      절대적: fout.seekp(0);                    // 처음 위치로 이동

      상대적: fout.seekp(20, ios_base::cur) // 현재 위치에서 20바이트 뒤로 이동.

 

    Def) tellg()

      읽기 포인터의 위치를 반환함.

ios_base::pos_type pos = fin.tellg();

 

    Def) seekg()

      읽기 포인터의 위치를 이동함.

      절대적: fin.seekg(0);                      // 처음 위치로 이동

      상대적: fin.seekg(-10, ios_base::end); // 파일 끝에서 10바이트 앞으로 이동.

 

    Ex01040301)

<hide/>

// main.cpp 

#include <iostream>
#include <fstream>

using namespace std;

int main(void)
{
    fstream fs;

    fs.open("HelloWorld6.dat", ios_base::in | ios_base::out | ios_base::binary);

    if (fs.is_open()) {
        fs.seekp(20, ios_base::beg);

        if (!fs.fail()) {
            // 21번째 위치에서부터 덮어쓰기 됨.
            fs << 1234 << endl;
        }
    }

    fs.close();

    return 0;
}

 

    Ex01040302)

<hide/>

// main.cpp

#include "recordManager.h"

int main(void)
{
    recordManager::startRecordManager();

    return 0;
}
<hide/>

#pragma once

#include <string>

namespace recordManager {
    struct Record {
        std::string FirstName;
        std::string LastName;
        std::string StudentId;
        std::string Score;
    };

    Record readRecords(std::istream& stream, bool bPrompt);
    void writeFileRecords(std::fstream& outputStream, const Record& record);
    void displayRecords(std::fstream& fileStream);
    void startRecordManager(void);

}
<hide/>

#include <iostream>
#include <fstream>

#include "recordManager.h"

using namespace std;

namespace recordManager {
    Record readRecords(istream& stream, bool bPrompt) 
    {
        Record record;

        if (bPrompt) {
            cout << "First name: ";
        }
        stream >> record.FirstName;

        if (bPrompt) {
            cout << "Last name: ";
        }
        stream >> record.LastName;

        if (bPrompt) {
            cout << "Score: ";
        }
        stream >> record.Score;

        return record;
    }

    void displayRecords(fstream& fileStream) 
    {
        fileStream.seekg(0);
        string line;
        string trash;

        while (true) {
            getline(fileStream, line);

            if (!fileStream.fail()) {
                cout << line << endl;
                continue;
            }

            if (fileStream.eof()) {
                break;
            }

            fileStream.clear();
            fileStream >> trash;
        }

        return;
    }

    void writeFileRecords(fstream& outputStream, const Record& record) 
    {
        outputStream.seekp(0, ios_base::end);

        outputStream << record.FirstName << ' '
                     << record.LastName << ' '
                     << record.StudentId << ' '
                     << record.Score << endl;

        outputStream.flush();

        return; 
    }

    void startRecordManager(void)
    {
        fstream fileStream;
        fileStream.open("studentRecords.bin", ios_base::in | ios_base::out);
        bool bExit = false;

        while (!bExit) {
            char command = ' ';
            cout << "a: add" << endl
                 << "d: display" << endl
                 << "x: exit" << endl;

            cin >> command;
            cin.ignore(LLONG_MAX, '\n');
            switch (command) {
                case 'a':
                {
                    Record record = readRecords(cin, true);
                    writeFileRecords(fileStream, record);
                    break;
                }
                case 'd':
                {
                    displayRecords(fileStream);
                    break;
                }
                case 'x':
                {
                    bExit = true;
                    break;
                }
                default:
                {
                    cout << "Invalid input" << endl;
                    break;
                }
                    
            }
        }

        fileStream.close();

        return;
    }

}

'C++ > 문법 정리' 카테고리의 다른 글

Chapter 06. C++11/14/17  (0) 2022.05.08
Chapter 05. 템플릿과 파일 시스템  (0) 2022.05.07
Chapter 04. 캐스팅과 인라인  (0) 2022.05.06
Chapter 03. 상속과 다형성  (0) 2022.05.05
Chapter 02. 클래스  (0) 2022.05.01

댓글