6.2 포인터 선언과 초기화     [doc]     [smts]

 포인터의 선언은 다음과 같이 한다.


  데이터형 *포인터변수명;

기본 데이터형의 선언 방식에서 데이터 형과 변수명 사이에 별표(*)가 추가되었음을 알 수 있다. 이 문자가 추가됨으로서 뒤의 변수는 ‘포인터(주소)’가 된다. 예를 들어서 int형 포인터는 다음과 같이 선언한다.


  int *ip;

여기서 ip는 int형 포인터(주소)로 선언된 것이다. 이 포인터변수는 선언만 되어 있고 아직 초기화되지 않았다. int형 포인터의 초기화는  int형 변수의 주소를 대입한다.


  int ia=1, *ip;
  ip = &ia;

ia는 일반변수이고 ip는 포인터 변수이다. 두 번째 줄에선 ip포인터가 변수 ia의 주소값으로 초기화 되었다. &ia 는 ‘변수 ia의 주소값’이다. &는 변수의 주소를 구해주는 연산자이다.


 한 줄에 여러 개의 포인터 변수를 정의하려면 다음과 같이 각각의 변수에 반드시 *를 붙여야 한다.


float *pa, *pb, *pc;


여기서 pa, pb, pc 는 모두 float형 포인터이다.


 포인터도 선언과 동시에 초기화를 할 수 있다.


  int ia=1;
  int *ip = &ia;

 계속 언급하는 바와 같이 포인터가 가리키는 것은 변수의 주소이다. 만일 가리키는 변수의 값을 읽어오거나 수정할 경우에는 실행문에서 포인터형 변수 앞에 ‘*’를 붙이면 된다.


0601-01.c
#include <stdio.h>

int main() {
   int ia = 1;
   int *ip = &ia; // (1)
   *ip = 2; // (2)
   printf("ia=%d, *ip=%d", ia, *ip); //(3)
}
실행 결과
ia=2, *ip=2

이 예에서 (1)에서는 int형 포인터 ip를 선언하고 동시에 ia의 주소로 초기화하였다. 그리고 (2)에서 ip의 주소에 저장된 (int형) 데이터를 2로 수정하였다. 그렇다면 변수 ia도 같이 변하는 효과가 있는 것이다. 이와 같이 포인터에 저장된 데이터를 수정하려면 포인터 앞에 별표(*)를 같이 써주면 된다. 즉 *ip 는 변수 a와 완전히 동일하게 사용할 수 있다.


  • ip 는 &ia 와 같다.

  • *ip는 ia와 같다.


 한 가지 혼동하기 쉬운 것은 포인터 선언문에서 쓰이는 별표(*)와 나중에 실행문에서  포인터에 붙여서 쓰는 별표(*)의 의미가 다르다는 것이다.


  int ia = 1, ib = 2;
  int *ipa = &ia, *ipb = ipa;
  *ipb = ib;

이 예제와 같이 선언/초기화하였다면 ia와 *ipa, *ipb 는 동일한 변수와 같이 사용된다. 즉 *ipa 가 변하면 ia도 변경되고 *ipb가 변경되도 마찬가지로 ia도 수정된다.


 다음 예제를 보자.


0601-02.c
#include <stdio.h>

int main() {
   float fa = 1.1, fb = 2.2;
   float *pf;
   
   pf = &fa;
   *pf = 11.1; // (2)
   
   pf = &fb;
   *pf = 22.2;
   
   printf("fa=%.2f, fb=%.2f", fa, fb); //(3)
}

실행 결과

fa=11.10, fb=22.20

이 예는 float형 포인터 변수 fp를 이용하여 fa와 fb의 값을 각각 변경시키는 예제이다. 이와 같이 포인터는 한 번 초기화 된 이후에도 얼마든지 그 값을 바꿀 수 있다.


 포인터는 ‘메모리 주소’이고 주소도 어떤 숫자값이지만 포인터 변수에 직접 숫자 상수를 대입하여 초기화 할 수는 없다. 포인터 변수에 대입할 수 있는 값은 다음의 세 종류이다.


  • NULL

  • 다른 변수의 주소나 배열명, 함수명

  • malloc()이나 calloc() 함수에 의해 반환되는 주소값


 첫 번째로 NULL 상수는 보통 ‘비정상적인 포인터’ 혹은 ‘아직 초기화되지 않은 포인터’임을 나타내는 상수로 내부적으로는 0으로 정의되어 있다.


long *pl = NULL;

포인터를 먼저 초기화 시켜놓고 추후에 정상적인 값을 대입하고자 할 때 NULL을 대입하면 된다.


 두 번째로 다른 변수의 주소를 대입시키는 경우이다. 앞에서 설명한 대로 기존 변수명에 주소연산자 ‘&’를 붙여서 구한 주소값으로 초기화 시키는 경우이다.


 마지막으로 malloc() 함수나 calloc()함수는 지정된 바이트 수의 메모리를 할당한 후 그것의 포인터(주소)를 반환하는 함수이다. 다음과 같이 포인터를 선언했다고 가정하자.


double *pd;

지금 이 상태로는 포인터 변수 pd에는 유효하지 않은 주소값(쓰레기 값)이 저장되어 있을 뿐 실제 double형 값을 저장할 메모리조차 할당되지 않은 상태이다.  8 byte 메모리를 할당한 후 그 메모리의 주소를 반환받기 위해서는 다음과 같이 malloc 함수를 사용하면 된다.


#include <stdlib.h>
...
double *pd = malloc(8); //8 바이트를 할당한다.
...

malloc() 함수는 stdlib.h 헤더 파일에 저장되어 있으므로 반드시 include 시켜야 한다. 하지만 보통은 sizeof 연산자와 조합해서 사용하는 것이 일반적이다.


#include <stdlib.h>
...
double *pd = malloc(sizeof(double));

...

*pd = 12.345; // 이제 값을 대입할 수 있다.
free(pd);

메모리가 할당된 이후에는 실제로 값을 저장할 수 있다. 한 가지 유의할 점은 malloc()함수를 이용하여 할당된 메모리는 사용이 끝나면 (보통 함수가 종료되기 전에)  반드시 free() 함수를 이용하여 수동으로 반환하여야 한다는 점이다.


Posted by 살레시오
,

 이전에 #define문을 이용한 상수의 정의를 살펴보았는데 이번에는 매크로(macro)를 정의하는 방법에 대해서 알아보겠다. 매크로는 함수와 외형 및 동작하는 방식이 비슷해 보이지만 내부적으로는 크게 다른 방식으로 동작한다.


 매크로는 #define문으로 정의되는데 프로그램 중간에서 정의된 이름을 만나면 해당하는 매크로 코드로 치환된다. 예를 들어서 다음과 같이 매크로를 정의한다.


#define pow3(x) x*x*x


이제 프로그램 중간에 pow3(2) 라고 쓰면 그 명령어가 통채로 2*2*2으로 대체되게 된다. 만약 pow(iA)라고 쓰면 iA*iA*iA로 바뀐다. 외형상 2이라는 숫자나 iA와 같은 변수를 매크로에 x로 인자로 넘길 수 있게 되므로 마치 함수 같아 보이지만 동작 방식은 전혀 다른 것이다.


#include <stdio.h>
#define pow3(x) x*x*x

int main() {
   int iA = 5;
   float fA = 1.5;
   printf("%d**3 = %d \n", 6, pow3(6)); //❶
   printf("%d**3 = %d \n", iA, pow3(iA)); //❷
   printf("%f**3 = %f \n", fA, pow3(fA)); //❸
}
6**3 = 216
5**3 = 125
1.500000**3 = 3.375000


여기서 ❶, ❷, ❸번 줄을 보면 매크로를 마치 함수를 호출하듯이 사용하고 있으나, 사실은 컴파일하기 전에 프로그램을 다음과 같이 단순 치환하여 변형한 것이 불과하다.


printf("%d^3 = %d \n", 6, 6*6*6 );
printf("%d^3 = %d \n", iA, iA*iA*iA );
printf("%f^3 = %f \n", fA, fA*fA*fA );


따라서 함수의 호출과는 동작하는 방식이 전혀 다른 것이다.


 매크로의 인수로는 두 개 이상도 사용가능하다. 다음 예는 두 수들 중에서 작은 수를 찾아주는 매크로이다.


#include <stdio.h>
#define MIN(a, b) (a<b)? a:b
int main() {
   short nA = -10, nB = -15;
   printf("MIN(%d,%d) is %d.\n", 2, 3, MIN(2,3));
   printf("MIN(%d,%d) is %d.\n", nA, nB, MIN(nA, nB));
}
MIN(2,3) is 2.
MIN(-10,-15) is -15.


 매크로는 단순치환이기 때문에 다음과 같은 경우를 주의해야 한다. 다음 예에서 세 숫자의 곱으로 치환하는 매크로를 예로 들어보았다.


#include <stdio.h>
#define MUL1(a,b,c) a*b*c //❶
#define MUL2(a,b,c) (a)*(b)*(c) //❷
int mulF(int, int, int);

int main(void)
{
   short sA = 2, sB = 3, sC = 4, sD1, sD2, sDF;
   sD1 = MUL1(sA+1, sB, sC);//❸
   sD2 = MUL2(sA+1, sB, sC);//❹
   sDF = mulF(sA+1, sB, sC);
   printf("MUL1: %d*%d*%d = %d \n", sA+1, sB, sC, sD1);
   printf("MUL2: %d*%d*%d = %d \n", sA+1, sB, sC, sD2);
   printf("MulF: %d*%d*%d = %d \n", sA+1, sB, sC, sDF);
}

int mulF(int iA, int iB, int iC)
{
   return iA*iB*iC;
}


❶과 같이 매크로를 정의했다면 ❸은 다음과 같이 치환된다.



nD1 = nA+1 * nB * nC;


따라서 의도하지 않은 엉뚱한 계산결과가 nD1변수에 저장되게 된다. 즉, 매크로는 정의된 그대로 치환을 하기 때문에 이와 같은 오류가 발생하는 것이다. 이를 방지하려면 ❷와 같이 각각의 인수에 괄호( )를 쳐주면 된다. 그러면 ❹는 다음과 같이 의도한 대로 치환된다.


nD2 = (nA+1) * (nB) * (nC);


이로서 의도한 계산 결과를 얻을 수 있다. 전체 실행 결과는 다음과 같다.


MUL1: 3*3*4 = 14
MUL2: 3*3*4 = 36
MulF: 3*3*4 = 36


매크로는 함수로 작성하기에는 다소 간단한 기능을 구현하는데 자주 사용된다. 하지만 매크로를 사용할 때에는 위와 같이 문제가 발생할 소지가 있으므로 보통 인수로 사용하는 변수에는 괄호를 꼭 붙여서 사용해야함에 주의해야 한다.


C++ 강좌 전체 목록 >>>


c{c++},n{c000x}

Posted by 살레시오
,

 정적(static) 변수는 전역 변수와 지역 변수의 중간쯤 되는 특성을 가진다. 정적변수도 전역정적 변수와 지역 정적 변수로 나뉘지만 여기에서는 지역 정적 변수에 대해서만 설명하도록 하겠다.


 지역 정적 변수는 함수의 수행이 끝나더라도 그 값을 유지시켜야만 할 때 사용되는 것이다. 즉, 이 변수는 함수의 수행이 끝나더라도 소멸되지 않으며 그 값을 유지하고 있으며 프로그램의 수행이 끝날 때까지 그 값을 유지하는 변수이다.


 정적 변수는 static이라는 키워드를 데이터형 앞에 붙여주면 되는데 다음과 같이 정의한다.


함수() {
   static 데이터형 변수명1, 변수명2, ...;
}


이렇게 정의된 변수들은 프로그램의 수행이 시작될 때 생성되며 함수의 수행이 끝나더라도 그 값이 유지되며 다음에 호출되었을 때 그 유지된 값을 이용할 수 있다. 프로그램이 실행될 때 생성되고 끝날 때 까지 보존이 된다는 점에서 전역 변수와 라이프 싸이클이 같지만 선언된 함수 내부에서만 사용할 수 있다는 점이 다르다.


다음의 예에서 간단한 사용법을 보였다.


#include <stdio.h>
void countUp(int);

int main(void) {
   int iIn;
   do {
       printf("Input an interger number : ");
       scanf("%d", &iIn);
       countUp(iIn);
   }
   while (0<=iIn && iIn<=10);
}

void countUp(int iR) {
   static int iCount = 0; //❶
   printf("%d번째 수는 %d.\n", ++iCount, iR);
}

실행결과:

Input an interger number : 8
1번째 수는 8.
Input an interger number : 4
2번째 수는 4.
Input an interger number : 10
3번째 수는 10.
Input an interger number : 0
4번째 수는 0.
Input an interger number : -1
5번째 수는 -1.


 이 예제는 사용자가 입력한 숫자가 0이상 10이하라면 다시 입력받고 현재까지 몇 개가 입력되었는가를 세는 간단한 프로그램이다. 이 예제의 countUp()함수 안의 ❶번 줄에서 iCount 변수가 정적 변수로 선언되었다. 따라서 이 함수가 종료되더라도 그 값은 계속 유지하며 이 사실은 실행 결과에서 확인할 수 있다.


 내부 정적 변수도 내부 변수이므로 그것이 위치한 함수 내부에서만 접근할 수 있다는 사실은 유의하자.


C++ 강좌 전체 목록 >>>


c{c++},n{c0028}


Posted by 살레시오
,

 변수는 정의된 위치에 따라서 유효한 범위가 결정되는데 크게 전역(global) 변수, 지역(local) 변수로 나눌 수 있다.  C/C++ 프로그램을 작성하다 보면 전 영역에서 (즉, 모든 함수의 내부에서) 공통적으로 접근할 수 있는 변수를 사용해야 하는 경우도 발생하는데 이러한 경우에 전역 변수를 정의하여 사용한다.


다음 예에서 main()함수 위에 선언된 변수들 cA, iA, lA이 전역변수이고 main()함수 내부에 선언된 cB, iB, lB 등이 (main 함수의) 지역 변수이다. 전역 변수가 main()함수 위에서 (밖에서) 정의되었다는 것을 눈여겨 보기 바란다. 반면 지역변수는 함수의 내부에서 정의되었다.


char ca;
int ia;
long la = 1111111;

int main() {
   char cb;
   int ib;
   long lb = la + 2222222;
}


 전역변수는 선언된 부분 아래에 위치하는 어느 함수에서도 사용할 수 있다, 반면 지역변수는 그 변수가 선언된 함수의 내부에서만 사용될 수 있다. 그리고 지역변수는 함수가 시작될 때 생성되며 함수 수행이 끝나면 소멸된다.


  • 전역변수는 프로그램이 시작될 때 생성, 프로그램 종료시 소멸된다.

  • 지역변수는 함수가 시작될 때 생성, 함수 종료시 소멸된다.


 다음 예는 전역변수 icnt를 화면에 표시하는 프로그램 예를 실행시킨 것이다. main()함수 위에 정의된 icnt변수는 전역 변수로서 프로그램이 종료될 때까지 소멸되지 않고 그 아래의 어떤 함수에서도 접근할 수 있으며 그 값을 유지시킨다. 이 예에서 보면 main()함수와 countup()함수 모두에서 전역 변수 icnt를 접근하거나 값을 변경시켰다.


#include <stdio.h>

#include <stdio.h>

int icnt = 0; //전역변수

int main(int argc, char **argv)
{
   while(icnt<10) {
   countup();
   printf("icnt = %d\n", icnt);
   }
}

void countup() {
   icnt++;
}


icnt = 1
icnt = 2
icnt = 3
icnt = 4
icnt = 5
icnt = 6
icnt = 7
icnt = 8
icnt = 9
icnt = 10


 정리하면 다음과 같다. 전역 변수는 함수 외부에서 선언(초기화)하며 그 아래에 있는 모든 함수에서 접근이 가능하다. 지역 변수는 함수 내부에서 선언된 변수이며 함수가 시작될 때 생성되고 종료되면 소멸된다.


C++ 강좌 전체 목록 >>>


c{c++},n{c0028}

Posted by 살레시오
,

C++에서 함수의 인자 전달

C언어,C++,함수,인자,function,argument,전달



 함수는 인자(argument)를 통해서 기능 수행에 필요한 값들을 전달 받는다. 또한 함수는 결과값을 반환하는 경우도 있고 반환 값이 없는 함수도 있다. 이렇게 함수와 호출자 사이의 정보 교환은 인자반환값이라는 매개체를 통해서 이루어진다.


  • 인자(argument) : 함수가 값을 받는 매개 변수

  • 반환값(return value) : 함수가 되돌려 주어야 할 수행 결과값


C++ 언어에서 함수 인자 전달 매커니즘을 한 번 살펴보도록 하자.


//호출부
int main() {
   int is = add(ix, 20); //① ix, 20은 실인자
}

//피호출부
int add(int ia, int ib) { //② ia, ib 값은 가인자
   int isum = ia + ib;
   return isum;
}


호출하는 함수의 인자를 실제 값이라는 의미로 실인자라고 하고 호출되는 함수에서는 가인자라고 한다. 실인자와 가인자 사이에 데이터를 넘겨주는 방식은 값에 의한 호출(call-by-value) 방식으로서 실인자의 값이 복사되어 넘겨진다.


 호출되는 함수의 return 명령은 이 함수가 내부적으로 연산을 수행한 후 반환값이 있다면 그 뒤에 반환값을 두게 된다. 반환값이 없다면 return문만 단독으로 사용한다. 입력 인자는 없을 수 도 있고 여러 개일 수 있지만 반환값은 없거나 한 개뿐이다.


 위의 예제에서처럼 호출하는 함수는 반드시 main()함수이어야 되는 것은 아니다. 어떤 함수에서든지 다른 함수를 자유롭게 호출할 수 있다. 심지어 어떤 함수 내부에서 그 함수 자체를 호출할 수도 있으며 이를 재귀 호출(recursive call)이라 한다.


사용자 함수를 작성하여 사용하려면 다음과 같은 단계를 따른다.


  1. 함수의 기능 및 인자, 반환값을 설계한다.

  2. 정해진 형식에 따라 함수를 선언한다.

  3. 함수의 본체 부분을 작성한 후 (정의) 호출하여 사용한다.

  4. 호출하는 쪽의 인자의 개수와 형식 그리고 반환값의 형식이 모두 정의된 대로여야 한다.


 앞에서도 언급했지만 C/C++ 언어의 값에 의한 호출 방식은 호출부와 피호출부의 인자들 사이의 관계에서 값만을 전달하는 방식으로 데이터값을 복사하여 전달한다. 이는 문서를 복사했을 때 복사본을 아무리 수정해도 원본이 변하지 않는 것과 같은 원리이다.


 다음 예를 보자.


//호출부:
int main() {
   int ia = 10, ib = 10, ic;
   ....
   ic = sub(ia, ib); //❶
   ....
   printf(“%d”,ia);
}

//피호출부:
int sub(int ia, int ib) { //❷
   ia -= ib;
   return ia;
}


호출부 ❶에서의 변수 ia, ib와 피호출부 ❷에서의 변수 ia, ib는 변수명은 같으나 저장 공간이 다른 완전히 다른 변수라는 점을 유의해야 한다. sub()함수 내부에서 ia값은 바뀌지만 그렇다고 main()함수 안의 변수 ia가 바뀌는 것은 아니다. 서로 다른 변수이기 때문이다.


호출부에서 반환값을 받을 때도 피호출된 함수에서 반환값이 복사되어 새로운 변수를 통해 전달된다. 즉, return ia; 실행문에 의해서 ia변수의 '값'이 복사되어 호출부의 변수 ic에 전달되는 것이다. 이것이 값에 의한 호출 방식의 동작 원리이다.


C++ 강좌 전체 목록 >>>


c{c++},n{c0015}


Posted by 살레시오
,

 명령어 return은 함수 내부에서 사용되어 함수의 실행을 끝내는 역할을 한다. return 다음에는 반환값이 오기도 하고 void형 함수의 경우는 반환값이 없이 단독으로 사용된다.


  • return; // void형 함수의 종료

  • return 반환값; // 반환값이 있는 함수의 종료 및 결과값 반환


이 명령은 또한 함수 실행 도중에 강제로 수행을 종료하려는 목적으로 사용할 수 있다.


void sayHi() {
 printf("Hi ");
 return;
 printf("all."); //절대 실행되지 않는다.
}


이 예에서 두 번째 printf()함수는 절대로 실행되지 않는다. return명령어에 의해서 함수의 실행이 종료되기 때문이다.


long factorial(long n){
 if (n<0) return -1;
 long lf = 1;
 while(n>1) {
   lf *= n--;
 }
 return lf;
}


이 함수는 만약 인자의 값이 음수라면 -1을 반환하고 바로 종료되어 버린다. 그렇지 않다면 팩토리얼을 구해서 반환한다.


 또 다른 예를 살펴보자. 함수 exec()는 char형을 받아서 char형을 반환한다. main()함수에서는 사용자로부터 숫자를 하나 입력 받아서 cn 변수에 대입한 후 그것을 exec()함수의 인자로 넘겨주면서 호출하도록 되어 있다. ❶줄을 보면 조건 검사를 하기위해서는 exec()함수를 호출해야만 하도록 되어있다. exec()함수 내부에서 보면 만약 입력한 인수가 1,2,3 셋 중 하나가 아니라면 ❷번 줄의 return –1; 명령에 의해서 함수의 수행이 바로 종료되게 된다. 1, 2, 3중 하나라면 해당되는 명령을 수행했다는 표시를 하게 된다. main()함수에서는 이 함수의 반환값이 –1이라면 수행이 제대로 안 되었다고 판단하고 “Execution failed.”라는 메세지를 표시한다. 또한 반환값이 1이라면 정상적으로 명령이 수행되었다고 판단할 수도 있다. 이 예에서와 같이 함수 내에서 return명령을 만나면 그 즉시로 함수는 종료되게 된다.


#include <stdio.h>

char exec(char);

int main(void) {
  char cn;
  printf("1. Turn left.\n");
  printf("2. Turn right.\n");
  printf("3. Stop.\n");
  printf("Command : ");
  scanf("%d", &cn);
  if (exec(cn)==-1) //❶
      printf("Execution Failed.\n");
}

char exec(char ca) {
  if (ca!=1 && ca!=2 && ca!=3)
      return –1; //❷

  switch (ca) {
      case 1:
          printf("Robot turned left.\n");
          break;
      case 2:
          printf("Robot turned right.\n");
          break;
      case 3:
          printf("Robot stopped.\n");
          break;
  }
  return 1;
}


직접 실행해 보고 결과를 확인해 보기 바란다.


C++ 강좌 전체 목록 >>>


c{c++},n{c0026}


Posted by 살레시오
,

 이전 포스트에서는 사용자 정의 함수가 main()함수 이전에 위치하였다. 또 다른 예를 들어보자.


double getArea(double dr) { //반지름으로 원의 면적을 구하는 함수
   double dArea = 3.14*dr*dr;
   return dArea;
}

int main() {
   double da = getArea(5.0);
}


하지만 C++ 프로그램은 관례적으로 main()함수를 다른 함수들보다 위에 위치시킨다. 맨 먼저 실행되는 함수이기도 하지만 이렇게 배치해야 다른 함수들과 호출 순서나 상관 관계를 파악하기 쉽다.


  하지만 다음과 같이 단순히 순서를 바꾸기만 해서는 문제가 생긴다.


int main() {
   double da = getArea(5.0); //문제 발생
}

double getArea(double dr) {
   double dArea = 3.14*dr*dr;
   return dArea;
}


왜냐면 컴파일을 수행할 때 main()함수 내부의 함수 호출 getArea() 을 처리하는 시점에서 이 함수에 대한 어떠한 정보도 없기 때문이다. 이렇게 하면 컴파일러는 오류를 발생시킨다.


 이런 경우 함수에 기본적인 정보를 주는 부분이 main()함수 위에 와야 하는데 이것을 ‘함수의 선언’ 이라고 한다. 함수의 선언은 함수 정의부에서 본체를 제외한 첫 줄만 따로 적어주면 된다.

  

double getArea(double dr); //함수의 선언

int main() {
   double da = getArea(5.0);
}

double getArea(double dr) { //함수의 정의 (본체 포함)
   double dArea = 3.14*dr*dr;
   return dArea;
}


이렇게 하면 컴파일시에 오류를 발생시키지 않는다. 이와 같이 함수의 정의가 호출하는 부분보다 뒤에 온다면 반드시 함수의 선언이 선행되어야 한다.


실행 가능한 전체 프로그램을 다음과 같다.


#include <stdio.h>
double getArea(double);

int main(int argc, char **argv) {
   double da = getArea(5.0);
   printf("Area: %f", da);
}

double getArea(double dr) {
   double dArea = 3.14 * dr * dr;
   return dArea;
}
//실행결과
78.500000


함수의 선언과 정의의 차이점을 다시 한 번 살펴보자.


  • 정의(definition) : 사용자가 만든 함수의 본체 부분까지 실제로 구현된다. 따라서 컴파일러가 함수 본체를 저장할 메모리를 당연히 확보하고 호출부보다 먼저 위치한다면 선언과 겸할 수 있다.


  • 선언(declaration) : 컴파일러에게 사용자가 만든 함수의 인자와 반환 자료형을 미리 알려준다. 컴파일러가 본체를 위한 메모리를 확보하지는 않는다 선언은 정의를 겸할 수 없다.


 함수의 선언에서 인자의 변수명은 생략할 수 있으나 인자의 자료형을 반드시 명시해주어야 한다. 다음 예를 보자.


#include <stdio.h>

int add(int, int);

int main() {
  int ia = 100, ib=150;
  int isum1, isum2;
  isum1 = add(10,20);
  isum2 = add(ia, ib);
  add(10, isum1);
  printf("%d, %d\n", isum1, isum2);
}

int add(int ix, int iy) {
  return ix+iy;
}

실행결과

30, 250


이 예제에서 함수 add()의 선언에서 인자의 변수명을 생략했음을 알 수 있다. 이렇게 해도 문법적으로 오류가 발생하지 않는다.


C++ 강좌 전체 목록 >>>


c{c++},n{c0014}

'프로그래밍언어.Lib > C,C++' 카테고리의 다른 글

C++에서 함수의 인자 전달  (0) 2015.05.18
C++ 의 return 명령어  (0) 2015.05.18
C++ 사용자 함수(function)의 정의  (0) 2015.05.17
C/C++ 함수(function) 개요  (0) 2015.05.17
C++ 클래스 예제 : Led 클래스  (0) 2015.05.17
Posted by 살레시오
,

 C++ 언어의 함수는 크게 반환값이 있는 함수와 반환값이 없는 함수 두 가지로 나눌 수 있다. 반환값이 있는 함수는 함수가 호출(call)이 되어 뭔가 작업을 거친 후 호출한 위치로 그 기능을 수행한 결과 값을 다시 보낸다. 반환값이 없는 함수는 단순히 어떤 기능만 수행한다는 것을 의미한다.


예를 들어서 두 개의 입력 값을 받아서 더한 값을 돌려 주는 기능을 하는 함수를 생각해 보자. 이 때 입력되는 값은 ‘인자’(argument) 혹은 ‘매개변수’(parameter)라고 부른다. (이후에는 ‘인자’ 라는 명칭만을 사용할 것이다.) 계산 결과값(이 경우 더한 값)을 호출한 곳으로 되돌려주는 경우 이것을 ‘반환값’, ‘리턴값’이라고 한다.


  • 인자(argument) - 함수로 넘겨주는 데이터

  • 반환값(return value)  - 함수가 작업이 끝나고 넘겨주는 결과값


사용자 정의 함수는 문자 그대로 ‘사용자가 필요에 의해서 만드는 함수’를 의미한다. C++ 프로그램에서 함수를 사용하기 전에 반드시 정의를 해 주어야 한다. 함수의 정의 형식은 다음과 같다.


반환자료형 함수명(데이터형 인수1, 데이터형 인수2 ...) {
   함수 본체
}


예를 들면 다음과 같다.


void sayHi() {
   printf("Hi ");
   printf("all.");
}


이 함수의 이름은 sayHi()이다. 함수명 앞에는 이 함수의 반환 값의 데이터 형을 명시한다. 반환값이 없는 경우에는 명시적으로 void라는 키워드를 써야 한다. 이 함수는 인자가 없다. 이렇게 인자가 없는 경우 함수명 뒤에 그냥 빈 괄호를 입력한다. 괄호를 생략할 수 없음에 유의하자.


이렇게 정의된 함수는 다음과 같이 호출할 수 있다.


int main() {
   ...
   sayHi(); // sayHi()함수 호출
   ...
}


이것을 합하여 완전한 프로그램으로 만들면 다음과 같다.


#include <stdio.h>

void sayHi()
{
   printf("Hi ");
   printf("all.");
}

int main(int argc, char **argv)
{
  sayHi();
}


Hi all.


이 프로그램을 보면 sayHi() 함수와 main() 두 개의 함수가 있다. 이 예제가 실행될 때는 항상 main()함수를 실행시키며 main()함수가 종료되면 프로그램도 종료된다. C++ 언어는 항상 main()함수를 제일 먼저 실행시킨다.


또 다른 함수의 예를 들어보자.


void sayHello(int in)
{
   for(;in>0;in--) {
       printf(“Hello.\n”);
   }
}


이 함수는 반환값이 없으며 인자를 하나 받는 함수이다. 그렇게 받은 인자의 개수만큼 “Hello.”를 반복해서 출력하는 일을 한다. 이것으로 완전한 프로그램을 만들어 보면 다음과 같다.


#include <stdio.h>

void sayHello(int in)
{
   for(;in>0; in--){
       printf("Hello. ");
   }
}

int main(int argc, char **argv)
{
   sayHello(3);
   int ia = 5;
   sayHello(ia);
}


Hello. Hello. Hello.
Hello. Hello. Hello. Hello. Hello.


main()함수 내에서 sayHello()함수를 두 번 호출했다. sayHello(3)이라고 호출하면 3이라는 정수형 값이 sayHello(int in) 의 인자 in에 저장된 후 함수가 수행된다. 따라서 “Hello.”라는 문자열이 3번 출력된 것이다. 마찬가지로 두 번째 호출인 sayHello(ia) 는 ia의 값은 5를 이 함수의 인자 in에 전달한다. 그러므로 “Hello.”가 다섯 번 출력 된다.


다른 예로 이번에는 반환값이 있는 함수를 들어보자.


float add(float fa, float fb) {
   float fc = fa + fb;
   return fc;
}


이 함수는 반환값의 자료형이 float형이라고 함수명 앞에 명시되어 있다. 인자(argument)는 두 개이고(fa, fb) 모두 float 형이다. 함수 본체에 보면 변수 fc에 두 인자의 합을 저장한 후 return 명령어를 이용하여 그 값을 반환하고 있다. 따라서 이 함수는 두 실수를 받아서 그 합을 반환하는 함수이다.


이와 같이 반환값이 있는 함수의 경우 호출부에서는 그 반환값을 변수에 저장할 수 있다. 예를 들어 다음과 같다.


float fr;
fr = add(1.0f, 2.5f);//fr에 3.5 저장

float fa = 10.1f, fb = 22.3f;
float fc = add(fa, fb);// fc에 32.3이 저장됨


이와 같이 반환값이 있는 함수의 경우는 호출부에서 그 반환값을 받아서 변수에 저장할 수 있다.


#include <stdio.h>

float add(float fa, float fb) {
   float fc = fa + fb;
   return fc;
}

int main(int argc, char **argv) {
   float fr;
   fr = add(1.0f, 2.5f);
   float fa = 12.3f, fb = 45.6f;
   float fc = add(fa, fb);
   printf("fr = %f, fc = %f", fr, fc);
}


fa = 3.500000, fb = 57.899998


 좀 더 다양한 함수의 정의 예를 다음 표에 들었다.


함수 정의 예

의미

void nop() {... }

반환값이 없음을 명시적으로 나타냄

int sub(int ia, int ib) {...}

두 개의 int형 입력, 반환값도 int형

double round(double dx) {...}

하나의 double형입력, 출력도 double형

void myDate(void) {... }

인자가 없음을 명시적으로 나타냄.

char display(char ca) {...}

하나의 char형입력, 출력도 char형


여기에서는 함수의 기본적인 정의 방법에 대해서 살펴 보았다.


C++ 강좌 전체 목록 >>>


c{c++},n{c0025}

'프로그래밍언어.Lib > C,C++' 카테고리의 다른 글

C++ 의 return 명령어  (0) 2015.05.18
C++ 사용자 함수의 선언  (0) 2015.05.17
C/C++ 함수(function) 개요  (0) 2015.05.17
C++ 클래스 예제 : Led 클래스  (0) 2015.05.17
C++의 생성자와 소멸자 실행 순서  (0) 2015.05.17
Posted by 살레시오
,

7.1 함수의 개요      [gdoc]       [smts]

 C 언어는 구조 지향 언어(structure-oriented language)라고 한다. 이 구조의 핵심으로 함수(function)가 있는데 C 언어는 함수로 시작해서 함수로 끝난다고 해도 과언이 아닐 정도이다. C++언어는 객체 지향(object-oriented) 언어이지만 C언어와의 하위 호환성을 유지하므로 구조 지향적인 프로그래밍도 가능하다.


 C 프로그램은 하나 이상의 함수들로 구성되어 있으며 각 함수들은 다양한 일들을 수행할 수 있는 독립적인(self-containted) 부프로그램을 일컫는다. 전술했듯이 C++ 의 경우 C 언어에 대해서 하위 호환성이 있으므로 함수에 대한 기본적인 내용을 똑같이 적용된다.


 함수는 식별자 뒤에 괄호()가 반드시 붙는다. 괄호가 붙지 않는 식별자는 변수이며 괄호가 붙은 식별자는 함수이다. 맨 처음부터 다뤄온 예제들에서 프로그램 본체인 main() 도 바로 함수이다. 그리고 scanf(), printf() 도 함수이다.


  • main() 함수 : C 프로그램의 시작점

  • scanf(), printf() 함수: 입출력 함수


 특히 C 언어는 함수에 의한 언어라고 할 수 있다. 아무리 덩치가 큰 C 프로그램이라도 모두 함수를 조합하여 만들어진다. 함수의 사전적인 의미는 ‘기능’이다. 즉 ‘어떤 일을 수행하는 프로그램의 작은 단위’를 함수라고 한다.


  • 함수(function) - 어떤 작업을 수행하는 프로그램의 작은 단위


 프로그래밍 입문자는 왜 하나의 프로그램을 여러 개의 함수로 나누어 작성해야 되는지 궁금할 수 있다. 함수를 사용하는 장점으로 다음과 같은 것들을 들 수 있다.


  1. 덩치가 큰 프로그램을 작고 다루기 쉬운 함수(모듈) 단위로 분할해서 설계하고 구현함으로서 프로그램을 더 쉽게 작성할 수 있다.

  2. 프로그램을 단계적으로 개발할 수 있다.

  3. 어떤 프로그램에서 작성한 함수를 다른 프로그램에서 재사용할 수 있다.

  4. 프로그램 내부에 세부적인 연산이나 작업 등을 함수의 내부에 숨길 수 있어 프로그램을 보기에 간결하게 만들 수 있다.


 C 언어의 함수는 이미 만들어져서 사용할 수 있는 표준 함수와 사용자가 만들어 사용하는 사용자 정의 함수로 구분할 수 있다.


  • 표준 함수 (standard function)

  • 사용자 정의 함수 (user-defined function)


지금까지 사용했던 scanf(), printf() 함수는 모두 표준 함수에 속하며 C/C++ 언어 표준에 포함되어 일반 사용자가 가져다 쓸 수 있는 이미 만들어진 함수이다.


 표준 함수와 사용자 정의 함수를 프로그램 내에서 적절하게 활용하면 중복되는 부분을 상당히 줄일 수가 있으며 프로그램의 크기도 줄일 수 있는 효과를 얻을 수 있다. 덩치가 큰 프로그램을 제작할 때 작은 크기의 모듈을 설계하여 전체를 짜 맞추는 식으로 만들어 가면 개발 시간을 단축할 수 있고 프로그램을 보다 명료하게 구현할 수 있다는 장점이 있다.


Posted by 살레시오
,

 여기에서는 Led 라는 클래스를 작성하는 예를 들어보도록 하겠다. 아두이노의 예제이지만 아두이노가 C++로 개발하므로 일반적인 클래스의 예제도 된다.


 아두이노의 디지털핀에는 LED를 연결할 수 있으므로 Led 클래스는 연결된 핀 번호를 갖는 멤버가 있어야 한다. 따라서 다음과 같이 작성할 수 있다.

class Led {
   public: byte pin;
};

그리고 인스턴스를 생성할 때 이 핀번호를 받도록 하려면 다음과 같이 생성자를 작성하면 될 것이다.

class Led {
   public:
       byte pin;
       Led(int);
};
Led::Led(int dp) {
       pin = dp;
       pinMode(pin, OUTPUT);
}

이렇게 작성하면 멤버 변수 pin을 외부에서 접근할 수 있다. 예를 들면

Led ledRed(12);
ledRed.pin = 11;

와 같이 인스턴스를 생성한 이후에 핀번호를 바꾸는 것이 가능하다.


 그렇지만 핀번호는 한 번 초기화되면 바꿀 필요가 없으므로 궂이 외부에 노출시킬 필요가 없다. 따라서 다음과 같이 private 멤버로 지정하는 것이 바람직하다. 또한 내부 멤버임을 표시하기 위하여 첫 문자를 underscore(_)로 했다.



class Led {
   public:
       Led(int);
   private:
       byte _pin;
};
Led::Led(int pin) {
   _pin = pin;
   pinMode(_pin, OUTPUT);
}

또는 기본 지정자가 private 이므로 아래와 같이 작성해도 된다.



class Led {
   byte _pin;

   public:
       Led(int);
};

Led::Led(int pin) {
   _pin = pin;
   pinMode(_pin, OUTPUT);
}

 

하지만 명시적으로 private 키워드를 이용하여 지정해 주는 것이 가독성 측면에서 더 바람직하다. 그리고 클래스 선언에서는 public 멤버들이 먼저 오는 것이 좋다. 왜냐면 작성자가 아닌 다른 사람이  (보통 라이브러리에 포함되어 배포되는) 이 클래스를 사용하고자 한다면 public 멤버들만 알면 되지 private 멤버들은 궂이 볼 필요가 없기 때문이다.

 이렇게 핀 번호를 private로 지정해 놓으면 외부에서 접근할 수 없으므로 LED를 켜고 끄는 것도 외부에서 수행할 수 없다. 하지만 멤버 함수는 내부 변수를 접근할 수 있으므로 LED를 켜고 끄는 public 멤버 함수를 다음과 같이 지정할 수 있다. 전체적인 클래스의 모양은 다음과 같을 것이다.

class Led {
   public:
       Led(int);
       void turnOn();
       void turnOff();
   private:
       byte _pin;
};
Led::Led(int pin) {
   _pin = pin;
   pinMode(_pin, OUTPUT);
}
Led::void turnOn() {
   digitalWrite(_pin, HIGH); // 멤버 함수는 _pin을 사용할 수 있다.
}
Led::void turnOff() {
   digitalWrite(_pin, LOW); // 멤버 함수는 _pin을 사용할 수 있다.
}

 멤버 변수나 함수는 관례적으로 그 이름이 소문자로 시작하며 클래스는 대문자로 시작한다는 점을 알아두자. 이것은 식별자(이름)만 보고 타잎을 유추하기 쉽게 하기 위한 C++ 프로그램의 관례이다.

 만약 12번 핀에 빨간색 LED가 연결되어 있다면 다음과 같이 Led 객체를 사용할 수 있다.

Led ledRed(12);
....
ledRed.turnOn();
delay(500);
ledRed.turnOff();
delay(500);
....


이와 같이 인스턴스의 이름도 그것으로 실제 의미를 유추할 수 있도록 작성하는 것이 프로그램의 가독성 측면에서 유리하다.


 만약 11번핀에 노란색, 10번핀에 파란색 LED가 추가로 달려있다면 아래와 같이 작성할 수 있을 것이다.


Led ledRed(12), ledYellow(11), ledBlue(10);

이후에 만약 파란색 LED를 켜고 싶다면 다음과 같이 멤버함수를 호출한다.


ledBlue.turnOn();

 이런 식으로 클래스를 이용하여 LED를 객체화 시키면 직관적으로 프로그램을 작성할 수 있다는 장점이 있다.

C++ 강좌 전체 목록 >>>


c{c++},n{c0023}

Posted by 살레시오
,