1. NULL 포인터를 리턴하는 new 연산자
: 동적 메모리 할당이 실패로 돌아가는 경우, 옛날 표준의 경우 new 연산자는 NULL을 리턴하였다. 하지만 오늘날의 경우에는 헤더 파일 new에 선언되어 있는 bad_alloc 예외가 전달된다. 하지만 그 형태는 컴파일러 별로 다를 수 있으니 확인이 필요하다.
2. 디폴트 매개 변수와 함수 오버로딩 사용시 주의 사항
: #include<iostream>
int func(int a = 10){
return a+1;
}
int func(void){
return 10;
}
int main(void){
std::cout<<func()<<std::endl;
return 0;
}
위와 같은 경우에는 위의 두 func() 함수가 모두 호출될 수 있는 상황이 되어버린다.
이런 경우 컴파일러는 어떤 함수를 호출해야하는지 결정짓지 못하고 에러를 발생 시킨다.
3. #ifdef, #ifndef 사용법
: http://istudyhard.tistory.com/entry/CC-ifdef-ifndef-전처리기-사용법
4. #if, #endif 사용법
:#include<iostream>
#define DEBUG 1 //테스트 버전 컴파일시
//#define DEBUG 0 //최종 서비스 버전 컴파일시
...
int main(void){
int size;
std::cout<<"~~~~~"<<std::endl;
#if DEBUG==1
std::cout<<"DEBUG MODE"<<std::endl;
#endif
for(int i=0; i < size; i++)
{
.....
}
return 0;
}
5. 클래스 내부에서의 함수 정의 = inline
: 요즘 컴파일러는 코드 최적화를 수행하기 때문에 inline 키워드가 있건 없건 큰 의미가 없다.
따라서 클래스 내부에서 함수를 정의하면 inline 함수로 취급한다고 생각해도 무방.
6. 정보 은닉(Information Hiding)과 캡슐화(Encapsulation)의 차이점
: 정보 은닉 - 객체의 외부에서 객체 내에 존재하는 멤버 변수에 직접 접근을 하지 못하도록 하는 것
: 캡슐화 - 관련있는 데이터와 함수를 하나의 단위로 묶는 것
7. 클래스 정의시, 프로그래머가 직접 정의한 생성자가 하나라도 존재하는 경우, 디폴트 생성자는 자동으로 생성되지 않는다.
따라서 프로그래머가 생성자를 직접 정의하는 경우에는 디폴트 생성자(매개변수가 없는..)를 꼭 같이 정의해주어야 한다.
8. friend 선언
: 전역 함수에 대한 friend 선언
: 클래스 내에 friend void SetX(Counter& c, int val); 과 같은 선언이 있는 경우, 이는 멤버 함수 선언이 아니라 전역함수 void SetX(...)가 해당 클래스 객체의 내부 영역(private)으로의 접근이 가능하도록 허용하는 선언이다.
: 클래스에 대한 friend 선언
: 만약 A라는 클래스 내부에 friend class B;라고 friend 선언이 되어 있다면, 클래스 B는 클래스 A의 private 영역에 대한 접근 권한을 가진다. 단, friend 선언은 단방향성을 지닌다는 것을 유의하라. 즉, 클래스 A는 클래스 B의 private 영역에 대한 접근 권한이 없다.
class A{
private:
int data;
friend class B;
};
class B{
public:
void SetData(A& a, int val){
A.data = val;
}
};
int main(){
A a;
B b;
b.SetData(a, 10);
return 0;
}
: 정보 은닉에 위배되므로 왠만하면 사용 말 것!ㅋ
9. 얕은 복사(Shallow Copy) 깊은 복사(Deep Copy)
: 얕은 복사 - 객체 복사 시에 멤버 변수를 복사하는데 있어 데이터에 대한 주소값만을 복사하는 것(실제 데이터를 공유하게 되는 것) -> 문제의 소지가 됨
: 깊은 복사 - 객체 복사 시에 멤버 변수를 복사하는데 있어 데이터 자체를 복사하는 것
: 디폴트 복사 생성자의 경우 얕은 복사를 하므로, 깊은 복사를 하도록 직접 복사 생성자를 구현해주어야 함
10. const 키워드
: const double PI = 3.14; - const 키워드에 의한 상수 선언 시 반드시 초기화도 함께 진행되어야 한다.
: const 키워드를 사용하여 클래스의 멤버 변수를 상수화한 경우 이를 초기화할때는 멤버 이니셜라이저를 사용하라.
: 멤버 함수 등 함수를 상수화할 경우 이는 해당 함수 내에서 데이터값을 변경할 수 없다. const 키워드 위치는 함수명 뒤에 붙는다.
: 상수화된 함수의 경우 함수 내에서 상수화되지 않은 함수의 호출을 허용하지 않을 뿐만 아니라, 멤버 변수의 포인터를 리턴하는 것도 허용하지 않는다.
: 객체를 상수화하는 경우, 어떤 경로를 통해서든 멤버 변수의 조작이 불가능해진다. 또한 상수화된 멤버 함수만 호출이 가능하다. 일반 함수가 호출되는 경우 컴파일 에러가 발생한다.
: 함수 오버로딩에 있어서 그 함수가 상수함수이냐 아니냐에 따라서 함수 오버로딩의 성립 조건이 된다. 예를 들어 동일 함수명과 매개변수를 가진 두개의 함수중 하나는 상수 다른 하나는 일반 함수라고 할 때, 이를 포함하는 객체가 상수화된 객체일 경우 해당 함수명으로 호출하게 되면 상수함수가 호출되고, 일반 객체일 경우에는 일반 함수가 호출되게 된다.
11. static 키워드
: main 함수가 호출되기도 전에 메모리 공간에 올라가서 초기화된다. 따라서 public으로 선언되어있다면 객체 생성 이전에도 접근이 가능하다.
: 객체의 멤버로 존재하는 것이 아니라 선언 되어 있는 클래스 내에서 직접 접근할 수 있는 권한이 부여되는 것이다.
: static 멤버의 초기화 - int AAA::n=1; //n은 AAA 클래스 내의 public으로 선언된 static 변수 static int n;
: static 멤버변수는 전역변수와 마찬가지로 데이터 영역에 올라간다.
12. 기타 키워드
: explicit - 생성자 앞에 붙어서 묵시적인 호출을 허용하지 않는다. 예를 들어 explicit AAA(int n){...}와 같이 생성자가 explicit 키워드와 함께 선언되는 경우, AAA a1 = 10; 와 같이 묵시적인 생성자 호출을 허용하지 않는다.
: mutable - const로 멤버 함수가 상수화되어 있는 경우 이 함수는 멤버 변수를 변경시키지 못하는데, 만약 멤버 변수가 mutable로 선언이 되어있다면 상수화된 멤버 함수일지라도 해당 데이터의 변경이 가능하다.
13. 파생 클래스의 객체가 생성될 때 파생 클래스의 생성자가 호출되기 이전에 부모 클래스의 생성자가 호출되게 되는데, 이때, 부모 클래스의 생성자가 2개 이상일 경우에는 기본적으로는 매개변수가 없는 '클래스명(void)' 형태의 생성자가 호출된다. 만약 특정 형태의 생성자를 호출하고자 한다면 '멤버 이니셜라이저'를 사용하여 호출할 수 있다. 예를 들어 A라는 클래스를 상속받은 B라는 클래스의 특정 생성자가 A클래스의 특정 생성자를 호출하고자 한다면 그 형태는 다음과 같다.
: B(int j) : A(j){...}
13. A 클래스가 상속 등의 관계가 없는 B 클래스의 객체를 멤버로 가지는 경우, 멤버 객체의 클래스에 대한 생성자가 먼저 호출되고 다음에 A 클래스의 생성자가 호출된다. 이는 객체 멤버가 메모리 공간에 할당되는 과정에서 생성자를 먼저 호출하기 때문이다.
14. 오버라이딩(Overriding)
: A 클래스를 상속 받은 B 클래스에서 func() 함수를 오버라이딩한 경우, 해당 함수를 호출했을 때 어느 클래스에서 해당 함수가 불러지는 어떤 포인터로 함수 호출을 하느냐에 따라 달라질 수 있다. 예를 들어 아래를 보라.
B* b = new B;
b->func(); //B 클래스에서 정의한 func() 호출
위의 경우에는 당연히 B 클래스에서 오버라이딩한 func() 함수가 호출된다. 하지만 만약,
A* a = b; //b의 원 클래스 B가 A 클래스를 상속 받았으므로 문제없음
a->func(); //A 클래스에서 정의한 func() 호출
이처럼 B 클래스의 객체를 가리키고 있는 A 클래스형 포인터인 경우, 가리키고 있는 대상이 B 클래스의 객체일지라도 A 클래스의 포인터이므로 A 클래스에서 정의된 형태의 func() 함수가 호출된다. 즉, 포인터 형에 맞춰서 함수가 호출된다는 말이다. 추가적으로 만약 A 클래스에서 func() 함수가 가상(Virtual) 함수로 선언이 되어있다면 어떤 포인터로 호출을 하던간에 B 클래스에 정의된 func() 함수가 호출되게 된다.
15. 가상(Virtual) 함수
: 가상 함수를 오버라이딩한 함수 또한 가상 함수가 된다. 설령 virtual 키워드를 붙이지 않더라도 말이다.
: 최종적으로 오버라이딩한 함수를 제외하고 그 이전의 구현되었던 가상함수들은 무시된다. 예를 들어 A 클래스에서 가상함수로 구현된 func()이 있다고 할 때, B 클래스에서 오버라이딩하고, B를 상속받은 C 클래스에서 또다시 오버라이딩했다고 했을 때 다음 코드의 결과물을 예측해보자.
B* b = new C;
A* a = b;
a->func();
.....
결과는 당연히 C 클래스에서 호출된 func()의 결과가 된다. 혹여나 A 클래스의 함수를 호출하고 싶다면 다음과 같이 호출하면 된다.
A::func(); or a->A::func();
16. 정적(Static) 바인딩, 동적(Dynamic) 바인딩
: 정적 바인딩 - 컴파일 시간에 호출될 함수가 결정되는 경우.
: B b;
b.func();
: 동적 바인딩 - 실행 시간에 호출될 함수가 결정되는 경우.
: a->func(); //a가 가리키고 있는 대상에 따라서 호출될 함수 혹은 출력 결과가 달라진다.
17. 순수(Pure) 가상 함수와 추상(abstract) 클래스
: 순수 가상 함수 선언
: virtual int getpay()=0; // ~=0 이라는 선언은 순수 가상 함수
: 직접 호출하기 위한 함수가 아닌 오버라이딩 용으로 사용되기 위해 존재하는 경우에 사용
: 추상 클래스
: 하나 이상의 멤버 함수가 순수 가상 함수인 클래스를 칭함
: 추상 클래스는 객체화 할 수 없음
18. virtual 소멸자의 필요성
: http://istudyhard.tistory.com/244
19. 한 클래스에 대해 객체가 생성되면 멤버 변수는 객체 내에 존재한다. 하지만 멤버 함수의 경우에는 한 공간에 존재하면서, 모든 객체가 공유하는 형태를 취한다.
20. 연산자 오버로딩
: Point Point::operator+(const Point& p){
Point temp(x+p.x, y+p.y);
return temp;
}
: 단항 연산자 오버로딩(++, --)
: Point& Point::operator++(){
x++;
y++;
return *this;
}
: 위의 코드를 보면 return을 하지 않아도 원하는 결과를 얻을 수 있을 것 같다. 하지만 실제로 ++p의 연산 후에 아무것도 존재하지 않으므로 이어질 수 있는 연산이 불가능하게 된다. 예를 들어 ++p + p2 와 같은.
: 그리고 리턴 타입이 Point&인 이유는 예를 들어 ++(++p)와 같은 연산이 이루어져야 한다고 할 때, 분명 의도는 멤버 변수에 대해 각각 2만큼 증가시키는 것일진데, 리턴 타입이 Point인 경우에는 p의 복사본이 리턴되기때문에 p의 값은 1만 증가하게 된다. 그리고 복사본에 대해서도 1이 증가한다. 하지만 p의 2 증가를 원했던 원래의 의도와 다른 결과를 얻게 된다.
: 선(先) 연산과 후(後) 연산의 구분
: ++p와 p++는 다른 연산이다. 하지만 연산자 오버로딩 규칙을 따르게 되면 사실상 형태가 같다. 따라서 이를 구분하기 위해서 후 연산에 대해서는 오버로딩시에 키워드 int를 매개 변수로 선언을 해주면 된다. 그러면 컴파일러는 제대로 알아먹고 돌아가게 된다.
: Point Point::operator++(){
Point temp(x, y); //후 연산, 증가하기 전의 기존값을 보존 //Point temp(*this); <- better
x++;
y++;
return temp;
}
: 두 개의 피연산자(변하지 않아야 함)를 연산하여 새로운 하나의 데이터를 만들어 내는 연산
: 전역 연산자 오버로딩을 사용해야 한다.
: Point Point::operator+(int val){
Point temp(x+val, y+val);
return temp; //return Point(x+val, y+val);
}
Point operator+(int val, Point& p){ //전역 연산자 오버로딩
return p+val;
}
: 위의 코드는 덧셈(+)에 대한 교환 법칙이 성립하도록 구현
21. 템플릿(Template)
: template <typename T> //함수 템플릿
void ShowData(T a, T b){...}
: template <typename T1, typename T2> //함수 템플릿
void ShowData(T1 a, T2 b){...}
: 클래스 템플릿 기반으로 객체를 생성할 때는 자료형을 명시적으로 선언해야 한다. Data<int> d1(10);
이는 객체 생성 과정에서 자료형을 판단할 수 있는 생성자 호출 과정보다 메모리 공간 할당이 먼저 일어나기 때문이다.
: #include <iostream>
template <typename T>
class Data{
T data;
public:
Data(T d);
void SetData(T d);
T GetData();
};
template <typename T>
Data<T>::Data(T d){
data = d;
}
template <typename T>
void Data<T>::SetData(T d){
data = d;
}
template <typename T>
T Data<T>::GetData(){
return data;
}
: 템플릿 함수는 함수 템플릿을 기반으로 해서 만들어지는 실제 호출이 가능한 함수들을 말한다. 즉, 함수 템플릿에 대한 자료형이 결정되고 이를 바탕으로 호출할 함수의 형태가 컴파일러에 의해 결정이 된 이후의 함수를 말한다.
: int Add(int a, int b){
return a+b;
}
22. 예외 처리
: try, catch, throw
: int func(double d) throw (int, double, char*){...} // int, double, char* 형 예외가 전달될 수 있음, 이외 abort()
: int func(double d) throw() //어떤 예외도 전달하지 않음, 이로써 abort() 호출의 여지가 없어짐, 사실 상 문제해결 X
: 객체를 사용하면 예외 상황이 발생한 원인에 대한 정보를 보다 자세하게 담을 수 있다.
'old drawer > C, C++, MFC' 카테고리의 다른 글
[MFC] Window 창 크기 조정 (1) | 2014.01.08 |
---|---|
[C/C++] ReadFile, WriteFile (0) | 2013.12.31 |
[Effective C++ 요약] 11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자. (0) | 2013.12.12 |
[Effective C++ 요약] #7 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하라. (0) | 2013.12.02 |
[C/C++] #ifdef, #ifndef 전처리기 사용법 (1) | 2013.09.09 |