Today
Total
Recent Posts
Link
반응형
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
관리 메뉴

아임'준

[C언어 기초] 배열 : 일차원 배열, 다차원 배열, 배열로 선언된 문자열 본문

[C]/[C : 개념]

[C언어 기초] 배열 : 일차원 배열, 다차원 배열, 배열로 선언된 문자열

아임'준 2021. 1. 5. 01:05
반응형

C언어 / 배열 / 문자열

 

*해당 글에 있는 코드들은 모두 복사해서 사용할 수 있게끔 만들어두었습니다. 주석만 보고 이해하려고 하지 말고 코드를 복사해서 직접 실행하며 그 결과를 통해 이해하고자 하면 더 도움이 될것입니다. 물론 그냥 복사 붙여넣기 하는 것보다 본인이 직접 코드를 타이핑하는 것이 이해와 후에 사용할 때에 더 큰 도움이 될 것이니 이 점 명심하고 공부해주시기 바랍니다.

 

오늘은 배열에 대해서 처음으로 설명해볼 것이다. 배열을 들어가기 전에 주로 과제에서 많이 사용하는 것 중 #define 이라는 구문이 있다.

이는 상수를 만드는것과 비슷한 기능을 하는데 다음과 같이 사용할 수 있다. #include와 같이 코드 시작 전에 적어준다.

#define MAX 100
#define age 22
#define time 1.5

 

이렇게 원하는 이름과 원하는 값을 띄어쓰기로 구분하여 적어주면 해당 코드에서 MAX라는 값은 100으로 사용이 되며 그 이하도 마찬가지이다. 주로 사용할 때는 전체 코드에 있어서 일정한 값을 사용할 때 전역변수처럼 사용한다.

 

참고로 아무것도 들어있지 않다는 뜻으로 사용하는 NULL은 stdio.h 에서 #define NULL 0이라고 설정이 돼 있는것이다. NULL 외에도 수많은 값이나 매크로가 헤더파일에 들어가 있으나 나는 전처리를 통해 전역변수처럼 사용하는 방법에 대해서만 설명하도록 하겠다.

 

 

이제 배열에 대해서 이야기를 해보도록 하겠다.

배열은 영어로 array이며 국어사전에는 '동일한 성격의 데이터를 관리하기 쉽도록 하나로 묶는 일'이라는 뜻으로 등록돼있다.

간단하게 설명하여 같은 자료형을 가지는 여러개의 값들을 접근하기 쉽게 묶어둔 것이라는 뜻이다.

 

배열의 선언 방식은 다음과 같다.

자료형 배열이름[배열크기];

int arr[5];
char name[10];

위의 두개 예시를 순서대로 살펴보자면 다음과 같이 해석할 수 있다.

5개의 int형 값을 받아들일 수 있는 배열 arr을 만들어라.

10개의 char형 값을 받아들일 수 있는 배열 name을 만들어라.

 

배열을 통해 어떤것을 할 수 있을까?

일단 이전과 가장 다른 점을 꼽으라면 이제 더이상 한 글자만이 아닌 글을 저장할 수 있을것이다.

이전까지는 char first = 'a' 와 같은 형식밖에 사용하지 못했다면 이제 배열을 사용하여 우리가 Hello와 같은 단어/문장을 저장할 수 있다.

 

그럼 배열은 어떻게 사용하는 것일까?

내가 32 55 66 21 7 이라는 5개의 정수를 저장하고 싶다고 생각해보자. 배열은 다음과 같이 값을 초기화시켜줄 수 있다.

또 내가 Imjune 이라는 단어를 혹은 I am June 이라는 문장을 저장하고 싶으면 다음과 같이 초기화시켜줄 수 있다.

 
    int arr[5] = { 32, 55, 66, 21, 7};
    char blog[15] = "ImJUNE";
    char blog1[15] = {'I','m','J','U','N','E'};
    char h[15] = "I am June";
    

문자(글자)는 저장할 때 작은 따옴표를 사용하여야 한다. 하지만 문자열은 저장할 때 쌍따옴표를 사용하여야 한다. blog와 blog1이라고 돼있는 배열을 보자. 사실 둘에 저장할 값은 같다. 하지만 초기화 방식은 다르다. 당신이라면 어떤 방식을 쓰겠는가? 나라면 당연히 훨씬 적기에 편한 ""를 활용한 문자열로 초기화할 것이다.

 

그럼 배열을 통해 저장한 값에 접근은 어떻게 하는 것일까?

위에 선언한 arr[5]라는 배열은 int형 값 5개를 담는 arr이라는 이름의 배열을 만들라는 뜻이라고 했다.

이에 접근하는 방법은 간단하다. arr[0] ~ arr[4]에 순서대로 우리가 저장한 값들이 들어가있다.

여기서 arr옆의 대괄호 안에 들어있는 것을 참조번호, 인덱스 라고 하며(본인은 인덱스라고 더 자주 말함) 이 인덱스의 특징은 0부터 시작한다는 것이다.

int arr[5] = {1,2,3,4,5};

for(int i=0;i<5;i++){
	printf("arr[%d] = %d",i,arr[i]);
    }
    

선언할 때 배열의 이름 옆에 적는 숫자는 배열의 크기이다. 배열의 크기가 5이고 인덱스는 0부터 시작하니 0부터 5개가 나오려면 마지막 인덱스는 4이다. 당연한 것이다. 위의 코드를 실행해보면 다음과 같은 결과를 얻을 수 있다.

마찬가지로 위에 선언된 다른 문자열들의 하나하나에 접근하면 한 글자 한 글자씩 분리되어 나오는 같은 내용을 볼 수 있을 것이다.

 

빗대어 말하자면 배열은 기차이고 기차의 한 칸에는 하나의 값만 들어갈 수 있다. 그리고 그 기차들의 칸 번호는 0부터 시작하는 것이다.

 

이런 내용을 알았다면 이제 당신은 연속적으로 정수를 입력받아야 하는 작업에 대해서 아래와 같이 처리를 할 수 있을 것이다.

    int arr[5];

    for(int i=0;i<5;i++){
        printf("수를 입력해주세요: ");
        scanf("%d",&arr[i]);
    }
    
    for(int i=0;i<5;i++){
        printf("arr[%d] = %d\n",i,arr[i]);
    }

이전에는 5개의 서로 다른 값을 입력받기 위해서는 5개의 변수가 필요했는데 이제 배열 하나로 다 처리할 수 있는 것이다!

 

이제 배열 선언에 있어서 주의해야할 점에 대해서 알아보자.

먼저 배열을 선언할 때 사용하는 배열 크기가 상수가 아니라 변수이면 에러가 난다.

int n;
scanf("%d",n);
int a[n]; //에러

사실 에러가 난다고 배웠었는데 내 맥북에서는 되긴 한다. 그런데 다른 친구 윈도우에서 안 되는것을 보고 컴파일러나 프로그램 차이인가? 싶었다. 그렇다고 '아 나는 저렇게 하고 싶은데 어떡해 ㅠㅠ 하고 걱정할 이유는 전혀 없다.' 다음 글에 올라갈 배열을 선언하는 다른 방법을 통해 저것과 똑같은 기능을 사용할 수 있다.

 

또 배열을 선언할 때 초기화된 배열이라면 배열의 크기를 생략할 수 있다.

int a[] ={1,2,3,4,5}; //가능
int a1[]; //에러

 

배열을 선언할 때 모든 배열을 0으로 초기화해줄 수도 있다. (여러 상황에서 상당히 유용하게 쓰인다)

int arr[5]={0,}; //굳이 0을 5번 칠 필요 없음

물론 바로 위의 방법은 몰라도 for문으로 돌아가면서 arr[i] 안에 원하는 값을 넣어줘도 된다. 코딩에는 정말 많은 방법이 있으니까!

 

선언된 배열의 크기를 알고 싶다면 어떻게 해야할까? 이 부분에 대해서는 여러분이 이전에 배웠던 sizeof 연산자를 이용하여 방법을 생각해보자. 힌트를 주자면 int arr[5]; 라고 선언했다면 arr의 자료형은 int가 아닌 int [5]이다.

더보기

정답 :

정답 :

    int arr[5]={0,};
    
    int size= sizeof(arr)/sizeof(int); //얘가 정답이죠?
    
    for(int i=0;i<5;i++){
        printf("arr[%d] = %d\n",i,arr[i]);
    }
    
    printf("size = %d",size);
        

배열로 여러분이 생각하는 거의 모든 것을 할 수 있다. 연산자도 사용 가능하고 안의 값을 변형시킬수도 있다. 그냥 인덱스를 사용하여 접근하기 쉽게 모여있는 여러개의 변수 덩어리라고 생각하면 되는것이다.

 

이제 문자열을 보면 더 설명해야할 것들이 있다.

정수형 배열에 대해서 5개의 값을 저장하고 싶으면 int a[5]라는 구문을 통해 선언할 수 있었다. 하지만 문자열의 경우는 조금 다르다.

 

가령 Hello 라는 문자열에 대해서 생각해보자. 이 문자열의 길이는 5이다. 그럼 길이가 5니까 그냥 char a[5]; 라는 구문을 통해 선언하면 되는 것 아니냐? 라고 할 수 있는데 문자열의 마무리를 위해서는 NULL이 마지막에 필수적으로 들어가야한다. '\0' 이 NULL과 같다.

 

참고로 문자열의 경우 출력을 원할 때 굳이 for문으로 각각의 인덱스에 접근할 필요 없이 %s 서식지정자를 통해 배열의 이름을 입력하면 문자열을 출력할 수 있다. (이 부분에 대해서는 포인터 설명이 조금 필요할 것 같은데 다음 글이 포인터이니 다음 글을 잘 보도록 하자)

printf("%s",arr);

하지만 이럴 경우 컴퓨터는 출력을 할 때 시작점을 알고 있지만 어디서 끝내야할지 알지 못한다. 이러한 컴퓨터를 위해 문자열은 항상 마지막에 널 문자('\0')를 포함하고 있어야 한다. 그렇기에 Hello와 같이 5글자를 저장하고 싶다면 배열의 크기를 글자수 +1로 설정해주어야 한다.

 

또한 문자열은 선언될 때 바로 초기화를 해주지 않고 후에 문자열을 할당하고자 할 경우 불가능하다.

char b[10]="Hello" //가능

char a[10];
a="Hello" // 오류

 

이런식으로 a에 원하는 값을 입력할 수는 있다.

    char a[5];
    scanf("%s",a); //& 없는게 오타가 아닙니다!

이제 위의 코드를 보면 의문이 생길 수 있다. 엥 왜 이번에는 &가 안 들어가죠? 하는 질문이 생기는 것이 당연하다. 이는 배열이라는 것이 포인터와 어느 관계인지를 파악해야한다. 배열과 포인터의 관계에 대해서는 다음 글에 대해서 더 자세하게 말하겠지만 기억을 더듬어보자. scanf는 이러한 주소에 이 값을 저장해주세요 라는 뜻이다. 그런데 배열의 경우 배열의 이름을 그냥 적을 시 배열이 가리키는 곳은 배열의 가장 처음 요소(데이터)의 주소이다. 그러니 사실상 scanf("%s",a)에 있는 a 자체가 그 배열의 주소를 가리키니 주소를 가리키는 &를 적어줄 필요가 없어지기 때문이다. 앞서 for문 없이 문자열을 출력할 수 있는 이유도 간략하게 설명해서 이것과 연관이 있다. 처음 시작점을 알려주니 출력을 시작할 수는 있는데 NULL 문자가 없으면 어디까지 출력해야할지 모르기 때문에 NULL 문자를 만날 때까지 이상한 값이 출력될 수 있는것이다.

 

이제 이 얘기를 들으면 그래서 &를 언제는 붙이고 언제는 안 붙여야 하는거야...? 하고 헷갈릴 수 있다. &를 붙일 때는 어떤 변수의 주소가 필요할 때 &(변수명)을 통해 쓰는거고 바로 위의 a의 경우 a가 의미하는 것이 a[0]의 주소이기 때문에 굳이 &를 붙일 이유가 없는 것이다. 그러니까 a[0] 은 따지면 그냥 변수 하나라고 생각 할 수 있는것이며 a==&a[0]이라는 식을 출력해본다면 참이라는 결과를 출력할 것이다.

 

이제 또 궁금한 점이 생길수도 있다. 그럼 배열을 선언해도 한 문자열밖에 저장을 못하지 않나요? 저는 이름 5개를 저장하고 싶은데 그럼 배열 5개를 따로 선언해야하나요? 하는 질문을 가질 수 있는데 이 또한 해결할 수 있다. 아래 내용을 보자.

 

 

 

일차원 배열에 대한 기본적인 설명들을 끝낸 것 같으니 이제 차원에 대해서 이야기해보도록 하겠다.

우리가 지금까지 쓴 배열은 모두 일차원 배열이다. 이렇게 말하면 이제 눈치챘을 가능성이 높겠지만 다차원 배열들도 있다. 하지만 주로 쓰는것은 이차원 배열이니 이차원 배열까지만 설명을 하도록 하겠다. 이차원 배열 위의 배열들을 사용하고 싶어도 이차원 배열만 쓸 줄 안다면 여러분들이 쓰고 싶다면야 쓸 수는 있을것이다.

 

이차원 배열을 선언하는 것 또한 예상할 수도 있을 것이다. 다음과 같다.

int a[가로(행)의 개수][세로(열)의 개수];

 

행과 열이 헷갈릴 수 있을텐데 가로가 행 세로가 열이다. 본인은 가행 이라고 하나만 외웠는데 사람마다 외우는 방법은 많은듯하다. 

대충 그림으로 표현해보고자 하면 다음과 같다.

a[2][4]를 선언하였을 때 0열    v 1열    v 2열    v 3열    v
0행 -> a[0][0] a[0][1] a[0][2] a[0][3]
1행 -> a[1][0] a[1][1] a[1][2] a[1][3]

그럼 이차원 배열의 초기화는 어떻게 해야할까?

    int a[2][3] ={
        {1,2,3}, //세로(열)에는 3개의 값 
        {4,5,6}  //가로(행)은 총 2개
    };
    
    char a1[2][3] ={
        "ad",
        "ap"
    };

다음과 같은 방식을 통해 할 수 있다.

 

여기서 재밌는 점을 말해주자면 만약 위의 코드를 통해 인덱스 범위를 벗어나는 값을 출력하고자 하면 에러는 일어나지 않지만 이상한 값이 튀어나온다. 그런데 이차원배열의 경우 배열이 일렬로 있는 것과 마찬가지라 a[0][3]등 인덱스 범위를 벗어나게 하면 다음 값들에 접근할 수 있다. ex) a[0][3] 은 a[1][0]과 같다 a[0][6]은 a[2][0]과 같다.

 

이제 우리는 앞서 의문을 가졌던 것에 대해 답을 얻을 수 있다. 이차원 배열을 통하면 문자열 또한 여러개 저장할 수 있다. 예를 들어 5명 사람들의 이름을 우리는 이차원 배열을 통해 저장할 수 있는 것이다. 굳이 이름 얘기를 꺼낸 이유는 이따가 실습해볼 것이기 때문이다.

 

여러분의 이해에 도움을 주기 위해 scanf를 통해 이차원 배열의 입력을 받는 코드를 아래에 적어두겠다. 근데 당연히 아래 열어보기 전에 여러분이 직접 손으로 꼭 시도해보길!

 

문제) int a[2][3]에 1 2 3 4 5 6이라는 값을 순서대로 넣기 위해서는 어떻게 해야할까?  값을 저장하고 출력해보자. 그리고 아까 말한 a[0][0]의 주소와 a는 진짜 같을까?  (다음에 배울 중요한 내용이니 알더라도 답을 꼭 봐주세요!)

정답:

더보기
    int a[2][3];
    
    for(int i=0;i<2;i++){
        for(int j=0;j<3;j++){
            scanf("%d",&a[i][j]); //i와 j가 각각 무엇을 의미하는지 생각하며 짜봐야합니다.
        }
    }
    
    for(int i=0;i<2;i++){
        for(int j=0;j<3;j++){
            printf("%d ",a[i][j]); //각 열을 구분하기 위해 띄어쓰기를 했습니다.
        }
        printf("\n"); // 행 열을 살리기 위해 한 행이 끝나고 개행문자를 출력해줬습니다.
    }
    
    printf("%d %d\n",a,&a[0][0]); //당연히 같게 나올겁니다
    

 

 

배열 설명은 여기까지 하고 실습을 위해 한가지 더 알려주고자 한다. 바로 랜덤함수이다.

랜덤함수는 범위 내의 랜덤한 값을 반환해주는 함수이다. 랜덤 함수는 stdlib.h (standard library)에 들어있는 함수이므로 이를 #include로 처리해주어야 사용할 수 있다.

rand() % n //0~n-1 사이의 랜덤한 수를 반환
(rand() % n) + 1 //1~n 사이의 랜덤한 수를 반환

다음과 같은 방식으로 사용할 수 있는데 주의해야할 점이 있다. 저렇게 랜덤함수를 사용할 경우 코드를 여러번 돌려보아도 항상 같은 값을 출력할 것이다. 이는 랜덤함수의 씨드라는 것과 관련이 있는데 이에 대해서는 굳이 깊게 설명하지 않겠다. 그럼 이렇게 같은 값이 나오는 상황을 어떻게 고쳐야 하는가에 대해서 알아보겠다.

 

time.h 에는 time이라는 인자값(넣는 값이라고 생각하면 됨)을 NULL로 할 시 1970년 1월 1일을 기준으로 지금까지 흐른 시간을 초를 반환해주는 함수가 있다. stdlib.h에는 rand외에 srand라는 함수 또한 들어가 있는데 이 srand가 rand의 결과값을 출력하는 기준이 된다고 생각하면 된다. 이 기준이 변하지 않으니 실행할 때마다 같은 값이 나왔던 것이고 이 기준을 time함수를 이용해 계속 변하는 시간으로 기준을 삼아준다면 코드를 실행할때마다 다른 값이 나올 것이다.

 

그래서 어떻게 사용하느냐면 다음과 같은 구문을 rand 함수를 사용하기 전에(이왕이면 빼먹지 않게 main 첫 줄에) 작성하기를 바란다.

srand(time(NULL));

 

이 구문을 적어주는 순간 실행할 때마다 다른 값이 나올 것이다.

 

반환이라는 단어가 생소해 글만 봐서는 헷갈릴 수 있다. 반환은 그냥 그 값을 뱉어낸다고 생각하면 된다. 다음을 보자.

rand() % 100;

이라는 구문을 적었다고 생각해보자. 해당 구문은 그냥 아래 구문과 다를 바가 없다.

3;

rand 함수를 통해 생성된 난수(무작위 값)을 그냥 적어놓은것이니 아무짝에도 쓸모가 없는 구문이 된다는 뜻이다. 그러므로 쓰고 싶다면 변수에 저장하는 방식으로 사용하도록 하자.

 

 

이제 랜덤 함수를 배웠으니 지난번에 만들었던 up&down 게임을 더 재밌게 할 수 있을 것이다.

답을 rand 함수를 통해 설정해주면 여러분도 무엇이 답인지 모르니 이제 여러분이 만든 코드로 여러분이 직접 게임을 할 수 있는 것이다.

 

※ up&down 게임 만들기 ↓

2021/01/01 - [[C]/[C : 기초]] - 0.4 반복문 : for, while, do-while, 이중반복 및 제어문 + 지역,전역변수

 

0.4 반복문 : for, while, do-while, 이중반복 및 제어문 + 지역,전역변수

*해당 글에 있는 코드들은 모두 복사해서 사용할 수 있게끔 만들어두었습니다. 주석만 보고 이해하려고 하지 말고 코드를 복사해서 직접 실행하며 그 결과를 통해 이해하고자 하면 더 도움이 될

stujune-to-devjune.tistory.com

 

오늘 글에 대한 본격적인 실습을 진행하기 전에 오늘 배운 두가지 내용을 동시에 만족하는 문제를 내보도록 하겠다.

문제) 한국 로또 6/45는 1~45의 숫자 6개를 맞춰야한다. 로또 번호 자동 생성기를 만들어보자. 6개 숫자는 중복이면 안됨

배열과 rand함수를 사용할 것!

출력 예시:

정답 예시:

더보기
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    
    int num[6] ={0,};
    int i,count=0;
    
    srand(time(NULL));
    
    while(count<6){
        int ran = (rand()% 45) + 1; //1~45 사이의 랜덤값
        int check=1; //중복이 있는지 없는지 판단
        
        for(int i=0;i<count+1;i++){
            if(ran==num[i]){ // 중복값이 있을 경우
                check=0; //체크=0
                break;
            }
        }
        
        if(check==1){ //체크=1 즉 중복값이 없을 경우
            num[count++]=ran;
        }
    }
    
    printf("당신의 번호는 ");
    
    for(i=0;i<6;i++){
        printf("%d ",num[i]);
    }
}

 

 

위 문제까지 풀었으면 이제 본격적으로 아까 말했던 실습을 진행하도록 하겠다.

친구들 이름을 5개를 입력받아서 그 중 한명을 벌칙 대상으로 뽑는 프로그램을 작성하고자 한다.

1. %d번째 이름을 입력하세요 라는 출력을 통해 이름을 5번 입력 받는다. %d에는 1부터 5까지의 수가 출력되게끔 한다.

2. 입력받은 모든 이름을 (참여자: 이름1 이름2 이름3 이름4 이름5)의 형식으로 출력한다.

3. rand 함수를 통해 범위 내의 특정 번호를 뽑아서 그 인덱스에 해당하는 친구의 이름을 출력한다.

4. 축하합니다 당첨자는 (이름)입니다! 이라는 문구를 출력함을 통해 누가 당첨됐는지 알려준다.

5. 배열은 이름이 충분히 들어가도록 크기를 선언하여 사용한다. 

 

생각해볼 점: scanf로 이름을 입력받을 때 어떻게 입력받아야 하는가? &를 붙여야 하나? []가 있어야 하나? 있어야 하면 몇개 있어야 하나?

정답 예시:

더보기
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    
    char name[5][20];
    
    int i;
    
    for(i=0;i<5;i++){
        printf("%d번쨰 이름을 입력해주세요 : ",i+1);
        scanf("%s",name[i]); //이차원 배열이기 때문에 name[i]는 name[i][0]의 주소와 같다 그렇기에 & 안 붙는다.
    }
    
    srand(time(NULL));
    
    int winner = rand()%5; //참가 인원이 5명이니 5로 한다 (인덱스 번호는 0~4니까 +1 해줄 필요 x)
    
    printf("참가자 명단: ");
    for(i=0;i<5;i++){
        printf("%s ",name[i]); //일차원 배열에서 []안 붙이고 출력하는 것과 같은 원리
    }
    
    printf("\n축하합니다! 당첨자는 %s입니다!",name[winner]);
}

 

다음 시간에는 포인터를 공부해보도록 하겠다.

질문, 오타 지적, 오류 지적 등은 댓글로 환영합니다.

Comments