윤성우 열혈 C++ 프로그래밍

Date:     Updated:

카테고리:

Chapter 01 C언어 기반의 C++ 1

01-1 printf와 scanf를 대신하는 입출력 방식

  • C++ 에서는 프로그래머가 정의하는 헤더파일의 선언이 아닌, 표준 헤더파일의 선언에서는 확장자를 생략하기로 약속되어 있다
  • 확장자를 생략하기로 한 첫 번째 이유는 과거의 표준 라이브러리와 새로운 표준 라이브러리의 구분을 위해서 이다
  • 확장자를 생략하기로 한 두 번째 이유는 새로운 표준 라이브러리를 사용하는 형태로 소스코드를 쉽게 변경할 수 있도록 하기 위해서이다
  • C++의 지역변수 선언은 함수 내 어디든 삽입이 가능하다

01-2 함수 오버로딩

  • C언어 에서는 함수 오버로딩이 안되고, C++에서는 가능한 이유는 함수를 호출할 때 함수를 찾는 방법이 다르기 때문이다

01-3 매개변수의 디폴트값

  • 매개변수의 디폴트값은 선언 부분에만 표현하면 된다 ( 컴파일을 위해 )
  • int YourFunc(int num1=12, int num, int num3) { ... } 이 함수의 매개변수에 지정되어 있는 디폴트 값 12가 의미를 가지려면, num1이 아닌, num2와 num3에만 인자를 전달할 수 있어야 한다 그런데 그것이 가능한가? 불가능하다! 함수에 전달되는 인자가 왼쪽에서부터 오른쪽으로 채워지기 때문이다

01-4 인라인(inline) 함수

  • 프로그램 코드라인 안으로 들어가 버린 함수 라는 뜻이다
  • 매크로 함수의 단점은 정의하기가 어렵다
  • 함수의 몸체부분이 함수호출 문장을 완전히 대체했을 때 함수가 인라인화 되었다 라고 표현한다
  • 매크로를 이용한 함수의 인라인화는 전처리기에 의해서 처리되지만, 키워드 inline을 이용한 함수의 인라인화는 컴파일러에 의해서 처리가 된다
  • 매크로 함수의 장점은 자료형에 의존적이지 않는 함수가 된다는 점인데, 인라인 함수 또한 함수 오버로딩을 통해서 해당 함수 구현이 가능하나, 그렇게 되면 한번만 정의하면 되는 매크로 함수의 장점과 거리가 멀어진다

01-5 이름공간(namespace)에 대한 소개

  • 이름공간은 BestCom 회사에서 정의한 함수와 ProgCom 회사에서 정의한 함수의 이름이 같으면 안되기 때문에 생겨난 개념이다
  • :: 를 가리켜 범위지정 연산자 라고 하며, 이름공간을 지정할 때 사용한다
namespace BestComImpl
{
	void SimpleFunc(void);
}
namespace BestComImpl
{
	void PrettyFunc(void);
}
void BestComImpl::SimpleFunc(void)
{
	PrettyFunc(); // 동일한 이름 공간
}
  • 위의 코드 처럼 동일한 이름공간에 정의된 함수를 호출할 때에는 이름공간을 명시할 필요가 없으며 이름공간은 둘 이상의 영역으로 나뉘어서 선언할 수 있다
  • C++의 기본문법을 익힐 때에는, 간단한 예제를 직접 작성해보면서 결과를 확인하는 것이 좋다
namespace Parent
{
	int num=2;

	namespace SubOne
	{
		int num=3;
	}

	namespace SubTwo
	{
		int num=4;
	}
}
  • 위의 코드처럼 이름공간은 다른 이름공간 안에 삽입될 수 있다
int main(void)
{
	using Hybrid::HybFunc; // 이 선언은 HybFunc를 이름공간 Hybrid에서 찾으라는 일종의 선언이다
	HybFunc();
	return 0;
}
  • 위의 using Hybrid::HybFunc 코드는 지역변수의 선언과 마찬가지로 선언된 이후부터 효력을 발휘하며, 선언된 지역을 벗어나면, 선언의 효력을 잃게 된다 따라서 프로그램 전체영역에 효력을 미치게 하려면 전역변수와 마찬가지로 함수 밖에 선언을 해야 한다
  • using 선언을 하는 것이 귀찮다면 using namespace std; 와 같이 이름공간 std에 선언된 모든 것에 대해 이름공간 지정을 생략할 수 있다 이렇게 선언을 해버리면, 그만큼 이름충돌이 발생할 확률은 상대적으로 높아진다 따라서 상황을 잘 판단해 사용하자
  • namespace ABC=AAA::BBB::CCC;와 같이 이름에 별칭을 줄 수 있다
int SimpleFunc(void)
{
	int val=20;
	val+=3;
	::val+=7; // 범위 지정 연산자를 이용해 전역변수 val의 값을 증가시키고 있다
}


Chapter 02 C언어 기반의 C++ 2

02-1 Chapter 02의 시작에 앞서

  • const 키워드의 의미, 실행중인 메모리 공간, Callb-by-value와 Call-by-reference 개념의 차이점을 설명해보자

02-2 새로운 자료형 bool

  • true와 false가 정의되기 이전에는 참을 표현하기 위해서 숫자 1을, 거짓을 표현하기 위해서 숫자 0을 사용했기 때문에, 이 둘을 출력하거나 정수의 형태로 형 변환하는 경우에 각각 1과 0으로 변환되도록 정의되어 있을 뿐이다

02-3 참조자의 이해

  • 이미 선언된 변수의 앞에 & 연산자가 오면 주소 값의 반환을 명령하는 뜻이 되지만, 새로 선언되는 변수의 이름 앞에 등장하면, 이는 참조자의 선언을 뜻하는 게 된다
  • 여러 개의 참조자를 선언하는 것도 가능하다
  • 참조자는 무조건 선언과 동시에 변수를 참조하도록 해야 한다 여기서 말하는 변수의 범위에는 배열요소도 포함된다
int num=12;
int *ptr=#
int **dptr=&ptr;

int &ref=num;
int *(&pref)=ptr;
int **(&dpref)=dptr;
  • 위의 코드와 같이 포인터 변수의 참조자 선언도 & 연산자를 하나 더 추가하는 형태로 진행이 된다

02-4 참조자(Reference)와 함수

  • Call by reference에서 주소 값이 전달 되었다는 사실이 중요한게 아니라, 주소 값이 참조의 도구로 사용되었다는 사실이 중요한 것이다
  • void SwapByRef2(int &ref1, int &ref2) 에서 매개변수 선언시 초기화가 이뤄지지 않은 것이 아니라, 함수호출 시 전달되는 인자로 초기화를 하겠다는 의미이다
  • 즉 Call by reference의 구현 방법에는 참조자를 이용하는 방법과 주소 값을 이용하는 방법, 이렇게 두가지가 상존한다
  • 함수 내에서, 참조자를 통한 값의 변경을 진행하지 않을 경우, 참조자를 const로 선언해서, 함수의 원형만 봐도 값의 변경이 이뤄지지 않음을 알 수 있게 하자
  • 반환형이 참조형인 경우, 반환 값을 무엇으로 저장하느냐에 따라서 그 결과에 차이가 있으므로, 적절한 선택을 해야한다
  • 반환형이 기본자료형으로 선언된 함수의 반환 값은 반드시 변수에 저장해야 한다 반환 값은 상수나 다름없기 때문이다 즉 지역변수를 참조형으로 반환하는 일은 없어야 한다
const int num=20;
const int &ref=num; // 상수화된 변수에 대한 참조자 선언은 이와 같이 해야한다
const int &ref=50; // const 참조자는 이와 같이 상수도 참조가 가능하다
  • const 선언에 의해서 만들어진 변수를 가리켜 상수화된 변수라 한다
  • const int &ref=30; 만약 이와 같은 선언을 하게되면, C++에서는 const 참조자를 이용해서 상수를 참조할 때 임시변수 라는 것을 만든다 그리고 이 장소에 상수 30을 저장하고선 참조자가 이를 참조하게끔 한다 이 개념 덕분에 Adder(3, 4) 와 같은 함수 호출이 가능하다

02-5 malloc과 free를 대신하는 new와 delete

  • new와 delete를 사용하면 malloc 사용시 불편했던 단점인 할당 대상의 정보를 무조건 바이크 크기단위로 전달해야 한다는 것적절한 형변환을 해줘야 한다는점이 커버된다

02-6 C++에서 C언어의 표준함수 호출하기

  • c를 더하고 .h를 빼라 ex) #include <stdio.h> -> #include<cstdio>
  • 이름공간 std 내에 선언되어 있다는 사실만 제외하면, C++의 헤더는 C언어의 헤더와 별 차이가 없다


Chapter 03 클래스의 기본

03-1 C++ 에서의 구조체

  • 연관 있는 데이터를 하나로 묶으면, 프로그램의 구현 및 관리가 용이하다
  • C++에서는 별도의 typedef 선언 없이도 구조체 변수 선언이 가능하다
struct Car
{
	enum
	{
		ID_LEN = 20,
		MAX_SPD = 200,
		FUEL_STEP = 2,
		ACC_STEP = 10,
		BRK_STEP = 10
	};
};
  • 위와 같이 열거형 enum을 이용해서 구조체 내에서만 유효한 상수를 정의하는게 가능하다
namespace CAR_CONST
{
	enum
	{
		ID_LEN = 20,
		MAX_SPD = 200,
		FUEL_STEP = 2,
		ACC_STEP = 10,
		BRK_STEP = 10
	};
};
struct Car
{
	char gamerID[CAR_CONST::ID_LEN];
}
  • 위와 같이 이름공간을 이용하면, 몇몇 구조체들 사이에서만 사용하는 상수들을 선언할 때 좋다
  • 구조체 내에 정의된 함수의 수가 많거나 그 길이가 길다면, 구조체 밖으로 함수를 빼낼수 있다
  • 구조체 안에 함수가 정의되어 있으면, 해당 함수를 인라인으로 처리해라 라는 의미를 갖게된다 따라서 함수를 구조체 밖으로 빼내면 이러한 의미가 사라지기 때문에 인라인의 의미를 그대로 유지하려면 inline을 이용해서 인라인 처리를 명시적으로 지시해야 한다

03-2 클래스(Class)와 객체(Object)

  • 접근제어 지시자의 뒤에는 세미콜론이 아닌 콜론이 붙는데, 이는 접근제어 지시자가 특정 위치 정보를 알리는 레이블(라벨)이기 때문이다
  • 구조체와 클래스는 변수의 성격만 지니는것이 아니기 때문에 변수라는 표현을 대신해서 객체 라는 표현을 사용한다
  • 클래스를 구성하는 변수를 가리켜 멤버변수라 하고 클래스를 구성하는 함수를 가리켜 멤버함수라 한다
  • 멤버함수의 정의부분을 컴파일 하는데도 클래스의 선언 정보가 필요하다 멤버함수에서 접근하는 변수의 존재유무를 확인해야 하기 때문이다
  • 컴파일러는 파일 단위로 컴파일을 한다 따라서 인라인 함수는 클래스의 선언과 동일한 파일에 저장되어서 컴파일러가 동시에 참조할 수 있게 해야 한다

03-3 객체지향 프로그래밍의 이해

  • 객체지향 프로그래밍은 현실에 존재하는 사물과 대상, 그리고 그에 따른 행동을 있는 그대로 실체화 시키는 형태의 프로그래밍이다
  • 객체는 하나 이상의 상태 정보(데이터)와 하나 이상의 행동(기능)으로 구성된다
  • 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 방법은 함수호출을 기반으로 한다 또한 이러한 형태의 함수호출을 가리켜 메시지 전달 이라고 한다


Chapter 04 클래스의 완성

04-1 정보은닉

  • 클래스의 멤버변수가 public으로 선언되면 해당 클래스의 변수를 구조체 변수를 초기화하듯이 초기화가 가능하다
  • 프로그래밍 덕목중 하나는 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야 하고, 또 실수를 했을 때, 실수가 쉽게 발견되도록 해야 한다
  • 멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버변수의 접근을 유도하는 것이 바로 정보은닉이며, 이는 좋은 클래스가 되기 위한 기본조건이다
  • Get(), Set() 함수들을 가리켜 엑세스 함수라고 한다
  • int GetX() const; 이 함수는 const 함수 이며 함수 내에서는 멤버변수에 저장된 값을 변경하지 못한다 또한 const 함수 내에서는 const가 아닌 함수의 호출이 제한된다
  • const 참조자를 이용해서는 const 함수만 호출이 가능하다

04-2 캡슐화

  • 캡슐화는 어려운 개념이다 왜냐하면 캡슐화의 범위를 결정하는 일이 쉽지 않기 때문이다
  • 캡슐화는 기본적으로 정보은닉을 포함하는 개념이라고 이야기 한다

04-3 생성자(Constructor)와 소멸자(Destructor)

  • 생성자를 이용해서 객체를 생성하기 위해 다음과 같이 문장을 구성하면 안 된다 SimpleClass sc1(); 대신 다음과 같이 구성을 해야한다 SimpleClass sc1 왜냐하면 컴파일러는 첫번째 예시와 같은 문장을 만나면 이것이 객체 생성문인지 함수의 원형선언 인지를 구분할 수 없게 되기 때문이다
  • Rectangle 객체를 생성하는 과정에서 Point 클래스의 생성자를 통해서 Point 객체를 초기화할 수 없을까? 이니셜라이저를 사용하면 가능하다
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
	: upLeft(x1,y1), lowRight(x2, y2) // 이니셜라이저를 이용해 Point 객체를 초기화 해주고 있다
{
	// empty
}
  • 객체의 생성과정은 1단계 : 메모리 공간의 할당 -> 2단계(필수아님) : 이니셜라이저를 이용한 멤버변수(객체)의 초기화 -> 3단계 : 생성자의 몸체부분 실행
  • 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다 즉 const 멤버변수를 이니셜라이저를 이용해 초기화 할 수 있다
  • const 변수와 const 상수는 같은 의미로 사용된다
  • 이니셜라이저를 이용하면 참조자도 멤버변수로 선언될 수 있다
  • 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다
  • AAA * ptr = (AAA*)malloc(sizeof(AAA)); 에서 malloc 함수호출 시 실제로는 AAA 클래스의 크기정보만 바이트 단위로 전달되기 때문에 생성자가 호출되지 않는다
  • private 생성자는 객체의 생성방법을 제한하고자 하는 경우에 매우 유용하게 사용이 된다
  • 소멸자의 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다

04-4 클래스와 배열 그리고 this 포인터

  • 객체가 배열을 통해 선언 되더라도 생성자는 호출된다 단 호출할 생성자를 별도로 명시하지 못한다(생성자에 인자를 전달하지 못한다)
  • 저장의 대상을 객체로 하느냐, 객체의 주소 값으로 하느냐를 결정해야 하기 때문에 객체 배열과 객체 포인터 배열의 차이점을 정확히 이해해야 한다
  • this는 객체자신의 주소 값을 의미한다는 사실을 인지하자 이렇듯 this 포인터는 그 주소 값과 자료형이 정해져 있지 않은 포인터이다
  • 객체의 포인터를 가지고는 지역변수에 접근이 불가능하다
  • SelfRef& Adder(int a) 는 객체 자신의 포인터가 아닌 객체 자신이 반환된다
  • 대입 연산자의 왼편에 참조자의 선언이 오거나 반환형으로 참조형이 선언되면, 그 때 전달되는 정보를 표현하기 위해서 참조의 정보 또는 참조 값 이라는 표현을 사용한다


Chapter 05 복사 생성자(Copy Constructor)

05-1 복사 생성자와의 만남

  • SoSimple sim2 = sim1; 이 문장은 SoSimple sim2(sim1); 과 같은 형태로 묵시적 변환이 일어나는데 이러한 묵시적 변환을 막을수 있는 키워드가 explicit 이다
  • 묵시적 변환이 많이 발생하는 코드일수록 코드의 결과를 예측하기가 어려워지기 때문에 explicit 키워드를 적극 활용하자
  • 복사생성자의 매개변수가 참조형이 아닐경우 무한루프에 빠진다

05-2 깊은 복사와 얕은 복사

  • 디폴트 복사 생성자는 멤버 대 멤버의 복사를 진행한다 그리고 이러한 방식의 복사를 가리켜 얕은 복사 라고 한다

05-3 복사 생성자의 호출시점

  • 복사 생성자가 호출 되는 시점은 크게 세가지로 구분된다 첫째 새로운 객체를 초기화 하는 경우, 둘째 객체를 인자로 전달하는 경우, 셋째 객체를 반환하되 참조형으로 반환하지 않는 경우
  • 함수가 값을 반환하면 별도의 메모리 공간이 할당되고 이 공간에 반환 값이 저장된다
  • 임시객체는 다음 행으로 넘어가면 바로 소멸되어 버린다
  • 참조자에 참조되는 임시객체는 바로 소멸되지 않는다


Chapter 06 friend와 static 그리고 const

06-1 const와 관련해서 아직 못다한 이야기

  • const SoSimple sim(20); 이 코드의 의미는 이 객체의 변경을 허용하지 않겠다
  • const 선언유무도 함수 오버로딩의 조건에 해당이 된다
  • 일반 객체를 대상으로 SimpleFunc 함수를 호출하면 일반 멤버함수가, const 객체를 대상으로 SimpleFunc 함수를 호출하면 const 멤버함수가 호출된다

06-2 클래스와 함수에 대한 friend 선언

  • friend 선언은 클래스 내에 어디든 위치할 수 있다 private 영역에 존재하든, public 영역에 존재하든 상관없다
  • friend class Girl; 이 코드는 두 가지를 동시에 선언하는 셈이다 첫째 Girl은 클래스의 이름이다(전방선언) 둘째 Girl클래스를 friend로 선언한다
  • friend 선언은 필요한 상황에서 극히 소극적으로 사용해야 한다
  • 전역함수를 대상으로도, 클래스의 멤버함수를 대상으로도 friend 선언이 가능하다
  • friend 선언을 위해서 별도의 함수원형을 선언할 필요는 없다

06-3 C++에서의 static

  • 전역변수에 선언된 static의 의미는 선언된 파일 내에서만 참조를 허용하겠다는 의미
  • int SoSimple::simobjCnt = 0; 이 코드와 같이 static 변수의 초기화는 생성자가 아닌, 클래스 외부에서 해야한다
  • simObjCnt에 접근을 허용하는 객체와 cmxObjCnt에 접근을 허용하는 객체가 구분되기 때문에 각각의 변수에 다른 영역에서 잘못 접근하는 일이 발생하지 않는다
  • static 멤버가 private으로 선언되면, 해당 클래스의 객체들만 접근이 가능하지만, public으로 선언되면, 클래스의 이름 또는 객체의 이름을 통해서 어디서든 접근이 가능하다
  • public static 멤버에 접근할 때에는 클래스의 이름을 이용해서 접근하는 것이 좋다
  • static 멤버함수가 멤버변수에 접근하면 안되는 이유 첫번째는 객체의 멤버가 아닌데, 어떻게 멤버변수에 접근을 하겠는가? 두번째는 객체생성 이전에도 호출이 가능하다 그런데 어떻게 멤버변수에 접근이 가능하겠는가? 세번째는 멤버변수에 접근을 한다고 치자 그렇다면 어떤 객체의 멤버변수에 접근을 해야겠는가?
  • static 멤버함수 내에서는 static 멤버변수와 static 멤버함수만 호출이 가능하다
  • const static int RUSSIA = 1707540; 이 코드와 같이 const static으로 선언되는 멤버변수는 선언과 동시에 초기화가 가능하다 ( 원래 const 멤버변수의 초기화는 이니셜라이저를 통해야만 가능하다 )
  • mutable 키워드는 const 함수 내에서의 값의 변경을 예외적으로 허용한다


Chapter 07 상속(Inheritance)의 이해

07-1 상속에 들어가기에 앞서

  • 기능의 처리를 실제로 담당하는 클래스를 가리켜 컨트롤 클래스 또는 핸들러 클래스라 한다
  • 프로그램 사용자의 요구에 다음과 같이 이야기 하는 일은 없어야 한다, 그 기능을 변경하려면 프로그램을 거의 처음부터 다시 만들다시피 해야 하는데요

07-2 상속의 문법적인 이해

UnivStudent(char* myname, int myage, char * mymajor)
	: Person(myage, myname)
{
	strcpy(major, mymajor);
}
  • 위의 코드 처럼 UnivStudent 클래스와 같이 상속받는 클래스는 이니셜라이저를 이용해서 상속하는 클래스의 생성자 호출을 명시할 수 있다
  • 부모 멤버 변수가 private이면 자식 클래스에 해당 멤버변수가 상속이 되긴 하지만 직접 접근이 불가능하다 이렇듯 정보의 은닉은 하나의 객체 내에서도 진행이 된다
  • 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출된다 즉 유도 클래스의 객체생성 과정에서 기초클래스의 생성자 호출, 유도 클래스의 생성자 호출 이렇게 생성자가 총 두 번 호출된다
  • 클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야 한다 라는 사실을 명심하자
  • 생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제해야 한다 라는 사실을 명심하자 더불어 부모 생성자에서 할당한 메모리 공간은 부모 소멸자에서 해제하고 자식 생성자에서 할당한 메모리 공간은 자식 소멸자에서 해제하자

07-3 protected 선언과 세 가지 형태의 상속

  • protected 선언은 private와 public에 비해 그리 많이 사용되지 않는데 그이유는 기초 클래스와 이를 상속하는 유도 클래스 사이에서도 정보은닉은 지켜지는 게 좋기 때문이다
  • 상속의 대부분은 public 상속이다

07-4 상속을 위한 조건

  • 상속관계가 성립하려면 기초 클래스와 유도 클래스간에 IS-A 관계가 성립해야 한다
Police(int bnum, int bcuff)
	: handcuffs(bcuff)
{
	if(bnum>0)	// 첫 번째 인자로 0이 전달되면, pistol 멤버변수는 NULL로 초기화되어, 총의 사용이 불가능해진다
		pistol = new Gun(bnum);
		..
}
  • 위의 코드는 pistol을 NULL로 초기화함으로써 권총을 소유하지 않은 경찰을 매우 간단히 표현했다
  • 대부분의 경우에 있어 IS-A와 HAS-A 이외의 관계를 상속으로 표현하지 않는다


Chapter 08 상속과 다형성

08-1 객체 포인터의 참조관계

  • PermanentWorker::GetPay() 이는 오버라이딩 된 기초 클래스의 GetPay 함수를 호출하는 구문이다 이렇듯, 클래스의 이름을 명시함으로 인해서 기초 클래스의 오버라이딩 된 함수를 호출할 수 있다
  • 기초 클래스와 동일한 이름의 함수를 유도 클래스에서 정의한다고 해서 무조건 함수 오버라이딩이 되는 것은 아니다 매개변수의 자료형 및 개수가 다르면, 이는 함수 오버로딩이 되어, 전달되는 인자에 따라서 호출되는 함수가 결정된다 즉 함수 오버로딩은 상속의 관계에서도 구성이 될 수 있다

08-2 가상함수(Virtual Function)

  • C++ 컴파일러는 포인터 연산의 가능성 여부를 판단할 때, 포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다
  • 가상함수의 선언은 virtual 키워드의 선언을 통해서 이뤄진다 그리고 이렇게 가상함수가 선언되고 나면, 이 함수를 오버라이딩 하는 함수도 가상함수가 된다
  • 함수가 가상함수로 선언되면, 해당 함수호출 시, 포인터의 자료형을 기반으로 호출대상을 결정하지 않고, 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정한다
  • 적용하고픈 공통규약을 모아서 부모클래스에 정의하자
  • 클래스 중에서는 객체생성을 목적으로 정의되지 않는 클래스도 존재한다 이를위해 가상함수를 순수 가상함수로 선언하여 객체의 생성을 문법적으로 막는것이 좋다
  • 순수 가상함수란 함수의 몸체가 정의되지 않은 함수
  • 순수 가상함수의 이점 두가지는 첫째 잘못된 객체의 생성을 막을수 있다는 점 둘째 실제로 실행이 되는 함수가 아닌 함수를 보다 명확히 명시하는 효과이다
  • 하나 이상의 멤버함수를 순수 가상함수로 선언한 클래스를 가리켜 추상 클래스 라고 한다
  • 다형성을 한마디로 정의하면 문장은 같은데 결과는 다르다

08-3 가상 소멸자와 참조자의 참조 가능성

  • 기초클래스의 소멸자만 virtual로 선언하면, 이를 상속하는 유도 클래스의 소멸자들도 모두 가상 소멸자로 선언이 된다
  • C++에서, AAA형 참조자는 AAA 객체 또는 AAA를 직접 혹은 간접적으로 상속하는 모든 객체를 참조할 수 있다


Chapter 09 가상(Virtual)의 원리와 다중상속

09-1 멤버함수와 가상함수의 동작원리

  • 객체가 생성되면 멤버변수는 객체 내에 존재하지만, 멤버함수는 메모리의 한 공간에 별도로 위치하고선, 이 함수가 정의된 클래스의 모든 객체가 이를 공유하는 형태를 취한다
  • 한 개 이상의 가상함수를 포함하는 클래스에 대해서는 컴파일러가 가상함수 테이블이란 것을 만든다
  • 오버라이딩 된 가상함수의 주소정보는 유도 클래스의 가상함수 테이블에 포함되지 않는다 때문에 오버라이딩 된 가상함수를 호출하면, 무조건 가장 마지막에 오버라이딩을 한 유도 클래스의 멤버함수가 호출되는 것이다
  • main 함수가 호출되기 이전에 가상함수 테이블이 메모리 공간에 할당되며 가상함수 테이블은 객체의 생성과 상관없이 메모리 공간에 할당된다 이는 가상함수 테이블이 멤버함수의 호출에 사용되는 일종의 데이터이기 때문이다
  • 가상함수를 하나이상 멤버로 지니고 있는 클래스의 객체에는 가상함수 테이블의 주소 값이 저장된다 이 주소 값은 우리가 직접 참조할 수 있는 주소 값은 아니다 다만 내부적으로 필요에 의해서 참조되는 주소 값일 뿐이다

09-2 다중상속(Multiple inheritance)에 대한 이해

  • 다중상속을 받는 클래스가 Base 클래스를 딱 한번만 상속하는것이 가능하게 해주는 것이 가상 상속이다


Chapter 10 연산자 오버로딩 1

10-1 연산자 오버로딩의 이해와 유형

  • 멤버함수 기반으로 오버로딩 된 함수가 전역함수 기반으로 오버로딩 된 함수보다 우선시 되어 호출된다
  • =, (), [], -> 연산자는 객체를 대상으로 진행해야 의미가 통하기 때문에 멤버함수 기반으로만 오버로딩이 가능하다
  • 연산자를 오버로딩 할 때에는 연산자의 본래 의도를 가급적 충실히 반영해서, 연산자의 오버로딩으로 인해서 발생하는 혼란스러운 부분을 최소화해야 한다
  • 연산자 오버로딩시 매개변수의 디폴트 값 설정은 허용되지 않는다
  • 피연산자의 종류에 따라서 연산의 방식이 달라지기 때문에 연산자 오버로딩이라 불리는 것이다

10-2 단항 연산자의 오버로딩

++pos;
pos.operator++();	// pos가 멤버함수 형태로 호출될때 이렇게 해석된다
operator++(pos);	// pos가 전역함수 형태로 호출될때 이렇게 해석된다

Point& operator++()
{
	xpos+=1;
	ypos+=1;
	return *this;	// 객체 자신을 참조할 수 있는 참조 값을 반환하는 이유는 일반적인 ++ 연산자와 같은 연산을 중복수행 하기위해
}
  • int 키워드를 이용해서 후위연산에 대한 함수를 전위연산에 대한 함수와 구분하고 있다
const Point operator++(int)
{
	const Point retobj(xpos, ypos); // 후위연산시 복사본의 값이 변경되면 안되기 때문에 const로 선언
	xpos += 1;
	ypos += 1;
	return retobj;
}
  • const 객체를 대상으로 참조자를 선언할 때에는 참조자도 const로 선언해야 한다

10-3 교환법칙 문제의 해결

Point operator*(int times, Point& ref) // 교환법칙이 성립되게 함수를 정의
{
	return ref*times;
}

10-4 cout, cin 그리고 endl의 정체

class ostream
{
public:
	void operator<< (ostream& (*fp)(ostream &ostm)) // endl 함수를 인자로 전달받을 수 있도록 정의되어 있다
}
ostream& endl(ostream &ostm)
{
	ostm<<'\n';
	fflush(stdout);
	retrun ostm;
}
  • cout과 endl의 사용을 위해서 지역적으로 using 선언이 가능하다
  • << 연산자는 왼쪽에서 오른쪽으로 진행이 된다
  • cout은 ostream 클래스의 객체이다
  • ostream은 이름공간 std 안에 선언되어 있으며, 이의 사용을 위해서는 헤더파일 을 포함해야 한다


Chapter 11 연산자 오버로딩 2

11-1 반드시 해야 하는 대입 연산자의 오버로딩

fob1=fob2=fsrc; // 대입 연산자는 오른쪽에서 왼쪽으로 진행된다 따라서 이 문장이 실행된다는 사실을 통해서 디폴트 대입 연산자의 반환형을 유추할 수 있다
  • 객체간의 대입연산은 C언어의 구조체 변수간의 대입연산과 본질적으로 다르다 이는 단순한 대입연산이 아닌, 대입 연산자를 오버로딩 한 함수의 호출이기 때문이다
  • 디폴트 대입 연산자는 깊은 복사를 진행하도록 정의해야하며 메모리 누수가 발생하지 않도록, 깊은 복사에 앞서 메모리 해제의 과정을 거쳐야 한다
  • 유도 클래스의 생성자에는 아무런 명시를 하지 않아도 기초 클래스의 생성자가 호출되지만, 유도 클래스의 대입 연산자에는 아무런 명시를 하지 않으면, 기초 클래스의 대입 연산자가 호출되지 않는다
  • 유도클래스의 대입 연산자 정의에서, 명시적으로 기초 클래스의 대입 연산자 호출문을 삽입하지 않으면, 기초 클래스의 대입 연산자는 호출되지 않아서, 기초 클래스의 멤버변수는 멤버 대 멤버의 복사 대상에서 제외된다
  • 위의 이유 때문에 유도 클래스의 대입 연산자를 정의해야 하는 상황에 놓이면, 기초 클래스의 대입 연산자를 명시적으로 호출해야 한다
  • 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다
  • 생성자의 몸체부분에서 대입연산을 통한 초기화를 진행하면, 선언과 초기화를 각각 별도의 문장에서 진행하는 바이너리 코드가 생성된다

11-2 배열의 인덱스 연산자 오버로딩

  • 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 유일성이 보장되어야 하기 때문에 깊은 복사가 진행되도록 클래스를 정의할 것이 아니라 복사 생성자와 대입 연산자를 private 멤버로 둠으로써 복사와 대입을 원천적으로 막는 것이 좋은 선택이다
  • 객체의 저장은 객체간의 대입연산을 기반으로 한다
  • arr[0]=new Point(3, 4); 이렇듯 객체의 주소 값을 저장할 경우, 깊은 복사나 얕은 복사냐 하는 문제를 신경 쓰지 않아도 된다

11-3 그 이외의 연산자 오버로딩

  • ->, * 이 두 연산자는 피 연산자가 하나인 단한 연산자의 형태로 오버로딩 된다는 특징만 기억하자
  • 라이브러리의 일부로 포함되어 있는 스마트 포인터를 활용하는 경우가 많다
  • Adder 클래스와 같이 함수처럼 동작하는 클래스를 가리켜 펑터라 한다 또한 함수 오브젝트 라고도 불린다
  • 펑터로 무엇이 전달되느냐에 따라서 정렬의 기준이 바뀌게 프로그램을 코딩할 수 있다
  • A형 객체가 와야 할 위치에 B형 데이터가 왔을 경우, B형 데이터를 인자로 전달받는 A형 클래스의 생성자 호출을 통해서 A형 임시객체를 생성한다 ( 일치하지 않는 자료간의 대입연산시 오버로딩 활용방법 )
  • 객체를 기본 자료형 데이터로 형 변환하는 것도 가능하다
  • operator int는 다음의 의미로 해석된다 int형으로 형 변환해야 하는 상황에서 호출되는 함수이다


Chapter 12 String 클래스의 디자인

12-1 C++의 표준과 표준 string 클래스

  • C++ 새로운 표준안은 기존의 C++ 문법을 포함하는 상태에서 문법의 일부를 확장하고, 라이브러리의 상당 부분을 추가하는 형태로 완성된다

12-2 문자열 처리 클래스의 정의

String& String::operator+=(const String& s) // 간단하게 구현해본 += 연산자 오버로딩
{
	*this=*this+s;
	return *this;
}


Chapter 13 템플릿

13-1 템플릿에 대한 이해와 함수 템플릿

  • 함수 템플릿은 함수를 만드는 도구가 된다
  • Add<int>(15, 20) 이것이 의미하는 바는 T를 int로 해서 만들어진 Add 함수를 호출한다
  • 함수 템플릿으로 한번 함수를 만들면 그 다음에는 만들어진 함수를 호출할 뿐 새로 함수를 만들지 않는다
  • 정의를 가리켜 함수 템플릿 이라 하며 컴파일러가 만들어 내는 함수들을 가리켜 템플릿 함수 라고 한다
  • 템플릿이 정의되어도 일반함수를 정의할 수 있다 템플릿 함수와 일반함수는 구분이 되기 때문이다
  • 일반함수가 정의되어 있지 않으면 템플릿 함수가 호출되는데, 일반함수가 정의되어 있으면 일반함수가 호출된다
  • 상황에 따라서 템플릿 함수의 구성방법에 예외를 둘 필요가 있는데, 이 때 사용되는 것이 함수 템플릿의 특수화이다
// 이 함수에 의해서 호출되는 함수는 const 함수이다
Max("Simple", "Best")
// 이 함수에 의해서 호출되는 함수는 일반 함수 이다
Max(str1, str2)

13-2 클래스 템플릿

  • 정의된 템플릿을 가리켜 클래스 템플릿 이라 하며, 컴파일러가 만들어 내는 클래스를 가리켜 템플릿 클래스라 한다
  • 저장의 대상이 다르다는 이유만으로 유사한 클래스를 세 개 씩이나 정의한다는 것은 불합리하게 느껴진다 이럴 때는 클래스 템플릿을 정의해서 이러한 불합리한 점을 해결할 수 있다
  • 템플릿 클래스의 객체를 생성할 때는 자료형 정보를 생략할 수 없다
  • 헤더파일 PointTemplate.h에 템플릿 Point의 생성자와 멤버함수의 정의를 모두 넣는다


Chapter 14 템플릿 2

14-1 Chapter 13에서 공부한 내용의 확장

  • 템플릿과 관련해서 무엇이 되고, 무엇이 안 되는지를 아는 것도 중요하지만, 템플릿 관련 코드를 보면서 이것이 의미하는 바가 무엇인지를 유추하는 것도 중요하다

14-2 클래스 템플릿의 특수화

  • 클래스 템플릿을 특수화하는 이유는 특정 자료형을 기반으로 생성된 객체에 대해, 구분이 되는 다른 행동양식을 적용하기 위해서이다
// T2 하나에 대해서만 부분적으로 특수화를 진행했다 이를 클래스 템플릿의 부분 특수화 라고 한다
template <typename T1>
class MySimple<T1, int> { ... }
  • 전체 특수화가 부분 특수화보다 우선시 된다

14-3 템플릿 인자

  • 템플릿 매개변수에 전달되는 자료형 정보를 가리켜 템플릿 인자라 한다
  • 템플릿 매개변수에도 변수가 올 수 있으며 다음의 형태로 객체가 생성된다 SimpleArray<int, 5> i5arr;
  • SimpleArray<int, 5>와 SimpleArray<int, 7>은 서로 다른 형(type)이다

14-4 템플릿과 static

  • 템플릿 함수 별로 static 지역변수가 유지된다
  • T SimpleStaticMem<T>::mem=0; 이는 템플릿 기반의 static 멤버 초기화 문장이다
  • 정의 부분에 T가 존재하면 T에 대한 설명을 위해서 <typename T> 의 형태로 덧붙이면 되고, T가 존재하지 않으면 <>의 형태로 간단하게 선언하면 된다


Chapter 15 예외 처리

15-1 예외상황과 예외처리의 이해

  • 예외라는 것은 문법적인 오류가 아닌, 프로그램의 논리에 맞지 않는 상황
  • 예외가 발생하면, 그에 따른 처리가 이뤄져야지, 그냥 프로그램이 종료되어버리는 상황을 만들어서는 안 된다
  • if문과 주석을 사용해 예외처리를 하면 딱 보고 이 부분에서 예외처리를 하고 있다고 인지하기 힘들다

15-2 C++의 예외처리 메커니즘

  • try와 catch는 하나의 문장이다
  • throw expn 에서 expn은 변수, 상수 그리고 객체 등 표현 가능한 모든 데이터가 될 수 있으나, 예외상황에 대한 정보를 담은, 의미 있는 데이터이어야 한다 그래서 위 문장에서 expn의 위치에 오는 데이터를 가리켜 그냥 예외라고 표현하기도 한다
  • throw절에 의해 던져진 예외 데이터의 자료형과 catch 블록의 매개변수 자료형은 일치해야 한다
  • 예외가 발생할만한 영역만 묶는게 아니라, 그와 관련된 모든 문장을 함께 묶어서 이를 하나의 일(work)의 단위로 구성해야 한다
  • try 블록 내에서 예외가 발생하면, 예외가 발생한 지점 이후의 나머지 try 영역은 건너뛴다

15-3 Stack Unwinding

  • 예외가 처리되지 않으면, 예외가 발생한 함수를 호출한 영역으로 예외 데이터가(더불어 예외처리에 대한 책임까지) 전달된다
  • 대부분의 경우에 있어서 예외의 발생위치와 예외의 처리위치는 다르다
  • 함수 내에서 함수를 호출한 영역으로 예외 데이터를 전달하면, 해당 함수는 더 이상 실행되지 않고 종료가 된다
  • 예외가 처리되지 않아서, 함수를 호출한 영역으로 예외 데이터가 전달되는 현상을 가리켜 스택 풀기 라고 한다
  • 예외가 처리될 때까지, 호출된 함수의 역순으로 예외 데이터가 전달된다
  • main 함수에서조차 예외를 처리하지 않으면, terminate 함수(프로그램을 종료시키는 함수)가 호출된다
  • catch 매개변수 형과 전달받은 데이터형이 일치하지 않으면 자료형의 불일치로 인해서 예외는 처리되지 않는다
  • 사용되는 예외 데이터의 자료형이 다를 수 있기 때문에, try 블록에 이어서 등장하는 catch 블록은 둘 이상이 될 수 있다
// 함수를 정의할 때에는 함수 내에서 발생 가능한 예외의 종류를 아래와 같이 명시해 주는 것이 좋다
int ThrowFunc(int num) throw (int, char)
{
	...
}
// 아래 코드는 어떠한 예외도 전달하지 않음을 의미한다
int ThrowFunc(int num) throw ()
{
	...
}

15-4 예외상황을 표현하는 예외 클래스의 설계

  • 예외발생을 알리는데 사용되는 객체를 가리켜 예외객체라 하며, 예외객체의 생성을 위해 정의된 클래스를 예외 클래스라 한다
// 예외객체는 아래와 같이 임시객체의 형태로 생성하는 것이 보통이다
if(money > balance)
	throw WithdrawException(balance);
  • 예외 클래스도 상속의 관계를 구성할 수 있다
  • 예외 클래스를 상속의 관계로 구성할 때 catch 블록의 배치에 유의하자

15-5 예외처리와 관련된 또 다른 특성들

  • bad_alloc은 헤더파일 <new>에 선언된 예외 클래스로써 메모리 공간의 할당이 실패 했음을 알리는 의도로 정의되었다 이와같이 프로그래머가 정의하지 않아도 발생하는 예외도 있다
try
{
	...
}
// 아래의 ...은 전달되는 모든 예외를 다 받아주겠다는 선언
// 밑의 catch는 매개변수 선언에서 보이듯이 발생한 예외와
// 관련해서 그 어떠한 정보도 전달받을 수 없으며, 전달된
// 예외의 종류도 구분이 불가능하다
catch(...)
{
	...
}
  • catch 블록에 전달된 예외는 다시 던져질 수 있다


Chapter 16 C++의 형 변환 연산자와 맺는 글

16-1 C++에서의 형 변환 연산

  • dynamic_cast는 상속관계에 놓여 이는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우에만 적절한 형 변환 이라고 판단한다
  • static_cast는 기초 클래스의 포인터 및 참조형 데이터도 유도 클래스의 포인터 및 참조형 데이터로 아무런 조건 없이 형 병환 시켜준다 하지만 책임은 프로그래머가 져야한다
  • 왠만하면 dynamic_cast 연산자를 사용해서 안전성을 높여야 하며, 그 이외의 경우에는 정말 책임질 수 있는 상황에서만 제한적으로 static_cast를 사용하자
  • static_cast 연산자는 기본 자료형 간의 형변환과 클래스의 상속관계에서의 형 변환만 허용한다
// 인자의 전달을 위해서 포인터 변수 name의 const를
// 제거하는 형태로 형 변환을 진행하고 있다
const char * name "Lee Sung Ju"
ShowString(const_cast<char*>(name));

// int형 정수에 바이트 단위 접근을 위해서 int형
// 포인터를 char형 포인터로 형 변환하고 있다
char * ptr = reinterpret_cast<char*>(&num);

// 주소 값을 정수로 반환
int adr = reinterpret_cast<int>(ptr);
  • 기초 클래스가 Polymorphic 클래스 이면 dynamic_cast 연산자도 기초 클래스의 포인터 및 참조형 데이터를 유도 클래스의 포인터 및 참조형으로의 형 변환을 허용한다
// NULL의 반환 유무를 판단해서 형 변환에 성공했을 때에만 멤버함수를 호출하도록 작성된 코드
SoSimple * simPtr = new SoSimple;
SoComplex * comPtr = dynamic_cast<SoComplex*>(simPtr);
if(comPtr==NULL)
	cout<<"형 변환 실패"<<endl;
else
	comPtr->ShowSimpleInfo();
return 0;
  • dynamic_cast는 컴파일 시간이 아닌 실행 시간에 안전성을 검사하도록 컴파일러가 바이너리 코드를 생성한다
  • static_cast는 실생중인 동안에 검사를 진행하지 않는 특성 때문에 연산자의 이름이 static으로 시작하는 것이다
SoSimple simObj;
SoSimple& ref = simObj;
try
{
	// ref가 실제 참조하는 대상이 SoSimple 객체이기 때문에 SoComplex 참조형으로의 형 변환은 안전하지 못하다 그리고 참조자를 대상으로는 NULL을 반환할 수 없기 때문에 이러한 상황에서는 bad_cast 예외가 발생한다
	SoComplex& comRef = dynamic_cast<SoComplex&>(ref);
}
catch(bad_cast expt)
{
	// what 함수는 예외의 원인을 문자열의 형태로 변환
	cout<<expt.what()<<endl;
}

16-2 윤성우의 열혈 C++ 프로그래밍을 맺는 글

  • 이해의 속도로 인해서 느끼는 불편함은 오래가지 않는다
  • 시간이 조금 지나고 고비만 조금 넘기면 어느 분야보다 쉽게, 그리고 많은 분량의 지식습득이 가능한게 IT 분야이다


맨 위로 이동하기

댓글남기기