레이블이 선언인 게시물을 표시합니다. 모든 게시물 표시
레이블이 선언인 게시물을 표시합니다. 모든 게시물 표시

2015년 5월 11일 월요일

C++의 클래스(class) 선언

 클래스(class)는 C++에서 지원하는 객체 지향 프로그래밍의 핵심 구조로서 이것을 정의하기 위해서는 class라는 키워드를 이용한다. C++의 클래스는 C언어의 구조체(struct)와 유사하게 개발자가 정의하는 새로운 데이터 타입이다. 구조체가 변수들만을 멤버(member)로 가질 수 있는 반면 클래스는 변수와 함수 모두를 멤버로 가질 수 있다. 멤버를 접근할 때 점(.)을 이용한다는 점은 C언어의 구조체와 동일하다. 또한 멤버들의 접근 영역을 public 이나 private 키워드를 이용하여 지정해 줄 수 있다.
 클래스는 선언부(declaration)과 구현부(implementation)로 나뉜다. 다음은 클래스를 선언하고 구현하는 기본적인 예이다.
// 클래스 선언부
class Rect {
   public:
       int width, height;
       int getArea();
};
// 클래스 구현부
int Rect::getArea() {
   return width*height;
}


멤버 함수 구현을 다음과 같이 클래스 선언부에 둘 수도 있다.
class Rect
{
   public:
       int width, height;
       int getArea()
       {
           return width*height;
       }
};
일견 후자의 경우가 더 간결해 보이고 실제로 함수 본체의 길이가 짧다면 별 문제가 없다. 하지만 멤버 함수의 구현이 길어지고 이러한 멤버 함수가 여러 개라면 클래스의 전체적인 구조를 한 눈에 파악하기가 어려워지고 가독성이 떨어지게 된다. 따라서 전자와 같이 클래스의 선언부와 멤버 함수의 구현부를 분리하는 것이 더 일반적이다.


클래스 선언부


클래스는 다음과 같이 class 키워드와 이름으로 선언한다.
class Rect { // Rect 라는 클래스 선언
   ...
}; // 반드시 세미콜론(;)으로 종료
클래스 이름은 일반적인 식별자 구성 규칙에 따라 개발자가 원하는 대로 붙이며 관습상 대문자로 시작한다. 클래스 선언은 클래스의 모양을 정의하는 것으로서 멤버 함수와 멤버 변수의 원형을 선언한다. 멤버들은 “{”와 “}” 으로 감싸고 마지막에 반드시 세미콜론(;)을 붙여야 한다.

클래스 멤버



 클래스의 멤버(member)는 변수와 함수로 구성된다. 멤버 변수는 클래스 선언부에서 초기화될 수 없다. 따라서 다음과 같은 경우 오류가 발생한다.


class Rect {
   public:
       int width = 10, height = 20;
       int getArea();
};
 
멤버 함수는 원형 형태로 선언되며 함수 선언 규칙과 동일하고 반환 타입, 입력 인수의 리스트 등이 모두 선언되어야 한다. getArea() 멤버 함수는 반환형이 int 이고 입력 인수는 없는 함수의 원형이다. 멤버 함수는 함수 선언만 클래스 선언부에 두고 함수 정의는 외부에 둘 수 있으며 멤버 함수 선언과 정의 모두 클래스 선언부에 둘 수도 있다.


클래스 구현부


 클래스 구현부에서는 클래스 선언부에 선언된 멤버 함수의 실제 코드를 작성한다. 구현부에서 작성되는 멤버함수의 소속을 지정하기 위해서 함수명 바로 앞에 className ::을 앞에 붙여야 하며 이 함수가 className 의 멤버함수라는 것을 나타낸다. 앞의 예에서


double Rect::getArea() {
   return width*height;
}


여기서 더블 콜론(::) 는 ‘범위 지정 연산자’라고 하며 함수가 어떤 클래스의 멤버인지를 지정할 때 사용되는 연산자이다.

2015년 5월 6일 수요일

C/C++언어의 포인터(pointer) 선언

 포인터(pointer)는 무엇인가를 가리키고 있는 것이라는 뜻인데 C/C++언어에서 포인터는 ‘메모리 주소(memory address)’이다. 변수를 선언하면 메모리 공간 어디엔가 그 변수값을 저장하기 위한 기억 공간이 할당되는데 포인터는 그 메모리 공간의 주소값을 가진다. 즉 변수값이 존재하는 물리적인 위치 정보인 것이다.
 포인터를 사용하는 경우는 여러 가지가 있다. 보통은 어떤 함수 내부에서 다른 함수의 변수값을 직접 변경시켜야 될 경우에 사용될 수 있다. 변수값이 아니라 주소값을 인자로 넘겨서 그 주소에 저장된 데이터를 직접 변경시키는 것이다. 또는 복잡한 구조를 가지는 배열이나 구조체가 함수의 인자로 사용될 경우 데이터형을 전달하기도 힘들뿐더러 메모리 낭비 및 비효율성으로 인한 속도 저하를 가져올 수 있다.
 포인터의 다양한 쓰임새 때문에 C/C++언어에는 관련된 많은 기능을 제공하고 있지만 편리한 만큼 위험부담도 있다. 경우에 따라서 매우 잡기 힘든 버그를 생산하기도 하고 심지어 시스템이 다운이 되어버리는 경우도 포인터 때문에 발생하기도 한다. 따라서 사용자는 사용법을 정확히 알고 주의해서 사용해야 한다.

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

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

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

   int *ip;

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

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

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

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

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

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

   int ia = 1;
   int *ip = &ia; // (1)
   *ip = 2; // (2)
   printf(“ia=%d, *ip=%d”, ia, *ip); //(3)

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

  • ip 는 &ia 와 같다.
  • *ip는 ia와 같다.

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

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

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


2015년 5월 1일 금요일

C/C++ 언어에서 사용자 함수의 선언

 이전 포스트에서는 사용자 정의 함수가 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()의 선언에서 인자의 변수명을 생략했음을 알 수 있다. 이렇게 해도 문법적으로 오류가 발생하지 않는다.


2015년 4월 20일 월요일

C/C++ 배열의 선언과 초기화 (part 2 / 2)

 이전 포스트에서처럼 배열은 선언을 먼저 하고 나중에 초기화할 수도 있지만 선언과 동시에 초기화할 수도 있다. 배열을 선언하면서 초기화하는 문법은 다음과 같다.

자료형 배열명[크기] = {값0, 값1, 값2, ... };

배열을 선언하면서 동시에 초기화를 하면 배열 크기는 생략할 수 있다. 이 경우 배열의 크기는 초기값의 개수에 따라 결정된다.

 앞서 들었던 예를 이 방법으로 초기화시키면 다음과 같다.

int iaLength[] = {170, 169, 178, 159, 164};

이렇게 하면 초기값의 개수에 의해서 배열의 크기는 5로 자동으로 결정되게 된다. 만약 배열에 저장할 값들이 미리 정해져 있다면 이 방법이 훨씬 더 간단함을 알 수 있다.

 선언할 때 배열의 크기를 지정했는데 주어진 초기값들이 그 크기보다 작다면 나머지 배열의 요소는 0으로 채워진다. 예를 들어

short saA[100]={1};

의 경우 변수 saA의 첫 번째 요소 saA[0]는 1로, 나머지 saA[1]부터 saA[99]까지는 0으로 초기화 된다.

 하지만 아예 초기값을 주지 않은 경우라면, 예를 들어

char caA[20];

와 같은 경우라면 다음 절에 설명하듯이 이 배열이 전역/정적 배열인지 지역배열인지에 따라서 동작이 달라진다.

이제 배열을 이용하며 다섯 명의 평균키를 구하는 예제를 작성하면 다음과 같다.

#include <stdio.h>
int main()
{
   int iaHeights[] = {170, 169, 178, 159, 164};
   float fAverage = 0.f;
   for (int k=0; k<5; k++) {
       fAverage += iaHeights[k];
   }
   fAverage /= 5;
   printf("average : %f\n", fAverage);
}

여기에서는 산술 평균 (다 더한 후 개수로 나눈 값)을 구해서 화면에 표시해 준다.



C/C++ 배열의 선언과 초기화 (part 1 / 2)

 배열을 설명하기 위해서 다음 표에 기록되어 있는 5명 학생의 몸무게를 데이터로 저장하는 문제로 설명을 시작해 보겠다.

[표 1] 다섯 명의 키 데이터
번호
1
2
3
4
5
키(cm)
170
169
178
169
164

이 5명의 키를 입력하기 위해서 다음과 같이 int형 변수 5개를 각각 사용할 수 있을 것이다.

int iHeight1 = 170;
int iHeight2 = 169;
int iHeight3 = 178;
int iHeight4 = 159;
int iHeight5 = 164;

이렇게 데이터를 저장하여 관리하여도 전혀 문제가 없지만 여러 가지 이유로 비효율적이다.

 이와 같이 동일한 성질의 데이터들을 하나의 이름으로 다루기 위해서 배열(array)이라는 자료형이 마련되어 있다. 배열은 같은 자료형의 데이터들을 연속적인 메모리 공간에 차례대로 저장하는 것이다.

 C/C++ 언어에서 배열을 선언하는 문법은 다음과 같다.

자료형 배열명[배열크기];

앞에서 예를 든 다섯 명의 키를 저장하는 배열을 선언하기 위해서 다음과 같이 하면 된다.

int iaHeights[5];

여기에서 iaHeights 는 배열의 이름(배열명)이며 배열의 요소(element)의 자료형은 int형이고 배열의 크기는 5이므로 이 배열에는 최대 다섯 개의 int형 값을 저장할 수 있는 것이다.

[표 2] iaHeights 배열의 메모리 구조
인덱스
0
1
2
3
4
요소
int형
int형
int형
int형
int형

 배열 변수를 선언하였다면 이제 다음과 같이 초기화할 수 있다.

iaHeights[0] = 170; //첫 번째 데이터 초기화
iaHeights[1] = 169; //두 번째 데이터 초기화
iaHeights[2] = 178; //세 번째 데이터 초기화
iaHeights[3] = 159; //네 번째 데이터 초기화
iaHeights[4] = 164; //다섯 번째 데이터 초기화

여기서 '배열명[0]' 은 배열의 첫 번째 요소를 지칭한다. 대괄호 [...] 안의 숫자는 배열의 인덱스(index)라고 한다. 인덱스는 1부터가 아니라 0부터 시작하는 것에 유의해야 한다. 배열의 요소는 일반 변수와 동일하게 읽을 수 있으며 갱신할 수 있다.

 다섯 명의 키를 처리하기 위해서 다섯 개의 별개의 변수를 각각 사용하는 것보다 이렇게 배열을 이용하는 것이 훨씬 효율적인데 이는 데이터의 일관성을 유지할 뿐만 아니라 반복문을 사용하기에 용이해서 그렇다.

 이제 이 다섯 명의 키를 화면에 출력하는 프로그램을 작성해 보자.

#include <stdio.h>
int main()
{
   int iaHeights[5]; //배열 선언

   iaHeights[0]=170;
   iaHeights[1]=169;
   iaHeights[2]=178;
   iaHeights[3]=159;
   iaHeights[4]=164;

   for (int k=0; k<5; k++) {
       printf("%d:%d\n",k,iaHeights[k];
   }
}