이글은 퍼온 글입니다.

원본은 아래 주소에 있습니다.

http://yatoyato.tistory.com/1534


배열을 처리하는 C++함수는 배열에 들어있는 데이터의 종류, 배열의 시작위치, 배열에 들어있는 데이터의 종류, 배열의 시작위치, 배열에 들어있는 원소 개수에 관한 정보를 넘겨받아야 한다. 전통적으로 C와 C++가 배열을 처리하는 함수에 접근하는 방법은, 첫번째 전달인자로 배열의 시작위치를 지시하는 포인터를 전달하고, 두번째 전달인자로 배열의 크기를 전달하는 것이다. (그 포인터는 배열이 어디에 있는지와 배열에 들어있는 데이터의 종류가 무엇인지를 말해준다.) 그렇게 하면 배열의 모든 데이터를 인식하는데 필요한 정보가 함수에 전달된다.

필요한 정보를 함수에 전달하는 또 하나의 방법이 있다. 원소들의 범위를 지정하는 것이다. 이것은 두 개의 포인터, 즉 배열의 시작을 지시하는 포인터와 배열의 끝을 지시하는 포인터를 전달하여 이루어진다. 예를 들면 C++ 표준 템플릿 라이브러리(C++ Standard Template Library)에서는 이러한 범위접근방법을 일반화한다. STL접근방법은 원소들의 범위를 지정하기 위해 "끝 바로 다음"이라는 개념을 사용한다. 즉, 어떤 배열이 있다고 했을 때, 배열의 끝을 인식하기 위한 전달인자는 배열의 마지막 원소 바로 다음을 지시하는 포인터가 될 것이다. 예를 들어, 다음과 같은 선언이 있다고 가정해보자.

double elbuod[20];

그러면 두 개의 포인터 elbuod와 elbuod+20이 배열의 범위를 지정한다. 먼저 배열의 이름인 elbuod는 첫번째 원소를 지시한다. elbuod+19는 마지막 원소(즉, elbuod[19])를 지시한다. 그러므로 elbuod+20은 배열의 마지막 원소 바로 다음을 지시한다. 범위를 전달하는 것은 어떤 원소들을 처리해야 하는지를 함수에게 알려준다.

아래의 예제는 범위를 지정하는 두 개의 포인터를 사용한다.

int sum = sum_arr(cookies, cookies+ARSIZE);

int sum_arr(const int *begin, const int *end) { 
    const int *pt; 
    int total = 0;

    for (pt=begin; pt!=end; pt++) 
        total = total + *pt; 
    return total; 
}

포인터 값 cookies+ARSIZE는 마지막 원소 바로 다음 위치를 지시한다. 배열이 ARSIZE개의 원소를 가지고 있으므로 cookies[ARSIZE-1]은 마지막 원소이다. 그리고 마지막 원소의 주소는 cookies+ARSIZE-1이다. 따라서 cookies, cookies+ARSIZE는 배열 전체를 지정하는 범위가 된다. 포인터 뺄셈의 규칙에 따르면 sum_arr()와 같은 함수에서 표현식 end-begin은 그 범위에 들어있는 원소의 개수와 동일한 정수값이 된다.

포인터와 const

const라는 키워드는 포인터에 두가지 방법으로 사용된다. 첫번째 방법은 상수 객체를 지시하는 포인터를 만드는 것이다. 상수객체를 지시하는 포인터를 사용하여 그 포인터가 지시하는 값을 변경할 수 없다. 두번째 방법은 포인터 자신을 상수로 만드는 것이다. 상수포인터를 사용하여 그 포인터가 지시하는 장소를 변경할 수 없다.

상수를 지시하는 pt라는 포인터를 선언한다.

const int *pt = &age;

pt에 대한 이 선언은 그것이 지시하는 값이 실제로 상수라는 것을 의미하지는 않는다. 단지 pt가 관계하는 한에서만 그 값이 상수라는 것을 의미한다. 예를 들어 pt는 age를 지시하고 있지만, age는 const가 아니다. 따라서 age변수를 직접 사용하여 age의 값을 변경할 수 있다. 그러나 포인터 pt를 사용해서는 age의 값을 변경할 수 없다.

const변수의 주소를 const를 지시하는 포인터에 대입하는 것은 가능하나, const변수의 주소를 일반포인터에 대입하는 것을 허용되지 않는다. 후자의 것은 변수의 const를 무의미하게 만든다. 그렇기 때문에 C++는 const변수의 주소를 const가 아닌 일반포인터에 대입하는 것을 금지한다. 그래도 필사적으로 꼭 그렇게 하기를 원한다면, const_cast연산자를 이용한 데이터형 변환을 사용하여 이 제한을 무시할 수 있다.

포인터를 지시하는 포인터를 사용할 때 이러한 사정을 더욱 복잡해진다. 앞에서도 살펴보았듯이, const가 아닌 포인터를 const포인터에 대입하는 것은 한다리 건너는 간접지시인 경우에만 사용할 수 있다.

int age = 39;                        // age++은 사용할 수 있다. 
int *pd = &age;                      // *pd = 41은 사용할 수 있다. 
const int *pt = pd;                  // *pt = 42는 사용할 수 없다.

cosnt와 const가 아닌 것을 이런 식으로 섞어서 사용하는 포인터대입은 두다리 건너는 간접지시인 경우에는 더이상 안전하지 않다. const와 const가 아닌 것을 섞어서 사용하는 것이 허용된다면 다음과 같은 것도 가능하게 되기 때문에 안된다.

const int **pp2; 
int *p1; 
const int n = 13; 
pp2 = &p1;                           // 허용되지 않지만 허용된다고 가정하면 
*pp2 = &n;                           // 둘다 const인데 p1이 n을 지시하게 만든다. 
*p1 = 10;                            // const n을 변경하게 만든다.

앞의 코드는 const가 아닌 주소(&p1)를 const포인터(pp2)에 대입한다. 그것은 p1을 사용하여 const데이터를 변경하도록 허용한다. 그러므로 const가 아닌 주소나 포인터를 const포인터에 대입할 수 있다는 규칙은 한다리 건너는 간접지시인 경우에만 유효하다. 예를 들면, 그 포인터가 기본 데이터형을 지시하는 경우에만 유효하다. 데이터형 자체가 포인터가 아니라면, const데이터의 주소이든 const가 아닌 데이터의 주소이든 const를 지시하는 포인터에 모두 대입할 수 있다. 그러나 const가 아닌 데이터의 주소는 const가 아닌 포인터에만 대입할 수 있다.

const데이터들을 원소로 가지는 다음과 같은 배열이 있다고 가정할 때, 상수배열의 주소를 대입할 수 없도록 금지하는 것은, 상수가 아닌 형식 매개변수를 사용하여 배열의 이름을 전달인자로 함수에 전달할 수 없다는 것을 의미한다.

const int months[12] = { ... }; 
int sum(int arr[], int n);           // const int arr[]이어야 한다. 
int j = sum(months, 12);             // 허용되지 않는다.

이 함수호출은 const포인터인 months를 const가 아닌 포인터 arr에 대입하려고 시도한다. 그러나 컴파일러는 이러한 함수호출을 허용하지 않는다. 이 선언에서 const를 사용한 것은 함수가 자신에게 전달되는 배열이 어떤 것이든 간에 그 배열에 들어있는 값을 변경할 수 없다는 것을 뜻한다. 이것은 한다리 건너는 간접지시의 경우에만 동작한다. 예를 들어, 그 배열의 원소들은 기본형이어야 한다. 그 배열의 원소들이 포인터이거나 포인터를 지시하는 포인터인 경우에는 const를 사용할 수 없다.

가능하면 const를 사용해야 한다. 포인터 전달인자를 상수데이터를 지시하는 포인터로 선언하는 이유는 두가지이다. 이것은 실수로 데이터를 변경시키는 프로그래밍 에러를 막을 수 있다. const를 사용하는 함수는 const와 const가 아닌 전달인자를 모두 처리할 수 있지만, 함수원형에서 const를 생략한 함수는 const가 아닌 데이터만 처리할 수 있다. 따라서 가능하다면 형식포인터 전달인자를 const를 지시하는 포인터로 선언해야 한다.

const를 사용하는 또 하나의 방법은 포인터 자신의 값을 변경하지 못하게 막는 것이다.

int* const finger = &sloth;          // int를 지시하는 const포인터

이러한 형태의 선언은 finger가 sloth만을 지시하도록 제한한다. 그러나 finger를 이용하여 sloth의 값을 변경할 수는 있다. 그러나 포인터가 가리키는 주소를 변경할 수는 없다.

const double* const stick = &trouble;

stick은 trouble만을 가리킬 수 있고 stick을 사용하여 trouble의 값을 변경할 수 없다.

함수와 C스타일 문자열

문자열을 리턴하는 함수를 작성하고 싶다고 가정하자. 함수로는 문자열 자체를 리턴할 수 없다. 그 대신에 문자열의 주소를 리턴할 수 있다. 그것이 더 효율적이다.

char *ps = buildstr(ch, times); 
delete []ps;

char* buildstr(char c, int n) { 
    char *pstr = new char[n+1]; 
    pstr[n] = '\0'; 
    while (n-- > 0)                  // 뒤에서부터 문자를 채워, 포인터가 문자열의 시작위치를 가리킨다. 
        pstr[n] = c; 
    return pstr; 
}

변수 pstr은 buildstr()함수의 지역변수이다. 따라서 함수가 종결되면 pstr에 할당되었던 기억장소는 자동으로 해제된다. 그러나 이 함수는 종결하기 전에 pstr의 값을 리턴하기 때문에 main()에 있는 ps포인터를 통하여 그 문자열에 접근할 수 있다.

이 예제는 문자열이 더이상 필요없게 되면 delete를 사용하여 그 문자열에 할당되었던 기억장소를 해제한다. 이 방식의 단점은 프로그래머가 delete사용을 항상 기억해야 한다는 것이다.

, .