과목의 개수를 저장할 변수, 평균을 저장할 변수, 점수 중 최댓값을 저장할 변수를 선언하고 점수들을 저장할 공간의 포인터를 선언하고, 과목의 개수를 입력받는다.

점수를 고치는 연산을 하며 소수가 될 수 있으므로 double 자료형 크기의 과목 개수만큼 저장할 수 있도록 메모리를 할당한다.

과목의 개수 만큼 반복하면서 최댓값을 찾고, 또 과목의 개수만큼 반복하면서 각 점수를 최댓값을 사용하여 문제에서 주어진 식으로 연산하여 저장한다.

연산한 점수들을 평균을 저장할 avg변수에 누적하여 저장하고, avg를 과목의 개수로 나누어 평균을 구한다.

메모리를 해제하고, 구한 평균을 출력한다.

정답 코드는 다음과 같다.

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
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int subNum = 0;
    double avg = 0, big = 0;
    double *subArr;
    
    scanf("%d"&subNum);
    
    subArr = (double*)malloc(sizeof(double* subNum);
    
    for(int i = 0; i < subNum; i++)
    {
        int tmp;
        scanf("%d"&tmp);
        subArr[i] = double(tmp);
        
    }
 
    for(int i = 0; i < subNum; i++)
    {
        if(subArr[i] > big)
            big = subArr[i];
    }
 
    for(int i = 0; i < subNum; i++)
    {
        subArr[i] = subArr[i]/big*100;
    }
 
    for(int i = 0; i < subNum; i++)
    {
        avg += subArr[i];
    }
    avg = avg / subNum;
    
    free(subArr);
    printf("%lf", avg);
    return 0;
}
cs

크기가 9인 int형 배열을 선언하고, 그 배열을 포인터에 저장한다.

최댓값과 최댓값의 위치를 저장할 변수를 선언하고 0으로 초기화한다.

반복문으로 9번 반복하면서 포인터에 저장된 주솟값으로 배열에 값을 저장한다.

반복문으로 0부터 9까지 반복하면서 각 인덱스가 max보다 큰지 비교하고, 크면 max에 그 값을 저장하고, loc에 그 인덱스를 저장한다.

반복문이 끝나면 max값과 인덱스는 0부터 시작하기 때문에 loc + 1을 출력한다.

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
int main()
{
    int arr[9];
    int *numList = arr;
    int max = 0, loc = 0;
    
    for(int i = 0; i < 9; i++)
    {
        scanf("%d"&numList[i]);
    }
    for(int i = 0; i < 9; i++)
    {
        if(numList[i] > max)
        {
            max = numList[i];
            loc = i;
        }
    }
    
    printf("%d\n%d", max, loc+1);
    return 0;
}
cs

테스트 케이스의 개수를 입력받고, 개수 만큼 반복한다.

반복문 안에는 char 자료형 크기 80개를 저장할 수 있는 메모리 공간을 할당하고, 그 공간에 문자열을 입력받는다.

점수를 누적할 변수 sum과 점수를 다음 추가할 점수를 저장할 변수 score를 만들고 sum은 0으로, score는 1로 초기화 한다.

입력받은 문자열의 길이 만큼 반복하여 'O'이면 score의 값을 sum에 누적시키고 score의 값을 1 증가시킨다. 'X'면 score의 값을 1로 초기화 한다.

입력 받은 문자열의 개수는 string.h헤더파일의 strlen()함수를 이용하면 구할 수 있다.

정답은 다음 코드와 같다.

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
31
32
33
34
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
 
int main()
{
    int cnt;
    scanf("%d",&cnt);
    
    for(int i=0; i<cnt; i++)
    {
        char *result = malloc(sizeof(char* 80);
        int score = 1,sum = 0;
        scanf("%s",result);
        
        for(int j=0; j < strlen(result); j++)
        {
            if(result[j]=='O')
            {
                sum += score;
                score++;
            }
            else
            {
                score = 1;
            }
        }
        printf("%d\n",sum);
        free(result); 
    }
    
    return 0;
}
 
cs

금지 키워드로 [ 가 설정되어 있으므로 인덱스 기능을 이용해서는 풀지 못하고, 역참조 연산자를 사용해야 한다.

(char 자료형 크기 * 100) 크기의 메모리를 할당한 포인터를 생성하고 할당한 메모리 공간에 문자열을 입력받는다.

부분 문자 시작 위치와 끝 위치로 사용할 변수 a,b를 선언하고 입력받는다.

반복문으로 a-1부터 b보다 작을때 까지 반복하여 역참조 연산자를 사용하여 문자를 출력한다.

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
 
int main() 
{
    char *sPtr = malloc(sizeof(char* 100);
    scanf("%s", sPtr);
    
    int a, b;
    scanf("%d %d"&a, &b);
    
    for(int i = a-1; i < b; i++)
    {
        printf("%c"*(sPtr + i));    // 역참조 연산자 
    }
    return 0;
}
cs

Unit 37. 2차원 배열 사용하기

2차원 배열은 다음과 같이 가로 x 세로 의 형태로 되어 있는 평면 구조이며 행과 열 모두 0부터 시작한다.

37.1 2차원 배열을 선언하고 요소에 접근하기

2차원 배열은 [](대괄호)를 두번 사용하여 선언하며 첫 번째 대괄호는 세로크기, 두 번째 대괄호는 가로 크기를 지정한다.

2차원 배열을 선언하면서 초기화 하려면 { }(중괄호)를 사용하는데, 다음과 같이 가로 요소들을 먼저 묶고, 가로 줄을 세로 크기만큼 다시 묶는다.

int numArr[3][4] = {
    { 가로 요소 4개 },
    { 가로 요소 4개 },
    { 가로 요소 4개 }
};

{ }를 사용라여 배열에 값을 할당하는 방법은 배열을 선언할 때만 사용할 수 있으며 이미 선언한 배열에는 사용할 수 없다.

2차원 배열의 요소에 접근하려면 배열 뒤에 [ ](대괄호)를 두 번 사용하여 안에 세로와 가로 인덱스를 지정하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    int numArr[3][4= {
        {1,2,3,4},
        {5,6,7,8},
        {9,10,11,12}
    };
 
    printf("%d\n", numArr[0][0]);
    printf("%d\n", numArr[1][2]);
    printf("%d\n", numArr[2][0]);
    printf("%d\n", numArr[2][3]);
    return 0;
}
cs

세로크기가 3, 가로크기가 4인 int형 2차원 배열을 선언하고, 값을 초기화 했다.

2차원 배열도 가로, 세로 인덱스 모두 0부터 시작한다.

배열을 초기화 할때 가로 요소를 중괄호로 묶지 않아도 되지만 알아보기가 힘들어 잘 사용하진 않는다.

37.2 2차원 배열을 초기화하기

2차원 배열은 다음 코드로 모든 요소를 0으로 초기화 할 수 있다.

int numArr[3][4] = { 0, };

37.3 2차원 배열의 요소에 값 할당하기

다음 코드와 같이 값을 초기화 하지 않은 배열의 요소에 값을 할당할 수 있다.

int numArr[3][4];

numArr[0][1] = 5;
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
#include <stdio.h>
 
int main()
{
    int numArr[3][4];
 
    numArr[0][0= 11;
    numArr[0][1= 22;  
    numArr[0][2= 33;    
    numArr[0][3= 44;  
 
    numArr[1][0= 55;   
    numArr[1][1= 66;  
    numArr[1][2= 77;   
    numArr[1][3= 88;   
 
    numArr[2][0= 99;   
    numArr[2][1= 110;  
    numArr[2][2= 121;   
    numArr[2][3= 132;  
 
    printf("%d\n", numArr[-1][-1]);   
    printf("%d\n", numArr[0][4]);     
    printf("%d\n", numArr[4][0]);  
    printf("%d\n", numArr[5][5]);     
 
    return 0;
}
cs

2차원 배열도 범위를 벗어난 인덱스에 접근하여 출력하면 쓰레기값이 출력된다. 배열의 범위를 벗어난 인덱스에 접근하면 배열이 아닌 다른 메모리 공간에 접근하게 된다. 또한 [0][4]처럼 가로 인덱스만 범위를 벗어나도록 지정하면 그 다음 세로 인덱스 요소인 [1][0]에 접근된다.

37.4 2차원 배열의 크기 구하기

2차원 배열의 전체공간, 가로, 세로 요소의 개수는 다음과 같이 sizeof 연산자를 이용하여 구할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
int main()
{
    int numArr[3][4= {    
        { 11223344 },
        { 55667788 },
        { 99110121132 }
    };
 
    printf("%d\n"sizeof(numArr));  
 
    int col = sizeof(numArr[0]) / sizeof(int);                                    
    int row = sizeof(numArr) / sizeof(numArr[0]); 
 
    printf("%d\n", col);   
    printf("%d\n", row);   
    return 0;
}
cs

sizeof로 2차원 배열의 크기를 구하면 전체 공간을 구할 수 있다. 위에서는 4바이트 요소가 8개 있으므로 48이 나온다.

가로의 요소 개수는 가로 한 줄의 크기를 요소 자료형의 크기로 나누면 된다.

세로의 요소 개수는 배열의 전체 공간을 가로 한 줄의 크기로 나누면 된다.

37.5 반복문으로 2차원 배열의 요소를 모두 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
int main()
{
    int numArr[3][4= {    
        { 11223344 },
        { 55667788 },
        { 99110121132 }
    };
 
    int col = sizeof(numArr[0]) / sizeof(int);  
    int row = sizeof(numArr) / sizeof(numArr[0]);
 
    for (int i = 0; i < row; i++)    
    {
        for (int j = 0; j < col; j++)    
        {
            printf("%d ", numArr[i][j]); 
        }
        printf("\n");                
    }
 
    return 0;
}
cs

배열의 세로와 가로 요소의 개수를 구하고, 2중 for문으로 세로부터 반복하고, 가로를 반복한다. 2차원 배열의 인덱스로는 세로에는 바깥쪽 반복문의 i를, 가로에는 안쪽 반복문의 j를 넣어 배열의 요소를 순서대로 접근하여 출력할 수 있다. 안쪽 반복문이 끝나면 개행문자를 출력함으로 세로줄이 바뀔때 줄을 바꿔 출력한다.

다음 코드와 같이 역순으로도 출력할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
int main()
{
    int numArr[3][4= {    
        { 11223344 },
        { 55667788 },
        { 99110121132 }
    };
 
    int col = sizeof(numArr[0]) / sizeof(int);  
    int row = sizeof(numArr) / sizeof(numArr[0]);
 
    for (int i = row - 1; i >= 0; i--)    
    {
        for (int j = col-1; j >= 0; j--)    
        {
            printf("%d ", numArr[i][j]); 
        }
        printf("\n");                
    }
 
    return 0;
}
cs

세로와 가로의 마지막 인덱스부터 0까지 인덱스를 1씩 감소시키면서 출력하면 된다.

37.6 2차원 배열을 포인터에넣기

2차원 배열을 포인터에 담으려면 가로의 크기를 알아야 한다. 다음과 같이 포인터를 선언할 때 *과 포인터 이름을 괄호로 묶은 뒤 []에 가로 크기를 지정하면 된다.

int (*numPtr)[4];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int numArr[3][4= {    
        { 11223344 },
        { 55667788 },
        { 99110121132 }
    };
    int (*numPtr)[4= numArr;
 
    printf("%p\n"*numPtr);
    printf("%p\n"*numArr);
    printf("%d\n", numPtr[1][2]);
    printf("%p\n"sizeof(numArr));
    printf("%p\n"sizeof(numPtr));
    return 0;
}
cs

위 코드와 같이 2차원 배열을 포인터에 할당할 수 있다.

2차원 배열을 포인터에 할당한 뒤 포인터를 역참조 해보면 세로 첫번째 요소의 주솟값이 나온다. 배열을 역참조해도 같은 값이나온다.

2차원 배열 포인터도 대괄호를 두 번 사용하여 배열의 요소에 접근할 수 있다.

sizeof로 크기를 계산하면 배열은 메모리가 차지하는 전체 공간이 출력되지만, 포인터는 포인터의 크기만 출력된다.

37.7 퀴즈

정답은 c이다.

정답은 d이다.

정답은 c이다.

정답은 b,d,e이다.

37.8 연습문제 : 행렬의 주대각선 성분 구하기

정답은 다음 코드와 같다.

for (int i = 0; i < sizeof(matrix) / sizeof(matrix[0]); i++)
{
    printf("%d ", matrix[i][i]);
}

인덱스 값이 (0,0), (1,1) ... 처럼 가로 세로 모두 1씩 증가하기 때문에 0~7까지 반복한 값을 가로, 세로 인덱스에 넣으면 된다.

37.9 심사문제 : 전치 행렬 구하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
int col = sizeof(matrix[0]) / sizeof(int);  
int row = sizeof(matrix) / sizeof(matrix[0]);
 
for (int i = 0; i < row; i++)    
{
    for (int j = 0; j < col; j++)    
    {
        printf("%d ", matrix[j][i]); 
    }
        printf("\n");                
}
cs

행과 열이 서로 바뀌어있기 때문에 2차원 배열을 출력할 때 세로에 i를 넣고, 가로에 j를 넣으면 된다.

Unit 38. 포인터와 배열 응용하기

다음과 같이 프로그램 실행 중 원하는 크기 만큼 배열을 생성하는 기능은 gcc에서는 지원하지만 Viual Studio에서는 지원하지 않아 컴파일 에러가 발생한다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main()
{
    int size;
    scanf("%d"&size);  
 
    int numArr[size];    // 입력값을 배열의 크기로 사용
    return 0;
}
cs

컴파일 에러 없이 배열의 크기를 동적으로 지정하려면 포인터를 선언하고 메모리를 할당한 뒤 메모리를 배열처럼 사용해야 한다.

38.1 포인터에 할당된 메모리를 배열처럼 사용하기

포인터에 malloc 함수로 메모리를 할당하여 포인터를 배열처럼 사용할 수 있다.

자료형 *포인터이름 = malloc(sizeof(자료형) * 크기);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int *numPtr = malloc(sizeof(int* 10);
    numPtr[0= 10;
    numPtr[9= 20;
 
    printf("%d\n", numPtr[0]);
    printf("%d\n", numPtr[9]);
    free(numPtr);
    return 0;
}
cs

위 코드와 같이 int 크기에 10을 곱하여 동적으로 메모리를 할당하여 배열처럼 사용할 수 있다. 배열 처럼 대괄호 안데 인덱스를 지정하여 값을 저장하거나 가져올 수 있다.

배열은 한번 선언하면 끝이지만 위와 같은 포인터는 malloc함수로 메모리를 할당했기 때문에 free함수로 해제해야 한다.

*numPtr처럼 역참조 한것과 numPtr[0]으로 0번 인덱스를 가져오는 것은 같은 값을 가져오고, *(numPtr + 1)은 numPtr[1]과 같은 값을 가져오게 된다. 포인터에 값을 더하는 방식을 포인터 연산 이라고 한다.

38.2 입력한 크기만큼 메모리를 할당하여 배열처럼 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int size;
    scanf("%d"&size);
 
    int *numPtr = malloc(sizeof(int* size);
 
    for(int i = 0; i < size; i++)
    {
        numPtr[i] = i;
    }
    for(int i = 0; i < size; i++)
    {
        printf("%d\n", numPtr[i]);
    }
    free(numPtr);
    return 0;
}
cs

scanf로 크기를 입력받고, int 크기에 입력받은 크기를 곱하여 메모리를 할당했다.

입력받은 크기 만큼 반복하며 값을 할당 하고, 입력받은 크기만큼 반복하며 값을 출력하였다.

사용이 끝나면 free함수로 할당한 메모리를 해제해야 한다.

38.3 포인터에 할당된 메모리를 2차원 배열처럼 사용하기

2차원 배열처럼 사용하기 위해 포인터에 할당하는 방법은 다음과 같다.

1. 자료형 **포인터이름 = malloc(sizeof(자료형 *) * 세로크기); 와 같이 세로 공간 메모리 할당

2. 반복문으로 반복하며 포인터[i] = malloc(sizeof(자료형) * 가로크기); 와 같이 가로 공간 메모리 할당

3. 반복문으로 반복하면서 free(포인터[i]); 와 같이 가로 공간 메모리 해제

4. 반복문으로 반복하면서 free(포인터); 와 같이 세로 공간 메모리 해제

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
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int **= malloc(sizeof(int ** 3);
 
    for(int i = 0; i < 3; i++)
    {
        m[i] = malloc(sizeof(int* 4);
    }
 
    m[0][0= 1;
    m[2][0= 5;
    m[2][3= 2;
 
    printf("%d\n", m[0][0]);
    printf("%d\n", m[2][0]);
    printf("%d\n", m[2][3]);
 
    for(int i = 0; i < 3; i++)
    {
        free(m[i]);
    }
    free(m);
    return 0;
}
cs

2중 포인터에 2차원 배열의 세로 공간에 해당하는 메모리를 할당한다. 이때 세로 공간에는 값이 들어가지 않고 가로 공간의 메모리 주소가 들어간다. 그래서 sizeof(int *)처럼 포인터의 크기를 구한 후 세로 크기 3을 곱하는 것이다.

이후 세로 크기 만큼 반복하며 2차원 배열의 가로 공간에 해당하는 메모리를 할당한다.

가로 공간은 int 자료형의 크기에 가로크기 4를 곱해주었다.

이중 포인터를 2차원 배열처럼 사용하도록 메모리를 할당하는 모습을 그림으로 표현하면 다음과 같다.

m은 pointer to pointer to int이므로 int **m 처럼 선언한다.

2차원 배열을 사용하는 것처럼 [ ] [ ]에 세로인덱스, 가로인덱스를 지정하여 값을 할당하거나 가져올 수 있다.

포인터를 다 사용하면 가로 공간에 해당하는 메모리부터 해제한다. 이후 세로 공간에 해당하는 메모리를 해제한다.

메모리를 할당할 때는 세로 - > 가로의 순서로 할당했으므로 해제할 때는 가로 -> 세로의 순서로 해제하는 것이다.

38.4 입력한 크기만큼 메모리를 할당하여 포인터를 2차원 배열처럼 사용하기

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
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int row, col;
    scanf("%d %d"&row, &col);
 
    int **= malloc(sizeof(int ** row);
    for(int i = 0; i < row; i++)
    {
        m[i] = malloc(sizeof(int* col);
    }
 
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            m[i][j] = i + j;
        }
    }
 
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            printf("%d ", m[i][j]);
        }
        printf("\n");
    }
 
    for(int i = 0; i < row; i++)
    {
        free(m[i]);
    }
    free(m);
    return 0;
}
cs

세로 크기와 가로 크기를 입력 받고, 입력받은 값을 활용하여 세로공간과 가로공간 메모리를 할당하였다.

세로, 가로 크기가 고정되어 있지 않으므로 입력값 row, col을 활용하여 반복하면서 값을 할당하고, 값 할당이 끝나면 다시 반복하면서 2차원 배열의 값을 출력한다.

사용이 모두 끝나면 입력받은 세로 크기만큼 반복하며 가로 공간 메모리를 해제하고, 이후 세로 공간 메모리를 해제한다.

38.5 퀴즈

정답은 d이다.

정답은 b이다.

38.6 연습문제 : 포인터에 할당된 메모리를 3차원 배열처럼 사용하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.
for(int depth = 0; depth < 2; depth++)
{
    m[depth] = malloc(sizeof(long long ** 3);
    
    for(int row = 0; row < 3; row++)
    {
        m[depth][row] = malloc(sizeof(long long* 5);
    }
}
 
// 2.
for(int depth = 0; depth < 2; depth++)
{
    for(int row = 0; row < 3; row++)
    {
        free(m[depth][row]);
    }
    free(m[depth]);
}
cs

3차원의 면에 해당하는 공간은 할당되어 있기 때문에 2중 for문으로 면의 개수만큼 반복하면서 세로공간에 해당하는 메모리를 할당하고, 안쪽 반복문에서 세로 크기만큼 반복하며 가로 공간에 해당하는 메모리를 할당하였다.

메모리를 해제할 때도 2차원 배열과 마찬가지로 할당의 역순으로 가로 -> 세로 -> 면 순서로 해제하면 된다.

38.7 심사문제 : 단위 행렬 만들기

정답은 다음 코드와 같다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int size;
    scanf("%d"&size);
 
    int **= malloc(sizeof(int ** size);
    for(int i = 0; i < size; i++)
    {
        m[i] = malloc(sizeof(int* size);
    }
 
    for(int i = 0; i < size; i++)
    {
        for(int j = 0; j < size; j++)
        {
            if(i == j)
            {
                m[i][j] = 1;
            }
            else
            {
                m[i][j] = 0;
            }
        }
    }
 
    for(int i = 0; i < size; i++)
    {
        for(int j = 0; j < size; j++)
        {
            printf("%d ", m[i][j]);
        }
        printf("\n");
    }
 
    for(int i = 0; i < size; i++)
    {
        free(m[i]);
    }
    free(m);
    return 0;
}
cs

가로 인덱스와 세로 인덱스가 같은 위치는 1이고, 다른 위치는 모두 0이므로 2차원 배열에 값을 할당하는 부분에서 if 조건문으로 해당 조건에 맞게 값을 저장하였다.

38.8 심사문제 : 지뢰찾기

정답은 다음 코드와 같다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int m, n;
    int cnt = 0;
    scanf("%d %d"&m, &n);
 
    // 메모리 할당
    char **matrix = malloc(sizeof(char ** m);
    for(int i = 0; i < m; i++)
    {
        matrix[i] = malloc(sizeof(char ** (n+1));
    }
 
    // 입력
    for(int i = 0; i < m; i++)
    {
        scanf("%s", matrix[i]);
    }
    
    for (int i = 0; i < m; i++
    {
        for (int j = 0; j < n; j++
        {
            if (matrix[i][j] == '.'
            {
                matrix[i][j] = '0';
            }
        }
    }
    for(int i = 0; i < m; i++)
    {
        for(int j = 0; j < n; j++)
        {
            // 지뢰일 경우
            if(matrix[i][j] == '*')
            {
                for(int y = i - 1; y < i + 2; y++)
                {
                    for(int x = j - 1; x < j + 2; x++)
                    {
                        if(y < 0 || x < 0 || y >= m || x >= n || matrix[i][j] == matrix[y][x] || matrix[y][x] == '*')
                        {
                            continue;
                        }
                        matrix[y][x] += 1;
                    }
                }
            }
        }
        
    }
    
    // 결과 출력
    for (int i = 0; i < m; i++) {
        for(int j = 0; j < n; j++)
        {
            printf("%c", matrix[i][j]);
        }
        printf("\n");
    }
    // 메모리 해제
    for (int i = 0; i < m; i++
    {
        free(matrix[i]);
    }
    free(matrix);
    return 0;
}
cs

행렬 크기와 행렬을 입력받고, 각 요소별로 반복하면서 '.' 인 경우 '0' 으로 바꿔 주었다.

각 요소별로 반복하면서 요소가 지뢰일 경우 그 요소를 둘러싸고 있는 요소들에 1씩 추가해 줬는데, 조건문을 사용하여 행렬을 벗어나지 않고, 둘러싸고 있는 요소가 지뢰가 아닐 경우에만 1씩 누적하였다.

반복문의 실행이 끝나면 다시 반복문으로 수정된 행렬의 결과를 출력하고 메모리를 해제하였다.

Unit 35. 메모리 사용하기

포인터에 원하는 만큼 메모리 공간을 할당받아 사용할 수 있다.

메모리는 malloc -> 사용 -> free의 패턴으로 사용한다.

35.1 메모리 할당하기

메모리를 사용하려면 malloc 함수로 사용할 메모리 공간을 확보해야 한다. 할당할 메모리 크기는 바이트 단위로 지정한다. malloc함수는 stdlib.h 헤더파일에 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int num1 = 20;
    int *numPtr1;
 
    numPtr1 = &num1;
 
    int *numPtr2;
    numPtr2 = malloc(sizeof(int));
 
    printf("%p\n", numPtr1);
    printf("%p\n", numPtr2);
    
    free(numPtr2);
    return 0;
}
cs

메모리를 할당할 때는 malloc 함수를 사용하여 할당할 메모리 공간의 크기를 넣어주는데, 위 코드에서는 sizeof 연산자를 이용하여 int크기 만큼의 즉 4바이트 크기의 메모리를 할당했다.

이처럼 원하는 시점에 원하는 메모리를 할당할 수 있어 동적 메모리 할당이라 부른다.

출력값은 같은 메모리 주소를 출력하는것 같지만 메모리 주소에는 내부적인 차이가 있다. 변수는 스택에 생성되며 malloc함수는 힙부분의 메모리를 사용한다.

스택에 생성된 변수는 사용한뒤 따로 처리를 하지 않아도 되지만 malloc함수를 사용하여 힙에 할당한 메모리는 반드시 해제를 해야 한다. free 함수를 이용하여 메모리를 해제할 수 있다.

메모리를 해제하지 않으면 시스템의 메모리가 부족해져 운영체제가 프로그램을 강제로 종료시키거나 메모리 할당에 실패할 수 있다. 메모리를 해제하지 않아 메모리 사용량이 계속 증가하는 현상을 메모리 누수(memory leak)라 한다.

35.2 메모리에 값 저장하기

다음은 할당한 메모리에 값을 저장하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int *numPtr;
    numPtr = malloc(sizeof(int));
    *numPtr = 10;
 
    printf("%d\n"*numPtr);
    free(numPtr);
    return 0;
}
cs

malloc 함수로 할당한 메모리에 값을 저장할 때는 포인터를 역참조한 뒤 값을 저장하면 된다. 값을 출력할 때도 역참조를 사용하면 된다. 메모리를 할당하고 사용한 뒤에는 반드시 free함수로 해제를 해 줘야 한다.

35.3 메모리 내용을 한꺼번에 설정하기

memset 함수를 사용하면 메모리의 내용을 원하는 크기 만큼 특정 값으로 설정할 수 있다. 설정하는 크기는 바이트 단위이며 이 함수는 string.h 헤더파일에 선언되어 있다. memset 함수는 다음과 같이 사용한다.

memset(포인터, 설정할값, 크기);

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
    long long *numPtr = malloc(sizeof(long long));
    memset(numPtr, 0x278);
 
    printf("0x%llx"*numPtr);
    free(numPtr);
    return 0;
}
cs

long long 자료형의 크기인 8바이트 만큼 동적 메모리를 할당하고, memset 함수로 numPtr이 가리키는 메모리에 16진수 27이 8개 들어가도록 한다.

memset함수는 다음과 같이 설정할 값을 모두 0으로 지정하여 메모리의 내용을 모두 0으로 만들 때 주로 사용한다.

memset(numPtr, 0, 8); 

35.4 널 포인터 사용하기

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    int *numPtr1 = NULL;  
 
    printf("%p\n", numPtr1); 
    return 0;
}
cs

위 코드와 같이 NULL이 들어있는 포인터를 널 포인터라고 하며 아무것도 가리키지 않는 상태이다. 역참조도 할 수 없다.

실무에서는 다음과 같이 포인터가 NULL인지 확인하고, NULL이면 메모리를 할당하는 방식을 주로 사용한다.

if (ptr == NULL)      
{
    ptr = malloc(1024);  
}

35.5 퀴즈

정답은 d이다.

정답은 c이다.

정답은 b이다.

정답은 a이다.

정답은 d이다.

정답은 널 포인터 이다.

35.6 연습문제 : 메모리 할당하기

정답은 다음과 같다.

1. malloc(sizeof(int));

2. malloc(sizeof(long long));

각각 4바이트와 8바이트 만큼의 메모리를 할당해 줘야 하기 때문이다.

35.7 심사문제 : 두 정수의 합 구하기

정답은 다음 코드와 같다.

1
2
int *numPtr1 = malloc(sizeof(int));
int *numPtr2 = malloc(sizeof(int));
cs

Unit 36. 배열 사용하기

배열은 같은 자료형의 변수를 일렬로 늘어놓은 형태이다.

반복문과 결합하면 연속적이고 반복되는 값을 손쉽게 처리할 수 있다.

36.1 배열을 선언하고 요소에 접근하기

배열은 변수 이름 뒤에 [](대괄호)를 붙인 뒤 크기를 설정한다. 배열을 선언하며 값을 초기화 할 때는 {}(중괄호)를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    printf("%d\n", numArr[0]);    
    printf("%d\n", numArr[5]);    
    printf("%d\n", numArr[9]);    
    return 0;
}
cs

크기가 10인 int형 배열을 선언하며 값을 할당하였다. 값을 할당할 때 {} 안의 값 개수는 배열의 크기보다 작아도 되만 크면 안된다. 또한 {}를 사용하여 값을 할당하는 것은 배열을 선언할 때만 사용할 수 있으며 이미 선언된 배열에서는 사용할 수 없다.

배열 값이 저장된 각각의 공간을 요소라고 하며 요소에 접근할 때는 배열 뒤에 [](대괄호)를 사용하고, 대괄호 안에 각 요소의 인덱스를 지정한다. 배열의 인덱스는 항상 0부터 시작한다.

배열을 선언하며 값을 초기화하면 다음과 같이 배열의 크기를 생략할 수 있다. 초기화를 하지 않고 생략하면 컴파일 에러가 발생한다.

int numArr1[] = { 11, 22, 33, 44, 55, 66, 77, 88, 99, 110 }; 

36.2 배열을 0으로 초기화하기

다음과 같이 한번에 배열의 모든 요소를 0으로 초기화 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    int numArr[10= {0, };    
 
    printf("%d\n", numArr[0]);    
    printf("%d\n", numArr[5]);    
    printf("%d\n", numArr[9]);    
    return 0;
}
cs

배열의 모든 요소를 한 번에 0으로 초기화하여 0을 일일이 나열하지 않아도 된다.

36.3 배열의 요소에 값 할당하기

배열은 []로 요소에 접근한 뒤 값을 할당할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main()
{
    int numArr[3];
 
    numArr[0= 1;
    numArr[1= 22;
    numArr[2= 8;
 
    printf("%d\n", numArr[0]); 
    printf("%d\n", numArr[1]); 
    printf("%d\n", numArr[2]);  
    return 0;
}
cs

인덱스는 0부터 시작하며 []에 인덱스를 지정한 뒤 값을 할당하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
 
int main()
{
    int numArr[3];
 
    numArr[0= 1;
    numArr[1= 22;
    numArr[2= 8;
 
    printf("%d\n", numArr[-1]); 
    printf("%d\n", numArr[5]); 
    printf("%d\n", numArr[10]);  
    return 0;
}
cs

위 코드와 같이 배열의 요소에 접근할 때 인덱스로 음수를 지정하거나, 배열의 크기를 벗어난 인덱스를 지정해도 컴파일에러는 발생하지 않지만, 쓰레기값을 출력한다. 배열의 범위를 벗어난 인덱스에 접근하면 배열이 아닌 다른 메모리 공간에 접근하여 엉뚱한 값을 출력하게 된다.

36.4 배열의 크기 구하기

반복문을 이용해서 배열의 요소를 모두 출력할 때 선언된 크기로 사용하면 나중에 배열의 크기를 늘려야 해서 코드를 수정해야 할 경우 반복문의 조건식도 수정해야 모든 배열이 출력될 수 있다. 이러한 불편함을 해결하려면 sizeof연산자를 사용하여 배열의 전체 요소의 개수를 구해 배열의 크기가 수정되면 반복문의 조건식에서도 알아서 계산되도록 할 수 있다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    printf("%d\n"sizeof(numArr));                  
    printf("%d\n"sizeof(numArr) / sizeof(int));                                                     
    return 0;
}
cs

sizeof연산자로 numArr의 크기를 구하면 4바이트인 int형 요소가 10개 있으므로 40이 나온다. 요소의 개수를 구하려면 배열의 크기에서 요소의 크기(sizeof(int))로 나눠주면 된다.

36.5 반복문으로 배열의 요소를 모두 출력하기

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    for (int i = 0; i < sizeof(numArr) / sizeof(int); i++)   
    {
        printf("%d\n", numArr[i]);   
    }
    return 0;
}
cs

반복문의 조건식에서 sizeof 연산자를 사용하여 배열의 요소 개수만큼 반복하였다.

변수 i는 변화식을 통해 1씩 증가하므로 인덱스에 i를 넣어서 모든 배열의 요소를 순서대로 출력할 수 있다.

다음과 같이 배열의 요소를 역순으로 출력할 수 도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };    
 
    for (int i = sizeof(numArr) / sizeof(int- 1; i >= 0; i--)  
    {
        printf("%d\n", numArr[i]);   
    }
    return 0;
}
cs

배열을 역순으로 출력할 때는 요소의 개수를 조건식에 바로 넣으면 인덱스는 0부터 시작하기 때문에 배열의 마지막 인덱스는 배열의 개수보다 1 작아서 반복문의 조건식에서도 요소의 개수에서 1 뺀 값에서 시작해야 한다. 조건식에서 0보다 크거나 같을 때 까지 반복하여 역순으로 출력할 수 있다.

36.6 배열의 요소 합계 구하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };
    int sum = 0;    
 
    for (int i = 0; i >= sizeof(numArr) / sizeof(int); i++)  
    {
        sum += numArr[i];
    }
    printf("%d\n", sum);
    return 0;
}
cs

위 코드와 같이 합을 저장할 변수를 만들고 배열의 요소만큼 반복하면서 배열의 각 요소의 값을 만든 변수에 누적시킨다. 저장할 변수를 만들때는 0으로 초기화 하지 않으면 쓰레기값이 들어간다.

36.7 배열의 요소에 저장된 값을 두 배로 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 }; 
 
    for (int i = 0; i < sizeof(numArr) / sizeof(int); i++
    {
        numArr[i] *= 2;
    }
 
    for (int i = 0; i < sizeof(numArr) / sizeof(int); i++
    {
        printf("%d\n", numArr[i]);
    }
    
    return 0;
}
cs

배열의 요소에 저장된 값을 두 배로 만드려면 배열의 요소에 접근하여 2로 곱한 뒤 다시 요소에 저장하면 된다. *= 연산자를 사용하고, 반복문으로 모든 요소에 접근하여 값을 두배로 변경하고 출력했다.

36.8 배열을 초인터에 넣기

배열은 첫 번째 요소의 주솟값만 담고 있다. 배열은 주솟값이기 때문에 포인터에 넣을 수 있다. 다음과 같이 포인터에 배열을 넣고 포인터에서 인덱스로 요소에 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int numArr[10= { 112233445566778899110 };
    int *numPtr = numArr;
 
    printf("%d\n"*numPtr);
    printf("%d\n"*numArr);
    printf("%d\n", numPtr[5]);
    printf("%d\n"sizeof(numArr));
    printf("%d\n"sizeof(numPtr));
    return 0;
}
cs

위 코드처럼 배열을 포인터에 바로 할당할 수 있다. 자료형이 같아야 하며 1차원 배열이면 *이 한개인 단일 포인터여야 한다. 배열을 포인터에 할당한뒤 포인터를 역참조하면 배열의 첫 번째 요소가 나온다. 배열도 주솟값을 가진 포인터이기 때문에 역참조 할 수 있다.

배열의 주소가 들어있는 포인터는 인덱스를 통해 요소에 접근할 수 있다.

sizeof 연산자를 사용하면 배열은 전체 공간의 크기가 출력되고, 포인터는 배열의 주소가 들어있는 포인터의 크기만 나온다. 운영체제가 32비트면 4, 64비트면 8이 나온다.

36.9 배열을 활용하여 10진수를 2진수로 변환하기

10진수를 2진수로 변환하는 방법은 다음과 같이 10진수를 0이 될 때 까지 2로 계속 나눈 뒤 나머지를 역순으로 읽으면 2진수가 된다.

이 식을 코드로 변환하면 다음과 같다.

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
#include <stdio.h>
 
int main()
{
    int dec = 13;         
    int bin[20= {0, };
    int pos = 0;
 
    while(1)
    {
        bin[pos] = dec % 2;
        dec = dec / 2;
        pos++;
 
        if(dec == 0)
            break;
    }
 
    for(int i = pos-1; i >= 0; i--)
    {
        printf("%d", bin[i]);    
    }
 
    return 0;
}
cs

변환할 10진수 13을 변수 dec에 저장하고, 2진수를 저장할 배열 bin을 선언한다.

배열에 나머지를 구해서 저장하고 10진수를 2로 나눈 것을 dec가 0이 될 때까지 무한 반복한다.

가장 아래의 나머지부터 읽어야 하므로 배열을 출력할 때는 요소를 역순으로 출력해야 한다.

36.10 퀴즈

정답은 c이다.

정답은 d이다.

정답은 e 이다.

정답은 a, e이다. 배열의 범위는 0~99인데 범위를 벗어난 인덱스를 접근했다.

36.11 연습문제 : 점수 평균 구하기

정답은 다음 코드와 같다.

1. sum += scores[i];

2. average = sum / ( sizeof(scores) / sizeof(float) );

점수 합계를 구할 때는 배열의 모든 요소를 더해주면 되고 평균을 구할 때는 합계에서 요소의 개수로 나누면 된다.

36.12 연습문제 : 2진수를 10진수로 변환하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
int pos = 0;
 
for(int i = sizeof(binary) / sizeof(int) - 1; i >= 0; i--)
 
{
 
    if(binary[i] == 1)
 
        decimal += 1 << pos;
 
    pos++;
 
}
cs

2진수를 10진수로 변환하는 방법은 2진수의 각 자리수 1이면 비트 연산자로 해당 자리에 1을 위치시키면 된다.

36.13 심사문제 : 2진수를 10진수로 변환하기

정답은 다음 코드와 같다.

1
2
3
4
5
6
smallestNumber = numArr[0];
for(int i = 0; i <= 4; i++)
{
    if(smallestNumber > numArr[i])
        smallestNumber = numArr[i];
}
cs

Unit 34. 포인터 사용하기

값을 저장할 때 사용하는 변수는 컴퓨터의 메모리에 생성된다. 메모리에 일정한 공간을 확보해 두고 원하는 값을 저장하거나 가져오는 방식이다.

변수는 num과 같이 지정된 이름으로 사용되지만, 메모리의 특정 장소에 위치하므로 메모리 주로로도 표현할 수 있다.

메모리 주소는 다음과 같이 출력할 때변수 앞에 &을 붙이면 된다.

1
2
3
4
5
6
7
8
#include <stdio.h>
 
int main()
{
    int num1 = 10;
    printf("%p\n", &num1);
    return 0;
}
cs

메모리주소는 위와 같이 16진수 형태이며 서식지정자 %p를 사용하여 출력한다. 16진수를 의미하는 %x나 %X를 이용해도 된다. 메모리주소는 고정된 값이 아니기 때문에 컴퓨터마다, 실행할 때 마다 달라진다.

시스템이 32비트인지, 64비트인지에 따라 메모리 주소의 범위도 달라진다. 각각의 범위는 다음과 같다.

  • 32비트: 16진수 8자리

  • 0x00000000 ~ 0xFFFFFFFF

  • 예) 0x008AF7FC

  • 64비트: 16진수 16자리

  • 0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF

  • 예) 0x00000000008AF7FC

  • 64비트 메모리 주소는 0x00000000`00000000처럼 8자리씩 끊어서 `를 붙이기도 한다

리눅스나 맥의 osX에서는 서식지정자 %p를 사용하며 주소 앞에 0x를 붙이고, a~f는 소문자로 출력되며 높은 자리수의 0은 생략된다.

 

34.1 포인터 변수 선언하기

C에서 메모리 주소는 포인터 변수에 저장한다.

포인터 변수는 다음과 같이 *을 이용하여 선언한다.

자료형 *포인터이름;
포인터 = &변수;
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int *numPtr;      
    int num1 = 10;   
    numPtr = &num1;  
 
    printf("%p\n", numPtr);   
    printf("%p\n"&num1);     
    return 0;
}
cs

포인터 변수를 선언할 때는 자료형 뒤에 *(Asterisk, 애스터리스크)를 붙인다. *위치는 자료형과 변수 사이에 있다면 어디에 있던 상관 없다.

&로 변수의 주소를 구해서 포인터 변수에 저장할 수도 있다.

printf로 포인터 변수 numPtr을 출력해보면 num1의 메모리 주소와 포인터 변수는 같은 값이 나온다. 포인터와 메모리 주소는 같은 의미이다.

포인터 변수를 선언할 때는 자료형을 알려주고 *을 붙여야 한다. 만약 변수가 int형이면 이 변수를 저장하는 포인터는 int *로 지정해야 한다.

포인터는 메모리의 특정 위치를 가리킬 때 사용한다. 포인터가 메모리를 가리키는것을 그림으로 확인하면 다음과 같다.

 

34.2 역참조 연산자 사용하기

포인터 변수에는 메모리 주소가 저장되어 있는데, 이 포인터 주소를 이용하여 값을 가져오고 싶으면 역참조 연산자 *을 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    int *numPtr;      
    int num1 = 10;  
    numPtr = &num1; 
    
    printf("%d\n"*numPtr);    // 역참조 연산자 사용
    return 0;
}
cs

역참조 연산자 *는 포인터 앞에 붙인다. 위와 같이 포인터 변수 앞에 *을 붙이면 그 변수에 저장된 메모리 주소로 가서 값을 가져온다.
다음은 포인터 변수에 역참조 연산자를 사용하여 값을 할당한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main()
{
    int *numPtr;      
    int num1 = 10;   
    numPtr = &num1;  
    *numPtr = 20;    
 
    printf("%d\n"*numPtr);    
    printf("%d\n", num1);       
    return 0;
}
cs

역참조 연산자로 메모리에 접근하여 값을 저장할 수도 있다. 위 num1 변수에는 10이 들어있었는데 num1을 가리키는 포인터변수 numPtr에 역참조 연산자로 20을 저장했기 때문에 num1 과 numPtr을 출력하면 둘 다 20이 나온다.
만약 포인터 변수 numPtr에 num1을 할당하면 간접 참조 수준이 다르다는 경고가 발생한다. numPtr과 num1이 각각 포인터와 int로 자료형이 다르기 때문이다. 컴파일 경고가 발생하지 않도록 하려면 numPtr앞에 * 을 붙여 값을 가져오게 하여 int와 동일한 자료형으로 만들어야 한다. 값을 가져오는것은 자료형을 동일하게 한다는 것이다. 주소연산자 &도 마찬가지로 자료형을 맞춰주는 역할을 한다.
변수, 주소 연산자, 역참조 연산자, 포인터의 차이는 다음과 같다.

 

34.3 디버거에서 포인터 확인하기

디버거를 사용하면 변수의 메모리 주소, 포인터, 역참조를 쉽게 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int *numPtr;     
    int num1 = 10;    
 
    numPtr = &num1;   
    *numPtr = 20;    
 
    printf("%d\n"*numPtr);    
    printf("%d\n", num1);      
    return 0;
}
cs

9번째 줄에 중단점을 설정하고 실행하면 다음과 같다.

위 코드에서 8번째 줄까지 실행한 상태이다. 포인터 변수 numPtr에 num1의 주소가 저장되있다. Locals 창에서 > 을 클릭하면 현재 메모리 주소에 저장된 값을 확인할 수 있다.

맥 OS에서 메모리 주소의 내용을 확인하려면 Xcode에서 디버깅 하면 된다. 

Xcode에서 9번째 줄과 10번째 줄에 중단점을 지정하고 한번 디버깅 버튼을 클릭하면 8번째 줄까지 실행되어 numPtr은 num1의 주소를 가리킨다.

이때 메모리 주소의 내용을 확인하려면 numPtr을 클릭하고 우클릭을 눌러 view memory of *numPtr을 클릭하면 된다.

num1은 int 자료형이기 때문에 0A 00 00 00 처럼 숫자 4개를 차지한다. 리틀 엔디언 방식으로 저장되기 때문에 값이 거꾸로 저장되어 원래는 00 00 00 0A 이며 0A는 10진수로 10이다. 

다음줄을 실행시키면 메모리 주소의 내용이 다음과 같이 바뀐다.

0A가 14로 바뀌었다. 16진수 14는 10진수로 20이다. 역참조후 20을 할당하여 메모리의 내용이 바뀐 것을 확인할 수 있다.

 

 

34.4 다양한 자료형의 포인터 선언하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
 
int main()
{
    long long *numPtr1;    
    float *numPtr2;        
    char *cPtr1;  
 
    long long num1 = 10;
    float num2 = 3.5f;
    char c1 = 'a';
 
    numPtr1 = &num1;    
    numPtr2 = &num2;    
    cPtr1 = &c1;       
 
    printf("%lld\n"*numPtr1);    
    printf("%f\n"*numPtr2);    
    printf("%c\n"*cPtr1);       
    return 0;
}
cs

C에서 사용할 수 있는 모든 자료형은 포인터로 만들 수 있다.

포인터에서 저장되는 메모리 주솟값은 정수형으로 동일하지만 선언하는 자료형에 따라 메모리에 접근하는 방법이 달라진다. 다음과 같이 포인터를 역참조 할 때 자료형의 크기에 맞게 값을 가져오거나 저장한다.

상수와 포인터

포인터에도 const 키워드를 붙일 수 있는데 const의 위치에 따라 특성이 달라진다.

1
2
3
4
5
const int num1 = 10;    
const int *numPtr;      
 
numPtr = &num1;
*numPtr = 20; //컴파일에러
cs

num1 이 const int 이므로 num1의 주소를 넣을 포인터도 const int로 선언해야 한다. 역참조 연산자로 값을 변경하려 해도 num1은 상수이기 때문에 컴파일 에러가 발생한다.

다음은 포인터 자체가 상수인 상황이다.

1
2
3
4
5
int num1 = 10;  
int num2 = 20;    
int * const numPtr = &num1;    
 
numPtr = &num2;    // 컴파일 에러
cs

numPtr은 포인터 자체가 상수이기 때문에 상수 포인터에는 다른 변수의 메모리 주소를 할당할 수 없다.

다음은 상수 포인터가 상수를 가리키는 상황이다.

1
2
3
4
5
6
const int num1 = 10;    
const int num2 = 20;   
const int * const numPtr = &num1;    
 
*numPtr = 30;      // 컴파일 에러
numPtr = &num2;    // 컴파일 에러
cs

포인터를 역참조한 값을 변경하려해도 해당 주소가 가리키는 것은 상수이기 때문에 컴파일 에러가 불가능하며 포인터도 상수 이므로 메모리 주소도 변경할 수 없다.

 

34.5 void포인터 선언하기

void 포인터는 자료형이 정해져있지 않은 포인터이다. 다음과 같이 사용한다.

void *포인터이름;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
 
int main()
{
    int num1 = 10;
    char c1 = 'a';
    int *numPtr1 = &num1;
    char *cPtr1 = &c1;
 
    void *ptr;        
 
    
    ptr = numPtr1;    
    ptr = cPtr1;     
 
   
    numPtr1 = ptr;   
    cPtr1 = ptr;      
 
    return 0;
}
cs

기본적으로 C는 자료형이 다른 포인터끼리 메모리 주소를 저장하면 컴파일 경고를 발생하지만 void 포인터는 어떤 자료형의 포인터든 모두 저장할 수 있다. 이런 특성 때문에 void 포인터를 범용 포인터라고 부르기도 한다.

void 포인터는 자료형이 정해지지 않았으므로 값을 가져오거나 저장할 크기도 정해지지 않았기 때문에 역참조를 하게 되면 컴파일 에러가 발생한다.

또한 void키워드로 변수를 선언할 수도 없다.

void포인터는 함수에서 다양한 자료형을 받아들일 때, 함수의 반환 포인터를 다양한 자료형으로 된 포인터를 저장할 때, 자료형을 숨기고 싶을 때 사용한다.

 

34.6 이중 포인터 사용하기

포인터의 메모리주소를 저장하는 포인터의 포인터도 선언할 수 있다. 이것을 이중 포인터 라고 하며 *을 두 번 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int *numPtr1;    
    int **numPtr2;    
    int num1 = 10;
 
    numPtr1 = &num1;  
    numPtr2 = &numPtr1; 
 
    printf("%d\n"**numPtr2);  
    return 0;
}
cs

포인터도 변수이기 때문에 포인터를 저장하고 있는 메모리 주소도 구할 수 있다. 포인터의 메모리 주소는 일반 포인터에는 저장할 수 없고 **을 사용한 이중 포인터에 저장해야 한다.

이중포인터를 끝까지 따라가서 실제 값을 가져오려면 변수 앞에 역참조 연산자를 두 번 사용하면 된다.

포인터를 선언할 때 *의 개수에 따라서 삼중포인터, 사중포인터 등도 만들 수 있다.

 

34.7 잘못된 포인터 사용

포인터는 메모리 주소를 저장하는 용도이므로 다음과 같이 값을 직접 저장하면 안된다.

 int *numPtr = 0x100; 

메모리에서 0x100은 잘못된 주솟값이기 때문이다.

위와 같이 할당할 때 까지는 에러는 발생하지 않지만 역참조로 메모리 주소를 접근할 때 에러가 발생한다. 운영체제는 프로그램이 잘못된 메모리 주소에 접근했을 때 에러를 발생시킨다.

만약 실제 존재하는 메모리 주소라면 포인터에 직접 저장할 수 있다.


34.8 퀴즈

정답은 c이다.

정답은 d이다.

정답은 a이다.

정답은 e이다.

정답은 d이다.

 

34.9 연습문제 : 포인터와 주소 연산자 사용하기

정답은 다음과 같다.

1. numPtr = &num1;

2. numPtr = &num2;

 

34.10 심사문제 : 포인터와 주소 연산자 사용하기

정답은 다음 코드와 같다.

1
2
numPtr1 = &num1;
numPtr2 = &numPtr1;
cs

 

'Language > C, C++' 카테고리의 다른 글

[C++] Reference(참조자)  (0) 2021.08.21
[C++] 입출력  (0) 2021.08.21
[C] 문자 단위 입출력 함수  (0) 2021.01.15
[C] 스트림  (0) 2021.01.15
[C] main 함수로의 인자 전달  (0) 2021.01.09

미로 크기의 2차원 배열을 만들고, 2중 for 문으로 각 요소의 값들을 입력받는다.

이후 무한 루프를 만들어 조건을 만족시키면 반복문을 탈출하는 구조로 코드를 구성하였다.

현재 좌표의 값이 2(먹이)면 현재 좌표를 9로 만들고 반복문을 종료한다.

이동할 위치가 0(이동가능)이면 9로 만들어 현재 위치를 이동하며, 이동할 위치가 1(벽)이면 오른쪽 또는 아래로 방향을 바꾼다. 방향을 바꿔도 이동할 위치가 1이면 반복문을 종료한다. 

위 작업을 수행하는 반복문에 주석을 달아 더 자세히 설명할 것이다. 

정답은 다음 코드와 같다.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include  <stdio.h>
 
int main()
{
    int x = 2, y = 2;   // 초기 좌표값
    int miro[11][11= {};  // 미로 상자
    
    for(int i = 1; i <= 10; i++)    // 2차원 배열의 각 요소에 입력값을 받음
    {
        for(int j = 1; j <= 10; j++)
            scanf("%d"&miro[i][j]);
    }
    
    while(1)    // 무한루프
    {
        if(miro[x][y] == 2// 현재 좌표가 2면 
        {
            miro[x][y] = 9// 현재 좌표를 9로 만들고
            break;          // 반복문 종료
        }
        miro[x][y] = 9;     // 현재 좌표를 9로 만듬
        if(miro[x][y+1== 0)   // 아래쪽으로 이동 가능하면
            y += 1;             // y좌표값을 1 증가시킴
        else if(miro[x][y+1== 1)  // 아래 좌표가 벽이면
        {
            if(miro[x+1][y] == 1)   // 오른쪽도 벽이면
                break;              // 갈 곳이 없으므로 반복 종료
            else if(miro[x+1][y] == 2)  //오른쪽으로 이동한 좌표가 2(먹이)면
            {
                miro[x+1][y] = 9;   // 그 좌표를 9로 만들고
                break;              // 반복 종료
            }
            else    // 벽도 아니고 먹이도 아니면, 1칸 오른쪽으로 이동
                x += 1;
        }
        else if(miro[x][y+1== 2)   // 한 칸 아래 좌표가 2(먹이)면
        {
            miro[x][y+1= 9;       // 그 좌표애 9를 지정하고
            break;                  // 반복문 종료
        }
    }
    
    for(int  i = 1; i <= 10; i++)       // 위 반복문을 마친 후 미로 상자의 상태를 출력   
    {   
        for(int j = 1; j <= 10; j++)
            printf("%d ", miro[i][j]);
        printf("\n");
    }        
    return 0;
}
cs

 

바둑판을 표현하는 2차원 배열을 만들고, 흰 돌의 개수를 입력받는다. 

흰 돌의 개수만큼 반복하면서 흰 돌을 올릴 좌표를 입력 받고, 2차원 배열의 그 좌표를 1로 만든다.

2중 for문을 사용하여 흰 돌이 표현된 바둑판을 출력한다.

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
#include <stdio.h>
 
int main()
{
    int n, x, y;    // 입력 받을 변수, x좌표, y좌표
    int pan[20][20]={};    // 바둑판
    scanf("%d"&n);    // 흰돌 개수 입력
    
    for(int i = 1; i <= n; i++)    // 1부터 흰돌 개수 만큼 반복
        {
            scanf("%d %d"&x, &y);    // x좌표, y좌표 입력
            pan[x][y]=1;            // 입력 받은 좌표에 해당하는 요소를 1로 바꿈
        }
        
    for(int i = 1; i <= 19; i++)    // 2중 for문으로 2차원 배열 출력(세로줄 만큼 반복)
        {
            for(int j = 1; j <= 19; j++)    // 가로줄 만큼 반복
            {
                printf("%d ", pan[i][j]);     // 가로줄 출력
            }
            printf("\n");    // 줄바꿈
        }
    
    return 0;
}
cs

 

부른 번호를 하나 씩 배열에 저장하고, 저장한 배열을 역순으로 출력하면 된다.

먼저 10000 크기의 배열을 선언한다. 배열의 인덱스는 0부터 시작한다. 

부른 횟수를 입력받고, for 반복문으로 0부터 입력한 횟수보다 작을 때 까지 반복하면서 출석 번호를 배열의 각 요소에 입력받는다.

for 반복문으로 n보다 1 작은 수 에서 시작하여 0까지 반복하면서 배열의 각 인덱스를 출력하면 된다.

정답은 다음 코드와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
 
int main()
{
    int n, a[10000];    // 입력 받을 횟수와 배열
    scanf("%d",&n);        // 횟수 입력
 
    for(int i = 0; i < n; i++)    // 0부터 입력한 횟수보다 작을 때 까지 반복
    {
        scanf("%d"&a[i]);        // 배열의 각 요소에 입력
    }
 
    for(int i= n - 1; i >= 0; i--)    // n-1 q부터 0까지 반복
    {
        printf("%d ", a[i]);        // n-1번 요소부터 역순으로 출력
    }
    return 0;
}
cs

 

+ Recent posts