배열
변수는 하나의 저장 공간, 배열은 연속적인 저장 공간을 나타낸다. 하나의 이름으로 여러개의 변수를 사용하는 것과 같은 효과를 가질 수 있어 편리하다.
Ex> int a1, a2, a3; => int a[3]; 이처럼 간편하게 사용 가능하다.
선언 방법은 변수와 같이 자료형 변수이름 그리고 뒤에 대괄호([])와 몇개의 공간을 사용할 것인지 명시해주면 된다.
Ex> int a[5]; // a라는 배열을 만들고 int형 자료를 5개 저장할 수 있다.
각각의 저장 공간은 다음과 같이 사용한다.
Ex> a[0]. a[1], a[2], a[3], a[4]
배열 이름 뒤에 대괄호 안에 있는 숫자는 첨자라고 하며 배열의 첨자는 0부터 시작한다. 그러므로 선언시 명시한 크기 값보다 하나 작은 값이 배열의 맨 끝이다. 다음과 같은 형태이다. 첨자는 인덱스라고도 한다.
a[0] |
a[1] |
a[2] |
a[3] |
a[4] |
||
... |
|
|
|
|
|
... |
배열의 초기화는 다음과 같은 방식이 있다.
Ex> 1. int a[3] = {1, 2, 3}; // 배열을 선언하고 바로 각각 초기화.
2. int a[3] = {1, 2}; // 배열의 크기보다 초기화 수가 적으면 나머지 배열은 0으로 초기화. {1, 2, 0} 과 같다.
3. int a[] = {1, 2, 3}; // 배열의 크기를 명시하지 않으면 초기화 하는 수만큼 크기를 설정. int a[3] = {1, 2, 3}과 같다.
4. int a[3] = {}; // 배열을 선언하고 전부 0으로 초기화.
그렇다면 배열의 크기를 설정하지 않고 단순히 int a[]; 라고만 하면 어떻게 될까?
위와 같은 오류가 발생한다. 변수를 선언하면 운영체제는 미리 메모리 공간을 할당해준다고 했는데 위와 같은 경우는 크기를 알 수 없기때문에 할당을 해 줄 수 없는 것이다. 컴퓨터는 애매모호한 것, 알 수 없는 것을 매우 싫어한다. 그렇기 때문에 초기화라든지 값을 사용하고 조건을 정할 때 확실하게 명시해주어야한다.
배열의 주소는 어떻게 될까? 연속적인 저장 공간이라고 했는데 그렇다면 할당 받는 주소를 알아보자.
- 소스 코드 예시
int a[3];
for(int i = 0; i < 3; i++)
{
cout << "a[" << i << "]의 주소 : " << &a[i] << endl;
}
위와 같은 결과가 나온다. 주소가 4씩 증가하는 것을 확인 할 수 있다.(int형 자료형의 크기가 4바이트 이기때문) 위의 보고 감이 안온다면 다음 예시를 보자.
- 소스 코드 예시
int a, b, c;
cout << "a의 주소 : " << &a << endl;
cout << "b의 주소 : " << &b << endl;
cout << "c의 주소 : " << &c << endl;
연달아 선언을 해주었지만 주소는 연관 없이 다른 곳에 지정되어 있다. 배열이 연속적인 저장 공간을 갖는다는 것을 알 수 있다. 그리고 그 연속된 공간의 간격은 자료형의 크기만큼이라는 것을 알 수 있다. 아래는 자료형의 크기가 8바이트인 double a[3] 배열의 주소이다.
그럼 배열 이름 자체는 무엇을 의미할까? 배열 자체만 출력을 해보자. int a[3]; 이 있을 때 cout << a << endl; 을 하면
위와 같은 출력 결과가 나온다. 배열의 이름은 주소를 갖는다? 그럼 a와 a[0]은 주소가 다를까?
a의 값과 a[0]의 주소가 같다. 이 말은 배열의 이름은 배열의 첫번째 칸을 참조하는 포인터 변수와 같다는 말이다.
배열의 이름은 상수포인터 = 모배열 = 배열 상수 라고도 한다. 상수라는 말이 붙는 이유는 안에 있는 주소 값을 변경할 수 없기때문이다. 안에 값을 변경할 수 있다면 엄청난 일을 야기시킬 수 있다. 실수로 모배열의 주소 값을 다른 주소 값으로 변경하면 뒤에 있던 배열들은 참조할 수 없게 되며 이 모배열을 통해 값을 사용하려고하면 알 수 없는 저장 공간에 값을 사용하게 되고 값을 변경하게 되면 아주 중요한 값들이 변경되고 수정되어서 망가지게 되기 때문에 상수로 되어있다.
그렇다면 a[0]를 포인터 변수로 표현한다면?
Ex> int a[3];
int *p;
p = a; // 포인터 p는 배열의 첫번째 칸을 참조한다.
&a[0] == p + 0;
&a[1] == p + 1;
&a[2] == p + 2;
// ()안에서 +연산을 하는 이유는 연산자 우선순위에서 *(간접 지정연산자)가 +(산술연산자)보다 우선순위가 높기 때문
a[0] == *(p + 0);
a[1] == *(p + 1);
a[2] == *(p + 2);
위의 내용은 같을까?
- 소스 코드 예시
int a[3] = {1, 3, 5} //산술연산으로 값이 같을 수 있기 때문에 2의 간격을 두고 값을 초기화함.
int *p = a;
for(int i = 0; i < 3; i++)
{
if(&a[i] == (p + i))
{
cout <<"a[" << i << "]의 주소 값 : " << &a[i] << endl;
cout <<"(p + " << i << ")의 주소 값 : " << (p + 1) << endl;
cout << "&a[" << i << "]와 (p + " << i << ")는 동일합니다." << endl;
}
}
cout << "*********************************************************" << endl;
for(int i = 0; i < 3; i++)
{
if(a[i] == *(p + i))
{
cout <<"a[" << i << "]의 값 : " << a[i] << endl;
cout <<"*(p + " << i << ")의 값 : " << *(p + 1) << endl;
cout << "a[" << i << "]와 *(p + " << i << ")는 동일합니다." << endl;
}
}
결과는 ???
결국 같다는 것을 알 수 있다. 그럼 *(p + i)에서 괄호가 없어진다면??? 좀 더 자세한 결과를 위해 if문을 주석처리하였다.
주소 값은 동일하다. 그러나 안에 있는 값이 다르다. 이것은 무엇을 의미할까? 위에서 써놓았듯이 *(간접 지정 연산자)의 연산 우선순위가 높기때문에 *p가 a[0]의 값 1이 되고 거기에 + i 값을 했다는 뜻이다. 괄호의 중요성을 알 수 있었다.
배열의 인덱스는 포인터 변수에 + 되는 값과 동일하고 + 1이 될 때 커지는 값은 자료형의 크기만큼 커진다.
- 출처 : 열혈강의 C++ 언어본색
※ 책을 보고 공부한 내용을 정리하였기때문에 내용이 책과 다를 수 있습니다.