오늘은 C++ 기초학습에 대한 마지막 날로, 반복자와 입출력 스트림에 대한 학습을 진행했다.
반복자는 특정 컨테이너의 항목을 어떻게 순회할지 알고 있는 포인터 객체로, STL에 구현된 알고리즘 대부분에 사용할 수 있게 구현되어 있다.
반복자는 입력, 출력, 순방향, 양방향, 임의 접근 반복자 5가지 카테고리로 분류할 수 있다. STL의 컨테이너에 따라 사용할 수 있는 반복자는 다르다
반복자 기능 입력 출력 순방향 양방향 임의 접근
접근(->): O X O O O
읽기(*): O X O O O
쓰기(*): X O O O O
증가(++): O O O O O
감소(--): X X X O O
인덱스([]): X X X X O
산술(+, -): X X X X O
산술대입(+=, -=): X X X X O
대소비교(<, <=, >, >=): X X X X O
반복자는 다음과 같은 과정으로 사용할 수 있다. (가장 많이 사용하는 vector의 반복자 사용법)
vector<int> v; // 벡터 v
vector<int>::iterator iter = v.begin(); // 벡터 v의 처음을 가르키는 반복자
...
# 반복자를 이용해 벡터 v 전체를 순회하는 반복자 예시
for(auto iter = v.begin(); iter != v.end(); ++iter){
cout << *iter << endl;
}
반복자는 일반적으로 순회하는 반복자 외에도 const를 지정하거나, reverse_iterator와 같이 역방향으로 순회하는 반복자를 지정할 수도 있다. 역방향 반복자는 반대 방향으로 순회한다. 역방향 반복자를 사용할 때에는 begin()이 아닌 rbegin()과 같이 반대 방향을 지정하는 것이 바람직하다.
주의해야 할 점은 begin()과 rbegin()은 요소의 처음이나 끝을 가르키지만, end()와 rend()는 이전이나 다음 항목을 가르키는 것으로 Dangling Pointer를 유발할 수 있다.
또한, 반복자는 포인터와 다를 바가 없기에, 가르키는 대상의 변동을 알지 못한다. 즉, 반복자로 가르키고 있을 때 대상이 소멸되면 Dangling Pointer가 된다. 따라서, erase 연산이나 후위증가 연산을 통해 유효한 항목을 확보해야 한다.
auto iter = v.begin();
for(int i = 0; i < n; ++i){
ls.erase(iter++); # 후위증가 연산을 사용
}
위 코드에서는 후위증가 연산을 통해 iter은 다음 항목을 가르키고 있지만, ls.erase()에 반환한 값은 증가 연산을 하기 전의 값이 된다. 즉, 삭제하는 시점에서 iter은 이미 다음 요소를 가르키고 있기 때문에 요소를 삭제하더라도 정상적으로 순회할 수 있다. 전위 증/감과 후의 증/감에 대한 내용을 이해하고 있을 필요가 있다.
C++은 C의 super set으로 C에서 사용하던 로우 레벨 입출력 함수(scanf, printf 등)들을 사용할 수 있다. C++은 커스텀된 데이터 타입이나, 객체 등 유연한 입출력이 가능한 스트림을 제공하고 있다.
입력 스트림은 >> 연산자를 통해 입력을 받을 수 있으며, 출력 스트림은 << 연산자를 통해 값을 출력할 수 있다.
int x = 10;
string str = "test";
# 다양한 타입을 붙여서 출력이 가능하다
cout << x << "." << str << "\n"; // (1) 개행문자 사용
cour << x << "." << str << end; // (2) endl 사용
endl은 스트림에 개행문자를 추가하고, 출력 버퍼를 밀어내는(flush) 역할을 수행하므로, 잦은 사용은 성능에 큰 영향을 줄 수 있다.
출력 스트림에는 다양한 매니퓰레이터가 존재하며, C++을 사용하다 보면 많이 사용한 것들이 여기에 포함되어 있다.
bool isUsed = false;
cout << boolalpha << endl; // true, false로 출력
cout << noboolalpha << endl; // 1, 0으로 출력
int num = 52;
cout << dec << num << endl; // 52 (10진수 포맷)
cout << oct << num << endl; // 64 (8진수 포맷)
cout << hex << num << endl; // 34 (16 진수 포맷)
cout << showbase; // 진수 형태 표시
cout << showpos; // 음수 양수 표시
cout << dec << num << endl; // +52
cout << hex << num << endl; // 0x34
cout << oct << num << endl; // 064
double x = 10.123456789;
cout << setprecision(5) << x << endl; // 10.123 부동소수점 자릿수(정수부 + 소수부)
cout << fixed << x << endl; // 10.12346 주어진 percision까지만 반올림해 출력
입출력 스트림을 참조 파라미터로 받아서, 함수 내에서 스트림을 통해 I/O 작업을 수행한다면 const를 붙일 수 없다. 모든 I/O 작업에서 객체 내부의 상태(읽기 위치정보, 상태 플래그 등)를 업데이트 할 수 있다.
ios::good() -> !eofbit && !failbit && !badbit
ios::eof() -> eofbot
ios::fail() -> failbit || badbit
ios::operator bool -> !failbit && !badbit
ios::bad() -> badbit
# 사용 예시
void readName(istream& is){ // 참조 파라미터로 받아서 수행시 const 사용 불가
while(is.good()){
int next = is.get(); // get()은 1 byte의 raw 데이터를 얻음
...
}
...
}
# get() 메소드는 char&를 받을 수 있는 버전도 존재한다.
char next;
while(is.get(next)){ // byte를 읽음과 동시에 bool로 return
...
}
입력 스트림에도 출력 스트림처럼 유용한 매니퓰레이터들이 존재한다.
skipws -> 공백 생략
noskipws -> 공백 포함
ws -> 현재 위치부터 연이은 공백 문자 생략
# c1 = "a", c2 = "b", c3 = "c"
istringstream("a b c") >> c1 >> c2 >> c3;
# c1 = "a", c2 = " ", c3 = "b"
istringstream("a b c") >> noskipws >> c1 >> c2 >> c3; // 공백을 포함
파일 스트림은 파일을 하나의 스트림으로 사용할 수 있게 해주며, fstream 헤더에 정의되어있다. ofstream, ofstream, fstream이 존재하며, ofstream은 생성자에서 파일명과 열기 모드를 파라미터로 받는다.
반복자와 스트림은 코딩테스트를 준비하면서 그래도 많이 사용해본 기능들이다. 크게 어려운 점은 없었다.
아직은 궁금한 점은 없었다.
반복자와 스트림에 대해서 대략적으로 알고 있었기에 강의를 따라가는데는 어렵지 않았다. 하지만, 이렇게 구체적으로 다룬 기억이 별로 없어서 기억에 남는 강의다. C++ 기초를 배우는 마지막 날이지만, 아직은 C++에 대한 이해가 많이 부족한것 같다.
📌 프로그래머스 데브코스 6기 자율주행 인지과정(Perception) 수강 내용을 바탕으로 정리한 TIL 입니다.
📅 Today: 2023. 9.22.