기반지식/C/C++2010. 6. 21. 22:20
☆ 문자열 관련 함수

#include <stdio.h>
#include <string.h>
#define pr(x) printf("%d\n",x)
#define prs(x) printf("%s\n",x)

void main()
{

//strcmp(a,b):a==b-> 0, a<b: -1, a>b:1
char s1[10]="apple",s2[10]="banana";
char s3[20];

int a;
a=strcmp(s1,s2);
pr(a);
a=strcmp(s2,s1);
pr(a);
a=strcmp("computer","command"); //p와 m이 비교되어 -1이 나옴
pr(a);
a=strcmp("computer","comp"); //u와 NULL문자가 비교되어 1이 나옴
pr(a);
a=strcmp("apple","apple"); // 마지막 NULL문자까지 비교되어 같으므로 0이 나옴
pr(a);

strcpy(s3,s1); //s1의 내용을 s3에 복사
prs(s3);

strcat(s3,s2); //s3에다 s2의 내용을 덧붙임
prs(s3);

a=strlen(s3); //문자열 s3의 길이를 구함
pr(a);

prs(strupr(s3));
prs(strlwr(s3));
prs(strrev(s3));
}

  - STRCMP : 문자열의 비교
  - STRCPY : 문자열의 복사
  - STRCAT : 문자열의 추가
  - STRLEN : 문자열 길이 구하기
  - STRUPR : 문자열 대문자로 만들기
Posted by windship
기반지식/C/C++2010. 6. 16. 22:19
☆ 문자열과 배열

#include <stdio.h>

void main()
{
char s[30]={'O','r','a','n','g','e','\0'};
char s2[]="Apple";
printf("s=%s\n",s);
printf("s2=%s\n",s2);
}

s=Orange
s2=Apple
Press any key to continue

 - char형으로 선언한 30개짜리 배열 s[30]에 문자를 1개씩 넣어서, 그냥 s를 출력하는 것만으로도 문자열을 취급할 수 있다
   (다만 이 경우 30개짜리인데 지정된 문자는 6개 뿐이므로, 마지막에 '\0'을 넣어 문자입력이 끝났음, 즉 문자열의 끝을 알려주어야 에러가 나지 않는다)
 - s2[]의 경우 크기를 정하지 않은 문자열 배열이므로 char s2[]="Apple"; 와 같이 직접 문자열을 넣을 수 있다

☆ 배열을 이용한 문자열의 취급

#include <stdio.h>

void main()
{
char msg[50];
int alpha[26]={0}; //50개짜리 배열 중 26개를 제외한 나머지를 0으로 초기화시킴
int i=0;
puts("영어 문장을 입력하세요");
gets(msg);
while(msg[i]!=NULL)
{
if(msg[i]>=65 && msg[i]<=90) // 소문자라면
alpha[msg[i]-65]++;
else if(msg[i]>=97 && msg[i]<=122) // 대문자라면
alpha[msg[i]-97]++;
i++;
}

puts("== 알파벳의 갯수 ==");

for(i=0;i<26;i++)
{
printf("%c:%d개\t",i+65,alpha[i]);
}
printf("\n");
}

  - 수식을 이용해 배열의 위치를 검사할 때 msg[i - 95]식으로 [] 안에 수식을 쓸 수는 없고, msg[i]-95 식으로 해 주어야 한다
  - 소문자/대문자를 구별할 때 C는 알아서 문자를 아스키 코드값으로 바꾼다(문자형 자료 자체가 내부적으로 아스키 코드값의 정수형으로 취급한다. 이 값을 수식으로 연산하는 것도 가능).


Posted by windship
기반지식/C/C++2010. 6. 15. 22:20
☆ 함수의 중복선언

#include <stdio.h>

void disp(char a)
{
printf("char : %c\n",a);
}

void disp(double a)
{
printf("double : %1lf\n",a);
}


void main()
{
disp('A');
disp('5.6');
}

  - 위 소스는 C에서는 에러가 나지만 C++에서는 에러가 나지 않는다. 
  - error C2084: function 'void __cdecl disp(char )' already has a body 라는 에러가 나는데 이것을 함수의 중복선언이라고 한다.

#include <stdio.h>

void disp(char a)
{
printf("char : %c\n",a);
}

void disp(double a)
{
printf("double : %1lf\n",a);
}

void disp(char *str)
{
printf("string : %s\n",str);
}


void main()
{
disp('A');
disp(5.6);
disp("Choongang Computer");
}

 - 같은 disp라는 이름으로 문자, 숫자, 문자열 함수를 선언해 사용하는 예.

☆ 함수 선언시에 초기값을 주는 예

// 함수의 초기값을 주는 예제. 이것 역시 C++에서만 가능.
// 매개변수 중의 하나에 초기값이 들어갔다면 나머지도 모두 들어가야 한다.


void test(int a, int b=3, int c=5)  <--- 요부분
{
printf("a=%d, b=%d, c=%d\n",a,b,c);
}

void main()
{
test(5);
test(8,9);
test(10,20,30);
}

  - 이것 역시 C에서는 에러가 나지만 C++에서는 가능한 예이다.
  - C++에서는 위와 같은 기능으로 중복선언이 가능하지만, 이 경우 매개변수의 선언 내용이 달라야 한다.

void test(int a, int b, int c=5)
{
printf("a=%d, b=%d, c=%d\n",a,b,c);
}

void test(int s=3)
{
printf("s=%d\n",s);
}

void main()
{
test();
test(5);
test(8,9);
test(10,20,30);
}

  - 이 경우는 test 함수가 2번 정의되어 선언되는데, 첫번째 선언에서는 (초기값 없음, 없음, 초기값 5)로 선언되고 있고, 두번째에서는 (초기값 3)으로 선언되기 때문에 다른 함수로 판단되어 에러가 발생하지 않는다.

void test(int a, int b, int c=5)
{
printf("a=%d, b=%d, c=%d\n",a,b,c);
}

void test(int s=3)
{
printf("s=%d\n",s);
}

void main()
{
test();
test(5);
test(8,9);
test(10,20,30);
}

 - 다만 첫번째의 test 함수 선언에서 초기값이 3인 것이 하나라도 들어간다면 '애매한 중복선언' 에러가 발생한다.

☆ 함수의 선언위치

void main()
{
int sum=0;
for(int i=1;i<=100;i++)
{
sum+=i;
}

printf("sum=%d\n",sum);
}

위 소스는 for 문에서 i를 int로 선언해주고 있는데, 이것 역시 C++에서만 허용되는 형식이다(C에서는 에러가 발생한다).

☆ 열거문(enum)

#include <stdio.h>
#include <conio.h>

void main()
{
// 초기값을 지정하지 않으면 0부터 1씩 증가
// 중간에 값이 없을 경우는 앞의 값 + 1이 됨
enum key{ENTER=13, ESC=27, SPACE=32};
while(1)
{
printf("아무 키나 누르세요 : ");
char a=getch();
if(a==ESC)
printf("\n\tESC 키를 누르셨네요\n");
else if(a==SPACE)
printf("\n\t스페이스 키를 누르셨네요\n");
else if(a==ENTER)
printf("\n\t엔터 키를 누르셨네요\n");
else
printf("\n\t그 이외의 키를 누르셨네요\n");
}
}

  - enum {...} 형식으로, 대괄호 안에 들어간 케이스를 여러 가지 값으로 나열해 갖게 된다.
  - 초기값을 지정해 주었을 때에는 지정된 값으로, 값이 없을 경우는 0부터 시작해 1씩 증가하는 값을 갖게 된다.
  - 위 예제에서는 13, 27, 32가 값이 된다

enum Num{ONE=1, THREE=3, FOUR, FIVE, TEN=10};

  - 값이 들어가다 말았을 경우는 마지막으로 지정된 값에서 1씩 증가하게 된다.
  - 위 예제에서는 1, 3, 4, 5, 10이 된다

☆ typedef

typedef double silsu;
typedef unsigned int jungsu;
typedef char munja;

void main()
{
jungsu a=20;
silsu b=1.2;
munja s[]="apple";

printf("a=%d\n",a);
printf("b=%1lf\n",b);
printf("s=%s\n",s);
}

  - 원래 정해져 있는 자료형 등의 타입을 원하는 이름으로 바꿀 수 있도록 해 주는 것

☆ 배열

#include <stdio.h>
#define N 5

void main()
{
// int a[5]={4,6,7,2,3}; // 예 1) 숫자 생략하면 갯수만큼 자동으로 값을 할당해버린다
// int a[N]; // 예 2) 이렇게 아무것도 주지 않으면 쓰레기 값이 들어간다
// int a[N]={4,6,7,2,3}; // 예 3) 위쪽의 define에서 정해진 10을 다 넣고 모자라는 나머지는 0으로 넣는다
int a[N]; // 예 4)
a[0]=10;
a[1]=13;
a[2]=56;
a[3]=60;
a[4]=70;
printf("a=%d Byte\n",sizeof(a));

for(int i=0;i<N;i++)
{
printf("a[%d]=%d\n",i,a[i]);
}

}

  - 일반 변수 선언하듯이 할 수 있지만 뒤쪽에 []를 붙여 크기를 지정한다(베이직과 비슷함).
  - 예 1) 처럼 뒤쪽에 배열 크기에 맞춰서 일일이 값을 지정해줄 수도 있다. 다만 거의 실제로는 사용되지 않는 방식일듯...
  - 예 4)번이 가장 많이 사용되는 방식일 듯. int a[N]; 과 같은 형식으로 선언한 뒤 식을 통해 배열에 값을 넣는다. (아마 이 부분에 제어문이나 반복문 등을 사용할 수 있을 듯...)
Posted by windship
기반지식/C/C++2010. 6. 14. 22:33
☆ scanf문에서 숫자 변수를 다룰 때에는 번지로 다루기 때문에 항상 숫자 변수 앞에 & 기호를 붙여야함
  - scanf("%d",&val);

☆ if, for, while, switch 등 제어문의 조건식 뒤에는 세미콜론을 붙이지 않는다
  - if(x == 0)
  - for(i=1;i<=100;i++)
  - while(x == 1)
  - switch(year % 12)
  - 붙일 시에는 결과가 달라지거나 오류가 발생한다

☆ 수식에서 나눗셈을 할 경우는 / 연산자를 사용한다. (% 연산자는 나눗셈을 하고 난 나머지를 구한다)

☆ 수식에서 "같다"를 판단할 때에는 = 연산자가 아니라 == 연산자를 사용한다.

☆ char을 이용해 문자열을 선언할 경우는 배열을 이용한다.
  - char ddi[10];  <-- 최대 10글자짜리 문자열변수 ddi를 선언
  - 선언된 문자열 변수에 문자열을 넣을 경우 = 연산자를 사용해 바로 넣을 수는 없다
    ( ddi = "원숭이"; <-- 이런 식으로는 불가능함. strcpy(ddi,"원숭이"); 와 같은 식으로 넣어야 한다)

☆ 함수의 선언에 대해 다시 한번 기억해 둘 것

void init()
{
puts("Shopping List: ");
puts("1. 사과");
        //return;
puts("2. 바나나");
puts("3. 수박");
}


void main()
{
init(); // 위에서 선언한 함수 init를 호출하여 실행
}

  - 함수의 종류
call by name : 매개변수 없이 호출
call by value : 값을 전달하는 함수
call by address : 주소를 전달하는 함수
call by reference : reference를 전달하는 함수(c++)
 ※ 레퍼런스 함수는 c++에만 있음

  - 함수의 문법
리턴타입, 함수명(매개변수1, 매개변수2...)

  - 리턴타입이 void일 경우는 매개변수 필요없음, 리턴값도 필요없음(함수 내에서 return을 써줄 때에도 값이 필요없다)

  - 리턴타입이 void가 아닐 경우는 매개변수가 필요해지며, return을 사용할 때에도 리턴값을 붙여 줘야 한다

  - C는 순차실행이므로, 위 예제에서 init 함수를 main() 다음에 선언해 버리면 에러가 발생한다(선언되지 않은 함수를 main() 내에서 호출했기 때문에). 이 경우는 다음과 같이 할 수 있다.

#include <stdio.h>

void init(); // 아래쪽에 정의된 함수 init를 먼저 선언한다   <-- ※ 중요!
char getalpha(); // 마찬가지로 아래쪽에 정의된 함수 getalpha를 먼저 선언한다

void main()
{
init(); // 위에서 선언한 함수 init를 호출하여 실행
printf("main함수에서 호출 : %c\n",getalpha());
putchar(getalpha());
printf("\n");
}

void init()
{
char s;
s=getalpha();
printf("init에서 호출 : %c\n",s);
puts("Shopping List: ");
puts("1. 사과");
puts("2. 바나나");
puts("3. 수박");
}

char getalpha()
{
puts("알파벳을 보내주마!!");
return 'A';
}

☆ 함수의 선언, 정의, 호출

char getgrade();

void main()
{
int sc;
char gr;
printf("점수?");
scanf("%d",&sc);
gr=getgrade(sc);
printf("%d 점은 %c 학점입니다.\n",sc,gr);
}

char getgrade(int s)
{
char gr;
gr = s <= 100 && s > 90 ? 'A' : s > 80 ? 'B' : s > 70 ? 'C' : s > 60 ? 'D' : 'F';
return gr;
}

 - 함수의 정의내용 중에서 return으로 돌려지는 값의 형태에 따라, 함수의 선언타입을 바꾼다. 위 예제에서는 getgrade의 결과로 돌려지는 gr값이 문자이므로, char로 선언하고 아래쪽 정의의 매개변수에서 (int s)로 정해준다. (색깔에 맞춰서 참조)

 - 사용자 함수가 정의되었을 때에 내부적으로 사용되는 변수는 타입만 같으면 이름은 달라도 상관없다. 값을 넘겨받고 넘겨주기 때문에.


Posted by windship
기반지식/C/C++2010. 6. 10. 22:27
☆ Bit 연산자
  - &(and)
  - |(or)
  - ~(not)
  - ^(exclusive or)
  - <<(L_shift)
  - >>(R_shift)
  - 무조건 내부적으로 2진수로 바꾸어서 계산을 한다

Posted by windship
기반지식/C/C++2010. 6. 9. 21:49
☆ 표준출력함수
  - putchar : 화면에 문자를 출력
  - puts : 화면에 문자열을 출력
  - printf : 화면에 숫자, 문자, 문자열을 출력

☆ 표준입력함수
  - getchar : 키보드 버퍼에서 문자를 입력받는다
  - getche : 키보드 버퍼를 거치지 않고 문자를 입력받는다(입력받는 문자를 보여줌)
  - getch : 키보드 버퍼를 거치지 않고 문자를 입력받는다(입력받는 문자를 보여주지 않음)
  - gets : 키보드 버퍼에서 문자열을 입력받는다
  - scanf : 키보드 버퍼에서 숫자, 문자, 문자열을 입력받는다

☆ scanf 함수의 특성
  - scanf는 입력받는 문자열에 공백이 들어가 있으면 인식하지 못한다.
  - 공백이 들어가야 할 경우는 gets 함수를 대신 사용하는데, 이 경우는 fflush(stdin); 명령으로 키보드 버퍼를 미리 비워주고 사용해야 한다. gets 함수를 한번 사용할 때마다 fflush도 한번씩 계속 나와줘야 함.

☆ getch, getche 함수는 stdio.h 헤더에 들어있지 않기 때문에 이 함수들을 사용하려면 conio.h 파일을 인클루드해야 한다.

☆ goto문
  - 실행위치를 점프하기 위해 사용(basic과 비슷)
  - 점프 위치를 지정하기 위해 레이블을 선언한다(끝에 :을 붙여서 구별)

☆ return();
  - 함수를 종료하기 위해 사용
  - 함수 안에서 반복이 되는 경우 빠져나오기 위해서 사용하기도 한다
  - int 함수인 경우 return 뒤에 종료값이 정수로 붙어야 한다
  - main 함수일 경우는 타입이 void이므로 종료값이 없어도 된다(그냥 return;으로 끝남)

☆ 증감연산자

        a=3;
printf("a=%d\n",a++); // 출력 후에 증가
printf("a=%d\n",a);
printf("a=%d\n",++a); // 증가 후에 출력

  - 연산우선순위와 방향이 중요함
  - printf() 명령 안에 들어 있지만 명령어와 상관 없이 변수의 연산자 순위대로 작동한다.

☆ 증감연산자
#include <stdio.h>
#define pr printf
//조건연산자(삼항연산자) : 조건문?참일때값:거짓일때값
//간단한 if문에 해당되지만 출력문에서도 사용가능하다는 점이 다르다.
void main()
{
int x,y,max;
printf("두 숫자를 입력하세요:");
scanf("%d%d",&x,&y);//엔터나 스페이스로 구분하여 x,y 2개를 동시 입력한다
max=x>y?x:y;

printf("큰 값은 %d입니다.",max);
pr("첫번째 수가 더 %s\n",x>y?"큽니다":"작습니다");
}


Posted by windship
기반지식/C/C++2010. 6. 9. 19:36
Posted by windship
기반지식/C/C++2010. 6. 7. 20:41

☆ 교육과정
 - 1주차 : 자료형, 출력, 연산자, 제어문
 - 2주차 : 배열, 포인터, 함수
 - 3주차 : 함수(계속), 구조체, 클래스

☆ 불러올 때에는 workspace(하나의 프로젝트, dsw 파일) 째로 불러온다

☆ 비주얼C++에서 일반적인 source파일을 만들 경우는 cpp(c++) 확장자가 자동으로 붙으므로, 그냥 c 파일을 만들 경우는 이름을 정할 때 끝에 .c까지 확실하게 붙여주면 c 파일로 저장된다.

☆ void main() - 함수
 - 앞의 void는 리턴값의 타입. void는 리턴값이 필요없을 때에 사용(그래서 main() 함수에 사용한다)
 - 함수에는 대괄호 { }가 붙는데, 이 대괄호 안에서 변수를 선언하면 그 안에서만 사용되는 지역변수가 된다.
 - 하나의 프로젝트 안에 같은 이름의 함수는 존재할 수 없다

☆ #include와 같이 #이 붙으면 전처리문이 된다
 - #include 뒤에는 < >가 올 수도 있고 " "가 올 수도 있다

☆ \뒤에 알파벳이 붙는 것은 "제어문자열"이라고 한다. printf(" ") 안에서 사용된다
 - \n : 커서위치 다음줄로 줄 바꿈(next)
 - \t : 탭공백 넣기(tab)
 - \b : 커서위치 한글자 앞으로(back)
 - \a : 경고음 출력(alert)
 - \r : 엔터(return)


☆ 경로명을 취급할 때 \가 붙으면 제어문자열로 판단해 버리기 때문에, 경로명의 "\"를 입력하기 위해서는 "\\"와 같이 2번 써준다

☆ 마찬가지로 문자열에 " "를 그냥 사용하면 printf 명령이 중간에 닫혀버리기 때문에, 따옴표를 그대로 문자열로 사용하기 위해서는 \"과 같이 앞에 \를 붙여준다

☆ MSDN으로 명령어에 대해 알아볼 경우 해당 명령어 위에 마우스 커서를 갖다두고 F1키를 입력한다
 - 그 명령어에 필요한 헤더 파일도 찾아볼 수 있다

☆ 자료형(변수의 종류)

 * 기본형
  - 정수형(Integer)
  - 문자형
  - 실수형
  - 열거형
  - void형 : 타입이 없는 자료형

 * 유도형
  - 배열 : 같은 타입의 자료 집합
  - 구조체 : 다른 타입의 자료 집합
  - 공용체 : 메모리를 공유하는 자료 집합
  - 포인터 : 대상의 메모리 주소(번지)를 가리키는 타입
  - 함수형 : 함수의 메모리 주소(번지)를 가리키는 타입

☆ 변수 여러개를 한꺼번에 선언할 때는 콤마로 이어서 해준다
 - int a, b, c <ㅡ이런 형식

☆ 변수 선언시 앞에 unsigned를 붙이면 부호 비트를 없앤다는 뜻이다
 - 자료형의 허용범위 2배 정도로 늘어난다
 - 음수 표현은 불가능해진다

☆ printf("a=%d(%d Byte)\n",a,sizeof(a));
 - printf의 따옴표 안에 들어가는 변수 값은 따옴표를 닫은 뒤 뒤에 붙게 된다.
 - 즉 빨간색 %d는 빨간색 a, 녹색 %d는 녹색 sizeof(a)의 값이 출력된다.

☆ 변수 선언시에는 a=0과 같이 미리 초기값을 넣어서 초기화를 해주는 것이 좋다. 그렇지 않으면 쓰레기 값이 들어가 버린다

☆ 변수에 정한 자료형의 범위를 벗어난 값이 들어가면 에러가 나거나, 에러가 나지 않아도 프로그램이 자료형에 의해 값을 수정해 버린다. 변수에 값이 들어갈 때에는 자료형의 허용범위와 맞는지 확인해야 한다

☆ (a) printf("%d Byte\n",sizeof(5/3));
    (b) printf("%d Byte\n",sizeof(5.0/3));

 - (a)의 경우 값은 1이 된다(5, 3이라는 상수값이 모두 정수형이므로 실수가 될수없다)
 - (b)의 경우 값은 1.6이 된다(5.0이라는 상수값이 실수이므로 실수 값이 허용된다)

☆  int a=9;
    int b=4;
    double c,d;
    c=a/b; // 정수형끼리의 계산은 무조건 정수형으로 된다.
    d=(double)a/b; //(자료형) cast 연산자라 함. 강제형변환

    printf("c=%.2lf\n",c);
    printf("d=%.2lf\n",d);

 - 3행에서 c에 double을 선언하더라도 이미 1, 2행에서 a와 b는 정수형이기 때문에 c 값에는 정수만 들어간다.
 - 실수 결과값을 얻고 싶으면 a나 b를 실수형으로 바꾸면 되지만, 경우에 따라 그쪽의 자료형을 고정하고 싶을 때가 있다. 이런 경우는 강제 형변환을 사용한다.
 - 5행에서 들어간 것처럼 수식의 앞에 ( )를 치고 자료타입을 선언하면 수식의 결과값을 강제로 원하는 자료타입으로 바꿔준다. 실제 코딩에서 자주 사용하게 되는 방법이다.

☆ void main()
   {
    int a=55; //10진
    int b=055; //8진
    int c=0x55; //16진

    printf("a=%d, b=%o, c=%x\n",a,b,c);
    printf("a=%d, b=%#o, c=%#x\n",a,b,c);
   }

 -  //변환기호
    //정수형 : %d(10진), %o(8진), %x(16진)
    //실수형 : %f(고정소수점), %e(지수형), %g(혼합형)
    //위 자료형들은 float이며, double일 경우는 앞에 L을 붙인다.
    //문자형 : %c(1바이트), %s(문자열)
 - 타입 앞에 b=%#o 와 같이 # 표시를 붙여주면 값을 해당 진법의 표기 형식에 맞게 보여준다

☆  printf("a=%04d\n",a); -> 4d로 하면 값을 4자리로 보여주며, 앞에 0이 붙으면 빈 자리를 0으로 채운다
     printf("a=%4d\n",a); -> 4d로 해서 값을 4자리로 보여주지만 별다른게 없으므로 빈 자리는 빈칸이 출력
     printf("a=%-4dHi!\n",a); ->4d로 값이 4자리가 되지만 -가 붙으면 빈칸을 뒤에 붙인다
      a=0055
      a=  55
      a=55  Hi!

Posted by windship
[강좌 : 객체지향 - 6] 상속성
2009.10.01 12:31
김성용(topmentor)
http://cafe.naver.com/gisdev/611  
6. 상속성
 
 
오랜만에 다시 이어갑니다. 지금도 중요한 일을 하고 있어야 하는 데 귀차니즘 때문에 그것은 미루고 이것에 손을 대고 있네요. ^^<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><?xml:namespace prefix = o />
 
이번 시간은 상속입니다. 상속은 객체지향의 꽃이라고 할 수 있습니다. 상속이 없다면 기존의 절차지향과 다를 게 없습니다. 절차지향에도 기능이나 속성을 추상화할 방법은 존재했기 때문입니다. 기능은 함수를 통해 속성은 구조체와 같은 사용자 정의형을 통해 구현을 할 수 있었죠. 상속이 있기에 객체지향과 절차지향은 중대한 문법적 차별성을 갖게 된 겁니다. 따라서 객체지향에서는 이 상속을 이해하고 활용해야 객체지향의 묘미를 살리는 셈이 됩니다.
 
 
   가. 상속의 개념
     : 상속의 기본 개념은 상위개념의 특징을 하위개념이 물려 받는 것이라 했습니다. ‘남학생’이라는 개념은 ‘학생’이라는 상위개념의 속성을 물려 받고 있습니다. 이런 특징을 프로그래밍적으로 어떻게 지원하는 가 하면 하나의 클래스가 가지고 있는 특징(데이터와 함수)들을 그대로 다른 클래스가 물려 받도록 문법화 하였습니다. 즉, 상속이라는 관계표시만 해 두면 별다른 조치없이 클래스의 기능과 속성을 그대로 가져오게 된다는 것입니다. 이는 기존 클래스에서 새로운 기능이나 특성을 부가하는 것만으로도 새로운 클래스를 정의할 수 있게 하기 위해서 입니다.
 
좀 더 프로그래밍적 측면으로 접근해 보면 ‘상속한다’의 의미는 클래스의 멤버를 다시 정의하지 않더라도 다른 클래스의 멤버변수나 함수들을 자신의 것처럼 쓸 수 있는 것을 말하는 것입니다.
 
오호!! 오늘도 저쪽에서 질문을 날리는 군요.
“편리한 것 같기는 한데, 요즘 에디터는 Copy & Paste가 쉬워서 기존에 만들어둔 코드를 그대로 복사해서 붙이면 되는데 굳이 상속을 쓸 필요가 있나요?”
 
대단한 지적입니다. 맞습니다. 복사해서 붙이기 하면 간단히 해결될 수 있습니다. 그럼 이런 상황을 생각해 봅시다. 화면에 원을 출력하는 기능을 확장해서 동심원을 출력하는 모듈을 만든다고 생각해 봅시다. 만약 복사를 해서 원을 출력하는 로직을 가져다 동심원 출력 모듈을 완성했다면 원을 출력하는 기능을 만든 사람이 그것을 업데이트 했을 때 동심원에 적용한 원 출력 로직은 업데이트가 안 된 상태로 남아있게 됩니다. 기능을 상속받았다면 업데이트 된 로직이 그대로 적용되었을 것입니다. 유지보수의 측면에서 상속이 복사&붙이기 보다 훨씬 일관성이 있고 안전하다고 할 수 있습니다.
 
“그런 경우 복사해서 붙이지 않고 함수를 호출하는 식으로 되면 안 되나요?”
 
이런 경우 원래 모듈의 안정성을 기할 수 있을 겁니다. 그런데 자료구조나 속성은 어떻게 물려줄 까요? 상속은 데이터이든 로직이든 안전하게 하위 클래스로 물려주고 자신의 색깔도 잃지 않게 하는 장점이 있습니다.
 
이런 측면도 있습니다. A라는 사람이 Car라는 클래스를 구현하였습니다. B라는 사람이 SuperCar라는 클래스를 만들려고 합니다. 그런데 Car의 기능과 속성을 이용하면 쉽게 구현할 수 있을 것 같습니다. Car 클래스는 A의 통제하에 있는 것이라 섣불리 B가 Car를 직접 수정할 수는 없습니다. Car는 그대로 두고 자신은 SuperCar만 손댈 수 있도록 하되 Car의 속성과 기능을 가져와야 합니다. 상속은 이런 상황에서 쓸 수 있는 좋은 방법입니다. Car의 속성과 기능을 물려 받되 마음에 들지 않는 것은 오버로딩이나 오바라이딩을 통해서 재구현할 수 있기 때문입니다. 빌려쓰는 모듈을 건들지 않고 원하는 모듈의 기능을 완성한다. 좋지 않습니까?
 
 
 
   나. 상속시 객체 구조
     : 상속을 받은 하위 클래스로 만든 객체의 구조를 이해하는 것은 상속관련 문법을 이해하는 데 매우 중요한 의미를 갖습니다. 일단 하위 클래스 객체에 대한 대원칙을 말씀드리겠습니다.
 
 원칙적으로 상속을 하면 모든 멤버를 하위로 가져온다. 단, 사용할 수 있느냐 없느냐로 상속이 된다 안 된다를 판단한다.
 
이것을 그림으로 설명드리겠습니다.
 
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><?xml:namespace prefix = v />
 
 
Car를 상속 받은 SuperCar 클래스를 가지고 인스턴스를 생성하면 위 그림과 같이 SuperCar 객체 내에 Car 객체를 포함하는 형태가 됩니다. 상속을 한다면 마치 기능과 속성단위로 하나씩 가져오는 것 같지만 실제로는 그렇지 않다는 것입니다. 상위객체를 통체로 안고 있는 것입니다. 이를 통해서 확인할 수 있는 사실 하나… 상속을 받을 때에는 필요한 멤버만 선택적으로 가져올 수는 없다는 것입니다. 도움이 되든 안 되든 모두 가져와야 하는 구조입니다.
 
이쯤에서 이런 질문을 하실 겁니다.
“접근지정자를 쓰면 필요한 것만 상속한다고 하던데요? 즉, private로 지정되는 것은 상속이 안 된다고 하던데…”
 
많은 객체지향 책에서 그렇게 설명하고 있습니다. 일부는 맞는 이야기입니다. 그런데 이 부분은 정확히 이해하셔야 합니다. 접근지정자는 접근을 제한하는 용도이지 상속을 제한하는 제한자가 아닙니다. 상속을 하면 모든 멤버를 가져오는 데 접근지정자에 의해 하위 클래스 쪽에서 접근하는 것을 차단하는 것입니다. 이런 식으로 해서 private멤버를 쓸 수 없게 되니 ‘상속이 안 된다’라고 표현을 합니다만 사실 정확한 표현은 아닙니다. 메모리상에서는 private 멤버도 엄연히 allocation되고 있습니다. 단지 쓰지 못할 뿐이죠.
 
부가적으로 설명하자면 private 멤버는 하위 클래스에서 접근이 차단됩니다. protected나 public으로 지정되어야 하위 클래스서 접근이 가능합니다.
 
그리고 C++에서는 private 상속, protected 상속, public 상속을 구분하도록 문법이 제공되지만 거의 99% public 상속을 하고 있기 때문에 거의 의미가 없다고 보시면 됩니다. 따라서 Java나 C# 같이 나중에 탄생한 객체지향 언어의 경우 상속방식을 접근제한자로 구분하는 문법이 없습니다.
 
자~ 정리해 보겠습니다.
상속의 목적은 새로운 클래스를 정의하는 데 기존의 클래스를 확장하여 정의하고 싶을 때 사용된다. 그리고 하위 클래스의 객체는 상위 클래스의 객체를 포함하는 형태를 취하며 접근지정자에 사용이 제한될 뿐이다.
 
다음 시간에는 다형성을 해야 되는 데 상속에 대해 한가지 더 이야기를 할 게 있어 그것을 다루려고합니다. 사실 객체지향이 제시하는 상속은 2가지가 있습니다. 오늘 알아본 것은 구현상속이라는 것입니다. 새로운 클래스 구현을 목적으로 하는 상속을 말합니다. 상속을 하면 상위 클래스의 기능과 속성을 물려받는다는 특징을 이용하는 것이죠.
 
그런데 한가지가 더 있습니다. Type 상속이라는 것이 있습니다. 이것은 클래스가 갖는 형태만 상속을 하는 것입니다. “‘형’만 상속을 한다.”는 것에 대해 감이 잘 안 오실 겁니다. 주는 것 없이 그저 유형만 가져온다는 게 어떤 의미를 지니고 왜 필요한지도 모르실 겁니다. 하지만 이것을 이해해야 다형성의 고비를 넘어가실 수 있습니다. 다음 시간에는 이 type 상속에 대해 알아 볼 겁니다.
 
C++에서는 엄밀히 구분하지 않지만 Java나 C#등에서는 이 두 가지 상속을 구분하는 문법을 제공하고 있습니다.
Posted by windship
[강좌 : 객체지향 - 5] 캡슐화
2009.09.04 14:30
김성용(topmentor)
http://cafe.naver.com/gisdev/543  
5. 캡슐화<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><?xml:namespace prefix = o />
 
   이번 시간은 캡슐화에 대해 알아보겠습니다. 개인적인 생각으로는 객체지향 4대 특징중에 어쩌면 가장 주목받지 못하는 특징이 아닌가 합니다. 그 이유는 캡슐화를 개념적인 측면으로만 접근하고 실제 코드로 별로 적용하지 않기 때문에 생기는 문제라고 봅니다. 캡슐화가 객체지향의 4대 특징으로 올라설 수 있었던 것은 그 만큼 개념적인 중요성이 크기 때문인데 많은 개발자들이 이것의 중요성에 대해 인식하지 못하고 있습니다. 그도 그럴 것이 많은 객체지향 프로그래밍 책에서 캡슐화를 자세히 다루지 않기 때문입니다. 그저 접근지정자 설명에 그치는 경우가 대부분이죠. 하지만 캡슐화의 특징은 접근지정자 이상의 의미를 가지고 있고 실제 코딩에서는 다양한 형태로 구현이 됩니다.
 
자! 그럼 캡슐화로 들어가 보겠습니다.
 
 
가. 캡슐화 개요
잠시 복습하는 차원에서… 객체지향 4대 특징의 개념 시간에 캡슐화를 어떻게 설명했었죠?
네~ 그렇습니다. '데이터 구조'와 '데이터를 다루는 방법'를 결합시켜 묶는 것을 말합니다. 질환을 치료하는 데 필요한 몇몇 약들이 적정한 비율로 섞여 하나의 캡슐에 들어있는 알약을 예로 들어 설명했습니다. 그러면서 특정 객체가 독립적으로 역할을 제대로 수행하기 위해 필요한 데이터와 기능을 하나로 묶어 관리하는 것을 캡슐화라고 한다고 정리했었습니다.
거기에 데이터는 은닉하고 그 데이터를 접근하는 기능(함수)를 밖으로 노출하는 것도 캡슐화라고 했습니다. 데이터를 기능으로 보호하여 데이터의 입출력이 기능을 통해 통제되도록 설계하는 것을 말하는 것이었죠.
 
지금까지의 내용을 정리해 보면 캡슐화는 두가지 측면이 있습니다.
  1) class안에 관련된 변수와 메소드를 묶어 넣는 것.
  2) 정보은닉
     : 객체는 자신이 수행하는 모든 행동의 과정이나 속성을 외부로 보이지 않게 합니다.
       즉, 외부에서는 사용하는 메소드의 로직이나 멤버변수들에 대해서는 알 필요가 없다
       는 것입니다.
 
 
나. 캡슐화의 구현
캡슐화를 구현하기 위해 각 객체지향언어들은 접근지정자라는 것을 제공합니다. 클래스의 멤버에 접근할 수 있는 권한을 통제하여 적절히 보안정책을 취할 수 있게 하는 것입니다. 대표적인 접근지정자는 다음의 3가지 입니다.
 
   . private   : 클래스 내부 (멤버함수)에서만 접근가능
      . protected : 클래스 내부 + 하위 클래스의 멤버함수에서 접근 가능
      . public    : 클래스 내부 + 하위 클래스의 멤버함수 + 외부 함수에서 접근 가능
 
class 클래스 이름
{
public :
        데이터형 멤버변수1;
        데이터형 멤버변수2;
리턴형 함수1 ( 매개변수 );
private:
데이터형 멤버변수3;
        데이터형 멤버변수4;
리턴형 함수2 ( 매개변수 );
};
 
 
위 예에서 멤버변수1, 멤버변수2, 함수1은 pulic 접근지정이 됩니다. 그럼 대충 눈치로 보면 멤버변수3, 멤버변수4, 함수2는 private 지정이 되겠구나라는 짐작이 가시죠? 클래스 내부에 인스턴스가 관련 기능을 온전히 하기 위해 필요한 데이터와 함수를 한묶음으로 가지고 있다면 이것은 캡슐화가 잘 된 것입니다. 캡슐화가 잘 된 클래스는 외부에 의존하는 정도가 줄어들겠죠.
 
두번째 캡슐화 정의를 구현하는 방법은 다음과 같습니다. 모든 멤버변수는 private로 접근지정을 해서 외부의 직접접근을 문법적으로 차단 시키고 멤버 변수 데이터를 수정하거나 데이터 값을 알아내기 위한 별도의 멤버 함수를 제공하는 것입니다. 이렇게 멤버변수를 수정하거나 값을 열람하는 접근함수를 Getter(accessor)/Setter(mutator)라고 합니다.
 
아래 예를 잠깐 살펴 보겠습니다.
 
class Car
{
private :
        int speed;
 
public:
void setSpeed(int n) {  speed = n; }
int getSpeed() {  return speed; }
 
};
 
int main()
{
   Car mycar;
   mycar.setSpeed(100);
   cout << mycar.getSpeed() ;
}
 
 
예제를 보면 speed라는 멤버변수는 private로 묶어 두었기 때문에 main함수에서는 직접 접근할 수 없습니다. 대신 이 멤버에 접근하기 위한 메소드를 제공하고 있는 것을 알 수 있습니다. speed에 데이터를 입력하기 위해 setSpeed()를 제공하고 speed 값을 얻어내기 위해 getSpeed()를 제공하고 있습니다. 그래서 main함수에서도 이들 함수를 호출하여 간접적으로 speed 멤버를 쓰고 있습니다. 함수를 통한 데이터의 접근, 이것이 바로 캡슐화인 것입니다.
 
 
아! 저기서 누가 손들고 계시는군요.
‘speed를 public로 해 두면 굳이 메소드를 만들지 않고 편하게 접근해서 쓸 텐데 이런 번거로운 것을 해야 하나요?’
 
네~!! 맞습니다. 그렇게 쓰면 편하죠. 그런데 이런 측면을 생각해 볼까요? 멤버변수중에 외부에서 값은 열람은 하되 수정은 하지 못하게 하고 싶다면 어떻게 해야 할까요? 멤버변수를 public로 그냥 노출하면 외부에서 수정하는 것을 원천적으로 차단할 수 없겠죠? Getter와 Setter를 선택적으로 두면 그 멤버변수는 읽기전용 혹은 쓰기전용식으로 데이터에 대한 접근권한을 더 세분화 할 수 있습니다.
 
또한 위 예제의 로직을 잘 보세요? speed멤버변수는 음수값이 들어오면 안 됩니다. 마이너스 속도란게 있을 수 없잖습니까? 만약 speed가 노출되어 있다면
speed = -100;
식으로 음수가 입력되는 것을 막을 수 없습니다. 하지만 메소드를 통해 간접적으로 접근하게 하면 이런 입력값의 유효성을 체크할 장치를 둘 수 있습니다. setSpeed()에서 두면 되겠죠.
 
void setSpeed(int n) {
if(n>=0)
  speed = n;
else
  speed = 0;
}
 
위와 같이 해 두면 아무리 main함수에서 ‘mycar.setSpeed(-2000);’ 한다 한들 speed에는 음수값이 입력되지 않습니다. 자~ 그럼 정리해 봅시다. 멤버변수를 멤버함수로 캡슐화하는 이유는
 
     1) 데이터에 대한 읽기 쓰기 권한 구분
     2) 입력데이터의 유효성 검증 부분을 로직에 포함 시킬 수 있음.
 
입니다. 정리가 되셨죠?  실제 COM과 같은 컴포넌트 프로그래밍에서는 외부에서 접근할 필요가 있는 멤버변수에 대해 Getter와 Setter를 반드시 두도록 되어있습니다.
 
 
 
다. 클래스 설계상의 캡슐화
세번째 캡슐화에 대해 설명 드리겠습니다. 이것을 객체지향 프로그래밍 책에는 소개되어 있지 않지만 실제 프로그래밍에서는 많이 활용되고 있는 기법입니다. 이것은 설계적인 측면이 있어서 이해하는 데 좀 어려울 수도 있습니다. 따라서 지금 이해가 안 되더라도 양심의 가책(?) 느끼지 마시고 넘어가셔도 됩니다. 다형성을 이해한 다음에 보시면 됩니다.
 
세번째 캡슐화는 선언만 노출하고 구현은 숨기는 것입니다. 잘 이해가 안 되시죠? 이와 비슷한 기법을 쓴 것이 바로 라이브러리입니다. C나 C++라이브러리를 이용할 때 보면 코딩시에 추가(include)하는 것은 라이브러리의 헤더입니다. 헤더에 드러난 함수나 클래스 선언을 보고 자신이 작성한 코드에서 라이브러리 함수를 호출합니다. 마치 자신이 작성한 함수처럼 말이죠. 그럼 그 라이브러리 함수의 실제 구현부분은 어디에 있는 것일까요? Lib 파일에 네이티브 코드형태로 있다가 link시에 이들 코드가 삽입되는 것입니다. 이것도 캡슐화입니다. 세부적인 구현은 숨기고 외부에는 사용할 수 있는 최소의 정보만 노출하는 것이면 모두 캡슐화입니다.
 
객체지향 설계에 있어서 캡슐화는 클래스의 선언만 담는 순수추상클래스를 전면에 내세워 외부에서는 이것을 사용하게 하고 순수추상클래스를 상속받는 클래스를 만들어 여기에 실질적인 구현을 하는 것입니다. 구조로 보면 아래와 같습니다.
 
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><?xml:namespace prefix = v />
 
 
 
위 그림에서 ImageSource라는 추상클래스는 구현부가 없습니다. 그냥 멤버함수의 선언만 들어있습니다. 이것이 인터페이스 역할을 하는 것입니다. 그 멤버함수의 구현은 하위 클래스인 LocalImageSource나 RemoteImageSource에 있습니다.따라서 외부에서는 ImageSource라는 추상클래스를 활용하고 실제 기능의 수행은 하위클래스인 LocalImageSource나 RemoteImageSource에서 이루어집니다. 이렇게 되면 클래스를 사용하는 측에서는 세부구현을 신경쓸 필요없이 ImageSource가 노출하는 인터페이스만 보면 되는 겁니다.
 
이렇게 함으로 해서 클래스 사용하는 쪽이 클래스의 실제 구현부를 건드려 클래스 개발에 대한 일관성을 잃게 되는 위험을 피할 수 있습니다. 클래스 구현의 버전관리에도 강점이 있죠. 아무튼 이 기법은 클래스 설계상 구현할 수 있는 캡슐화입니다. 자바를 좀 하신 분은 JDBC가 이런 구조를 가지고 있다는 것을 아실 겁니다. 세번째 기법은 나중에 다형성이 끝난 후 디자인 패턴을 이야기 할 때 자세한 코드를 예로 들어 설명하겠습니다.
 
정리하죠. 캡슐화는 실질적인 것은 숨기고 접근할 방법만 노출하는 것을 의미한다. 요즘에는 관련된 것을 묶는 다는 의미는 많이 퇴색되었습니다.
Posted by windship