C 언어(14) - 배열, 상수

C 언어 강의, 배열과 상수

Featured image

🔚 짧게 하는 복습

✅ 1. 다중 포인터를 이해한다.

✅ 2. 포인터와 정수의 덧셈, 뺄셈을 이해한다.

✅ 3. 포인터 간의 뺄셈을 계산한다.

혹시 기억이 안 난다면, 다시 돌아가자


혹시 반복문에 대해서 기억하는가? (기억이 안 나거나, 처음 왔다면 여기로)

이제 여러분은 반복된 작업을 하는 것이 두렵지 않을 것이다.

그런데 100개의 성적을 입력받고, 100개의 변수를 각각 다루어야 한다면 어떨까?

당연히 별거 아니지..라고 생각했다면, 다시 잘 생각해보길 바란다.

변수의 선언은 반복문으로 줄일 수 없다.

int a;
for(int i= 0; i<100; i++){
    scanf("%d", &a);
    //???
}

이렇게 하면 마지막에 입력된 값만이 a에 저장되기 때문이다.

그럼 할 수 없이 우리는 아래와 같이 100개의 변수를 선언 해야한다.

int a1, a2, a3, ... ,a100;

더 큰 문제는 입력이나 값을 다루는 것이다…. 이 역시 반복문으로 다룰 수가 없기 때문이다.

scanf("%d", &a1);
scanf("%d", &a2);
...
scanf("%d", &a100);

이런 문제에 봉착한 컴퓨터 공학자들은 새로운 방법을 만들어야 했다.


자료구조?

공학자들은 같은 자료형 변수들을 일련의 순서로 묶어 한 번에 처리하는 구조를 만든다. 이를 우리는 배열이라고 부른다.

여기서 우리가 한 번도 들어보지 못한 단어가 나오는데, 바로 구조이다.

배열은 자료형이 아니라 구조인데, 이는 하나의 변수가 아니라 여러 변수를 다루는 방법의 하나기 때문이다.

아직은 다루지 않았지만, 배열뿐만 아니라 여러 구조가 있다. 이런 구조들은 모두 데이터를 효과적으로 처리하기 위해 만들어졌다.

또, 이렇게 데이터를 조직화하고 저장하고 검색하고 조작하기 위한 방법론을 자료구조라고 한다. (아직은 이 용어에 너무 신경 쓰지 않아도 된다.)


배열이란?

아무튼, 다시 돌아와서, 배열자료구조 중 하나이며, 같은 자료형 변수를 여러 개를 연속적으로 다루는 구조라고 생각하면 좋다. (엄밀한 정의는 포인터와 결합하여 추후에 다루겠다.)

중요한 점은 같은 자료형이어야 한다는 점이다. 배열을 선언하는 방법은 (자료형) (배열 이름)[원소 개수]이다.

int arr[3];
double arr2[2];
char arr3[5];

이런 식으로 선언할 수 있다. 또한, 변수처럼 초기화도 할 수 있는데 {}를 이용해서 원소들을 입력해주면 된다.

int arr[3] = {1, 3, -2};
double arr2[2] = {0.3, 3.1};
char arr3[5] = {'e', 'A', 'B', 'S', 'x'};

그리고 약간의 팁으로는

int arr[] = {1, 3, -2}; //1번 라인
// int arr[3] = {1, 3, -2};
int arr2[5] = {0, }; //2번 라인
// int arr2[5] = {0,0,0,0,0}
int arr3[5] = {1, 2}; //3번 라인
// int arr3[5] = {1,2,0,0,0}

1번 라인처럼 입력하면 원소 개수를 자동으로 컴퓨터에서 설정해주고, 2번 라인처럼 입력하면 배열의 모든 값을 0으로 채워준다.

또한, 3번 라인처럼 최대 크기에 비해서 적은 값을 입력하면, 순서대로 앞에서부터 들어가고 나머지는 0으로 채워진다.

또 주의할 점은, 아래 코드처럼 {} 대입은 배열의 선언 시에만 사용할 수 있고 한 번 선언된 배열에는 사용할 수 없다.

int arr2[5];

arr2 = {1, 3, 5, 8, 2};// 오류

배열에서 원소 접근

배열 안의 각각의 원소들은 인덱스(정숫값)으로 접근이 가능하다.

인덱스란 몇 번째 원소인지를 나타내는 숫자로, 주의할 점은 맨 앞의 원소는 0번째이다.

아래의 코드를 실행해보자.

각각의 원소들은, 배열 이름[인덱스]로 참조가 가능함을 알 수 있다.


for 문으로 배열 다루기

scanf("%d", &a1);
scanf("%d", &a2);
...
scanf("%d", &a100);

아까는 변수 이름이 바뀌는 상황이라 반복문을 사용할 수 없었다.

하지만 이제 우리는 새로운 방법이 생겼다. 배열 이름[인덱스]로 참조가 가능하게 되었고, 배열 이름은 안 바뀌니까 인덱스만 바꾸면 다양하게 참조가 가능해졌다.

여기서 특히 인덱스는 정수형이라고 했다. 같은 작업을 하는데 인덱스만 바뀌면 된다…? for 문이 떠올랐다면 훌륭하다.

저번 반복문 수업에서 정형적인 상황에서는 for 문이 훨씬 더 많이 쓰인다고 했었는데, 바로 이런 상황이다.

for 문을 통해서 입력 및 출력하여보는 코드를 알아보자.

인제야 일반적인 for 문의 초기 조건이 i= 1; 이 아니라 i = 0;인 이유가 나온다. 배열의 첫 원소 인덱스가 0이기 때문이다.

이를 이용해서 여러 문제를 해결할 수 있는데, 과제더 공부하기를 통해서 연습해보자.

이렇게 이번 수업은 끝나는가…. 했는데, 아직 하나가 남았다. 배열의 원소 개수는 꼭 고정돼야 할까?

아래의 코드를 실행해보자. size란 변수를 통해서 배열의 원소 개수를 바꾸려는 코드이다.

#include<stdio.h>

int main(){
    int size;
    scanf("%d", &size);

    int arr[size];
    int i = 0;

    for(i= 0; i<size; i++){
        scanf("%d", &arr[i]);
    }

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

    return 0;
}

아마 대부분 컴파일러에서 에러가 날 것이다. 이는 배열 원소 개수는 특정 상수만들어가야 하기 때문이다.


상수란?

상수변수와 다르게 한 번 정의하면 그 값을 바꿀 수 없는 저장공간을 의미한다.

상수의 종류는 총 4가지가 있다. 리터럴, const, 매크로, enum 상수가 있다.


리터럴 상수란?

리터럴 상수는 우리가 항상 대입하던 일반적인 정수, 실수, 문자이다.

예를 들면,

int arr[3] = {1, 3, -2};
double arr2[2] = {0.3, 3.1};
char arr3[5] = {'e', 'A', 'B', 'S', 'x'};

이 코드에서 나오는 정수 3, 2, 5, 1, 3, -2, 실수 0.3, 3.1, 문자 ‘e’, ‘A’, ‘B’, ‘S’, ‘x’들이 리터럴 상수이다.

여기에 문자열 리터럴 상수도 있는데, “Hello, World”, “I’m Minsu”도 포함된다.

당연히 여기서 보이는 것처럼, 배열 원소 개수 부분에 넣어서 배열 선언에 사용할 수 있다.


const 상수

const 상수는 리터럴과 달리 프로그래머가 설정하는 상수이다.

설정하는 방법은 변수 선언 앞에 const를 붙이면 된다.

const int a = 2;
const double b = 1.5;
const char c = 'x'

이렇게 하는 이유는, 누군가가 변수에 접근에서 값을 바꾸려고 시도하면 오류가 나기 때문이다.

절대 변해서는 안 되는 값에는 const를 붙여주면 된다.

하지만 const 상수는 리터럴 상수와 다르게 배열 원소 개수로 사용할 수 없다.

그 이유는 여기서 다루기는 어려워서, 더 공부해보기로 넘기겠다.


매크로 상수

매크로 상수란 코드가 실행되기 전 처리되는 상수이다. (이를 전처리라고 한다.)

쓰는 방법은 간단한데, int main보다 위에

#define PI 3.14159

이렇게 적어놓고, PI를 3.14159라는 리터럴처럼 사용하면 된다.

리터럴처럼 사용할 수 있으니, 매크로 상수도 배열 원소 개수로 사용할 수 있다.


enum형 상수

enum형 상수열거형 상수라고도 하는데, 배열과 굉장히 비슷하게 생겼다.

역시나 int main보다 위에,

enum 태그명 {상수1, 상수2, 상수3, 상수4 ... };//열거형 상수 상수1 : 0 , 상수2 : 1, 상수3: 2, 상수4 : 3 ...

이렇게 적으면 된다.

태그명이라는 것은 이 열거형 상수들의 이름을 말하고 {}안의 값들은 순서대로 0부터 1, 2…. 의 값이 매칭된다.

이는 주로 옵션이나, 항목이 한정될 때 쓰인다.

예를 들어 아래 코드를 보자.

if(answer == 0){
    //code1
}
else if(answer == 1){
    //code2
}
else if(answer == 2){
    //code3
}

물론 오류도 없고 훌륭하게 짠 코드지만, 이렇게 수정하면 더 가독성이 좋아진다.

enum 태그명 {YES, NO, ETC};

if(answer == YES){
    //code1
}
else if(answer == NO){
    //code2
}
else if(answer == ETC){
    //code3
}

이전의 코드와 결과나 논리는 다를 바 없지만, 이쪽이 더 좋은 코드로 보인다.

이쪽도 const 상수와 마찬가지로 배열 원소 개수로는 사용할 수 없는데 const 상수와 같은 이유이다.


📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 배열의 선언, 초기화, 원소 접근을 안다.

✅ 2. 상수의 종류와 쓰임을 안다.

✅ 3. 어떠한 상수들이 배열 선언 시 원소 개수로 사용될 수 있는지 알자.

⚠️ 초기화되지 않은 변수를 사용하는 것은 위험하듯, 초기화되지 않은 배열을 쓰는 것도 위험하다.

⚠️ 배열의 인덱스를 항상 조심하자. 0 ~ 원소 개수 -1까지만 접근 가능한 인덱스이다.

💣 과제,

  1. 정수 10개를 입력받고, 배열을 이용해 최댓값을 구하는 코드를 짜보자 (난이도 下)

  2. 정수 10개를 입력받고, 배열을 이용해 평균을 구하는 코드를 짜보자 (난이도 下)

  3. 정수 10개를 입력받고, 특정 값이 배열 안에 있는지 구하는 코드를 짜보자 (난이도 中)

  4. 정수 10개를 입력받고, 오름차순으로 정렬하여 출력하는 코드를 짜보자 (난이도 上) (버블 정렬을 검색해보자, 특히 값을 교환하는 방법을 배울 것)

🔜 더 공부해보기,

  1. 읽어볼 거리(1) - const와 enum 상수는 배열 원소 크기로 쓰일 수 없는 이유

백준(BOJ) 10810번 - 공 넣기 (배열의 최대 크기가 정해질 수 있다는 점을 명심할 것)

정답 보기
#include<stdio.h>
#define MAX 101

int main(){
    int size, orders;
    scanf("%d %d", &size, &orders);

    int arr[MAX] ={0, };

    for(int i = 0; i<orders; i++){
        int start, end, ball;
        scanf("%d %d %d", &start,&end, &ball);

        for(int j = start -1; j<end; j++){
            arr[j] = ball;
        }
    }

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

    return 0;
}

백준(BOJ) 10183번 - 공 바꾸기

정답 보기
#include<stdio.h>
#define MAX 101

int main(){
    int size, orders;
    scanf("%d %d", &size, &orders);

    int arr[MAX] ={0, };

    for(int i= 0; i<size; i++){
        arr[i] = i+1;
    }

    for(int i = 0; i<orders; i++){
        int box1, box2;
        scanf("%d %d", &box1 ,&box2);

        int temp = arr[box1-1];
        arr[box1-1] = arr[box2-1];
        arr[box2-1] = temp;// 값을 교환하는 방법을 꼭 알자.
    }

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

    return 0;
}

백준(BOJ) 5597번 - 과제 안 내신 분..? (약간의 센스가 필요한 문제)

정답 보기
#include<stdio.h>

int main(){
    int arr[30] ={0, };

    for(int i= 0; i<28; i++){
        int num;
        scanf("%d", &num);
        arr[num-1] = 1;
    }

    for(int i= 0; i<30; i++){
        if(arr[i] == 0){
            printf("%d\n", i+1);
        }
    }

    return 0;
}

백준(BOJ) 3052번 - 나머지

정답 보기
#include<stdio.h>

int main(){
    int numbers[42] = {0,};

    for(int i = 0; i<10; i++){
        int number;
        scanf("%d", &number);
        numbers[number%42] += 1;
    }

    int count = 0;

    for(int i= 0; i<42; i++){
        if(numbers[i]!=0){
            count+=1;
        }
    }

    printf("%d", count);

    return 0;
}

백준(BOJ) 10811번 - 바구니 뒤집기 (정말 좋은 문제)

정답 보기
#include<stdio.h>
#define MAX 101

int main(){
    int size, orders;
    scanf("%d %d", &size, &orders);

    int arr[MAX] ={0, };

    for(int i= 0; i<size; i++){
        arr[i] = i+1;
    }

    for(int i = 0; i<orders; i++){
        int box1, box2;
        scanf("%d %d", &box1 ,&box2);

        box1 -= 1;
        box2 -= 1;

        int length = box2 + box1;

        for(int i= box1; i<= (box1+box2)/2; i++){
            int temp = arr[i];
            arr[i] = arr[length - i];
            arr[length - i] = temp;// 값을 교환하는 방법을 꼭 알자.
        }
    }

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

    return 0;
}

백준(BOJ) 1546번 - 평균 (정말 좋은 문제)

정답 보기
#include<stdio.h>
#define MAX 1001

int main(){
    int n;
    scanf("%d", &n);

    int max = -1; //최대값을 구하기 위한 초기값 설정

    int arr[MAX] = {0,};

    for(int i= 0; i<n; i++){
        scanf("%d", &arr[i]);
        if(max < arr[i]){
             max = arr[i];
        }//최대값 구하기 알고리즘
    }

    double sum = 0;

    for(int i= 0; i<n; i++){
        sum += 100.0*arr[i]/max; //형변환 스킬
    }

    printf("%.3lf", sum/n);

    return 0;
}