26
Function & Pointer C-언어의 활용을 위한 주요 기법 (3) Dong Kyue Kim Hanyang University [email protected]

Function & Pointeresslab.hanyang.ac.kr/uploads/data_structure_2019_1... · 2019. 3. 25. · Function & Pointer C-언어의활용을위한주요기법(3) Dong Kyue Kim Hanyang University

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

  • Function & Pointer

    C-언어의 활용을 위한 주요 기법 (3)

    Dong Kyue Kim

    Hanyang University

    [email protected]

  • 함수의 인자 전달

  • int fct(int a){

    a++;return a;

    }

    #include

    int main(void){

    int val = 10;fct(val);return 0;

    }

    3

    함수의 인자 전달

    • 함수의 인자 전달 방식

    – 인자 전달의 기본 방식은 복사다.

    – 함수 호출 시 전달되는 값을 매개 변수를 통해서 전달 받는데, 이때에 값의 복사가 일어난다.

    val의 값을 변수 a에 복사

  • Call-By-Value (값에 의한 호출)

    • Call-By-Value

    – 값에 의한 호출이라는 뜻으로 함수의 호출 방식을 의미한다.

    – Call-By-Value의 기본 호출 방식은 값의 복사 이다.

    4

    #include void sub (int v);

    int main (void){

    int i = 100;sub(i);printf("i=%d\n", i);return 0;

    }

    void sub (int v){

    v = 200;}

    v에 i 값이 복사된다.

    i = 100

  • #include void sub (int *p);

    int main (void){

    int i = 100;sub(&i);printf("i=%d\n", i);return 0;

    }

    void sub (int *v){

    *v = 200;}

    Call-By-Reference (참조에 의한 호출)

    • Call-By-Reference

    – Call-By-Value와 반대되는 개념

    – 변수의 복사본이 함수로 전달되는 것이 아닌, 원본이 주소 값을 통해 직접 전달됨

    – 함수의 호출 방식으로 함수를 호출하면서 주소 값을 전달한다.

    – 전달된 주소가 가리키는 원본의 변수의 조작을 함수 내에서 직접 가능하게 한다.

    5

    1. 변수 i의 주소 전달.

    2. 포인터 변수 v가 가리키는 변수의 값이 200으로 변경.

    i = 200

  • #include void swap(int a, int b);

    int main(void){

    int val1 =10, val2 = 20;

    swap(val1, val2);printf("val1, vla2 : %d, %d\n", val1, val2);return 0;

    }void swap(int a, int b){

    int temp;int *pa, *pb;pa = &a;pb = &b;temp = (*pa);(*pa) = (*pb);(*pb) = temp;printf("a, b : %d, %d \n", a, b);

    }

    잘못된 포인터의 사용

    • 예시. Swap() 함수 ( 포인터의 잘못된 사용)

    – Swap함수 내에서 포인터를 사용하더라도 swap의 지역 변수를 가리키기때문에 main함수에 값에 영향을 주지 않는다.

    a, b : 20, 10val1, val2 : 10, 20

  • Call-By-Value와 Call-By-Reference

    • 정리

    – 호출한 함수 내에서 변수를 단순히 읽기만 하는 경우에는 Call-By-Value로 충분하다.

    – 외부에서부터 전달받은 변수의 값을 변경하여 외부로 내보내고 싶은 경우Call-By-Value를 사용하면 제한이 생긴다.

    – 함수의 반환(Return)을 이용하여 하나의 값만을 외부로 반환 할 수 있다.

    – 호출한 함수 내에서 연산에 의해 변화된 값을 반영하여 외부로 내보내고 싶은 변수가 많은 경우에는 Call By Reference를 사용

  • scanf

    • scanf 함수 호출 시 &를 붙이는 이유

    – scanf 함수로 main에 있는 val 변수에 값을 저장한다고 하자.

    – scanf 함수 내에서 main함수에서 선언된 지역 변수 val에 접근하기 위해서는 해당 변수의 주소를 알아야 한다.

    – 따라서 scanf 함수를 호출하면서 값이 채워질 지역 변수 val의 주소 값을인자로 전달하는 것이다.

    8

    #include

    int main(void){

    int val;scanf ("%d", &val);return 0;

    }

  • #include

    void fct(int *arr2);

    int main(void){

    int arr1[2] = {1, 2};fct(arr1);printf("arr1[0] : %d \n", arrr1[0]);printf("arr1주소 : %d \n", arr1);return 0;

    }

    void fct(int *arr2){

    printf("arr2[0] : %d \n", arr2[0]);printf("arr2주소 : %d \n", arr2);arr2[0] = 3;

    }

    배열을 함수의 인자로 전달

    • 함수의 인자로 배열을 전달하는 방식

    – 배열의 경우 일반 변수처럼 값 전체를 복사하는 방법을 사용할 수 없다.

    – 따라서 배열의 주소를 넘겨서 접근하도록 유도하는 방법을 사용한다.

    9

    배열의 주소를 전달

    1245020

    1245024

    arr1

    arr2

    arr2[0] : 1arr2주소 : 1245020arr1[0] : 3arr1주소 : 1245020

  • 배열을 함수의 인자로 전달

    • 배열 전달 시 주의점

    – 배열의 주소만 전달하기 때문에 배열의 크기를 알 수가 없다.

    – 따라서 배열의 주소를 인자로 전달할 경우 주소와 함께 크기도 보내는 것이 좋다.

    10

    #include int ArrAdder(int *pArr, int n);

    int main(void){

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};int sumArr;sumArr = ArrAdder9arr, sizeof(arr)/sizeof(int));printf ("배열의 총합 : %d\n", sumArr);return 0;

    }int ArrAdder(int *pArr, int n){

    int sum = 0;int i;for(i=0; i

  • #include

    int MaxVal(int pArr[], int n);

    int main(void){

    int arr[10] = {4,8,3,7,2};int maxA;

    maxA = MaxVal(arr, sizeof(arr)/sizeof(int));printf ("최대 값 : %d\n", maxA);return 0;

    }

    int MaxVal (int pARr[], int n){

    int max, i;

    max = pArr[0];for (i=1; i

  • 다차원 배열과 포인터

  • 2차원 배열 이름

    • 2차원 배열 이름이 가리키는 것

    – 배열의 이름은 포인터

    – 2차원 배열의 이름도 포인터

    – 아래의 예시에서 a[0], a[1], a[2] 역시 포인터 이다.

    13

    a[0][0] = 1

    a[0][1] = 2

    a[1][0] = 3

    a[1][1] = 4

    a[2][0] = 5

    a[2][1] = 6

    a a[0]

    a[1]

    a[2]

    #include

    int main(){

    int a[3][2] = {1,2,3,4,5,6};

    printf("a[0] : %d\n", a[0]);printf("a[1] : %d\n", a[1]);printf("a[2] : %d\n", a[2]);printf("a : %d\n", a);

    }

    a[0] : 1245004a[1] : 1245012a[2] : 1245020a : 1245004

  • 2차원 배열 이름과 연산

    • 2차원 배열 이름을 이용한 포인터 연산

    – 아래 예시의 2차원 배열의 이름인 포인터 a와배열의 원소인 포인터 a[0], a[1], a[2]의 차이를비교

    – a[0], a[1], a[2]와 a, a+1, a+2, a[0] + 1, a[1] +1 비교

    14

    a[0][0] = 1

    a[0][1] = 2

    a[1][0] = 3

    a[1][1] = 4

    a[2][0] = 5

    a[2][1] = 6

    a a[0]

    a[1]

    a[2]

    a+1

    a+2

    a[0]+1

    a[1]+1

    #include

    int main(){

    int a[3][2] = {1,2,3,4,5,6};

    printf("a[0] : %d\n", a[0]);printf("a[1] : %d\n", a[1]);printf("a[2] : %d\n", a[2]);printf("a[0] + 1: %d\n", a[0]+1);printf("a[1] + 1: %d\n", a[1]+1);printf("a : %d\n", a);printf("a+1 : %d\n", a + 1);printf("a+2 : %d\n", a + 2);return 0;

    }

    a[0] : 2030040a[1] : 2030048a[2] : 2030056a[0] + 1 : 203044a[1] + 1 : 203052a : 2030040a+1 : 2030048a+2 : 2030056

  • 2차원 배열의 포인터 타입 비교

    • 2차원 배열의 포인트 타입 비교

    – 2차원 배열의 차이에 따른 포인터의 주소 값 증가 및 감소 비교

    15

    arr1은 8씩 증가하지만 arr2는 16씩 증가한다. 따라서 arr1과 arr2는 같은 2차원배열이지만 포인터 타입이 다르다.

    #include

    int main(){

    int arr1[4][2];int arr2[2][4];

    printf("arr1 : %d\n", arr1);printf("arr1+1 : %d\n", arr1+1);printf("arr1+2 : %d\n", arr1+2);printf("arr2 : %d\n", arr2);printf("arr2+1 : %d\n", arr2+1);printf("arr2+2 : %d\n", arr2+2);return 0;

    }

    arr1 : 1244996arr1+1 : 1245004arr1+2 : 1245012arr2 : 1244956arr2+1 : 1244972arr2+2 : 1244988

  • 2차원 배열과 포인터

    16

    a[0][0] = 1

    a[0][1] = 2

    a[1][0] = 3

    a[1][1] = 4

    a[2][0] = 5

    a[2][1] = 6

    a[0]+1

    a[0]

    a[1]

    a[2]

    a[0]+3 = a[1]+1

    a[2]+1

    a[0]+4

    • 2차원 배열 원소인 배열의 연산

    – a[i][j] 중 a[k]가 연산으로 자신의 배열의 범위를넘을 경우 a[k+1] 배열의 영역을 가리킨다.

    – 아래 예시. a[2] = a[0] + 4 = a[1] + 2

    #include

    int main(){

    int a[3][2] = {1,2,3,4,5,6};

    printf("*a[0] : %d\n", *a[0]);printf("*(a[0]+1) : %d\n", *(a[0]+1));printf("*(a[1]+1) : %d\n", *(a[1]+1));printf("*(a[2]+1) : %d\n", *(a[2]+1));printf("*(a[0]+3) : %d\n", *(a[0]+3));printf("*(a[0]+4) : %d\n", *(a[0]+4));

    return 0;}

    *a[0] : 1*(a[0]+1) : 2*(a[1]+1) : 4*(a[2]+1) : 6*(a[0]+3) : 4*(a[0]+4) : 5

  • 포인터의 포인터

  • 포인터의 포인터

    • 포인터의 포인터

    – *연산자를 두 개 선언하는 포인터로 더블 포인터라고도 부른다.

    – 더블 포인터는 포인터를 가리킨다.

    – 포인터가 기본 자료형 변수의 주소 값을 저장하는 것처럼 더블 포인터는 포인터의주소 값을 저장한다.

    18

    #include

    int main(){

    int val = 4;int *ptrl1 = &val; //싱글포인터int **ptrl2 = &ptrl1; //더블포인터return 0;

    }

  • • 더블 포인터의 메모리 구조

    – 더블 포인터는 포인터의 첫 바이트 주소 값을 가리킨다.

    – 포인터의 주소 값도 최대 4바이트 이기 때문에 더블 포인터 자료 형의 크기도4바이트

    더블 포인터와 메모리

    19

    …12345678

    12345690ptrl1 =

    12345678

    val = 4

    12346800ptrl2=

    12345690

    int val = 4;int *ptrl1 = &val;int **ptrl2 = &ptrl1;

  • 함수 호출전pA : 10pB : 20

    함수 호출후pA : 10pB : 20

    더블 포인터의 의한 call-by-reference

    • 잘못 구현된 call-by-reference

    – 주소 값만 넘긴다고 무조건

    call-by-reference인 것이 아니다.

    20

    함수 호출 후에도 값이바뀌지 않는다.

    pswap 함수는 pA와 pB의 주소 값을 복사에 의해 전달 받았을 뿐이다.p1, p2가 서로 값을 바꾸어도 지역변수 이기 때문에 pA, pB는 기존의 값을 그대로 유지하게 된다.

    #include void pswap (int *p1, int *p2);int main(){

    int A = 10, B = 20;int *pA, *pB;pA = &A;pB = &B;printf("함수 호출전\n");printf("pA : %d\n", *pA);printf("pB : %d\n", *pB);pswap(pA, pB);printf("\n함수 호출 후\n");printf("pA : %d\n", *pA);printf("pB : %d\n", *pB);return 0;

    }

    void pswap (int *p1, int *p2){

    int *temp;temp = p1;p1 = p2;p2 = temp;

    }

  • • 제대로 작성된 call-by-reference

    더블 포인터의 의한 call-by-reference

    21

    10

    20

    더블 포인터p1

    더블 포인터p2

    포인터pA

    포인터pB

    포인터 temp

    변수 A

    변수 B

    10

    20

    포인터pA

    포인터pB

    변수 A

    변수 B

    #include void pswap (int **p1, int **p2);int main(){

    int A = 10, B = 20;int *pA, *pB;pA = &A;pB = &B;printf("함수 호출전\n");printf("pA : %d\n", *pA);printf("pB : %d\n", *pB);pswap(&pA, &pB);printf("\n함수 호출 후\n");printf("pA : %d\n", *pA);printf("pB : %d\n", *pB);return 0;

    }

    void pswap (int **p1, int **p2){

    int *temp;temp = *p1;*p1 = *p2;*p2 = temp;

    }

    함수 호출전pA : 10pB : 20

    함수 호출후pA : 20pB : 10

  • void function1(int *a){

    …}

    int arr2[10][10];int arr1[10];

    2차원 배열 이름

    • 2차원 배열 이름과 포인터

    – 함수로 전달하는 1차원 배열 이름은 포인터로 받을 수 있다.

    – 하지만 2차원 배열의 경우 더블 포인터와 다르다.

    22

    OK Warning

    더블 포인터 b가 arr2라는 2차원 배열을 받을때 배열의 첫 주소 값만 넘기므로 b는 arr2가10x10인지 2x50인지 알 수가 없다.

    void function2(int **b){

    …}

  • 참고: 우선순위와 결합성

    • 우선순위

    – 곱셈과 덧셈이 같이 있으면 곱셈부터계산하는 것처럼 모든 연산자에는 우선 순위라는 것이 있다.

    – 우선 순위를 신경 쓰고 싶지 않으면계산 하나하나 () 처리를 하면 된다.

    • 결합성

    – 우선 순위가 같은 경우 계산하는 방향을 말한다.

    23

    연산순위 연산자 결합성

    1 (), [], ->, . →

    2 sizeof, &(참조), ++, --, ~, !, *(간접 참조), +(부호), -(부호)

    3 *(산술), /, % →

    4 +(산술), -(산술) →

    5 >>,

  • 배열 포인터

    • 배열 포인터

    – 배열 포인터란 배열을 가리키는 포인터 이다.

    – 2차원 배열의 경우 배열 포인터를 사용하여 대응되는 변환이 가능하다.

    24

    포인터 타입.

    연산 시 4(크기) x 4byte (int) 씩건너 뛰는 포인터.

    배열 포인터라고 한다.

    arr[4] 형태의 배열을 가리키는 포인터

    int arr[2][4];

    int (*pArr)[4];

  • 배열 포인터와 포인터 배열

    • 배열 포인터와 포인터의 배열의 차이

    – 포인터 배열

    – 배열 포인터

    25

    int* int* int* int*

    pArr[0] pArr[1] pArr[2] pArr[3]

    가리킨다

    int int int int

    int int int int

    X ?

    X 4…

    배열생성

    int arr[?][4] 배열

    int *pArr[4];

    int (*pArr)[4]; int arr[4];

    int arr[4];

  • #include void pswap (int *p1, int *p2);int main(){

    int arr1[2][4] = {1,2,3,4,5,6,7,8};int arr2[3][4] = {{10},{20},{30}};

    show_arr (arr1, 2);show_arr (arr2, 3);return 0;

    }

    void show_arr (int (*ptr)[4], int a){

    int i, j;printf("----Start Print----\n");for (i=0; i