본문 바로가기

cs50 기초강의

3. 배열 - 배열(1, 2)

● 메모리

 

C에는 다양한 data type 이 존재한다. C에서 프로그램을 작성하면 각각의 자료형은 서로 다른 크기의 메모리를 차지한다.

 

bool : 1byte

char : 1byte

int : 4byte

float : 4byte

long : 8byte

double : 8byte

string : ?byte

...

 

위 이미지는 RAM 이다. 컴퓨터나 스마트폰의 하드 드라이브를 구성하고 있다. RAM 의 검은 칩은 일정 크기의 Byte 를 의미한다. 이 검은 칩은 소프트웨어 구동시에 정보가 저장되는 곳인데 이 칩을 여러 Byte 들의 묶음으로 생각할 수 있다.

위 이미지 처럼 나눈다음에 첫 칸은 첫 바이트 둘째 칸은 두번째 바이트 이런식으로 생각할 수 있다.

위 이미지의 노란색 사각형이 메모리를 의미하고, 작은 사각형 하나 1 바이트를 의미한다고 볼 수 있는 것이다.

 

만약 char, 즉 문자를 포함한 프로그램을 작성한다고 하면 char를 저장하기 위해 1칸을 요청하고,

숫자를 int 로 저장한다면 컴퓨터 메모리에서 4바이트를 할당해야 한다.

 

이 작은 사각형 각 칸마다 1 byte 이기 때문에 8 bit 씩 존재 혹은 8개의 작은 트랜지스터로도 생각할 수 있고 어떤 식으로든 0과 1을 표현하고 있는 것이다.

 

하드웨어적인 이야기는 생각하지 말고 여태껏 우리가 작성한 프로그램에서 컴퓨터는 어떻게 정보를 저장할까??

 

다음과 같은 코드가 있다고 하자.

※ char를 입력할 때는 " " 가 아닌 ' ' 를 사용해야한다. 단순히 string 과 구별하기 위함이다.

 

H I ! 를 출력하기 위해 printf("%c %c %c\n", c1, c2, c3); 라고 표현하면 의도한 바와 같이 H I ! 가 출력된다.

다만 각 문자 사이에 공백이 있다. 여기서 H I ! 는 모두 char 이다.

하지만 이 문자는 뭐였을까?

바로 정수(숫자) 이다. ASCII 코드에서 확인할 수 있었다.

 

따라서 H I ! 를 숫자로 출력하기 위해 형식지정자를 %i 로 바꾸고 c1, c2, c3 는 char 이지만 int 처럼 여겨달라고 하기 위해 변수 앞에 (int) 를 붙인다.

이것이 바로 형변환이다. 하나의 자료형을 다른 종류로 바꾸는 행위를 의미한다.

따라서 컴파일하면 72 73 33 이 출력되는 것을 확인할 수 있다.

 

사실 (int) 를 입력하지 않아도 clang 이 알아서 char 를 int 로 바꿔준다.

 

이 안에서 무슨일이 벌어지고 있는지를 이해하면 컴퓨터 메모리 내부에서 일어나는 일들을 이해하고 조금씩 다룰 수 있게 된다.

 

다시 돌아와서 만약 c1 c2 c3 3개의 변수를 가진 프로그램이 있다면 컴퓨터가 할일은 메모리 안에

H 를 하나의 칸에 넣고,

I를 또 다른 칸에 넣고,

!를 3번째 칸에 넣는 것이다. 그리고 각 칸에 해당하는 변수명을 가상으로 저장한다. 실제로 저장된 것은 H I ! 이다.

 

하지만 이들은 엄밀히 말하면 char가 아니다. 숫자인 것이다. 즉, 메모리 안에 저장된 것은 사실 72 73 33 이다.

 

그런데 사실 72 73 33은 컴퓨터에서 2진법으로 표현된다. 즉, 정말 실제로 저장된 형태는 2진법의 모습을 가지고 있는 것이다.

01001000 c1

01001001 c2

00100001 c3

결국 0과 1의 조합이다. 

 

물론 우리는 보통은 신경쓰지 않아도 되는 기초적인 것들이다. 추상화라는 개념덕분에 복잡한 것들은 무시하고 더 높은 수준의 정보를 다룰 수 있게 된다.

따라서 단순히 72 73 33 으로 생각해도 된다. 더 나아가 H I ! 라고 생각하는 것이다.

 

● 배열

 

이번에는 char 가 아닌 3개의 int 변수를 생각해보자.

 

다음과 같은 코드가 있다.

위 코드는 int 형태를 가지는 score0 score1 score2 변수에 각 정수를 입력하고 평균을 낸 코드이다. 그리고 그 평균은 59라는 출력을 띄고 있다.

 

score1 이라는 변수에 저장된 72라는 값은 메모리 내에서 4칸을 차지한다. 그리고 73, 33 또한 각각 4칸 4칸을 차지한다. 그리고 실제로 이는 00000000000000000000000001001000(72) 와 같이 2진법으로 저장되어 있지만, 위에서 이야기한 바와 같이 생각할 필요없다.

 

그렇다면 위 코드의 문제점은 무엇일까? 최선의 디자인이 아니라고 할 수 있다.

너무 고정적인 단점이 있다. 만약 더 많은 점수가 존재한다면 이 프로그램은 지원할 수 없다. 또한 점수가 부정확할 수 있는 다양한 문제점이 존재한다.

 

C에서 하나 이상의 값들이 있고 서로 연관되어 있으며 여러 점수들을 가진 변수에 적절한 이름은 뭘까? 바로 scores다.

물론 int scores 는 하나의 값을 가진다.

 

이러한 문제를 해결하기 위한 방법 중 하나가 배열이다. 배열은 여러 개의 값을 가진 하나의 변수를 만들 수 있다. 또한 배열의 값들의 리스트로 모두 같은 자료형의 값들이 같은 이름의 변수에 저장될 수 있다.

 

배열을 만드는 방법은 다음과 같다.

즉, 각각이 정수 값인 점수 3개를 갖고 싶다면 [] 를 사용해서 원하는 점수의 개수를 적고 ; 를 붙이면 된다. 이는 컴퓨터에게 정수 3개를 위한 메모리를 달라고 하는 것이다. 따라서 배열 아래 코드도 위와같이 scores[0] = 72; ... 으로 바꿔준다.

 

이는 각 점수들을 배열에 넣을 것인데 배열의 이름인 scores를 적고 []를 이용하여 배열의 첫 위치부터 0, 1, 2 ... 을 써주는 방식이다.

 

이는 앞서 만든 것과 같이 값이 3개 생겼지만 복사 붙여넣기 형태가 아닌 배열을 통해 모두 같이 한 변수에 저장하게 된 것이다.

 

물론 위 코드 또한 좀 더 잘 만들 수 있다. 동적으로 혹은 상호작용하게 만들 수 있다.

 

2번째 소스 코드에서는 물론 배열을 통해 한 변수에 3개의 값을 저장할 수 있게 되었기에 1번째 소스 코드보다는 더 좋은 코드라고 할 수 있다. 하지만, 평균부분을 보면 언제나 3으로 나누고 있고, 배열에 저장할 수 있는 값의 개수도 언제나 3이 될 것이다. 따라서, 시간이 많이 지나고 난 뒤 이 소스 코드를 다시 봤을 때 서로 같은 값을 가져야 한다는 것을 잊을 수도 있다.

 

이를 해결하려면 어떻게 해야할까?

 

int 값을 같는 변수 n을 3으로 초기화 하고, 원래 3이 있던 자리에 n을 대신 넣어주면 된다.

 

어떤 변수에 특정 값을 지정할 때 그 값을 바꾸고 싶지 않다면 함수 몸체에 int n 을 선언하는 것이 아닌 상단에 따로 선언하는 방법이 있다.

상단에 따로 const int N = 3; 이라고 선언하는 것이다. 대문자 N 은 그냥 관습에 따라 대문자로 적는 것이다. 이것이 바로 상수이다. 그리고 이 변수를 전역변수라고 한다. (함수 바깥에서 선언하는 변수를 의미)

const 로 해당 변수를 상수로 지정하면 clang 컴파일러는 사용자의 실수로 값이 바뀌지 않게 해준다.

상수를 선언하면 시간이 많이 지나도 상수인 것을 알 수 있기 때문에 문제가 발생하지 않는다.

 

이번에는 프로그램을 좀 더 동적으로 만들어보자.

위 프로그램은 과제 점수의 평균을 계산하는 프로그램이다. 배열을 사용했고 for문을 사용했으며, 사용자 정의 함수를 이용했다.

 

우선 main 함수 내

1. 사용자에게 먼저 과제의 개수를 물어본다

int n = get_int("number of scores : ");

 

2. scores라는 배열을 만들어서 배열의 크기를 n으로 설정한다.

int scores[n];

 

3. scores 배열의 값을 개수 만큼 넣기 위해 for문을 활용하여 사용자에게 i 번째 점수를 n 번째 과제까지 계속해서 프롬프트한다. 이때 몇번째 점수인지 알기 위해 %i 형식지정자를 설정해주고, i 는 0부터 시작하기 때문에 점수를 1부터 시작하기 위해서 i + 1 로 설정해준다.

for (int i = 0; i < n; i++)

{

   scores[i] = get_int("score %i : ", i + 1);

}

 

이렇게 하면 평균을 동적인 방식으로 계산하는 방법만이 남게된다. 즉, 각각의 점수를 printf에 입력하는 것이 아닌 동적으로 바꾸기 위함이다.

이때는 사용자 저의 함수를 만들어서 활용하는 것이 더 좋은 코드 작성 방법이다.

 

main 함수 밖

4. average 라는 함수를 만드는데 이때 평균은 소수점이 될 수 있으니 출력 형태를 float 라고 하고 함수명은 average이이다. 그리고 사용자가 입력한 값들의 평균을 내기 위해서는 배열의 길이와 배열을 인자로 받는 함수를 만들어준다. 입력의 형태는 int 이다.

 

float average(int length, int array[])  (이때 배열의 크기는 모르지만 컴파일러가 알아서 계산할테니 [] 라고한다.)

{

    int sum = 0;

    for(int i = 0; i < length; i++)

    {

         sum += array[i];   ( 각 반복마다 sum에 배열의 다음 원소의 값들을 더해주기 위한 코드 )

    }

return (float) sum / (float) length;

}

 

여기서 함수명 옆에 있는 float 의 의미를 알고 있어야한다.

float 는 반환 값을 의미한다. 즉, 함수가 사용자에게 돌려주는 값을 말한다. average 함수는 float 값을 반환한다는 의미이다.

따라서, average 함수내에서 값을 반환하기 위해 return 을 사용한 것이다.

return (float) sum / (float) length; 는

각 값을 더한 값에 점수들의 개수를 나눠준 것을 반환 하는 것이다. 그리고 이때 형변환을 통해서 값이 정확하게 나올 수 있게 해준다. 단순히 sum / length 라고 해준다면 정수 / 정수가 되기 때문에 출력되는 값이 정수가 된다. 따라서 실수 / 실수가 되기 위해서 형변환을 해준 것이다. 

 

※ int 와 float 를 나눈 값은 어떤 데이터 타입을 가지게 될까? 이때는 더 강력한 자료형으로 나오게 된다. 따라서 float를 얻게 된다.

 

5. 함수의 프로토타입을 반드시 적어준다.

float average(int length, int array[]);

 

main 함수 내

6. 화면의 평균을 출력하기 위해 printf 함수를 사용한다. 이때 소수점 1자리가 나오도록 하고, average 함수를 호출한 것이다. average 함수의 인자로는 배열의 길이인 n 과 scores 배열을 입력해준다.

printf("average : %.1f\n", average(n, scores));

 

※ 이때 알 수 있는 것은 함수가 printf 함수의 인자로써 사용될 수 있다는 것이다.

 

 

자료출처 : 부스트코스 - https://www.boostcourse.org/cs112/lecture/119014?isDesc=false, https://www.boostcourse.org/cs112/lecture/119015?isDesc=false