Coding/C

[C언어] 함수에서 매개변수 사용하기(포인터, 배열, 구조체) - 코딩도장

GunP4ng 2024. 2. 6. 23:17

[코딩도장] Unit 63 ~ 65


1. 포인터 매개변수 

1. 포인터 매개변수 사용하기

C언어에서 값을 여러개 반환하는 함수를 만들 때 포인터 매개변수를 사용한다

반환값자료형 함수이름(자료형 *매개변수1, 자료형 *매개변수2)
{
}

( ) 안에서 자료형 뒤에 * 를 붙여 매개변수를 포인터 형태로 만든다

 

void swapNumber(int *first, int *second)    // 반환값 없음, int 포인터 매개변수 두 개 지정
{
    int temp;    // 임시 보관 변수

    // 역참조로 값을 가져오고, 값을 저장함
    temp = *first;
    *first = *second;
    *second = temp;
}

매개변수를 int *first, int *second 와 같이 포인터 형식으로 지정해준다

함수 안에서는 매개변수를 역참조 해서 first 와 second의 값을 바꾼다

 

int main()
{
    int num1 = 10;
    int num2 = 20;

    swapNumber(&num1, &num2);        // &를 사용하여 num1과 num2의 
    								 // 메모리 주소를 넣어줌

    printf("%d %d\n", num1, num2);   // 20 10: swapNumber에 의해서 
    								 // num1과 num2의 값이 서로 바뀜

    return 0;
}

main 함수에서는 num1 에 10, num2 에 20 을 넣어준다

& (주소 연산자)를 사용해서 swapNumber 에 num1 과 num2 의 주소를 넣어준다

pritnf 로 값을 출력하면 num1 과 num2 의 값이 바뀐 것을 알 수 있다

 

포인터 매개변수

함수의 반환값은 값을 하나만 반환할 수 있지만

포인터 매개변수를 사용하면 함수 바깥으로 여러개의 값을 전달할 수 있다

 

2. void 포인터 매개변수 사용하기

void 포인터 매개변수를 사용하면 자료형 변환을 하지 않아도

모든 자료형을 함수에 넣을 수 있다

enum TYPE {
    TYPE_CHAR,
    TYPE_INT,
    TYPE_FLOAT
};

void swapValue(void *ptr1, void *ptr2, enum TYPE t)    // 반환값 없음, void 포인터 매개변수 두 개와
{                                                      // 변수의 자료형을 알려줄 열거형을 받음
    switch (t)
    {
        case TYPE_CHAR:    // 문자면 char *로 변환한 뒤 역참조하여 값을 서로 바꿈
        {
            char temp;
            temp = *(char *)ptr1;
            *(char *)ptr1 = *(char *)ptr2;
            *(char *)ptr2 = temp;
            break;
        }
        case TYPE_INT:     // 정수면 int *로 변환한 뒤 역참조하여 값을 서로 바꿈
        {
            int temp;
            temp = *(int *)ptr1;
            *(int *)ptr1 = *(int *)ptr2;
            *(int *)ptr2 = temp;
            break;
        }
        case TYPE_FLOAT:    // 실수면 float *로 변환한 뒤 역참조하여 값을 서로 바꿈
        {
            float temp;
            temp = *(float *)ptr1;
            *(float *)ptr1 = *(float *)ptr2;
            *(float *)ptr2 = temp;
            break;
        }
    }
}

매개변수를 void 포인터로 2개 지정하고

void 포인터는 역참조 할 수 없으므로

어떤 자료형인지 알려주기 위해 TYPE 열거형도 같이 받는다

 

함수 안에서는 각 TYPE 에 따라 자료형을 변환한 뒤

역참조 하여 값을 서로 바꾼다

 

char c1 = 'a';
char c2 = 'b';
swapValue(&c1, &c2, TYPE_CHAR);        // 변수의 메모리 주소와 TYPE_CHAR를 넣음
printf("%c %c\n", c1, c2);             // b a: swapValue에 의해서 값이 서로 바뀜

int num1 = 10;
int num2 = 20;
swapValue(&num1, &num2, TYPE_INT);     // 변수의 메모리 주소와 TYPE_INT를 넣음
printf("%d %d\n", num1, num2);         // 20 10: swapValue에 의해서 값이 서로 바뀜

float num3 = 1.234567f;
float num4 = 7.654321f;
swapValue(&num3, &num4, TYPE_FLOAT); // 변수의 메모리 주소와 TYPE_FLOAT를 넣음
printf("%f %f\n", num3, num4);       // 7.654321f 1.234567: 
                                     // swapValue에 의해서 값이 서로 바뀜

swapValue 함수는 자료형 변환을 하지 않아도

다양한 자료형의 포인터를 넣을 수 있다

 

void 포인터 매개변수에는 메모리 주소 뿐만 아니라 메모리를 할당한 포인터도 넣을 수 있다

구조체, 공용체, 열거형의 포인터도 넣을 수 있다

 

3. 이중 포인터 매개변수 사용하기

매개변수를 이용해서 포인터(메모리 주소)를 가져오려면 이중 포인터 매개변수를 사용해야 한다

void allocMemory(void **ptr, int size)    // 반환값 없음, void 이중 포인터 매개변수 지정
{
    *ptr = malloc(size);    // void **ptr을 역참조하여 void *ptr에 메모리 할당
}

int main()
{
    long long *numPtr;

    // 단일 포인터 long long *numPtr의 메모리 주소는 long long **와 같음, 할당할 크기도 넣음 
    allocMemory((void**)&numPtr, sizeof(long long));

    *numPtr = 10;
    printf("%lld\n", *numPtr);

    free(numPtr);    // 동적 메모리 해제

    return 0;
}

allocMemory 함수를 만들 때 void **ptr 같이 이중 포인터를 만든다

함수에서는 void **ptr 를 역참조하여 void *ptr 로 만든 뒤 메모리를 할당한다

 

long long *numPtr;

// 단일 포인터 long long *numPtr의 메모리 주소는 long long **와 같음, 할당할 크기도 넣음
allocMemory((void**)&numPtr, sizeof(long long));

*numPtr = 10;
printf("%lld\n", *numPtr);

free(numPtr);    // 동적 메모리 해제

long long *numPtr 의 주소는 long long ** 과 같기 때문에

매개변수 void **ptr 로 받을 수 있다

 

이중 포인터 매개변수

매개변수 void **ptr 을 역참조해서 *numPtr 에 메모리를 할당하게 된다

 

4. 문자열 매개변수 사용하기

void helloString(char *s1)    // 반환값 없음, char 포인터 매개변수 한 개 지정
{
    printf("Hello, %s\n", s1);    // Hello, 와 매개변수를 조합하여 문자열 출력
}

int main()
{
    helloString("world!");    // Hello, world!: 함수를 호출할 때 문자열을 전달

    return 0;

함수에서 매개변수로 문자열을 받으려면

매개변수를 문자열 포인터로 지정하면 된다

 

void helloString(char *s1)    // 반환값 없음, char 포인터 매개변수 한 개 지정
{
    printf("Hello, %s\n", s1);    // Hello, 와 매개변수를 조합하여 문자열 출력
}

int main()
{
    char s1[10] = "world!";    // 배열 형태의 문자열

    helloString(s1);    // Hello, world!: 함수를 호출할 때 배열 전달

    return 0;
}

배열 형태의 문자열도 문자열 포인터 매개변수에 전달할 수 있다

매개변수는 char 포인터로 지정한다

 

void helloString(char s1[])    // 반환값 없음, char 배열을 매개변수로 지정, 크기 생략
{
    printf("Hello, %s\n", s1);    // Hello, 와 매개변수를 조합하여 문자열 출력
}

매개변수로 문자 배열을 입력받는다는 것을 확실히 하려면

매개변수 뒤에  [ ] 를 붙이면 된다

※ [ ] 안의 배열의 크기는 생략한다

 

 

2. 배열 매개변수

1. 배열 매개변수 사용하기

함수에서 배열을 매개변수로 사용하려면

매개변수 이름 뒤에 [ ] 를 붙이거나 매개변수를 포인터로 지정해준다

반환값자료형 함수이름(자료형 매개변수[])
{
}

반환값자료형 함수이름(자료형 *매개변수)
{
}

 

void printArray(int arr[], int count)    // 배열의 포인터와 요소의 개수를 받음
{
    for (int i = 0; i < count; i++)
    {
        printf("%d ", arr[i]);
    }

    printf("\n");
}

함수에서 배열을 매개변수로 사용할 때는 매개변수 이름 뒤에 [ ] 만 붙이면 된다

[ ] 안에 크기를 지정해도 무시된다

arr[ ] 은 배열의 메모리 주소를 담고 있는 포인터이므로 배열의 크기를 알 수 없다

다른 매개변수를 통해 배열의 크기를 받아야 한다

 

arr은 포인터이기 때문에

함수 안에서 매개변수로 받은 배열의 요소를 변경하면

함수 바깥에 있는 배열의 요소가 바뀌게 된다

 

void printArray(int *arr, int count)    // 매개변수를 포인터로 지정하여 배열을 받음
{
    for (int i = 0; i < count; i++)
    {
        printf("%d ", arr[i]);
    }

    printf("\n");
}

1차원 배열은 int *arr 과 같이 int 형 포인터로 받을 수 있다

포인터로는 배열의 크기를 알 수 없으므로 다른 매개변수를 통해 배열의 크기를 받는다

 

마찬가지로 arr은 포인터이기 때문에

함수 안에서 매개변수로 받은 배열의 요소를 변경하면

함수 바깥에 있는 배열의 요소가 바뀌게 된다

 

2. 2차원 배열 매개변수 사용하기

함수에서 2차원 배열을 매개변수로 사용하려면

매개변수 뒤에 [ ] 를 2개 붙이고 가로 크기를 지정하거나

괄호로 묶은 포인터 뒤에 [ ] 를 붙이고 가로 크기를 지정한다

반환값자료형 함수이름(자료형 매개변수[][가로크기])
{
}

반환값자료형 함수이름(자료형 (*매개변수)[가로크기])
{
}

 

//                          ↓ 배열의 가로 크기 지정
void print2DArray(int arr[][5], int col, int row)    // 2차원 배열의 포인터와 가로, 세로 크기를 받음
{
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            printf("%d ", arr[i][j]);
        }

        printf("\n");
    }
}

두 번째 대괄호에는 배열의 가로 크기를 지정해야 한다

※ 첫 번째 대괄호 안에는 세로 크기를 지정하더라도 무시된다

 

arr[ ][5] 는 2차원 배열의 메모리 주소를 담고 있는 포인터이다

2차원 배열의 크기를 알 수 없으므로 다른 매개변수로 배열의 가로, 세로 크기를 받아야 한다

 

arr도 포인터이므로 함수 안에서 2차원 배열의 요소를 변경하면

함수 바깥에 있는 2차원 배열의 요소가 바뀐다

 

void print2DArray(int (*arr)[5], int col, int row)   // 매개변수를 포인터로 만든 뒤 가로 크기 지정
{
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            printf("%d ", arr[i][j]);
        }

        printf("\n");
    }
}

int (*arr)[5] 와 같이 int 형 배열의 포인터로 지정하면 2차원 배열을 매개변수로 사용할 수 있다

대괄호 안에는 2차원 배열의 가로 크기를 지정한다

※ (*arr)[5] 와 같이 포인터를 괄호로 묶어 배열의 포인터라고 명확하게 표시해야 한다

 

포인터로는 2차원 배열의 크기를 알 수 없으므로 배열의 가로, 세로 크기를 받는다

 

3. 구조체 매개변수

1. 구조체 매개변수 사용하기

함수에서 구조체를 매개변수로 사용하려면

struct 키워드와 구조체 이름으로 매개변수를 지정해주면 된다

반환값자료형 함수이름(struct 구조체이름 매개변수)
{
}

 

struct Person {
    char name[20];
    int age;
    char address[100];
};

void printPerson(struct Person p)    // 반환값 없음, 구조체 매개변수 한 개 지정
{
    // 구조체 매개변수 멤버의 값 출력
    printf("이름: %s\n", p.name);       // 이름: 홍길동
    printf("나이: %d\n", p.age);        // 나이: 30
    printf("주소: %s\n", p.address);    // 주소: 서울시 용산구 한남동
}

 

구조체를 매개변수로 지정하고

함수 안에서 구조체 매개변수 멤버의 값을 출력한다

 

int main()
{
    struct Person p1;

    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구 한남동");

    printPerson(p1);    // 함수를 호출할 때 구조체 변수 전달, 멤버가 복사됨

    return 0;
}

함수를 호출할 때 구조체 변수를 넣어주면

구조체 변수의 모든 멤버가 매개변수로 복사된다

→ 메모리가 낭비되어 비효율적이다

 

※ 매개변수로 구조체를 전달할 때는 포인터를 활용하는 것이 좋다

 

void setPerson(struct Person p)    // 반환값 없음, 구조체 매개변수 한 개 지정
{
    // 매개변수로 받은 구조체 멤버의 값 변경
    strcpy(p.name, "고길동");
    p.age = 40;
    strcpy(p.address, "서울시 서초구 반포동");
}

int main()
{
    struct Person p1;

    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구 한남동");

    setPerson(p1);    // 함수를 호출할 때 구조체 변수 전달, 멤버가 복사됨

    // setPerson에서 변경한 값은 영향을 미치지 않음
    printf("이름: %s\n", p1.name);       // 이름: 홍길동
    printf("나이: %d\n", p1.age);        // 나이: 30
    printf("주소: %s\n", p1.address);    // 주소: 서울시 용산구 한남동

    return 0;
}

함수 안에서 매개변수로 받은 구조체 멤버의 값을 변경해도

함수 바깥에는 영향을 미치지 않는다

 

매개변수로 값이 전달될 때 구조체 멤버가 모두 복사되었기 때문에

구조체 멤버를 변경해도 바깥에 있는 구조체 변수에는 영향을 미치지 않는다

 

2. 구조체 포인터 매개변수 사용하기

구조체 포인터를 매개변수로 사용하려면

구조체 이름 뒤에 *를 붙여서 매개변수를 포인터 형태로 만들면 된다

반환값자료형 함수이름(struct 구조체이름 *매개변수)
{
}

 

void setPerson(struct Person *p)    // 반환값 없음, 구조체 포인터 매개변수 한 개 지정
{
    // 매개변수로 받은 포인터에서 구조체 멤버의 값 변경
    strcpy(p->name, "고길동");
    p->age = 40;
    strcpy(p->address, "서울시 서초구 반포동");
}

매개변수를 구조체 포인터로 지정하고

함수 안에서 포인터로 받은 구조체 멤버의 값을 변경한다

 

struct Person p1;

strcpy(p1.name, "홍길동");
p1.age = 30;
strcpy(p1.address, "서울시 용산구 한남동");

setPerson(&p1);    // 함수를 호출할 때 구조체 변수의 메모리 주소를 전달

구조체 변수를 선언하고, 각 멤버에 값을 저장한다

함수 setPerson 은 매개변수가 구조체 포인터이므로 & (주소 연산자)를 사용하여

p1 의 주소를 전달한다

 

매개변수를 구조체 포인터로 사용하면

구조체 멤버의 값이 복사되지 않고 메모리 주소만 전달된다

함수 바깥에 선언된 구조체 변수의 값을 변경한다

 

구조체 포인터 별칭을 정의했다면

매개변수에 구조체 포인터 별칭을 그대로 지정해줄 수 있다

 

 

4. 심사문제

63.9 몫과 나머지를 구하는 함수 만들기

표준 입력으로 정수 두 개가 입력됩니다(입력 값의 범위는 int의 범위와 같음) 다음 소스 코드를 완성하여 입력된 두 정수를 나누었을 때 몫과 나머지가 출력되게 만드세요. 함수의 반환값과 매개변수를 모두 활용하여 값을 꺼내와야 합니다

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

____________________________
____________________________
____________________________
____________________________
____________________________

int main()
{
    int num1;
    int num2;
    int quotient;     // 몫
    int remainder;    // 나머지

    scanf("%d %d", &num1, &num2);

    quotient = getQuotientAndRemainder(num1, num2, &remainder);

    printf("%d %d\n", quotient, remainder);

    return 0;
}

 

정답

int getQuotientAndRemainder(int num1, int num2, int *remainder) {
    *remainder = num1 % num2;
    return num1 / num2;
}

63.10 함수에서 메모리를 할당하여 배열 만들기

표준 입력으로 정수 두 개가 입력됩니다. 다음 소스 코드를 완성하여 입력된 두 정수가 동적 메모리에 저장된 뒤 다시 출력되게 만드세요. allocArray 함수의 매개변수는 배열(포인터), 배열의 크기, 요소의 크기 순서입니다.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

___________________________________
___________________________________
___________________________________
___________________________________

int main()
{
    long long *numArr1;
    int *numArr2;

    allocArray((void **)&numArr1, 10, sizeof(long long));
    allocArray((void **)&numArr2, 3, sizeof(int));

    scanf("%lld %d", &numArr1[9], &numArr2[2]);
    printf("%lld %d\n", numArr1[9], numArr2[2]);

    free(numArr2);
    free(numArr1);

    return 0;
}

 

정답

void allocArray(void **numarr, int num, int size) {
    *numarr = malloc(size * num);
}

63.11 문자열 매개변수 사용하기

표준 입력으로 길이 30 이하의 성과 이름 문자열이 두 개 입력됩니다. 다음 소스 코드를 완성하여 전체 이름이 출력되게 만드세요(성과 이름은 붙여서 출력)

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

__________________________
__________________________
__________________________
__________________________

int main()
{
    char familyName[31];
    char givenName[31];

    scanf("%s %s", familyName, givenName);

    printFullName(familyName, givenName);

    return 0;
}

 

정답

void printFullName(char *first, char *last) {
    printf("%s%s", first, last);
}

64.6 배열 매개변수 사용하기

표준 입력으로 정수 10개가 입력됩니다. 다음 소스 코드를 완성하여 입력된 정수가 반대 순서로 출력되게 만드세요(각 정수는 공백으로 구분하여 출력합니다.)

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

_______________________________
_______________________________
_______________________________
_______________________________
_______________________________
_______________________________

_______________________________
_______________________________

int main()
{
    int numArr[10];

    scanf("%d %d %d %d %d %d %d %d %d %d", 
        &numArr[0], &numArr[1], &numArr[2], &numArr[3], &numArr[4], 
        &numArr[5], &numArr[6], &numArr[7], &numArr[8], &numArr[9]
    );

    printReverse(numArr, sizeof(numArr) / sizeof(int));

    return 0;
}

 

정답

void printReverse(int numarr[], int size) {
    for (int i = size - 1; i >= 0; i--) {
        printf("%d ", numarr[i]);
    }
}

배열의 인덱스를 거꾸로 출력하여 입력된 값의 반대로 출력되게 만들었다


64.7 전치행렬 만들기

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________
____________________________________

int main()
{
    int matrix[4][4];

    scanf("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
        &matrix[0][0], &matrix[0][1], &matrix[0][2], &matrix[0][3],
        &matrix[1][0], &matrix[1][1], &matrix[1][2], &matrix[1][3],
        &matrix[2][0], &matrix[2][1], &matrix[2][2], &matrix[2][3],
        &matrix[3][0], &matrix[3][1], &matrix[3][2], &matrix[3][3]
    );

    int n = sizeof(matrix[0]) / sizeof(int);

    transpose(matrix, n);

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)

        {
            printf("%d ", matrix[i][j]);
        }

        printf("\n");
    }

    return 0;
}

 

정답

void transpose(int matrix[][4], int size) {
    int reverse[size][size];
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            reverse[i][j] = matrix[j][i];
        }
    }
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            matrix[i][j] = reverse[i][j];   
        }
    }
}

입력된 행렬의 전치행렬이 저장될 빈 배열을 생성한다

빈 배열의 값을 다시 기존 행렬에 할당한다

※ 같은 반복문 안에 코드를 짜면 인덱스가 반복되어 기존 행렬이 중첩되는 문제가 발생한다


65.6 두 점 사이의 거리를 구하는 함수 만들기

표준 입력으로 x, y 좌표 두 개가 입력됩니다. 다음 소스 코드를 완성하여 두 점 사이의 거리가 출력되게 만드세요. 이 때 두 점 사이의 거리는 double형 실수로 출력되어야 합니다.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>

struct Point2D {
    int x;
    int y;
};

____________________________
____________________________
____________________________
____________________________
____________________________

____________________________
____________________________

int main()
{
    struct Point2D p1;
    struct Point2D p2;

    scanf("%d %d %d %d", &p1.x, &p1.y, &p2.x, &p2.y);

    printf("%f\n", getDistance(p1, p2));

    return 0;
}

 

정답

double getDistance(struct Point2D p1, struct Point2D p2) {
    int x = p1.x - p2.x;
    int y = p1.y - p2.y;

    return sqrt(pow(x, 2) + pow(y, 2));
}

65.7 좌표를 이동시키는 함수 만들기

표준 입력으로 정수 네 개가 입력됩니다. 다음 소스 코드를 완성하여 입력된 정수만큼 좌표를 이동시키도록 만드세요.

첫 번째 정수는 점의 x, 두 번째 정수는 점의 y, 세 번째 정수는 점의 이동할 x 값, 네 번째 정수는 이동할 y 값입니다.

정답에는 밑줄 친 부분에 들어갈 코드만 작성해야 합니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

struct Point2D {
    int x;
    int y;
};
 
______________________
______________________
______________________
______________________
______________________

int main()
{
    struct Point2D p1;
    int offsetX, offsetY;
 
    scanf("%d %d %d %d", &p1.x, &p1.y, &offsetX, &offsetY);

    movePoint2D(&p1, offsetX, offsetY);

    printf("%d %d\n", p1.x, p1.y);

    return 0;
}

 

정답

void movePoint2D(struct Point2D *p, int x, int y) {
    p->x += x;
    p->y += y;
}