c++ 프로그래밍 6/17일

2022. 6. 17. 15:08카테고리 없음

 C++ 문자열 라이브러리

 

empty() 빈 문자열 확인하는 함수

 

push_back() : 첫글자 삽입 함수

append() : 문자열에서 문자열 삽입 함수

연산자 "문자열" 해도 문자열 삽입가능하다. 

insert (문자열 자리, 삽입할 문자열) 

pop_back() :마지막 문자삭제

erase(5,9) :삭제할문자열 첫자리부터 끝자리까지

 

== , != , > , <  로도 문자열 비교가 가능

substr(index) 문자index값부터 문자열끝까지 추출

substr(startdex,enddex) 매개변수 이름 처럼 쓰면 된다.

 

stof() 문자열 float으로 변환.

stoi() 문자열 int로 변환.

to_string(); 변환한 문자열 변수명으로 가져오는거

 

예시

 

더보기

 

#include <iostream>
#include <string> // std::string을 쓰고 싶다면 이 헤더를 포함해야 한다.
#include <assert.h>

int main()
{
    std::string s = "Hello";
    std::cout << s << "\n";

    // empty()로 빈 문자열인지 확인할 수 있다.
    if (s.empty())
    {
        std::cout << "빈 문자열이다.\n";
    }

    // length() / size()로 문자열의 길이를 알 수 있다.
    std::cout << "이 문자열의 길이는 " << s.length() << "\n";
    // std::cout << "이 문자열의 길이는 " << s.size() << "\n";

    // [] 연산자로 각 문자에 접근할 수 있다.
    // front()로 첫 번째 문자에, back()으로 마지막 문자에 접근할 수 있다.
    std::cout << "이 문자열의 3번째 문자는 " << s[2] << "\n";
    std::cout << "이 문자열의 1번째 문자는 " << s.front() << "\n";
    std::cout << "이 문자열의 마지막 문자는 " << s.back() << "\n";

    // clear()로 빈 문자열로 만들 수 있다.
    s.clear();
    if (s.empty())
    {
        std::cout << "빈 문자열이다.\n";
    }

    // push_back() / append() / + 연산자로 맨 끝에 문자(열)를(을) 삽입할 수 있다.
    s.push_back('H');   // "H"
    s.append("ello");   // "Hello"
    s += " World!";     // "Hello World!"
    std::cout << s << "\n";

    // insert()로 문자(열)를(을) 중간에 삽입할 수 있다.
    s.insert(5, " Inserted"); // "Hello Inserted World!"
    std::cout << s << "\n";

    // pop_back()으로 마지막 문자를 삭제할 수 있다.
    s.pop_back(); // "Hello Inserted World"
    std::cout << s << "\n";

    // erase()로 문자(열)를(을) 삭제할 수 있다.
    s.erase(5, 9); // "Hello World"
    std::cout << s << "\n";

    // ==, !=, <, > 연산자로 문자열을 비교할 수 있다.
    if (s == "Hello World")
    {
        std::cout << "Hello World와 같다.\n";
    }

    if (s != "Hello")
    {
        std::cout << "Hello와 다르다.\n";
    }

    if (s > "Hello")
    {
        std::cout << s << "가 Hello 뒤에 나온다.\n";
    }

    if (s < "Idle")
    {
        std::cout << s << "가 Idle 전에 나온다.\n";
    }

    // substr()로 문자열을 추출할 수 있다.
    std::cout << s.substr(6) << "\n";       // "World" 인덱스부터 문자열 끝까지
    std::cout << s.substr(0, 5) << "\n";    // "Hello"  

    // 숫자와 문자열 간 변환도 가능하다.
    s = "3.14";
    float f = std::stof(s); // f(3.14) 문자열 float 로 변환
    s = "142";
    int i = std::stoi(s); // i(142) 문자열 int 로 변환
    s = std::to_string(i); // s("3.14")

    // 입력도 받을 수 있다.
    std::cout << "단어를 입력해주세요. : ";
    std::cin >> s;
    std::cout << s << "\n";
    std::cout << "문장을 입력해주세요. : ";
    std::getline(std::cin, s);

    return 0;
}

 

stirng_view 는 읽어오는것만 가능 더빠름 16byte

string은 조금 무겁다. 8 byte

 

입출력 라이브러리

 

스트림 기반의 입출력 라이브러리는 기본적으로 ios_base 클래스를 상속받아 구현되엇다. 사용법이 비슷하다.

 

입력과 출력을할때 기본동작들을 정의한다.

 

예시

 

더보기

 

#include <iostream> // 표준 파일 입출력은 이 헤더를 포함해야 한다.
#include <iomanip>  // 입출력과 관련된 조작을 위한 헤더다.

int main()
{
    // 출력은 << 연산자를 사용하면 된다.
    std::cout << "Hello World!\n";

    // 포맷팅도 가능하다.
    // 1. 정수 관련 포맷
    std::cout << 12 << "\n";     // "12"
    std::cout << std::hex;       // 16진수로 출력한다.
    std::cout << 12 << "\n";     // "c"
    std::cout << std::showbase;  // 진법을 표현한다.
    std::cout << 12 << "\n";     // "0xc"
    // 다시 원래대로 되돌린다.
    std::cout << std::dec << std::noshowbase;

    // 2. 실수 관련 포맷
    std::cout << 12.34 << "\n";         // "12.34"
    std::cout << std::fixed;            // 소수점 자리를 고정시켜 표현한다. 뒤에 6자리를 고정시킨다
    std::cout << 12.34 << "\n";         // "12.340000"
    std::cout << std::setprecision(12); // 소수점 자리를 12자리로 고정한다.
    std::cout << 12.34 << "\n";         // "12.340000000000"
    // 다시 원래대로 되돌린다.
    std::cout << std::defaultfloat << std::setprecision(6);

    // 3. 불리언 관련 포맷
    std::cout << std::boolalpha;    // 불리언 값을 출력할 때 숫자 대신 문자로 출력한다. 초창기떄 썼음 
    std::cout << true << "\n";      // "true"
    // 다시 원래대로 되돌린다.
    std::cout << std::noboolalpha;

    // 4. 정렬 관련 포맷
    std::cout << std::setfill('*'); // 공백대신 *출력
    std::cout << std::setw(12);     // 출력 길이를 일시적으로 12로 지정한다. 한번만해주는것 같아여
    std::cout << std::left;         // 왼쪽 정렬
    std::cout << std::hex << std::showbase;
    std::cout << 42 << "\n"; // "0x2a********"
    // 계속 같은 길이를 사용하려면 width()을 사용한다.
    std::cout.width(12); 
    std::cout << std::right; // 오른쪽 정렬
    std::cout << 42 << "\n"; // "********0x2a"
    // 다시 원래대로 되돌린다.
    std::cout << std::setfill(' ');
    std::cout.width(0);

    // 입력은 >> 연산자를 사용하면 된다.
    std::cout << "숫자를 입력하세요 : ";
    int num;
    std::cin >> num;

    // good() / fail()로 오류 여부를 알 수 있다.
    if (std::cin.good()) // true
    {
        std::cout << "숫자를 입력함.\n";
    }
    else if (std::cin.fail()) //false 
    {
        std::cout << "숫자가 아닌 다른 값을 입력함.\n";
        std::cin.clear(); // clear()를 사용하면 객체를 정상화할 수 있다.
    }

    // bool 타입으로 변환이 가능하다. 
    if (/*bool*/std::cin) // if(std :: cin.good())
    { //1. if 에 작성하는 조건식의 *타입*은 bool
       //2. if (std::cin) => std::cin의 타입이 istream => bool
       //3. std :: cin.operator bool();
       //4. 오류가 없다면 true, 오류가 있다면 false
        std::cout << "오류가 없음.\n";
    }
    else //if (std :: cin.fail())
    {
        std::cout << "오류 발생함.\n";
    }

    return 0;
}

실습

 

 

다음은 파일 입출력이다.

 

더보기

 

#include <iostream>
#include <fstream> // 파일 입출력을 위한 헤더다.
#include <string>
 
int main()
{
    // 파일 출력은 ofstream을 이용한다.
    std::ofstream of("temp.txt");
 
    // 사용법은 표준 파일과 비슷하다.
    of << "Hello World!";
   
    // close()로 명시적으로 파일을 닫을 수 있다.
    // 물론 굳이 하지 않아도 소멸자에서 자동으로 호출한다.
    of.close();
 
    {
        // 파일 입력은 ifstream을 이용한다.
        std::ifstream ifs;
       
        // is_open()으로 파일을 열었는지 확인할 수 있다.
        if (false == ifs.is_open())
        {
            // open()으로 파일을 열 수 있다.
            ifs.open("temp.txt");
        }
 
        std::string str;
        std::getline(ifs, str);
        std::cout << str << "\n";
 
        // eof()로 파일의 끝에 도달했는지 알 수 있다.
        if (ifs.eof())
        {
            std::cout << "파일의 끝에 도달함.\n";
        }
    } // 자동으로 ifs.close()가 호출된다.
   
    // 바이너리 모드로 열고 싶다면 ios::binary를 넘겨준다.
    std::ofstream of2("temp.bin", std::ios::binary);
 
    return 0;
}

문자열 입출력

 

더보기

 

#include <iostream>
#include <string>
#include <sstream> // 문자열 입출력을 위한 헤더다.

int main()
{
    // 문자열 입출력은 stringstream을 이용한다.
    std::stringstream ss;

    // 마찬가지로 출력은 << 연산자를 사용한다.
    ss << "Hello";

    // 입력은 >> 연산자를 사용한다.
    std::string str;
    ss >> str;
    std::cout << str << "\n";

    // str()을 이용하면 스트림에 있는 문자열을
    // std::string 객체로 변환한다.
    std::string str2 = ss.str();
    std::cout << str2 << "\n";

    return 0;
}

 

 

만약 사용자 정의 타입도 입출력 객체를 통해 입출력을 하고 싶다면 >> 연산자와 << 연산자를 오버로딩하면 된다.

 

더보기

 

#include <iostream>
#include <fstream>
 
class A
{
public:
    A() = default;
    explicit A(int data) : _data(data) { }
 
    int GetData() const { return _data; }
    void SetData(int data) { _data = data; }
private:
    int _data = 0;
};
 
// 출력을 재정의 하고 싶다면 아래와 비슷한 선언을 하면 된다.
std::ostream& operator<<(std::ostream& oss, const A& a)
{
    return oss << "My Data : " << a.GetData();
}
 
// 입력을 재정의 하고 싶다면 아래와 비슷한 선언을 하면 된다.
std::istream& operator>>(std::istream& iss, A& a)
{
    iss.ignore(sizeof("My Data : "), ':');
   
    int data;
    iss >> data;
    a.SetData(data);
 
    return iss;
}
 
int main()
{
    // 출력
    std::ofstream ofs("temp.txt");
    A a(14);
    ofs << a;
    ofs.close();
 
    // 입력
    std::ifstream ifs("temp.txt");
    A b;
    ifs >> b;
    std::cout << b;
    ifs.close();
 
    return 0;
}

실습

 

더보기

Printf 쓴버전

    string name[] = { "김재성","용준헌","김재민","김동현" };
    int age[] = { 31,28,25,28 };
    printf("%s", "printf() 쓴 버전\n");
    printf("%s", "---------------------------\n");
    printf("|%-12s|%12s|\n", "Name", "Age");
    printf("%s", "---------------------------\n");
    for (int i= 0; i < 4; i++)
    {
        printf("|%-12s|%12s|\n",name[i].c_str(), age[i]);
    }
    printf("%s", "---------------------------");

 std :: cout 쓴버전

using namespace std; <- 이걸로 std:: 부분 처리함

      cout << "---------------------------\n";
        cout << "|";
        cout << setw(12);
        cout << left << "Name";
        cout << "|" << right;
        cout << setw(12);
        cout << right << "Age" << "|\n";
       cout << "---------------------------\n";
    for (int i = 0; i < 4; i++)
    {
        cout << "|";
        cout << setw(12);
        cout  << left << name[i];
        cout << "|" << right;
        cout << setw(12);
        cout << right << age[i] << "|\n";
    }
    cout << "---------------------------\n";
    return 0;
}

 

 

 

1-1 이름과 나이 출력의 고정 너비는 12이다.

1-2. 프로그램을 확장성이 있어야 한다. 즉 , 너비를 자유롭게 조정할 수 있어야 하며, 데이터가 늘어나도 프로그램의 수정이 없어야 한다.

1-3. printf()로 구현된 부분과 std::cout으로 구현된 부분이 각각 존재해야 한다.

 

2.1번 프로그램을 파일에 저장하는 프로그램을 작성하시오.

3.1번 프로그램을 문자열에 저장하여 출력하는 프로그램을 작성하시오.

4.여러분의 타입을 만들고 입출력을 재정의해보세요.

 

 

Node : 접점

 

연결 리스트

 

연결 리스트는 STL 상에서는 std::forward_list, std::list로 구현되어 있다. 선형 리스트와 다르게 임의 접근이 불가능하다.

연속적으로 할당이 안되어있고 서로 떨어져있다.

 

  • 읽기

연결 리스트는 임의 접근이 불가능해 요소 하나하나를 탐색해야 하므로 O(n)(선형시간)의 시간이 걸린다. 하지만 처음과 끝이라면 구현에 따라 O(1)이 될 수 있다. 

 

  • 검색

하나하나 원소를 비교해가야 하므로 O(n)의 시간이 걸린다. 선형 리스트와 다르게 이진 검색을 사용할 수 없다.

 

  • 삽입 == 손코딩테스트에서 많이나옴

원소를 어디에 삽입하냐에 따라 시간이 달라진다. 앞이나 뒤에 데이터를 추가할 경우 O(1)이지만, 중간이라면 해당 위치까지 검색해야 하기 때문에 O(n)이 걸린다. 정확히 내가 삽입하는 위치를 알면 O(1) 이므로 오래걸리지 않는다.

 

  • 삭제 ==  손코딩테스트에서 많이나옴

삽입과 마찬가지로 원소를 어디에서 삭제하냐에 따라 시간이 달라진다. 앞이나 뒤의 데이터를 삭제할 경우 O(1)이지만, 중간이라면 O(n)이 걸린다.  정확히 내가 삽입하는 위치를 알면 O(1) 이므로 오래걸리지 않는다.

 

예시

 

단일 연결리스트

 

더보기
#include <forward_list> // std::forward_list를 쓰기 위한 헤더
#include <iostream>
using namespace std;

int main()
{
    std::forward_list<int> flist;

    // 삽입
    flist.push_front(1); // push_front() : 맨 앞에 삽입한다.
    // flist{ 1 }
    
    flist.insert_after(flist.begin(), 2); // insert_after(pos, value) : pos 뒤에 value를 삽입한다. //단일 연결리스트는 다음것만 넣어주기때문에 이전주소를 모르기때문에 오로지 다음꺼 밖에 모른다.
    // flist{ 1, 2 }
    flist.push_front(3); 
    // flist { 3, 1, 2 }

    // dummy node : (바보같은) node 임시 노드

    // 반복자
    std::forward_list<int>::iterator iter;
    iter = flist.before_begin(); //단일 연결리스트라 이전주소를 모르기때문에 첫시작값 앞에다가만 넣을수있다. 더미 노드가 있으면 구현이 좀더 편하다. 없으면 예외처리할께 늘어남
    // [ ]->[3]->[1]->[2]->[ ]
    //  ↑
    iter = flist.begin();
    // [ ]->[3]->[1]->[2]->[ ]
    //       ↑
    iter = flist.end();
    // [ ]->[3]->[1]->[2]->[ ]
    //                                 ↑
    // 이전주소가 없기 때문에 rbegin() rend()가 없다.
    //  
    // 삭제
    flist.pop_front(); // pop_front() : 첫 번째 원소를 삭제한다.
    // flist{ 1, 2 };

    // flist{ 1 }
    flist.push_front(3);
    // flist{ 1, 3  } 
    flist.erase_after(flist.begin()); // erase_after(pos) : pos 다음 원소를 삭제한다.
    
    flist.erase_after(flist.before_begin());
    
    flist.clear(); // clear() : 컨테이너를 전부 비운다.
    // flist{ }

    // 크기
    if (flist.empty()) // empty() : 비었는지 확인한다.
    {
        std::cout << "flist는 비었음.\n";
    }
    // 주의! 다른 컨테이너와 다르게 size()는 없음

    // 아래처럼 초기화 가능
    std::forward_list<int> flist2 = { 1, 2, 3, 4, 5 };

    // 읽기 .단일 연결리스트에는 이전주소를 모르기때문에 back같은것을 안쓴다.
    std::cout << "flist2.front() : " << flist2.front() << "\n"; // front() : 첫 번째 원소를 반환한다.

    // 비교 : 다른 컨테이너와 마찬가지로 == / != / > / >= / < / <= 지원
    flist = flist2;
    if (flist == flist2)
    {
        std::cout << "flist는 flist2와 같다.\n";
    }

    // C++17부터는 원소 타입을 적지 않아도 알아서 추론한다.
    std::forward_list flist3 = { 1, 2, 3, 4, 5 };
    // C++은 실행시간에 하던거를 컴파일시간에 계산하려고 발전하고있다. tmp 컴파일시간에 실행하는것 알려줌

    return 0;
}

연결리스트 부분은 리스트 요소에 접근하려면 front()로만 접근이 가능

 

 

 

이중연결리스트

 

더보기

 

#include <list> // std::list를 쓰기 위한 헤더
#include <iostream>

int main()
{
    std::list<int> list;

    // 삽입
    list.push_front(1); // push_front() : 맨 앞에 삽입한다.
    // list{ 1 }
    list.push_back(2); // push_back() : 맨 뒤에 삽입한다.
    // list{ 1, 2 }
    list.insert(list.begin(), 9); // insert(pos, value) : pos 전에 value를 삽입한다.
    // list{ 9, 1, 2 }
    list.push_back(5); // list{ 9, 1, 2, 5 }

    // 반복자
    std::list<int>::iterator iter;
    iter = list.begin();
    // [9]<->[1]<->[2]<->[5]<->[ ]
    //  ↑
    iter = list.end();
    // [9]<->[1]<->[2]<->[5]<->[ ]
    //                          ↑
    std::list<int>::reverse_iterator riter;
    riter = list.rbegin();
    // [ ]<->[9]<->[1]<->[2]<->[5]
    //                          ↑
    riter = list.rend();
    // [ ]<->[9]<->[1]<->[2]<->[5]
    //  ↑

    // 삭제
    list.pop_front(); // pop_front() : 첫 번째 원소를 삭제한다.
    // list{ 1, 2, 5 };
    list.pop_back(); // pop_back() : 마지막 원소 삭제한다.
    // list{ 1, 2 };
    list.erase(list.begin()); // erase(pos) : pos를 삭제한다.
    list.clear(); // clear() : 컨테이너를 전부 비운다.
    // list{ }

    // 크기
    if (list.empty()) // empty() : 비었는지 확인한다.
    {
        std::cout << "list는 비었음.\n";
    }
    std::cout << "list.size() : " << list.size() << "\n";

    // 아래처럼 초기화 가능
    std::list<int> list2 = { 1, 2, 3, 4, 5 };

    // 읽기
    std::cout << "list2.front() : " << list2.front() << "\n"; // front() : 첫 번째 원소를 반환한다.
    std::cout << "list2.back() : " << list2.back() << "\n"; // back() : 마지막 원소를 반환한다.

    // 비교 : 다른 컨테이너와 마찬가지로 == / != / > / >= / < / <= 지원
    list = list2;
    if (list == list2)
    {
        std::cout << "flist는 flist2와 같다.\n";
    }

    // C++17부터는 원소 타입을 적지 않아도 알아서 추론한다.
    std::list list3 = { 1, 2, 3, 4, 5 };

    return 0;
}

이중연결리스트는 앞원소를 알려주는게 하나 더있다.

 

디자인 패턴 객체지향적으로 설계를 하다보니 비슷한 패턴이 발견됬다 . 그것들은 모아서 4천왕의 디자인 패턴 그중에하나가 반복자 패턴