C & C++/디딤돌 C언어

Part 15. 배열과 포인터 사용은 인덱스와 간접 연산자 [디딤돌 C언어]

언휴 2024. 1. 12. 08:04

Part 15. 배열과 포인터 사용은 인덱스와 간접 연산자

49. 간접 연산자

배열과 포인터 사용은 인덱스와 간접 연산자 [디딤돌 C언어]

피연산자로 포인터를 사용하는 더하기, 빼기 연산은 프로그램 메모리 주소를 계산하거나 상대적 거리를 계산하죠.
하지만 실제 개발자는 프로그램 메모리 주소를 아는 것은 큰 의미가 없어요.

개발자는 특정 프로그램 메모리 주소에 있는 값을 얻어오거나 설정하는 것을 원해요.
C언어에서 포인터(배열 이름 포함)가 갖는 메모리 주소에 원하는 값을 설정하거나 얻어오는 방법은 크게 간접 연산자와 인덱스 연산자를 사용하는 방법이 있어요.

간접 연산자는 선언문이 아닌 코드 구문에서 포인터 형식을 피연산자로 오는 단항 연산자예요.
연산 기호는 포인터 변수를 선언할 때 사용한 지시 연산자 *와 같아요.
모양은 같지만 선언문에 있는 것은 선언한 변수가 포인터 형식임을 나타내는 지시 연산자예요.

간접 연산의 결과는 포인터 변수가 가리키는 메모리를 나타내며 형식은 포인터의 원소형식이예요.

예를 들어 int 형식의 변수 a의 주소를 int 형식의 포인터 변수 p에 대입하였다고 가정할게요.
그리고 간접 연산으로 p가 가리키는 곳에 값을 8로 설정하면 어떻게 될까요?
포인터 변수 p에는 변수 a의 주소를 값으로 갖고 있으면 간접 연산을 하면 a변수에 의해 관리하는 메모리를 의미하죠.
따라서 간접 연산으로 p가 가리키는 곳에 8을 설정하면 a  변수의 값은 8로 변해요.

포인터 변수에 간접 연산을 사용하면 가리키는 메모리에 접근
포인터 변수에 간접 연산을 사용하면 가리키는 메모리에 접근

◈ 기본 형식 변수의 주소를 포인터 변수의 초기값으로 설정한 후에 간접 연산 사용 예

#include <stdio.h>
int main()
{
    int a = 0;
    int *p = &a; //a 변수의 메모리 주소를 p의 초기값으로 설정
 
    *p = 8;
    printf("&a:%d p:%d *p;%d a:%d\n", &a, p, *p, a);
    return 0;
}

◈ 실행 결과

&a: 1043440 p:1243440 *p:8 a:8

배열 이름은 관리하는 메모리의 시작 주소를 의미하여 배열 원소 형식의 포인터로 상수로 취급해요.
따라서 배열 이름은 원소 형식이 같은 포인터 변수에 대입할 수 있어요.
또한 포인터와 정수로 원하는 원소의 주소를 계산한 후 간접 연산을 통해 각 원소에 원하는 값을 설정할 수도 있답니다.

◈ 배열 이름을 포인터 변수에 대입한 후 간접 연산 사용 예

#include <stdio.h>
int main()
{
    int arr[3] = {1,2,3};
    int *p = arr;
    int index = 0;
    for(index = 0; index<3; index++)
    {
        printf("%d %d\n",*(p+index), *(arr+index));
    }
    return 0;
}

◈ 실행 결과

1 1
2 2
3 3

포인터 변수와 정수 사이에 더하기 연산은 상대적 거리에 있는 메모리 주소를 의미
포인터 변수와 정수 사이에 더하기 연산은 상대적 거리에 있는 메모리 주소를 의미

◈ 기본 연습
1. 다음 3단계를 수행하는 코드를 작성하세요.
a. char 형식 변수 c를 선언하고 char 형식 을 원소로 하는 포인터 변수 p를 선언한 후에 변수 c의 주소를  p의 초기값으로 설정하세요.

b. 포인터 변수  p에 간접 연산을 이용하여 ‘A’를 대입하세요.

c. 그리고 변수 c의 문자를 출력하세요.

더보기
//char 형식 변수 c를 선언, char 포인터 변수 p 선언 및 변수 c의 주소로 초기화
//포인터 변수  p에 간접 연산을 이용하여 'A'를 대입
//변수 c의 문자를 출력
#include <stdio.h>
int main(void)
{
    char c;
    char *p = &c;
    *p = 'A';
    printf("%c\n",c);
    return 0;
}

▷실행 결과

A

2. 다음 3단계를 수행하는 코드를 작성하세요.

a. int 형식을 원소로 하고 원소 개수가 5인 배열 arr을 선언하시오.
그리고 int 형식의 포인터 변수 p를 선언하고 arr로 초기화하시오.
int 형식의 변수 index를 선언하시오.

b. index 변수를 0에서 5보다 작을 동안 순차적으로 증가하면서 다음을 수행하시오.
포인터 변수 p와 index 의 더하기 연산한 결과에 간접 연산을 이용하여 배열의 각 원소에 1, 2, 3, 4, 5의 값을 설정

c. index 변수를 0에서 5보다 작을 동안 순차적으로 증가하면서 다음을 수행하시오.
배열 이름 arr과 index의 더하기 연산한 결과에 간접 연산을 이용하여 배열의 각 원소 값을 출력

더보기
//int 형식을 원소로 하고 원소 개수가 5인 배열 arr을 선언
//int 형식의 포인터 변수 p를 선언하고 arr로 초기화
//int 형식의 변수 index를 선언
//index 변수를 0에서 5보다 작을 동안 순차적으로 증가하면서 다음을 수행
//포인터 변수 p와 index 의 더하기 연산한 결과에 간접 연산을 이용하여 배열의 각 원소에 1, 2, 3, 4, 5의 값을 설정
//index 변수를 0에서 5보다 작을 동안 순차적으로 증가하면서 다음을 수행
//배열 이름 arr과 index의 더하기 연산한 결과에 간접 연산을 이용하여 배열의 각 원소 값을 출력
 
#include <stdio.h>
int main(void)
{
    int arr[5];
    int *p = arr;
    int index;
    for (index = 0; index < 5; index++)
    {
        *(p + index) = index + 1;
    }
 
    for (index = 0; index < 5; index++)
    {
        printf("%d ", *(arr + index));
    }
    printf("\n");
    
    return 0;
}

▷실행 결과

1 2 3 4 5

3. 아래와 같이 10명의 성적을 기억하는 배열 scores를 선언하였다.
int scores[10]={90,38,45,67,98,87,88,20,98,85};
그리고 다음과 같이 포인터 변수 p를 scores로 초기화하였다.
int *p = scores;
반복문과 조건문과 포인터 변수와 더하기 연산, 간접 연산을 이용하여 최대값이 있는 원소가 몇 번째 원소인지 판단하는 코드를 작성하시오.

더보기
//int scores[10] = { 90,38,45,67,98,87,88,20,98,85 };
//int *p = scores;
//반복문과 조건문과 포인터 변수와 더하기 연산, 간접 연산을 이용
//최대값이 있는 원소가 몇 번째 원소인지 판단하는 코드
#include <stdio.h>
int main(void)
{
    int scores[10] = { 90,38,45,67,98,87,88,20,98,85 };
    int *p = scores;
    int *max_p;
    int *last_p1 = scores + 10;
 
    max_p = p;//최대값이 있는 위치를 맨 앞으로 설정
    p++;
    for (p++; p < last_p1; p++)
    {
        if (*p > *max_p)//p가 가리키는 곳이 더 크면
        {
            max_p = p;//최대값이 있는 위치를 변경
        }
    }
 
    printf("최대값이 있는 위치는 %d번째\n", max_p - scores + 1);
 
    return 0;
}

▷실행 결과

최대값이 있는 위치는 5번째

50. 인덱스 연산자

C언어에서는 포인터와 정수 사이의 더하기 연산 후에 간접 연산을 수행하는 것을 간단하게 인덱스 연산으로 표현할 수 있어요.

인덱스 연산자는 배열 선언에 사용하는 지시 연산자 [ ]와 기호가 같아요.
선언문 이외에서 포인터와 정수가 피연산자인 이항 연산자 [ ]를 인덱스 연산자라 불러요.

인덱스 연산은 상대적 거리에 있는 원소를 접근할 수 있어요.
인덱스는 상대적 거리를 나타내기 때문에 n 번째 원소에 접근하려면 n-1을 사용해야 한답니다.

int arr[3]={1,2,3};
int *p = arr;
int index = 0;

위와 같이 배열과 포인터, 기본 형식을 선언하였을 때 arr[index]와 p[index]와 같이 사용하면 *(arr+index), *(p+index)와 같은 표현이예요.
인덱스 연산은 간접 연산보다 간단해서 배열의 각 원소에 접근할 때 자주 사용한답니다.

◈ 인덱스 연산을 사용한 예

#include <stdio.h>
int main()
{
    int arr[3]={1,2,3};
    int *p = arr;
    int index = 0;
 
    printf("Test 1\n");
    for(index = 0; index < 3; index++)
    {
        printf("index: %d, 값: %d\n", index, arr[index]);
    } 
    printf("Test 2\n");
    for(index = 0; index < 3; index++)
    {
        printf("index: %d, 값: %d\n", index, p[index]);
    }
    return 0;
}

◈ 실행 결과

Test 1
index: 0, 값: 1
index: 1, 값: 2
index: 2, 값: 3
Test 2
index: 0, 값: 1
index: 1, 값: 2
index: 2, 값: 3

◈ 기본 연습
1. 4 명의 국어 성적을 관리할 수 있는 배열을 선언하시오.
그리고 첫번째 학생은 90점, 두번째 학생은 85, 세번째 학생은 69, 네번째 학생은 75점으로 대입합니다.
네 명의 국어 성적과 합계, 평균을 출력하는 프로그램을 작성하시오.

더보기
//4 명의 국어 성적을 관리할 수 있는 배열을 선언하시오.
//그리고 첫번째 학생은 90점, 두번째 학생은 85, 세번째 학생은 69, 네번째 학생은 75점으로 대입
//네 명의 국어 성적의 합계, 평균을 출력하는 프로그램을 작성하시오
 
 
#include <stdio.h>
#define MAX_STUDENT 4
 
int main(void)
{
    int scores[MAX_STUDENT];    
    int ni;
    int sum=0;
    
    scores[0] = 90;
    scores[1] = 85;
    scores[2] = 69;
    scores[3] = 75;
 
    for (ni = 0; ni < MAX_STUDENT; ni++)
    {
        sum += scores[ni];        
    }
    printf("합계: %d , 평균: %.2f\n", sum, sum / (double)MAX_STUDENT);
    return 0;
}

▷실행 결과

합계: 319 평균: 79.75

2. 한 명의 국어, 영어, 수학 성적을 관리하는 프로그램을 작성하시오.
a. 국어, 영어, 수학 성적은 초기값을 -1로 설정합니다.
b. 프로그램은 다음을 반복합니다.
b.1 “I: 추가 D: 삭제 S:검색 L:전체 출력 E:종료”를 화면에 출력
b.2 하나의 문자를 입력받음
b.3 입력받은 문자가 ‘i’ 혹은 ‘I’일 때는 추가, ‘d’ 혹은 ‘D’일 때는 삭제 , ‘s’ 혹은 ‘S’일 때는 검색,
‘l’, ‘L’일 때는 전체 출력을 수행합니다.
* 추가 기능에서는 과목을 선택한 후에 성적을 입력받아 해당 과목의 성적을 설정합니다.
*삭제 기능에서는 과목을 선택한 후에 선택한 과목의 성적을 -1로 설정합니다.
*검색 기능에서는 과목을 선택한 후에 선택한 과목의 성적을 출력합니다.
*전체 출력에서는 모든 과목의 성적을 출력합니다.
(단, E를 누르면 프로그램을 종료)

더보기
//한 명의 국어, 영어, 수학 성적을 관리하는 프로그램을 작성하시오.
//a.국어, 영어, 수학 성적은 초기값을 - 1로 설정합니다.
//b.프로그램은 다음을 반복합니다.
//b.1 “I: 추가 D : 삭제 S : 검색 L : 전체 출력 E : 종료”를 화면에 출력
//b.2 하나의 문자를 입력받음
//b.3 입력받은 문자가 ‘i’ 혹은 ‘I’일 때는 추가, ‘d’ 혹은 ‘D’일 때는 삭제, ‘s’ 혹은 ‘S’일
//때는 검색,
//‘l’, ‘L’일 때는 전체 출력을 수행합니다.
//* 추가 기능에서는 과목을 선택한 후에 성적을 입력받아 해당 과목의 성적을 설정합니다.
//*삭제 기능에서는 과목을 선택한 후에 선택한 과목의 성적을 - 1로 설정합니다.
//*검색 기능에서는 과목을 선택한 후에 선택한 과목의 성적을 출력합니다.
//*전체 출력에서는 모든 과목의 성적을 출력합니다.
//(단, E를 누르면 프로그램을 종료) 
#include <stdio.h>
enum SubjectType
{
    KOREAN, ENGLISH, MATH, MAX_SUBJECT
};
 
int main(void)
{
    int scores[MAX_SUBJECT] = { -1,-1,-1 };
    const char *titles[MAX_SUBJECT] = { "국어","영어","수학" };
    char key = '\0';
    int si, s;
    while (key != 'E')
    {
        printf("I:추가 D:삭제 S:검색 L:전체 출력 E:종료\n");
        printf("메뉴 선택:");
        scanf_s(" %c", &key, 1); //엔터를 스킵하기 위해 앞에 공백을 포함
        switch (key)
        {
        case 'i': case 'I':
            for (si = 0; si < MAX_SUBJECT; si++)
            {
                printf("%s:%d ", titles[si], si + 1);
            }
            printf("과목 입력:");
            scanf_s("%d", &s);
            if ((s <= 0) || (s>MAX_SUBJECT))
            {
                printf("잘못 선택하셨네요.");
            }
            else
            {
                printf("성적 입력:");
                scanf_s("%d", scores + s - 1);
                if ((scores[s - 1] < 0) || (scores[s - 1]>100))
                {
                    scores[s - 1] = -1;
                }
            }
            break;
        case 'd': case 'D':
            for (si = 0; si < MAX_SUBJECT; si++)
            {
                printf("%s:%d ", titles[si], si + 1);
            }
            printf("과목 입력:");
            scanf_s("%d", &s);
            if ((s <= 0) || (s>MAX_SUBJECT))
            {
                printf("잘못 선택하셨네요.");
            }
            else
            {
                scores[s - 1] = -1;
            }
            break;
        case 's': case 'S': 
            for (si = 0; si < MAX_SUBJECT; si++)
            {
                printf("%s:%d ", titles[si], si + 1);
            }
            printf("과목 입력:");
            scanf_s("%d", &s);
            if ((s <= 0) || (s>MAX_SUBJECT))
            {
                printf("잘못 선택하셨네요.");
            }
            else
            {
                printf("%d\n",scores[s - 1]);
            }
            break;
        case 'l': case 'L': 
            for (si = 0; si < MAX_SUBJECT; si++)
            {
                if (scores[si] != -1)
                {
                    printf("%s과목 성적:%d\n", titles[si], scores[si]);
                }
                else
                {
                    printf("%s과목 성적:입력하지 않았음\n", titles[si]);
                }
            }
            break;
        case 'E': break;
        default: printf("잘못 선택하셨네요.\n");  break;
        }
 
    }
    return 0;
}

코드를 보시면 같은 작업을 계속 작성하는 부분을 알 수 있습니다.
이처럼 같은 코드를 중복하는 것은 유지 보수 비용을 많이 들게 합니다.
이러한 부분들은 함수를 사용하여 구조적으로 프로그래밍하여 개선할 수 있습니다.

3.10 개의 정수를 입력받아 크기 순으로 정렬하시오.

더보기
//10 개의 정수를 입력받아 크기 순으로 정렬
#include <stdio.h>
int main(void)
{
    int arr[10];
    int i,j;
    
    
    printf("10개의 정수를 입력하세요.\n");
    //입력
    for (i = 0; i < 10; i++)
    {
        printf("%d 번째:",i+1);
        scanf_s("%d", arr + i);
    }
 
    //순차 정렬
    for (i = 0; i < 10; i++)
    {
        for (j = i; j < 10; j++)
        {
            if (arr[i] > arr[j])//arr[i]가 arr[j]보다 크면
            {
                //두 수 교환
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    
    //출력
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}