728x90
728x90
이번 단원에 알아두어야 할 핵심개념 : call-by-reference, call-by-value

 

함수의 인자로 배열 전달하기

기본적인 인자 전달 방식 : 값의 복사에 의한 전달

정확히는 val을 전달하는 게 아닌, val이 지니고 있는 값을 전달
'복사'해서 a를 초기화하고 있다.

 

배열의 함수 인자 전달 방식 : 배열이름(배열주소, 포인터)에 의한 전달

배열전체를 복사하여 함수의 인자로 넘길 수는 없다 (나중에 구조체로는 가능)
그래서 주소값만 복사해 전달하여 사용한다.

배열 전달의 예

#include <stdio.h>

void fct(int *arr2);

int main ()
{

  int arr1[2] = { 1, 2 }; //arr1 배열이름이자 포인터는 int형 포인터, 주소를 가리킨다

  fct (arr1); // arr1이 지닌 주소값을 배열의이름으로 전달하면서 fct 함수를 호출하면서 전달한다
  printf ("%d\n", arr1[0]);

  return 0;
}

void fct(int *arr2) 
{
    // arr2는 arr1의 주소값을 받는다
    // int형 변수의 주소값이기 때문에 int형 포인터여야 한다 **
    printf("%d\n", arr2[0]); 
    // *(arr2+0)와 같은 문장...
    // *(arr2+0)이라는 주소값이 가리키는 메모리공간을 참조하라
    // 1 출력
    
    arr2[0]=3;
    // *(arr2+0) = 3; 과 같은
}

 

 

배열이름, 포인터의 sizeof 연산

배열이름 : 배열전체 크기를 바이트로 반환
포인터 : 포인터의 크기를 바이트로 반환

#include <stdio.h>

int main()
{
    int arr[5];
    int* pArr=arr;
    
    printf("%d\n", sizeof(arr)); //배열 이름 : 배열전체 크기를 바이트단위로 반환 (20)
    printf("%d\n", sizeof(pArr)); // 포인터의 크기를 바이트단위로 반환(4 or 8)
    //printf("%d\n", sizeof(*pArr)); //4
     
    printf("%d\n", sizeof(int)); // 4byte
    
    return 0;
}

 

배열의 길이는 인자로 전달받은 함수 내에서 계산이 불가능하고
함수를 호출할 때 아래처럼(sizeof(배열)/sizeof(자료형) 계산하여 전달을 해야 한다...

#include <stdio.h>

int ArrAdder(int* pArr, int n);

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int SumOfArr;

	// 배열의 길이가 바뀌어도 아래 코드는 그대로 쓸 수 있다.
    SumOfArr = ArrAdder(arr, sizeof(arr)/sizeof(int));
    // SumOfArr = ArrAdder(arr, 10); //하드코딩
    // sizeof(int) int의 크기는 시대에 따라 다를 수 있다
    
    printf("배열의 길이체크 : %d\n", sizeof(arr)); //40
    printf("배열의 총 합 : %d\n", SumOfArr); 
    
    return 0;
}

//배열의 시작번지, 배열의 길이
int ArrAdder(int* pArr, int n)
{
    int sum = 0;
    int i;
    
    // n 대신 sizeof(pArr)를 쓰면 배열의 길이가 아니고 포인터 크기가 반환
    for(i=0; i<n; i++)
        sum+= pArr[i];
    
    return sum;
}

 

 

int* pArr vs int pArr[ ]

int* pArr을 많이 쓰기를 권해드림. 배열과 포인터를 혼동하기 때문이다.
둘은 같은 의미이나, 선언 int pArr[ ] 은 함수의 매개 변수 선언 시에만 사용 가능
매개변수 선언 시가 아니면 불가

// int function(int* pArr)
// 변수의 주소값 전달인지, 배열의 이름 전달인지 알수가 없다

// 배열이 인자로 전달됨을 알 수 있다 -- 의미명확
// 매개변수 선언 시 아래 방식을 선호한다

// 배열의 이름이 아니고 진짜 포인터다
// sizeof 연산 시 포인터 크기가 나온다
int function(int pArr[]) 
{
	int a = 10;
	pArr = &a;
	return *pArr;
}

 

배열이 갖고있는 요소 중 최대값을 반환하는 예

#include <stdio.h>

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

int main(void)
{
    int arr[10] = {4,8,3,7,2};
    int max;

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

// 배열의 시작번지, 배열의 길이
// int pArr[]은 포인터변수
// int MaxVal(int *pArr, int n)
 int MaxVal(int pArr[], int n) 
{
    int max, i;
    
    printf("sizeof(pArr): %d\n", sizeof(pArr));

    max=pArr[0];    
    for(i=1; i<n; i++)
        if(max<pArr[i])
            max=pArr[i];
    
    return max;
}
#include <stdio.h>

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

int main(void)
{
    int arr[10] = {4,8,3,7,2};
    int max;
	
    //int *p;
    //int p[]; //error - 배열 선언인데 size 생략했다고 판단됨
    
    max = MaxVal(arr, sizeof(arr)/sizeof(int));
    printf("최대값 : %d\n", max); 
    
    return 0;
}

// 배열의 시작번지, 배열의 길이
// int pArr[]은 포인터변수
// int MaxVal(int *pArr, int n)
 int MaxVal(int pArr[], int n) 
{
    int max, i;
    
    printf("sizeof(pArr): %d\n", sizeof(pArr));

    max=pArr[0];    
    for(i=1; i<n; i++)
        if(max<pArr[i])
            max=pArr[i];
    
    return max;
}

 

 

Call-By-Value

값의 복사에 의한 함수의 호출
call : 함수 호출을 뜻함
가장 일반적인 함수 호출 형태

#include <stdio.h> 

int add(int a, int b);

int main() 
{ 
    int val1 = 10;    
    int val2 = 20;
    
    printf("결과: ", add(val1, val2)); 
    //call by value
    
    return 0; 
}

// 복사
int add(int a, int b)
{
    // val1과 다른 변수
    return a+b;
}

 

call-by-value에 의한 swap

swap함수 : 서로 바꾸는 기능을 하는 것
가짜 swap? 찐 swap은 val2와 val1의 진짜 값이 변경됨
직접적으로 val1, val2의 값을 바꿀 수 없고, 코드 실행 후에도 그대로이다.

#include <stdio.h>

int main()
{
    int val1=10;
    int val2=20;
    swap(val1, val2);
    
    printf("val1 : %d\n", val1);
    printf("val2 : %d\n", val2); 

    return 0;
}

void swap(int a, int b)
{
    int temp=a;
    a= b;
    b=temp;
    
     printf("a : %d\n", a); 
     printf("b : %d\n", b); 
}

 

note : previous implicit declaration of 'swap' was here swap(...
묵시적 선언 오류 : 함수 선언을 하지 않고 먼저 사용했다는 뜻.

묵시적 선언 오류 수정 후

#include <stdio.h>

void swap(int a, int b);

int main()
{
    int val1=10;
    int val2=20;
    swap(val1, val2);
    
    printf("val1 : %d\n", val1);
    printf("val2 : %d\n", val2); 

    return 0;
}

void swap(int a, int b)
{
    int temp=a;
    a= b;
    b=temp;
    
     printf("a : %d\n", a); 
     printf("b : %d\n", b); 
}

 

 

call-by-reference

참조(참조를 가능케하는 주소값)를 인자로 전달하는 형태의 함수 호출
main함수 내 지역변수에 접근할 수 있었다.

#include <stdio.h>

int main()
{
    int val = 10;
    
    adder(&val);
    // val의 주소 전달
    
    printf("val: %d", val);
    // 11 출력

    return 0;
}

void adder(int* pVal)
{
    // val의 주소값이 전달됨 (Ox10 등..)
    
    // 포인터변수 pVal이 가리키는 변수의 값 1 증가
    (*pVal)++;
}

 

call-by-reference에 의한 swap
#include <stdio.h>

int main()
{
    int val1 = 10;
    int val2 = 20;
    
    printf("before val1: %d\n", val1);
    printf("before val2: %d\n", val2);
    
    swap(&val1, &val2); //주소전달
    
    printf("After val1: %d\n", val1);
    printf("After val2: %d\n", val2);

    return 0;
}

void swap(int* a, int* b)
{
    int temp = *a;
    *a=*b;
    *b=temp;
}

 

 

call-by-value는 값을 전달 / call-by-reference는 주소 값을 전달하는 함수 호출이다.
call-by-value는 값을 복사해 전달해서 다른 메모리공간을 접근, 값을 변경할 수 없고
call-by-reference는 주소값을 전달해서 주소값에 해당하는 메모리공간에 접근 가능하다.

 

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

scanf는 메인함수의 val에 접근하여 값을 대입하는 것
접근하려면 주소값을 알아야 한다. (call-by-reference 방식)

#include <stdio.h>

int main()
{
    int val;
	scanf("%d", &val);
#include <stdio.h>

int main()
{
    char str[100];
    printf("문자열 입력 : ");
	scanf("%s", str);
    // 배열 이름이 주소값이기 때문에 &를 붙이지 않는다

 

포인터와 const 키워드

const는 선언 첫부분, 포인터 이름 앞에, 두 군데 모두 붙일 수 있다.

1. 포인터가 가리키는 변수의 상수화

int a = 10;
const int* p = &a; 
// 포인터가 가리키는 대상(a)을 상수화
// 포인터를 이용한 변경이 불가하다
// p의 관점에서 a가 상수화

// *p=30; //error
a=30; //ok

 

2. 포인터 상수화

int a = 10;
int b = 20;

int* const p = &a; 
// 포인터가 갖고있는 값 자체를 상수화
// 한번 가리킨 포인터의 대상 변경 x 주소값 변경 ㄴ
// 변수는 변경 가능

// p=&b //error
*p=30; //ok

 

3. 둘 다

const int* const p = &a;

 

 

const  키워드를 사용하는 이유

컴파일 시 잘못된 연산에 대한 에러메시지
프로그램을 안정적으로 구성

#include <stdio.h> 
float PI = 3.14;

int main() 
{ 
    float rad;
    PI = 3.07; //실수..바뀌면 안되는 대상
    // 그러나 컴파일에러도 없다
    // 경고메세지까지 구현했어야 함
    
    scanf("%f", &rad);
    printf("원의 넓이는 %f\n ", rad*rad*PI);
    
    return 0; 
}

 

아래처럼 컴파일에러가 발생될 수 있게 하여 안정적으로 코드를 짜도록 한다.

#include <stdio.h> 
const float PI = 3.14;

int main() 
{ 
    float rad;
    PI = 3.07; // 컴파일에러 발생
    
    scanf("%f", &rad);
    printf("원의 넓이는 %f\n ", rad*rad*PI);
    
    return 0; 
}

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v