본문 바로가기
혼공학습단 C

C언어 W5(8강 배열)

by 알래스카코코넛 2023. 8. 8.
반응형

*본 포스팅은 한빛미디어의 '혼자공부하는 C언어(1판)'기준으로 작성되었습니다.

 

8-1 배열의 선언과 사용

 

그동안 변수를 선언할 때면 각각 이름을 하나씩 만들어줬다. 그런데 선언해야 할 변수가 너무 많으면 어떻게 할까? 예를 들어, 한 반에 있는 학생들의 수학 점수를 입력받는다고 하면 30개 정도의 변수가 필요하다. 그런데 만약 전교생의 수학 점수를 입력받는다면? 더 나아가 한 학교 전교생의 모든 과목 성적을 입력받는다면? 이 경우, 전교생이 400명이면 서로 다른 이름의 변수가 적어도 400개 필요할 것이고 인당 5과목씩만 본다고 해도 변수가 2000개 필요하다. 이런 변수들 이름을 하나하나 다 생각하기에도 힘들고, 메모리 낭비도 심하기 때문에 같은 형태의 데이터가 반복된다면 메모리에 연속적으로 저장해놓고 쪼개서 사용하는 방법을 사용한다. 이를 배열이라 한다. 

 

처음 들었을 때는 무슨 의미인지 이해하기가 어렵다. 따라서 간단한 그림과 함께 예시를 들어보자. 

위와 같이 명수, 준하, 재석의 국어, 수학, 영어, 정보 점수가 주어졌다고 하자. 이 경우 하나하나를 변수로 만들어주려면 다음과 같다. 

#include <stdio.h>
int main(){
    int MyungSoo_Korean = 80;
    int MyungSoo_Math = 65;
    int MyungSoo_English = 75;
    int MyungSoo_Com = 83;
    
    int JunHa_Korean = 76;
    int JunHa_Math = 95;
    int JunHa_English = 88;
    intJunHa_Com = 60;
   
    int JaeSeok_Korean = 90;
    int JaeSeok_Math = 85;
    int JaeSeok_English = 80;
    int JaeSeok_Com = 75;
    
    return 0;
}

모든 데이터를 하나하나 변수로 선언해주자니 효율이 너무 떨어진다. 그럼 어떻게 데이터를 정리하면 좋을까?

바로 표 형태로 데이터를 배열해주면 된다.

  국어 수학 영어 정보
명수 80 65 75 83
준하 76 95 88 60
재석 90 85 80 75

이 표를 C언어로 구현한 것이 배열이다. 

 

배열의 선언

배열 선언은 변수를 선언해주듯이 하면 된다. '자료형 배열명[요소 개수]'로 표현한다.

예)

int MyungSoo[5];

int JunHa[10];

 

위의 표를 배열로 선언하는 예시)

#include <stdio.h>

int main() {
    int MyungSoo[5]; //명수의 배열 선언
    int JunHa[5]; //준하의 배열 선언 
    int JaeSeok[5]; //재석의 배열 선언 
    
    MyungSoo[0] = 80; //명수의 국어 성적 
    MyungSoo[1] = 65; //명수의 수학 성적
    MyungSoo[2] = 75; //명수의 영어 성적 
    MyungSoo[3] = 83; //명수의 정보 성적 
    
    printf("명수의 국어 성적: ");
    printf("%d\n", MyungSoo[0]);
    //명수의 국어 성적:80

    return 0;
}

이처럼 MyungSoo라는 배열 하나만 사용해도, 같은 자료형이면(int, float, str 등등) 계속 선언된 변수를 사용할 수 있게 된다. 

 

아래에 설명하는 배열의 의미는 중요하므로 반드시 알아둬야 한다. 

처음 선언할 때 int MyungSoo[5]의 [5]는 배열의 크기를 나타내는 것으로, 안의 숫자-1개에 해당하는 수의 요소를 저장할 수 있다. 예를 들어, MyungSoo[5]는 총 4개(5-1)의 데이터를 저장할 수 있다는 의미이다. 얼마나 클지 모르는데 그냥 무작정 크면 좋은거 아니에요?라고 생각할 수 있지만, 컴퓨터의 용량은 한정되어있으므로 밑도 끝도 없이 큰 값을 사용하면 안된다. 

선언 이후 MyungSoo[0] = 80은 MyungSoo배열에서 0번째 요소가 80이라는 뜻이다. 컴퓨터의 대부분 요소는 0부터 시작이다. 1이 아니다.

컴퓨터계에서 시작은 1이 아닌 0이다

따라서 예시 코드에서는 MyungSoo 배열의 0번 요소는 80, 1번 요소는 65, 2번 요소는 75, 3번 요소는 83이라는 뜻이다. 그럼 선언하지 않은 4번 요소는 printf하였을 때 어떤 값이 나올까? 중요하지는 않지만, 쓰레기 값이 나온다. 그러나 이 쓰레기 값을 개발자가 굳이 출력하지 않으면 프로그램에 이상을 일으키지는 않으므로, 배열에 남은 칸이 있는 것에 크게 스트레스 받을 필요는 없다. 물론 앞에서 말했듯 너무 밑도 끝도 없이 큰 값을 선언해서 남은 공간이 10000개쯤 되면 이건 좀 문제가 된다...

참고삼아 언급해본 쓰레기 값. 아무 의미도 없다.

그럼 MyungSoo[5]를 언급하면 어떻게 될까? 답은 '그때그때 다르다'지만, 한 가지 확실한건 언제나 의도하지 않은 버그를 일으킨다는 점이다. 따라서 절대 선언한 배열의 용량을 넘는 선언은 하면 안된다. 

 

배열 초기화

    int MyungSoo[5]; //명수의 배열 선언
    int JunHa[5]; //준하의 배열 선언 
    int JaeSeok[5]; //재석의 배열 선언 
    
    MyungSoo[0] = 80; //명수의 국어 성적 
    MyungSoo[1] = 65; //명수의 수학 성적
    MyungSoo[2] = 75; //명수의 영어 성적 
    MyungSoo[3] = 83; //명수의 정보 성적

이 코드가 결국 변수 하나하나 언급해주면 비효율적 아니에요? 라고 할 수 있다. 그러나 이건 이해를 돕기 위해 풀어쓴 것이고, 이제 배열의 효율적인 선언을 알아보자.

int MyungSoo[5] = {80, 65, 75, 83};

이 배열 선언은 위에서 하나하나 배열 요소를 선언한 것과 의미와 쓰임이 같다. 이제 효율성이 눈에 확 들어온다. 그러나 이렇게 중괄호로 배열 선언은 처음 초기화 할 때만 가능하고, 이후에 배열을 변경할 때는 MyungSoo[0] = 90;과 같이 하나하나 언급해서 변경해야 한다. 최초 주문 혜택 같은 느낌으로 이해하면 쉽다. 

 

반복문을 이용해서 배열을 한번에 입력받아보자.

#include <stdio.h>

int main() {
    int JunHa[5]; //준하의 배열 선언 
    
    printf("준하님의 국어, 수학, 영어, 정보 성적을 입력하세요: ");
    for (int i = 0; i<4; i++){
        scanf("%d", &JunHa[i]);
    }
    
    printf("준하님의 국어, 수학, 영어, 정보 성적은 다음과 같습니다.");
    for (int a = 0; a<4; a++){
        printf("%d\t", JunHa[a]);
    }
    //준하님의 국어, 수학, 영어, 정보 성적을 입력하세요: 80 30 54 23
    //준하님의 국어, 수학, 영어, 정보 성적은 다음과 같습니다.80	30	54	23
    return 0;
}

이런 식으로 배열과 for문을 이용해서 한번에 데이터를 입력받을 수 있다.

 

그런데 배열을 선언한 후, 중간에 너무 많은 코드가 있어서 내가 선언한 배열의 크기(MyungSoo[5] 중 5)가 얼마인지 모르면 어떻게 할까? Ctrl+F를 사용해도 되지만 이것보다 더 간단한 방법이 있다. 배열을 저장 공간 크기로 나누면 된다. 무슨 뜻인지 말로 설명하면 모를 수 있으니, 예를 또 들어보자. 

어떤 학교가 있는데, 한 학년의 모든 반에 학생이 30명씩 있다고 하자. 그리고 해당 학년은 총 360명이다. 이러면 '총 12개의 반이 있다!'라고 직접적으로 말하지 않아도, 360/30 = 12이므로 우리는 12개의 반이 있는 것을 알 수 있다. 따라서

sizeof(배열명) / sizeof(배열 요소)

를 해주면, '한 학년의 총 인원 / 반별 인원'과 같이 때문에 간단히 배열의 크기를 알 수 있다. 


8-2 문자를 저장하는 배열 

char형 배열의 선언하고 초기화

위에서는 정수형인 int배열을 다뤘지만, 이번에는 문자열을 입력받는 char형 배열에 대해 알아보겠다. 

모든 배열의 중요한 점은 반드시 저장하는 데이터의 길이보다 최소 하나 이상 크게 배열을 선언해야 한다는 것이다. 그 이유는 널 문자 때문이다.

 

char형 배열 예시 타임: 

#include <stdio.h>

int main() {
    char words[20] = "Hello, World!";
    printf("최초 문자열: %s\n", words);
    
    printf("바꿀 문자열: ");
    scanf("%s", words);
    printf("바뀐 문자열: %s\n", words);
    
    return 0;
}

//최초 문자열: Hello, World!
//바꿀 문자열: Goodbye!
//바뀐 문자열: Goodbye!

이처럼 문자열의 길이가 줄어들면 남은 공간은 어떻게 될까? 이 남은 공간을 차지하는 문자가 널 문자다. 모든 배열들은 선언된 후 남은 자리를 널 문자로 채운다. 이 널 문자의 아스키코드 값은 0이며 문자 상수로는 \0으로 표기한다. 주로 문자열의 끝을 표시하는 용도로 사용되며, 널 문자는 영어 뜻 그대로 '아무것도 없음'을 의미하므로 출력될 때 아무것도 출력되지 않는다. 마치 문자열 단체의 유령 회원과 같다고 생각하면 된다. 

 

---------------널 문자 관련 중요사항-------------

char words[20] = "apple";

위 코드처럼 배열 선언 시 초기화를 해주면 널 문자를 끝에 언급할 필요가 없지만, 

char words[20];
words[0] = a;
words[1] = p;
words[2] = p;
words[3] = l;
words[4] = e;

words[5] = '\0'; //널 문자 선언

이렇게 하나하나 요소를 직접 대입할 시 반드시 마지막 문자 다음에는 널 문자를 대입해야 한다. 

 

문자열 대입

문자열을 초기화한 후에 새로운 문자열도 같은 내용으로 문자열을 대입하고 싶으면, strcpy(스트링 카피라고 외우면 편하다) 함수를 사용한다. 

#include <stdio.h>
#include <string.h>

int main() {
    char str1[10] = "abcdefg";
    char str2[10];
    
    strcpy(str1, "hijklm");
    strcpy(str2, str1);
    printf("str1: %s, str2: %s\n", str1, str2);
    
    //str1: hijklm, str2: hijklm
    return 0;
}

맨 위 include...부분에 늘 있던 <stdio.h>말고 <string.h>라는 새로운 녀석이 등장했다. 이건 뭘까?

문자열을 다루는 헤더파일이다. strcpy 등의 문자열 관련 함수 등을 모아놓은 책이라고 생각하면 편하다. 

 

문자열 전용 입출력 함수 gets와 puts

strcpy가 위에 보면 만능인 것 같지만, 그리고 scanf도 만능인 것 같지만 그렇지 않다. 

#include <stdio.h>

int main() {
    char words[20] = "Hello, World!";
    printf("최초 문자열: %s\n", words);
    
    printf("바꿀 문자열: ");
    scanf("%s", words);
    printf("바뀐 문자열: %s\n", words);
    
    return 0;
}

이 코드는 앞에서 사용한 코드다. 여기서 바뀐 문자열로 Goodbye!를 사용했는데, 만약 띄어쓰기가 있는 'Thank you!'등을 입력하면 어떻게 될까? 여기서 띄어쓰기를 못받는 C언어의 아픔이 드러난다. 

결과

띄어쓰기 이후의 값은 사라지고, Thank만 출력된다. 따라서 띄어쓰기까지 포함해서 출력하려면 다른 함수가 필요한데, 이때 사용하는게 gets함수다. 

#include <stdio.h>
#include <string.h>

int main() {
    char words[20] = "Hello, World!";
    printf("최초 문자열: %s\n", words);
    
    printf("바꿀 문자열: ");
    gets(words);
    printf("바뀐 문자열: %s\n", words);
    
    return 0;
}

띄어쓰기를 입력받을 수 있다

이처럼 gets(배열명)을 이용하면 쉽고 간편하게 띄어쓰기를 입력받을 수 있다.

추가로, printf 대신 puts를 사용하여 puts(words)를 printf("%s\n", words)처럼 사용할 수 있다. puts()함수는 파이썬 print()처럼 자동으로 뒤에 줄바꾸기("\n")기능이 포함된 printf()라고 생각하면 된다. 

 

기본미션 <배열의 개념 정리하고 공유하기>와 추가미션 <널 문자의 정의, 용도와 표기법 공유하기>가 단원 정리하면서 같이 끝났다! 

이번주도 끝!

혼공학습단 완주까지 한 주 남았는데 어떤 책을 살지 생각해봐야지~~

반응형

'혼공학습단 C' 카테고리의 다른 글

혼공학습단 10기 활동 회고  (0) 2023.08.23
C언어 W6(9강 포인터)  (0) 2023.08.16
C언어 W4(7강 함수)  (0) 2023.07.24
C언어 W3(5 ~ 6강)  (0) 2023.07.20
C언어 W2-2(4강)  (0) 2023.07.13