Function & Pointer C- 언어의활용을위한주요기법 (3) Dong Kyue Kim Hanyang University dqkim@hanyang.ac.kr
함수의인자전달
함수의인자전달 함수의인자전달방식 인자전달의기본방식은복사다. 함수호출시전달되는값을매개변수를통해서전달받는데, 이때에값의복사가일어난다. int main(void) int val = 10; fct(val); val 의값을변수 a 에복사 int fct(int a) a++; return a; 3
Call-By-Value ( 값에의한호출 ) Call-By-Value 값에의한호출이라는뜻으로함수의호출방식을의미한다. Call-By-Value 의기본호출방식은값의복사이다. void sub (int v); int main (void) int i = 100; sub(i); printf("i=%d\n", i); void sub (int v) v = 200; v 에 i 값이복사된다. i = 100 < 결과 > 4
Call-By-Reference ( 참조에의한호출 ) Call-By-Reference Call-By-Value 와반대되는개념 변수의복사본이함수로전달되는것이아닌, 원본이주소값을통해직접전달됨 함수의호출방식으로함수를호출하면서주소값을전달한다. 전달된주소가가리키는원본의변수의조작을함수내에서직접가능하게한다. void sub (int *p); int main (void) int i = 100; sub(&i); printf("i=%d\n", i); 1. 변수 i의주소전달. void sub (int *v) *v = 200; 2. 포인터변수 v 가가리키는변수의값이 200 으로변경. i = 200 < 결과 > 5
잘못된포인터의사용 예시. Swap() 함수 ( 포인터의잘못된사용 ) Swap함수내에서포인터를사용하더라도 swap의지역변수를가리키기때문에 main함수에값에영향을주지않는다. 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); 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); < 결과 > a, b : 20, 10 val1, 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 의주소값을인자로전달하는것이다. int main(void) int val; scanf ("%d", &val); 8
배열을함수의인자로전달 함수의인자로배열을전달하는방식 배열의경우일반변수처럼값전체를복사하는방법을사용할수없다. 따라서배열의주소를넘겨서접근하도록유도하는방법을사용한다. 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); void fct(int *arr2) printf("arr2[0] : %d \n", arr2[0]); printf("arr2 주소 : %d \n", arr2); arr2[0] = 3; 1245020 1245024 배열의주소를전달 < 결과 > arr2[0] : 1 arr2 주소 : 1245020 arr1[0] : 3 arr1 주소 : 1245020 arr1 arr2 9
배열을함수의인자로전달 배열전달시주의점 배열의주소만전달하기때문에배열의크기를알수가없다. 따라서배열의주소를인자로전달할경우주소와함께크기도보내는것이좋다. 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); int ArrAdder(int *parr, int n) int sum = 0; int i; for(i=0; i<n; i++) sum+=parr[i]; return sum; < 결과 > 배열의총합 : 55 10
배열을함수의인자로전달 int MaxVal(int parr[], int n); int main(void) int arr[10] = 4,8,3,7,2; int maxa; int parr[] 과 int *parr 은완전히같은선언이다. 둘다 int 형포인터변수이지만 int parr[] 는매개변수에서만선언가능하다. int parr[] 을함수내부에서선언하려면초기화가필요하다. maxa = MaxVal(arr, sizeof(arr)/sizeof(int)); printf (" 최대값 : %d\n", maxa); int MaxVal (int parr[], int n) int max, i; max = parr[0]; for (i=1; i<n; i++) if (max < parr[i]) max = parr[i]; return max; 최대값 : 8 < 결과 > 11
다차원배열과포인터
2 차원배열이름 2 차원배열이름이가리키는것 배열의이름은포인터 2차원배열의이름도포인터 아래의예시에서 a[0], a[1], a[2] 역시포인터이다. a a[0][0] = 1 a[0][1] = 2 a[0] 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] : 1245004 a[1] : 1245012 a[2] : 1245020 a : 1245004 a[1][0] = 3 a[1][1] = 4 a[2][0] = 5 a[1] a[2] a[2][1] = 6 13
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 비교 a a[0][0] = 1 a[0][1] = 2 a[0] a[0]+1 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); < 결과 > a[0] : 2030040 a[1] : 2030048 a[2] : 2030056 a[0] + 1 : 203044 a[1] + 1 : 203052 a : 2030040 a+1 : 2030048 a+2 : 2030056 a+1 a+2 a[1][0] = 3 a[1][1] = 4 a[2][0] = 5 a[2][1] = 6 a[1] a[1]+1 a[2] 14
2 차원배열의포인터타입비교 2 차원배열의포인트타입비교 2 차원배열의차이에따른포인터의주소값증가및감소비교 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); arr1 은 8 씩증가하지만 arr2 는 16 씩증가한다. 따라서 arr1 과 arr2 는같은 2 차원배열이지만포인터타입이다르다. < 결과 > arr1 : 1244996 arr1+1 : 1245004 arr1+2 : 1245012 arr2 : 1244956 arr2+1 : 1244972 arr2+2 : 1244988 15
2 차원배열과포인터 2 차원배열원소인배열의연산 a[i][j] 중 a[k] 가연산으로자신의배열의범위를넘을경우 a[k+1] 배열의영역을가리킨다. 아래예시. a[2] = a[0] + 4 = a[1] + 2 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)); < 결과 > *a[0] : 1 *(a[0]+1) : 2 *(a[1]+1) : 4 *(a[2]+1) : 6 *(a[0]+3) : 4 *(a[0]+4) : 5 a[0]+1 a[0]+3 = a[1]+1 a[0]+4 a[2]+1 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] a[1] a[2] 16
포인터의포인터
포인터의포인터 포인터의포인터 * 연산자를두개선언하는포인터로더블포인터라고도부른다. 더블포인터는포인터를가리킨다. 포인터가기본자료형변수의주소값을저장하는것처럼더블포인터는포인터의주소값을저장한다. int main() int val = 4; int *ptrl1 = &val; int **ptrl2 = &ptrl1; // 싱글포인터 // 더블포인터 18
더블포인터와메모리 더블포인터의메모리구조 더블포인터는포인터의첫바이트주소값을가리킨다. 포인터의주소값도최대 4 바이트이기때문에더블포인터자료형의크기도 4 바이트 12345678 val = 4 int val = 4; int *ptrl1 = &val; int **ptrl2 = &ptrl1; 12345690 ptrl1 = 12345678 12346800 ptrl2= 12345690 19
더블포인터의의한 call-by-reference 잘못구현된 call-by-reference 주소값만넘긴다고무조건 call-by-reference 인것이아니다. 함수호출전 pa : 10 pb : 20 함수호출후 pa : 10 pb : 20 < 결과 > 함수호출후에도값이바뀌지않는다. pswap 함수는 pa 와 pb 의주소값을복사에의해전달받았을뿐이다. p1, p2 가서로값을바꾸어도지역변수이기때문에 pa, pb 는기존의값을그대로유지하게된다. 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); void pswap (int *p1, int *p2) int *temp; temp = p1; p1 = p2; p2 = temp; 20
더블포인터의의한 call-by-reference 제대로작성된 call-by-reference 포인터pA 포인터pB 포인터 temp 더블포인터p1 포인터pA 더블포인터p2 포인터pB 10 변수 A 20 변수 B 10 변수 A 20 변수 B 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); < 결과 > void pswap (int **p1, int **p2) int *temp; temp = *p1; *p1 = *p2; *p2 = temp; 함수호출전 pa : 10 pb : 20 함수호출후 pa : 20 pb : 10 21
2 차원배열이름 2 차원배열이름과포인터 함수로전달하는 1 차원배열이름은포인터로받을수있다. 하지만 2 차원배열의경우더블포인터와다르다. int arr1[10]; int arr2[10][10]; OK Warning void function1(int *a) void function2(int **b) 더블포인터 b 가 arr2 라는 2 차원배열을받을때배열의첫주소값만넘기므로 b 는 arr2 가 10x10 인지 2x50 인지알수가없다. 22
참고 : 우선순위와결합성 우선순위 곱셈과덧셈이같이있으면곱셈부터계산하는것처럼모든연산자에는우선순위라는것이있다. 우선순위를신경쓰고싶지않으면계산하나하나 () 처리를하면된다. 연산순위연산자결합성 1 (), [], ->,. 2 sizeof, &( 참조 ), ++, --, ~,!, *( 간접참조 ), +( 부호 ), -( 부호 ) 3 *( 산술 ), /, % 4 +( 산술 ), -( 산술 ) 5 >>, << 6 <, <=, >=, > 7 ==,!= 8 & 결합성 우선순위가같은경우계산하는방향을말한다. 9 ^ 10 11 && 12 13?: ( 조건 ) 14 =, +=, -=, *=, /=, %=, &=, ^=, =, <<=, >>= 15,( 콤마 ) 23
배열포인터 배열포인터 배열포인터란배열을가리키는포인터이다. 2 차원배열의경우배열포인터를사용하여대응되는변환이가능하다. int arr[2][4]; 포인터타입. int (*parr)[4]; 배열포인터라고한다. 연산시 4( 크기 ) x 4byte (int) 씩건너뛰는포인터. arr[4] 형태의배열을가리키는포인터 24
배열포인터와포인터배열 배열포인터와포인터의배열의차이 포인터배열 int *parr[4]; 배열생성 int* int* int* int* parr[0] parr[1] parr[2] parr[3] 배열포인터 int arr[?][4] 배열 X 4 int (*parr)[4]; 가리킨다 int int int int int arr[4]; int int int int int arr[4]; X? 25
배열포인터 2 차원배열을함수의인자로받기 2 차원배열은더블포인터로받을수없다. 배열포인터를사용하여 2 차원배열을전달 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); void show_arr (int (*ptr)[4], int a) int i, j; printf("----start Print----\n"); for (i=0; i<a; i++) for(j=0; j<4; j++) printf("%d ", ptr[i][j]); printf("\n"); < 결과 > ----Start Print---- 1 2 3 4 5 6 7 8 ----Start Print---- 10 0 0 0 20 0 0 0 30 0 0 0 26