C/C++에서 포인터와 배열 표기법의 차이점

Ilja Kostenko 2023년10월12일
  1. C++ 배열
  2. C++ 포인터
C/C++에서 포인터와 배열 표기법의 차이점

포인터와 배열은 의심할 여지 없이 C++의 가장 중요하고 복잡한 측면 중 하나입니다. 연결 목록과 동적 메모리 할당을 지원하고 함수가 인수의 내용을 변경할 수 있도록 합니다.

C++ 배열

배열은 인덱스에 의해 액세스되는 동일한 유형의 요소 집합입니다(배열에 있는 요소의 서수). 예를 들어:

int ival;

ivalint 유형 변수 및 명령어로 정의합니다.

int ia[10];

10개의 int 개체 배열을 설정합니다. 이러한 각 개체 또는 배열 요소는 인덱스를 가져오는 작업을 사용하여 액세스할 수 있습니다.

ival = ia[2];

인덱스가 2인 배열 ia 요소의 값을 ival 변수에 할당합니다. 유사하게

ia[7] = ival;

인덱스가 7인 요소에 ival 값을 할당합니다.

배열 정의는 유형 지정자, 배열 이름 및 크기로 구성됩니다. 크기는 배열 요소의 수(최소 1개)를 지정하며 대괄호로 묶입니다. 배열의 크기는 컴파일 단계에서 이미 알고 있어야 하므로 반드시 리터럴로 설정되는 것은 아니지만 상수 표현식이어야 합니다.

요소의 번호는 0부터 시작하므로 10개의 요소로 구성된 배열의 경우 올바른 인덱스 범위는 1에서 10이 아니라 0에서 9입니다. 다음은 배열의 모든 요소를 ​​정렬하는 예입니다.

int main() {
  const int array_size = 10;
  int ia[array_size];
  for (int ix = 0; ix < array_size; ++ix) ia[ix] = ix;
}

배열을 정의할 때 배열 요소의 값을 쉼표로 구분하여 중괄호 안에 나열하여 명시적으로 초기화할 수 있습니다.

const int array_size = 3;
int ia[array_size] = {0, 1, 2};

값 목록을 명시적으로 지정하는 경우 배열의 크기를 지정하지 않을 수 있습니다. 컴파일러 자체가 요소 수를 계산합니다.

C++ 포인터

포인터는 다른 개체의 주소를 포함하고 이 개체를 간접적으로 조작할 수 있는 개체입니다. 포인터는 일반적으로 동적으로 생성된 객체로 작업하고, 연결 목록 및 계층 트리와 같은 관련 데이터 구조를 구축하고, 큰 객체(배열 및 클래스 객체)를 매개변수로 함수에 전달하는 데 사용됩니다.

각 포인터는 일부 유형의 데이터와 연결됩니다. 내부 표현은 내부 유형에 의존하지 않습니다. 포인터 유형의 객체가 차지하는 메모리 크기와 값 범위는 모두 동일합니다. 차이점은 컴파일러가 주소 지정 가능한 개체를 인식하는 방법입니다. 다른 유형에 대한 포인터는 동일한 값을 가질 수 있지만 해당 유형의 메모리 영역은 다를 수 있습니다.

여기 예시들이 있습니다 :

int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;

포인터는 이름 앞에 별표로 표시됩니다. 목록으로 변수를 정의할 때 별표는 각 포인터 앞에 위치해야 합니다(위 참조: ip1 및 ip2). 아래 예에서 lp는 long 유형의 객체에 대한 포인터이고 lp2는 long 유형의 객체입니다.

long *lp, lp2;

다음 경우에 fp는 float 객체로 해석되고 fp2는 이에 대한 포인터입니다.

float fp, *fp2;

int 유형의 변수가 주어집니다.

int ival = 1024;

다음은 int pi 및 pi2에 대한 포인터를 정의하고 사용하는 예입니다.

[//] pi is initialized with the null address
int *pi = 0;
[//] pi2 is initialized with the address ival
int *pi2 = &ival;
[//] correct: pi and pi2 contain the ival address
pi = pi2;
[//] pi2 contains the null address
pi2 = 0;

포인터에는 주소가 아닌 값을 할당할 수 없습니다.

[//] error: pi cannot take the value int
pi = ival

마찬가지로 다음 변수가 정의된 경우 다른 유형의 개체 주소인 한 유형의 포인터에 값을 할당할 수 없습니다.

double dval;
double *ps = &dval;

그러면 아래 주어진 두 할당 표현식 모두 컴파일 오류가 발생합니다.

[//] compilation errors
[//] invalid assignment of data types: int* <== double*
pi = pd
pi = &dval;

변수 pi가 dval 개체의 주소를 포함할 수 없다는 것은 아닙니다. 다른 유형의 개체 주소는 길이가 같습니다. 컴파일러의 객체 해석은 포인터 유형에 따라 다르기 때문에 이러한 주소 혼합 작업은 의도적으로 금지됩니다.

물론 주소가 가리키는 객체가 아니라 주소 자체의 값에 관심이 있는 경우가 있습니다(이 주소를 다른 주소와 비교하고 싶다고 가정해 봅시다). 이러한 상황을 해결하기 위해 모든 데이터 유형을 가리킬 수 있는 특수한 유효하지 않은 포인터를 도입할 수 있으며 다음 표현식은 정확합니다.

[//] correct: void* can contain
[//] addresses of any type
void *pv = pi;
pv = pd;

void*가 가리키는 개체의 유형을 알 수 없으며 이 개체를 조작할 수 없습니다. 그러한 포인터로 할 수 있는 일은 그 값을 다른 포인터에 할당하거나 주소 값과 비교하는 것뿐입니다.

주소가 있는 개체에 액세스하려면 별표(*)로 표시된 역참조 작업 또는 간접 주소 지정을 적용해야 합니다. 예를 들어,

int ival = 1024;
, ival2 = 2048;
int *pi = &ival;

포인터 pi에 역참조 연산을 적용하여 val의 값을 읽고 저장할 수 있습니다.

[//] indirect assignment of the ival variable to the ival2 value
*pi = ival2;
[//] value indirect use of variable value and pH value value
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

int 유형의 객체에 주소(&)를 취하는 작업을 적용하면 int* 유형의 결과를 얻습니다.

int *pi = &ival;

동일한 작업이 int* 유형의 객체(int C 유형에 대한 포인터)에 적용되고 int 유형에 대한 포인터, 즉 int** 유형에 대한 포인터를 얻는 경우. int**는 int 유형의 개체 주소를 포함하는 개체의 주소입니다.

ppi를 역참조하면 ival 주소가 포함된 int* 개체를 얻습니다. ival 개체 자체를 가져오려면 역참조 작업을 PPI에 두 번 적용해야 합니다.

int **ppi = &pi;
int *pi2 = *ppi;
cout << "ival value\n"
     << "explicit value: " << ival << "\n"
     << "indirect addressing: " << *pi << "\n"
     << "double indirect addressing: " << **ppi << "\n"
     << end;

포인터는 산술 표현식에 사용할 수 있습니다. 두 식이 완전히 다른 작업을 수행하는 다음 예에 주의하십시오.

int i, j, k;
int *pi = &i;
[//] i = i + 2
*pi = *pi + 2;
[//] increasing the address contained in pi by 2
pi = pi + 2;

포인터에 정수 값을 추가하고 포인터에서 뺄 수도 있습니다. 포인터에 1을 추가하면 해당 유형의 개체에 할당된 메모리 영역 크기만큼 값이 증가합니다. char 유형이 1바이트, int – 4double - 8을 차지하는 경우 문자, 정수 및 double에 대한 포인터에 2를 추가하면 값이 각각 2, 8 및 16만큼 증가합니다.

이것은 어떻게 해석될 수 있습니까? 같은 유형의 개체가 메모리에 차례로 있는 경우 포인터를 1씩 늘리면 다음 개체를 가리킵니다. 따라서 포인터를 사용한 산술 연산은 >배열을 처리할 때 가장 자주 사용됩니다. 다른 경우에는 거의 정당화되지 않습니다.

다음은 반복자를 사용하여 배열 요소를 반복할 때 주소 산술을 사용하는 일반적인 예입니다.

int ia[10];
int *iter = &ia[0];
int *iter_end = &ia[10];
while (iter != iter_end) {
  do_the event_ with_(*iter);

관련 문장 - C++ Pointer

관련 문장 - C++ Array