본문 바로가기
Dev Language/C++

배열과 포인터

by 미티치 2016. 12. 5.

배열

 

 중요한 것은, 배열을 선언하면 내부적으로 포인터와 동일하게 작동한다는 것이다. 그래서 포인터 표기 *과 배열 표기[]는 거의 동일하게 사용할 수 있다. 예를 들어 int pt[3] = { 0 }; 으로 선언하면 pt[0] *pt는 같은 값이다. pt[1]*(pt+1)은 같은 값이다.

-      일반 변수를 함수의 매개변수로 사용하면 함수는 전달된 값의 복사본을 갖고 작업을 한다. 하지만 배열을 매개변수로 전달하면, 배열의 복사본을 생성하여 작업을 하는 것이 아니라 배열의 주소, 즉 포인터를 전달하여 배열의 원본을 가지고 작업한다.

-      배열의 주소를 매개변수로 사용하는 것은 전체 배열을 복사하는 것보다 시간과 메모리를 절약한다. 복사본을 사용하는 것은 배열이 클 경우에는 부담이 된다. 반면 원본을 대상으로 작업하면 부주의에 의해 데이터가 손상될 위험이 있다. 이것은 C에서 골치 아픈 문제였다.

-      따라서 어떤 함수에서 배열의 원본 데이터를 갖고 작업은 하지만, 절대 이 배열의 값을 직접 변경하지 않는 경우에는 위와 같은 문제를 해결하기 위해 const를 이용한다.

-      배열의 내용을 출력하는 함수를 작성한다고 하자. 배열 이름과 값이 채워진 원소의 개수를 함수에 전달하면, 함수는 루프를 사용하여 각 원소를 출력한다. 그러나 여기에서 출력이 배열의 원본을 변경시키지 않는다는 보증을 고려해야 한다. 함수의 목적이 데이터를 변경하는 것이 아니라면 데이터가 변경되지 않도록 보호해야 한다. 일반 변수는 위에서 언급했듯 함수에 전달될 때 복사본을 갖고 작업하기 때문에 문제가 없지만, 배열은 원본을 넘기기 때문에 이러한 문제가 생길 수 있다. 이러한 문제를 해결하기 위해 const 키워드를 형식 매개변수를 선언할 때 사용하면 이러한 문제를 해결할 수 있다.

-      void show_ary(const double ar[], int n); (# 전통적으로 CC++이 배열을 처리하는 함수에 접근하는 방법은, 첫 번째 매개변수를 배열의 시작 위치를 지시하는 포인터를 전달하고, 두 번째 매개변수로 배열의 크기를 전달하는 것이다. ) 이렇게 선언하면 포인터 ar이 상수 데이터를 지시하고 있다는 것을 의미하며, ar을 사용해서는 그 데이터를 변경할 수 없다는 것을 의미한다. 이것은 원본 배열이 반드시 상수여야 한다는 의미가 아니고, show_ary() 함수가 ar을 사용하여 그 데이터를 변경할 수 없다는 뜻이다. 따라서 이 함수는 ar배열을 읽기 전용 데이터로 취급한다.

-      위와 같이 선언한 후, 함수 안에서 배열의 값을 변경하려는 시도를 하면 컴파일러는 에러를 뱉는다. 따라서 함수가 원본 배열의 값을 변경할 목적을 갖고 있다면 매개변수에 넘기는 배열을 const로 선언하면 안 된다

 

 

포인터와 const

 

 포인터에 const를 사용하는 것은 상수 객체를 지시하는 포인터를 사용하여 그 포인터가 지시하는 값을 변경할 수 없는, 위와 같은 방법 이외에 한 가지 방법이 더 있다. 두 번째 방법은 포인터 자신을 상수로 만드는 것이다. 상수 포인터를 사용하여 그 포인터가 지시하는 장소를 변경할 수 없다.

int age = 39;
const int *pt = &age;

 위와 같은 경우에 age 변수를 이용해서 age의 값을 변경할 수는 있지만 포인터 pt를 사용해서는 age의 값을 변경할 수 없다.

-      이외에도 두 가지의 가능성이 더 남아있다. 하나는 const 변수의 주소를 const를 지시하는 포인터에 대입하는 것이고, 이러한 경우는 gpe를 사용하여 9.8이라는 값을 변경시킬 수 없다.
const float g = 9.8;
const float * pe = &g;

 

-      다른 하나는 const 변수의 주소를 일반 포인터에 대입하는 것이다. 하지만 이 경우는 아예 사용할 수 없다. const로 선언한 변수를 포인터로 변경할 수 없기 때문이다
const float h = 1.6;
float *pm = &h;

-      참조는 기본 built-in 데이터 타입보다는 주로 사용자 정의 데이터 형에 사용하기 위해 도입된 것이다.

 

클래스 Stock을 선언하고, main에서 생성하여 객체를 사용할 때 const는 함수에도 사용할 수 있다. 예를 들어 Stock 클래스에 void show(); 라는 함수가 있다고 하자. 이 함수는 객체의 필드를 변경하진 않고 객체의 정보를 출력해주는 함수이다.

const Stock land = Stock(“Klud Pro”);
           land.show();

위처럼 선언했을 때, 객체를 const로 선언하였기 때문에 변경하면 안 되는 객체로 인식한다. 하지만 show 메소드가 호출 객체를 수정하지 않는다는 보장이 없다. 따라서 show 메소드가 객체를 변경하지 않는다는 약속을 해주어야 하기 때문에 함수 선언을 할 때 const를 선언해주면 된다.

        void show() const;          //함수 원형

 마찬가지로 show() 함수 정의의 시작 부분도 다음과 같다.

        void Stock::show() const

이와 같이 호출 객체를 변경하면 안 되는 클래스 메서드들은 const로 만들어야 한다.

 

 

** 클래스 생성자와 const

 

Queue 생성자가 다음과 같을 때,

Queue :: Queue(int qs)
{          front = rear = NULL;
        items = 0;
        qsize = qs;
}

 여기서 qsizeconst이기 때문에 어떤 값으로 초기화될 수는 있지만, 어떤 값이 대입될 수는 없다. 개념적으로 보았을 때, 생성자를 호출하면 중괄호 안의 코드가 실행되기 전에 객체가 먼저 생성된다. 따라서 Queue(int qs) 생성자를 호출하면, 프로그램은 먼저 4개의 멤버 변수를 위한 기억 공간을 대입하고 중괄호 안으로 들어가 일반적인 대입을 통해 대입된 그 기억 공간에 값들을 대입한다. 따라서 const 데이터 멤버를 초기화하려면, 프로그램 제어가 생성자 몸체에 도달하기 전인, 객체가 생성될 때 초기화 해야 한다.

 C++은 이 일을 처리하기 위해 멤버 초기자 리스트를 제공한다. 멤버 초기자 리스트는 앞에 콜론(:)이 붙어있고, 초기자들을 콤마로 분리해 놓은 리스트이다. 이러한 표기 형식을 사용한 Queue  생성자를 다음과 같이 작성할 수 있다.

Queue :: Queue(int qs) : qsize(qs)
{          front = rear = NULL;
        items = 0;
}

 더 나아가서 다음과 같이 작성할 수 있다.

Queue :: Queue(int qs) : qsize(qs) , front(NULL) , rear(NULL) , items(0)
{         
}

 이처럼 frontitems와 같이 간단한 데이터 멤버의 경우, 멤버 초기자 리스트를 사용하는 거소가, 함수 몸체 안에서 대입을 사용하는 것이 별 다른 차이가 없다. 그러나 그 자체가 클래스 객체인 멤버들에 대해서는 멤버 초기자 리스트를 사용하는 것이 훨씬 효율적이다. 문법은 다음과 같다.

           NClass :: NClass (int n, int m) : mem1(n), mem2(0), mem3(n*m +2) { … }

 

'Dev Language > C++' 카테고리의 다른 글

참조 변수 & 복사 생성자  (1) 2016.12.06
if문과 switch문  (0) 2016.12.06
함수 오버로딩  (0) 2016.12.06
함수 원형이 필요한 이유  (0) 2016.12.05
스택에 저장되는 값 (자료 백업)  (0) 2016.11.29