오늘의 TIL 목차 (22. 09. 15)
- 컨테이너 함수
- 반복자
컨테이너 함수
[ 컨테이너 함수 ]
: 컨테이너 별로 선언되어 있는(즉 사용할 수 있는) 함수가 다름
[ 모든 컨테이너가 소유하고 있는 일부 함수 ]
@. size() : 원소의 개수를 반환하는 함수 (int형 반환)
@. capacity() : 메모리 블럭의 개수를 반환하는 함수 (int형 반환)
@. clear() : 컨테이너 내의 모든 원소를 삭제하는 함수 (메모리 블럭은 삭제X)
@. swap(객체) : 동일한 컨테이너끼리 원소와 메모리 블럭까지 완전히 교환하는 함수
// shrink_to_fit(객체) : 여유분 메모리 공간을 원소 개수만큼 축소하는 함수, swap() 사용 권장
@. begin() : 첫번째 원소의 위치/값을 Iterator타입으로 반환하는 함수
@. end() : 마지막 원소의 다음 공간인 end의 정보를 Iterator타입으로 반환하는 함수
@. empty() : 공간에 아무 원소도 없으면 true를 반환, 있으면 false를 반환하는 함수
* 읽기 쓰기가 둘 다 가능한 함수
@. front() : 첫번째 '원소(=값)'를 출력하는 함수 '읽기'
front() = 5; : 첫번째 '원소(=값)'에 5를 대입하는 '쓰기'도 가능
@. back() : 마지막 원소를 출력하는 기능 '읽기'
back() = 10; : 마지막 원소의 값에 10을 대입하는 '쓰기'
* 중간 삽입 / 삭제
@. insert(반복자, 값); 반복자가 가리키는 위치에 값을 대입함 - 대입 후 현재 원소의 반복자 반환
@. erase(반복자) : 반복자가 가리키는 원소를 삭제함 - 삭제 이후 다음 원소를 가리키는 반복자 반환
[ 시퀀스 컨테이너 모두 소유하고 있는 함수 ] ; 연관 컨테이너는 소유 X
1. push_back(인자) : 맨 뒤에(back) 원소를 추가(push)하는 함수
2. pop_back() : 맨 뒤의 원소를 삭제하는 함수
■ 예시
[ swap() 응용 ]
vector 컨테이너는 자동으로 동적 배열을 해제해준다.
그러나 의도적으로 메모리 블럭을 해제하고 싶다면 ( 벡터는 원소 삭제해도 메모리 블럭은 유지 )
Vector<int> vecInt(20); // 원소가 0으로 채워진 메모리 블럭 20개 할당된 벡터 객체 생성
Vector<int>().swap(vecInt); 로 임시 객체와 자신 객체를 swap해서 완전 삭제할 수 있다.
- Vector<T>()는 임시 메모리에 저장되는 임시 객체로 해당 라인을 벗어나면 소멸
[ erase() 주의 ]
: erase는 벡터의(동적배열) 원소를 전부 지우는 함수
포인터 동적 배열의 경우, 가리키고 있는 주소의 힙 메모리 공간을 동적할당 해제 후
erase()로 배열 안의 포인터들을 erase() 해줘야 한다.
vector<int*> vecInt;
// vecInt의 배열들이 각각의 힙 메모리 주소를 가리키고 있다면
for
Safe_Delete(vecInt[i]); // vecInt[i]는 배열의 값, 현재 값은 int* 주소
[ deque ]
: 벡터에서 앞 원소 삽입/삭제 기능을 추가한 동적 배열 기반의 구조 (시퀀스 컨테이너)
[ vector 벡터 ]
- [ , ) 반개 특성으로 앞은 채울 수 없고 뒤만 채울 수 있다.
[ deque 벡터 ]
- ( , ) 특성으로 앞, 뒤 둘 다 채울 수 있다. ( 그러나 순차적으로 읽히기 떄문에 시퀀스 컨테이너 )
- 메모리 블럭 단위로 할당하여 공간을 여유롭게 할다앻놓고 채우는 방식
- 중간 삽입/삭제 시 앞, 뒤 둘 다 이동해야 하기 때문에 vector보다 느림 => vector 선호
* 동적 배열과 정적 배열 둘 다 배열은 개수를 정해놓고 쓰는 것
=> 모든 동적 배열은 배열보다 큰 삽입이 발생할 시 동적 배열을 재할당하여 메모리를 복사하고
확장된 동적 배열의 주소를 가리키는 형태를 사용해야 함
속도 : 정적 > 동적 , 유틸성 : 정적 < 동적
반복자
[ 반복자 ] = 이터레이터
: 각 컨테이너 내부에 선언되어 있는 Iterator 클래스 타입으로 선언한 객체, 컨테이너 내의 원소에 접근 가능
[ 반복자 종류 ]
1. 출력 반복자 : 원소에 접근해서 출력하는 것 가능
- *
2. 입력 반복자 : 입력을 받아서 값 변경이 가능
- *, =
3. 순방향(정방향) 반복자 : 다음 원소로 접근할 수 있다.
- *, =, ++
4. 양방향 접근자 : 원소의 앞, 뒤 접급 가능
- +, =, ++, --
5. 임의 접근 반복자 : 임의 접근(인덱스 접근)으로 가능
- +, =, ++, --, +=, -=
[ 양방향 접근자, 임의 접근 반복자 ] * 주요 사용 반복자 *
1. 양방향 컨테이너 ; 노드 기반 컨테이너
2. 배열 기반 컨테이너 (= vector, deque) : 임의 접근 반복자 사용
=> 컨테이너에 따라 가지고 있는 반복자가 다르다.
-
[ 템플릿 컨테이너 내부에 있는 vector 클래스 템플릿을 유추하여 작성해본 것 ]
template<typename T>
class vector
{
public:
void push_back(T _Input); // 동적 배열의 끝에 값을 추가하는 함수
void pop_back(void); // 동적 배열의 맨 끝 원소를 삭제하는 함수 (벡터의 경우 메모리 블럭은 삭제 X)
void clear(); // 원소 삭제 함수 (메모리 블럭 삭제 X)
int size(void); // 원소 개수 반환 함수
private:
T형 동적배열 멤버 변수
// Iterator는 각 컨테이너의 클래스 템플릿의 멤버 클래스로 유추
class Iterator{
private:
주소 가리키는 포인터 멤버 변수
값 담는 멤버 변수
}
}
=> 이와 비슷한 클래스 템플릿 vector가 벡터 컨테이너에 담겨져 있는 것이다.
※ 배열(정적, 동적)은 첨부터 메모리 공간이 정해져있기 때문에 동적 배열 특성 상 값 추가 시 배열 재할당이 이뤄짐
■ 기능 / 주의
- 반복자는 각 컨테어너 내의 함수 텟플릿 안에 선언되어 있는 Iterator클래스 타입으로 생성한 객체
- 반복자는 Iterator(클래스)타입 객체로 사용자 정의 자료형 타입이기 때문에 반복자 자체를 cout 할 수 없다.
- cout << 도 operator<<(매개변수); 로 선언된 연산자 오버로딩이기 때문에 사용자 정의 자료형을 받을 수 없다.
- 반복자 iter자체는 클래스 객체이기 때문에 (*iter)를 하면 해당 자료형의 값을 반환하여 가능하다.
- *iter도 Iterator 내에 ptr이 가리키는 값을 반환하는 operator* (연산자 오버로딩)이 선언되어 있기 때문에 가능 - 반복자는 컨테이너 내부에 접근 가능한 "객체"이며, 동작 원리는 포인터와 같지만 포인터가 아니다.
- 포인터 p를 출력하면 '주소'가 나오지만 iter는 출력이 불가한 이유이다. iter는 Iterator 클래스 객체니까.
- &iter는 iter 객체의 주소를 출력, iter가 가리키는 값의 주소는 &(*iter)로 출력해야 한다. - 반복자는 메모리 블럭이 존재하더라도 삭제된 원소의 공간을 가리킬 수 없다.
- 컨테이너들은 클래스 템플릿으로 사용자 정의 자료형이기 때문에 '반복자'를 사용해서 접근한다.
- 사용자 정의 자료형 안의 멤버에 접근하기 위해서 포인터를 사용하는 원리와 동일함
- 반복자는 컨테이너 내부 멤버에 접근 가능하도록 허용된 "객체"이고, 포인터도 접근 가능하다.
▷ 메모리 블럭이 존재해도 삭제된 원소의 공간은 가리킬 수 없다의 예제
// int형 벡터 객체 vecInt에 값이 존재한다고 햇을 때
vector<int>::iterator iter2;
vecInt.clear(); // vecInt 객체 안의 원소 전부 삭제, 메모리 블럭은 삭제 X
iter2 = vecInt.begin();
cout << (*iter) << endl; // 런타임 시 오류
=> 메모리 블럭이 존재해도 삭제된 원소의 공간은 가리킬 수 없다
-
void CReport::Pop_Data(void)
{
string sInput = "";
cout << " * 전체 삭제를 원하시면 0 을 눌러주세요 *" << endl;
cout << endl << "삭제할 학생의 이름 : ";
cin >> sInput; // cin은 공백 전까지 입력 받음, 공백 포함은 getline() 사용
for (m_iter = m_vecInfo.begin(); m_vecInfo.end() != m_iter; ) // 반복자를 이용하여 begin부터 end 아닐 때까지 돌림
{
if (!strcmp((*m_iter).szName, sInput.c_str())) // sInput은 문자열 이므로 c_str()을 이용하 char형으로 비교할 문자열과 자료형 일치 시켜주기
{
cout << (*m_iter).iNum << "번 " << (*m_iter).szName << "이(가) 삭제되었습니다." << endl;
m_iter = m_vecInfo.erase(m_iter); // erase(반복자) 는 반복자가 가리키는 원소를 삭제, 삭제 후 다음 위치의 반복자를 반환
// 반복문에 ++iter 시 erase하면 다음 원소를 가리키니까 -- 미리 해주려고 하면
// --시 erase된 원소에 접근해서 오류 발생 ( 메모리 블럭이 존재해도 원소 삭제했으면 접근 불가 )
// 그래서 erase되면 다음 원소 가리키는 반복자 반환하니까 걍 대입 받아서 다음 원소 가리키기
// 만약 지워지지 않았다면 ++m_iter해줘야하니까 밑에 else로 ++m_iter; 해주기
}
else
++m_iter; // erase시 다음 원소를 가리키는 반복자를 반환하므로 erase가 발생하지 않을 때만 ++
}
* m_vecInfo.erase(m_iter)를 m_iter에 꼭 대입해줘야 하는 이유 :
반환되는 다음 원소를 가리키는 반복자를 대입 받지 않는다면 m_iter은 삭제된 원소의 공간을 여전히 가리킴.
그러나 메모리 블럭이 존재해도 삭제된 원소의 공간을 가리키면 오류가 발생하기 떄문에 대입 받아야 한다.
[ 불가능한 예시 ]
for(m_iter = m_vecInfo.begin(); m_vecInfo.end() != m_iter; ++iter) // ++iter이 존재한다면
{
if (!strcmp((*m_iter).szName, sInput.c_str())) // sInput은 문자열 이므로 c_str()을 이용하 char형으로 비교할 문자열과 자료형 일치 시켜주기
{
cout << (*m_iter).iNum << "번 " << (*m_iter).szName << "이(가) 삭제되었습니다." << endl;
m_iter = m_vecInfo.erase(m_iter); // erase(반복자) 는 반복자가 가리키는 원소를 삭제, 삭제 후 다음 위치의 반복자를 반환
--iter; // erase로 인해 다음 원소를 가리키므로 또 증가될 ++iter을 방지해 --iter 시도
// --시 erase된 원소에 접근해서 오류 발생 ( 메모리 블럭이 존재해도 원소 삭제했으면 접근 불가 )
}
}
▷ list의 원하는 번호에 iter로 접근하는 법
LIGHT_DESC* CLight_Manager::Get_Light(_uint iIndex)
{
auto& iter = m_ListLight.begin();
for (_uint i = 0; i < iIndex; ++i)
++iter;
return (*iter)->Get_Light(); // *iter는 iter 클래스가 담고 있는 포인터 멤버 변수
}
* for (iter ; iter < iIndex; ++iter)는 불가, _uint와 iter자료형은 다르기에 비교 불가
■ 예시
#include <vector> // vector 컨테이너 사용 시 포함, 이것이 벡터 컨테이너?
// vector 컨테이너는 동적배열로 반개특성을 가진 vector라는 자료구조 개념을 코드적으로(클래스 템플릿) 구현해놓은 것
void Cout_Endl(); // 보기 편하도록 endl;과 cout << "========" << endl; 을 넣은 함수
class CDainn {
private:
int iA;
public:
int Get() { return iA; }
int operator*() { return iA; }
};
void main(void)
{
CDainn cd;
//CDainn cd;
cout << *cd << endl; // 원래 클래스를 *으로 호출 불가능, 연산자 오버로딩 해준거임 ( operator* )
cout << "여기까지 실험이였음" << endl;
vector<int> vecInt; // 클래스 템플릿인 vector<T>를 이용해서 int형 vector 클래스 vecInt 객체 생성
size_t iInput = 0;
cout << "입력할 값의 개수 : ";
cin >> iInput;
/*
컨테이너(클래스 템플릿)로 생성한 객체 안의 멤버를 접근하기 위해선 반복자를 사용해야함
반복자 : 각 컨테이너 안에 선언되어 있는 Iterator 클래스로 선언한 객체 (컨테이너마다 반복자 종류가 다름)
아마 Iterator 클래스는 값을 담는 멤버와 주소를 담는 멤버를 갖고 있는 듯 하다. ( MS 측에서 내부 코드 공개 안 함 )
Iterator는 컨테이너 안의 원소에 접근할 수 있도록 허용된 " 객체 "로 ( 포인터는 접근 불가? )
포인터와 동작원리가 같아 똑같이 주소를 담고 그 주소를 가리켜 원소에 접근하지만 "포인터"는 아님
즉 Iter라는 반복자(객체)를 생성했을 때 얘는 iter 자체로 출력할 수 없음 (포인터는 p 출력 시 주소 나옴)
=> iter는 Iterator 클래스의 객체 이기 때문이다.
(*iter)를 하게 되면 iter의 '주소 가리키는 멤버'가 가리키는 원소를 출력한다.
&(*iter)를 한다면 당연히 iter클래스 내부의 멤버가 가리키는 원소의 주소를 출력할 것이다.
&iter만 하게 된다면 클래스 Iterator타입으로 할당된 iter의 메모리 공간 주소가 나올 것이다.
*/
for (size_t i = 1; i <= iInput ; ++i)
{
int iData = 0;
cout << i << "번 원소" << endl;
cout << " ========== " << endl;
cin >> iData;
vecInt.push_back(iData); // vecInt에 있는 멤버 변수 int형 배열 끝에 10*i를 넣겠다는 말
cout << "메모리 블럭 : " << vecInt.capacity() << endl; // vecInt 클래스 내에 있는 배열의 메모리 블럭 수를 출력
// 원소가 들어오면 +1 메모리 공간의 배열을 재할당 ( 복붙으로 넣어주고 해당 메모리 주소 가리킴 )
// push_back()할 때마다 재할당해야 하는 문제를 방지하여 일정 수준이 되면
// 추가 원소가 들어올 때 메모리 블럭을 +1이 아닌 2 + 2/n만큼 넉넉히 마련
// 그럼 재할당 없이 원소를 채우다가 여유분 메모리 블럭마저 다 사용하면 다시 여유분을 두어 재할당
}
// vecInt 안의 원소에 접근(출력, 값변경)하기 위해 반복자 사용
// 클래스의 멤버에 접근하기 위해선 원래 객체 또는 포인터로 접근하는 원리와 같음
// 컨테이너는 Iterator타입의 객체로 접근할 수 있는 것, 포인터로도 가능
vector<int>::iterator iter;
// 아마 반복자는 한 번만 초기화가 가능한 듯하다.
iter = vecInt.begin(); // begin()는 원소의 시작 값/위치 를 Iterator타입으로 반환하는 함수
// iter는 Iterator 클래스 타입이기 때문에 = 이 성립하기 위해선 같은 자료형이어야 됨 ( = 은 좌항 우항의 자료형이 같아야 성립함 )
// 따라서 반복자는 begin(), end()로만 초기화 가능 ( begin, end 함수는 반환타입 Iterator )
/*
for ( iter ; vecInt.size() > iter; ++i)의 vecInt.size() > iter은 성립할 수 없음
vecInt.size()는 원소의 개수를 int형으로 반환, iter는 Iterator타입이므로 비교 자체가 불가
따라서 자료형이 Iterator타입으로 일치하는 vecInt.end() != iter로 조건식 넣어줘야함 */
/*
!=, ++ 다 일반 연산이 아니고 연산자 오버로딩이 되어 있는 것 ( ++는 다음 원소를 가리키도록 연산자 오버로딩 되어 있겠죵 )
클래스는 사용자 정의 자료형이고 당연히 사용자 정의 자료형을 일반 연산으로 할 수 없음
객체 간의 연산을 위해 존재하는 것이 연산자 오버로딩
각 컨테이너마다 내부에 선언되어 있는 연산자 오버로딩이 다름
vecInt.end() != iter 를 사용하는 이유도 벡터의 경우 > 로도 가능하지만 (벡터 내부에 operator> 이 있음)
다른 컨테이너의 경우 operator> 이 없는 경우도 있어서 전부 선언되어있는 operator!= 을 주로 사용
*/
Cout_Endl();
for (iter; vecInt.end() != iter; ++iter)
{
cout << "원소 : " << *iter << endl; // iter는 Iterator 클래스 타입의 객체이기 때문에 그 자체로 출력 불가
}
// 반복자 대신 포인터를 사용한 vector 클래스 접근
int* p = &(vecInt.front()); // vecInt의 첫번째 원소의 주소를 포인터에 대입
Cout_Endl();
for (size_t i = 0; vecInt.size() > i; ++i)
{
cout << "포인터로 원소 출력 버전 " << i + 1 << "번 : ";
cout << *p << endl; // 포인터가 가리키는 값 출력
/* back(), front() 함수
얘네는 템플릿 안의 T형 배열의 front, back 위치에 해당하는 원소를 반환 , vecInt.front() = 10; 처럼 쓰기도 가능하다. */
if (i == vecInt.size() - 1)
{
cout << "마지막 원소의 값 : ";
cout << vecInt.back() << endl; // 원소의 마지막 값 출력
*p = 100;
cout << "포인터로 값 변경 : " << *p << endl;
cout << "마지막 원소의 값 : " << vecInt.back() << endl;
// return은 반복문이 아니라 함수 탈출문임을 명심하자, break가 switch문과 반복문 탈출
}
++p; // 다음 원소의 주소를 가리키도록 포인터 증가
}
}
'C++' 카테고리의 다른 글
| [TIL 31장] 리스트(feat. 벡터&리스트 시간 복잡도 및 응용), 임시 객체 (0) | 2022.09.22 |
|---|---|
| [TIL 30장] 벡터 함수(feat. 2차원벡터), 알고리즘(feat. 조건자) (1) | 2022.09.19 |
| [TIL 28장] STL 개론(feat. 자료구조), 컨테이너, 시간 복잡도, 벡터 (0) | 2022.09.14 |
| [TIL 27장] 템플릿(함수 템플릿, 클래스 템플릿), 템플릿 특수화(feat. static 템플릿) (0) | 2022.09.13 |
| [TIL 26장] 연산자 오버로딩, 함수 객체, 함수 포인터, inline (0) | 2022.09.13 |