상관관계가 있는 여러 언어들의 연합체로 보아라.
이 언어의 하위 언어(sublanguage)
1. C: C를 기본으로 하고 있음
2. 객체 지향 개념의 C++: 클래스를 쓰는 C. 클래스, 캡슐화, 상속, 다형성, 가상 함수(동적 바인딩)
3. 템플릿 C++: 용도에 맞춘 템플릿 구문. 템플릿 메타프로그래밍(TMP)
4. STL: 템플릿 라이브러리. 컨테이너(container), 반복자(iterator), 알고리즘(algorithm)과 함수객체(function object)가 얽혀 돌아가는 것이 규약
이 네 가지의 하위언어로 이루어져있다는 것을 마음애 새기자
한 하위언어에서 다른 하위언어로 옮겨갈 때, 각각의 규칙에 맞춰 적용
-C는 값 전달이 참조 전달보다 효율 좋음
-C++은 생성자, 소멸자 개념이 있고 참조 전달이 효율 좋음
-템플릿 C++은 객체 타입조차 알 수 없음
-STL은 C의 포인터를 본떠 만들었기 때문에 다시 값 전달이 효율 좋음
가급적 선행 처리자보다 컴파일러를 더 가까이 하자!
#define Aspect_Ratio 1.653; // 매크로 표기(대문자로만)
위와 같이 작성할 경우, 컴파일러는 Aspect_Ration를 기억하지 못해
아래와 같이, 매크로 대신 상수를 쓰기
const double AspectRation = 1.653; // 상수 정의
이렇게 사용되면 최종 코드의 길이도 짧아짐. 왜? 사용시 사본이 딱 한개만 생기기 때문.
#define을 상수로 교체할 시에, 주의해아 할 것.
-상수 포인터 정의시, 포인터는 반드시 const선언, 가리키는 대상까지 const
const char* const authorName = "Scot Meyers";
const std::string authorName("Scot Meyers"); // 구닥다리 char* 말고 string
-클래스 멤버로 상수 정의시, 사본 개수가 한 개를 넘지 못하게 하려면 static멤버로
class GamePlayer{
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수를 사용
}
클래스 멤버가 선언된 시점에 초기값이 주어짐
- 이것이 먹히지 않는경우(구식 컴파일러), 헤더 파일에서는 선언만 하고, 구현 파일에서 정의
- 나열자 둔갑술(enum hank)기법 사용 추천.
class GamePlayer{
private:
enum {NumTurns = 5}; // 나열자 둔갑술. 5에 대한 기호식 이름으로 만듦
int scores[NumTurns];
}
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
주의해야할 점은, 클래스 상수를 #define으로 만들어서는 절대 안됨
- 컴파일이 끝날 때까지 유효해짐
- 클래스 상수 정의 불가하며 어떤 형태의 캡슐화 혜택도 받을 수 없음(private한 #define은 없음)
그러니까, const, enum, inline의 도움으로 선행 처리자(특히 #define)의 사용을 줄일 수 있음
아예 안쓸수는 없지만, 줄이도록 하자.
const 는 팔방미인
-의미적인 제약을 단단히 지켜줌(외부에서 변경 불가능)
-namespace 유효범위의 상수를 선언하는데 사용 가능
-파일, 함수, 블록 유효범위에서 static으로 선언한 객체에 사용 가능
-정적 멤버 및 비정적 데이터 멤버 모두 상수 선언 가능
포인터의 경우
- 포인터가 가리키는 데이터를 상수 선언 가능
char greeting[] = "Hello";
char *p = greeting; // 비상수 포인터, 비상수 데이터
const char *p = greeting; // 비상수 포인터, 상수 데이터
char * const p = greeting; // 상수 포인터, 비상수 데이터
const char * const p = greeting; // 상수 포인터, 상수 데이터
- 함수 매개변수에서 포인터가 가리키는 대상을 상수로 만들 때 두 가지 방법을 취함
void f1(const Widget *pw);
void f2(Widget const *pw);
STL 반복자의 경우
- 반복자를 상수로 선언하려면
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
*iter = 10; (O)
iter++; (X)
-반복자가 가리키는 대상을 상수로 선언하려면
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10; (X)
cIter++; (O)
가장 강력한 부분, 함수선언의 경우
-함수 반환 값, 각각의 매개변수, 멤버 함수 앞에, 함수 전체에 const를 붙일 수 있다
-함수 반환 값을 상수로 정하면 에러 돌발 상황을 줄이는 효과를 꽤 자주 볼 수 있음
class Rational {...};
const Rational operator* (const Rational& lhs, const Rational& rhs);
Rational a, b, c;
...
(a * b) = c; // 결과에 c를 저장? const라 어림도 없지~
-위와 같이 ==를 써야 하는데 =를 쓴것과 같은 '쓸데없는'경우의 실수를 방지할 수 있음
-상수 멤버 함수
해당 멤버 함수가 상수 객체에 대해 호출될 함수이다 라는 사실을 알려 주는 것
클래스의 인터페이스를 이해하기 좋게 하기 위해(변경할 수 있는 함수? 변경할 수 없는 함수?)
상수 객체를 사용할 수 있게 하기 위해
- 상수 객체에 대한 참조자로 진행 위해(실제 C++프로그램의 실행 성능을 높이는 핵심 기법 중 하나)
const 멤버 함수가 준비되어 있어야함
class TextBook{
public:
...
const char& operator[] (std::size_t position) const // 상수 객체에 대한 operator[]
{ return text[position]; }
char& operator[] (std::size_t position) // 비상수 객체에 대한 operator[]
{ return text[position]; }
private:
std:: string text;
};
위의 경우, const키워드 유무에 따라 오버로딩되어있음 => 만들어진 객체에 따라 사용하는 operator가 달라짐
TextBook tb("Hello"); // 비상수 객체
std::cout << tb[0]; // 비상수 멤버 호출
const TextBook cTb("World"); // 상수 객체
std::cout << cTb[0]; // 상수 멤버 호출
쓰임새의 구분
tb[1] = 'x'; (O)
cTb[1] = 'x'; (X) // const char& 타입에 대입 연산을 시도했기 때문
상수 멤버의 의미는?
-비트수준 상수성(bitwise constness, 물리적 상수성): 어떤 멤버 함수가 그 객체의 어던 데이터 멤버도 건드리지 않아야 const임을 인정.
-논리적 상수성(logical constness): 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다는 것
class CTextBook{
public:
...
char& operator[] (std::size_t position) const
{ return pText[position]; }
private:
char *pText;
};
// 이상한 코드(상수객체가 상수 멤버함수를 사용했는데 값이 변경됨)
const CTextBlock cctb("Hello");
char *pc = &cctb[0];
*pc = "J" // cctb는 "Jello"를 가짐
// 논리적 상수성에 따라 아래와 같은 멤버 추가 가능
private:
char *pText;
std::size_t textLength;
bool lengthIsValid;
};
std::size_t CTextBook::length() const
{
if(!lengthIsValid){
textLength = std::strlen(pText); // 상수멤버 내에서 private변수에 대입 불가..
lengthIsValid = true;
}
return textLength;
}
mutable std::size_t textLength; // mutable과 함께라면, 어떤 순간에도 수정 가능!
mutable bool lengthIsValid;
상수 멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법
- 일단 캐스팅은 피해라
-중복된 코드를 없애기 위해선 캐스팅이 불가피
- const_cast<char&>를 통해 반환값의 const를 떼어내고
- static_cast<const TextBlock&>을 통해 const 객체를 가져옴
- (this) [position] 으로 this타입의 캐스팅까지 하면 상수 버전의 op[]를 호출!
=> TextBlock에서 const TextBlock으로 바꾼것