Search
Duplicate
📚

기말

생성일
2022/06/14 12:32
태그

8장. 실습문제

문제 1~2에 적용되는 원을 추상화한 Circle 클래스가 있다.
1.
다음 코드가 실행되도록 Circle을 상속받은 NamedCircle 클래스를 작성하고 전체 프로그램을 완성하라
#include <iostream> using namespace std; class Circle { int radius; public: Circle (int radius = 0) { this->radius = radius; } int getRadius() { return radius; } void setRadius(int radius) { this->radius = radius; } double getArea() { return 3.14 * radius * radius; } }; class NameCircle : public Circle{ string name; public: NameCircle(int radius, string name) { setRadius(radius); this->name = name; } void show() { cout << "반지름이 " << getRadius() << "인 " << this->name; } }; int main() { NameCircle waffle(3, "waffle"); waffle.show(); }
C++
복사
2.
다음과 같이 배열을 선언하여 다음 실행 결과가 나오도록 Circle을 상속받은 NamedCircle 클래스와 main() 함수 등 필요한 함수를 작성하라.
#include <iostream> using namespace std; class Circle { int radius; public: Circle (int radius = 0) { this->radius = radius; } int getRadius() { return radius; } void setRadius(int radius) { this->radius = radius; } double getArea() { return 3.14 * radius * radius; } }; class NameCircle : public Circle { string name; public: void setRN(int R, string N) { setRadius(R); this->name = N; } string getName() { return name; } }; int main() { string name; int radius, big; double size[5]; NameCircle pizza[5]; cout << "5 개의 정수 반지름과 원의 이름을 입력하세요\n"; for (int i = 0; i < 5; i++) { cout << i + 1 << ">> "; cin >> radius >> name; pizza[i].setRN(radius, name); size[i] = pizza[i].getArea(); } big = 0; for (int i = 0; i < 4; i++) { if(size[i] < size[i+1]) big = i + 1; } cout << "가장 면적이 큰 피자는 " << pizza[big].getName() << "입니다"; }
C++
복사
3~4에 적용되는 2차원 상의한 점을 표현하는 Point 클래스
3.
다음 main() 함수가 실행되도록 Point 클래스를 상속받은 ColorPoint 클래스를 작성하고, 전체 프로그램을 완성하라
#include <iostream> using namespace std; class Point { int x, y; public: Point(int x, int y) { this->x = x; this->y = y; } int getX() { return x; } int getY() { return y; } protected: void move(int x, int y) { this->x = x; this->y = y; } }; class ColorPoint : public Point { string color; public: ColorPoint(int x, int y, string color) : Point(x, y) { this->color = color; } void setPoint(int x, int y) { move(x, y); } void setColor(string color) { this->color = color; } void show() { cout << color << "색으로 (" << getX() << ", " << getY() << ")에 위치한 점입니다."; } }; int main() { ColorPoint cp(5, 5, "RED"); cp.setPoint(10, 20); cp.setColor("BLUE"); cp.show(); }
C++
복사
4.
다음 main() 함수가 실행되도록 Point 클래스를 상속받는 ColorPoint 클래스를 작성하고, 전체 프로그램을 완성하라
#include <iostream> #include <string> using namespace std; class Point { int x, y; public: Point(int x, int y) { this->x = x; this->y = y; } int getX() { return x; } int getY() { return y; } protected: void move(int x, int y) { this->x = x; this->y = y; } }; class ColorPoint : public Point { string color; public: ColorPoint(int x=0, int y=0, string color="BLACK") : Point(x, y) { this->color = color; } void setPoint(int x, int y) { move(x, y); } void setColor(string color) { this->color = color; } void show() { cout << color << "색으로 (" << getX() << ", " << getY() << ")에 위치한 점입니다." << endl; } }; int main() { ColorPoint zeroPoint; zeroPoint.show(); ColorPoint cp(5, 5); cp.setPoint(10, 20); cp.setColor("BLUE"); cp.show(); }
C++
복사
5~6에 적용되는 BaseArray 클래스
5.
MyQueue
#include <iostream> #include <string> using namespace std; class BaseArray { int capacity; int *mem; protected : BaseArray(int capacity=100) { this->capacity = capacity; mem = new int [capacity]; } ~BaseArray() { delete [] mem; } void put(int index, int val) { mem[index] = val; } int get(int index) { return mem[index]; } int getCapacity() { return capacity; } }; class MyQueue : public BaseArray { int enindex; int deindex; public : MyQueue(int size) : BaseArray(size){ enindex=0; deindex=-1; } void enqueue(int n){ put( enindex, n); enindex++; } int capacity() { return getCapacity(); } int length() { return enindex; } int dequeue() { enindex--; deindex++; return get(deindex); } }; int main() { MyQueue mQ(100); int n; cout << "큐에 삽입할 5개의 정수를 입력하라>> "; for (int i = 0; i < 5; i++) { cin >> n; mQ.enqueue(n); } cout << "큐의 용량:" << mQ.capacity() << ", 큐의 크기:" << mQ.length() << endl; cout << "큐의 원소를 순서대로 제거하여 출력한다>> "; while(mQ.length() != 0) { cout << mQ.dequeue() << ' '; } cout << endl << "큐의 현재 크기 : " << mQ.length() << endl; }
C++
복사
6.
MyStack
#include <iostream> #include <string> using namespace std; class BaseArray { int capacity; int *mem; protected: BaseArray(int capacity=100) { this->capacity = capacity; mem = new int [capacity]; } ~BaseArray() { delete [] mem; } void put(int index, int val) { mem[index] = val; } int get(int index) { return mem[index]; } int getCapacity() { return capacity; } }; class MyStack : public BaseArray { int top; public: MyStack(int size) : BaseArray(size) { top = 0; } void push(int n) { put(top, n); top++; } int capacity() {return getCapacity(); } int length() { return top; } int pop() { top--; return get(top); } }; int main() { MyStack mStack(100); int n; cout << "스택에 삽입할 5개의 정수를 입력하라>> "; for (int i = 0; i < 5; i++) { cin >> n; mStack.push(n); } cout << "스택 용량:" << mStack.capacity() << ", 스택 크기:" << mStack.length() << endl; cout << "스택의 모든 원소를 팝하여 출력한다>> "; while(mStack.length() != 0) { cout << mStack.pop() << ' '; } cout << endl << "스택의 현재 크기 : " << mStack.length() << endl; }
C++
복사
7.
아래와 같은 BaseMemory 클래스를 상속받는 ROM, RAM 클래스를 작성하라
#include <iostream> #include <string> using namespace std; class BaseMemory { char *mem; protected: BaseMemory(int size) { mem = new char [size]; } ~BaseMemory() { delete [] mem; } char getMem() { return *mem; } char getIndex(int index) { return mem[index]; } void setIndex(int index, char c) { mem[index] = c; } void setMem(char x[], int s) { for (int i = 0; i < s; ++i) mem[i] = x[i]; } }; class ROM : virtual protected BaseMemory { public: ROM(int size, char x[], int s) : BaseMemory(size) { setMem(x, s); } char read(int index) { return getIndex(index); } }; class RAM : virtual protected BaseMemory { public: RAM(int size) : BaseMemory(size) {} void write(int index, char c) { setIndex(index, c); } char read(int index) { return getIndex(index); } }; int main() { char x[5] = {'h', 'e', 'l', 'l', 'o'}; ROM biosROM(1024 * 10, x, 5); RAM mainMemory(1024 * 1024); for (int i = 0; i < 5; i++) mainMemory.write(i, biosROM.read(i)); for (int i = 0; i < 5; i++) cout << mainMemory.read(i); }
C++
복사

8장. 수업 코드

예제 8-3 생성자 매개 변수 전달

TV, WideTV, SmartTV

TV.h

// #ifndef __TV_H #define __TV_H #include <iostream> using namespace std; class TV { int size; public: TV() { size = 20; } TV(int size) { this->size = size; cout << "TV 생성자" << endl;} ~TV() { cout << "TV 소멸자" << endl; } int getSize() { return size; } }; #endif
C++
복사

WideTV.h

#ifndef __WIDETV_H #define __WIDETV_H #include "TV.h" class WideTV : public TV{ bool videoIn; public: WideTV(int size, bool videoIn) : TV(size) { this->videoIn = videoIn; cout << "WideTV 생성자" << endl; } ~WideTV() { cout << "WideTV 소멸자" << endl; } bool getVideoIn() { return videoIn; } }; #endif
C++
복사

SmartTV.h

#ifndef __SMARTTV_H #define __SMARTTV_H #include "WideTV.h" #include <string> using namespace std; class SmartTV : public WideTV { string ipAddr; public: SmartTV(string ipAddr, int size) : WideTV(size, true) { this->ipAddr = ipAddr; cout << "SmartTV 생성자" << endl; } ~SmartTV() { cout << "SmartTV 소멸자" << endl; } string getIpAddr() { return ipAddr; } }; #endif
C++
복사

MyStack.cpp

#include "MyStack.h" #include <iostream> using namespace std; MyStack::MyStack(int capacity) : BaseArray(capacity) { top = 0; } void MyStack::push(int n) { // 스택 다 찼나? if (top == getCapacity()) { cout << "Stack Full!!" << endl; return; } put(top, n); // top에다가 n값을 넣어라 //top++; } int MyStack::capacity() { return getCapacity(); } int MyStack::length() { return top; } int MyStack::pop() { // 스택 비어있는지? if (top == 0) { cout << "Stack Empty!!" << endl; return -1; } //top--; return get(--top); }
C++
복사

9장. 실습문제

1.
위의 Shape 클래스를 상속받아 타원을 표현하는 Oval, 사각형을 표현하는 Rect, 삼각형을 표현하는 Triangular 클래스를 작성하라.
#include <iostream> 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; } string getName() { return name; } }; class Oval : public Shape { public: Oval(string n, int w, int h) : Shape(n, w, h){;} virtual double getArea() { return 3.14 * width * height; } }; class Rect : public Shape { public: Rect(string n, int w, int h) : Shape(n, w, h){;} virtual double getArea() { return width * height; } }; class Triangular : public Shape { public: Triangular(string n, int w, int h) : Shape(n, w, h){;} 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]; }
C++
복사
2.
Shap 클래스를 추상클래스로 만들고 다시 작성
#include <iostream> 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() = 0; // 순수 가상함수 // 순수가상함수를 하나 이상 가지고 있는 클래스 -> 추상클래스 string getName() { return name; } }; class Oval : public Shape { public: Oval(string n, int w, int h) : Shape(n, w, h){;} virtual double getArea() { return 3.14 * width * height; } }; class Rect : public Shape { public: Rect(string n, int w, int h) : Shape(n, w, h){;} virtual double getArea() { return width * height; } }; class Triangular : public Shape { public: Triangular(string n, int w, int h) : Shape(n, w, h){;} 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]; Shape *s; s = new Rect("모양", 10, 20); cout << s->getName() << s->getArea() << endl; }
C++
복사
#include <iostream> using namespace std; class Base { public: virtual void f() { cout << "Base f()" << endl; } }; class Derived : public Base { public: virtual void f() { cout << "Derived::f() called" << endl; } }; int main() { Base b; Base *bp; bp = &b; bp->f(); Derived d; Derived* dp; dp = &d; dp->f(); bp = &d; //upcasting bp->f(); }
C++
복사
#include <iostream> using namespace std; class Shape { public: void paint(); virtual void draw() = 0; // 순수 가상함수 }; class Circle : public Shape { public: void draw(); }; class Line : public Shape { public: void draw(); }; class Rect : public Shape { public: void draw(); }; void Shape::paint() { draw(); } /* void Shape::draw() { cout << "Shape::draw()" << endl; } */ // 여기를 할 필요 없다 void Circle::draw() { cout << "원 그리기" << endl; } void Line::draw() { cout << "선 그리기" << endl; } void Rect::draw() { cout << "사각형 그리기" << endl; } int main() { Shape* paintPad[100]; paintPad[0] = new Line(); paintPad[1] = new Rect(); paintPad[2] = new Line(); paintPad[3] = new Circle(); paintPad[4] = new Circle(); int count = 5; for (int i = 0; i < count; i++) { paintPad[i]->paint(); } }
C++
복사

10장. 수업 코드

Template - Generic Stack Class

Stack.h
#ifndef __STACK_H #define __STACK_H template <class T> class MyStack { int tos; T data[100]; public: MyStack(); void push(T element); T pop(); }; #endif
C++
복사
Point.h
#ifndef __POINT_H #define __POINT_H #include <iostream> #include <string> using namespace std; class Point { int x, y; public: Point(int x=0, int y=0) { this->x = x; this->y = y; } void print() { cout << '(' << x << ", " << y << ')' << endl; } }; #endif
C++
복사
Stack.cpp
#include "Stack.h" #include "Point.h" #include <iostream> using namespace std; template <class T> MyStack<T>::MyStack() { tos = 0; } template <class T> void MyStack<T>::push(T element) { if (tos >= 100) { cout << "Stack Full!!!" << endl; return; } data[tos++] = element; } template <class T> T MyStack<T>::pop() { if (tos <= 0) { cout << "Stack Empty!!!" << endl; } return data[--tos]; } template class MyStack<int>; template class MyStack<double>; template class MyStack<Point>; template class MyStack<Point*>;
C++
복사
main.cpp
#include <iostream> #include "Stack.h" #include "Point.h" using namespace std; int main() { MyStack<int> iStack; // int 타입의 스택을 만들것이다 iStack.push(10); iStack.push(20); cout << iStack.pop() << ", " << iStack.pop() << endl; MyStack<double> dStack; dStack.push(1.1234); dStack.push(3.14); dStack.push(0.101); cout << dStack.pop() << endl; MyStack<Point> pStack; Point p1(10, 2), p2(40, 70); pStack.push(p1); pStack.pop().print(); pStack.pop().print(); MyStack<Point*> ppStack; ppStack.push(new Point(10, 20)); ppStack.push(new Point(100, 200)); ppStack.pop()->print(); ppStack.pop()->print(); MyStack<Point*> *pppStack; pppStack = new MyStack<Point*>(); pppStack->push(new Point(500, 600)); pppStack->push(new Point(230, 240)); pppStack->pop()->print(); pppStack->pop()->print(); }
C++
복사

Vector 컨테이너 활용 스택

#include <iostream> #include "Stack.h" #include "Point.h" #include <vector> using namespace std; int main() { MyStack<int> iStack; // int 타입의 스택을 만들것이다 iStack.push(10); iStack.push(20); cout << iStack.pop() << ", " << iStack.pop() << endl; MyStack<double> dStack; dStack.push(1.1234); dStack.push(3.14); dStack.push(0.101); cout << dStack.pop() << endl; MyStack<Point> pStack; Point p1(10, 2), p2(40, 70); pStack.push(p1); pStack.pop().print(); pStack.pop().print(); MyStack<Point*> ppStack; ppStack.push(new Point(10, 20)); ppStack.push(new Point(100, 200)); ppStack.pop()->print(); ppStack.pop()->print(); MyStack<Point*> *pppStack; pppStack = new MyStack<Point*>(); pppStack->push(new Point(500, 600)); pppStack->push(new Point(230, 240)); pppStack->pop()->print(); pppStack->pop()->print(); vector<int> intV; intV.push_back(10); intV.push_back(20); intV.push_back(30); cout << intV.at(0) << endl; cout << intV[1] << endl; intV.at(1) = 300; cout << intV[1] << endl; vector<Point*> pointV; for (int i = 0; i < 1000; i++) { pointV.push_back(new Point(i*2, i*3)); } cout << pointV.size() << endl; cout << pointV.capacity() << endl; for (int i = 0; i < pointV.size(); i++) { pointV[i]->print(); } }
C++
복사

iterator를 사용하여 Vector 활용

#include <iostream> #include "Stack.h" #include "Point.h" #include <vector> using namespace std; int main() { MyStack<int> iStack; // int 타입의 스택을 만들것이다 iStack.push(10); iStack.push(20); cout << iStack.pop() << ", " << iStack.pop() << endl; MyStack<double> dStack; dStack.push(1.1234); dStack.push(3.14); dStack.push(0.101); cout << dStack.pop() << endl; MyStack<Point> pStack; Point p1(10, 2), p2(40, 70); pStack.push(p1); pStack.pop().print(); pStack.pop().print(); MyStack<Point*> ppStack; ppStack.push(new Point(10, 20)); ppStack.push(new Point(100, 200)); ppStack.pop()->print(); ppStack.pop()->print(); MyStack<Point*> *pppStack; pppStack = new MyStack<Point*>(); pppStack->push(new Point(500, 600)); pppStack->push(new Point(230, 240)); pppStack->pop()->print(); pppStack->pop()->print(); vector<int> intV; intV.push_back(10); intV.push_back(20); intV.push_back(30); cout << intV.at(0) << endl; cout << intV[1] << endl; intV.at(1) = 300; cout << intV[1] << endl; vector<Point*> pointV; for (int i = 0; i < 1000; i++) { pointV.push_back(new Point(i*2, i*3)); } cout << pointV.size() << endl; cout << pointV.capacity() << endl; for (int i = 0; i < pointV.size(); i++) { pointV[i]->print(); } vector<Point*>::iterator iP; for (iP = pointV.begin(); iP != pointV.end(); iP++) { (*iP)->print(); } iP = pointV.end() - 3; // 끝에서 3번째 출력 (*iP)->print(); pointV.erase(iP); // 지우기 for (iP = pointV.begin()+990; iP != pointV.end(); iP++) { (*iP)->print(); } cout << pointV.size() << endl; (*iP)->print(); pointV.insert(iP-3, new Point(30, 30)); for (iP = pointV.begin()+990; iP != pointV.end(); iP++) { (*iP)->print(); } }
C++
복사

map 예제 - 영한사전

#include <iostream> #include <map> #include <string> using namespace std; int main() { map<string, string> dic; dic.insert(make_pair("love", "사랑")); dic.insert(make_pair("apple", "사과")); dic["cherry"] = "체리"; cout << dic.size() << endl; string eng; while(true) { cout << "Word? "; cin >> eng; if (eng == "exit") break; if (dic.find(eng) == dic.end()) { // 만약 못찾았다면 cout << "Not Found this word" << endl; string newWord; cout << "Mean? "; cin >> newWord; dic[eng] = newWord; } else cout << dic[eng] << endl; } cout << "exit" << endl; }
C++
복사

Vector 와 Algorithm 이용한 정렬

#include <iostream> #include <map> #include <string> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; for (int i = 0; i < 5; i++) { int in; cin >> in; v.push_back(in); } // sort(v.begin()+1, v.end()); // 첫번째는 제외하고 sort(v.begin(), v.end()); for (int i = 0; i < v.size(); i++) { cout << v[i] << endl; } map<string, string> dic; dic.insert(make_pair("love", "사랑")); dic.insert(make_pair("apple", "사과")); dic["cherry"] = "체리"; cout << dic.size() << endl; string eng; while(true) { cout << "Word? "; cin >> eng; if (eng == "exit") break; if (dic.find(eng) == dic.end()) { // 만약 못찾았다면 cout << "Not Found this word" << endl; string newWord; cout << "Mean? "; cin >> newWord; dic[eng] = newWord; } else cout << dic[eng] << endl; } cout << "exit" << endl; }
C++
복사
10-14 Auto 들어가는지 안들어가는지 확실히 알기

lambda

#include <iostream> #include <map> #include <string> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; for (int i = 0; i < 5; i++) { int in; cin >> in; v.push_back(in); } // sort(v.begin()+1, v.end()); // 첫번째는 제외하고 sort(v.begin(), v.end()); for (int i = 0; i < v.size(); i++) { cout << v[i] << endl; } [](int x, int y)->void { cout << "sum : " << x + y << endl; } (10, 20); map<string, string> dic; dic.insert(make_pair("love", "사랑")); dic.insert(make_pair("apple", "사과")); dic["cherry"] = "체리"; cout << dic.size() << endl; string eng; while(true) { cout << "Word? "; cin >> eng; if (eng == "exit") break; if (dic.find(eng) == dic.end()) { // 만약 못찾았다면 cout << "Not Found this word" << endl; string newWord; cout << "Mean? "; cin >> newWord; dic[eng] = newWord; } else cout << dic[eng] << endl; } cout << "exit" << endl; }
C++
복사
#include <iostream> #include <map> #include <string> #include <vector> #include <algorithm> using namespace std; template<typename F> void callMyFn(F f) { f(1000, 2000); } int main() { vector<int> v; for (int i = 0; i < 5; i++) { int in; cin >> in; v.push_back(in); } // sort(v.begin()+1, v.end()); // 첫번째는 제외하고 sort(v.begin(), v.end()); for (int i = 0; i < v.size(); i++) { cout << v[i] << endl; } [](int x, int y)->void { cout << "sum : " << x + y << endl; } (10, 20); auto myFunction = [](int x, int y) { cout << "합 : " << x + y << endl; }; auto myFunction2 = [](int x, int y) { cout << "차 : " << x - y << endl; }; myFunction(300, 400); callMyFn(myFunction); callMyFn(myFunction2); map<string, string> dic; dic.insert(make_pair("love", "사랑")); dic.insert(make_pair("apple", "사과")); dic["cherry"] = "체리"; cout << dic.size() << endl; string eng; while(true) { cout << "Word? "; cin >> eng; if (eng == "exit") break; if (dic.find(eng) == dic.end()) { // 만약 못찾았다면 cout << "Not Found this word" << endl; string newWord; cout << "Mean? "; cin >> newWord; dic[eng] = newWord; } else cout << dic[eng] << endl; } cout << "exit" << endl; }
C++
복사

for_each()

#include <iostream> #include <map> #include <string> #include <vector> #include <algorithm> using namespace std; template<typename F> void callMyFn(F f) { f(1000, 2000); } void print(int n) { cout << n << " "; } int main() { vector<int> v; for (int i = 0; i < 5; i++) { int in; cin >> in; v.push_back(in); } // sort(v.begin()+1, v.end()); // 첫번째는 제외하고 sort(v.begin(), v.end()); for (int i = 0; i < v.size(); i++) { cout << v[i] << endl; } for (auto it = v.begin(); it != v.end(); it++) { print(*it); } for_each(v.begin(), v.end(), print); [](int x, int y)->void { cout << "sum : " << x + y << endl; } (10, 20); auto myFunction = [](int x, int y) { cout << "합 : " << x + y << endl; }; auto myFunction2 = [](int x, int y) { cout << "차 : " << x - y << endl; }; myFunction(300, 400); callMyFn(myFunction); callMyFn(myFunction2); map<string, string> dic; dic.insert(make_pair("love", "사랑")); dic.insert(make_pair("apple", "사과")); dic["cherry"] = "체리"; cout << dic.size() << endl; string eng; while(true) { cout << "Word? "; cin >> eng; if (eng == "exit") break; if (dic.find(eng) == dic.end()) { // 만약 못찾았다면 cout << "Not Found this word" << endl; string newWord; cout << "Mean? "; cin >> newWord; dic[eng] = newWord; } else cout << dic[eng] << endl; } cout << "exit" << endl; }
C++
복사

실습 문제 2번)

Book.h
#ifndef __BOOK_H #define __BOOK_H #include <string> using namespace std; class Book { string title; string author; int year; public: Book(string title, string author, int year) : title(title), author(author), year(year) {} // 멤버변수, 이름이 똑같아도 구분할 수 있다. void set(string title, string author, int year); string getAuthor() { return author; } int getYear() { return year; } void show(); }; #endif
C++
복사
Book.cpp
#include "Book.h" #include <iostream> void Book::set(string title, string author, int year) { this->title = title; this->author = author; this->year = year; } void Book::show() { cout << year << "년도, " << title << ", " << author << endl; }
C++
복사
BookManager.h
// book을 관리 #ifndef __BOOKMANAGER_H #define __BOOKMANAGER_H #include "Book.h" #include <vector> class BookManager { vector<Book> v; void searchByAuthor(); void searchByYear(); void bookIn(); public: void run(); }; #endif
C++
복사
BookManager.cpp
#include <iostream> #include "BookManager.h" using namespace std; void BookManager::bookIn() { string title, author; int year; cout << "입고할 책을 입력하세요. 년도에 -1을 입력하면 입고를 종료합니다." << endl; while (true) { cout << "년도>> "; cin >> year; if (year == -1) break; cout << "책이름>> "; //cin >> title;-> 띄어쓰기 안됨 cin.ignore(); getline(cin, title); cout << "저자>> "; // cin >> author; getline(cin, author); Book b(title, author, year); v.push_back(b); } cout << "총 입고된 책은 " << v.size() << "권 입니다." << endl; } void BookManager::searchByYear() { cout << "검색할 년도는?"; int year; cin >> year; for (int i = 0; i < v.size(); i++) { Book b = v[i]; if (b.getYear() == year) { b.show(); } } } void BookManager::searchByAuthor() { cout << "검색할 저자는?"; string author; cin.ignore(); // cin의 버퍼를 완전히 비워준다 getline(cin, author); for (int i = 0; i < v.size(); i++) { if (v[i].getAuthor() == author) { v[i].show(); } } } void BookManager::run() { /* bookIn(); searchByAuthor(); searchByYear(); */ bool end = false; while (!end) { cout << "메뉴 (1: 책 추가, 2: 저자 검색, 3: 년도 검색, 0: 종료" << endl; int menu; cin >> menu; if (menu == 0) break; switch(menu) { case 0: cout << "종료"; end = true; break; case 1: bookIn(); break; case 2: searchByAuthor(); break; case 3: searchByYear(); break; default: cout << "메뉴 선택 오류!!" << endl; } } }
C++
복사
main.cpp
#include <iostream> using namespace std; #include "BookManager.h" int main() { BookManager bm; bm.run(); }
C++
복사
11장
1.
0부터 127까지 아스키코드와 해당 문자를 다음과 같이 출력하는 프로그램을 작성하라. 화면에 출력가능하지 않는 아스키코드는 ‘.’ 으로 출력하라
#include <iostream> #include <cctype> // isprint() #include <iomanip> // setw() using namespace std; // void showDec(int d) { // 10진수 출력 // cout << setw(10) << dec << d; // } // void showHexa(int h) { // 16진수 출력 // cout << setw(10) << hex << h; // } // void showChar(int c) { // int i = 0; // if ( (i = isprint(c)) != 0) // 출력 가능한 문자인지 확인 // cout << setw(10) << (char)c; // else // 출력 가능한 문자이면 "." 출력 // cout << setw(10) << "."; // } // void print() { // for (int i = 0; i < 4; i++) { // cout << setw(10) << "dec"; // cout << setw(10) << "hexa"; // cout << setw(10) << "char"; // } // cout << endl; // for (int i = 0; i < 4; i++) { // cout << setw(10) << "___"; // cout << setw(10) << "____"; // cout << setw(10) << "____"; // } // cout << endl; // f // } int main() { for (int i = 0; i < 4; i++) cout << setw(4) << "dec" << setw(5) << "hex" << setw(5) << "char"; cout << endl; for (int i = 0; i < 4; i++) cout << setw(4) << "___" << setw(5) << "____" << setw(5) << "____"; cout << endl; for (int i = 0; i < 128; i++) { cout << setw(4) << dec << i << setw(5) << hex << i << setw(5) << (isprint(i) ? (char)i : '.'); if(i%4 == 3) cout << endl; } }
C++
복사
2.
Phone 클래스의 객체를 입출력하는 아래 코드와 실행결과를 참조하여 <<. >> 연산자를 작성하고, Phone 클래스를 수정하는 등 프로그램을 완성하시오.
#include <iostream> using namespace std; // class Phone { // string name; // string telnum; // string address; // public: // Phone(string name="", string telnum="", string address="") { // this->name = name; // this->telnum = telnum; // this->address = address; // } // friend ostream& operator << (ostream& os, Phone p); // friend istream& operator >> (istream& is, Phone& p); // }; // ostream& operator << (ostream& os, Phone p) { // os << "(" << p.name << "," << p.telnum << "," << p.address << ")"; // return os; // } // istream& operator >> (istream& ins, Phone& p) { // cout << "이름:"; // ins >> p.name; // cout << "전화번호:"; // ins >> p.telnum; // cout << "주소:"; // ins >> p.address; // return ins; // } // int main() { // Phone girl, boy; // cin >> girl >> boy; // cout << girl << endl << boy << endl; // } class Phone { string name; string telnum; string address; public: Phone(string name="", string telnum="", string address="") { this->name = name; this->telnum = telnum; this->address = address; } friend ostream& operator << (ostream& outs, Phone phone); friend istream& operator >> (istream& ins, Phone& phone); }; ostream& operator << (ostream& outs, Phone phone) { outs << "(" << phone.name << "," << phone.telnum << "," << phone.address << ")"; return outs; } istream& operator >> (istream& ins, Phone& phone) { cout << "이름:"; getline(ins, phone.name); cout << "전화번호:"; getline(ins, phone.telnum); cout << "주소:"; getline(ins, phone.address); return ins; } int main() { Phone girl, boy; cin >> girl >> boy; cout << girl << endl << boy << endl; }
C++
복사

11장. 수업 코드

#include <iostream> using namespace std; int main() { cout << "입력: "; int n; while( (n = cin.get()) != EOF) { cout.put(n); // 무한히 출력 // if (n == '\n') // break; // 한번만 출력 } }
C++
복사
#include <iostream> using namespace std; int main() { cout << "입력: "; char str[100]; cin.getline(str, 100); // 엔터키까지 버퍼에서 가져와서 count int count = cin.gcount(); cout << count << endl; }
C++
복사

ios format 사용자 지정

cout.unsetf(ios::dec); cout.setf(ios::hex|ios::uppercase|ios::showbase); cout.unsetf(ios::uppercase); cout << 1 << endl; cout << 11 << endl; cout << 22 << endl; cout.unsetf(ios::hex); cout.setf(ios::dec);
C++
복사
cout.width(10); cout.fill('+'); cout << "ash" << endl << "island" << endl; cout.width(20); cout << "YU CSE" << endl; cout << 11./3. << endl; cout.precision(5); cout << 11./3. << endl; cout << 11./3. << endl;
C++
복사
cout << hex << showbase << 123 << 1234 << 12345 << endl; cout << dec << noshowbase << setw(10) << setfill('^') << 1004 << ', ' << 1005 << endl;
C++
복사
Point.h
#ifndef __POINT_H #define __POINT_H #include <iostream> using namespace std; class Point { int x, y; public: Point(int x = 0, int y = 0) { this->x; this->y; } friend ostream& operator << (ostream& out, Point a); friend istream& operator >> (istream& in, Point& a); }; ostream& operator << (ostream& out, Point a) { out << " (" << a.x << ", " << a.y << ")"; return out; } istream& operator >> (istream& in, Point& a) { cout << "x 좌표: "; in >> a.x; cout << "y 좌표: "; in >> a.y; return in; } #endif
C++
복사
main.cpp
#include <iostream> #include "Point.h" using namespace std; int main() { Point p(100, 200); cout << "점 :" << p << endl; Point p2; cin >> p2; cout << p2 << endl; }
C++
복사

12장. 실습문제

1.
영문 텍스트 파일을 읽고, 영문 글자를 모두 대문자로 변환하여 저장하라. /etc/passwd 파일을 읽고, /tmp/new_passwd 로 저장함
#include <iostream> #include <fstream> #include <string> #include <cctype> // toupper() using namespace std; // int main() { // string line; // ifstream file("/etc/passwd"); // } int main(int argc, char* argv[]) { string inputFilename, outputFilename; if (argc == 3) { //argument가 3개인지 확인 inputFilename = argv[1]; outputFilename = argv[2]; } else { cout << "Usage: " << argv[0] << " 원본 파일 새로운 파일" << endl; return 0; } ifstream fin(inputFilename); if (!fin) { cout << inputFilename << "읽기 실패" << endl; return 0; } ofstream fout(outputFilename); if (!fout) { cout << outputFilename << "읽기 실패" << endl; return 0; } cout << "읽기 시작" << endl; int ch; while((ch = fin.get()) != EOF) { ch = toupper(ch); // 소문자 -> 대문자 fout.put(ch); } cout << "저장 끝!" << endl; fout.close(); fin.close(); }
C++
복사

12장. 수업 코드

파일 입출력
#include <iostream> #include <fstream> using namespace std; int main() { char name[10], dept[20]; int sid; cout << "name: "; cin >> name; cout << "sid: "; cin >> sid; cout << "dept: "; cin >> dept; ofstream fout("student.txt"); if(!fout) { // 열기 실패한 경우 cout << "student.txt 파일을 열 수 없습니다." << endl; return 0; } fout << name << endl; fout << sid << endl; fout << dept << endl; cout << "파일 저장 완료" << endl; fout.close(); }
C++
복사
cat student.txt
#include <iostream> #include <fstream> using namespace std; int main() { ifstream fin; fin.open("student.txt"); if (!fin) { cout << "Not open" << endl; return 0; } char name[10], dept[20]; int sid; fin >> name; fin >> sid; fin >> dept; fin.close(); cout << name << sid << dept << endl; }
C++
복사
#include <iostream> #include <fstream> using namespace std; int main() { ifstream fin; string fname = "student.txt"; fin.open(fname); if (!fin) { cout << "Not open" << endl; return 0; } char name[10], dept[20]; int sid; fin >> name; fin >> sid; fin >> dept; fin.close(); cout << name << sid << dept << endl; string fName = "/etc/passwd"; ifstream fin2(fName); if (!fin2) { cout << "File Open Fail:" << fName << endl; return 0; } int count = 0; int c; while((c = fin2.get()) != EOF) { cout << (char)c; count++; } cout << "Read Byte Count:" << count << endl; fin2.close(); fstream fout2(fname, ios::out | ios::trunc); if (!fout2) return 0; fstream fin3(fName, ios::in); while((c = fin3.get()) != EOF) { fout2.put(c); } fin3.close(); fout2.close(); ifstream fin4; fin4.open(fName); if (!fin3) return 0; string line; while (getline(fin4, line)) cout << line << endl; fin3.close(); }
C++
복사
단어 찾기
#include <iostream> #include <fstream> #include <vector> using namespace std; void fileRead(vector<string>& v, ifstream &fin) { string line; while(getline(fin, line)) v.push_back(line); cout << v.size() << "단어를 읽었습니다." << endl; } void search(vector<string>& v, string word) { int count = 0; for (auto it = v.begin(); it != v.end(); it++) { int index = (*it).find(word); if(index != -1) { cout << *it << endl; count++; } } cout << "총 " << count << "개의 단어를 찾았습니다." << endl; } int main() { string wordFileName = "words.txt"; ifstream fin(wordFileName); if(!fin) { cout << "파일 읽기 오류" << endl; return 0; } vector<string> v; fileRead(v, fin); fin.close(); while(true) { cout << "검색할 단어:"; string word; getline(cin, word); if(word == "exit") break; search(v, word); } cout << "종료!" << endl; }
C++
복사

13장. 실습문제

1.
다음은 정수를 입력받아 구구단을 출력하는 프로그램이다. 그런데 이 프로그램은 1~9가 아닌 정수가 입력되는 것을 거러내지 못하고, 특히 문자가 입력되면 무한루프에 빠진다. try-throw-catch를 이용해 이 프로그램을 수정하라.
#include <iostream> using namespace std; // int main() { // int n; // while(true) { // cout << "양수 입력 >> "; // cin >> n; // try { // throw -1; // throw "s"; // } catch(float f) { // cout << "잘못된 입력입니다. 1~9 사이의 정수만 입력하세요"; // } catch(char c) { // cout << "입력 오류가 발생하여 더 이상 입력되지 않습니다. 프로그램을 종료합니다" // } // for (int i = 1; i < 9; i++) // cout << n << "x" << i << "=" << n*i << ' '; // cout << endl; // } // } int main() { int n; while(true) { cout << "양수 입력 >> "; try { cin >> n; if(cin.fail()) throw "정수값이 아닌 값을 입력!"; if(n < 1 || n > 9) throw n; for (int i = 1; i < 9; i++) cout << n << "x" << i << "=" << n*i << ' '; cout << endl; } catch(const char* s) { cout << "입력 오류 : " << s << endl; return 0; } catch(int n) { cout << "잘못된 입력입니다. 1~9 사이의 정수만 입력하세요" << endl; } } }
C++
복사
2.
다음은 C코드로서 get.c파일에 저장되어 있다. get() 함수를 호출해 두 정수를 키보드로부터 입력받아 아래 실행화면과 같이 곱을 출력하는 프로그램을 main.cpp 파일로 저장하고, get.c와 main.cpp 파일로 구성되는 프로그램을 작성하라.
// get.c #include <stdio.h> int get() { int c; printf("숫자를 입력하세요>>"); scanf("%d", &c); return c; }
C
복사
// main.cpp #include <iostream> using namespace std; extern "C" int get(); int main() { int n = get(); int m = get(); cout << n * m << endl; }
C++
복사

과제 수업 코드

void BaseArray::increaseCapacity(int size) { int *tmp = new int[capacity+size]; memcpy(tmp, mem, capacity * sizeof(int)); capacity += size; delete [] mem; mem = tmp; } void MyQueue::increaseCapacity(int size) { int capacity = getCapacity(); int *tmp = new int[capacity + size]; int *mem = getMem(); memcpy(tmp, mem+head, (capacity-head) * sizeof(int)); memcpy((tmp+(capacity-head)), mem, (head) * sizeof(int)); head = capacity; tail = -1; setCapacity(capacity + size); delete [] mem; setMem(tmp); }
C++
복사
스택, 큐 동적으로 늘리기

8장. 상속 (Inheritance)

유전적 상속

객체 지향 상속

C++ 에서의 상속 (Inheritance)

C++에서 상속?
클래스 사이에서 상속관계를 정의한다
객체 사이에는 상속 관계 없다
기본 클래스의 속성과 기능을 파생 클래스에 물려주는 것
기본 클래스 - 상속해주는 클래스, 부모 클래스
파생 클래스(derived class) - 상속받는 클래스, 자식 클래스
기본 클래스의 속성과 기능을 물려받고 자신 만의 속성과 기능을 추가하여 작성한다.
기본 클래스에서 파생 클래스로 갈수록 클래스의 개념이 구체화
다중 상속을 통한 클래스의 재활용성 높임

상속의 표현

간단한 상속 예)
class Phone { void call(); void receive(); }; // Phone을 상속받는다 class MobilePhone : public Phone { void connectWireless(); void recharge(); }; // MobilePhone을 상속받는다 class MusicPhone : public MobilePhone { void downloadMusic(); void play(); };
C++
복사

상속의 목적 및 장점

1.
간결한 클래스 작성
기본 클래스의 기능을 물려받아, 파생 클래스를 간결하게 작성가능
2.
클래스 간의 계층적 분류 및 간리의 용이함
상속은 클래스들의 구조적 관계 파악 용이
3.
클래스 재사용과 확장을 통한 소프트웨어 생산성 향상
빠른 소프트웨어 생산 필요
기존에 작성한 클래스의 재사용 - 상속
상속받아 새로운 기능을 확장
앞으로 있을 상속에 대비한 클래스의 객체 지향적 설계 필요

상속 선언

class Student : public Person { // 파생클래스명, 상속접근지정, 기본클래스명 // Person을 상속받는 Student 선언 ....... }; class StudentWorker : public Student { // Student를 상속받는 StudentWorker 선언 ....... };
C++
복사
Student 클래스는 Person 클래스의 멤버를 물려받는다
StudentWorker 클래스는 Student의 멤버를 물려받는다
Student가 물려받은 Person의 멤버도 함께 물려받는다
예제 8-1. Point 클래스를 상속받는 ColorPoint 클래스 만들기
#include <iostream> #include <string> using namespace std; // 2차원 평면에서 한 점을 표현하는 클래스 Point 선언 class Point { int x, y; public: void set(int x, int y) { this->x = x; this->y = y; } void showPoint() { cout << "(" << x << ", " << y << ")" << endl; } }; class ColorPoint : public Point { // 2차원 평면에서 컬러점을 표현하는 클래스 ColorPoint. // Point를 상속받음 string color; // 점의 색 표현 public: void setColor(string color) { this->color = color; } void showColorPoint(); }; void ColorPoint::showColorPoint() { cout << color << " : "; showPoint(); // Point의 showPoint() 호출 } int main() { Point p; //기본 클래스의 객체 생성 ColorPoint cp; // 파생 클래스의 객체 생성 cp.set(3, 4); // 기본 클래스의 멤버 호출 cp.setColor("Red"); // 파생 클래스의 멤버 호출 cp.showColorPoint(); // 파생 클래스의 멤버 호출 }
C++
복사

파생 클래스의 객체 구성

파생 클래스에서 기본 클래스 멤버 접근

외부에서 파생 클래스 객체에 대한 접근

상속과 객체 포인터 - 업 개스팅

업 개스팅 (up-casting)

파생 클래스 포인터가 기본 클래스 포인터에 치환되는 것
ex) 사람을 동물로 봄
int main() { ColorPoint cp; ColorPoint *pDer = &cp; Point* pBase = pDer; // 업캐스팅 pDer->set(3, 4); pBase->showPoint(); pDer->setColor("Red"); pDer->showColorPoint(); pBase->showColorPoint(); // 컴파일 오류 }
C++
복사
업캐스팅 이해하기! (업캐스팅 개념)
→ 생물을 가리키는 손가락으로 어류, 포유류, 사람, 식물 등 생물의 속성을 상속받은 객체들을 가리키는 것은 자연스럽다
→ 생물을 가리키는 손가락으로 컵 같은 관려없는 것을 가리키면 오류

다운 캐스팅 (down-casting)

기본 클래스의 포인터가 파생 클래스의 포인터에 치환되는 것
int main() { ColorPoint cp; ColorPoint *pDer; Point* pBase = &cp; // 업캐스팅 pBase->set(3, 4); pBase->showPoint(); pDer = (ColorPoint *)pBase; // 다운캐스팅 // 강제 타입 변환 반드시 필요하다 pDer->setColor("Red"); // 정상 컴파일 pDer->showColorPoint(); // 정상 컴파일 }
C++
복사

protected 접근 지정

접근 지정자

private 멤버
선언된 클래스 내에서만 접근 가능
파생 클래스에서도 기본 클래스의 private 멤버 직접 접근 불가
public 멤버
선언된 클래스나 외부 어떤 클래스, 모든 외부 함수에 접근 허용
파생 클래스에서 기본 클래스의 public 멤버 접근 가능
protected 멤버
선언된 클래스에서 접근 가능
파생 클래스에서만 접근 허용
파생 클래스가 아닌 다른 클래스나 외부 함수에서는 protected 멤버를 접근할 수 없다
멤버의 접근 지정에 따른 접근성시험문제 언급
예제 8-2, protected 멤버에 대한 접근 ( Error )
#include <iostream> #include <string> using namespace std; // 2차원 평면에서 한 점을 표현하는 클래스 Point 선언 class Point { int x, y; public: void set(int x, int y); void showPoint(); }; void Point::set(int x, int y) { this->x = x; this->y = y; } void Point::showPoint() { cout << "(" << x << ", " << ")" << endl; } class ColorPoint : public Point { // 2차원 평면에서 컬러점을 표현하는 클래스 ColorPoint. // Point를 상속받음 string color; // 점의 색 표현 public: void setColor(string color) { this->color = color; } void showColorPoint(); bool equals(ColorPoint p); }; void ColorPoint::showColorPoint() { cout << color << " : "; showPoint(); // Point의 showPoint() 호출 } bool ColorPoint::equals(ColorPoint p) { if (x == p.x && y == p.y && color == p.color) // Error 포인트 return true; else return false; } int main() { Point p; // 기본 클래스의 객체 생성 p.set(2, 3); p.x = 5; // Error p.y = 5; // Error p.showPoint(); ColorPoint cp; // 파생 클래스의 객체 생성 cp.x = 10; // Error cp.y = 10; // Error cp.set(3, 4); cp.setColor("Red"); cp.showColorPoint(); ColorPoint cp2; cp2.set(3, 4); cp2.setColor("Red"); cout << ((cp.equals(cp2))?"true":"false"); }
C++
복사

상속 관계의 생성자와 소멸자 실행

Q1) 파생 클래스의 객체가 생성될 때, 파생 클래스의 생성자와 기본 클래스의 생성자가 모두 실행되는가? 아니면 파생 클래스의 생성자만 실행되는가?
A1) 파생 클래스의 생성자와 기본 클래스의 생성자 모두 실행된다.
Q2) 파생 클래스의 생성자와 기본 클래스의 생성자 중 어떤 생성자가 먼저 실행되는가?
A2) 기본 클래스의 생성자가 먼저 실행된 후, 파생 클래스의 생성자가 실행된다.
→ 반대로 소멸자는 파생 클래스의 소멸자부터 실행된 후, 기본 클래스의 소멸자가 실행된다.
생성자 호출 관계 및 실행 순서

소멸자의 실행 순서

파생 클래스의 객체가 소멸될 때
파생 클래스의 소멸자가 먼저 실행되고
기본 클래스의 소멸자가 나중에 실행된다

컴파일러에 의해 묵시적으로, 기본 클래스의 생성자를 선택하는 경우

→ 파생 클래스의 생성자에서 기본 클래스의 기본 생성자 호출
→ 따로 선택하지 않으면, 부모 클래스의 기본 생성자를 호출하는 것이 디폴트 동작.

기본 클래스에 기본 생성자가 없는 경우

→ Error : 사용할 수 있는 적절한 기본 생성자가 없습니다.

매개 변수를 가진 파생 클래스의 생성자는 묵시적으로 기본 클래스의 기본 생성자 선택

→ 파생 클래스의 매개 변수를 가진 생성자가 기본 클래스의 기본 생성자 호출

파생 클래스의 생성자에서 명시적으로 기본 클래스의 생성자 선택

컴파일러의 기본 생성자 호출 코드 삽입

class B { B() : A() { // 컴파일러가 묵시적으로 삽입한 코드 cout << "생성자 B" << endl; } B(int x) : A() { // 컴파일러가 묵시적으로 삽입한 코드 -> 자동으로 부모의 생성자를 호출하도록 세팅 cout << "매개변수생성자 B" << x << endl; } };
C++
복사
예제 8-3. TV, WideTV, SmartTV 생성자 매개 변수 전달
#include <iostream> #include <string> using namespace std; class TV { int size; public: TV() { size = 20; } TV(int size) { this->size = size; } int getSize() { return size; } }; class WideTV : public TV { bool videoIn; public: WideTV(int size, bool videoIn) : TV(size) { this->videoIn = videoIn; } bool getVideoIn() { return videoIn; } }; class SmartTV : public WideTV { string ipAddr; public: SmartTV(string ipAddr, int size) : WideTV(size, true) { this->ipAddr = ipAddr; } string getIpAddr() { return ipAddr; } }; int main() { SmartTV htv("192.0.0.1", 32); cout << "size= " << htv.getSize() << endl; cout << "videoIn= " << boolalpha << htv.getVideoIn() << endl; cout << "IP=" << htv.getIpAddr() << endl; }
C++
복사

상속 지정

기본 클래스의 멤버의 접근 속성을 어떻게 계승할지 지정한다
public
기본 클래스의 protected, public 멤버 속성을 그래도 계승
private
기본 클래스의 protected, public 멤버를 private으로 계승
protected
기본 클래스의 protected, public 멤버를 protected로 계승

상속 시 접근 지정에 따른 멤버의 접근 지정 속성 변화 (중요)

예제 8-4, private 상속 사례
1, 2, 3 - 모두 private으로 보여서 에러
4 -
5 - protected니까 바깥쪽에서 접근 x
상속 사례 꼭 이해하고, 두세번 보고 넘어갈 것
예제 8-7) 다중상속받는 Calculator 클래스
#include <iostream> using namespace std; class Adder { protected: int add (int a, int b) { return a + b; } }; class Subtractor { protected: int minus (int a, int b) { return a - b; } }; // 다중 상속 class Calculator : public Adder, public Subtractor { public: int calc (char op, int a, int b); }; int Calculator::calc (char op, int a, int b) { int res = 0; switch(op) { case '+': res = add(a, b); break; case '-': res = minus(a, b); break; } return res; } int main() { Calculator handCalculator; cout << "2 + 4 = " << handCalculator.calc('+', 2, 4) << endl; cout << "100 - 8 = " << handCalculator.calc('-', 100, 8) << endl; }
C++
복사

가상 상속

다중 상속으로 인한 기본 클래스 멤버의 중복 상속 해결
가상 상속
파생 클래스의 선언문에서 기본 클래스 앞에 virtual으로 선언
파생 클래스의 객체가 생성될 때, 기본 클래스의 멤버는 오직 한번만 생성
⇒ 기본 클래스의 멤버가 중복하여 생성되는 것을 방지
class In : virtual public BaseIO { // In 클래스는 BaseIO 클래스를 가상 상속 }; class Out : virtual public BaseIO { // Out 클래스는 BaseIO 클래스를 가상 상속 };
C++
복사

9장. 가상 함수와 추상 클래스

파생클래스에서 함수를 재정의하는 사례
함수 중복
상속을 사용하는데 있어서, Base Class 의 Destructor는 virtual public 이거나 protected 여야 한다.
→ protected로 선언이 되면, Base Class를 object로 만들지 않겠다는 뜻
→ 거의 모든 경우에는 virtual public으로 선언이 된다

가상 함수와 오버라이딩

가상 함수 (Virtual Function)

virtual 키워드의 의미
동적 바인딩 지시어
컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
함수 오버라이딩 (Function Overriding) → 함수 중복(파생클래스 중복함수, 기본클래스 함수 겹침)
파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
기본 클래스의 가상 함수의 존재감 상실시킴
파생 클래스에서 Overriding한 함수가 호출되도록 동적 바인딩
함수 재정의
다형성의 한 종류

함수 재정의와 오버라이딩 사례 비교

함수 재정의 (redefine)
class Base { public: void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { public: void f() { cout << "Derived::f() called" << endl; } };
C++
복사
→ 동등한 호출 기회를 가진 함수 f()가 두 개 존재
오버라이딩 (overriding)
class Base { public: virtual void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { public: virtual void f() { cout << "Derived::f() called" << endl; } };
C++
복사
→ 두 개의 함수 f()가 존재하지만, Base의 f()는 존재감을 잃고, 항상 Derived의 f()가 호출된다.
가상 함수를 재정의 하는 것 → 오버라이딩
가상 함수를 재정의 하지 않는 것 → 함수 재정의
Java의 경우, 멤버 함수가 가상이냐, 아니냐 구분되지 않으며, 함수 재정의는 곧 오버라이딩이며, 무조건 동적 바인딩이다 일어난다.
예제 9-2) 오버라이딩과 가상함수 호출
#include <iostream> using namespace std; class Base { public: virtual void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { public: virtual void f() { cout << "Derived::f() called" << endl; } }; int main() { Derived d, *pDer; pDer = &d; pDer->f(); // Derived::f() 호출 Base *pBase; pBase = pDer; // 업 캐스팅 pBase->f(); // 동적 바인딩 발생!! Drived::f() 실행 }
C++
복사
오버라이딩의 목적
파생 클래스에서 구현할 함수 인터페이스 제공 (파생 클래스의 다형성)
다형성의 실현 예)
draw() 가상함수를 가진 기본 클래스 Shape
오버라이딩을 통해 Circle, Rect, Line 클래스에서 자신만의 draw() 구현
class Shape { protected: virtual void draw() { } // 가상함수 선언, // 파생클래스에서 재정의할 함수에 대한 인터페이스 역할 }; class Circle : public Shape { protected: virtual void draw() { // Circle을 그린다 // 오버라이딩, 다형성 실현 } }; class Rect : public Shape { protected: virtual void draw() { // Rect를 그린다 } }; class Line : public Shape { protected: virtual void draw() { // Line을 그린다 } }; void paint(Shape* p) { p->draw(); // p가 가리키는 객체에 오버라이딩된 draw() 호출 } paint(new Circle()); // Circle을 그린다 paint(new Rect()); // Rect을 그린다 paint(new Line()); // Line을 그린다
C++
복사

동적 바인딩

동적 바인딩

파생클래스에 대해
기본 클래스에 대한 포인터로, 가상함수를 호출하는 경우
객체 내에 오버라이딩한 파생클래스의 함수를 찾아 실행
실행 중에 이루어짐
실행시간 바인딩, 런타임 바인딩, 늦은 바인딩 으로 불림

C++ 오버라이딩 특징

오버라이딩 성공 조건
가상함수 이름
매개변수 타입과 개수
리턴 타입이 모두 일치
오버라이딩 시 virtual 지시어 생략 가능
가상함수의 virtual 지시어는 상속됨,
파생클래스에서 virtual 생략 가능
가상함수의 접근 지정
private, protected, public 중 자유롭게 지정
예제 9-3) 상속이 반복되는 경우, 가상함수 호출
#include <iostream> using namespace std; class Base { public: virtual void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { public: void f() { cout << "Derived::f() called" << endl; } }; class GrandDerived : public Derived { public: void f() { cout << "GrandDerived::f() called" << endl; } }; int main() { GrandDerived g; // 파생클래스 GreandDerived 객체 g Base *bp; // 기본클래스 Base의 포인터 bp Derived *dp; // 파생클래스 Derived의 포인터 dp GrandDerived *gp; // 파생클래스 Derived의 파생클래스 GrandDerived의 포인터 gp bp = dp = gp = &g; bp->f(); // bp->f() => gp->f() dp->f(); // dp->f() => gp->f() gp->f(); // gp->f() // 동적 바인딩에 의해 모두 GrandDerived의 함수 f() 호출 // 모두 GrandDerived 파생클래스의 f() 함수가 출력된다. }
C++
복사

오버라이딩과 범위 지정 연산자(::)

범위 지정 연산자 (::)

정적 바인딩 지시
기본클래스::가상함수()
기본클래스의 가상함수를 정적 바인딩으로 호출
예제 9-4) 범위 지정 연산자 (::)를 이용한 기본 클래스의 가상함수 호출
#include <iostream> using namespace std; class Shape { public: virtual void draw() { cout << "-- Shape --"; } }; class Circle : public Shape { public: virtual void draw() { Shape::draw(); // 기본클래스 Shape의 가상함수 draw()를 정적 바인딩으로 호출 cout << "Circle" << endl; } }; int main() { Circle circle; // 파생클래스 Circle의 객체 circle Shape *pShape = &circle; // pShape 이라는 기본클래스 Shape의 포인터 변수를 선언하고, 객체 circle의 주소값으로 초기화 pShape->draw(); // 동적 바인딩을 포함하는 호출 // 출력 : -- Shape --Circle pShape->Shape::draw(); // 정적 바인딩 호출 // 출력 : -- Shape -- }
C++
복사

가상 소멸자

가상 소멸자

소멸자를 virtual 키워드로 선언
소멸자 호출 시, 동적 바인딩 발생
파생클래스의 소멸자가 자신의 코드 실행 후,
기본클래스의 소멸자를 호출하도록 컴파일 됨
class Base { public: virtual ~Base(); }; class Derived : public Base { public: virtual ~Derived(); } int main() { Base *p = new Derived(); delete p; } // 1) ~Base() 소멸자 호출 // 2) ~Derived() 실행 // 3) ~Base() 실행
C++
복사
예제 9-6) 소멸자를 가상함수로 선언
#include <iostream> using namespace std; class Base { public: virtual ~Base() { cout << "~Base()" << endl; } }; class Derived : public Base { public: virtual ~Derived() { cout << "~Derived()" << endl; } }; int main() { Derived *dp = new Derived(); Base *bp = new Derived(); delete dp; // Derived의 포인터로 소멸 delete bp; // Base의 포인터로 소멸 // 출력 // ~Derived() // ~Base() // ~Derived() // ~Base() }
C++
복사

오버로딩

정의
매개변수 타입이나 개수가 다르지만, 이름이 같은 함수들이 중복 작성되는 것
존재
클래스의 멤버들 사이
외부 함수들 사이
기본클래스와 파생클래스 사이에 존재 가능
목적
이름이 같은 여려 개의 함수를 중복 작성하여, 사용의 편의성 향상
바인딩
정적 바인딩
컴파일 시에 중복된 함수들의 호출을 구분
객체 지향 특성
컴파일 시간 다형성

함수 재정의 (가상함수가 아닌 멤버에 대해서)

정의
기본클래스의 멤버함수를, 파생클래스에서 이름, 매개변수 타입과 개수, 리턴 타입까지 완벽히 같은 원형으로 재작성하는 것
존재
상속 관계
목적
기본클래스의 멤버함수와 별도로, 파생클래스에서 필요하여 재작성
바인딩
정적 바인딩
컴파일 시에 함수의 호출을 구분
객체 지향 특성
컴파일 시간 다형성

오버라이딩

정의
기본클래스의 가상함수를, 파생클래스에서 이름, 매개변수 타입과 개수, 리턴 타입까지 완벽히 같은 원형으로 재작성하는 것
존재
상속 관계
목적
기본클래스에 구현된 가상함수를 무시하고, 파생클래스에서 새로운 기능으로 재작성하고자 함
바인딩
동적 바인딩
실행 시간에 오버라이딩된 함수를 찾아 실행
객체 지향 특성
실행 시간 다형성

가상함수와 오버라이딩 활용 사례

가상함수를 가진 기본클래스의 목적

class Shape { Shape* next; protected: virtual void draw(); public: Shape() { next = NULL; } virtual ~Shape() {} void paint(); Shape* add(Shape* p); Shape* getNext() { return next; } };
C++
복사
Shape 클래스는 상속을 위한 기본클래스로의 역할
가상함수 draw()로 파생클래스의 인터페이스를 보여준다
Shape 객체를 생성할 목적 아님
파생클래스에서 draw() 재정의
자신의 도형을 그리도록 유도

가상함수 오버라이딩

파생클래스마다 다르게 구현하는 다형성
void Circle::draw() { cout << "Circle" << endl; } void Rect::draw() { cout << "Rect" << endl; } void Line::draw() { cout << "Line" << endl; }
C++
복사
파생클래스에서 가상함수 draw()의 재정의
어떤 경우에도 자신이 만든 draw()가 호출됨을 보장 받음
동적 바인딩에 의해
#include <iostream> using namespace std; class Shape { Shape* next; protected: virtual void draw(); public: Shape() { next = NULL; } virtual ~Shape() {} void paint(); Shape* add(Shape* p); Shape* getNext() { return next; } }; class Circle : public Shape { protected: virtual void draw(); }; class Rect : public Shape { protected: virtual void draw(); }; class Line : public Shape { protected: virtual void draw(); }; void Circle::draw() { cout << "Circle" << endl; } void Rect::draw() { cout << "Rect" << endl; } void Line::draw() { cout << "Line" << endl; } void Shape::paint() { draw(); } void Shape::draw() { cout << "-- Shape --" << endl; } Shape* Shape::add(Shape *p) { this->next = p; return p; } int main() { Shape *pStart = NULL; Shape *pLast; pStart = new Circle(); // 처음에 원 도형을 생성한다 pLast = pStart; pLast = pLast->add(new Rect()); pLast = pLast->add(new Circle()); pLast = pLast->add(new Line()); pLast = pLast->add(new Rect()); // 현재 연결된 모든 도형을 화면에 그린다 Shape* p = pStart; while(p != NULL) { p->paint(); p = p->getNext(); } // 현재 연결된 모든 도형을 삭제한다 p = pStart; while(p != NULL) { Shape* q = p->getNext(); // 다음 도형 주소 기억 delete p; // 기본클래스의 가상 소멸자 호출 p = q; // 다음 도형 주소를 p에 저장 } }
C++
복사

기본 클래스의 포인터 활용

기본클래스의 포인터로 파생클래스 접근

pStart, pLast, p의 타입이 Shape*
연결리스트를 따라 Shape을 상속받은 파생 객체들 접근
p→paint()의 간단한 호출로 파생 객체에 오버라이딩된 draw() 함수 호출

순수 가상 함수

기본 클래스의 가상 함수 목적

파생 클래스에서 재정의할 함수를 알려주는 역할
실행할 코드를 작성할 목적이 아님
기본 클래스의 가상 함수를 굳이 구현할 필요가 있을까?

순수 가상 함수 (Pure Virtual Function)

함수의 코드가 없고, 선언만 있는 가상 멤버 함수
class Shape { public: virtual void draw() = 0; };
C++
복사

추상 클래스

최소한 하나의 순수 가상 함수를 가진 클래스
class Shape { // Shape는 추상 클래스 Shape *next; public: void paint(); draw(); } virtual void draw() = 0; // 순수 가상 함수 }; void Shape::paint() { draw(); // 순수 가상 함수라도 호출은 할 수 있다 }
C++
복사

추상 클래스의 특징

온전한 클래스가 아니므로, 객체 생성 불가능
Shape shape; // 컴파일 오류 Shape *p = new Shape(); // 컴파일 오류 // 추상 클래스를 인스턴스화할 수 없다
C++
복사
추상 클래스의 포인터는 선언 가능
Shape *p;
C++
복사

추상 클래스의 목적

추상 클래스의 인스턴스를 생성할 목적 아님
상속에서 기본 클래스의 역할을 하기 위함
순수 가상 함수를 통해
파생 클래스에서 구현할 함수의 형태(원형)를 보여주는 인터페이스 역할
추상 클래스의 모든 멤버 함수를
순수 가상 함수로 선언할 필요 없다

추상 클래스의 상속과 구현

추상 클래스의 상속
추상 클래스를 단순 상속하면, 자동 추상 클래스
추상 클래스의 구현
추상 클래스를 상속 받아, 순수 가상 함수를 오버라이딩
class Shape { // 추상 클래스 public: virtual void draw() = 0; }; class Circle : public Shape { public: virtual void draw() { // 순수 가상 함수 오버라이딩 cout << "Circle"; } string toString() { return "Circle 객체";} }; Shape shape; // 객체 생성 오류 Circle waffle; // 정상적인 객체 생성
C++
복사
#include <iostream> using namespace std; class Calculator { void input() { cout << "정수 2개를 입력하세요>> "; cin >> a >> b; } protected: int a, b; virtual int calc(int a, int b) = 0; public: void run() { input(); cout << "계산된 값은 " << calc(a, b) << endl; } }; class GoodCalc : public Calculator { public: int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } double average(int a [], int size) { double sum = 0; for (int i = 0; i < size; i++) sum += a[i]; return sum / size; } }; class Adder : public Calculator { protected: int calc(int a, int b) { return a + b; } }; class Subtractor : public Calculator { protected: int calc(int a, int b) { return a - b; } }; // int main() { // int a[] = {1, 2, 3, 4, 5}; // Calculator *p = new GoodCalc(); // cout << p->add(2, 3) << endl; // cout << p->subtract(2, 3) << endl; // cout << p->average(a, 5) << endl; // delete p; // } int main() { Adder adder; Subtractor subtractor; adder.run(); subtractor.run(); }
C++
복사

10장. 템플릿과 표준 템플릿 라이브러리 (STL)

함수 중복의 약점 - 중복 함수의 코드 중복

#include <iostream> using namespace std; void myswap(int& a, int& b) { int tmp; tmp = a; a = b; b = tmp; } void myswap(double& a, double& b) { double tmp; tmp = a; a = b; b = tmp; } // 동일한 코드 중복 작성 // 두 함수는 매개변수만 다르고 나머지 코드는 동일함 int main() { int a = 4, b = 5; myswap(a, b); cout << a << '\t' << b << endl; double c = 0.3, d = 12.5; myswap(c, d); cout << c << '\t' << d << endl; }
C++
복사

일반화와 템플릿

제네릭 (generic) 또는 일반화

함수나 클래스를 일반화시키고, 매개변수 타입을 지정하여
틀에서 찍어내듯이
함수나 클래스 코드르 생산하는 기법

템플릿

함수나 클래스를 일반화하는 C++ 도구
template 키워드로 함수나 클래스 선언
변수나 매개변수의 타입만 다르고, 코드 부분이 동일한 함수를 일반화시킴
제네릭 타입 - 일반화를 위한 데이터 타입

템플릿 선언

template <class T> 또는 template <typename T> // 3개의 제네릭 타입을 가진 템플릿 선언 template <class T1, class T2, class T3>
C++
복사
template <class T> // 제네릭 타입 T 선언 void myswap(T& a, T& b) { T tmp; tmp = a; a = b; b = tmp; }
C++
복사

구체화 (specialization)

템플릿의 제네릭 타입에, 구체적인 타입을 지정
템플릿 함수로부터 구체화된 함수의 소스코드 생성
template <class T> // 제네릭 타입 T 선언 void myswap(T& a, T& b) { T tmp; tmp = a; a = b; b = tmp; }
C++
복사
이 코드를
void myswap(int& a, int& b) { int tmp; tmp = a; a = b; b = tmp; }
C++
복사
로 구체화

제네릭 myswap() 함수 만들기

#include <iostream> using namespace std; class Circle { int radius; public: Circle(int radius = 1) { this->radius = radius; } int getRadius() { return radius; } }; template <class T> void myswap(T& a, T& b) { T tmp; tmp = a; a = b; b = tmp; } int main() { int a = 4, b = 5; myswap(a, b); cout << "a = " << a << ", " << "b = " << b << endl; double c = 0.3, d = 12.5; myswap(c, d); cout << "c = " << c << ", " << "d = " << d << endl; Circle donut(5), pizza(20); myswap(donut, pizza); cout << "donut 반지름 = " << donut.getRadius() << ", "; cout << "pizza 반지름 = " << pizza.getRadius() << endl; }
C++
복사

구체화 오류

제네릭 타입에 구체적인 타입 지정 시 주의
template <class T> void myswap(T& a, T& b)
C++
복사
→ 두 매개변수 a, b의 제네릭 타입 동일
int s = 4; double t = 5; myswap(s, t);
C++
복사
→ 두 개의 매개변수의 타입이 서로 다름
→ 컴파일 오류, 템플릿으로부터 myswap(int &, double &) 함수를 구체화할 수 없다

템플릿 장점과 제네릭 프로그래밍

템플릿 장점

함수 코드의 재사용
높은 SW의 생산성과 유용성

템플릿 단점

포팅에 취약
컴파일러에 따라 지원하지 않을 수 있음
컴파일 오류 메시지 빈약, 디버깅에 많은 어려움

제네릭 프로그래밍 (generic programming)

일반화 프로그래밍
제네릭 함수나 제네릭 클래스를 활용하는 프로그래밍 기법
C++ 에서 STL (Standard Template Library) 제공, 활용
보편화 추세 - JAVS, C# 등 많은 언어에서 활용
예제 10-2) 큰 값을 리턴하는 bigger() 함수 만들기 연습
#include <iostream> using namespace std; template <class T> T bigger(T a, T b) { if (a > b) return a; else return b; } int main() { int a = 20, b = 50; char c = 'a', d = 'z'; cout << "bigger(20, 50)의 결과는 " << bigger(a, b) << endl; cout << "bigger('a', 'z')의 결과는 " << bigger(c, d) << endl; }
C++
복사
예제 10-3) 배열의 합을 구하여 리턴하는 제네릭 add() 함수 만들기 연습
#include <iostream> using namespace std; template <class T> T add(T data [], int n) { T sum = 0; for (int i = 0; i < n; i++) { sum += data[i]; } return sum; } int main() { int x[] = {1, 2, 3, 4, 5}; double d[] = {1.2, 2.3, 3.4, 4.5, 5.6, 6.7}; cout << "sum of x[] = " << add(x, 5) << endl; cout << "sum of d[] = " << add(d, 6) << endl; }
C++
복사
예제 10-4) 배열을 복사하는 제네릭 함수 mcopy() 함수 만들기 연습
#include <iostream> using namespace std; // 두 개의 제네릭 타입 T1, T2를 가지는 copy() 의 템플릿 template <class T1, class T2> void mcopy(T1 src [], T2 dest [], int n) { for (int i = 0; i < n; i++) dest[i] = (T2)src[i]; // T1 타입의 값을 T2 타입으로 변환한다 } int main() { int x[] = {1, 2, 3, 4, 5}; double d[5]; char c[5] = {'H', 'e', 'l', 'l', 'o'}, e[5]; mcopy(x, d, 5); // int x[]의 원소 5개를 double d[] 에 복사 mcopy(c, e, 5); // char c[]의 원소 5개를 char e[] 에 복사 for (int i = 0; i < 5; i++) cout << d[i] << ' '; // d[] 출력 cout << endl; for (int i = 0; i < 5; i++) cout << e[i] << ' '; // e[] 출력 cout << endl; }
C++
복사
배열을 출력하는 print() 템플릿 함수의 문제점
#include <iostream> using namespace std; template <class T> void print(T array [], int n) { for (int i = 0; i < n; i++) cout << array[i] << '\t'; // T가 char로 구체화되는 경우, 정수 1, 2, 3, 4, 5에 대한 그래픽 문자 출력 // char로 구체화되면, 숫자대신 문자가 출력되는 문제 발생 cout << endl; } int main() { int x[] = {1, 2, 3, 4, 5}; double d[5] = {1.1, 2.2, 3.3, 4.4, 5.5}; print(x, 5); // print() 템플릿의 T가 int 타입으로 구체화 print(d, 5); char c[5] = {1, 2, 3, 4, 5}; print(c, 5); // print() 템플릿의 T가 char 타입으로 구체화 }
C++
복사
예제 10-5) 템플릿 함수보다 중복 함수가 우선
#include <iostream> using namespace std; template <class T> void print(T array [], int n) { for (int i = 0; i < n; i++) cout << array[i] << '\t'; cout << endl; } // 템플릿 함수와 중복된 print() 함수 void print(char array [], int n) { // char 배열을 출력하기 위한 함수 중복 for (int i = 0; i < n; i++) cout << (int)array[i] << '\t'; // array[i]를 int 타입으로 변환하여 정수 출력 cout << endl; } int main() { int x[] = {1, 2, 3, 4, 5}; double d[5] = {1.1, 2.2, 3.3, 4.4, 5.5}; print(x, 5); print(d, 5); // 템플릿 print() 함수로부터 구체화 char c[5] = {1, 2, 3, 4, 5}; print(c, 5); // 중복된 print() 함수가 우선 바인딩 }
C++
복사

제네릭 클래스 만들기

제네릭 클래스 선언

template <class T> class MyStack { int tos; T data[100]; // T 타입의 배열 public: MyStack(); void push(T element); T pop(); };
C++
복사

제네릭 클래스 구현

template <class T> void MyStack<T>::push(T element) { ... } tmeplate <class T> T MyStack<T>::pop() { ... }
C++
복사

클래스 구체화 및 객체 활용

MyStack<int> iStack; // int 타입을 다루는 스택 객체 생성 MyStack<double> dStack; // double 타입을 다루는 스택 객체 생성 iStack.push(3); int n = iStack.pop(); dStack.push(3.5); double d = dStack.pop();
C++
복사
예제 10-6) 제네릭 스택 클래스 만들기
#include <iostream> using namespace std; template <class T> class MyStack { int tos; T data [100]; // T 타입의 배열, 스택의 크기는 100 public: MyStack(); void push(T element); // element를 data [] 배열에 삽입 T pop(); }; template <class T> MyStack <T>::MyStack() { // 생성자 tos = -1; // 스택은 비어 있음 } template <class T> void MyStack<T>::push(T element) { if (tos == 99) { cout << "stack full"; return; } tos++; data[tos] = element; } template <class T> T MyStack<T>::pop() { T retData; if (tos == -1) { cout << "stack empty"; return 0; // 오류 표시 } retData = data[tos--]; return retData; } int main() { MyStack<int> iStack; // int만 저장하는 스택 iStack.push(3); cout << iStack.pop() << endl; MyStack<double> dStack; // double만 저장하는 스택 dStack.push(3.5); cout << dStack.pop() << endl; MyStack<char> *p = new MyStack<char>(); // char만 저장하는 스택 p->push('a'); cout << p->pop() << endl; delete p; }
C++
복사
예제 10-7) 제네릭 스택의 제네릭 타입을 포인터나 클래스로 구체화하는 예
#include <iostream> #include <string> using namespace std; class Point { int x, y; public: Point(int x = 0, int y = 0) { this->x = x; this->y = y; } void show() { cout << "(" << x << ', ' << y << ")" << endl; } }; int main() { MyStack<int *> ipStack; // int* 만을 저장하는 스택 int *p = new int [3]; for (int i = 0; i < 3; i++) p[i] = i*10; // 0, 10, 20으로 초기화 ipStack.push(p); // 포인터 푸시 int *q = ipStack.pop(); // 포인터 팝 for (int i = 0; i < 3; i++) cout << q[i] << ' '; cout << endl; delete [] p; MyStack<Point> pointStack; // Point 객체 저장 스택 Point a(2, 3) b; pointStack.push(a); // Point 객체 a 푸시, 복사되어 저장 b = pointStack.pop(); // Point 객체 팝 b.show(); MyStack<Point*> pStack; // Point* 포인터 스택 pStack.push(new Point(10, 20)); // Point 객체 푸시 Point* pPoint = pStack.pop(); // Point 객체의 포인터 팝 pPoint->show(); MyStack<string> stringStack; // 문자열만 저장하는 스택 string s = "c++"; stringStack.push(s); stringStack.push("java"); cout << stringStack.pop() << ' '; cout << stringStack.pop() << endl; }
C++
복사
예제 10-8) 두 개의 제네릭 타입을 가진 클래스 만들기
#include <iostream> using namespace std; template <class T1, class T2> // 두 개의 제네릭 타입 선언 class GClass { T1 data1; T2 data2; public: GClass(); void set(T1 a, T2 b); void get(T1 &a, T2 &b); // data1을 a에, data2를 b에 리턴하는 함수 }; template <class T1, class T2> GClass<T1, T2>::GClass() { data1 = 0; data2 = 0; } template <class T1, class T2> void GClass<T1, T2>::set(T1 a, T2 b) { data1 = a; data2 = b; } template <class T1, class T2> void GClass<T1, T2>::get(T1 & a, T2 & b) { a = data1; b = data2; } int main() { int a; double b; GClass<int, double> x; x.set(2, 0.5); x.get(a, b); cout << "a=" << a << '\t' << "b=" << b << endl; char c; float d; GClass<char, float> y; y.set('m', 12.5); y.get(c, d); cout << "c=" << c << '\t' << "d=" << d << endl; }
C++
복사

C++ 표준 템플릿 라이브러리, STL

STL (Standard Template Library)

표준 템플릿 라이브러리
많은 제네릭 클래스와 제네릭 함수 포함

STL의 구성

컨테이너 - 템플릿 클래스
데이터를 담아두는 자료구조를 표현한 클래스
리스트, 큐, 스택, 맵, 셋, 벡터
iterator - 컨테이너 원소에 대한 포인터
컨테이너의 원소들을 순회하면서, 접근하기 위해 만들어진 컨테이너 원소에 대한 포인터
알고리즘 - 템플릿 함수
컨테이너 원소에 대한 복사, 검색, 삭제, 정렬 등의 기능을 구현한 템플릿 함수
컨테이너의 멤버 함수 아님

STL 컨테이너의 종류

컨테이너 클래스
설명
헤더 파일
vector
가변 크기의 배열을 일반화한 클래스
<vector>
deque
앞 뒤 모두 입력 가능한 큐 클래스
<deque>
list
빠른 삽입/삭제 가능한 리스트 클래스
<list>
set
정렬된 순서로 값을 저장하는 집합 클래스, 값은 유일
<set>
map
(key, value) 쌍을 저장하는 맵 클래스
<map>
stack
스택을 일반화한 클래스
<stack>
queue
큐를 일반화한 클래스
<queue>

STL iterator의 종류

iterator의 종류
iterator에 ++ 연산 후 방향
read/write
iterator
다음 원소로 전진
read/write
const_iterator
다음 원소로 전진
read
reverse_iterator
지난 원소로 후진
read/write
const_reverse_iterator
지난 원소로 후진
read

STL 알고리즘 함수들

copy
merge
random
rotate
equal
min
remove
search
find
move
replace
sort
max
partition
reverse
swap

vector 컨테이너

가변 길이 배열을 구현한 제네릭 클래스 - 벡터의 길이에 대한 고민할 필요 없다!
원소의 저장, 삭제, 검색 등 다양한 멤버 함수 지원
벡터에 저장된 원소는 인덱스로 접근 가능 - 인덱스는 0부터 시작
멤버와 연산자 함수
설명
push_back (element)
벡터의 마지막에 element 추가
at (int index)
index 위치의 원소에 대한 참조 리턴
begin ()
벡터의 첫 번째 원소에 대한 참조 리턴
end ()
벡터의 끝(마지막 원소 다음)을 가리키는 참조 리턴
empty ()
벡터가 비어 있으면 true 리턴
erase (iterator it)
벡터에서 it가 가리키는 원소 삭제, 삭제 후 자동으로 벡터 조절
insert (iterator it, element)
벡터 내 it 위치에 element 삽입
size()
벡터에 들어있는 원소의 개수 리턴
operator[]()
지정된 원소에 대한 참조 리턴
operator=()
이 벡터를 다른 벡터에 치환(복사)

vector 다루기 사례

// vector 생성 vector<int> v; // 정수 원소 삽입 v.push_back(1); v.push_back(2); v.push_back(3); // 원소 개수 s, 벡터 용량 c int s = v.size(); int c = v.capacity(); // 원소 값 접근 v.at(2) = 5; int n = v.at(1); // 원소 값 접근 v[0] = 10; int m = v[2];
C++
복사
예제 10-9) vector 컨테이너 활용하기
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v; // 정수만 삽입 가능한 벡터 생성 v.push_back(1); v.push_back(2); v.push_back(3); for (int i = 0; i < v.size(); i++) // 벡터의 모든 원소 출력 cout << v[i] << " "; // v[i]는 벡터의 i번째 원소 cout << endl; v[0] = 10; // 벡터의 첫 번째 원소를 10으로 변경 int n = v[2]; // n에 3 저장 v.at(2) = 5; // 벡터의 3번째 원소를 5로 변경 for (int i = 0; i < v.size(); i++) cout << v[i] << " "; cout << endl; }
C++
복사
예제 10-10) 문자열을 저장하는 벡터 만들기 연습
#include <iostream> #include <string> #include <vector> using namespace std; int main() { vector<string> sv; // 문자열 생성 string name; cout << "이름을 5개 입력하라" << endl; for (int i = 0; i < 5; i++) { cout << i + 1 << ">>"; getline(cin, name); sv.push_back(name); } name = sv.at(0); // 벡터의 첫 원소 for (int i = 1; i < sv.size(); i++) { if (name < sv[i]) // sv[i]의 문자열이 name보다 사전에서 뒤에 나옴 name = sv[i]; // name을 sv[i]의 문자열로 변경 } cout << "사전에서 가장 뒤에 나오는 이름은 " << name << endl; }
C++
복사

iterator

반복자
컨테이너의 원소를 가리키는 포인터
iterator 변수 선언
구체적인 컨테이너를 지정하여 반복자 변수 생성
vector<int>::iterator it; it = v.begin();
C++
복사
→ it는 원소가 int 타입인 벡터의 원소에 대한 포인터
// 벡터 생성 vector<int> v; for (int i = 1; i < 4; i++) v.push_back(i); // iteraotr 변수 선언 및 초기화 vector<int>::iterator it; it = v.begin(); // iterator 증가 it++; // 원소 읽기 int n = *it // 원소 쓰기 n = n*2; *it = n; // 원소 삭제 it = v.erase(it); // 끝으로 옮기기 it = v.end();
C++
복사
10-11) iterator를 사용하여 vector의 모든 원소에 2 곱하기
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); vector<int>::iterator it; // 벡터 v의 원소에 대한 포인터 it 선언 for (it = v.begin(); it != v.end(); it++) { // iterator를 이용하여 모든 원소 탐색 int n = *it; // it가 가리키는 원소 값 리턴 n = n*2; *it = n; // it가 가리키는 원소에 값 쓰기 } for (it = v.begin(); it != v.end(); it++) // 벡터 v의 모든 원소 출력 cout << *it << ' '; cout << endl; }
C++
복사

map 컨테이너

특징
(’키', ‘값') 의 쌍을 원소로 저장하는 제네릭 컨테이너
동일한 ‘키’를 가진 원소가 중복 저장되면 오류 발생
‘키'로 ‘값' 검색
#include <map>
C++
복사
맵 컨테이너 생성 예
// 맵 생성 Map<string, string> dic; // 원소 저장 dic.insert(mak_pair("love", "사랑")); dic["love"] = "사랑"; // 원소 검색 string kor = dic["love"]; string kor = dic.at("love");
C++
복사
멤버와 연산자 함수
설명
insert (pair<> &element)
맵에 ‘키’와 ‘값’으로 구성된 pair 객체 element 삽입
at (key_type& key)
맵에서 ‘키’ 값에 해당하는 ‘값’ 리턴
begin ()
맵의 첫 번째 원소에 대한 참조 리턴
end ()
맵의 끝(마지막 원소 다음)을 가리키는 참조 리턴
empty ()
벡터가 비어 있으면 true 리턴
find(key_type& key)
맵에서 ‘키’ 값에 해당하는 원소를 가리키는 iterator 리턴
erase (iterator it)
맵에서 it가 가리키는 원소 삭제
size()
맵에 들어있는 원소의 개수 리턴
operator[key_type& key]()
맵에서 ‘키’ 값에 해당하는 원소를 찾아 ‘값’ 리턴
operator=()
맵 치환(복사)
예제 10-12) map으로 영한 사전 만들기
#include <iostream> #include <string> #include <map> using namespace std; int main() { map<string, string> dic; dic.insert(make_pair("love", "사랑")); dic.insert(make_pair("apple", "사과")); dic["cherry"] = "체리"; cout << "저장된 단어 개수 " << dic.size() << endl; string eng; while (true) { cout << "찾고 싶은 단어>> "; getline(cin, eng); if (eng == "exit") break; if (dic.find(eng) == dic.end()) // eng "키"를 끝까지 찾았는데 없다면 cout << "None" << endl; else cout << dic[eng] << endl; } cout << "exit" << endl; }
C++
복사

STL 알고리즘 사용하기

알고리즘 함수

템플릿 함수
전역 함수
STL 컨테이너 클래스의 멤버 함수가 아님

sort() 함수 사례

두 개의 매개변수
첫번재 매개변수 : sorting을 시작한 원소의 주소
두번째 매개변수 : sorting 범위의 마지막 원소 다음 주소
예제 10-13) sort() 함수를 이용한 vector sorting
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; cout << "5개의 정수를 입력하세요>> "; for (int i = 0; i < 5; i++) { int n; cin >> n; v.push_back(n); // 키보드에서 읽은 정수를 벡터에 삽입 } sort(v.begin(), v.end()); // v.begin()에서 v.end() 사이의 값을 오름차순으로 정렬 // sort() 함수의 실행 결과 벡터 v의 원소 순서가 변경됨 vector<int>::iterator it; // 벡터 내의 원소를 탐색하는 iterator 변수 선언 for (it = v.begin(); it != v.end(); it++) cout << *it << ' '; cout << endl; }
C++
복사

auto를 이용하여 쉬운 변수 선언

c++ auto

기능
컴파일러에게 변수 선언문에서 추론하여, 타입을 자동 선언하도록 지시
장점
복잡한 변수 선언을 간소하게, 긴 타입 선언 시 오타 줄인다
auto pi = 3.14 auto n = 3; auto *p = &n; int n = 10; int &ref = n; auto ref2 = ref; // ref2는 int& 변수로 자동 선언
C++
복사
함수의 리턴 타입으로부터 추론하여, 변수 타입 선언
int square(int x) { return x * x; } ... auto ret = square(3) // 변수 ret는 int 타입으로 추론
C++
복사
STL 템플릿에 활용
vector<int>::iterator it; // 벡터 내의 원소를 탐색하는 iterator 변수 선언 for (it = v.begin(); it != v.end(); it++) cout << *it << ' ';
C++
복사
이거를
for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl;
C++
복사
이렇게 간단히 선언 가능
예제 10-14) auto 이용한 변수 선언
#include <iostream> #include <vector> using namespace std; int square(int x) { return x * x; } int main() { auto c = 'a'; auto pi = 3.14; auto ten = 10; auto *p = &ten; cout << c << " " << pi << " " << ten << " " << *p << endl; auto ret = square(3); cout << *p << " " << ret << endl; vector<int> v = { 1, 2, 3, 4, 5 }; vector<int>::iterator it; for (it = v.begin(); it != v.end(); it++) cout << *it << " "; cout << endl; for (auto it = v.begin(); it != v.end(); it++) cout << *it << " "; }
C++
복사

람다 (lambda)

람다 대수와 람다식

람다 대수에서 람다식은 수학 함수를 단순하게 표현하는 기법
구성
캡쳐 리스트
매개변수 리스트
리턴 타입
함수 바디
[ ]( ) -> 리턴타입 { /* 함수코드 작성*/ }; [](int x, int y){cout << x+y;}; [](int x, int y)->int{return x + y;}; [](int x, int y){cout << x+y;}(2, 3);
C++
복사
#include <iostream> using namespace std; int main() { [](int x, int y){cout << "합은 " << x+y;}(2, 3); }
C++
복사
예제 10-16) auto로 람다식 다루기
#include <iostream> #include <string> using namespace std; int main() { auto love = [](string a, string b) { cout << a << "보다" << b << "가 좋아" << endl; }; love("돈", "너"); love("냉면", "만두"); }
C++
복사
예제 10-17) 반지름 r이 원의 면적으로 리턴하는 람다식 만들기
#include <iostream> using namespace std; int main() { double pi = 3.14; auto calc = [pi](int r) -> double { return pi*r*r; }; cout << "면적은 " << calc(3); }
C++
복사
예제 10-18) 캡쳐 리스트에 참조 활용, 합을 외부에 저장하는 람다식 만들기
#include <iostream> using namespace std; int main() { int sum = 0; [&sum](int x, int y) { sum = x + y; }(2, 3); // 합 5를 지역변수 sum에 저장 cout << "합은 " << sum; }
C++
복사
예제 10-19) STL for_each() 함수를 이용하여 벡터의 모든 원소 출력
#include <iostream> #include <vector> #include <algorithm> using namespace std; void print(int n) { cout << n << " "; } int main() { vector<int> v = {1, 2, 3, 4, 5}; for_each(v.begin(), v.end(), print); // for_each()는 벡터 v의 첫번째 원소부터 끝까지 검색하면서, // 각 원소에 대해 print(int n) 호출, 매개변수 n에 각 원소 값 전달 }
C++
복사
예제 10-20) STL 함수 for-each() 와 람다식을 이요하여, 벡터의 모든 원소 출력
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v = { 1, 2, 3, 4, 5 }; for_each(v.begin(), v.end(), [](int n) { cout << n << " "; }); // for_each()는 벡터 v의 첫번째 원소부터 끝까지 검색하면서 // 각 원소에 대해 3번째 매개변수인 람다식 호출, 매개변수 n에 각 원소 값 전달 // 람다식 호출 -> 매개변수 n에는 벡터의 각 원소 전달 }
C++
복사

11장. C++ 입출력 시스템

스트림

스트림 (stream)

데이터의 흐름, 혹은 데이터를 전송하는 SW 모듈
ex) 흐르는 시내와 유사한 개념으로 생각
스트림의 양 끝에는 프로그램과 장치 연결
보낸 순서대로 데이터 전달
입출력 기본 단위 : 바이트(byte)

C++ 스트림 종류

입력 스트림
입력 장치, 네트워크, 파일로부터 데이터를 프로그램으로 전달하는 스트림
출력 스트림
프로그램에서 출력되는 데이터를 출력 장치, 네트워크, 파일로 전달하는 스트림

C++ 입출력 스트림 버퍼

C++ 입출력 스트림은 버퍼를 가짐

키 입력 스트림의 버퍼

목적
입력장치로부터 입력된 데이터를 프로그램으로 전달하기 전에 일시 저장
키 입력 도중 수정 가능
<Backspace> 키가 입력되면 이전에 입력된 키를 버퍼에서 지움
C++ 응용 프로그램은 사용자의 키 입력이 끝난 시점에서 읽음
<Enter> 키 : 키 입력의 끝을 의미
<Enter> 키가 입력된 시점부터 키 입력 버퍼에서 프로그램이 읽기 시작

스크린 출력 스트림 버퍼

목적
프로그램에서 출력된 데이터를 출력 장치로 보내기 전에 일시 저장
출력 장치를 반복적으로 사용하는 비효율성 개선
버퍼가 꽉 차거나 강제 출력 명령 시에 출력 장치에 출력

C++ 표준은 스트림 입출력만 지원

입출력 방식 2가지

스트림 입출력 방식 (stream I/O)
스트림 버퍼를 이용한 입출력 방식
입력된 키는 버퍼에 저장
<Enter> 키가 입력되면 프로그램이 버퍼에서 읽어가는 방식
출력되는 데이터는 일차적으로 스트림 버퍼에 저장
버퍼가 꽉 차거나, ‘\n’ 을 만나거나, 강제 출력 명령의 경우에만 버퍼가 출력 장치에 출력
저 수준 입출력 방식 (raw level console I/O)
키가 입력되는 즉시 프로그램에게 키 값 전달
<Backspace> 키 그 자체도 프로그램에게 바로 전달
게임 등 키 입력이 즉각적으로 필요한 곳에 사용
프로그램이 출력하는 즉시, 출력 장치에 출력
컴파일러마다 다른 라이브러리나 API wldnjs
C++ 프로그램의 호환성이 낮음

C++ 표준은 스트림 입출력 방식만 지원

스트림 입출력은 모든 표준 C++ 컴파일러에 의해 컴파일 됨
높은 호환성

구 버전 입출력 라이브러리의 약점

문자를 한 바이트의 char로 처리
한글 문자 읽을 수 없음 → 물론 지금도 cin으로 한글을 문자 단위로는 읽을 수 없음

현재의 표준 C++ 입출력 라이브러리

다양한 크기의 다국어 문자를 수용하기 위해, 입출력 라이브러리가 템플릿으로 작성됨

typedef로 선언된 ios, istream, ostream, iostream 클래스

typedef basic_ios<char, char_traits<char> > ios; typedef basic_istream<char, char_traits<char> > istream; typedef basic_ostream<char, char_traits<char> > ostream; typedef basic_iostream<char, char_traits<char> > iostream;
C++
복사

입출력 클래스 소개

클래스
설명
ios
모든 입출력 스트림 클래스들의 기본(Base) 클래스, 스트림 입출력에 필요한 공통 함수와 상수, 멤버 변수 선언
istream, ostream, iostream
istream은 문자 단위 입력 스트림, ostream은 문자 단위 출려 스트림, iostream은 문자 단위로 입출력을 동시에 할 수 있는 스트림 클래스
ifstream, ofstream, fstream
파일에서 읽고 쓰는 기능을 가진 파일 입출력 스트립 클래스, 파일에서 읽을 때는 ifstream 클래스를, 파일에 쓸 때는 ofstream 클래스를, 읽고 쓰기를 동시에 할 때 fstream 클래스 이용

C++ 표준 입출력 스트림 객체

→ C++ 프로그램이 실행될 때, 자동으로 생겨나는 스트림

cin

istream 타입의 스트림 객체로서 키보드 장치와 연결

cout

ostream 타입의 스트림 객체로서 스크린 장치와 연결

cerr

ostream 타입의 스트림 객체로서 스크린 장치와 연결
오류 메시지를 출력할 목적
스트림 내부 버퍼 거치지 않고 출력

clog

ostream 타입의 스트림 객체로서 스크린 장치와 연결
오류 메시지를 출력할 목적
스트림 내부에 버퍼 거쳐 출력
istream cin ostream cout ostream cerr ostream clog
C++
복사

ostream 멤버 함수

ostream& put(char ch) // ch의 문자를 스트림에 출력 ostream& wirte((char* str, int n) // str 배열에 있는 n개의 문자를 스트림에 출력 ostream& flush() // 현재 스트림 버퍼에 있는 내용 강제 출력
C++
복사
예제 11-1) ostream 멤버 함수를 이용한 문자 출력
#include <iostream> using namespace std; int main() { // "Hi!" 를 입력하고 다음 줄로 넘어간다 cout.put('H'); cout.put('i'); cout.put(33); // 아스키코드 33은 '!' 문자이다 cout.put('\n'); // "C++ "을 출력한다 cout.put('C').put('+').put(' '); // put 메소드를 연결하여 사용할 수 있다 char str[] = "I love you programming"; cout.write(str, 6); // str 배열의 6개의 문자 "I love"를 스트림에 출력 }
C++
복사

istream 멤버 함수 - 문자 입력, get() 함수

int get() // 입력 스트림에서 문자를 읽어 리턴, 오류나 EOF를 만나면 -1(EOF) 리턴 istream& get(char& ch) // 입력 스트림에서 문자를 읽어 ch에 저장, // 현재 입력 스트림 객체(*this)의 참조 리턴, // 오류나 EOF를 만나면, 스트림 내부의 오류 플래그(failbit) 세팅
C++
복사
int get() 을 이용하여 한 라인의 문자들을 읽는 코드
int ch; while((ch = cin.get()) != EOF) { // EOF는 -1 cout.put(ch); // 읽은 문자 출력 if (ch == '\n') break; // <Enter> 키가 입력되면 읽기 중단 }
C++
복사
istream& get(char& ch) 을 이용하여, 한 라인의 문자들을 읽는 코드
char ch while (true) { cin.get(ch); // 입력된 키를 ch에 저장하여 리턴 if (cin.eof()) break; // EOF를 만나면 읽기 종료
C++
복사

12장. C++ 파일 입출력

텍스트 파일 - 문자만으로 구성된 문서 (읽을 수 있는 것)
특수 문자도 포함 (’\n’, ‘\t’)
각 문자마다 문자코드(2진수) 할당 - 아스키코드, 유니코드
txt 파일, HTML 파일, XML 파일, C++ 소스 파일 등
바이너리 파일 - 문자, 그림, 표, 사운드, 동영상 등으로 구성된 문서 (읽을 수 있는 것 + 읽을 수 없는 것)

바이너리 파일

문자로 표현되지 않는 바이너리 데이터가 기록된 파일
이미지, 오디오, 그래픽 등등
텍스트 파일의 각 바이트 → 문자로 해석된다
바이너리 파일의 각 바이트 → 문자로 해석되지 않는 것도 있음
각 바이트의 의미는 파일을 만든 응용프로그램 만이 해석 가능
문자로 매핑되지 않는 바이너리 값

바이너리 파일의 종류

jpeg, bmp 등의 이미지 파일
mp3
hwp, doc, ppt
obj, exe

파일 입출력 스트림은 파일을 프로그램과 연결한다

>> 연산자와 istream의 get(), read() 함수
연결된 장치로부터 읽는 함수
키보드에 연결되면 키 입력을, 파일에 연결되면 파일에서 입력
<< 연산자와 ostream의 put(), write() 함수
연결된 장치에 쓰는 함수
스크린에 연결되면 화면에, 파일에 연결되면 파일에 출력

파일 입출력 모드 : 텍스트 I/O 와 바이너리 I/O

파일 입출력 방식
텍스트 I/O
문자 단위로 파일에 쓰기, 파일에서 읽기
문자 기록, 읽은 바이트 문자로 해석
텍스트 파일에만 적용
바이너리 I/O
바이트 단위로 파일에 쓰기, 파일에서 읽기
데이터를 문자로 해석하지 않고, 있는 그대로 기록, 읽음
텍스트 파일과 바이너리 파일 모두 입출력 가능
둘의 차이점
개형 문자(’\n’)를 다루는데 있다
<< 연산자를 이용한 간단한 파일 출력
ofstream fout; fout.open("song.txt"); if (!fout) { } fout << age << '\n'; fout << singer << endl; fout.close();
C++
복사

파일 모드 (file mode)

파일 모드

파일 입출력에 대한 구체적인 작업 행태에 대한 지정
구체적인 작업 → 읽기를 할지, 쓰기를 할지 등

파일 모드 지정 → 파일 열 때

open (”파일이름", 파일모드)
ifstream (”파일이름", 파일모드)
ofstream (”파일이름", 파일모드)
파일모드
의미
ios::in
읽기 위해 파일을 연다
ios::out
쓰기 위해 파일을 연다
ios::ate
쓰기 위해 파일을 연다. 열기 후 파일 포인터를 파일 끝에 둔다. 파일 포인터를 옮겨 파일 내의 임의의 위체에 쓸 수 있다.
ios::app
파일 쓰기 시에만 적용된다. 파일 쓰기 시마다, 자동으로 파일 포인터가 파일 끝으로 옮겨져서 항상 파일의 끝에 쓰기가 이루어진다.
ios::trunc
파일을 열 때, 파일이 존재하면 파일의 내용을 모두 지워 파일 크기가 0인 상태로 만든다. ios::out 모드를 지정하면 디폴트로 함께 지정된다.
ios::binary
바이너리 I/O 로 파일을 연다. 이 파일 모드가 지정되지 않으면 디폴트가 텍스트 I/O 이다

파일 모드 설정

void open(const char *filename, iost::openmode mode) // mode로 지정된 파일 모드로 filename의 파일을 연다
C++
복사
student.txt 파일에서 처음부터 읽고자 하는 경우
ifstream fin; fin.open("student.txt", ios::in);
C++
복사
student.txt 파일의 끝에 데이터를 저장하는 경우
ofstream fout; fout.open("student.txt", ios::out | ios::app);
C++
복사
바이너리 I/O 로 data.bin 파일을 기록하는 경우
fstream fbinout; fbinout.open("data.bin", ios::out | ios::binary);
C++
복사

get() 과 EOF

파일의 끝을 만나면 읽기를 멈추어야 하는데, get()은 파일의 끝을 어떻게 인식할까?
파일의 끝에서 읽기를 시도하면 get()은 EOF(-1값)를 리턴한다

get()으로 파일의 끝을 인지하는 방법

while(true) { int c = fin.get(); // 파일에서 문자(바이트)를 읽는다 if (c == EOF) { ... // 파일의 끝을 만난 경우. 이에 대응하는 코드를 작성 break; // while 루프에서 빠져나온다. } else { ... // 읽은 문자(바이트) c를 처리한다 } }
C++
복사
== 동일한 코드
while((c = fin.get()) != EOF) { // 파일의 끝을 만나면 루프 종료 ... // 파일에서 읽은 값 c를 처리하는 코드 }
C++
복사

파일의 끝을 잘못 인지하는 코드

while (!fin.eof()) { int c = fin.get(); // 마지막 읽은 EOF(-1) 값이 c에 리턴된다 ... // 읽은 값 c를 처리하는 코드 }
C++
복사
→ EOF 값을 c에 읽어 사용한 후 , 다음 루프의 while 조건문에서 EOF에 도달한 사실을 알게 된다.
예제 12-4) 텍스트 파일 연결
C++
복사

텍스트 파일의 라인 단위 읽기

두 가지 방법

istream
getline(char* line, int n)
C++
복사
getline(ifstream& fin, string& line)
C++
복사

라인 단위로 텍스트 파일을 읽는 전형적인 코드

1) istream의 getline() 함수 이용
2) 전역 함수 getline(ifstream& fin, string& line) 함수 이용
예제 12-5) istream의 getline()을 이용하여 텍스트 파일을 읽고 화면 출력
#include <fstream> ifstream fin("주소") char buf[81]; while (fin.getline(buf, 81)) { cout << buf << endl; } fin.close()
C++
복사
예제 12-6) getline(ifstream&, string&) 으로, words.txt 파일을 읽고 단어 검색
#include <iostream> #include <fstream> #include <string> #include <vector> using namespace std; void fileRead(vector<string> &v, ifstream &fin) { string line; while (getline(fin, line)) { v.push_back(line); } } void search(vector<string> &v, string word) { for (int i = 0; i < v.size(); i++) { int index = v[i].find(word); if (index != -1) cout << v[i] << endl; } } int main() { vector<string> wordVector; ifstream fin("words.txt"); if (!fin) { cout << "words.txt 파일을 열 수 없습니다." << endl; return 0; } fileRead(wordVector, fin); fin.close(); cout << "words.txt 파일을 읽었습니다." << endl; while (true) { cout << "검색할 단어를 입력하세요>> "; string word; getline(cin, word); if (word == "exit") break; search(wordVector, word); } cout << "프로그램을 종료합니다." << endl; }
C++
복사

바이너리 I/O

데이터의 바이너리 값을 그대로 파일에 저장하거나
파일의 바이너리 값을 그대로 읽어서 변수나 버퍼에 저장하는 방식
ios :: binary 모드 속성 사용
ifstream fin; fin.open("desert.jpg", ios::in | ios::binary); ofstream fout("desert.jpg", ios::out | ios::binary); fstream fsin("desert.jpg", ios::in | ios::binary);
C++
복사
예제 12-8) read()로 텍스트 파일을 바이너리 I/O 로 읽기
#include <fstream> ifstream fin; fin.open(file, ios::in | ios::binary); // 읽기 모드로 파일 열기 if (!fin) { cout << "파일 열기 오류"; return 0; } int count = 0; char s[32]; while (!fin.eof()) { // 파일 끝까지 읽는다 fin.read(s, 32); // 최대 32바이트를 읽어 배열 s에 저장 int n = fin.gcount(); // 실제 읽은 바이트 수 알아냄 cout.write(s, n); // 버퍼에 있는 n개의 바이트를 화면에 출력 count += n; } cout << "읽은 바이트 수는 " << count << endl; fin.close();
C++
복사
예제 12-9) read() / write() 로 이미지 파일 복사
// 소스파일에서 목적파일로 복사하기 char buf[1024]; while (!fsrc.eof()) { fsrc.read(buf, 1024); // 최대 1024바이트를 읽어 배열 s에 저장 int n = fsrc.gcount(); // 실제 읽은 바이트 수 알아냄 fdest.write(buf, n); // 읽은 바이트 수 만큼 버퍼에서 목적 파일에 기록 } fsrc.close(); fdest.close();
C++
복사
예제 12-10) int배열과 double 값을 바이너리 파일에 저장하고 읽기
#include <iostream> #include <fstream> using namespace std; int main() { char* file = "주소"; ofstream fout; fout.open(file, ios::out | ios::binary); if (!fout) { cout << "파일 열기 오류"; return 0; } int n[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; double d = 3.15; fout.write((char*)n, sizeof(n)); // int 배열 n을 한번에 파일에 쓴다 fout.write((char*)(&d), sizeof(d)); // double 값 하나를 파일에 쓴다 fout.close(); // 배열 n과 d를 임의의 값으로 변경시킨다 for (int i = 0; i < 10; i++) n[i] = 99; d = 8.15; ifstream fin(file, ios::in | ios::binary); if (!fin) { // 열기 실패 검사 cout << "파일 열기 오류"; return 0; } //read()로 한번에 배열을 읽는다 fin.read((char*)n, sizeof(n)); fin.read((char*)(&d), sizeof(d)); for (int i = 0; i < 10; i++) cout << n[i] << ' '; cout << endl << d << endl; fin.close(); }
C++
복사

텍스트 I/O 와 바이너리 I/O의 확실한 차이점

파일의 끝을 처리하는 방법은 똑같다
파일의 끝을 만나면 EOF 리턴
개행문자 ‘\n’ 를 읽고 쓸때, 서로 다르게 작동
텍스트 I/O 모드
char buf[] = {'a', 'b', '\n'); fout.write(buf, 3) // 파일에 'a', 'b', '\r', '\n'의 4개의 바이트 저장
C++
복사
바이너리 I/O 모드
ofstream fout("주소", ios::out | ios::binary); char buf[] = {'a', 'b', '\n'}; fout.write(buf, 3); // 파일에 'a', 'b', '\n'의 3개의 바이트 저장
C++
복사

스트림 상태 검사

파일에 입출력이 진행되는 동안, 스트림(열어놓은 파일)에 관한 입출력 오류 저장
스트림 상태를 저장하는 멤버 변수 이용

스트림 상태를 나타내는 비트 정보

비트
설명
eofbit
파일의 끝을 만났을 때, 1로 세팅
failbit
정수를 입력받고자 하였으나, 문자열이 입력되는 등 포맷 오류나, 쓰기 금지된 곳에 쓰기를 시행하는 등 전반적인 I/O 실패 시에 1로 세팅
badbit
스트림이나 데이터가 손상되는 수준의 진단되지 않는 문제가 발생한 경우나, 유효하지 않는 입출력 명령이 주어졌을 때 1로 세팅

스트림 상태를 검사하는 멤버 함수

멤버 함수
설명
eof()
파일의 끝을 만났을 때 (eofbit = 1), true 리턴
fail()
failbit나 badbit가 1로 세팅되었을 때, true 리턴
bad()
badbit이 1로 세팅되었을 때, true 리턴
good()
스트림이 정상적 (모든 비트가 0)일 때, true 리턴
clear()
스트림 상태 변수를 0으로 지움

임의 접근과 파일 포인터

C++ 파일 입출력 방식

순차 접근

읽은 다음 위치에서 읽고,
쓴 다음 위치에 쓰는 방식
디폴트 파일 입출력 방식

임의 접근

파일 내 임의의 위치로 옮겨 다니면서, 읽고 쓸 수 있는 방식
파일 포인터를 옮겨 파일 입출력

파일 포인터 (file pointer)

파일은 연속된 바이트의 집합
파일에서 다음에 읽거나, 쓸 위치를 표시하는 특별한 마크

C++는 열려진 파일마다, 두 개의 파일 포인터 유지

get pointer
파일 내에 다음에 읽을 위치
put pointer
파일 내에 다음에 쓸 위치

임의 접근 방법

파일 포인터 제어

절대 위치로 이동
상대 위치로 이동
istream& seekg(streampos pos) // 정수 값으로 주어진 절대 위치 pos로 get pointer를 옮김 istream& seekg(streamoff offset, ios::seekdir seekbase) // seekbase를 기준으로 offset만큼 떨어진 위치로 get pointer를 옮김 ostream& seekp(streampos pos) // 정수 값으로 주어진 절대 위치 pos로 put pointer를 옮김 ostream& seekp(streamoff offset, ios::seekdir seekbase) // seekbase를 기준으로 offset만큼 떨어진 위치로 put pointer를 옮김 streampos tellg() // 입력 스트림의 현재 get pointer의 값 리턴 streampos tellp() // 출력 스트림의 현재 put pointer의 값 리턴
C++
복사
seekbase
설명
ios :: beg
파일의 처음 위치를 기준으로 파일 포인터를 움직인다
ios :: cur
현재 파일 포인터의 위치를 기준으로 파일 포인터를 움직인다
ios :: end
파일의 끝(EOF) 위치를 기준으로 파일 포인터를 움직인다
예제 12-12) 파일 크기 알아내기
long getFileSize(ifStream& fin) { fin.seekg(0, ios::end); // get pointer를 파일의 맨 끝으로 옮기 long length = fin.tellg(); // get pointer의 위치를 알아냄 return length; // length는 파일의 크기와 동일 }
C++
복사

13장. 예외 처리와 C언어와의 링크 지

예제 13-1) 예외 상황에 대한 대처가 없는 프로그램 사례 → 문제
#include <iostream> using namespace std; int getExp(int base, int exp) { // base의 exp 지수승을 계산하여 리턴 int value = 1; for (int n = 0; n < exp; n++) value = value * base; // base를 exp번 곱하여 지수 값 계산 return value; } int main() { int v = getExp(2, 3); cout << "2의 3승은 " << v << "입니다." << endl; int e = getExp(2, -3); cout << "2의 -3승은 " << e << "입니다." << endl; // -> 오류 발생! 1/8이 나와야하는데 -1이 출력된다 }
C++
복사
예제 13-2) if문리턴 값을 이용한 오류 처리
getExp()의 리턴 값이 오류 상태와 계산값을 함께 표시하는 예민한 코드
#include <iostream> using namespace std; int getExp(int base, int exp) { // base의 exp 지수승을 계산하여 리턴 if (base <= 0 || exp <= 0) { return -1; // 오류 리턴 } int value = 1; for (int n = 0; n < exp; n++) value = value * base; // base를 exp번 곱하여 지수 값 계산 return value; } int main() { int v = 0; v = getExp(2, 3); if (v != -1) cout << "2의 3승은 " << v << "입니다." << endl; else cout << "오류. 2의 3승은 " << "계산할 수 없습니다." << endl; int e = 0; e = getExp(2, -3); // 2의 -3승? getExp()는 false 리턴 if (e != -1) cout << "2의 -3승은 " << e << "입니다." << endl; else cout << "오류. 2의 -3승은" << "계산할 수 없습니다." << endl; }
C++
복사
예제 13-3) 리턴 값과 참조 매개 변수를 이용한 오류 처리
getExp()의 리턴 값의 단일화 - 오류 상태만 표시
참조 매개변수를 통해 계산 값을 전달하는 정리된 코드
#include <iostream> using namespace std; int getExp(int base, int exp, int &ret) { // base ** exp 값을 계산하여 ret에 저장 if (base <= 0 || exp <= 0) { return false; } int value = 1; for (int n = 0; n < exp; n++) value = value * base; // base를 exp번 곱하여 지수 값 계산 ret = value; return true; } int main() { int v = 0; if (getExp(2, 3, v)) // v = 2**3 = 8, getExp()는 true 리턴 cout << "2의 3승은 " << v << "입니다." << endl; else cout << "오류. 2의 3승은 " << "계산할 수 없습니다." << endl; int e = 0; if (getExp(2, -3, e)) // 2**-3 ? -> getExp()는 false 리턴 cout << "2의 -3승은 " << e << "입니다." << endl; else cout << "오류. 2의 -3승은 " << "계산할 수 없습니다." << endl; }
C++
복사

예외

예외

실행 중, 프로그램 오동작이나 결과에 영향을 미치는 예상치 못한 상황 발생

예외 처리기

예외 발생을 탐지하고 예외를 처리하는 코드
잘못된 결과, 비정상적인 실행, 시스템에 의한 강제 종료를 막음

예외 처리 수준

운영체제 수준 예외 처리
운영체제가 예외의 발생을 탐지하여, 응용프로그램에게 알려주어 예외에 대처하게 함
OS마다 서로 다르므로, OS나 컴파일러 별로 예외 처리 라이브러리로 작성
응용프로그램 수준 예외 처리
사용자의 잘못된 입력이나
없는 파일을 여는 등, 응용프로그램 수준에서 발생하는 예외를 자체적으로 탐지하고 처리하는 방법

C++ 예외 처리

C++ 표준의 예외 처리
응용프로그램 수준 예외 처리

C++ 예외 처리 기본 형식

try-throw-catch

try {} 블록

예외가 발생할 가능성이 있는 코드를 묶음

throw문

발견된 예외를 처리하기 위해, 예외 발생을 알리는 문장
try{} 블록 내에서 이루어져야 함

catch() {} 블록

throw에 의해 발생한 예외를 처리하는 코드
try { .... 예외 발견한다면 { throw XXX; // 예외 발생 알림. XXX는 예외 값 } 예외 발견한다면 { throw YYY; } } catch(처리할 예외 파라미터 선언) { // catch {} 블록 예외 처리문 } catch(처리할 예외 파라미터 선언) { // catch {} 블록 예외 처리문 }
C++
복사

throw와 catch

throw 3; ... catch(int x) { // throw 3; 이 실행되면 catch()문 실행, x에 3이 전달 ... }
C++
복사
예)
try { throw 3.5; } catch(double d) { ... } /////////////////// try { throw "음수 불가능"; } catch(const char* s) { // const char* 타입의 예외 처리, 얘외 값은 "음수 불가능"이 s에 전달됨 cout << s; }
C++
복사
예제 13-4) 0으로 나누는 예외 처리
#include <iostream> using namespace std; int main() { int n, sum, average; while(true) { cout << "합을 입력하세요>> "; cin >> sum; cout << "인원 수를 입력하세요>> "; cin >> n; try { if (n <= 0) throw n; else average = sum / n; } catch(int x) { cout << "예외 발생!! " << x << "으로 나눌 수 없음" << endl; average = 0; cout << endl; continue; } cout << "average = " << average << endl << endl; } }
C++
복사

하나의 try {} 블록에 다수의 catch() {} 블록 연결

try { ... throw "음수 불가능"; ... throw 3; ... } // 문자열 타입 예외 처리, "음수 불가능"이 s에 전달 catch (const char* s) { ... } // int 타입 예외 처리, 3이 x에 전달됨 catch (int x) { ... }
C++
복사

함수를 포함하는 try {} 블록

try { int n = multiply(2, -3); } catch (const char* negative) { }
C++
복사
예제 13-5) 지수 승 계산을 예외 처리 코드로 재작성 (완결판)
#include <iostream> using namespace std; int getExp(int base, int exp) { if (base <= 0 || exp <= 0) { throw "음수 사용 불가"; } int value = 1; for (int n = 0; n < exp; n++) { value = value * base; return value; // 계산 결과 리턴 } } int main() { int v = 0; try { v = getExp(2, 3); // v = 2의 3승 = 8 cout << "2의 3승은 " << v << "입니다." << endl; v = getExp(2, -3); // 2의 -3승은? cout << "2의 -3승은 " << v << "입니다." << endl; } catch(const char *s) { cout << s <<endl; } }
C++
복사
예제 13-6) 문자열을 정수로 변환하기
#include <iostream> #include <cstring> using namespace std; // 문자열 -> 정수 // 정수로 변환하기 어려운 문자열의 경우, char* 타입 얘외 발생 int stringToInt(const char x[]) { int sum = 0; int len = strlen(x); for (int i = 0; i < len; i++) { if (x[i] >= '0' && x[i] <= '9') sum = sum*10 + x[i] - '0'; else throw x; // char* 타입의 예외 발생 } return sum; } int main() { int n; try { n = stringToInt("123"); cout << "\"123\" 은 정수 " << n << "로 변환됨" << endl; n = stringToInt("1A3"); // 문자열을 정수로 변환 cout << "\"1A3\" 은 정수 " << n << "로 변환됨" << endl; } catch(const char* s) { cout << s << "처리에서 예외 발생!!" << endl; return 0; } }
C++
복사

예외를 발생시키는 함수의 선언

함수 원형에 연이어 throw(예외 타입, 예외 타입, …) 선언
int max(int x, int y) throw(int) { if (x < 0) throw x; else if (y < 0) throw y; else if (x > y) return x; else return y; } double valueAt(double *p, int index) throw(int, char*) { if (index < 0) throw "index out of bounds exception"; // char* 타입 예외 발생 else if (p == NULL) throw 0; // int 타입 예외 발생 else return p[index]; }
C++
복사
→ 장점
프로그램의 작동을 명확히 함
프로그램의 가동성 높임
예제 13-7) 예외 처리를 가진 스택 클래스 만들기
#include <iostream> class MyStack { int data[100]; int tos; public: MyStack() { tos = -1; } void push(int n) throw(char*); int pop() throw(char*); } void MyStack::push(int n) { if (tos == 99) throw "Stack Full"; tos++; data[tos] = n; } int MyStack::pop() { if (tos == -1) throw "Stack Empty"; int rData = data[tos--]; return rData; } int main() { MyStack intStack; try { intStack.push(100); intStack.push(200); cout << intStack.pop() << endl; cout << intStack.pop() << endl; cout << intStack.pop() << endl; // "Stack Empty" 예외 발생 } catch(const char* s) { cout << "예외 발생 : " << s << endl; } }
C++
복사

다중 try {} 블록

try {} 블록 내, try {} 블록의 중첩 가능

throw 사용 시 주의사항

throw문의 위치
항상 try {} 블록 안에서 실행
시스템이 abort() 호출, 강제 종료
예외를 처리할 catch()가 없으면 → 프로그램 강제 종료
catch() { } 블록 내에도, try { } catch() { } 블록 선언 가능

예외 클래스 만들기

예외 값의 종류

기본 타입의 예외 값
정수, 실수, 문자열 등 비교적 간단한 예외 정보 전달
객체 예외 값
예외 값으로 객체를 던질 수 있디
예외 값으로 사용할 예외 클래스 작성 필요

예외 클래스

사용자는 자신 만의 예외 정보를 포함하는 클래스 작성
throw로 객체를 던짐
객체가 복사되어 예외 파라미터에 전달
예제 13-8) 예외 클래스 만들기
#include <iostream> #include <string> using namespace std; class MyException { // 사용자가 만드는 기본 예외 클래스 선언 int lineNo; string func, msg; public: MyException(int n, string f, string m) { lineNo = n; func = f; msg = m; } void print() { cout << func << " : " << lineNo << " ," << msg << endl; } }; class DivideByZeroException : public MyException { // 0으로 나누는 예외 클래스 선언 public: DivideByZeroException (int lineNo, string func, string msg) : MyException(lineNo, func, msg) {} }; class InvalidInputException : public MyException { // 잘못된 입력 예외 클래스 선언 public: InvalidInputException(int lineNo, string func, string msg) : MyException(lineNo, func, msg) {} }; int main() { int x, y; try { cout << "나눗셈을 합니다. 두개의 양의 정수를 입력하세요>> "; cin >> x >> y; if (x < 0 || y < 0) throw InvalidInputException(32, "main()", "음수 입력 예외 발생"); if (y == 0) throw DivideByZeroException(34, "main()", "0으로 나누는 예외 발생"); cout << (double)x / (double)y; } catch(DivideByZeroException &e) { e.print(); } catch(InvalidInputException &e) { e.print(); } }
C++
복사

C++ 코드에서 C 코드의 링킹

이름 규칙 (naming mangling)

컴파일 후 목적 코드에 이름 붙이는 규칙
변수, 함수, 클래스 등의 이름

C언어의 이름 규칙

이름 앞에 밑줄표시문자(_)를 붙인다
int f (int x, int y) → _f
int main() → _main

C++의 이름 규칙

함수의 매개 변수 타입과 개수, 리턴 타입에 따라 복잡한 이름

C++에서 C 함수 호출 시, 링크 오류 발생

extern “c”

C 컴파일러로 컴파일할 것을 지시
C이름 규칙으로 목적 코드를 생성할 것을 지시

사용법

함수 하나만 선언
extern "C" int f(int x, int y);
C++
복사
여러 함수들 선언
extern "C" { int f(int x, int y); void g(); char s(int []); }
C++
복사
헤더 파일 통째로 선언
extern "C" { #include "mycunction.h" }
C++
복사