[초보자들] S69 6번째 – 배열과 반복문

대망의(?) S69 마지막 스터디가 진행되었습니다. 마지막 스터디 시간에는 지난 후기에서 예고해드렸던 반복문과 배열에 대해 공부를 했습니다.

기초 프로그래밍의 마지막 후기! 배열과 반복문의 내용을 정리해보겠습니다.

배열

프로그래밍을 할 때 비슷한 종류의 데이터를 뭉텅이로 사용할 때가 있습니다. 보통의 데이터는 변수를 이용해 기억하지만, 위와 같은 경우에는 배열을 이용할 수 있습니다. ‘배열’은 동일한 타입의 데이터를 여러개를 묶어둔 것인데, 배열 안에 들어가는 데이터 하나 하나를 ‘요소’라고 합니다. 요소들은 차례대로 0부터 시작해서 1씩 차근 차근 증가하는 번호를 순서대로 가지고 있는데 이를 ‘인덱스’라고 합니다.

배열은 메모리에 저장될 때 한 묶음으로 묶여있기 때문에 배열의 요소들이 모두 저장될 수 있는 충분한 공간이 마련되어 있어야합니다.

배열 요소 개수는 배열의 길이라고도 합니다. 배열의 길이는 마지막 인덱스의 크기 + 1의 값과 같습니다.

배열은 메모리에 저장될 때 얼마만큼 자리를 차지할 것인지 미리 정해놓고 저장이 되므로 배열의 크기는 변할 수 없습니다. 이를 다른 표현으로 ‘배열은 고정길이를 갖는다’라고 할 수 있습니다.

배열 요소의 값은 배열명과 인덱스로 접근할 수 있습니다.

그렇다면 컴퓨터는 어떻게 인덱스로 배열의 요소 값을 메모리에서 찾을 수 있을까요? 다음과 같은 과정으로 찾을 것 같습니다^^

위와 같은 과정으로 메모리의 주소를 찾기 때문에, 배열은 변수명을 1개만 가지고도 많은 값의 주소를 찾을 수 있습니다.

처음에 배열에 대해서 “사람이 편하게 데이터를 관리하려고 인지적으로 같은 분류의 것들로 묶나보다…”라고 생각했지만, 위의 원리를 살펴해보니 “인지적으로 같은 분류일 뿐만이 아니라 동일한 데이터형이 아니면 메모리 주소를 찾을 때 문제가 있겠구나….”라는 생각이 들었습니다.

정리해보자면 배열은 다음과 같은 특징을 가진다고 볼 수 있습니다.

  1. 배열은 동일한 데이터를 묶어둔 것임.
  2. 배열의 이름이 되는 변수명이 1개 있고, 각각의 요소는 순서대로 인덱스가 있음. 이 둘을 이용해 메모리에서 요소의 주소를 찾을 수 있음.
  3. 배열 요소의 총 개수를 배열 길이라고 하는데, 이는 가장 큰 인덱스 값 + 1의 값을 가짐.
  4. 배열은 길이는 고정임.

JavaScript에서 배열 사용하기

JavaScript에서 배열은 배열 리터럴인 [] 대괄호를 이용해 만듭니다. 각 요소들은 대괄호 안에 ‘,’로 나열해 담습니다.

//배열 생성
var numbers = [1, 2, 3]; 
var animals = ['dog', 'cat', 'rabbit'];

배열의 각 요소는 위에서 말한대로 인덱스로 접근할 수 있는데 “배열명[인덱스]”의 형식을 이용합니다.

//배열의 요소 접근하기
var animals = ['dog', 'cat', 'rabbit'];
var myPet = animals[0];

console.log('My pet is a ' + myPet); //"My pet is a dog"

특정 값을 바꾸고 싶다면 해당 인덱스를 찾아 새로운 값을 할당하면 됩니다.

//요소의 값을 변경
animals[0] = 'monkey';
console.log(animals);  // ["monkey", "cat", "rabbit"]

만약 없는 번호의 인덱스에 값을 할당하면 어떻게 될까요?
새로운 인덱스가 생겨 값이 할당됩니다.

animals[3] = 'lion';
console.log(animals);  //  ["monkey", "cat", "rabbit", "lion"]

흐음… 그런데 조금 이상합니다. 배열은 길이를 변경할 수 없다고 했습니다. 그런데 JavaScript에서는 없는 인덱스에 값을 넣어 배열의 크기를 늘릴 수 있네요.

또, 원래 위에서 말한 배열의 정의에 따르면 각 요소는 ‘동일한 데이터 타입’이어야만 합니다.
하지만 JavaScript에서는 배열에 다양한 데이터 타입을 섞어 만들어도 오류가 나지 않습니다.

//다양한 데이터 타입을 담은 배열
var arr = [1, 'hi', true, false, 182];

JavaScript는 요소의 데이터 타입이 다 달라도 되고 길이도 막 변하고… JavaScript의 배열만 특별한 것일까요?

JavaScript 배열의 진짜 정체를 알아보겠습니다.

Hashmap

JavaScript의 배열은 사실 배열이 아닙니다. 형태가 배열처럼 생긴 유사배열(Array-like object)일 뿐, 본 정체는 해시맵(Hashmap)입니다.

그럼 해시맵은 무엇인가?!? 해시맵은 Hash + Map 두 단어가 합쳐진 단어인데요, 각각의 뜻을 알아보겠습니다.

Hash

해시(Hash)는 어떤 문자열을 유일한 숫자값으로 바꿔주는 함수를 말합니다.

예를 들어 a를 해쉬하면 어떤 과정을 거쳐 a만 가질 수 있는 유일한 숫자값이 나옵니다. 또 b를 해쉬하면 a 해시값과 다른 b의 유일한 숫자값이 나오게 되구요.

프로그래밍을 하다 보면 유일한 숫자의 값을 얻어야 하는 때가 있을 수 있습니다. 그런데 사람이 숫자를 직접 다뤄 유일한 숫자의 조합을 만들고 기억하는 것은 어렵습니다. 문자를 사용하면 한결 더 나은데, 사람이 어떤 문자를 입력하면 그 문자만이 갖는 유일한 숫자값을 얻어내는데 해시를 이용합니다. 뭔가 메모리 주소 대신 변수를 사용하는 것이랑 비슷하네요~!

Map

Map은 값을 키:값(key:value)형태로 짝지은 것을 말합니다.

예를 들어 저의 신상정보를
이름 : Summer,
직업 : 개발자,
국적 : 대한민국
… 이렇게 키-값으로 짝지어 나타낼 수 있습니다.

맵에서는 키를 알면 값을 알아낼 수 있습니다. 즉, 이름, 직업, 국적과 같은 키 값으로 각각에 해당하는 값을 접근할 수 있는 것이지요.

Hashmap & object

Hashmap은 Hash와 Map을 결합한 것으로, Map의 방식인 키-값으로 데이터를 저장하는데 그 키를 Hash한 후 저장하는 것을 의미합니다.

JavaScript에서 해시맵은 object라고도 합니다.

object… 어디서 본 것 같은데… 기억을 더듬어 보니 JavaScript의 데이터 타입 중 하나였습니다. object는 데이터 타입중에서 기본 데이터 타입이 아닌 것들을 모두 말합니다. (*JavaScript의 기본 데이터 타입에는 숫자, 문자열, 불린, undefined, null이 있었습니다.)

우리가 지금 배우고 있는 배열도 object이고, 함수도 모두 object에 포함됩니다.

JavaScript에서 object는 {}리터럴을 이용해 만들 수 있습니다. object의 값들은 {} 안에 키:값, 키:값… 의 형식으로 나열하면 됩니다.

var info = {
  name:'Summer',
  job:'developer',
  nationality:'Korea'
}

위 예문에서는 name, job, nationality가 object의 키(프로퍼티)가 되고, 각각 문자열을 값으로 가집니다. 또 여러개의 키 값을 갖는 경우에는  ‘,’로 이어져있구요.

해시맵에서는 키를 통해 값을 알 수 있다고 했는데, 위의 예에서 제 이름을 알고 싶다면 다음과 같이 ‘name’이라는 키를 이용해 2가지 방식으로 알아낼 수 있습니다.

info['name'];  //object명[키이름]
info.name;     //object.키이름

오잉 보니까 대괄호를 이용하는 군요. 어디서 많이 본 것 같은데..? 배열의 요소를 접근할 때와 모양이 비슷합니다.

그럼 만약에 키 값이 숫자인 object인 경우에는 어떻게 될까요?

var fruits = {
  '0':'apple',
  '1':'orange',
  '2':'pineapple'
}
var myFavoriteFruit = fruits['0'];

fruits[‘0’]이라고 써서 apple값을 얻은 후 myFavoriteFruit이라는 변수에 넣어주고 있습니다. 만약 object의 값들이 배열이었다면 fruits[‘0’] 대신 fruits[0]이라고 썼을 것입니다.

배열과 object는 [] 안에 ”가 있나 없나 차이가 있지만, 실제로 JavaScript는 object 또는 배열의 [] 안의 값은 우선 무조건 문자열로 바꾸므로 결국엔 똑같은 것이 됩니다.

결론적으로 JavaScript에서 배열은 실제로 숫자를 키값으로 갖는 object(해시맵)을 의미하는 것이라고 볼 수 있습니다.

따라서 JavaScript의 배열이 메모리에 저장될 때에도 본래 ‘배열’과 다른 방식으로 저장될 것입니다.

위와 같은 방식으로 저장되기 때문에 배열의 요소를 자유롭게 추가/삭제 할 수 있습니다.

 

그렇다면 JavaScript의 배열과 object는 똑같은 것일까요?

둘의 차이점이 있으니…! 그것은 바로 배열의 길이 계산을 할 수 있는가 없는가의 여부입니다.

JavaScript의 배열은 해시맵이지만 내부 속성 ‘length’에 배열의 길이를 값으로 담고 있습니다. (원리는 배열이 길이를 계산하는 원리와 같습니다.) 하지만 object는 length 속성이 없습니다.

//배열 : length 있음
var fruits = ['apple', 'orange', 'pineapple'];
console.log(fruits.length); //3

//object : length 없음
var fruitObj = {
  0:'apple',
  1:'orange',
  2:'pineapple'
};
console.log(fruitObj.length); //undefined

그런데 재밌는 것은… JavaScript 배열의 길이가 배열 요소의 개수가 아닐 수도 있다는 점 입니다.

위 fruits 배열의 100번째 인덱스에 ‘watermelon’을 추가하면 배열의 길이는 어떻게 될까요? 배열의 길이는 요소의 개수를 의미하므로 5개가 되어야 합니다.

하지만….

fruits[100] = 'watermelon';
console.log(fruits.length); //101

배열의 길이는 101이 되었습니다. 왜냐하면 배열의 length의 값은 가장 큰 인덱스 + 1를 갖는 방식으로 작동하기 때문입니다.

그렇다면 fruits 배열의 4번째 인덱스부터 99번째 인덱스에 값이 생겼을까요?

console.log(fruits); //["apple", "orange", "pineapple", 100:"watermelon"]; 
console.log(fruits[88]); //undefined

위 결과를 보시면 중간에 4번째부터 99번째 인덱스는 출력되지 않습니다. 88번째의 인덱스를 출력해보면 undefined가 나오구요. 이처럼 JavaScript에서는 배열의 길이가 인덱스의 숫자와 일치하지 않을 수도 있습니다.

신기하네요! 이것은 JavaScript가 배열이 아니라 해시맵이기 때문에 가능한 현상이겠지요..? fruits 배열이 메모리에 저장된 상태를 생각해보면 다음과 같지 않을까 싶습니다.

이처럼 JavaScript의 배열은 배열이 아니라 당황스러웠지만… 해시맵이기 때문에 배열의 삭제, 추가를 자유롭게 할 수 있습니다. 게다가 길이 계산까지 해줘서 length속성으로 담아주니, 이를 이용한 다양한 함수나 반복문도 사용할 수 있어 참 감사하네요!

물론 장점만 있는 것은 아니기때문에 ECMAScript 2015부터는 본래의 배열처럼 작동하는 ArrayBuffer가 도입되었다고 합니다.

많은 이야기가 있었지만… 결론적으로 배열, JavaScript의 배열, object의 특징은 다음과 같이 비교/정리할 수 있습니다.

반복문

드디어 반복문입니다! 프로그램을 작성하다보면 데이터에 일정한 행위를 반복하는 경우가 많습니다. 원래는 그 일정한 행위를 하나 하나 반복해서 써주면 됩니다만… 사람은 반복을 하나 하나 하는 것은 굉장히 귀찮고, 실수도 많이 생기고, 중복도 생기기므로…. 반복문(Loop)을 도입해 특정 구간을 반복할 수 있도록 했습니다.

반복문은 다음과 같이 2가지 부분으로 이루어졌습니다.

  1. 언제까지 흐름을 반복할 것인지를 판단하는 Loop Condition
  2. 어떤 내용을 반복하는지를 기술하는 Loop Body

이 두가지를 어떻게 조합하냐에 따라 먼저 판단을 하고 반복구간을 실행하는 반복문, 상태가 어쨌든 우선 반복을 먼저 하고 판단을 하는 반복문 이렇게 두가지 종류의 판단문을 만들 수 있습니다.

그렇다면 JavaScript에서는 어떻게 반복문을 이용하는지 알아보겠습니다.

while문

while문의 형식은 다음과 같습니다.

while(식) 문

위는 식이 참일 동안에는 문이 계속해서 반복된다는 뜻을 갖고 있습니다.

반복문하면 구구단 출력을 예로 많이 드는데… ^^;; 저도 while문의 예로 구구단 2단을 출력해보겠습니다.

var i = 1;
while(i <= 10){
  console.log('2 x ' + i + ' = ' + 2 * i);
  i+= 1;
}

먼저 while문 실행 전에 while문의 실행 여부를 판단할 값으로 변수 i를 만들어 초기값 1을 넣어두었습니다. (보통 반복문에서 반복을 세는 변수로 i를 사용합니다)

그리고 i가 10이하인 경우만 작동되도록 판단식에 i <= 10을 써두었습니다. i가 10을 넘을 때 까지는 console.log가 실행되며 2 * 1 = 2, 2 * 2 = 4 …. 2 * 10 = 20와 같은 내용을 출력할 것입니다.

console.log 다음에는 i의 값을 한번 증가시켜주는데요, 이렇게 i의 값을 증가시켜줘야만 i가 10이 되어 while문이 멈출 수 있습니다. 루프가 10번 돌고 나서는 i의 상태가 11이 되어있으므로 while 판단문이 거짓이 되어 반복이 더이상 실행되지 않습니다.

// 결과
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
2 x 10 = 20

이렇게 while문을 이용한 루프는

  1. 판단식의 기준으로 사용할 변수를 선언, 초기화하고 (var i = 1;)
  2. 변수의 상태를 판단하는 판단식을 쓰고     ( i <= 10 )
  3. 1번에서 만든 변수 값이 변할 수 있는 조건을 설정 ( i += 1; )

라고 정리할 수 있습니다.

3의 내용은 필수가 아닙니다. 하지만 판단식에서 사용하는 변수의 값이 변하지 않으면 true 또는 false의 값을 계~~~속 가지게 되어 영원히 반복을 하거나 한번도 실행되지 않는 while문이 될 것입니다.

특히 판단식이 고정된 true값을 가져 영원히 반복하는 반복문은 “무한 루프”라고 합니다.

//무한루프 예
while(1){
  console.log('My name is Summer!');
}

위 예를 살펴보면, while문의 판단식에 1이 있습니다. 1은 JavaScript에서 참으로 판단되기 때문에 콘솔에 ‘My name is Summer!’라는 내용이 계속해서 출력될 것입니다. 실제로는 summer가 무한히 찍히는게 아니라 버벋..버벅대면서 찍히다가 브라우저가 뻗었지만요… 이렇게 브라우저가 죽어버리기에 ‘무한 루프는 위험해…!!!’라고 생각했습니다.^^;;

하지만 브라우저에서 실행되는 JavaScript는 환경의 특수성때문이지 다른 프로그램에서는 프로그램을 종료하지 않고 계속 동작시키기 위해 무한 루프를 많이 쓴다고 합니다.

for문

무한 루프가 아닌 while문의 구조를 생각해보면

  1. 판단식의 기준으로 사용할 변수를 선언하고
  2. 변수의 상태를 판단하는 판단식을 쓰고
  3. while문 내부에 1번에서 선언한 변수의 값을 변화시키는 문을 씀.

이렇게 3가지 내용이 있었습니다. 특정 조건에서만 반복되는 루프의 같은 경우 이 구조가 반복될 것이므로 이를 구조화한 for문이 등장하게 되었습니다.

for문의 구조는 다음과 같습니다

for( 식1; 식2; 식3) 문

for문은 3개의 식과 1개의 문으로 이루어졌습니다. 각각의 식을 좀 더 자세히 기술하자면 다음과 같습니다.

for(초기화 식 ; 판단식; 증감식) 문

즉 위에서 말한 3가지 내용이

  1. 판단식의 기준으로 사용할 변수를 선언하고 ==> 초기화식
  2. 변수의 상태를 판단하는 판단식을 쓰고 ==> 판단식
  3. while문 내부에 1번에서 선언한 변수의 값을 변화시키는 문을 씀. ==> 증감식

초기화식, 판단식, 증감식의 구조로 모두 포함되었습니다.

위에서 예로 든 구구단 2단을 for문으로 쓰면 다음과 같습니다.

for(var i = 1; i <= 10; i += 1){
  console.log('2 x ' + i + ' = ' + 2 * i);
}

결과는 while문과 동일하게 나옵니다.

앗…! 그런데 좀 특이한 점 눈치채셨나요? 바로 초기화 부분의 var i = 1이라는 부분인데요, 원래는 ‘식’이 들어가야 하는 자리입니다. 그런데 변수 선언문이 들어가다니 !?!?

원래는 안되지만 for문에서 초기식에 변수 선언문 만큼은 허용이 된다고 하네요. (사실 전 매의 눈이 아니라… 맹대표님이 이야기 해주실 때 까지 몰랐어요ㅠ.ㅠ)

그래서 위 식은 “변수 i를 만들어서 1로 초기화하고, i가 10이하 일 때까지만 {} 안의 내용을 반복하는데, {} 안의 내용을 반복할 때마다 i를 1씩 증가해줘!”라는 내용을 컴퓨터에 명령하는 문이 됩니다.

 

처음에 for문을 접했을 때는 너무 불편했습니다. 형식이 낯설어서 그런지 안읽히더군요. 그래서 while문을 더 썼습니다.그런데 for문을 계속 쓰면서 익숙해지니 이젠 while문이 더 어렵게 느껴지는 신기한 현상이…!
왜 그런가 생각을 해보니… for문 구조에 익숙해지니 반복 여부를 판단하는 부분이 while문보다 for문이 더 명확하고, 이를 이용해 반복문을 제어하는 것이 쉽다고 느껴지게 된 것 같습니다^^;;

//while문 이용
var i = 1;
while(i <= 10){
  console.log('2 x ' + i + ' = ' + 2 * i);
  i+= 1;
}

//for문 이용
for(var i = 1; i <= 10; i += 1){
  console.log('2 x ' + i + ' = ' + 2 * i);
}

위의 반복문을 비교해보시면 while문에서는 무엇이 loop에 관련된 것인지, 어떤 것이 loop조건과 상관없는 것인지 판단하기가 어렵습니다. while문의 body 안에 있는 i를 j로 오타내거나 body문 안의 내용이 매우 복잡해서 i 값을 추적하기 어려워진다면 반복문을 제어하기가 더 힘들구요.

그에 비해 for문은 판단과 관련된 것들이 구조적으로 명확하게 구분되어있어 loop를 제어할 때 실수를 줄일 수 있습니다. 그래서 저는 왠만하면 이젠 for문을 씁니다..^^;;;

이렇게 반복문은 형식부터 낯설고 흐름을 컨트롤 하기도 쉽지 않습니다. 그래서 내가 컴퓨터에게 “어떤 상황에만 반복되기를 원한다”를 정확하게 표현해내기 위해서는 많은 연습이 필요합니다. 맹대표님 말씀에 의하면 반복문에서 개발자 되기를 많이 포기한다고 하시네요(흑흑) 물론 이 외에도 반복문을 쓰기 어려운 이유는 많지만… 우선 표현이 익숙해지도록 많이 연습해야겠습니다.

결론

배열과 반복문을 배웠으니 배열을 이용해서 반복문을 실행하는 내용도 배웠으면 좋겠지만 아쉽게도 시간이 모자라서 다루지 못했습니다. (개인적으로 공부하는 걸로..^^)

지난 6주간의 스터디의 전체적인 소감을 말해보자면, 기간은 참 짧았지만 배운 내용의 양이나 깊이는 결코 짧게 느껴지지 않았던 스터디었다고 생각합니다. 그리고 무엇보다도 가장 크게 배운 ‘기초를 가볍게 보지 않는 겸손한 마음가짐’이 생각나네요.

앞으로도 겸손한 마음가짐으로 기초를 잘 닦아가겠습니다^^

> 다음회 스터디 S70 공고 모집 확인


동영상 강의 보기

마지막 강의 영상은 1개입니다~!


summer| bsidesoft 신입사원
디자인을 공부했던 섬머는 개발까지 해버리겠다는 욕심으로 개발자의 세계에 입문하게 되었습니다. 개발왕이 되어 멋진 제품을 만들어내는 꿈을 꾸고 있습니다. 코드, 디자인을 포함한 세상의 모든 아름다운 것들과 미드, 그리고 달리기를 좋아합니다.

%d 블로거가 이것을 좋아합니다: