*레퍼런스 변수
int b;
int &a = b; //레퍼런스 변수는 특이하게 변수이름을 할당하여 사용함
int &a = int(3);
Point클래스가 있다고 가정
Point a = Point(); //임시객체를 생성하여 레퍼런스 변수 a에 저장
1. 클래스 정의
멤버 변수 + 멤버 함수
2. 객체 생성
Point pt;
pt.Print();
3. 멤버 함수 안에서의 이름 충돌
Class Point
{
int x;
void Point::Print()
{
int x = 3;
cout << "( " << x << ", " << y << ")\n";
}
}
Print함수 안의 x는 함수 안에서 정의한 변수 x를 의미
4. 객체를 사용한 초기화와 대입
int main()
{
Point pt1; //초기화
Point pt3 = pt1; //대입
}
5. 생성자와 소멸자
생성자(Constructors) : 객체를 생성할 때 자동적으로 호출되는 함수
소멸자(Destructors) : 객체가 소멸될 때 자동으로 호출되는 함수
6. 디폴트 생성자
아무일도 하지 않음
Point::Point()
{
}
int main()
{
Point pt();
}
7. 인자가 있는 생성자
Point::Point(int initialX, int initialY)
{
x = initialX;
y = initialY;
}
int main()
{
Point pt(3, 5);
}
8. 복사 생성자(copy constructor)
Point::Point(const Point& pt) //const 레퍼런스pt를 이용해서 pt = 3; (error)
이렇게 별명으로 가리키고 있는 저장공간에 변수를 저장할 수 없음
{
x = pt.x;
y = pt.y;
}
int main()
{
Point pt1(100, 100), pt2(200, 200);
Point pt3 = pt1; // 복사 생성자 호출 (= Point pt3(pt1);)
pt3 = pt2; //다른 객체를 대입하는 경우 복사 생성자가 호출 안됨
}
객체를 사용한 초기화는 기본적으로 멤버 변수를 1:1로 복사
이것이 마음에 들지 않는다면 복사 생성자를 만들고 여러분이 원하는 방식으로 복사하면 됨
얕은 복사
: 같은 문자열을 공유
깊은 복사
: 서로 다른 문자열을 가지고 있음
9. 멤버 초기화 리스트
반드시 초기화해야 하는 멤버 변수들은 생성자의 초기화 리스트를 사용해서 초기화한다
const 속성을 가진 타입 변수 또는 레퍼런스 타입의 멤버 변수를 초기화
NeedConstructor::NeedConstructor(int count, int& number)
: maxCount(count), ref(number)
{
sample = 200;
}
int main()
{
NeedConstructor cr(300, number);
}
10. 임시객체
int main()
{
Area(Point(x, y)); //임시 객체를 생성해서 넘겨준다
}
임시 객체는 그 줄의 실행이 끝나면 자동적으로 소멸됨
함수를 호출하기 위해서 임시적으로 객체가 필요한 경우에는 생성자를 사용해서 임시 객체를 만들 수 있다
11. 소멸자
DynamaicArrary::~DynamicArray()
{
delete[] arr;
arr = NULL:
}
12. 접근제어
public : 외부에서의 접근 허용
protected, private : 외부에서 접근할 수 없음
클래스의 내부 : 멤버 함수
클래스의 외부 : 멤버 함수 이외의 모든 함수
13. 클래스의 멤버 변수 설정
생성자를 사용 할 때 및 외부로부터 멤버 변수의 값을 변경(멤버함수) 할 수 있는 모든 경우를 감시해야 함
모든 경우에 동일한 코드를 사용하게 만들어야 함
14. 정적 멤버
정적 멤버는 모든 객체가 공유하는 멤버이다
{
public:
static int student_count;
static void PrintStdCount();
};
int Student::student_count = 0;
Student::~Student()
{
student_count--;
}
void Func()
{
Student::PrintStudCount();
}
int main()
{
Student::PrintStdCount();
}
정적 멤버 함수에서는 일반 멤버에 접근이 불가능
어떤 클래스인지 알 수 없기때문에
정적 멤버 함수에서 일반 멤버에 접근하기 위해서는
정적 멤버 함수 안에서 객체를 생성하고 그 객체의 private 멤버 변수에 접근 가능
15. 헤더 파일에 위치해야 할 것, 구현 파일에 위치해야 할 것
헤더 파일 |
구현 파일 |
클래스의 정의 인라인 멤버 함수 |
멤버 함수의 정의 정적 멤버 함수의 정의 정적 멤버 변수의 정의 |
16. 인라인 함수
함수를 호출하는 대신에 함수의 내용을 붙여 넣음
함수의 내용이 몇 줄 정도로 아주 짧은 경우에만 사용해야 함
인라인 함수의 장점 |
인라인 함수의 단점 |
실행의 흐름이 다른 함수로 넘어갔다가 돌아오는 작업, 인자를 복사하는 과정에서 많은 부하가 발생하는 것을 없애므로 시간을 범 |
인라인 함수를 많이 써서 프로그램의 코드가 커지면 오히려 프로그램의 수행 속도가 떨어짐 하드디스크에 있는 실행 파일이 메모리에 올라갔다 내려왔다 하는 일이 더욱 자주 반복되기 때문임 |
인라인 함수를 만드는 법
- 인라인 함수는 반드시 헤더 파일에 있어야 함
(멤버가 아닌 함수가 인라인 경우도 그 함수는 헤더파일에 위치해야 함)
1. 클래스의 내부에 정의한 멤버 함수들은 모두 자동으로 인라인 함수가 됨.
2. 클래스의 외부에 정의한 멤버 함수는 함수의 정의 앞에 inline키워드를 추가
17. const 멤버 함수
const 멤버 함수의 의미
1. 이 함수는 멤버 변수의 값을 변경하지 않음
2. 실수로 멤버 변수의 값을 바꾸려고 하면, 컴퓨터가 오류 메시지를 통해서 알려줌
3. const 객체를 사용해서 이 함수를 호출함
const 객체
해당 객체의 멤버 변수의 값을 바꿀 수 없음
멤버 변수의 값을 바꾸지 않는 const멤버 함수를 호출하는 것은 허용
const멤버 함수가 아닌 함수는 사용 불가
18. 멤버 함수에 대한 포인터
typedef void (*FP1) (int);
typedef void (Point::*FP2) (int);
int main()
{
Point pt(50, 50);
FP2 fp2 = &Point::SetX; //함수 포인터에 함수의 주소 저장
(pt.*fp2) (100); //함수 포인터를 이용한 함수 호출
}
함수 포인터 설명 참조 (시그널 함수 부분 참조할 것)
: http://comfun.tistory.com/entry/10장-멀티프로세스-기반의-서버구현
참조 : c언어 함수 포인터
void (*fPtr) (void); //함수 포인터 변수 할당
fPtr = Add; //함수 포인터에 함수의 주소 저장 (함수의 이름은 상수)
fPtr(); // == (*fPtr)();
//함수 포인터를 이용한 함수호출
19. 함수 오버로딩
함수이름은 같지만 넘겨주는 인자가 다르게 함
void Point::Offset(int x_delta, int y_delta) {}
void Point::Offset(const Point& pt) {}
같은 일을 하는 함수기 때문에 인자의 형식만 바꿔서 Offset(const Point& pt)함수 안에서
Offset(int x_delta, int y_delta)를 호출함
오버로딩한 함수는 이런 방식으로 구현을 많이 함
20. 객체의 배열
Point arr[3];
객체의 배열을 정의할 때 각 객체들은 디폴트 생성자로 초기화됨
디폴트 생성자가 아닌 다른 생성자로 호출하기 위한 방법
Point arr[3] = {Point(100, 100), Point(50, 100), Point(10, 10)};
21. 객체의 동적인 생성
하나의 객체를 동적으로 생성
Point* pt = new Point(100, 100);
객체의 멤버에 접근방법
pt -> Print(); // = (*pt).Print();
하나의 객체를 동적으로 생성된 객체를 해제
delete pt;
마지막으로 사용한 포인터를 NULL로 만들어야 함
pt = NULL;
c언어
동적 생성
int* arr = new int [18];
동적 생성 해제
delete[] arr;
생성자와 소멸자의 호출 시점
|
정적인 생성 |
동적인 생성 |
생성자의 호출 |
객체를 정의할 때 |
new 연산자로 할당할 때 |
소멸자의 호출 |
객체를 정의한 함수가 끝날 때 |
delete 연산자로 할당할 때 |
22. 정적 멤버 함수에서 객체 생성하기
{
public:
Static int studentNumber;
Static Student* CreateStudent(const string& name_arg);
}
int Student::studentNumber = 0;
Student* Student::CreateStudent(const string& name_arg)
{
Student* p = new Student(name_arg, studenNumber++);
return p;
}
int main()
{
Student* p1;
p1 = Student::CreateStudent("이계희");
p1 -> Print();
}
생성자가 private인 경우에도 정적 멤버 함수에서는 객체를 생성할 수 있다
이렇게 하려면 객체를 동적으로 생성해서 그 주소를 반환하는 수밖에 없다
23. 클래스에 넣을 수 있는 다른 것들
+열거체
class Point
{
public:
enum {MIN_X = 0, MAX_X = 100, MIN_Y = 0, MIN_Y = 100};
void SetX(int value);
private:
int x, y,;
};
inline void Point::SetX(int value)
{
if(value < MIN_X)
x = MIN_X;
else if(value > MAX_X)
x = MAX_X;
else
x = value;
}
열거체의 심볼을 클래스의 외부에서 참조하고 싶다면 영역 지정 연산자를 사용해서
Point::MAX_X와 같은 방식으로 사용할 수 있음
+static const 멤버 변수 (꼼수, 보통 열거체를 사용하는 것이 일반적)
Class Point
{
public:
static const MIN_X = 0;
}
클래스 정의에서 멤버 변수를 초기화하는 것은 불가능
static const인 경우에는 허용됨
하지만 이런 문법을 사용하는 데는 제약이 따름
(어떤 컴파일러에서는 오류, 정수 타입(실수가 아닌)의 경우만 이런 문법을 사용 할 수 있음)
+typedef
{
typedef int COOR_T;
}
24. This 포인터
this포인터는 객체에 대한 주소를 가지고 있음
멤버 함수에만 있음
int main()
{
obj.ShowYourSelf();
}
가상의 코드
WhoAmI* const this = &obj;
void WhoAmI::ShowYourSelf() const
{
cout << "{ID = " << id << ", this = " << this << "}\n";
}
+This 포인터 타입
= 클래스* const 타입
만약 클래스가 const함수를 사용한다면
This 포인터 타입 = const 클래스* const타입이 됨
+const에 대한 설명
Point pt;
Point* const ppt1 = &pt; //pt의 멤버 변수의 값 변경 가능, 다른 객체를 가리킬 수 없음
const Point* ppt2 = &pt; //pt의 멤버 변수의 값 변경 불가, 다른 객체를 가리킬 수 있음
const Point* const ppt3 = &pt; //pt의 멤버 변수의 값 변경 불가, 다른 객체를 가리킬 수 없음
+this 포인터로 이름 충돌 해결하기
WhoAmI::WhoAmI(int id)
{
this->id = id;
}
this->id //멤버 변수 id를 의미
id //매개변수 id를 의미
+멤버 함수는 내부적으로는 클래스에 하나만 존재(디버깅시 유용한 지식)
멤버함수는 같은 내용이므로 객체마다 하나씩 가지고 있으면 컴퓨터 입장에서는 메모리 낭비
//////////////////////////상속과 포함/////////////////////////////////////
1.포함
point클래스
Rectangle클래스
클래스의 메모리 구조(p.635)
2. 생성자와 소멸자
Rect::Rect(int left, int top, int right, int bottom)
: _topLeft(left, top), _bottomRight(right, bottom) //초기화 리스트(Rect클래스 생성자의 코드보다 먼저 실행)
{
}
_topLeft(left, top) _topLeft객체(객체의 이름)의 생성자를 호출
_bottomRight(right, bottm) _bottomRight객체(객체의 이름)의 생성자를 호출
생성자의 실행 순서
Point::Point() -> Rect::Rect()
소멸자의 실행 순서
Rect::~Rect() -> Point::~Point()
3. 상속
class HTMLWriter : public DocWriter
{
};
DocWriter 클래스를 상속 받음
4. 생성자와 소멸자
자식객체에는 부모로부터 상속 받은 멤버들이 있는데 이 부분을 초기화하기 위해서 부모 클래스의 생성자가 필요
부모 클래스의 어떤 생성자를 호출할지 지정하지 않으면 부모 클래스의 디폴트 생성자가 호출됨
멤버인 객체를 초기화할 때는 객체의 이름을 사용
부모 클래스를 초기화할 때는 클래스의 이름을 사용
HTMLWriter::HTMLWriter(const string& fileName, const string& content)
: DocWriter(fileName, content)
{
}
DocWriter() = 부모 클래스 이름을 사용해서 생성자를 호출
자식 객체의 생성 시 : 부모 클래스의 생성자 -> 자식 클래스의 생성자
자식 객체의 소멸 시: 자식 클래스의 소멸자 -> 부모 클래시의 소멸자
5. 클래스간의 형변환
+부모 클래스의 객체를 자식 클래스의 객체로 대입(실패)
HTMLWriter hw; //자식 클래스의 객체 생성
DocWriter dw; // 부모 클래스의 객체 생성
hw = dw; // 부모 클래스의 객체를 자식 클래스의 객체로 대입(실패)
+자식 클래스의 객체를 부모 클래스의 객체로 대입(성공)
HTMLWriter hw; //자식 클래스의 객체 생성
DocWriter dw; // 부모 클래스의 객체 생성
dw = hw; // 자식 클래스의 객체를 부모 클래스의 객체로 대입(성공)
6. 포인터간의 형변환, 레퍼런스간의 형변환
HTMLWriter* phw = &dw; //실패 (HTMLWrite* <- DocWriter* 형변환 불가능)
DocWriter dw;
DocWriter* pwd = &dw; //성공 (같은 타입은 대입가능)
HTMLWriter* phw = pdw; //실패 (HTMLWrite* <- DocWriter* 형변환 불가능)
DocWriter dw;
DocWriter& rdw = dw; //성공
HTMLWriter& rhw = rdw; //실패 (HTMLWrite* <- DocWriter* 형변환 불가능)
+자식클래스의 포인터로 부모 객체를 가르킴 (실패)
DocWriter dw; //부모 객체 생성
HTMLWriter* phw = &dw; //실패 (자식클래스의 포인터로 부모 객체를 가르킴)
DocWriter dw; //부모 객체 생성
HTMLWriter& rhw = dw; //실패 (자식클래스의 레퍼런스로 부모 객체를 가르킴)
+부모클래스의 포인터로 자식 객체를 가르킴 (성공)
HTMLWriter hw; //부모 객체 생성
DocWriter* pDW = &hw; //성공 (부모클래스의 포인터로 자식 객체를 가르킴)
HTMLWriter hw; //부모 객체 생성
DocWriter& rDW = hw; //성공 (부모클래스의 레퍼런스로 자식 객체를 가르킴)
실체 객체가 무엇이던 간에 상관없이 포인터의 타입을 기준으로 호출될 함수가 결정됨
HTMLWriter hw; //자식 클래스
DocWriter* pDW = &hw; //부모 클래스 포인터에 자식 클래스 할당
pDW->Write(); //부모 클래스에 해당하는 멤버 함수가 호출됨
업 캐스트 : 자식 클래스 -> 부모 클래스 (가능)
다운 캐스트 : 부모 클래스 -> 자식 클래스 (불가능)
다운 캐스트는 허용 안됨
예외적으로 가능한 경우:
DocWriter* pdw = &hw; //업 캐스트
HTMLWriter* phw = (HTMLWriter*)pdw; //다운 캐스트
7. 접근 제어
public : 모든 곳으로부터의 접근을 허용
protected : 자식 클래스의 멤버 함수로부터의 접근만 허용
pribate : 자신의 멤버 함수 외에는 접근 불가
8. 다중 상속
class UnderGradStudent
{
}
class DormStudent
{
}
class UnderGrad_DormStudent : public UnderGradStudent, public DormStudent
{
}
9. 멤버 이름의 충돌 회피하기
class UnderGradStudent
{
void Warn()
{
}
}
class DormStudent
{
void Warn()
{
}
}
class UnderGrad_DormStudent :
public UnderHradStudent, public DormStudent
{
}
int main()
{
UnderGrad_DormStudent std;
std.Warn();
}
std 객체는 두개의 Warn()함수가 존재하므로 오류발생
오류를 피하기 위해서
std.UnderGradStudent::Warn();
std.DormStudent::Warn();
이와 같이 의도를 구체적으로 밝혀야 함
////////////////////다형성과 가상 함수///////////////////////////
+클래스와 자식들
class Shape
{
}
class Rectangle : public Shape
{
}
class Circle : public Shape
{
}
int main()
{
Shape s;
s.Move(100, 100);
Rectangle r;
r.Move(200, 100);
Circle c;
c.Move(300, 100);
return 0;
}
+Shape클래스를 이용한 하나의 배열에 모든 객체를 보관
int main()
{
Shape* shapes[5] = {NULL};
shapes[0] = new Circle(100, 100, 50);
shapes[1] = new Rectangle(300, 300, 100, 100);
shapes[2] = new Rectangle(200, 100, 50,150);
shapes[3] = new Circle(100, 300, 150);
shapes[4] = new Rectangle(200, 200, 200, 200);
for(int i = 0; i < 5; ++i)
shapes[i] -> Draw();
for(i = 0; i < 5; ++i)
{
delete shapes[i];
shapes[i] = NULL;
}
return 0;
}
문제점
모두 Shape::Draw()함수를 호출하게 됨
+가상함수
#include <iostream>
using namespace std;
class Shape
{
public:
virtual void Draw() const;
virtual ~Shape();
}
virtual을 통해서 이제 객체의 타입에 맞는 Draw()함수가 호출됨
소멸자도 virtual을 붙여주어야 delete shapes[i];호출시 올바른 소멸자가 호출됨
virtual 사용방법
virtual Shape::Draw() 가상함수를 만들면 Circle::Draw(), Rectangle::Draw()함수를 선언할 때 virtual키워드를 붙여주는 것과 상관없이 자동으로 virtual 가상함수가 됨
virtual 키워드는 클래스의 정의 안쪽에서만 한번 붙여주면 됨.
클래스 밖에서 함수를 정의할 때는 virtual 키워드가 필요없음
+다형성
Circle이나 Rectangle 객체들을 각각의 타입에 상관 없이 Shape객체처럼 다룰 수 있는 능력 = 다형성
+동적 바인딩, 정적 바인딩
동적 바인딩 : 프로그램을 실행한 후에야 어떤 함수를 호출할 지를 알 수 있는 경우
정적 바인딩 : 프로그램이 실행하지 않은 상태에서도 어떤 함수가 호출될지 명백하게 알 수 있는 경우
오버라이딩
+순수 가상 함수
virtual void Draw() const = 0;
순수 가상 함수의 의미 = 이 함수는 정의가 없음, 호출 할 수 없음
하지만 자식 클래스에서 이 함수를 오버라이딩 할것임. 그러므로 다형성을 사용해서 이 함수를 호출할 것
+추상 클래스
하나 이상의 순수 가상 함수를 가진 클래스를 추상 클래스라고 함
추상 클래스의 객체를 만드는 것은 불가능
+멤버함수의 사용
일반적인 멤버 함수 -> 다형성을 이용해야 하는 경우라면 가상 함수 -> 다형성을 위해서 함수의 원형만 필요한 경우라면 순수 가상 함수로 만듬
+오버라이딩한 함수에서 부모 클래스의 함수 호출
오버라이딩 하는 대부분의 이유는 부모 클래스에서 해주는 일에 추가적인 작업을 해주기 위해서 이다
클래스이름Shape::함수이름Draw();
void Circle::Draw() const
{
Shape::Draw();
cout << "[Circle] Radisu = " << _radius << "\n";
}
+오버로드된 함수를 오버라이딩할 때
부모 클래스에서 오버로드된 함수 중에 어느 것 하나라도 오버라이드하면 나머지 다른 함수들도 모두 사용할 수 없다
다른 함수를 호출하고 싶으면 클래스이름::함수이름()와 같은 형태로 호출 가능
dog1.Pet::Eat("mkil");
+오버로드된 생성자들
class Dog
{
public:
void Eat();
Dog(int n); //생성자를 오버로딩함
}
Dog::Dog(int n)
{
}
void Dog::Eat()
{
cout << name << " says, 'Growl~'\n";
}
int main()
{
Dog dog1; //기본 생성자를 사용할 수 없음
return 0;
}
우리가 아무런 생성자도 정의하지 않은 경우에는
컴퓨터가 기본적으로 디폴트 생성자와 복사 생성자를 제공
이 디폴트 생성자는 아무 일도 하지 않고 복사 생성자는 멤버를 1:1로 복사해줌
디폴트 생성자에 오버로딩된 생성자를 하나 만든 순간에 컴퓨터가 제공해주는 디폴트 생성자를 사용할 수 없게 됨
이런 경우 아무 일도 하지 않는 디폴트 생성자를 우리가 직접 추가해주어야 함
//////////////예외 처리///////////////
예외 처리 필요한 경우
1. 컴퓨터에 사용 가능한 메모리가 부족한 경우
2. 하드디스크에 파일을 쓰는데 하드디스크의 남은 용량이 부족한 경우
3. 사용자가 범위 밖의 값을 입력하거나 존재하지 않는 파일의 이름을 입력하는 경우
+반환 값을 사용한 예외 처리
배열의 인덱스 범위가 허용범위를 넘어선 경우
{
if(index < 0 || index >= GetSize())
return false;
arr[index] = value;
return true;
}
int main()
{
DynamicArray arr(10);
bool b;
b = arr.SetAt(5, 0);
if(!b)
cout << "arr[5] 사용 실패!!\n";
return 0;
}
+반환 값을 사용한 예외 처리의 문제점
1. 본연의 소스 코드와 예외 처리 코드가 뒤엉켜서 지저분하고 읽기 어렵다
2. 예외 처리 때문에 반환 값을 본래의 용도로 사용할 수 없다
+구조적 예외 처리(일반적인 방법)
키워드 : throw, try, catch
예외의 발생과 실행의 흐름
throw에 의해서 예외가 던져지면 그 함수는 바로 종료
throw에 의해서 던져진 예외는 함수를 뛰어넘어서까지 전달
+예외 객체의 사용
void DynamicArray::SetAt(int index, int value)
{
if(index < 0 || index >= GetSize())
throw MyException(this, "Out of Range!!!", index);
}
int main()
{
DynamicArra arr1(10);
UseArray(arr1);
return 0;
}
void UseArray(DynamicArray& arr1)
{
try
{
arr1.SetAt(5, 100);
}
catch(MyException& ex)
{
cout << "예외를 던진 객체의 주소 = " << ex.sender << "\n";
}
}
throw의 의미
throw MyException(this, "Out of Range!!!", index);
//임시객체의 복사본을 만들고 이 복사본을 아래의 UseArray함수의 인자로 보냄
DynamicArray& arr1 = MyException(this, "Out of Range!!!", index); //이것과 같은 것으로 볼 수 있음
생성된 MyException 객체 대신에 MyException 객체의 복사본이 throw 에 의해서 던져지게 됩니다.
Stack 에 생성되는 다른 객체들과는 성격이 다릅니다.
생성되는 위치가 Heap 인지 아니면 예외 처리 메커니즘을 위해 존재할지도 모를 Stack 공간인지는 정확히 알 수 없지만 최소한 SetAt 함수의 Stack 공간은 아닙니다.
throw 를 하는 순간, 예외 처리 메커니즘에 의해서 MyExeption 객체의 복사본이 생성되며, SetAt함수가 끝나게 되고 MyException 객체의 소멸자가 호출되면서 MyException 객체가 메모리에서 해제되는 스택 되감기 현상이 일어납니다.
이 시점에서 Exception 객체는 소멸이 되겠지만, MyException 객체의 복사본이 존재하게 되겠지요.
+다형성을 사용해서 일관된 관리를 할 수 있다
MyException클래스 -> OutOfRangeException클래스 (상속)
MyException클래스 -> MemoryException클래스 (상속)
void UseMemory()
{
throw MemoryException(NULL, 1000);
}
void UseArray(DynamicArray& arr1, DynamicArray& arr2)
{
try
{
arr1.SetAt(5, 100);
arr2.SetAt(5, 100);
UseMemory();
arr1.SetAt(8, 100);
arr2.SetAt(8, 100);
}
catch(MyException& ex)
{
cout << "예외에 대한 설명 = " << ex.description << "\n";
}
}
MyException클래스는 MemoryException클래스와 OutOfRangeException클래스 둘 다 받을 수 있음
+예외는 함수를 여러 개 건너서도 전달할 수 있다
main() -> A() -> B() -> C()의 순서로 함수를 호출
C()에서 정수 값을 예외로 던짐(이 예외가 던져진 순간 C()함수가 종료)
B(), A()함수도 차례로 종료
그리고 main()함수의 catch블록으로 실행 흐름이 이동
예외는 자신의 타입에 맞는 catch 블록을 찾을 때까지 함수를 거슬러 올라간다
+예외를 다시 던지기
void A()
{
try
{
B();
}
catch(char c)
{
cout << "A() 함수에서 잡은 예외 = " << c << "\n";
throw; //받은 예외를 그대로 다시 던짐
또 다시 함수를 거슬러 올라가면서 알맞은 catch 블록을 찾게 됨
}
}
+catch가 여럿인 경우
{
try
{
A();
}
catch(MemoryException& ex)
{
cout << "MemoryException 타입으로 잡혔음\n";
}
catch(OutOfRangeException& ex)
{
cout << "OutOfRangeException 타입으로 잡혔음\n";
}
catch(...)
{
cout << "그 밖의 타입\n";
}
return 0;
}
void A()
{
throw OutOfRangeException(NULL, 0);
}
try블록 안에서 예외가 던져지면 제일 앞에 있는 catch 블록부터 시작해서 예외를 받을 수 있는지 비교해 나감
만약에 타입이 일치한다면 해당 catch블록을 실행함
+예외 객체는 레퍼런스로 받자
객체를 예외로 던지고 받을 때 3가지 경우
1. 객체
2. 포인터
3, 레퍼런스
1. 객체를 받는 경우 문제점
불필요한 복사가 발생
{
MyException e(this, "객체", 0);
throw e;
}
catch(MyException ex)
{
cout << ex.description << "\n";
}
throw e를 수행시 객체 e의 내용을 가진 임시객체가 생성되고 객체 e는 삭제됨
임시객체의 내용이 ex객체로 들어가게 됨
2. 포인터를 받는 경우 문제점
try
{
MyException* p = new MyException(this, "객체", 0);
throw p;
}
catch(MyException* pex)
{
cout << pex->description << "\n";
delete pex;
}
포인터를 던지고 받을 때는 불필요한 복사가 일어나지 않음
하지만 메모리 할당과 해제를 신경써야 함
3. 레퍼런스를 사용한 경우
try
{
MyException e(this, "객체", 0);
throw e;
}
catch(MyException& ex)
{
cout << ex.description << "\n";
}
불필요한 복사가 발생하지 않음
또한 메모리 관리를 신경쓸 필요도 없음
+리소스를 정리하기 전에 예외가 발생하여 함수가 종료가 된 경우
함수 안에 할당된 메모리가 해제하기 전에 예외를 던져 함수를 그냥 빠져나가 할당된 메모리가 해제되지 않는 문제가 발생
+소멸자로 리소스 문제 해결하기
class SmartPointer
{
public:
SmartPointer(char* p)
:ptr(p)
{
}
~SmartPointer()
{
cout << "메모리가 해제된다1!\n";
delete[] ptr;
}
public:
char* const ptr;
};
예외 때문에 함수가 종료되는 경우에도 객체의 소멸자는 반드시 호출됨
이 점을 이용해서 리소스를 해제하는 용도의 클래스를 만들 수 있음
+생성자에세 예외가 발생한 경우의 문제점
생성자가 올바르게 종료된 경우만 객체를 생성한 것으로 간주
생성자가 예외가 발생한 경우라면 정상적으로 종료되지 않은 것이고 객체도 생성되지 않은 것이다
객체가 생성되지 않았으니 소멸자도 호출될 일이 없다
+예외 다시 던지기
예외를 중간에 가로채서 필요한 정리 작업을 한 후에 예외를 다시 던지는 것
DynamicArray::DynamicArray(int arraySize)
{
try
{
arr = new int[arraySize];
size = arraySize;
throw MemoryException(this, 0);
}
catch(...)
{
cout << "여기는 실행된다!!\n";
delete[] arr;
throw;
}
}
+소멸자에서의 예외는 반드시 막아야 한다
객체의 소멸자에서 예외가 던져지는 경우에는 프로그램이 비정상 종료할 수 있다
생성자에서 했던 것 처럼 소멸자의 모든 코드를 try블록으로 감쌀 필요가 있음
이렇게 잡아낸 예외는 절대로 소멸자 밖을 다시 던져서는 안된다
DynamicArray::~DynamicArray()
{
try
{
cout << "여기는 소멸자다!!\n";
delete[] arr;
arr = 0;
}
catch(...)
{
}
}
+auto_ptr
auto_ptr<int> p(new int);
auto_ptr : auto_ptr 클래스의 객체를 정의한다
<int> : auto_ptr 중에서도 int변수를 가리킬 수 있는 auto_ptr이다
p : 스마트 포인터의 이름
(new int) : 동적으로 int타입의 변수를 생성하고, 그 변수의 주소를 auto_ptr<int>의 생성자의 인자로 넣는다
#include <memory>
using namespace std;
int main()
{
auto_ptr<int> p(new int);
*p = 100;
return 0;
}
+메모리 할당 시에 발생하는 예외
new, new[]연산자는 컴퓨터의 메모리가 부족하면 bad_alloc이라는 예외를 던진다
그리므로 new, new[]연산자를 사용해서 메모리를 동적으로 할당하는 부분을반드시 try 블록으로 감싸주어야 한다
#include <new>
#include <iostream>
using namespace std;
int main()
{
try
{
char* p = new char[0xfffffff];
}
catch(bad_alloc& ex)
{
cout << ex.what() << "\n";
}
}
/////////////접근 범위와 존속 기간//////////////////////
가장 최근에 정의한 변수가 이전에 정의한 변수를 숨김
중복된 여러 개의변수가 있다면 가장 최근의 변수를 사용하게 됨
변수가 정의되는 위치에 따른 전역변수와 지역변수 정리
지역, 전역 |
종류 |
키워드 |
기억장소 |
생존기간 |
유효범위 | |
전역 |
전역변수 |
정의시 |
|
메모리(외부 정적 영역) |
영구적(프로그램 종료 시 제거) |
프로그램 전역 |
참조선언 시 |
extern | |||||
정적 지역변수 |
|
static |
정의된 파일 내부 | |||
지역 |
정적 지역변수 |
|
static |
정의된 함수나 블록 내부 | ||
레지스터 변수 |
|
register |
레지스터 |
일시적 (함수 블록 종료 시 제거) | ||
자동 지역변수 |
생략가능 |
auto |
메모리(스택) |
지역, 전역 |
종류 |
프로그램 시작 |
다른 함수에서의 이용(같은 파일) |
함수(블록) 시작 |
함수(블록) 종료 |
다른 함수에서의 이용(다른 파일) |
프로그램 종료 |
전역 |
전역 변수 |
생성 |
O |
O |
O |
O |
제거 |
정적 전역변수 |
생성 |
O |
O |
O |
X |
제거 | |
지역 |
정적 지역변수 |
생성 |
X |
O |
O |
X |
제거 |
레지스터 변수 |
|
X |
생성 (레지스터) |
제거 |
X |
| |
자동 지역변수 |
|
X |
생성 메모리 (스택) |
제거 |
X |
|
지역, 전역 |
종류 |
초기 값을 부여하지 않은 경우 |
초기 값 저장 |
전역 |
전역 변수 |
자료형에 따라 0이나 null(\0) |
프로그램 시작 시 |
정적 전역변수 | |||
지역 |
정적 지역변수 |
부정 (어떠한 값이 있을지 모름) |
함수나 블록이 실행될 때마다 |
레지스터 변수 | |||
자동 지역변수 |
+함수와 접근 범위
+다른 파일에서 정의한 함수에 접근하기
extern키워드를 사용하면 다른 파일에서도 접근하기
extern void Func();
다른 파일에서 정의한 함수를 사용할 때는 extern 키워드를 생략하고 사용가능
+static으로 정의한 함수
static void Func();
+c언어와의 연결
Fuc()함수의 이름을 내부적으로 보관하는 방식
함수의 원형 |
c언어 |
c++ 언어 |
void Func() |
_Func |
.cpp파일에서 .c파일을 참조하기 위한 방법
extern "C" void Func();
////////////////사용자 정의 타입으로서의 클래스////////////////////
연산자 오버로딩
멤버함수를 사용한 연산자 오버로딩, 일반함수를 사용한 연산자 오버로딩
멤버함수를 사용한 연산자 오버로딩
+피연산자가 두 개인 연산자
class Complex
{
Complex operator+ (const Complex& right)
{
int real = this->real + right.real;
int imag = this->imaginary + right.imaginary;
return Complex(real, imag);
}
};
int main()
{
Complex c1(1, 1);
Complex c2(2, 2);
Complex c3(0, 0);
c3 = c1 + c2; //c3 = (3, 3)
c3 = c1.operator +(c2);
return 0;
}
+피연산자가 한 개인 연산자
class Complex
{
Complex operator++() //++전치 연산
{
this->real++; // == (this->real)++;
//++연산결과 값은 real이 되고, 그 후 real = real + 1;이 처리됨
return *this;
}
Complex operator++(int) // ++후치 연산
{
Complex prev(this->real, this->imaginary);
this->real++; // == (this->real)++;
//++연산결과 값은 real이 되고, 그 후 real = real + 1;이 처리됨
return prev;
}
};
int main()
{
Complex c1(1, 1);
Complex prefix(0, 0);
Complex postfix(0, 0);
prefix = ++c1;
postfix = c1++;
return 0;
}
+일반 함수를 사용한 연산자 오버로딩
{
friend Complex operator+(const Complex& left, const Complex& right);
//친구로 지정한 함수에서는 Complex 클래스의 모든 멤버에 접근할 수 있게 된다
};
Complex operator+ (const Complex& left, const Complex& right)
{
int real = left.real + right.real;
int imag = left.imaginary + right.imaginary;
return Complex(real, imag);
}
int main()
{
Complex c1(1, 1);
Complex c2(2, 2);
Complex c3(0, 0);
c3 = c1 + c2;
c3 = operator + (c1, c2);
return 0;
}
%참조
class Node
{
friend class List;
//List클래스의 모든 멤버 함수에서 Node클래스의 모든 멤버에 접근할 수 있게 된다
};
+멤버 함수로 정의할 수 없는 경우(일반 함수로만 가능한 경우)
Complex c(10, 5);
cout << c;
위와 같이 두 개의 피연산자 중에서 오른쪽 피연산자만 Complex인 경우에는 멤버 함수를 사용해서 구현 할 수 없다
이 경우 일반 함수로 연산자 오버로딩을 해야한다
일반 함수를 사용한 operator<<()
ostream& operator<<(ostream& o, const Complex& right)
{
o << right.Rea() << showpos << right.Imaginary() << "i" << noshowpos;
return 0;
}
int main()
{
Complex c1(10, 5);
cout << c1 << "\n";
return 0;
}
+연산자 오버로딩의 규칙
기존 연산 방법을 바꿀 수 없다
1. 연산자 오버로딩을 할 때 피연산자들 중에 적어도 하나는 객체가 되어야 한다
2. 연산자의 우선 순위나 계산 순서를 변경하는 것도 불가능하다
기존 연산자의 의미에서 크게 벗어나지 않게 만드는 것도 중요한 일이다
c++ 스타일의 형변환
배우는 이유:
c 스타일의 형변환은 눈에 잘 띄지도 않고 툴을 사용해서 찾아내기도 힘듬
c 스타일의 형변환은 형변환의 의도를 구별해내기가 힘듬
c++ 스타일의 형변환
1.const_cast
어떤 타입에서 const 속성이나 volatile 속성을 제거할 때 사용
const int ci = 100;
int i = const_cast<int>(ci);
2. reinterpret_cast
일반적으로 허용하지 않는 위험한 형변환을 할 때 사용
int a, b;
a = reinterpret_cast<int>(&b);
3. static_cast
가장 일반적인 형태의 현변환을 할 때 사용
double d = 30.0;
char c;
c = static_cast<char>(d);
4.dynamic_cast
서로 상속 관계에 있는 클래스 간에 형변환을 할 때 사용
이를 사용하기 위해서 visual c++의 RTTI(Runtime Type information)을 활성화시켜야 한다
형변환에 문제가 있는 경우 dynamic_cast연산자는 NULL값을 반환하거나 bad_cast 예외를 던짐
class A
{
public:
virtual void Func() {}
};
class B : public A
{
};
class C : public B
{
};
int main()
{
A* pa1 = new C;
A* pa2 = new A;
C* pc1 = dynamic_cast<C*>(pa1);
C* pc2 = dynamic_cast<C*>(pa2);
try
{
C& rc1 = dynamic_cast<C&>(*pa2);
}
catch(bad_cast& e) //레퍼런스의 형변환인 경우에는 어떤 특정한 값은 반환이 불가능
{ bad_cast예외를 던짐
}
return 0;
}
+형변환 방법을 컴퓨터에게 알려주기
int a = 4;
float b = 4.0;
b = (float) a; // b = float (a);와 같음
+내가 만든 클래스를 다른 타입으로 형변환하기
class Complex
{
operator int()
{
return this->real;
}
};
int main()
{
Complex c1(10, 5);
int i;
i = c1;
i = c1.operator int();
return 0;
}
+다른 타입을 내가 만든 클래스로 형변환하기
class Complex
{
Complex(int i)
:real(i), imaginary(0)
{}
};
int main()
{
int i = 5;
Complex c(0, 0);
c = i;
c = Complex(i);
c = c + i;
c = c + Complex(i);
//c = c + i;
//c = c + Complex(i);
//c = operator+(c, Complex(i));
return 0;
}
%explicit 키워드
이름처럼 명시적인 형변환만을 허용
///////네임스페이스 - 관련된 코드를 묶어주는 논리적인 가방//////
+왜 네임스페이스를 사용해야 할까?
c++에서 네임스페이스가 폴더와 같은 역할을 해줌
관련된 코드별로 모아 서로 이름이 충돌할 염려도 없음
소스 파일과 코드를 구조적으로 관리할 수 있음
namespace Data
enum FileType(HTML, TEXT, OTHER);
bool Initialize()
{
}
class HTMLWriter
{
}
namespace UserInterface
const int MAX_INPUT_CHAR = 255;
bool Initialize()
{
}
class InvalidInputException
{
}
-네임스페이스의 기본적인 사용법
+네임스페이스 안에서 정의하기
namespace Dog
{
struct Info
{
char name[20];
int age;
};
Info dogs[20];
int count;
void CreateAll();
}
namespace Cat
{
class Info
{
public:
void Meow();
protected:
char name[20];
};
Info cats[20];
int count;
void CreateAll();
}
int count;
+네임스페이스 안에서 정의한 이름 사용하기
무작정 사용하기
namespace Dog
{
struct Info
{
char name[20];
int age;
};
Info dogs[20]'
int count;
void CreateAll();
}
namespace Cat
{
class Info
{
public:
void Meow();
protected:
char name[20];
};
Info cats[20];
int count;
void CreateAll();
}
int count;
int main()
{
CreateAll(); //어떤 네임스페이스의 함수인지 알 수 없어 접근 실패
cats[0].Meow(); //어떤 네임스페이스의 함수인지 알 수 없어 접근 실패
return 0;
}
::(영역 지정 연산자)사용
int main()
{
Cat::CreateAll();
Cat::cats[0].Meow();
Dog::CreateAll();
int dog_count = Dog::count;
}
using 키워드를 사용해서 네임스페이스 지정하기
using namespace Cat; //지금부터 말하는 이름들은 Cat네임스페이스에 소속한 이름들을 의미함
int main()
{
CreateAll();
cats[0].Meow();
return 0;
}
using키워드를 사용해서 네임스페이스에 소속한 이름 지정하기
using Cat::CreateAll; //이제부터 말하는 CreateAll은 Cat 네임스페이스에 소속한 CreateAll을 말함
int main()
{
CreateAll();
return 0;
}
%참고
::count;
영역 지정 연산자(::)앞에 아무런 이름도 적어주지 않으면 전역 공간에서 정의한 변수를 의미
-C++ 표준 라이브러리와 std 네임스페이스
%주의 사항 :using키워드를 사용해서 네이스페이스에 소속된 특정한 이름을 지정하는 방법
using Cat::CreateAll; //전역 공간에 함수가 다시 한번 선언되는 효과를 갖음
마치 전역 공간에 CreateAll()이라는 함수를 선언한 것과 같음
그러므로
void CreateAll(); 다음과 같은 코드를 추가한다면 오류가 발생
+cout 객체 사용법 분석하기
cout, cin을 비롯한 c++의 표준 라이브러리와 관련된 코드들은 std라는 이름의 네임스페이스 안에 정의되어 있음
+네임스페이스와 소스 파일의 관계
네임스페이스와 소스파일은 서로 독립적인 관계
하나의 네임스페이스가 여러 파일에 걸쳐서 존재
한 파일 안에 여러 네임스페이스가 존재 //주로 이 방식을 사용하게 됨
cat.h
#ifndef CAT_H
#define CAT_H
namespace Cat
{
class Info
{
public:
void Meow();
protected:
char name[20];
};
extern Info cats[20];
extern int count;
void CreateAll();
}
#endif
cat.cpp
#include "cat.h"
namespace Cat
{
Info cats[20];
int count;
void Info::Meow()
{
}
void CreateAll()
{
}
}
example.cpp
#include "dog.h"
#include "cat.h"
int main()
{
Dog::CreateAll();
Cat::CreateAll();
return 0;
}
서로 다른 파일에서 만든 Cat 네임스페이지만 컴퓨터 입장에서는 모두 동일한 Cat네임스페이스가 됨
-네임스페이스가 가진 그 밖의 기능
+이름 없는 네임스페이스
이름 없는 네임스페이스를 사용하면 다른 파일에서 접근할 수 없음
static 키워드를 사용한 것과 같은 동일한 효과를 얻을 수 있음
a.cpp
namespace
{
int g;
}
void Func()
{
g = 200;
}
example.cpp
extern int g; //a.cpp에서 정의한 g를 사용하기 위한 준비
int main()
{
g = 200; //접근 실패
return 0;
}
+중첩된 네임스페이스
namespace Data
{
namespace User
{
int number;
}
}
int main()
{
int user_number = Data::User::number;
return 0;
}
+네임스페이스를 별명으로 부르기
namespace this_namespace_has_a_very_long_name
{
int n;
}
namespace oh = this_namespace_has_a_very_long_name; //별명을 붙여줌
int main()
{
oh::n = 100;
return 0;
}
/////템플릿과 STL - 컴퓨터에게 코딩을 맡기자///////
-왜 템플릿을 사용해야 할까?
변수의 타입형에 따라 각각 코딩을 할 필요가 없이 템플릿 원형이 있으면 사용자가 원하는 변수의 타입형을 알려주면 그 변수의 타입형으로 바꾸어서 자동으로 컴퓨터가 코딩을 해줌
템플릿은 함수, 클래스를 자동으로 생성해 줌
-템플릿 클래스(스마트 포인터)
template <typename T>
class AutoArray
{
public:
AutoArray(T* ptr)
{
_ptr = ptr;
}
~AutoArray()
{
delete[] _ptr;
}
T& operator[] (int index)
{
return _ptr[index];
}
private:
T* _ptr;
};
int main()
{
AutoArray<float> arr(new float[100]);
arr[0] = 99.99f;
return 0;
}
컴퓨터에게 코딩을 맡김
float용 AutoArray 클래스를 새로 만드는 대신에 컴퓨터가 T 대신에 float를 넣어서 새로운 클래스를 만듬
그렇게 만들어진 소스 코드가 눈에 보이지는 않음, 컴퓨터 내부적으로는 그 클래스가 존재하고 있음
여러 개의 템플릿 매개 변수를 갖는 템플릿 클래스
template <typename A, typename B, int MAX> //typename 대신에 class라고 해도 됨
class TwoArray
{
A arr1[MAX];
B arr2[MAX];
};
TwoArray<char, double, 20> arr;
-템플릿 함수
template<typename T>
T max(T a, T b)
{
return (a > b ? a : b);
}
int main()
{
int i1 = 5, i2 = 3;
int i3 = max(i1, i2);
double d1 = 0.9, d2 = 1.0;
double d3 = max(d1, d2);
return 0;
}
템플릿 함수의 경우에는 예제에서처럼 타입을 명시하지 않고 그냥 사용하는 것이 가능
-템플릿 사용 시 유의할 점
1. 템플릿은 컴파일 시간에 코드를 만들어 낸다
2. 템플릿 함수의 정의는 헤더 파일에 놓여야 한다
템플릿 함수의 정의 = (일반 템플릿 함수, 멤버 템플릿 함수, 템플릿 클래스의 일반 멤버 함수)
-stl(standard template library)
stl = 템플릿을 사용해서 만들어진 라이브러리
stl은 클래스와 함수로 이루어짐
클래스(자료), 함수(자료를 이용한 정보처리)
장점
1. 확장성
stl의 링크드 리스트를 사용해서 우리가 만든 클래스 객체를 보관하는 등의 일을 간단하게 할 수 있음
2. 표준
3. 전문가들이 만들어 놓은 것이기 때문에 효율적이고 안전함
-stl 컨테이너
컨테이너란 다수의 정보를 담는 역할을 하는 "클래스"
컨테이너의 예)
1. 링크드 리스트
2. 동적인 배열
3. 큐
4. 맵
철학 : 최소한의 소스 수정으로 컨테이너를 다른 것으로 교체 할 수 있다
+STL의 list 사용하기
#include <list>
#include <iostream>
int main()
{
std::list<int> intList; //int 타입을 담을 링크드 리스트 생성
for(int i = 0; i < 10; i++) //0~9까지 링크드 리스트에 넣음
intList.push_back(i); //리스트의 맨 뒤쪽에 노드를 추가하는 함수
intList.remove(5); //5를 보관하는 노드를 제거하기 위해서 remove()멤버 함수를 호출
std::list<int>::iterator it; //노드를 가르키는 포인터 역할 변수
for(it = intList.begin(); it != intList.end(); ++it) //처음 노트부터 끝 노드까지 이동함
std::cout << *it << "\n";
return 0;
}
-STL 알고리즘
stl의 알고리즘 = stl에서 제공하는 함수
+stl의 sort()함수
#include <algorithm>
#include <vector>
#include <iostream>
int main()
{
std::vector<char> vec; //벡터 객체를 생성
vec.push_back('e'); //'a' ~'e' 사이의 문자를 임의의 순서로 동적인 벡터 객체에 삽입
vec.push_back('b');
vec.push_back('a');
vec.push_back('d');
vec.push_back('c');
std::sort(vec.begin(), vec.end()); //정렬함수
//정렬 후 상태를 출력
std::cout << "vector 정렬 후\n";
std::vector<char>::iterator it;
for(it = vec.begin(); it != vec.end(); ++it)
std::cout << *it;
char arr[5] = {'d', 'c', 'b', 'a', 'e'};
std::sort(arr, arr + 5);
std::cout << "\n\n배열 정렬 후\n";
for(char* p = arr; p != arr + 5; ++p)
std::cout << *p;
std::cout << "\n";
return 0;
}
-제너릭 프로그래밍(generic programming)
제너릭 프로그래밍에서 가장 중요한 것
: 정보의 타입(컨테이너)과 정보를 처리하는 알고리즘(알고리즘 함수)을 분리하는 것
타입과 알고리즘 분리하게 되면 얻는 장점
1. 타입과 알고리즘 간의 불필요한 연관성을 제거
2. 재사용성이 증가
3. 확장의 용이함
새로운 컨테이너 클래스를 만들 때 STL의 컨테이너들의 공통의 인터페이스를 유지한다면 STL의
모든 알고리즘을 사용 할 수 있음