Eat Study Love

먹고 공부하고 사랑하라

SW 만학도/C++

Review 1 - Basic Standard Library in C++(Cin/out,file I/O, String)

eatplaylove 2024. 7. 16. 21:54

이번엔 C++의 기초 복습이다.

 

사실 C++이 이름때문에 프로그래밍 언어 중에 제일 끝판왕 격으로 어려운 줄 알았으나,

 

C가 젤 짜치긴 하다;; ㅎㅎ

 

C 하다가 C++ 넘어오면 C++은 양반이라는 것!

 

각설하고 C++ 복습으로 얼른 넘어가보자~~~~~~ ㅠㅠ

 

C++은 기본적으로, C와 유사한데, 몇 가지 표현 방식이 다르다.

 

 

1. 단순 Print를 할 때의 차이

#include <iostream>

int main(void){

    int num;
    std::cout << "GIMME SOME NUMBER TO PRINT :";
    std::cin >> num;
    std::cout << num << std::endl;
    
    return 0;

}
---
GIMME SOME NUMBER TO PRINT :123
123

 

C의 printf / scanf 모두 C++에선 가볍게 cin / cout으로 처리해버린다.

 

저 std:: 같은 건 나중에 using namespace std; 로 생략 가능한데, 일단 초창기니까 유지

 

cin / cout 모두 iostream이라는 header file 안에 predefined되어있다. 이처럼 predefined 되어있는 function 또는 variable들이 동일 이름으로 인한 conflict가 발생하지 않게 std:: 이렇게 namespace를 정해준다.

(근데 솔직히 변수 이름겹쳐서 Error 뜬 적은 단 한 차례도 없었다.)

 

하나 구분해줘야 하는건, Namespace와 Header File은 같은 개념이 아니다.

예를 들면, std라는 namespace는 <iostream> , <string>, <algorithm> 등 많은 header에 퍼져서 정의되어 있다.

 

 

2. Cin & Cout

 

cin은 input을 받는 instance로, 정의된 적절한 data type의 variable을 '여백'이 나타날 때까지 최대한 많이 받는다.

cout은 반대로 output을 담당하는 instance인데, 임의로 data를 flush해주지 않으면 특정 상황에 buffer에 남아있는 data는 알아서 flush된다.

 

Buffer는 cin으로 받은 Data들이 cout 송출전까지 대기타고 있는 장소로 알아두면 되겠다.

    double pi = 3.14159265358979;
    cout << "PI is : " << pi << endl;
    // PI is : 3.14159

 

이렇게 소수점을 주구장창 적으니까 기본 소수점 5개를 반환하는데, 반환 소수점에 제한을 두는 방법도 있다.

#include <iostream>
#include <iomanip> ## 요놈을 선언해야 한다

using namespace std;
int main(void){

    double pi = 3.14159265358979;
    cout << "PI is : "<<setprecision(8) << pi << endl;
    // PI is : 3.1415927 ## 총 8자리, 소수점 7자리까지 반환
    double pi = 3.14159265358979;
    cout << "PI is : "<<setprecision(8) << fixed<<pi << endl;
    // PI is : 3.14159265

"fixed"와 같이 쓰면 소수점 8자리로 고정되긴 하는데,, 솔직히 걍 참고용이다.

 

결론 : #include <iomanip> -> setprecision(n):총 n개의 숫자 반환 여기에 fixed를 더하면 n개의 소수점 반환

    int num = 5;
    cout << setfill('a') << setw(8) << num << endl;
    // aaaaaaa5

 

별 기능들이 다 있다. setfill('~')와 setw(n)을 같이 쓰면, n만큼 크기의 문자열을 반환하는데, 일단 num으로 오른쪽부터 채우고 모자른 부분은 'a'로 채워서 반환한다. 당연히 setw 안 숫자가 variable의 길이보다 작으면 걍 variable만 print된다.

 

구찮게 이래저래 암기해야할 것들이 많은 녀석이다 ㅠㅠ

cerr 같은 경우는 error message를 반환하는 함수이다. error message는 기존 cout과 다르게 message가 buffer 되지 않는다. 즉, user가 즉각적으로 error message를 확인할 수 있다.(error 발생 즉시 바로 flushed!(print))

※ error logging 용 std::clog는 buffered 된다.

 

 

3. File I/O Streams

 

C++에서 file stream은 #include <fstream>이라는 header가 필요하며 여기서 ifstream(input) , ofstream(output) class를 사용한다.

 

file I/O 도 standard I/O와 상당히 유사한데, 1) 일단 ifstream으로 file을 읽고 2) is_open으로 혹여나 error는 없는지 체크하며 3) getline()으로 미리 선언해둔 string variable에 file 내용을 집어 넣고 4) file.close()로 마무리한다.

    std::ifstream f("data.txt");
    if(!f.is_open()){
        std::cerr << "ERROR"<<endl;
        return -1;
    }

    string line;
    getline(f,line);
    cout << line << endl;
    // Hello C++ 한 줄만 읽힌다

 

참고로 그냥 getline을 1회만 띡 하면, txt file에서 한 줄만 띡 읽힌다.

그래서 문장 전체를 다 읽어주려고 while문을 사용하나보다.

 

getline이라는 문장단위로 읽는 function을 쓰지 않으면, ifstream은 character를 cin처럼 1) 여백이 나타나거나, 2)Variable data type에 맞을때까지만 읽는다.

 

data.txt
---
1 2 abc
3 def
---
    string line;
    while(f >> line){
        cout << line << endl;
    }
1
2
abc
3
def
---
    int line2;
    while(f2 >> line2){
        cout << line2 << endl;
    }
1
2

 

여기서 알 수 있는 건, 일단 string/char 로 var을 선언하면 공백때 멈추면서, 그 기준으로 txt 문장을 다 읽어버리고, int로 선언한 var에 저장하면 숫자를 읽다가 int가 아닌 var이 나오면 읽는 걸 아예 멈춘다.

 

즉 string / char -> "White Space" 기준으로 input을 끊어서 읽는데 모든 걸 다 빨아들인다.

int -> "White Space" 기준으로 input을 읽는데, Number가 아닌 다른 놈이 읽히면 그 자리에서 읽는 걸 Stop한다.

 

 

File Writing의 경우 fstream header의 ofstream class를 이용한다.

 

1) ofstream으로 file을 읽어오고, 2) 역시나 is_open() 으로 file이 잘 열렸는지 체크하며 3) file에 "<<" 꺽쇠를 이용해서 txt문장을 집어 넣은 뒤 4) file.close()로 마무리한다.

 

using namespace std;
int main(void){

    ofstream file("output.txt");
    if(!file.is_open()){
        cerr << "ERROR" << endl;
        return -1;
    }

    file << "Hi\nMy name is\n47 AK!" << endl;

    file.close(); // file output completed

    ifstream f("output.txt");
    if(!f.is_open()){
        cerr << "ERROR!" << endl;
        return -1;
    }
    string l;
    while(getline(f,l)){
        cout << l << endl;
    }
    f.close();	//file input completed
// Hi
// My name is
// 47 AK!

 

C++ file I/O 종합검토 완료!

 

 

3. String

 

하,, 요놈이 또 은근 쉽지 않은 녀석이다. 적잖이 속썩이는 C++의 String, 낱낱이 파헤쳐보자.

 

C에서 String은 Char array였다. C++에선 "string" class에 저장되어 있는 녀석임.

 

string 요 녀석은 Data / Size / Capa 이렇게 3가지 정보를 갖고 있으며, 크기가 동적으로 변하므로 String Memory는 "Heap"에 할당되어 있다.

 

#include <iostream>
#include <string>

using namespace std;

int main(){
    string str1; //empty
    string str2 = "STRING 2";
    string str3("STRING 3");

    cout << str1 << endl;
    cout << str2 << endl;
    cout << str3 << endl;

// STRING 2
// STRING 3

    return 0;
}

 

String은 위 3가지 방식으로 initialization 할 수 있다. 첫 번 째의 경우 empty string으로 선언한 케이스다.

 

    string first = "Hi";
    string second("HOW ARE U?");
    string add = first + " " + second;

    cout << add << endl;
// Hi HOW ARE U?
    string test = "TEST";
    test += test;
    cout << test << endl;
// TESTTEST
    string test2 = test.append("<test2>");
    cout << test2 << endl;
// TESTTEST<test2>

 

String의 경우 요렇게 Concatenation이 가능하다. 그냥 "+" , "+=", "append" 모두 가능하다.

 

String 간 비교는 == . < , > , != 를 사용한다. Compare method는 사전순으로 string을 비교한다.

 

int main(){

    string str1 = "AAA";
    string str2("BBB");

    if(str1<str2){
        cout << "yes" << endl;
    }else{
        cout << "no" << endl;
    } //yes
    cout << str1.compare(str2) << endl; //-1
    cout << str2.compare(str1) << endl; // 1

 

다 영어사전 순으로 비교하는 것이니, 혹여나 string 비교 할 일이 있을 때 참고하면 되겠다.

 

다음으론, String의 Substring 파악해보기! find method를 이용한다.

 

    string test = "Love Data always";
    size_t pos = test.find("Data");
    if(pos != string::npos){
        cout << "Data Position :" << pos << endl;
    } //Data Position :5

 

정확히 Data라는 친구가 나오는 위치를 반환한다.  pos를 그냥 int로 선언해도 되고, "ata"이렇게 부분적으로 find를 요청해도 된다.

 

string.substr(position,length) / string.replace(position,length,바꿀문장) 이 2가지를 활용하여 string의 substring을 반환받거나 일부 변경할 수도 있다.

 

    string test = "Love you";
    
    cout << test.substr(5,3) << endl; //you
    
    test.replace(5,3,"WE");

    cout << test << endl; //Love WE

 

다음은 이따금 유용하게 쓰이는 StringData type conversion. string을 int/double type으로 바꾸거나 반대로 숫자를 string type으로 바꾸는 것이다.

    string nums = "42.123";
    int numi(stoi(nums));
    double numd = stod(nums);

    cout << nums << " " << numi << " " << numd;
// 42.123 42 42.123
    double num = 123.89707;
    string test;
    test = to_string(num);

    cout << test << endl;
    //123.897070

 

잘 기억하자! stoi(test) : string -> int  / stod(test) : string -> double / to_string(test) : number -> string

 

String의 Memory 공부

 

String은 Char array 형태인데, Capa 보다 큰 값들이 할당되려고 하면 array Re-allocation이 발생한다.

 

int main(){

    string test = "hi hello ?";
    cout << "1st size: " << test.size() << endl;
    cout << "1st capa: " << test.capacity() << endl;
    cout << "1st address: " << (void*)test.c_str() << endl;
// 1st size: 10
// 1st capa: 15
// 1st address: 0x61fdf0
    test += "^^";
    cout << "2nd size: " << test.size() << endl;
    cout << "2nd capa: " << test.capacity() << endl;
    cout << "2nd address: " << (void*)test.c_str() << endl;
// 2nd size: 12
// 2nd capa: 15
// 2nd address: 0x61fdf0
    test += test;
    cout << "3rd size: " << test.size() << endl;
    cout << "3rd capa: " << test.capacity() << endl;
    cout << "3rd address: " << (void*)test.c_str() << endl;
// 3rd size: 24
// 3rd capa: 30
// 3rd address: 0xfc1450

 

위 Code에서 볼 수 있듯이, string은 일단 기본적으로 size 이상의 capacity를 가지고 있는데, 새로 string array에 character를 추가할 때도 capa만 넘지 않으면 capa / address 변동이 없다.

 

다만, capa가 넘는 size가 할당될 시, Memory address reallocation이 발생하고 string의 capa도 넉넉하게 재할당된다.

 

 

3-2 . Stringstreams ( 너무나 중요! )

기본적으로 stringstream 은 object type으로서, #inlcude <sstream> header에 포함되어 있으며 stringstream type의 object는 String을 읽고, 쓸 수 있다. 특히 String Parsing 할 때 가장 많이 쓰이는 Object니까 관심있게 쳐다봐야 한다.

 

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main(){
    // Parsing a text by comma(" , ")

    string str = "314,9.99,hello world END";
    stringstream test(str); //parser

    int ivar;
    double dvar;
    string svar;
    char ignoreChar; // comma parsing용

    test  >> ivar >> ignoreChar >> dvar >> ignoreChar >>svar;

    string line;
    getline(test,line);

    cout << ivar << endl << dvar << endl << svar << endl << line << endl;
// 314
// 9.99
// hello
//  world END

 

자, code를 한 번 분석해보자.

일단 우리가 하고 싶은 것은 string type의 str 변수에 저장된 string을 Parsing 해보는 것이다.

 

Parsing을 위해 #include <sstream>에 저장된 stringstream type의 아무개 변수를 만들어 주고, 그 constructor 안에 str을 집어 넣어 준다.

 

그리고 입맛대로 다양한 data type의 변수를 만든다음, 각 변수에 Parsing 된 정보를 집어 넣으면 된다.

 

기본적으로, 초반부에 설명했듯이 변수가 Data를 읽을 때 딱 2가지를 고려해서 Stop한다.

  1) Data Type이 나와 같은지 2) White Space가 나타났는지

 

여기서 잘 보면, stringstream >> 로 각 변수에 Parsing된 Data들을 input해줄 때 ivar은 int 값인 314까지만 읽고 Stop한다. 다음 콤마는 ignoreChar로 읽고 넘긴다. 그 다음 dvar가 double type의 9.99를 읽어 버린다. 그리고 다음 comma를 다시 ignoreChar에다가 넣고 다음 String type의 svar에는 "Hello"가 들어간다. 왜냐하면 string type도 white space에선 input 받는 걸 stop하기 때문!

 

그래서 최종적으로 variable에서 할당받다가 남은 것들을 getline으로 읽어주는 방법을 택한다. 따라서 마지막 line에는 hello 다음 띄어쓰기를 포함해서 문장 끝까지 출력되는 것을 볼 수 있다.

 

※ 참고로 parsing 하다가 data type miss로 parsing이 중단되면, getline으로 나머지 잔여물을 읽으려고 해도 안 된다.

 

int main(){
    // Parsing a text by comma(" , ")

    string str = "abc 1234";
    stringstream test(str); //parser

    int ivar;
    // double dvar;
    // string svar;
    char ignoreChar; // comma parsing용

    test  >> ivar >> ignoreChar;

    string line;
    getline(test,line);

    cout << ivar << endl <<ignoreChar<< line << endl;
// 0
int main(){
    // Parsing a text by comma(" , ")

    string str = "a1234 test";
    stringstream test(str); //parser

    int ivar;
    // double dvar;
    string svar;
    char ignoreChar; // comma parsing용

    test  >> ignoreChar>> ivar;

    string line;
    getline(test,line);

    cout << ivar << endl <<ignoreChar<< endl <<line << endl;
// 1234
// a
//  test

 

Stringstream은 같 stringstream에 동시에 read/write 하는 것을 제한한다.

 

    stringstream ss;

    ss << 5.18;
    ss << "ABCD";
    ss << 4321;

    cout << ss.str() <<endl;
    //지금까지 stringstream에 입력된 것을 string으로 반환
    // 5.18ABCD4321
    stringstream ss;

    ss << 5.18;
    ss << "ABCD";
    ss << 4321;

    cout << ss.str() <<endl;
    //지금까지 stringstream에 입력된 것을 string으로 반환
    // 5.18ABCD4321

    double test;
    ss >> test;
    string line;
    getline(ss,line);
    
    cout << test << endl << line << endl;
    
// read 후 다시 write 시도
    ss << "LOVELY";
    ss << 9090;
    cout << ss.str() << endl;

    string line2;
    getline(ss,line2);
    cout << line2 << endl;
// 5.18ABCD4321
// 5.18
// ABCD4321
// 5.18ABCD4321

 

근데 뭐.. stringstream object에다가 이것저것 write 한 뒤에 바로 read도 해봤는데 딱히 error는 안 났지만,,

 

write -> read -> write(반영 x) 를 확인할 수 있다.

 

이렇게 예상치 못한 결과를 초래할 수 있다니까 굳이 read / write은 동시에 하지 않는 것이 좋겠다.

 

 

Review하는 시간인만큼, 내용은 얼른 얼른 훑고

 

HW/Practice 문제로 나왔던 것을 풀어보며 실전감각을 익혀야겠다.

 

누누히 말하지만 코딩은 실습이 메인디쉬다.

 

이론 공부 빠삭하게 하고 시험장 갔는데 중간/기말 다 0점 맞은 녀석이 바로 여기에 있다..

 

최대한 답안을 안 보고 20분 정도 생각 한 뒤에 문제를 풀어보고,

 

그럼에도 답이 안 보이면 얼른 답지 확인 후, 다음 문제를 풀어보자..!

 

 

- E. O. D -