Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-16 04:51
관리 메뉴

nomad-programmer

[Programming/C++] 배열의 대안 (vector, array 템플릿 클래스) 본문

Programming/C++

[Programming/C++] 배열의 대안 (vector, array 템플릿 클래스)

scii 2023. 2. 15. 01:29

vector 템플릿 클래스

vector 템플릿 클래스는 동적 배열에 속하는 string 클래스와 유사하다. 프로그램에 실행되는 동안 vector 객체의 크기를 세팅할 수 있고, 새로운 데이터를 마지막에 추가하거나 중간에 데이터를 삽입할 수도 있다. 기본적으로 동적 배열을 생성하기 위해 new를 사용하는 것을 대체할 수 있다. 실제로 vector 클래스는 메모리를 관리하기 위해서 new와 delete를 사용하지만, 그 과정은 자동으로 진행된다.
몇 가지 기본적이고 실질적인 문제를 살펴보도록 하자. 

첫째, vector 객체를 사용하기 위해서는 vector 헤더 파일을 포함해야 한다.

둘째, vector 식별자는 std 이름 공간의 일부분이기 때문에 using 명령, using 선언 또는 std::vector를 사용할 수 있다.

셋째, 템플릿은 저장된 데이터 형태를 지시하기 위해서 다른 구문을 사용한다.

넷째, vector 클래스는 원소의 개수를 지칭하기 위해서 다른 구문을 사용한다. 아래의 몇 가지 예문을 참고하자.

#include <vector>
...
using namespace std;
vector<int> vi;
int n;
cin >> n;
vector<double> vd(n); // n개의 더블형 배열 생성

vi는 vector<int> 형의 객체라고 얘기하고 vd는 <double>vector형의 객체라고 말한다. vector 객체는 값을 삽입하거나 더할 때 자동으로 크기를 조정하기 때문에 vi가 0의 크기에서부터 시작하는 것은 아무런 문제가 되지 않는다. 그런, 크기를 재조정할 때에는 vetor 패키지에 포함된 다양한 방법들을 사용할 수 있다. 일반적으로 다음의 선언은 typeName 형태의 n_elem 원소들을 보유할 수 있는 vt vector 객체를 생성한다.

vector<typeName> vt(n_elem);

매개변수 n_elem은 정수형 상수 또는 정수형 변수가 될 수 있다.


array 템플릿 클래스 (C++11)

vector 클래스는 내재 배열 형보다 많은 기능을 지니고 있지만, 다소 비효율적인 면인 있다. 만약 사용자가 고정된 크기의 배열만 필요하다면, 내재 배열형을 사용하는 것이 매우 흥미로운 일이 될 것이다. 그러나, 그럴 경우에 안전성과 편리성은 다소 줄어들 수 있다. C++11은 그 array 템플릿 클래스를 더해 줌으로써 이러한 경우에 대하여 해결 방안을 제시하고 있는데, 이것은 std 이름 공간의 일부분에 해당 된다. 내재 배열형과 마찬가지로, array 객체는 자유 저장 대신에 고정된 크기와 고정 메모리 대입을 사용하여 내재 배열이 지닌 것과 동일한 수준의 효율성을 지닌다.

array 객체를 생성하기 위해서 사용자는 array 헤더 파일을 포함시켜야 하는데, 그 구문은 vector에 대한 구문과 다소 상이하다.

#include <array>
...
using namespace std;
array<int, 5> ai;  // 5개의 int array 객체 생성
array<double, 4> ad = {1.1, 2.2, 3.3, 4.4};

일반적으로, 다음의 구문은 typeName의 n_elem 요소를 통해서 array 객체 arr를 생성한다.

array<typeName, n_elem> arr;

vector의 경우와는 달리, n_elem은 변수가 될 수 없다. C++11은 vector와 array 객체에 대하여 목록 초기화 기능을 사용할 수 있지만, C++98 버전의 vector 객체에서는 이 옵션 기능이 없었다.


배열, vector 객체, array 객체 비교

배열(arrays), vector 객체, array 객체 간의 공통점과 상이점을 아마도 가장 쉽게 이해하기 위해 상기 세 가지 방법을 사용하고 있는 다음의 코드를 살펴보자.

#pragma warning(disable: 4996)

#include <iostream>
#include <vector>
#include <array>

using namespace std;


int main() {
    double a1[4] = { 1.1, 2.2, 3.3, 4.4 };

    vector<double> a2(4);
    a2[0] = 1.0 / 3.0;
    a2[1] = 1.0 / 5.0;
    a2[2] = 1.0 / 7.0;
    a2[3] = 1.0 / 9.0;

    array<double, 4> a3 = { 3.14, 2.25, 1.33, 1.58 };
    array<double, 4> a4;
    a4 = a3;    // 동일한 크기의 array 객체에 유효하다.

    cout << "a1[2]: " << a1[2] << " at " << &a1[2] << endl;
    cout << "a2[2]: " << a2[2] << " at " << &a2[2] << endl;
    cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
    cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;

    // 잘못됨
    a1[-2] = 20.2;
    cout << "a1[-2]: " << a1[-2] << " at " << &a1[-2] << endl;
    cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
    cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;

    return 0;
}


// 결과
a1[2]: 3.3 at 00000053632FFA68
a2[2]: 0.142857 at 00000213E0282E30
a3[2]: 1.33 at 00000053632FFAE8
a4[2]: 1.33 at 00000053632FFB28
a1[-2]: 20.2 at 00000053632FFA48
a3[2]: 1.33 at 00000053632FFAE8
a4[2]: 1.33 at 00000053632FFB28

프로그램 분석

첫째, 내재 배열, vector 객체, 또는 array 객체를 사용하건 간에, 사용자는 개별 멤버에 접속하기 위해서는 표준 배열 표식을 사용할 수 있다. 
둘째, 주소를 통해서 array 객체가 동일한 지역의 메모리를 사용한다는 것을 알 수 있다(stack 영역). 반면 vector 객체는 다른 지역에 저장된다(heap 영역). 
셋째, 하나의 array 객체를 또 다른 array 객체에 대입할 수 있다. 내재 배열에 대하여 데이터를 요소별로 복사해야 한다.

a1[-2] = 20.2;

위의 코드는 잘못되었다. 하지만 컴파일 에러가 발생하지 않는다. C와 마찬가지로 C++은 범위 밖 에러에 대하여 체크하지는 않는다. 

이것을 대체할 수 있는 다른 방법이 있다. 그 중 하나는 at() 멤버 함수를 사용하는 것이다. at() 멤버 함수를 vector 객체 또는 배열 타입에 사용할 수 있다.

a2.at(1) = 2.3; // 2.3을 a2[1]에 대입함.

bracket notation을 사용하는 것과 at() 멤버 함수를 사용하는 것의 차이는 만약 사용자가 at()을 사용할 경우 유효하지 않은 인덱스가 런타임과 프로그램 도중에 디폴트에 의해 잡히게 되어 전격 최소된다는 사실일 것이다. 체크를 늘리려면 러닝 타임을 늘려야 가능하기 때문에, C++에서는 두 가지 notation을 사용할 수 있는 옵션을 제공하고 있다. 무엇보다도, 이 클래스들은 실수로 인한 범위 에러의 가능성을 줄이는 객체 사용 방법을 제공한다. 예를 들면, 이 클래스는 사용자로 하여금 사고로 한계 범위를 초과할 수 있는 경우를 방지하여 범위를 다시 조정할 수 있게 해 주는 begin()과 end() 멤버 함수를 지닌다. 

Comments