ch09 함수

 


함수란?

  • 프로그래밍에서 함수(function)란 하나의 특별한 목적의 작업을 수행하기 위해 독립적으로 설계된 프로그램 코드의 집합으로 정의할 수 있다.
  • C 프로그램은 이러한 함수들로 구성되며, 포함된 함수들을 사용하여 프로그램의 목적을 달성한다.

 

함수를 사용하는 이유

  • 함수를 사용하는 가장 큰 이유는 바로 반복적인 프로그래밍을 피할 수 있기 때문이다.
  • 프로그램에서 특정 작업을 여러 번 반복해야 할 때는 해당 작업을 수행하는 함수를 작성해두고 필요할 때마다 작성한 함수를 호출하면 해당 작업을 반복해서 수행할 수 있다.
  • 또한, 프로그램을 여러 개의 함수로 나누어 작성하면, 모듈화로 인해 전체적인 코드의 가독성이 좋아진다.
  • 그리고 프로그램에 문제가 발생하거나 기능의 변경이 필요할 때에도 손쉽게 유지보수를 할 수 있다.

 

함수의 정의

C언어에서 사용자 정의 함수를 정의하는 방법은 다음과 같다.

 

 
  1. 반환 자료형 : 함수가 모든 작업을 마치고 반환하는 데이터의 타입을 명시한다.
  2. 함수 이름 : 함수를 호출하기 위한 이름을 명시한다.
  3. 매개변수 목록(parameters) : 함수 호출 시에 전달되는 인수의 값을 저장할 변수들을 명시한다.
  4. 함수 몸체 : 함수의 고유 기능을 수행하는 명령문의 집합이다.

** 함수 호출 시에는 여러 개의 인수를 전달할 수 있지만, 함수가 반환할 수 있는 값은 1개를 넘지 못한다.

** 또한, 함수의 특성에 따라 인수나 반환값이 하나도 없는 함수도 존재할 수 있다.

 

 

프로토타입 선언

#include <stdio.h> 

int maxNum (int, int); /* 함수의 원형 선언 */

int main(void)
{
    int result;  
    result = maxNum(3, 5); // 함수의 호출
    printf("두 수 중 더 큰 수는 %d입니다.\n", result);
    result = maxNum(3, 1); // 함수의 호출
    printf("두 수 중 더 큰 수는 %d입니다.\n", result);
    result = maxNum(7, 5); // 함수의 호출
    printf("두 수 중 더 큰 수는 %d입니다.\n", result);
    return 0;
}  

int maxNum(int num1, int num2) /* 함수의 정의 */
{
    if (num1 >= num2)
    {
        return num1;
    }
    else
    {
        return num2;
    }
}
  • C언어에서는 가장 먼저 main() 함수가 컴파일된다.
  • 위의 예제에서 컴파일러는 main() 함수에 등장하는 maxNum() 함수를 아직 모르기 때문에 컴파일 오류가 발생한다.
  • 따라서 컴파일러에게 maxNum() 함수는 나중에 정의되어 있다고 알려줘야 하는데 그 역할을 하는 것이 바로 함수의 원형(prototype) 선언이다. 

 

함수의 데이터형

  • 함수도 데이터형으로 선언해야 한다.
    • 리턴값이 있는 함수는 리턴값과 같은 데이터형으로 선언해야 한다.
    • 리턴값이 없는 함수는 void형으로 선언해야 한다.
  • 함수에 데이터형을 선언하지 않으면, 구형 C 컴파일러는 그 함수를 int형이라고 가정한다.

 

 


 

 

문제 01 

  • 간단한 편지지 인쇄문구를 표시하는 프로그램에서 애스터리스크(*) 40개를 한 라인에 출력하는 함수 사용하기
  • 구성 : main(), starbar() 
/* 애스터리스크(*) 40개를 한 라인에 표시하는 함수 */

#include <stdio.h>

#define NAME "Shin Jeonghee"
#define ADDRESS "Gwangsan-gu, Gwangju, Republic of Korea"
#define PLACE "37, Sochon-ro 152beon-gil"
#define WIDTH 40

/* 함수 프로토타입 */
void starbar(void);  

/* 함수 사용 */
int main(void)
{
    starbar();
    printf("%s\n", NAME);
    printf("%s\n", ADDRESS);
    printf("%s\n", PLACE);
    starbar();       
    
    return 0;
}

/* 함수 정의 */
void starbar(void)  
{
    int count;
    
    for (count = 1; count <= WIDTH; count++)
        putchar('*');
    putchar('\n');
}

 

문제01 프로그램 분석

  • 프로그램은 식별자 starbar를 세 군데에서 사용하고 있다.
    • 첫 번째 : starbar()가 어떤 유형의 함수인지 컴파일러에게 알려주는 함수 프로토타입이다.
      void starbar(void);
    • 두 번째 : 그 함수를 실행되게 만드는 함수 호출이다.
      starbar();
    • 세 번째 : 그 함수가 무엇을 하는 것인지 정확하게 서술하는 함수 정의이다.
  1. [함수 프로토타입] void starbar(void);
    • 변수들과 마찬가지로 함수도 데이터형을 가진다. 함수를 사용하는 프로그램은 그것을 사용하기 전에 그 함수의 데이터형을 미리 선언해야 한다. 결과적으로 다음과 같은 ANSI C 프로토타입이 main() 함수 정의보다 앞에 온다.
    • 여기서 괄호는 starbar가 함수 이름이라는 것을 나타낸다.
    • 첫 번째 void가 함수의 데이터형이다. void형은 그 함수가 값을 리턴하지 않는다는 것을 나타낸다.
    • 두 번째 void는 그 함수가 전달인자를 사용하지 않는 다는 것을 나타낸다. 세미콜론은 그 곳에서 함수를 정의하고 있는 것이 아니라 함수를 선언하고 있다는 것을 나타낸다. 즉, 이 라인은, 프로그램이 starbar()라는 함수를 사용하는데 그 함수가 리턴값이나 전달인자가 없고 컴파일러는 어딘가에 있을 이 함수의 정의를 반드시 찾아야 한다고 공표한다.
      • ANSI C 프로토타입을 인식하지 못하는 컴파일러들의 경우에는 함수의 데이터형을 다음과 같이 선언해야 한다.
        void starbar(); //
      • 매우 오래된 일부 컴파일러들은 void형을 인식하지 못한다는 점을 유의해야 한다. 이런 경우 리턴값이 없는 함수들에 대해 int형을 사용해야 한다.
    • 일반적으로 프로토타입은 함수 반환 값의 형과 예상되는 전달인자(argument)의 형을 모두 명시한다. 총괄적으로 이 정보는 함수의 시그니처(signature)라고 하는데, 리턴값도 없고 전달인자도 없는 함수를 말한다.
    • 프로그램은 starbar()의 함수 프로토타입을 main() 앞에 놓는다. 그렇게 하지 않고, main() 내부의 변수를 선언하는 장소에 그것을 놓을 수도 있다.
  2. [함수 호출] starbar();
    • 프로그램은 함수 이름에 괄호를 두고, 그 괄호 뒤에 세미콜론을 붙여서 main()에서 starbar() 함수를 호출한다.
    • 이것은 void형 함수를 호출하는 형식이다. 
    • 컴퓨터는 starbar();문을 만날 때마다 starbar() 함수를 찾아서, 그곳에 있는 명령들을 수행한다.
    • starbar() 안에 있는 코드들을 모두 수행하고 나서, 컴퓨터는 호출 함수(이 문제의 경우 main() 다음 라인)으로 복귀한다.
  3. [함수 정의] void starbar(void) { … }
    • 프로그램은 main() 함수를 정의하는 것과 동일한 형식으로 starbar()를 정의한다.
    • 함수의 데이터형, 함수 이름, 괄호로 시작하고 여는 중괄호를 넣고, 함수에서 사용할 변수들을 선언하고, 함수의 문장들을 정의하고, 닫는 중괄호로 끝을 맺는다.
    • 여기서는 starbar()에 세미콜론을 붙이지 않는다. 세미콜론이 없으면 컴파일러는 이것을 프로토타입이나 호출이 아닌 정의로 인식한다.
  4. starbar() 안에 들어 있는 변수 count는 지역 변수이다. 이것은 그 변수가 starbar()에게만 알려진다는 것을 뜻한다.
    • main()뿐만 아니라 다른 함수에서도 count라는 이름을 사용할 수 있지만 이들은 서로 충돌하지 않는다.
    • 별개의 독립된 변수들을 같은 이름으로 사용하는 것일 뿐 !!

 

 

 


 

 

 

문제 02

  • 문제 01에서 사용한 편지지 인쇄문구를 가운데로 정렬하여 표시하는 프로그램을 작성한다.
    • 문구를 표시하기 전에 필요한 만큼의 스페이스를 앞에 넣으면 그 문구를 가운데로 정렬시킬 수 있다.
    • 이것은 지정된 수만큼 별표를 표시하는 starbar()와 비슷한데, 여기서는 어떤 수만큼 스페이스를 넣어야 한다. 각각 작업을 따로 수행하는 두 개의 함수를 작성하는 대신에, 두 작업을 모두 수행할 수 있는 일반적인 하나의 함수를 작성한다.
  • 전달인자가 어떻게 동작하는지 강조하기 위해, 이 프로그램은 다양한 형식의 전달인자를 사용한다.
/* 애스터리스크(*) 40개를 한 라인에 표시하는 함수 */

#include <stdio.h>

#define NAME "Shin Jeonghee"
#define ADDRESS "Gwangsan-gu, Gwangju, Republic of Korea"
#define PLACE "37, Sochon-ro 152beon-gil"
#define WIDTH 40

/* 함수 프로토타입 */
void starbar(void);  

/* 함수 사용 */
int main(void)
{
    starbar();
    printf("%s\n", NAME);
    printf("%s\n", ADDRESS);
    printf("%s\n", PLACE);
    starbar();       
    
    return 0;
}

/* 함수 정의 */
void starbar(void)  
{
    int count;
    
    for (count = 1; count <= WIDTH; count++)
        putchar('*');
    putchar('\n');
}

 

실전달인자와 형식매개변수

  • 실전달인자는 함수 호출에서 괄호 안에 나타나는 값이다.
  • 형식매개변수는 함수 정의에서 함수 머리에 선언되는 변수이다.
  • 함수가 호출될 때 형식매개변수로 선언된 변수들이 생성되고, 형식매개변수는 실전달인자를 평가하여 얻어지는 값으로 초기화된다.

 

전달인자를 사용하는 함수의 정의 : 형식매개변수

문제02 프로그램 분석 (1)

  • 함수 정의는 void show_n_char (char c, int num) 이라는 함수 헤더로부터 시작한다.
  • 이 라인은 show_n_char()가 char형 ch와 int형 num이라는 두 개의 전달인자를 사용한다는 것을 컴파일러에게 알린다.
    • 두 변수를 '형식 전달인자(formal argument)' 또는 '형식 매개변수(formal parameter)'라고 부른다.
    • 함수 안에서 정의되는 변수와 마찬가지로, 형식매개변수도 지역 변수로서 그 함수에게만 알려진다.
    • 이 변수들은 그 함수가 호출될 때마다 값이 대입된다.
  • ANSI C 형식은 변수 앞에 그 변수의 데이터형을 각각 따로 지정할 것을 요구한다. 보통의 변수 선언과는 달리, 동일한 데이터형을 사용하는 변수들의 리스트를 나열할 수 없다.
    - void dibs(int x, y, z)             // 잘못된 함수 헤더
    - void dubs(int x, int y, int z)  // 올바른 함수 헤더

 

전달인자를 사용하는 함수의 프로토타입

문제02 프로그램 분석 (2)

 

void show_n_char(char ch, int num); 

  • 함수를 사용하기 전에 먼저 선언하기 위해 ANSI 프로토타입을 사용한다.
  • 함수가 전달인자를 사용할 때, 프로토타입은 전달인자의 개수와 데이터형을 표시하기 위해 콤마로 분리된 데이터형 리스트를 사용한다.

void show_n_char(char, int) ;

  • 원한다면 프로토타입에서 변수 이름을 생략할 수 있다.
  • 목적은 몇개의, 어떤 데이터형의 전달인자로 받을 것인지를 먼저 알리는 것 !!

 

실전달인자를 사용하는 함수의 호출

문제02 프로그램 분석 (3)

 

show_n_char(SPACE, 12);

  • 여기서 실전달인자는 스페이스 문자와 12이다. 이 값들이 show_n_char()의 대응하는 형식매개변수(ch와 num)에 각각 대입된다.
  • 요약하면 형식매개변수는 피호출 함수에 있는 변수이고, 실전달인자는 호출 함수에 의해 형식매개변수에 대입되는 특정한 값이다.
  • 상수, 변수, 복잡한 표현식도 실전달인자가 될 수 있다. 그것이 무엇이든간에 실전달인자는 하나의 값으로 평가되며, 함수의 대응하는 형식매개변수에 그 값이 복사된다.

show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);

  • 두 번째 실전달인자를 구성하는 긴 표현식은 10으로 평가된다. 그러고 나서 값 10이 형식매개변수 num에 대입된다. 함수는 그 값이 상수로부터 왔는지, 변수로부터 왔는지, 표현식으로부터 왔는지 알지 못할 뿐만 아니라 관심도 없다.
  • 피호출 함수(called function)는 호출 함수(calling function)로부터 복사된 데이터를 가지고 작업하기 때문에, 피호출 함수가 사본 데이터에 어떠한 조작을 가하더라도, 호출 함수에 있는 원래의 데이터는 보호된다.

  

 


 

 

문제 03

  • 문제 01~02는 호출 함수에서 피호출 함수로 정보를 전달하는 방법을 살펴보았다.
  • 반대 방향으로 정보를 보내려면 함수의 리턴값을 사용한다.
  • 두 전달인자 중 작은 값을 리턴하는 함수를 작성한다.
/* 둘 중에서 작은 것을 구한다 */

#include <stdio.h>

int imin(int, int);

int main(void)
{
    int evil1, evil2;
    
    printf("정수 두 개를 입력하세요. (종료하려면 q 입력) : \n");
    while (scanf("%d %d", &evil1, &evil2) == 2)
    {
        printf("%d, %d 중 작은 값은 %d 입니다.\n",
               evil1, evil2, imin(evil1,evil2));
        printf("정수 두 개를 입력하세요. (종료하려면 q 입력) : \n");
    }
    printf("-종료-\n");
    
    return 0;
}

int imin(int n,int m)
{
    int min;
    
    if (n < m)
        min = n;
    else
        min = m;
    
    return min;
}

 

문제03 프로그램 분석

 

while (scanf("%d %d", &evil1, &evil2) == 2)

  • scanf()는 성공적으로 읽은 아이템의 수를 리턴하기 때문에, 두 개 이상의 정수를 입력하면 while 루프가 끝난다.

int imin(int n, int m){ … return min; }

  • 키워드 return은 뒤에 오는 표현식의 값을 함수의 리턴값으로 만든다.
  • imin() 함수는 min에 대입된 값을 리턴한다. min이 int형이기 때문에imin() 함수도 int형이다.
  • 변수 min은 imin()에만 알려진다. 그러나 min의 값은 키워드 return을 통하여 호출 함수로 리턴된다.

lesser = imin(n, m);

  • imin() 함수를 실행하고 얻어낸 min의 값을 lesser 변수에 대입하는 것이다.
  • 변수 min은 imin()에서만 사용되는 지역 변수 이므로 함수 내에서만 사용이 가능하다.

 

함수의 리턴값

  • 리턴값은 변수에 대입할 수도 있고, 표현식의 일부로 사용할 수도 있다.
    • answer = 2 * imin(z, zstar) + 25;
    • printf("%d\n", imin(-32 + answer, LIMIT));
  • 리턴값은 변수에 의해서만이 아니라 어떤 표현식에 의해서도 리턴될 수 있다. 예를 들면, 문제03 프로그램을 다음과 같이 간략하게 줄일 수 있다.
    • /* 문제 02 최소값 함수 수정 버전 */
      imin(int n, int m)
      {
          return (n < m) ? n : m;    // 어느 것이 작은가에 따라 n 또는 m이 된다. 그 값이 호출 함수로 리턴된다.
      }
  • 선언된 데이터형이 아닌 다른 데이터형을 리턴하면 지시된 리턴값을 선언된 리턴형의 변수에 대입했을 때 얻어지는 값이 실제 리턴값이 된다.
    • result = what_if(64);
      int what_if (int n)
      {
          double z = 100.0 / (double) n;      // z에는 1.5625가 대입된다. 
          return z;                                        // 리턴문은 int형 값 1을 리턴한다.
  • return을 사용하는 것은 함수를 종료시키고, 호출 함수에 있는 다음 문장으로 제어를 넘긴다. 이것은 return문이 그 함수의 마지막 문장이 아닌 겨웅에도 적용된다.
    • /* 문제 02 최소값 함수 수정 버전 */
      imin(int n, int m)
      {
          if (n < m)
              return n;
          else
              return m;
      }

  

 


 

 

문제 04 

  • ANSI C 이전 형식으로 함수를 선언하고, imax()를 부정확하게 사용하는 프로그램
/* 부정확한 함수 사용 */

#include <stdio.h>

int imax();   // ANSI C 이전 형식의 함수 선언

int main(void)
{
    printf("%d, %d 중에 큰 값은 %d 입니다.\n",
           3, 5, imax(3));
    printf("%d, %d 중에 큰 값은 %d 입니다.\n",
           3, 5, imax(3.0, 5.0));
    return 0;
}

int imax(n, m)
int n, m;
{
    return (n > m ? n : m);
}

 

문제04 프로그램 분석

 

printf("%d, %d 중에 큰 값은 %d 입니다.\n", 3, 5, imax(3));

printf("%d, %d 중에 큰 값은 %d 입니다.\n", 3, 5, imax(3.0, 5.0));

  • printf()의 첫 번째 호출은 imax()의 전달인자를 하나 빼먹었다. 두 번째 호출에서는 정수 대신에 부동소수점 수를 사용했다.
  • 이러한 에러에도 불구하고, 프로그램은 컴파일되고 실행된다. 
  • 호출 함수는 전달인자를 스택(stack)이라는 임시 저장 공간에 놓는다. 피호출 함수는 그 스택으로부터 전달인자를 읽는다.
    이 두 과정은 서로 부합되도록 일어나지 않는다. 호출 함수는 호출에서 실전달인자에 기초하여 전달할 데이터형을 결정한다.
    • 피호출 함수는 형식매개변수의 데이터형에 기초하여 값들을 읽는다. 그러므로, 함수 호출 imax(3)은 스택에 하나의 정수만 놓는다. imax() 함수가 동작을 시작할 때 스택에서 두 개의 정수를 읽는데 실제로는 하나의 값만 있기 때문에 두 번째로 읽은 값은 그 시점에서 우연히 스택에 놓여있는 어떤 값이다.
  • float형은 전달인자로 전달될 때 double형으로 올림 변환이 일어난다. 즉 두 개의 double형 값을 스택에 놓는다. 이것들은 64비트 값이고, 스택에는 128비트의 데이터가 놓인다.
    • imax()가 스택에서 두 개의 int를 읽을 때, int는 32비트 이기 때문에 스택에 있는 첫 64비트를 읽는다. imax()는 이 두 32비트를 두 개의 정수값으로 읽는다. 

문제 05

  • 문제 05는 문제 04에서 함수 프로토타입을 사용하도록 수정한 것이다.
/* 함수 프로토타입 사용 */

#include <stdio.h>

int imax(int, int);   // 함수 프로토타입

int main(void)
{
    printf("%d, %d 중에 큰 값은 %d 입니다.\n",
           3, 5, imax(3, 5));
    printf("%d, %d 중에 큰 값은 %d 입니다.\n",
           3, 5, imax(3.0, 5.0));
    return 0;
}

int imax(int n, int m)
{
    return (n > m ? n : m);
}

 

문제05 프로그램 분석

 

int imax(int, int);

  • 불일치한 전달인자가 발생시키는 문제점에 대한 ANSI C 표준의 해결책은 함수 선언에서 변수들의 데이터형까지도 선언하도록 허용하는 것이다. 
  • 이 정보를 이용하여 컴파일러는 함수 호출이 프로토타입과 일치하는지 검사를 할 수 있다. 전달 인자의 개수가 맞는지, 그들의 데이터형이 올바른지 검사한다.
  • 데이터형이 프로토타입과 일치하지 않는다면 컴파일러는 캐스트 연산을 적용하여 실전달인자들의 값을 형식매개변수와 일치하는 데이터형으로 변환한다.

  

 


 

p 415 ~ 424 재귀함수

p 425 ~ 431 둘 이상의 소스 코드 파일로 된 프로그램의 컴파일 

 


 

 

포인터 (1)

  • C에서 가장 중요한 개념 중 하나가 포인터(pointer)이다.
  • 포인터는 주소(address)를 저장하는 데 사용되는 변수이다.
    • 포인터는 주소를 값으로 가지는 변수이다. 
    • char형 변수가 문자를 값으로 가지고, int형 변수가 정수를 값으로 가지는 것처럼 포인터 변수는 주소를 값으로 가진다.
  • scanf()가 전달인자들을 위해 주소를 사용할 때 잠깐 봤는데, 좀더 일반적으로 말하면 return값을 통하지 않고 호출 함수에 있는 값을 변경하는 모든 함수들은 주소를 사용한다.

 

주소 알아내기 : &연산자

  • 단항 연산자 &는 어떤 변수가 저장되어 있는 주소를 알아낸다.
  • pooh가 어떤 변수의 이름이라면, &pooh는 그 변수의 주소이다.
  • 주소는 메모리의 위치라고 생각하면 된다.

 

문제 12

  • 같은 이름이지만 서로 다른 함수에서 사용하는 변수들이 어디에 저장되는지 알아보기 위해 주소연산자를 사용한다.
/* 변수들이 어디에 저장되는지 확인한다 */

#include <stdio.h>

void mikado(int);    // 함수 선언

int main(void)      
{
    int pooh = 2, bah = 5;    // main()의 지역 변수
    
    printf("main()에 있는 pooh = %d and &pooh = %p\n",
           pooh, &pooh);
    printf("main()에 있는 bah = %d and &bah = %p\n",
           bah, &bah);
    mikado(pooh);
    
    return 0;
}

void mikado(int bah)         // 함수 정의
{
    int pooh = 10;           // mikado()의 지역 변수
    
    printf("mikado()에 있는 pooh = %d and &pooh = %p\n",
           pooh, &pooh);
    printf("mikado()에 있는 bah = %d and &bah = %p\n",
           bah, &bah);
}

 

문제 12 프로그램 분석

 

printf("main()에 있는 pooh = %d and &pooh = %p\n", pooh, &pooh);
printf("main()에 있는 bah = %d and &bah = %p\n", bah, &bah);
printf("mikado()에 있는 pooh = %d and &pooh = %p\n", pooh, &pooh);
printf("mikado()에 있는 bah = %d and &bah = %p\n", bah, &bah);

  • %p가 주소를 표시하는 방식은 시스템마다 다르다.
  • 대부분의 컴파일러들이 16진수 표기로 주소를 출력한다. 주어진 각 16진수의 자릿수가 4비트에 해당하므로 12자릿수 주소는 48비트 주소에 상응된다.
  • 문제 12의 실행 결과로 알 수 있는 것은 다음과 같다. 
    1. 두 개의 pooh와 두 개의 bah는 서로 다른 주소를 가진다는 것이다. 
      • 컴퓨터는 그것들을 네 개의 서로 다른 변수로 취급한다.
    2. 함수 호출 mikado(pooh)는 실전달인자(main()에 있는 pooh)의 값(2)을 형식매개변수(mikado의 bah)에 전달한다.
      • 이때 값만 전달된다는 것을 주목해야 한다.
      • 두 개의 변수(main()에 있는 pooh와 mikado()에 있는 bah)는 자신들의 고유한 값을 지니게 된다.
  • 모든 언어에 적용되는 것이 아니기 때문에 두 번째 요점이 특히 중요하다. 
    • 예를 들어 FORTRAN에서 서브루틴은 호출 루틴에 있는 원래의 변수에 영향을 미친다.
      서브루틴에 있는 변수가 다른 이름을 가질 수도 있지만 주소는 같다.
    • 그러나 C는 각각의 함수가 자신만의 변수를 사용한다. 피호출 함수의 어떤 부작용 때문에 원래의 변수가 변경되는 것을 막을 수 있으므로 더 좋은 방식이지만 몇 가지 어려움이 있기도 하다.

 

 


 

 

 

호출 함수에 있는 변수 바꾸기

문제 13

  • 때로는 다른 함수에 있는 변수를 변경하는 함수를 원할 때가 있다. 
  • 어떤 변수가 main()에 속하고 어떤 변수가 interchange()에 속하는지를 분명히 하기 위해 변수명을 다르게 정한다.
/* 맞교환 함수 version 1 */

#include <stdio.h>

void interchange(int u, int v); /* 함수 프로토타입 */

int main(void)
{
    int x = 5, y = 10;
    
    printf("교환 전 x = %d and y = %d.\n", x , y);
    interchange(x, y);
    printf("교환 후 x = %d and y = %d.\n", x, y);
    
    return 0;
}

void interchange(int u, int v)  /* 함수 정의 */
{
    int temp;
    
    temp = u;
    u = v;
    v = temp;
}

 

문제 13 프로그램 분석

  • 교환이 이루어지지 않았다.

 

문제 14

  • 몇 개의 출력문을 interchange() 함수 안에 추가한다.
/* 맞교환 함수 version 2 */

#include <stdio.h>

void interchange(int u, int v);

int main(void)
{
    int x = 5, y = 10;
    
    printf("교환 전 x = %d and y = %d.\n", x , y);
    interchange(x, y);
    printf("교환 후 x = %d and y = %d.\n", x, y);
    
    return 0;
}

void interchange(int u, int v)
{
    int temp;
    
    printf("교환 전 u = %d and v = %d.\n", u , v);
    temp = u;
    u = v;
    v = temp;
    printf("교환 후 u = %d and v = %d.\n", u, v);
}

 

제 14 프로그램 분석

  • interchange()에는 잘못된 것이 없다. u와 v의 값이 맞교환되었다. 그 결과를 main()에 커뮤니케이션하는 데 문제가 있는 것이다.
  • 앞에서 설명했던 대로 interchange()는 main()에 있는 것이 아닌 다른 변수를 사용한다. 그래서 u와 v의 값을 맞교환하는 것은 x와 y에 영향을 주지 않는다.
  • 어떻게든 return을 사용할 수는 없을까?
    1. interchange() 함수의 끝에 다음과 같은 라인을 추가한 후, 
      return(u);
    2. main()에서 interchange() 함수 호출을 다음과 같이 바꿀 수도 있다.
      x = interchagne(x, y);
  • 이렇게 하면 x는 새로운 값을 갖게 되지만 y는 변하지 않는다. return은 호출 함수에 하나의 값만 리턴할 수 있다.
  • 이것을 해결할 수 있는 방법이 포인터를 사용하는 것이다.

 

 


 

 

포인터 (2)

  • ptr이라는 이름의 특별한 포인터 변수를 가지고 있다면 다음과 같은 문장을 사용할 수 있다.
    • ptr = &pooh;  // pooh의 주소를 ptr에 대입한다.
    • ptr이 pooh를 가리킨다. ptr &pooh의 차이는, ptr는 변수이고 &pooh는 우변값(rvalue)이다.
  • 원한다면 ptr이 다른 곳을 가리키도록 변경할 수 있다.
    • ptr = &bah;  // ptr이 pooh 대신 bah를 가리키도록 변경한다.
    • ptr의 값은 bah의 주소다.
  • 포인터 변수를 만들려면 그것의 데이터형을 선언할 필요가 있다. int형의 주소를 가질 수 있도록 ptr을 선언하려 한다고 가정하면 새로운 연산자를 사용해야 한다.

 

간접 연산자: *

  • 다음과 같이 ptr이 bah를 가리키고 있다고 가정한다.
    • ptr = &bah;
  • 그러면 간접(indirection) 연산자(또는 역참조(dereferencing) 연산자) *를 사용하여 bah에 저장되어 있는 값을 알아낼 수 있다.
  • 이 단항 간접 연산자 *와 곱셈 연산자의 이진 *를 혼동하면 안된다. // 같은 기호, 다른 신택스(문법, 구문)
    • val = *ptr;   // ptr이 가리키고 있는 주소의 값
  • 문장 ptr = &bah;와 val = *par;를 함께 사용하는 것은 다음과 같은 하나의 문장을 사용하는 것과 같다.
    • val = bah;
  • 주소 연산자와 간접 연산자를 사용하는 것은, 이 결과를 간접적으로 얻는 방법이므로 '간접 연산자'라고 한다.

 

요약 : 포인터 관련 연산자

주소 연산자 : &
>> 설명 :  변수 이름 앞에 사용했을 때, 그 변수의 주소를 제공한다.
>> 예시 : &nurse는 변수 nurse의 주소다.

간접 연산자 : *
>> 설명 :  포인터 이름이나 주소 앞에 사용했을 때, 그것이 가리키는 주소에 저장되어 있는 값을 제공한다.
>> 예시 : nurse = 22; 
               ptr = &nurse;  // nurse를 가리키고 있는 포인터
               val = *ptr;       // ptr이 가리키고 있는 주소의 값을 val에 대입한다.
               /* 이것은 val에 22를 대입하는 것과 같다. */

 

포인터 선언

  • 포인터 변수를 선언하는 방법은 다른 데이터형의 변수를 선언하는 것과 다르다.
    • pointer ptr; // 포인터를 선언하는 방법이 아니다.
  • 이것은 변수가 포인터라는 사실을 충분하게 알려주지 않기 때문에 사용할 수 없다.
  • 그 포인터가 가리키는 변수의 데이터형도 함께 알려주어야 한다. 그 이유는 서로 다른 데이터형의 변수들은 메모리를 차지하는 크기가 다르고, 일부 포인터 연산이 그와 같은 메모리 크기를 요구하기 때문이다. 
    • long형과 float형이 같은 메모리 크기를 사용할 수도 있지만 수를 저장하는 방식은 매우 다르다.
  • 다음은 포인터를 선언하는 방법이다.
    • int *pi;                // pi는 int형 정수 변수를 가리키는 포인터이다.
    • char *pc;            // pc는 char형 문자 변수를 가리키는 포인터이다.
    • float *pf, *pg;      // pf, pg는 float형 변수를 가리키는 포인터이다.
  • 여기서 데이터형 키워드(int, char, float 등)는 포인터가 가리키는 변수의 데이터형을 나타내고 애스터리스크(*)는 그 변수가 포인터라는 것을 나타낸다.
  • *와 포인터 이름 사이에 스페이스를 넣는 것은 옵션이다. 일반적으로 프로그래머들은 선언할 때 스페이스를 넣고, 변수를 참조할 때에는 생략한다.
  • pc가 가리키는 주소에 저장되어 있는 값(*pc)이 char형이다.
  • pc 자체는 "char형을 가리키는 포인터형"이라고 한다.
  • pc의 값은 주소이며, 그것은 대부분 시스템에서 내부적으로 부호 없는 정수로 표현되지만 포인터를 정수형이라고 생각하면 안 된다. 정수로는 할 수 있지만 포인터로는 할 수 없는 작업이 있고, 그 반대인 작업들도 있다.
    • 예를 들어 어떤 정수에 다른 정수를 곱할 수 있지만, 포인터는 다른 포인터를 곱할 수 업다.
    • 그러므로 실제로 포인터는 정수형이 아니라 새로운 데이터형이다.

 

함수 간의 커뮤니케이션에 포인터 사용하기

문제 15

포인터를 사용하여 드디어 interchange() 함수가 제대로 동작하게 만든 프로그램이다. 

/* 맞교환 함수 version 3 */

#include <stdio.h>

void interchange(int * u, int * v);

int main(void)
{
    int x = 5, y = 10;
    
    printf("교환 전 x = %d and y = %d.\n", x , y);
    interchange(&x, &y);                            // 함수에 주소를 전달한다.
    printf("교환 후 x = %d and y = %d.\n", x, y);
    
    return 0;
}

void interchange(int * u, int * v)
{
    int temp;
    
    temp = *u;                                       // temp는 u가 가리키는 값을 얻는다.
    *u = *v;
    *v = temp;
}

 

 

문제 15 프로그램 분석

  • 드디어 드디어 드디어 두 값이 제대로 맞교환되었다.
interchange(&x, &y);
  • 이 함수 호출은 x와 y의 값들(values)을 전달하는 대신 x와 y의 주소들(addresses)을 전달한다.
  • 이것은 interchange()의 프로토타입과 정의에 있는 형식매개변수 u와 v가, 주소를 값으로 갖는다는 것을 의미한다.

void interchange(int * u, int * v)

  • x와 y는 정수이고, u와 v는 정수를 가리키는 포인터이기 때문에, 형식매개변수를 위와 같이 선언해야 한다.

①   int temp;     

②   temp = *u;    
③   *u = *v;
④   *v = temp;

  1. temp라는 임시 보관 장소를 하나 만들고
  2. x의 값을 temp에 저장한다.
  3. 여기서 u는 &x 값을 가지므로 u는 x를 가리킨다. 이것은 *u가 x의 값을 제공한다는 것을 의미한다.
    // temp = u는 안된다. u는 주소 값이다.
  4. y의 값을 x에 대입한다. 이것의 효과는 x=y;와 같다.

 

요약

x와 y의 값을 맞교환하는 함수
1. 함수에 x와 y의 주소를 전달함으로써 interchange()가 그 변수들에 접근할 수 있게 한다.
2. 포인터와 *연산자를 사용함으로써 함수는 각각의 주소에 저장되어 있는 값들을 구하고 변경할 수 있다.

포인터는 interchange() 함수의 변수들이 지역적이라는 사실을 극복할 수 있게 해준다.
즉, 포인터는 interchange() 함수가 main()에 손을 뻗쳐서 main()에 저장되어 있는 것을 변경할 수 있게 해 준다.



 

 

 

'C언어 > 스터디' 카테고리의 다른 글

ch11 문자열 p.551~  (0) 2024.08.12
ch10 배열과 포인터  (0) 2024.08.11
ch7 제어문_분기와 점프  (0) 2024.08.09
ch6 제어문_루프 문제  (0) 2024.08.08
ch6 제어문_루프  (2) 2024.08.08