함수는 인자를 전달받도록 정의할 수 있다. 함수라는 이름은 인자의 전달과 값의 반환이 가능하기 때문이다. 


인자전달의 기본방식은 값의 복사이다!


함수 호출 시 전달되는 인자의 값은 매개변수에 복사가 된다.

즉, 복사가 되는 것 뿐이기 때문에 함수가 호출되고 나면, 전달되는 인자와 매개변수는 별개가 된다.


ex)

int Simple(int num) { . . . . . .}

int main(void)

{

int age = 17;

Simple(age);    // age에 저장된 값이 매개변수 num에 복사됨

. . . .   

}


실제로 전달되는 것은 age가 아닌, age에 저장된 값이다. 그리고 이 값이 매개변수 num에 복사되는 것이다. 따라서 num과 age는 아무런 관계도 아니다.

그렇다면 num의 값이 증가했다고 age은 값도 증가하는 것은 절대 아니다. 단순히 복사가 됐기 때문에!


다음 함수 호출시 인자로 배열을 전달하는 방법은 존재하지 않는다. 이유는 매개변수로 배열을 선언할 수 없기 때문이다.

그래서 배열에 접근할 수 있도록 배열의 주소값을 전달하는 것이다.


int arr[3] = {1,2,3};


위 처럼 배열이 선언되었다면 


simple(arr);    // simple함수를 호출하면서 배열 arr의 주소 값 전달


위 처럼 배열의 주소 값을 전달할 수 있다. 배열이름이 주소값이기 때문에!


그렇다면 simple함수의 매개변수는 int형 포인터 변수로 선언되어야 한다.


void Simple(int * ptr) { . . . . . .}


그 다음 매개변수 ptr을 이용해서 배열에 접근하는 방법은 다음과 같다. 포인터 변수를 이용해도 배열의 형태로 접근이 가능하기 때문에 다음과 같은 접근이 가능하다.(공부함)


printf("%d %d", ptr[0], ptr[1]);    // 첫 번째, 두 번째 요소 출력


ex)

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

cs

출력결과

1 2 3 

4 5 6 7 8 


위의 예제는 simple함수 내에서 외부에 선언된 배열의 값을 출력하고 있다. 


ex)이제 값의 변경을 해보자

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
#include<stdio.h>
 
void simple(int *ptr,int len)
{
    int i;
    for(i=0;i<len;i++)
    {
        printf("%d ",ptr[i]);
    }
    printf("\n");
}
 
void add(int *ptr,int len,int add)
{
    int i;
    for(i=0;i<len;i++)
    {
        ptr[i] += add;
    }
}
 
int main()
{
    int arr1[3]={1,2,3};
    int arr2[5]={4,5,6,7,8};
    
    add(arr1,sizeof(arr1)/sizeof(int),1);
    simple(arr1,sizeof(arr1)/sizeof(int));
    add(arr2,sizeof(arr2)/sizeof(int),2);
    simple(arr2,sizeof(arr2)/sizeof(int));
    
    return 0;
}

cs

출력결과

2 3 4 

6 7 8 9 10 


지금까지의 예제들은 int형 배열의 주소 값을 인자로 전달받을 수 있도록 int형 포인터 변수가 선언되어 있다.


void simple(int *ptr, int len){. . . .}

void add(int *ptr, int len, int add){. . . .}


위의 두 문장을 대신하여 다음과 같이 선언도 가능하다.


void simple(int ptr[], int len){. . . .}

void add(int ptr[], int len, int add){. . . .}


위의 int ptr[]과 int *ptr은 완전히 같은 동일한 선언이다. 그런데 전자의 선언이, 배열이 인자로 전달된다는 느낌을 더 강하게 주는 선언이다. 일반적으로 배열의 주소값이 인자로 전달될 때에는 int ptr[]형태의 선언을 주로 쓴다.


이 둘이 같은 선언으로 간주되는 경우는 매개변수의 선언으로 제한된다.


int arr[3]={1,2,3};

int *ptr = arr;     // int ptr[]=arr; 불가능


위의 코드는 대체가 불가능하다.


그 다음, call by value 와 call by reference에 대해서 공부를 하였다. 하지만 페**북으로 여러 사람들의 이야기를 들어본 결과, C언어에는 call by reference가 존재하지 않는다고 하였다. 이는 나에게 큰 충격이었고, 일단 두고 넘어가서 다음에 다른 책으로 다시 공부하여 작성하도록 하겠다. 일단 swap함수만 적어보겠다.


ex)swap함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
 
void swap(int n1, int n2)
{
    int temp;
    temp = n1;
    n1 = n2;
    n2 = temp;
    
    printf("n1:%d n2:%d \n",n1,n2);
}
 
int main()
{
    int num1 = 10, num2 = 20;
    
    printf("num1:%d num2:%d \n",num1,num2);
    swap(num1,num2);
    printf("num1:%d num2:%d \n",num1,num2);
    
    return 0;
}
 

cs

출력결과

num1:10 num2:20 

n1:20 n2:10 

num1:10 num2:20 


num1과 num2에 저장된 값이 변경되기를 기대했지만, 변하지 않고 swap함수 내에있는 n1과 n2에 저장된 값만 변경이 일어난다.

이는 매개변수 n1과 n2에 저장된 값을 변경시키는 것일 뿐, num1과 num2에 저장된 값의 변경으로까지 이어지지는 않는다. 이 둘은 완전 별개의 것이기 때문이다.


그렇다면 어떻게 해야 변경을 할 수 있는 것인가. 이는 간단하다. 18행의 swap의 매개변수에 값을 넘겨주지 않고, 주소값을 넘겨주고, 3행에 받는 매개변수를 주소값을 받는 int형 포인터로 변경하면 바뀔 것이다. 말이 어렵지 예를 보자.

ex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
 
void swap(int *n1, int *n2)
{
    int temp;
    temp = *n1;
    *n1 = *n2;
    *n2 = temp;
}
 
int main()
{
    int num1 = 10, num2 = 20;
    
    printf("num1:%d num2:%d \n",num1,num2);
    swap(&num1,&num2);
    printf("num1:%d num2:%d \n",num1,num2);
    
    return 0;
}
 

cs

출력결과

num1:10 num2:20 

num1:20 num2:10 


이렇게 swap이 됨을 확인할 수 있다. swap함수를 보면 주소값을 인자로 받아서 해당 변수에 직접 접근하는 형태를 띤다. main함수의 16행이 실행되면 n1과 n2는 각각 num1과 num2를 가리키는 상황이 된다.따라서 *n1은 num1을 의미하고 *n2는 num2를 의미하게 된다. 


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


int main(void)

{

int num;

scanf("%d",&num);    // 변수 num의 주소값을 scanf함수에 전달

....

}


위의 scanf 함수호출이 완료되면 변수 num에는 값이 채워진다. 즉 프로그램 사용자로부터 값을 입력 받아서 변수 num에 그 값을 채우는 일을 scanf 함수가 하는 것이다. 그리고 이를 위해 변수 num의 주소 값을 알아야 한다. 그래야 변수 num에 접근을 해서 값을 채워 넣을 수 있기 때문이다. 그래서 scanf 함수호출 시에 변수 num의 주소 값을 전달하고 있다.

그런데 문자열을 입력받을 때에는 왜 & 연산자를 붙여주지 않는가 ? 이는 간단하다. 문자열이 저장되는 배열의 이름 str 은 그 자체로 배열의 주소 값이다. 그 자체인 주소값을 전달해주기 때문에 &연산자를 붙이지 않고도 주소값이 전달이 된다. 


변수를 상수화 하는 const는 포인터 변수를 대상으로도 가능하다.


포인터 변수가 참조하는 대상의 변경을 허용하지 않는 const 선언

int main(void)

{

int num=20;

const int *ptr = &num;

*ptr = 30; // 컴파일 에러

num = 40;  // 컴파일 성공

. . . .

}


코드는 " 포인터 변수 ptr을 이용해서 ptr이 가리키는 변수에 저장된 값을 변경하는 것을 허용하지 않겠다." 라는 뜻

따라서 *ptr=30; 은 에러가 발생한다. 그렇다고 포인터 변수 ptr이 가리키는 변수 num이 상수화가 되는 것이 아니다.

num = 40; 이 변경은 허용한다.!


또 한, 포인터 변수를 상수화 할 수 있다.

int main(void)

{

int num1=20;

int num2=30;

int * const ptr = &num1;

ptr = &num2; // 컴파일 에러

*ptr = 40;   // 컴파일 성공

. . . .

}

위 코드는 상수화 된 포인터 변수 ptr이 num1을 가리키고 있다. 그런데 그 다음 행에서, 가리키는 대상을 num2로 바꾸는 연산을 진행하고 있다. 하지만 ptr은 상수이기 때문에 이 부분에서 컴파일 에러가 발생한다. 하지만 ptr이 상수일 뿐이니, ptr이 가리키는 대상에 저장된 값을 변경하는 연산은 문제가 되지 않는다.


const int * const ptr = &num ;


이렇게 선언이 되면 *ptr = 20;이 불가능 해지고, ptr = &num2; 가 불가능 해진다.


const의 선언을 하는 이유는 프로그램 코드의 안전성이 높아진다. 예를 들어 double PI=3.1415;로 저장해 뒀으나, 

밑에 생각지도 못하게 PI=3.07; 이라는 문장이 들어가버리면 이는 컴파일 에러도 뜨지 않을 뿐더러, 출력이 잘 나온다. 하지만 내가 지정한 값이 나오지 않기 때문에 이를 모르고 지나치지 일쑤다. 즉 컴파일러가 문제점을 발견하지 못한다는데에 있다. 하지만 const double PI=3.1415;로 지정한 후에 나도 모르게 문장의 중간에 PI=3.07; 이라는 문장을 적었을 시에 이는 컴파일 시 에러가 발생한다. 그만큼 코드의 안정성이 높아진다.

'programming > C' 카테고리의 다른 글

C // 포인터의 포인터  (0) 2017.12.23
C // 다차원배열  (0) 2017.12.22
C // 포인터와 배열  (0) 2017.12.20
C // 포인터  (0) 2017.12.19
C // 1차원배열  (0) 2017.12.18

+ Recent posts