[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_0monsters 배열의 시작 주소가 담기게 되고, 곧 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바이트씩 끊어서 가져온다. 어셈블리 코드를 보면 확연히 차이가 느껴진다.

Alt Text

위 코드에서 가리키는 주소(06B9BCCh)로 가면 Hello World가 저장이 되어있는 것을 볼 수 있다.

Alt Text

포인터는 주소를 담는 바구니, 배열은 닭장 즉, 그 자체로 같은 데이터끼리 붙어있는 바구니 모음이다.
다만, 배열 이름바구니 모음의 시작 주소를 가리킨다. 이러한 점 때문에 포인터와 동일시하다 생각할 수 있지만 다르다는 것을 항상 기억 해야한다.


🌱 배열을 함수의 인자로 넘기게 되면?

배열을 함수의 인자로 넘기게 되면 어떻게 될까?

void Test(char a[]) {
    a[0] = 'Z';
}

int main() {
    Test(test2);
}

해당 코드를 작성하고 실행시키면 결과값이 영구적으로 바뀐 것을 확인할 수 있다. 일반적인 변수의 값 복사 방식과는 다르게 값이 바뀐 걸 확인할 수 있었는데, Test 함수 이름 위에 마우스를 올려보면 함수의 인자로 받는 문자 타입의 배열(char a[])이 포인터 방식(char* a)으로 바뀐 것을 알 수 있다.

즉, 배열을 함수 인자로 넘기면 컴파일러가 알아서 포인터로 치환해주게 되고, 배열의 내용 전체를 넘기는 것이 아니라 시작 주소(포인터)만 넘겨주게 된다. 그래서 값이 바뀌게 되는 것이다.


👻 글을 마치며

이번 시간에는 배열에 대해 알아보고, 지난 시간과 비슷하게 포인터와 배열을 비교해보면서 차이점에 대해 알아보았다. 배열을 처음 접했을 때 무조건 인덱스로만 공부했어서 그런지 주소에 직접 접근을 한다는 것이 새로웠다. 더불어 배열이 어떻게 해서 데이터를 관리하고 접근하는 지 포인터와 비교하면서 공부하니 더 이해가 잘 됐던 것 같다. 정리하기도 편하고 값 가져오기도 편해서 배열도 내가 좋아하는 타입 중 하나다. 😋


소스코드 보러가기


출처
인프런 Rookies님 강의

Categories:

Updated:

Leave a comment