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

Part 14. 배열과 포인터 그리고 산술 연산 [디딤돌 C언어]

언휴 2024. 1. 11. 09:28

Part 14. 배열과 포인터 그리고 산술 연산 

45. 배열과 포인터 개요

 

배열과 포인터 개요 및 배열 선언문 동영상 강의

C언어에서는 같은 형식의 여러 데이터를 효과적으로 사용할 수 있는 배열을 제공하고 있어요.

C언어에서 제공하는 기본 형식들은 변수가 관리하는 데이터가 하나여서 변수 이름은 갖고 있는 값을 의미하죠.
하지만 배열은 관리하는 데이터가 여러 개이므로 배열 이름이 특정 값을 대표할 수 없어요.
C언어에서는 배열 이름은 관리하는 특정 값이 아닌 배열에 할당한 메모리의 시작 주소를 의미한답니다.

배열 이름은 할당한 메모리 시작 주소를 의미
배열 이름은 할당한 메모리 시작 주소를 의미

배열의 원소에 접근할 때는 배열 이름과 상대적 거리를 의미하는 인덱스 값을 사용하는 것이 일반적이예요.
예를 들어 3번째 원소를 접근하려면 시작 위치에서 거리 2이므로 인덱스 2를 사용해요.
따라서 상대적 거리를 나타낼 때 첫 번째 원소는 상대적 거리가 0이므로 n번째 원소는 상대적 거리가 n-1임을 잊지 마세요.

배열의 인덱스는 상대적 거리를 의미하여 0부터 시작
배열의 인덱스는 상대적 거리를 의미하여 0부터 시작

또한 C언어에서는 프로그램의 메모리 주소를 값으로 갖는 포인터 형식을 제공하고 있어요.

실제 개발자에게 프로그램의 메모리 주소가 얼마인지를 아는 것은 큰 의미가 없겠죠.
하지만 C언어에서는 포인터(배열 이름도 포함)를 피연산자로 하는 몇 가지 연산자들이 있어요.
이를 이용하면 포인터 변수가 갖고 있는 메모리 주소의 데이터를 얻어오거나 원하는 값으로 설정할 수 있답니다.

특히 배열 이름도 관리하는 메모리의 시작 주소를 의미하므로 프로그램에서 포인터로 취급하고 있어요.
여러분이 C언어의 배열을 효과적으로 사용하려면 배열 이름이 메모리 주소를 의미하여 포인터 취급한다는 것을 잊지 마세요.
그리고 포인터와 관련하는 연산자를 잘 알고 있어야 할 거예요.

46. 배열 선언문

C언어에서 배열을 선언할 때 컴파일러에게 선언하는 것이 배열임을 알려주는 [ ]지시 연산자를 사용해요.
포인터를 선언할 때는 포인터임을 알려주는 * 지시 연산자를 사용해요.

배열을 선언하려면 컴파일러에게 선언하는 것이 배열임을 알려주는 [ ]지시 연산자 내부에 원소 개수를 지정하세요.
그리고 배열 이름과 지시 연산자, 원소 개수를 제외한 나머지 부분이 원소 형식이예요.
포인터도 변수 이름과 지시 연산자를 제외한 나머지 부분이 원소 형식이죠.

int arr[10]; //배열 선언문, 원소 개수:10, 원소 형식: int
int *p = arr; //포인터 선언문, 원소 형식: int

int arr[10]; 구문은 원소 형식이 int 이고 원소 개수가 10인 배열 arr을 나타내는 선언문이예요.
컴파일러는 int 형식의 메모리 크기 X 원소 개수의 메모리를 할당하고 배열명은 할당한 메모리 주소를 의미한답니다.
즉 배열명은 첫 번째 원소의 메모리 주소를 의미하여 int 형식의 포인터 상수처럼 사용할 수 있어요.

배열을 선언할 때는 여러 개의 원소의 값을 지정할 수 있어요.
배열 선언문에서 초기값을 지정하려면 { } 연산자에 초기값을 콤마로 구분하여 나열하세요.
그리고 초기값을 지정하지 않은 멤버는 컴파일러가 0으로 초기화 해 준답니다.
주의할 사항은 배열 선언문에서 초기값을 지정하지 않았을 때는 초기화해 주지 않는다는 거예요.

int arr1[5]={1,2}; //int arr1[5]={1,2,0,0,0};과 같은 표현
char arr2[10]; //배열 선언문, 원소 개수:10, 원소 형식: char, 각 원소의 값은 쓰레기 값
int arr3[5]={1,2}; //int arr4[5]={1,2,0,0,0};과 같은 표현

그리고 배열을 선언하면서 초기화 구문을 사용하면 원소 개수를 나타내지 않을 수 있어요.
이 때는 초기화한 개수가 원소 개수가 되요.
하지만 명확하게 원소 개수를 지정하는 것이 프로그램의 논리적 버그를 줄일 수 있는 좋은 습관이예요.
그리고 배열은 컴파일 시점에 메모리 크기를 결정하여 동작 중에 배열의 크기를 바꿀 수 없어요.
만약 크기를 중간에 바꾸길 원하면 동적 메모리 할당을 사용하세요.
책의 뒷 부분에서 동적 메모리 할당은 배울거예요.

int arr3[]= {1,2,3}; //배열 선언문, 원소 개수: 3 (초기값을 지정한 개수), 원소 형식 int

◈ 배열의 초기화 예

#include <stdio.h>
int main()
{
    int arr1[4]={1,2};
    int arr2[4];
    int index = 0;
    for(index = 0; index<4; index++)
    {
        printf("arr1[%d]: %d\n",index, arr1[index]);
    }
    for(index = 0; index<4; index++)
    {
        printf("arr2[%d]: %d\n",index, arr2[index]);
    }
    return 0;
}

◈ 실행 결과

arr[0]: 1
arr[1]: 2
arr[2]: 0
arr[3]: 0
arr2[0]: -858993460 (의미없는 쓰레기 값임)
arr2[1]: -858993460 (의미없는 쓰레기 값임)
arr2[2]: 858993460 (의미없는 쓰레기 값임)
arr2[3]: 858993460 (의미없는 쓰레기 값임)

◈ 기본 연습 
1. 한 학생의 국어 성적, 영어 성적, 수학 성적을 관리하는 배열을 선언하세요.
그리고 초기값으로 국어 성적은 80, 영어 성적은 60, 수학 성적은 90으로 지정하고 각 원소의 값을 출력하세요.

더보기
//1. 한 학생의 국어 성적, 영어 성적, 수학 성적을 관리하는 배열을 선언하세요.
//그리고 초기값으로 국어 성적은 80, 영어 성적은 60, 수학 성적은 90으로 지정하고 
//각 원소의 값을 출력하세요.
#include <stdio.h>
enum SubjectType
{
    KOREAN, ENGLISH, MATH,MAX_SUBJECT
};
 
int main(void)
{
    int scores[MAX_SUBJECT] = { 80,60,90 };
    const char *titles[MAX_SUBJECT] = { "국어","영어","수학" };
    int si;
    for (si = 0; si < MAX_SUBJECT; si++)
    {
        printf("%s과목 성적:%3d\n", titles[si], scores[si]);
    }
    return 0;
}

2. 다음의 출력 결과를 예측하시오.

#include <stdio.h>
int main()
{
    int arr[10]={1};
    int index = 0;
 
    for(index = 0; index<10; index++)
    {
        printf("%d\n",arr[index]);
    }
    return 0;
}
더보기

1
0
0
0
0
0
0
0
0
0

 

3. 세 학생의 국어 성적, 영어 성적, 수학 성적을 관리하는 배열을 선언하세요.
그리고 세 명의 성적을 입력받아 과목별 합계와 평균을 출력하시오.
그리고 학생별로 합계와 평균을 출력하시오.

더보기
//세 학생의 국어 성적, 영어 성적, 수학 성적을 관리하는 배열을 선언
//그리고 세 명의 성적을 입력받아 과목별 합계와 평균을 출력
//그리고 학생별로 합계와 평균을 출력
 
#include <stdio.h>
#define MAX_STUDENT 3
enum SubjectType
{
    KOREAN, ENGLISH, MATH, MAX_SUBJECT
};
 
int main(void)
{
    int scores[MAX_STUDENT][MAX_SUBJECT];
    const char *titles[MAX_SUBJECT] = { "국어","영어","수학" };
    int ni,si;
    int sum;    
    for (ni = 0; ni < MAX_STUDENT; ni++)
    {
        printf("%d번째 학생 성적 입력\n", ni+1);
        for (si = 0; si < MAX_SUBJECT;si++)
        {
            printf("%s 성적 입력:", titles[si]);
            scanf_s("%d", scores[ni] + si);        
        }
    }
 
    for (ni = 0; ni < MAX_STUDENT; ni++)
    {        
        for (sum = 0, si = 0; si< MAX_SUBJECT; si++)
        {
            sum += scores[ni][si];
        }
        printf("%d번째 학생 성적 합계%d , 평균:%.2f\n", ni+1, sum, sum / (double)MAX_SUBJECT);
    }
    return 0;
}
 

47. 포인터 + 정수

C 언어에서 배열 이름은 관리하는 메모리의 시작 주소를 의미하죠.
프로그램에서 배열 이름은 포인터 상수로 취급해요.
따라서 배열을 효과적으로 사용하려면 포인터 관련 연산자를 잘 사용할 수 있어야 겠죠.

C 언어에서 + 연산에는 하나의 포인터 형식과 정수 형식을 피연산자가 오는 것을 허용해요.
이 때 연산 결과는 상대적 거리의 메모리 주소를 의미한답니다.

여기에서 말하는 상대적 거리는 예를 들어 알아볼게요.
int 형식 원소의 포인터 변수의 값이 1000일 때 상대적 거리 4는 1000번지에서 int 형식 원소 4개 크기를 더한 주소예요.
즉 int 형식의 메모리 크기가 4바이트이므로 1000번지에서 상대적 거리 4는 1016이예요.
물론 여기서 1016은 정수 형식이 아니고 포인터 형식이랍니다.

◈ 포인터 변수와 정수 형식 사이의 + 연산

#include <stdio.h>
int main()
{
   int *p = (int *) 1000;
 
    printf("before : %d\n",p);
    p = p+4;
    printf("after : %d\n",p);
 
    return 0;
}

◈ 실행 결과

before : 1000
after : 1016

배열 이름도 관리하는 메모리 시작 주소를 의미해서 프로그램에서는 원소 형식이 같은 포인터 상수로 취급하죠.
따라서 배열 이름도 정수 형식과 + 연산이 가능하며 상대적 거리에 있는 원소의 메모리 주소가 연산 결과예요.

◈ 배열 이름과 정수 형식 사이의 + 연산

#include <stdio.h>
int main()
{
    int arr[10];
    printf("arr:%d\n",arr);
    printf("arr+4:%d\n",arr+4);
    return 0;
}

◈ 실행 결과

arr : 3013304 (실행 환경에 따라 결과는 다를 수 있음)
arr+4 : 3013320 (앞에 출력한 결과에 16을 더한 값임)

배열 이름은 할당한 메모리 주소를 의미
포인터 + 정수는 상대적 거리를 의미
포인터 + 정수

◈ 기본 연습
1. char 형식을 원소로 하는 포인터 변수 p를 선언하고 1000으로 초기화하세요.
그리고 p에 4를 더한 후에 p가 얼마인지 출력하세요.

더보기

1004
char 형식의 크기는 1 바이트이므로 p+4를 하면 p에서 sizeof(char)*4를 더하므로 1004

//char 형식을 원소로 하는 포인터 변수 p를 선언하고 1000으로 초기화하세요.
//그리고 p에 4를 더한 후에 p가 얼마인지 출력하세요.
 
#include <stdio.h>
int main(void)
{
    char *p = (char *)1000;
    printf("%d\n", p + 4);
    return 0;
}

 

2. short 형식을 원소로 하는 포인터 변수 p를 선언하고 1000으로 초기화하세요.
그리고 p에 4를 더한 후에 p가 얼마인지 출력하세요.

더보기

1008
short 형식의 크기는 2 바이트이므로 p+4를 하면 p에서 sizeof(short)*4를 더하므로 1008

//short  형식을 원소로 하는 포인터 변수 p를 선언하고 1000으로 초기화하세요.
//그리고 p에 4를 더한 후에 p가 얼마인지 출력하세요.
 
#include <stdio.h>
int main(void)
{
    short  *p = (short  *)1000;
    printf("%d\n", p + 4);
    return 0;
}

3. int 형식을 원소로 하며 원소 개수가 10인 배열 arr을 선언하세요.
int 형식을 원소로 하는 포인터 변수 p를 선언하고 arr+5로 초기화하세요.
arr과 p의 값을 출력하세요.

더보기

12188244 12188264 (실행할 때마다 다릅니다. 그렇지만 두 수의 차이는 20입니다.)

//int 형식을 원소로 하며 원소 개수가 10인 배열 arr을 선언
//int 형식을 원소로 하는 포인터 변수 p를 선언하고 arr + 5로 초기화
//arr과 p의 값을 출력
#include <stdio.h>
int main(void)
{
    int arr[10];
    int *p = arr + 5;
    printf("%d %d\n", arr, p);
    return 0;
}

48. 포인터와 – 연산

C 언어에서는 같은 원소의 포인터 사이의 – 연산을 제공하죠.

연산 결과는 정수 형식으로 두 포인터 사이의 원소 개수예요.
예를 들어 int 형식 변수 a의 값이 1016이고 int 형식 변수 b의 값이 1000일 때 a-b의 결과는 4랍니다.

◈ 포인터 형식 사이의 – 연산

#include <stdio.h>
int main()
{
    int *a = (int *)1016;
    int *b = (int *)1000;
    printf("a:%d b:%d a-b:%d\n",a,b,a-b);
    return 0;
}

◈ 실행 결과

a:1016 b:1000 a-b:4

◈ 배열 이름과 포인터 형식 사이의 – 연산

#include <stdio.h>
int main()
{
    int arr[10];
    int *p = arr+4;
    printf("arr:%d p:%d p-arr:%d\n", arr, p, p-arr);
    return 0;
}

◈ 실행 결과

a:1242912 p:1242928 p-arr:4

포인터 - 포인터는 상대적 거리
포인터 - 포인터는 상대적 거리