[C++] 배열(Array) 기초
👻 배열이란?
배열(Array)이란 같은 타입의 데이터들을 모아둔 집합소이다. 공통된 타입의 데이터들을 모아놓았기 때문에 데이터들을 하나하나씩 관리할 때보다 훨씬 간편하고 효율적이다. type 이름[개수]
로 표현한다.
🌱 배열의 선언
StatInfo monsters[10];
위 코드는 크기가 10
이고, StatInfo
타입의 monsters
라는 배열을 생성한 것이다.
VC 컴파일러 기준으로, 배열의 크기는 상수여야 한다.
🌱 배열의 사용
일반적인 변수처럼은 사용할 수 없다.
StatInfo players[10];
players = monster; // 사용 불가
배열의 이름은 조금 다르게 동작하는데, 배열의 이름은 곧 배열의 시작 주소이다. 정확히는 시작 위치를 가리키는 TYPE* 포인터
라고 볼 수 있다.
auto whoAmI = monster; // StatInfo* whoAmI
배열의 시작 주소를 가리키는 정보를 이용하면 다음과 같이 배열에 접근할 수 있다.
StatInfo* monster_0 = monsters;
monster_0->hp = 100;
monster_0->attack = 10;
monster_0->defence = 1;
monster_0
에 monsters
배열의 시작 주소가 담기게 되고, 곧 monsters[0]
에 값을 변경한다는 의미임을 알 수 있다.
🌱 포인터의 덧셈
포인터에 1을 더하면 값 자체에 1을 더하라는 의미가 아닌, 다음 주소를 가리킨다고 지난 시간에 배웠었다. 그것을 활용하면 배열에 차례차례로 접근을 할 수가 있다.
StatInfo* monster_1 = monsters + 1; // 다음 주소. 즉, monsters[1]
monster_1->hp = 200;
monster_1->attack = 20;
monster_1->defence = 2;
🌱 포인터와 참조
포인터와 참조는 자유자재로 변환 가능하다. 무엇을 가리키는지만 정확하게 설정해주면 큰 이상없이 사용할 수 있다.
StatInfo& monster_2 = *(monsters + 2);
monster_2.hp = 300;
monster_2.attack = 30;
monster_2.defence = 3;
단, 아래 코드는 위 코드와 완전히 다른 의미이다.
StatInfo temp = *(monsters + 2);
temp.hp = 300;
temp.attack = 30;
temp.defence = 3;
자칫
monster_2
와 같은 값을 가리킨다고 볼 수 있지만temp
는 포인터도 참조도 아닌, 값 복사 방식을 사용하는 변수이다. 고로 해당 변수는StatInfo
타입의 데이터 자체를 담는 그릇이기 때문에,monsters + 2
가 가리키는 곳의 데이터를 그대로 복사해와temp
에 담아서 값을 변경하는 방식이 된다. 원본값을 건드리는 것이 아니다.
🌱 자동화
for문
을 사용하면 배열을 순회하거나 값의 변경을 자동화 시킬 수 있다.
for (int i = 0; i < 10; i++) {
StatInfo& monster = *(monsters + i);
monster.hp = 100 * (i + 1);
monster.attack = 10 * (i + 1);
monster.defence = 1 * (i + 1);
}
🌱 인덱스(Index)
*(monsters + i)
를 보다 편리하게 사용할 수 있는 방법이 있다. 인덱스(Index)를 사용하면 가독성 좋게 배열을 관리할 수 있다.
n
의 크기를 가지는 배열은 0
번부터 시작하여 n-1
번까지 존재한다.
monsters[0].hp = 100;
monsters[0].attack = 10;
monsters[0].defence = 1;
for (int i = 0; i < 10; i++) {
monsters[i].hp = 100 * (i + 1);
monsters[i].attack = 10 * (i + 1);
monsters[i].defence = 1 * (i + 1);
}
🌱 배열 초기화
배열을 초기화시키지 않으면 임의의 값이 들어가 있는 경우가 많다. 초기화 시키는 방법엔 몇가지가 존재한다.
int numbers[5] = {};
- 모두 0으로 설정된다.
int numbers[10] = {1, 2, 3, 4, 5};
- 0번부터 차례대로 1, 2, 3, 4, 5가 설정되고 나머지는 0으로 설정된다.
int numbers[] = {1, 2, 3, 4, 11, 24};
- 데이터 개수만큼의 크기에 해당하는 배열이 만들어진다.
char helloStr[] = {'H', 'e', 'l', 'l', 'o', '\0'};
- 문자 배열은 마지막에
null(\0)
추가는 필수다.
- 문자 배열은 마지막에
👻 포인터 vs 배열
포인터와 배열을 비교해보자.
- 문자열
다음은 각각 포인터와 배열로 문자열을 선언하는 코드이다.
const char* test1 = "Hello World";
char test2[] "Hello World";
둘 다 선언하는 데에는 아무런 이상이 없고 언뜻 보면 같은 기능을 하는 것 같지만 이 둘은 엄연히 다르다.
test1
은 문자 타입의 배열에 각 저장되어 있을 Hello World
의 처음 시작 주소를 가리키고 있다. 반면 test2
는 해당 데이터 자체를 4바이트씩 끊어서 가져온다. 어셈블리 코드를 보면 확연히 차이가 느껴진다.
위 코드에서 가리키는 주소(06B9BCCh
)로 가면 Hello World
가 저장이 되어있는 것을 볼 수 있다.
포인터는 주소를 담는 바구니, 배열은 닭장 즉, 그 자체로 같은 데이터끼리 붙어있는 바구니 모음이다.
다만, 배열 이름은 바구니 모음의 시작 주소를 가리킨다. 이러한 점 때문에 포인터와 동일시하다 생각할 수 있지만 다르다는 것을 항상 기억 해야한다.
🌱 배열을 함수의 인자로 넘기게 되면?
배열을 함수의 인자로 넘기게 되면 어떻게 될까?
void Test(char a[]) {
a[0] = 'Z';
}
int main() {
Test(test2);
}
해당 코드를 작성하고 실행시키면 결과값이 영구적으로 바뀐 것을 확인할 수 있다. 일반적인 변수의 값 복사 방식과는 다르게 값이 바뀐 걸 확인할 수 있었는데, Test
함수 이름 위에 마우스를 올려보면 함수의 인자로 받는 문자 타입의 배열(char a[]
)이 포인터 방식(char* a
)으로 바뀐 것을 알 수 있다.
즉, 배열을 함수 인자로 넘기면 컴파일러가 알아서 포인터로 치환해주게 되고, 배열의 내용 전체를 넘기는 것이 아니라 시작 주소(포인터)만 넘겨주게 된다. 그래서 값이 바뀌게 되는 것이다.
👻 글을 마치며
이번 시간에는 배열에 대해 알아보고, 지난 시간과 비슷하게 포인터와 배열을 비교해보면서 차이점에 대해 알아보았다. 배열을 처음 접했을 때 무조건 인덱스로만 공부했어서 그런지 주소에 직접 접근을 한다는 것이 새로웠다. 더불어 배열이 어떻게 해서 데이터를 관리하고 접근하는 지 포인터와 비교하면서 공부하니 더 이해가 잘 됐던 것 같다. 정리하기도 편하고 값 가져오기도 편해서 배열도 내가 좋아하는 타입 중 하나다. 😋
Leave a comment