반응형

 

문자열을 특정 구분자로 분리할 수 있는 StringTokenizer와 Splite을 사용하던 중
구체적인 차이점이 무엇인지 궁금하여 조사하게 되었다.
메모리에서 장점을 가진다고 하지만 정확히 어떤구조로 작용하는지 알아보자

 

StringTokenizer

StringTokenizer 클래스 내부를 살펴보면 다음과 같은 내용을 볼 수 있습니다.

    public StringTokenizer(String str, String delim, boolean returnDelims) {
        currentPosition = 0;
        newPosition = -1;
        delimsChanged = false;
        this.str = str;
        maxPosition = str.length();
        delimiters = delim;
        retDelims = returnDelims;
        setMaxDelimCodePoint();
    }
  • 생성자 3가지
생성자 설명
StringTokenizer(String str) 문자열(str)을 기본 구분자를 제외하고 문자열을 반환한다.
기본 구분자 : 공백( ), 탭(\t), 줄바꿈(\n), 캐리지 리턴(\r), 폼 피드(\f)
StringTokenizer(String str, String delim) 문자열(str)을 구분자(delim)를 제외하고 문자열을 반환한다.
StringTokenizer(String str, String delim, boolean returnDelims) 문자열(str)을 구분자(delim)을 제외할 것 인지(returnDelims)에 따라 true면 반환한다.
  • 주요 메서드
메서드 설명
hasMoreTokens() 더 읽을 토큰이 있는지 확인, true/flase 반환
nextToken() 다음 토큰 반환 및 포인터 이동
nextToken(String delim) 구분자를 새로 지정하여 다음 토큰 반환
countTokens() 남아있는 토큰 개수를 반환
hasMoreElements() hasMoreToken()와 동일
nextElement() nextToken()과 동일, 반환값이 Object 타입

 


1. StringTokenizer(String str)

public StringTokenizer(String str) {
        this(str, " \t\n\r\f", false);
}


StringTokenizer stringtokenizer = new StringTokenizer("Hello World\tJAVA");
	while (tokenizer.hasMoreTokens()) {
    		System.out.println(tokenizer.nextToken();
    	}
Hello
world
JAVA
  • 기본 구분자 : 공백( ),(\t), 줄바꿈(\n), 캐리지 리턴(\r), 폼 피드(\f)
  • returnDelims는 기본적으로 false

2. StringTokenizer(String str, String delim)

  public StringTokenizer(String str, String delim) {
        this(str, delim, false);
 }

StringTokenizer stringTokenizer = new StringTokenizer("A,B|C", ",|");

while (stringTokenizer.hasMoreTokens()) {
	System.out.println(stringTokenizer.nextToken());
}

 

A
,
B
|
C

3. StringTokenizer(String str, String delim, boolean returnDelims)

public StringTokenizer(String str, String delim, boolean returnDelims) {
        currentPosition = 0;
        newPosition = -1;
        delimsChanged = false;
        this.str = str;
        maxPosition = str.length();
        delimiters = delim;
        retDelims = returnDelims;
        setMaxDelimCodePoint();
}

StringTokenizer stringTokenizer = new StringTokenizer("A,B|C", ",|", true);

while (stringTokenizer.hasMoreTokens()) {
    System.out.println(stringTokenizer.nextToken());
}

 

A
,
B
| C

 

다음과 같은 생성자를 가지고 있는데 코드를 살펴보면 단 하나의 구분자로만 사용되는 것으로 보인다.

즉, 구분자 문자가 개별로 작용하여 하지만 String 클래스의 split은 ,| 를 하나의 구분자로 사용할 수 있다.

 


Split

split 메드는 문자열을 특정 정규식(regex)을 기준으로 나눈다.

다음은 String 클래스에 있는 split 메서드이다.

    public String[] split(String regex) {
        return split(regex, 0);
    }
    
    
    public String[] split(String regex, int limit) {
        char ch = 0;
        if (((regex.length() == 1 &&
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substring(off, next));
                    off = next + 1;
                } else {    // last one
                    //assert (list.size() == limit - 1);
                    int last = length();
                    list.add(substring(off, last));
                    off = last;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0)
                return new String[]{this};

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, length()));

            // Construct result
            int resultSize = list.size();
            if (limit == 0) {
                while (resultSize > 0 && list.get(resultSize - 1).isEmpty()) {
                    resultSize--;
                }
            }
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
        return Pattern.compile(regex).split(this, limit);
    }

 

위를 보면 limit을 통해 배열의 길이를 조절할 수 있다.

로직을 보기 전까지는 사실 어떻게 작동하는 지 헷갈릴 수 있다.

A, B, C와 2를 대입하여 사용한다면 A와 B인지 A와 B,C 인지...

그것을 보여주는 코드예시이다.

public class SplitExample {
    public static void main(String[] args) {
        String str = "apple,banana,cherry,orange,grape";

        String[] result = str.split(",", 3);

        for (String token : result) {
            System.out.println(token);
        }
    }
}

 

apple
banana
cherry,orange,grape

 

split은 구분자를 찾아 subString으로 분리반복하고 결과 배열을 생성하는 것을 볼 수 있다.

그리고 빈 문자열, 구분자 사이에 문자가 없는 경우

,,

StringTokenizer는 이를 무시하지만 split은 빈 문자열로 간주한다.

StringTokenizer와 split의 차이는 다음과 같다.

 

특징 String.split() StringTokenizer
구분자 정규식 지원 ( 복잡한 패턴 가능 ) 단순한 문자 집합( 정규식 미지원 ) 
결과 형태 String [] 배열  각 토큰으로 개별 반
구분자 반환 지원하지 않음 returenDelims 설정을 통한 반환 옵션 제공 
동작 방식 정규식으로 문자열 분리
빠른 최적화 코드 포함
빈 문자열도 인식
구분자 위치계산
내부 위치 포인터로 하나씩 반환
빈 문자열은 인식하지 않음
유연성 졍규식으로 높은 유연성 제한적
사용성 결과를 한 번에 반환 각 토큰으로 분리하여 하나씩 처리

 

둘 중 누가 성능이 좋은가?

정규식이나 복잡한 패턴이 아닌 두 메서드 모두 가능한 구분자로 진행하였을 때, 어느 때 어떤 메서드가 성능이 좋은지 4가지 기준으로 분류했다. 4가지 기준은 다음과 같다

  • 유니코드를 사용한 경우
  • 긴 문자열과 한 가지 구분자를 사용한 경우
  • 짧은 문자열과 한 가지 구분자를 사용한 경우
  • 짧은 문자열과 여러 구분자를 동시에 사용한 경우

StringTokenizer 한 가지 구분자로 이루어진 문자열을 사용한 경우는 문자열이 길든 짧든 항상 빨랐다.

split() 여러 구분자 유니코드를 사용한 경우에 빨랐다. 


StringTokenizer

StringTokenizer 클래스의 내부를 살펴보면 구분자에 따라 성능이 좌우된다.

 

StringTokenizer 클래스 scanToken 메서드

    private int scanToken(int startPos) {
        int position = startPos;
        while (position < maxPosition) {
            if (!hasSurrogates) {
                char c = str.charAt(position);
                if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))
                    break;
                position++;
            } else {
                int c = str.codePointAt(position);
                if ((c <= maxDelimCodePoint) && isDelimiter(c))
                    break;
                position += Character.charCount(c);
            }
        }
        if (retDelims && (startPos == position)) {
            if (!hasSurrogates) {
                char c = str.charAt(position);
                if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))
                    position++;
            } else {
                int c = str.codePointAt(position);
                if ((c <= maxDelimCodePoint) && isDelimiter(c))
                    position += Character.charCount(c);
            }
        }
        return position;
    }

( hasSurrogates는 유니코드인지 확인하는 것이고 indexOf와 isDelimiter 메서드는 같은 로직이다. )

 

여기서 성능에 영향을 끼치는 것은 indexOf() 메서드 이다. 이는 순차탐색으로 최악의 경우 O(|delimiter|)의 시간이 걸린다.

모든 문자에 이 코드를 작동된다고 생각한다면 성능은 구분자의 개 수에 따라 달라진다. 다음 예시를 보자

 

@ , < -

아스키 코드 값

@ : 64

, : 44

< : 60

{ : 123

 

@ , < {

@ 찾는 경우

 

 

@ , < {

< 찾는 경우

 

 

@ , < {

} ( 없는 구분자 ) 를 찾는 경우

}  는 아스키 코드 값이 125이기 때문에 maxDelimCodePoint ( 123 ) 보다 크다.

때문에 탐색되지 않는다.

 

 

@ , < {

구분자가 아닌 문자 A인 경우

A( 65 ) < maxDelimCodePoint (125)

때문에 모두 탐색해본다.

 


 

이처럼 단순 문자일 경우도 모두 탐색한다. 그래서 구분자가 많으면 많을수록 괴랄하게 탐색시간이 늘어난다.

유니코드라면 두 개의 char 를 쓰기 때문에 더욱 더 심각해진다.

split()

  • split 메서드는 정규식을 사용하여 구분자를 기준으로 문자열을 나눈다.
  • Pattern 클래스(정규 표현식 엔진)를 활용해 문장열을 순회, 구분자를 매칭한다.
  • 그 구분자 자리를 기억하고 문자열을 자른다.

다음과 같이 표현할 수 있다.

String input = "apple,banana,grape";
String[] result = input.split(",");

 

매칭 과정

  1. 구분자 ,를 문자열에서 찾기 시작.
    • 첫 번째 ,의 위치: 5.
    • 두 번째 ,의 위치: 12.
  2. 매칭된 위치: [5, 12].

자르는 위치 계산

구분자의 위치를 기반으로 문자열을 나눕니다:

  • 첫 번째 구분자의 앞부분 → 0~5 ("apple").
  • 첫 번째 구분자와 두 번째 구분자의 사이 → 6~12 ("banana").
  • 마지막 구분자 이후 → 13~끝 ("grape").

 

때문에  StringTokenizer가 많은 구분자나 유니코드를 문자마다 확인하는 경우에 정규 표현식 엔진으로 보다 빠르게 비교할 수 있고 문자열을 자르기 때문에 split() 성능이 좋지만, 정규 표현식 엔진의 기본 처리 시간이 StringTokenizer가 한 개의 구분자를 비교하는 것보다는 느린 경우가 많다. 또한 긴 문자열일 때 종종 같은 구분자라도 split() 빠른 경우가 있는데.. 같은 문자열을 길게 복사한 것이라서 모든 문자가 구분자의 코드 포인트보다 낮아 코드를 끝까지 읽어서 느린 경우가 아닌 찾아보니 split()은 JVM 메모리 관리 측면에서 같은 문자를 나누는 기준은 캐싱되어 성능이 최적화될 수 있다고 한다.

 

그렇다면 무엇을 사용해야 할까?
자바에서는 다음과 같이 설명하고 있다.

 

사실  StringTokenizer는 레거시 클래스이다?

StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method of String or the java.util.regex package instead.

호환성 이유로 유지되는 레거시 클래스이며 새로운 코드에서는 사용이 권장되지 않음. 대신 Split이나 java.util.regex 패키지를 사용하는 것이 좋다.

출처 : JAVA StringTokenizer

 

StringTokenizer는 사용하면 안될까?

결론적으로는 그렇다고 생각한다. JDK가 업데이트 됨에 따라 레거시 클래스는 삭제될 수도 있다.

JDK가 업데이트 되었다고 해서 사용하던 JDK를 바로 당장 전부 업데이트하지 않겠지만,

미래 지향적으로 본다면 그렇다고 생각한다.

반응형
반응형

문제

다음 프로그램은 커파일 오류가 발생한다. 소스의 어디에서 왜 컴파일 오류가 발생하는가?

 

#include<iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle(int radius = 1) { this->radius = radius; }
	int getRadius() { return radius; }
};

template<class T>
T bigger(T a, T b){ // 두 개의 매개 변수를 비교하여 큰 값을 리턴
	if (a > b)return a;
	else return b;
}

int main()
{
	int a = 20, b = 50, c;
	c = bigger(a, b);
	cout << "20과 50중 큰 값은 " << c << endl;
	Circle waffle(10), pizza(50), y;
	y = bigger(waffle, pizza);
	cout << "waffle과 pizza 중 큰 것은 반지름은 " << y.getRadius() << endl;
}

 

아래 결과와 같이 출력되도록 프로그램을 수정하라

20과 50중 큰 값은 50
waffle과 pizza 중 큰 것의 반지름은 20

 

y = bigger(waffle, pizza); 이 부분에서 클래스에 대한 비교가 없기 때문에 오류가 납니다.

 

소스코드

#include<iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle(int radius = 1) { this->radius = radius; }
	int getRadius() { return radius; }
};

template<class T>
T bigger(T a, T b){ // 두 개의 매개 변수를 비교하여 큰 값을 리턴
	if (a > b)return a;
	else return b;
}

Circle bigger(Circle a, Circle b) {
	if (a.getRadius() > b.getRadius())return a;
	else return b;
}

int main()
{
	int a = 20, b = 50, c;
	c = bigger(a, b);
	cout << "20과 50중 큰 값은 " << c << endl;
	Circle waffle(10), pizza(20), y;
	y = bigger(waffle, pizza);
	cout << "waffle과 pizza 중 큰 것은 반지름은 " << y.getRadius() << endl;
}
반응형
반응형

문제

간단한 그래픽 편집기를 콘솔 바탕으로 만들어보자. 그래픽 편집기의 기능은 "삽입", "삭제", "모두보기", "종료" 의 4가지이고, 실행 과정은 다음과 같다. (연결리스트 문제입니다.)

 

결과

 

소스코드

#include<iostream>
using namespace std;
class Shape {
	Shape* next;
protected:
	virtual void draw() = 0;
public:
	Shape() { next = NULL; }
	void paint() { draw(); }
	Shape* add(Shape* p) { this->next = p; return p; }
	Shape* getNext() { return next; }
	void setNext(Shape* p) { next = p; }
};
class Line : public Shape {
public:
	virtual void draw() { cout << "Line" << endl; }
};
class Circle : public Shape {
public:
	virtual void draw() { cout << "Circle" << endl; }
};
class Rect : public Shape {
public:
	virtual void draw() { cout << "Rect" << endl; }
};
class UI {
public:
	static int Mainmenu() {
		int input;
		cout << "삽입:1, 삭제:2, 모두보기:3, 종료:4 >> ";
		cin >> input;
		return input;
	}
	static int Insert() {
		int input;
		cout << "선:1, 원:2, 사각형:3 >> ";
		cin >> input;
		return input;
	}
	static int Delete() {
		int input;
		cout << "삭제하고자 하는 도형의 인덱스 >> ";
		cin >> input;
		return input;
	}
};
class GraphicEditor {
	Shape* pStart;
	Shape* pLast;
	int size;
public:
	GraphicEditor() { pStart = NULL; pLast = NULL; size = 0; }
	void make_shape() {
		cout << "그래픽 에디터입니다." << endl;
		while (true) {
			int input;
			input = UI::Mainmenu(); //처음 메뉴중 선택
			switch (input)
			{
			case 1: //1번 선택시
				input = UI::Insert(); // 도형 선택
				newshape(input);
				break;
			case 2:
				input = UI::Delete();
				delshape(input);
				break;
			case 3:
				show();
				break;
			case 4:
				exit(1);
			default:
				cout << "존재 하지 않는 메뉴" << endl;
				break;
			}
		}
	}
	void newshape(int input) {
		switch (input)
		{
		case 1:
			if (size == 0) { //처음 입력시
				pStart = new Line();
				pLast = pStart;
			}
			else  //아닐 시
				pLast = pLast->add(new Line());
			size++;
			break;
		case 2:
			if (size == 0) { //처음 입력시
				pStart = new Circle();
				pLast = pStart;
			}
			else  //아닐 시
				pLast = pLast->add(new Circle());
			size++;
			break;
		case 3:
			if (size == 0) { //처음 입력시
				pStart = new Rect();
				pLast = pStart;
			}
			else  //아닐 시
				pLast = pLast->add(new Rect());
			size++;
			break;
		default:
			cout << "존재 하지 않는 메뉴" << endl;
			break;
		}
	}
	void delshape(int n) {
		Shape* removenode = NULL;

		if (-1 > n || n > size - 1)
			cout << "존재 하지 않습니다." << endl;
		else {
			if (n == 0)
			{ //처음의 값을 삭제할 때는 pStart를 이용하여 삭제한다.
				removenode = pStart;
				pStart = pStart->getNext();
			}
			else { //아니라면
				int i = 1;
				for (Shape* p = pStart; p != NULL; p = p->getNext()) {
					//pStart 다음 값부터 그 다음값이 NULL이 아닐때까지 반복
					if (i == n) {
						removenode = p->getNext();
						//예) 1번째라면 이 반복문의 시작은 pStart이므로 다음 값을 삭제할 노드에 옮겨준뒤
						p->setNext(removenode->getNext());
						//p의 next가 가리키고 있는 노드를 현재의 p next로 옮겨주어 삭제할 노드를 한칸 띄어 이동하게 만든다.
					}
					i++;
				}
			}
			size--;
			delete removenode;
		}
	}
	void show()
	{
		if (size == 0)
			cout << "아무것도 존재하지 않습니다." << endl;
		else {
			int i = 0;
			for (Shape* p = pStart; p != NULL; p = p->getNext()) {
				cout << i << ": ";
				p->paint();
				i++;
			}
		}
	}
};

int main() {
	GraphicEditor* shape = new GraphicEditor;
	shape->make_shape();
	delete shape;
}

 

반응형
반응형

8장 8번을 응용한 문제입니다.

https://p-coding.tistory.com/24

 

명품 C++ Programming 실습문제 8장 8번

문제 다음 그림과 같은 상속 구조를 갖는 클래스를 설계한다. 모든 프린터는 모델명(model), 제조사(manufacturer), 인쇄 매수(printedCount), 인쇄 종이 잔량(availableCount)을 나타내는 정보와 print(int pages..

p-coding.tistory.com

문제

다음 그림과 같은 상속 구조를 갖는 클래스를 설계한다.

모든 프린터는 모델명(model), 제조사(manufacturer), 인쇄 매수(printedCount), 인쇄 종이 잔량(availableCount)을 나타내는 정보와 print(int pages) 멤버 함수를 가지며, print()가 호출할 때마다 pages 매의 용지를 사용한다. 잉크젯 프린터는 잉크 잔량(availableInk) 정보와 printInkJet(int pages) 멤버 함수를 추가적으로 가지며, 레이저 프린터는 토너 잔량(availableToner) 정보와 역시 printLaser(int pages) 멤버 함수를 추가적으로 가진다. 각 클래스에 적절한 접근 지정으로 멤버 변수와 함수, 생성자, 소멸자를 작성하고, 다음과 같이 실행되도록 전체 프로그램을 완성하라. 잉크젯 프린터 객체와 레이저 프린터 객체를 각각 하나만 동적 생성하여 시작한다.

결과

 

소스코드

#include<iostream>
using namespace std;
#include<iostream>
#include<string>
using namespace std;
class Printer {
	string model;		 //모델
	string manufacturer; //제조사
	int printedCount;	 //인쇄 매수
	int availableCount;  //인쇄 잔량
public:
	Printer(string m, string f, int ac) {
		model = m;
		manufacturer = f;
		availableCount = ac;
	}
	string getmodel() { return model; }				//모델명 리턴
	string getmanufacturer() { return manufacturer; }  //제조사 리턴
	int getCount() { return availableCount; } //인쇄 잔량 리턴
	virtual void show() = 0;
	virtual int getavailable() = 0;
	virtual void printitem(int pages) = 0;
	virtual int getavailableCount() = 0;
	void print(int pages) {
		printedCount = pages;
		availableCount -= printedCount;
	}

};
class IPrinter : public Printer {
	int availbleInk;	 //잉크 잔량
public:
	IPrinter(string m, string f, int aCount, int aInk) : Printer(m, f, aCount) { availbleInk = aInk; }
	virtual int getavailableCount() { return getCount(); }
	virtual void printitem(int pages) {
		availbleInk -= pages;
		print(pages);
	}
	virtual int getavailable() { return availbleInk; }
	virtual void show() {
		cout << getmodel() << ", " << getmanufacturer()
			<< ", 남은 종이" << getavailableCount() << ", 남은 잉크" << getavailable() << endl;
	}
};
class RPrinter : public Printer {
	int availableToner;	 //토너 잔량
public:
	RPrinter(string m, string f, int aCount, int aToner) : Printer(m, f, aCount) { availableToner = aToner; } //토너 초기화
	virtual int getavailableCount() { return getCount(); }
	virtual void printitem(int pages) {
		availableToner -= pages;
		print(pages);
	}
	virtual int getavailable() { return availableToner; }
	virtual void show() {
		cout << getmodel() << ", " << getmanufacturer()
			<< ", 남은 종이" << getavailableCount() << ", 남은 토너" << getavailable() << endl;
	}
};
int main()
{
	IPrinter IP("Officejet V40", "HP", 5, 10);
	RPrinter RP("SCX-6x45", "삼성전자", 3, 20);
	char q = 'y';
	int printer, pages;
	cout << "현재 작동중인 2대의 프린터는 아래와 같다" << endl;
	cout << "잉크젯 : ";
	IP.show();
	cout << "레이저 : ";
	RP.show();

	while (true) {
		cout << endl;
		cout << "프린터 (1:잉크젯, 2:레이저)와 매수 입력>>";
		cin >> printer >> pages;
		if (!(printer == 1 || printer == 2)) //프리터 1번 또는 2번을 선택하지 않았다면
			cout << "프린터가 존재하지 않습니다." << endl;
		else
		{
			if (printer == 1) { //잉크젯 선택
				if (IP.getavailableCount() < pages) //용지가 부족할 경우
					cout << "용지가 부족하여 프린트할 수 없습니다." << endl;
				else {
					IP.printitem(pages);
					cout << "프린터하였습니다.\n";
				}
			}
			else { //그 외 선택
				if (RP.getavailableCount() < pages) //용지가 부족할 경우
					cout << "용지가 부족하여 프린트할 수 없습니다." << endl;
				else {
					RP.printitem(pages);
					cout << "프린터하였습니다.\n";
				}
			}
		}
		IP.show();
		RP.show();
		cout << "계속 프린트 하시겠습니까?(y/n)>>";
		cin >> q; //n이면 루프 종료
		if (q == 'y')
			continue;
		else //그이외에의 값 종료 while(q != 'n')을 쓰셔도 됩니다.
			exit(1);
	}
}
반응형
반응형

7~8번에 쓰이는 사각형에 내접하는 도형을 표현하기 위한 Shape 클래스 입니다.

class Shape {
protected:
	string name; // 도형의 이름
	int width, height; // 도형이 내접하는 사각형의 너비와 높이
public:
	Shape(string n = "", int w = 0, int h = 0) { name = n; width = w; height = h; }
	virtual double getArea() { return 0; } //dummy 값 리턴
	string getName() { return name; } //이름 리턴
};

 

문제 7

Shape 클래스를 상속받아 타원을 표현하는 Oval, 사각형을 표현하는 Rect, 삼각형을 표현하는 Triangular 클래스를 작성하라. main() 을 작성하고 실행하면 다음과 같다.

int main() {
	Shape* p[3];
	p[0] = new Oval("빈대떡", 10, 20);
	p[1] = new Rect("찰떡", 30, 40);
	p[2] = new Triangular("토스트", 30, 40);
	for (int i = 0; i < 3; i++)
		cout << p[i]->getName() << " 넓이는 " << p[i]->getArea() << endl;

	for (int i = 0; i < 3; i++)delete p[i];
}

 

결과

빈대떡 넓이는 628
찰떡 넓이는 1200
토스트 넓이는 600

 

소스코드

#include<iostream>
#include<string>
using namespace std;

class Shape {
protected:
	string name; // 도형의 이름
	int width, height; // 도형이 내접하는 사각형의 너비와 높이
public:
	Shape(string n = "", int w = 0, int h = 0) { name = n; width = w; height = h; }
	virtual double getArea() { return 0; } //dummy 값 리턴
	string getName() { return name; } //이름 리턴
};
class Oval : public Shape {
public:
	Oval(string name, int width, int height) : Shape(name, width, height) {};
	virtual double getArea()
	{
		return width * height * 3.14;
	}
};
class Rect : public Shape {
public:
	Rect(string name, int width, int height) : Shape(name, width, height) {};
	virtual double getArea()
	{
		return width * height;
	}
};
class Triangular : public Shape {
public:
	Triangular(string name, int width, int height) : Shape(name, width, height) {};
	virtual double getArea()
	{
		return (width * height) / 2;
	}
};
int main() {
	Shape* p[3];
	p[0] = new Oval("빈대떡", 10, 20);
	p[1] = new Rect("찰떡", 30, 40);
	p[2] = new Triangular("토스트", 30, 40);
	for (int i = 0; i < 3; i++)
		cout << p[i]->getName() << " 넓이는 " << p[i]->getArea() << endl;

	for (int i = 0; i < 3; i++)delete p[i];
}

 

문제 8

문제 7에 주어진 Shape 클래스를 추상 클래스로 만들고 문제 7을 다시 작성하라.

#include<iostream>
#include<string>
using namespace std;

class Shape {
protected:
	string name; // 도형의 이름
	int width, height; // 도형이 내접하는 사각형의 너비와 높이
public:
	Shape(string n = "", int w = 0, int h = 0) { name = n; width = w; height = h; }
	virtual double getArea() { return 0; } //dummy 값 리턴
	string getName() { return name; } //이름 리턴
};
class Oval : public Shape {
public:
	Oval(string name, int width, int height) : Shape(name, width, height) {};
	 double getArea()
	{
		return width * height * 3.14;
	}
};
class Rect : public Shape {
public:
	Rect(string name, int width, int height) : Shape(name, width, height) {};
	 double getArea()
	{
		return width * height;
	}
};
class Triangular : public Shape {
public:
	Triangular(string name, int width, int height) : Shape(name, width, height) {};
	 double getArea()
	{
		return (width * height) / 2;
	}
};
int main() {
	Shape* p[3];
	p[0] = new Oval("빈대떡", 10, 20);
	p[1] = new Rect("찰떡", 30, 40);
	p[2] = new Triangular("토스트", 30, 40);
	for (int i = 0; i < 3; i++)
		cout << p[i]->getName() << " 넓이는 " << p[i]->getArea() << endl;

	for (int i = 0; i < 3; i++)delete p[i];
}
반응형
반응형

문제

다음 AbstractStack은 정수 스택 클래스로서 추상 클래스이다.

class AbstractStack {
public:
	virtual bool push(int n) = 0; //스택에 n을 푸시한다. 스택이 full이면 false 리턴
	virtual bool pop(int& n) = 0; //스택에서 팝한 정수를 n에 저장하고 스택이 empty이면 리턴
	virtual int size() = 0; // 현재 스택에 저장된 개수 리턴
};

이를 상속받아 정수를 푸시, 팝하는 IntStack 클래스를 만들고 사용 사례를 보아라.

 

소스코드

#include<iostream>
using namespace std;

class AbstractStack {
public:
	virtual bool push(int n) = 0; //스택에 n을 푸시한다. 스택이 full이면 false 리턴
	virtual bool pop(int& n) = 0; //스택에서 팝한 정수를 n에 저장하고 스택이 empty이면 리턴

	virtual int size() = 0; // 현재 스택에 저장된 개수 리턴
};

class IntStack : public AbstractStack {
	int* data;
	int s, top;
public:
	IntStack(int s) {
		this->s = s;
		data = new int[s];
		top = -1;
	}
	~IntStack() { delete[] data; }
	void show() {
		for (int i = top; i >= 0; i--)
		{
			cout << data[i] << " ";
		}
		cout << endl;
	}
	virtual bool push(int n) {
		if (top + 1 >= s)return false; //top size보다 같거나 크면 false리턴
		data[++top] = n; //아니라면 값을 넣은 후 true 리턴
		return true;
	}
	virtual bool pop(int& n) {
		if (top <= -1)return false; //top이 -1보다 같거나 작다면 false리턴
		n = data[top--];
		return true;
	}
	virtual int size() {
		return top + 1; //저장된 개수 리턴 0부터 시작하므로 +1
	}
};

int main()
{
	int size, menu, push, pop;

	cout << "스택의 크기는?>> ";
	cin >> size;

	IntStack stack(size);

	while (true) {
		cout << endl << "1. 푸시 2. 팝 3. 스택보기 4. 종료" << endl << "메뉴 선택>> ";
		cin >> menu;
		switch (menu) {
		case 1:
			cout << "push 할 값>> ";
			cin >> push;
			if (stack.push(push))
				cout << "push 완료" << endl;
			else
				cout << "push할 공간이 없습니다." << endl;
			break;
		case 2:
			if (stack.pop(pop))
				cout << "pop 완료 pop한 값 : " << pop << endl;
			else
				cout << "빈 스택입니다." << endl;
			break;
		case 3:
			cout << "스택 데이터 : ";
			stack.show();
			break;
		case 4:
			cout << "종료" << endl;
			exit(1);
		default:
			cout << "잘못 입력" << endl;
			break;
		}
	}
}

 

결과

 

반응형
반응형

문제

디지털 회로에서 기본적인 게이트로 OR 게이트, AND 게이트, XOR 게이트 등이 있다.

이들은 각각 두 입력 신호를 받아 OR 연산, AND 연산, XOR 연산을 수행한 결과를 출력한다. 이 게이트들을 각각 ORGate, XORGate, ANDGate 클래스로 작성하고자 한다. ORGate, XORGate, ANDGate 클래스가 AbstractGate를 상속받도록 작성하라.

class AbstractGate { //추상 클래스
protected:
	bool x, y;
public:
	void set(bool x, bool y) { this->x = x; this->y = y; }
	virtual bool operation() = 0;
};

 

ANDGate, ORGate, XORGate를 활용하는 사례와 결과는 다음과 같다.

int main() {
	ANDGate and;
	ORGate or;
	XORGate xor;

	and.set(true, false);
	or.set(true, false);
	xor.set(true, false);
	cout.setf(ios::boolalpha); //불린 값은 "true", "false" 문자열로 출력할 것을 지시
	cout << and .operation() << endl; // AND 결과는 false
	cout << or.operation() << endl; // OR 결과는 true
	cout << xor .opeartion() << endl; // XOR 결과는 true
}

 

결과

false
true
true

 

소스코드

#include<iostream>
using namespace std;
class AbstractGate { //추상 클래스
protected:
	bool x, y;
public:
	void set(bool x, bool y) { this->x = x; this->y = y; }
	virtual bool operation() = 0;
};

class ANDGate : public AbstractGate {
public:
	virtual bool operation() {
		if (x == true && y == true)
			return true;
		else return false;
	}
};
class ORGate : public AbstractGate {
public:
	virtual bool operation() {
		if (x == true || y == true)
			return true;
		else return false;
	}
};
class XORGate : public AbstractGate {
public:
	virtual bool operation() {
		if (x != y)
			return true;
		else return false;
	}
};

int main() {
	ANDGate And;
	ORGate Or;
	XORGate Xor;

	And.set(true, false);
	Or.set(true, false);
	Xor.set(true, false);
	cout.setf(ios::boolalpha); //불린 값은 "true", "false" 문자열로 출력할 것을 지시
	cout << And.operation() << endl; // AND 결과는 false
	cout << Or.operation() << endl; // OR 결과는 true
	cout << Xor.operation() << endl; // XOR 결과는 true
}

 

반응형
반응형

3~4번에 쓰이는 추상 클래스 LoopAdder 입니다.

class LoopAdder { // 추상 클래스 
	string name; // 루프의 이름 
	int x, y, sum; // x에서 y까지의 합은 sum 
	void read(); // x, y 값을 읽어 들이는 함수 
	void write(); // sum을 출력하는 함수 
protected:
	LoopAdder(string name = "") { // 루프의 이름을 받는다. 초깃값은 "" 
		this->name = name;
	}
	int getX() { return x; }
	int getY() { return y; }
	virtual int calculate() = 0; // 순수 가상 함수. 루프를 돌며 합을 구하는 함수 
public:
	void run(); // 연산을 진행하는 함수 
};

void LoopAdder::read() { // x, y 입력 
	cout << name << ":" << endl;
	cout << "처음 수에서 두번째 수까지 더한다. 두 수를 입력하세요 >> ";
	cin >> x >> y;
}

void LoopAdder::write() { // 결과 sum 출력 
	cout << x << "에서 " << y << "까지의 합 = " << sum << " 입니다" << endl;
}

void LoopAdder::run() {
	read(); // x, y를 읽는다 
	sum = calculate(); // 루프를 돌면서 계산한다. 
	write(); // 결과 sum을 출력한다. 
}

 

문제

LoopAdder 클래스를 상속받아 다음 main() 함수와 실행 결과처럼 되도록 WhileLoopAdder, DowhileLoopAdder 클래스를 작성하라. while 문, do-while 문을 이용하여 합을 구하도록 calculate() 함수를 각각 작성하면 된다.

int main() {
	WhileLoopAdder whileLoop("While Loop");
	DoWhileLoopAdder doWhileLoop("Do While Loop");

	whileLoop.run();
	doWhileLoop.run();
}

 

결과

While Loop :
처음 수에서 두번째 수까지 더합니다.두 수를 입력하세요 >> 3 10
3에서 5까지의 합 = 12 입니다 Do While Loop :
처음 수에서 두번째 수까지 더합니다.두 수를 입력하세요 >> 10 20
10에서 20까지의 합 = 165

 

소스코드

#include<iostream>
using namespace std;
class LoopAdder { // 추상 클래스 
	string name; // 루프의 이름 
	int x, y, sum; // x에서 y까지의 합은 sum 
	void read(); // x, y 값을 읽어드리는 함수 
	void write(); // sum을 출력하는 함수 
protected:
	LoopAdder(string name = "") { // 루프의 이름을 받는다. 초깃값은 "" 
		this->name = name;
	}
	int getX() { return x; }
	int getY() { return y; }
	virtual int calculate() = 0; // 순수 가상 함수. 루프를 돌며 합을 구하는 함수 
public:
	void run(); // 연산을 진행하는 함수 
};

void LoopAdder::read() { // x, y 입력 
	cout << name << ":" << endl;
	cout << "처음 수에서 두번째 수까지 더한다. 두 수를 입력하세요 >> ";
	cin >> x >> y;
}

void LoopAdder::write() { // 결과 sum 출력 
	cout << x << "에서 " << y << "까지의 합 = " << sum << " 입니다" << endl;
}

void LoopAdder::run() {
	read(); // x, y를 읽는다 
	sum = calculate(); // 루프를 돌면서 계산한다. 
	write(); // 결과 sum을 출력한다. 
}
class WhileLoopAdder : public LoopAdder {
	string name;
public:
	WhileLoopAdder(string name = "") :LoopAdder(name) { this->name = name; }
	virtual int calculate() {
		int sum = 0;
		int x = getX();
		int y = getY();
		while (x <= y) {
			sum += x;
			x++;
		}
		return sum;
	}
};

class DoWhileLoopAdder : public LoopAdder {
	string name;
public:
	DoWhileLoopAdder(string name = "") :LoopAdder(name) { this->name = name; }
	virtual int calculate() {
		int sum = 0;
		int x = getX();
		int y = getY();
		do {
			sum += x;
			x++;
		} while (x <= y);
		return sum;
	}
};
int main() {
	WhileLoopAdder whileLoop("While Loop");
	DoWhileLoopAdder doWhileLoop("Do While Loop");

	whileLoop.run();
	doWhileLoop.run();
}
반응형
반응형

3~4번에 쓰이는 추상 클래스 LoopAdder 입니다.

class LoopAdder { // 추상 클래스 
	string name; // 루프의 이름 
	int x, y, sum; // x에서 y까지의 합은 sum 
	void read(); // x, y 값을 읽어 들이는 함수 
	void write(); // sum을 출력하는 함수 
protected:
	LoopAdder(string name = "") { // 루프의 이름을 받는다. 초깃값은 "" 
		this->name = name;
	}
	int getX() { return x; }
	int getY() { return y; }
	virtual int calculate() = 0; // 순수 가상 함수. 루프를 돌며 합을 구하는 함수 
public:
	void run(); // 연산을 진행하는 함수 
};

void LoopAdder::read() { // x, y 입력 
	cout << name << ":" << endl;
	cout << "처음 수에서 두번째 수까지 더한다. 두 수를 입력하세요 >> ";
	cin >> x >> y;
}

void LoopAdder::write() { // 결과 sum 출력 
	cout << x << "에서 " << y << "까지의 합 = " << sum << " 입니다" << endl;
}

void LoopAdder::run() {
	read(); // x, y를 읽는다 
	sum = calculate(); // 루프를 돌면서 계산한다. 
	write(); // 결과 sum을 출력한다. 
}

 

문제

LoopAdder 클래스를 상속받아 다음 main() 함수와 실행 결과처럼 되도록  ForLoopAdder 클래스를 작성하라. ForLoopAdder 클래스의 calculate() 함수는 for 문을 이용하여 합을 구한다.

int main() {
	ForLoopAdder forLoop("For Loop");
	forLoop.run();
}

 

결과

While Loop:
처음 수에서 두번째 수까지 더합니다. 두 수를 입력하세요 >> 3 10
3에서 10까지의 합 = 52 입니다

 

소스코드

#include<iostream>
using namespace std;
class LoopAdder { // 추상 클래스 
	string name; // 루프의 이름 
	int x, y, sum; // x에서 y까지의 합은 sum 
	void read(); // x, y 값을 읽어 들이는 함수 
	void write(); // sum을 출력하는 함수 
protected:
	LoopAdder(string name = "") { // 루프의 이름을 받는다. 초깃값은 "" 
		this->name = name;
	}
	int getX() { return x; }
	int getY() { return y; }
	virtual int calculate() = 0; // 순수 가상 함수. 루프를 돌며 합을 구하는 함수 
public:
	void run(); // 연산을 진행하는 함수 
};

void LoopAdder::read() { // x, y 입력 
	cout << name << ":" << endl;
	cout << "처음 수에서 두번째 수까지 더한다. 두 수를 입력하세요 >> ";
	cin >> x >> y;
}

void LoopAdder::write() { // 결과 sum 출력 
	cout << x << "에서 " << y << "까지의 합 = " << sum << " 입니다" << endl;
}

void LoopAdder::run() {
	read(); // x, y를 읽는다 
	sum = calculate(); // 루프를 돌면서 계산한다. 
	write(); // 결과 sum을 출력한다. 
}

class ForLoopAdder : public LoopAdder {
	string name;
public:
	ForLoopAdder(string name = "") : LoopAdder(name) { this->name = name; }
	virtual int calculate() {
		int sum = 0;
		for (int i = getX(); i <= getY(); i++)
			sum += i;
		return sum;
	}
};

int main() {
	ForLoopAdder forLoop("For Loop");
	forLoop.run();
}
반응형
반응형

1~2번에 쓰이는 단위변환 추상 클래스 Converter입니다.

#include<iostream>
using namespace std;
class Converter {
protected:
	double ratio;
	virtual double convert(double src) = 0; //src를 다른 단위로 변환한다.
	virtual string getSourceString() = 0; //src 단위 명칭
	virtual string getDestString() = 0; //dest 단위 명칭
public:
	Converter(double ratio) { this->ratio = ratio; }
	void run() {
		double src;
		cout << getSourceString() << "을 " << getDestString() << "로 바꿉니다. ";
		cout << getSourceString() << "을 입력하세요>> ";
		cin >> src;
		cout << "변환 결과 : " << convert(src) << getDestString() << endl;
	}
};

 

문제

Converter 클래스를 상속받아 km를 mile(마일)로 변환하는 KmToMile 클래스를 작성하라. main() 함수와 실행 결과는 다음과 같다.

int main()
{
	KmToMile toMile(1.609344); //1mile은 1.609344km
	toMile.run();
}

 

결과

Km을 Mile로 바꿉니다. Km을 입력하세요>> 25
반환 결과 : 15.5343Mile

 

소스코드

#include<iostream>
using namespace std;
class Converter {
protected:
	double ratio;
	virtual double convert(double src) = 0; //src를 다른 단위로 변환한다.
	virtual string getSourceString() = 0; //src 단위 명칭
	virtual string getDestString() = 0; //dest 단위 명칭
public:
	Converter(double ratio) { this->ratio = ratio; }
	void run() {
		double src;
		cout << getSourceString() << "을 " << getDestString() << "로 바꿉니다. ";
		cout << getSourceString() << "을 입력하세요>> ";
		cin >> src;
		cout << "변환 결과 : " << convert(src) << getDestString() << endl;
	}
};
class KmToMile : public Converter {
	double mile;
public:
	KmToMile(double mile) : Converter(mile) { this->mile = mile; }
	virtual double convert(double src) { return src / mile; }
	virtual string getSourceString() { return "Km"; }
	virtual string getDestString() { return "Mile"; }
};
int main()
{
	KmToMile toMile(1.609344); //1mile은 1.609344km
	toMile.run();
}

 

반응형

+ Recent posts