~cpp
class String { ... }; // 문자열 클래스 (이건 밑의 언급과 같이 표준 스트링 타입과
// 같이 사용 방식이 적용된다고 가정한다. 하지만 결코 존재하지는 않는다.)
String s1 = "Hello";
String s2 = s1; // String 복사 생성자를 부른다.
String 복사 생성자의 적용시, s2는 s1에 의하여 초기화 되어서 s1과 s2는 각각 "Hello"를 가지게된다. 그런 복사 생성자는 많은 비용 소모에 관계되어 있는데, 왜냐하면, s1의 값을 s1로 복사하면서 보통 heap 메모리 할당을 위해 new operator(Item 8참고)를 s1의 데이터를 s2로 복사하기 위해 strcpy를 호출하는 과정이 수행되기 때문이다. 이것은 eager evaluation(구지 해석하면 즉시 연산 정도 일것이다.) 개념의 적용이다.:s1의 복사를 수행 하는 것과, s2에 그 데이터를 집어넣는 과정, 이유는 String의 복사 생성자가 호출되기 때문이다. 하지만 여기에는 s2가 쓰여진적이 없이 새로 생성되는 것이기 때문에 실제로 s2에 관해서 저런 일련의 복사와, 이동의 연산의 필요성이 없다.~cpp
cout << s1; // s1의 값을 읽는다.
cout << s1 + s2; // s1과 s2의 값을 읽는다.
사실, 값을 공유하는 시간은 둘중 아무거나 값이 수정되어 버릴때 다른점이 발생하기 전까지만 유효한 것이다. :ㅣ그런데 양쪽이 다 바뀐게 아니라 한쪽만 바뀌는 이런 지적은 중요한것이다. 다음과 구문처럼~cpp
s2.convertToUpperCase();
이건 s2의 값만을 바꾸고 s1에는 영향을 끼치지 않은 요구로, 매우 치명적이다.~cpp
String s = "Homer's Iliad"; // 다음 문자열이 reference-counting으로
// 구현되었다고 생각하자
...
cout << s[3]; // operator []를 호출해서 s[3]을 읽는다.(read)
s[3] = 'x'; // operator []를 호출해서 s[3]에 쓴다.(write)
첫번째 operator[]는 문자열을 읽는 부분이다,하지만 두번째 operator[]는 쓰기를 수행하는 기능을 호출하는 부분이다. 여기에서 읽기와 쓰기를 구분할수 있어야 한다.(distinguish the read all from the write) 왜냐하면 읽기는 refernce-counting 구현 문자열로서 자원(실행시간 역시) 지불 비용이 낮고, 아마 저렇게 스트링의 쓰기는 새로운 복사본을 만들기 위해서 쓰기에 앞서 문자열 값을 조각내어야 하는 작업이 필요할 것이다.~cpp
class LargeObject { // 크고, 계속 유지되는 객체들
public:
LargeObject(ObjectID id); // 디스크에서 객체의 복구(부르기)
cosnt string& field1() const; // 필드상의 값1
int field2() const; // 필드상의 값2
double field3() const; // ...
const string& field4() const;
const string& field5() const;
...
};
자 그럼 디스크에서 복구(자료를 부르기)되어지는 LargeObject의 비용을 생각해 보자:~cpp
void restoreAndProcessObject(ObjectID id) // 객체 복구
{
LargeObject object(id);
...
}
LargeObject 인스턴스들이 크기 때문에 그런 객체에서 모든 데이터를 얻는 것은, 만약에 특별히 데이터 베이스가 외부에 네크워크 상에서 자료 로드가 있다면 데이터 베이스의 작업은 비쌀것이다. 몇몇 상황에서 모든 데이터 베이스를 읽어들이는 비용은 필요가 없다. 예를 들어서 다음의 어플리케이션의 종류에 관하여 생각해 보자.~cpp
void restoreAndProcessObject(ObjectID id)
{
LargeObject object(id);
if (object.field2() == 0) {
cout << "Object " << id << ": null field2.\n";
}
}
이런 경우에서는 오직 field2의 값만을 요구한다. 따라서 다른 필드를 로드하는 작업은 필요없는 작업이 되어 진다.~cpp
class LargeObjectP
pulic:
LargeObject(ObjectID id);
const string& field1() const;
int field2() const;
double field3() const;
const string& field4() const;
...
private:
ObjectID oid;
mutable string *field1Value; // 앞으로의 "mutable"에 관한 토론을 보라
mutable int *field2Value;
mutable double *field3Value;
mutable string *field4Value;
...
};
LargeObject::LargeObject(ObjectID id):oid(id), field1Value(0), field2Value(0), field3Value(0), ...
{}
const string& LargeObject::field() const
{
if (field1Value == 0){
field1의 데이터를 읽기 위하여 데이터 베이스에서 해당 데이터를 가지고 와서
field1Value 가 그것을 가리키게 한다.
}
return *field1Value;
}
객체의 각 필드는 필요한 데이터의 포인터로 표현되어 있고, LargeObject의 생성자는 null로 초기화 된다. 그런 null 포인터는 아직 데이터 베이스에서 해당 필드의 정보를 안읽었다는 걸 의미한다. 데이터를 접근하기 전에 LargeObject의 각 멤버 함수는 반드시 이 필드의 포인터를 검사한다. 만약 포인터가 null이라면 데이터를 사용하기 전에 반드시 데이터 베이스에서 읽어 온다.~cpp
class LargeObject {
public:
const string& field1() const; // 바뀌지 않음
...
private:
string *field1Value; // mutable로 선언되지 않았다.
... // 그래서 과거 컴파일러는 이걸 허용한다.
};
const string& LargeObject::field1() const
{
// 자 이것이 fake This 인데, 저 포인터로 this에 대한 접근에서 const를 풀어 버리는 역할을 한다.
// LargeObject* 하고 const가 뒤에 붙어 있기 때문에 LargeObject* 자체는 const가 아닌 셈이다.
LargeObject * const fakeThis = const_cast<LargeObject* const>(this);
if( field1Value == 0){
fakeThis->field1Value = // fakeThis 가 const가 아니기 때문에
데이터베이스에 접근해서 // 이러한 시도는 합당하다.
포인터를 넘기는 부분
}
return * field1Value;
}
이 함수는 *this의 constness성질을 부여하기 위하여 const_cast(Item 2참고)를 사용했다.만약 당신이 const_cast마져 지원 안하면 다음과 같이 해야 컴파일러가 알아 먹는다.~cpp
const string& LargeObject::field1() const
{
LargeObject * const fakeThis = (LargeObject* const)(this);
...
}
~cpp
template<class T>
class Matrix { ... };
Matrix<int> m1(1000, 1000); // 굉장히 큰 int형 1000x1000배열을 선언한거다.
Matrix<int> m2(1000, 1000);
...
Matrix<int> m3 = m1 + m2; // 그리고 그 둘을 더한다 굉장한 연산이 필요해진다.
보통 operator+에 대한 구현은 아마 eager evaluation(즉시 연산) 이 될것이다.;이런 경우에 그것은 아마 m1과 m2의 리턴 값을 대상으로 한다. 이 계산(1,000,000 더하기)에 적당한 게산양과, 메모리 할당에 비용 이 모드것이 수행되어져야 함을 말한다.~cpp
Matrix<int> m4(1000, 1000);
... // 아까 위에서 했던 m4에 어떠한 값을 넣는 코드들이다.
m3 = m4 * m1;
우리는 이제 아까 m1, m2의 합인 m3를 잃어버렸다.( 그리고 이건 합의 계산 비용을 줄인다는 의미도 된다.) 그것에는 m4,m1의 곱이 새로운 값으로 기억되어 진다. 말할 필요도 없이 이제 곱은 수행안하는 거다. 왜냐? 우리는 lazy 하니까. ~~cpp
cout << m3[4]; // m3의 4번째 열만을 요구한다.
확실히 우리는 이제 lazy 상태를 벗어나야 함을 알수 있다.-우리는 m3의 4번째 열에 대하여 계산된 값을 가지고 있어야 한다. 그러나, 역시나 너무나 지나친 것이다. 우리는 m3의 4번재 열을 계산해야만 하는건 말할 필요도 없다.:m3는 그것이 필요해지기 전까지는 계산할필요가 없다. 하지만 행운으로 그렇게 할 필요가 없을것이다.~cpp
cout << m3; // m3의 모든것을 찍는다.
뭐 끝났다. m3를 위한 모든 값을 가지고 있어야 한다. 비슷하게 m3가 의존하는 행렬들중에 수정되는것 이 있어도, 즉시 계산을 필요로 한다.~cpp
m3 = m1 + m2; // m3가 m1,m2의 합인걸 기억하라
m1 = m4; // 이제 m3는 m2와 과거 m1의 합이 된다.
그러므로 몇가지의 m1에 대한 할당이 m3를 변화시키지 않는다는 확신을 가지고 있어야 한다. Matrix<int>의 내부에 할당된 operator 내부에 m3의 값이 m1의 계산 이전에 계산되어 있거나, m1의 과거 값에 대한 복사본을 가지고 있고 m3는 그것에 의존해야 한다. 다른 함수들도 이러한 행렬의 변경을 위하여 다른 형식의 함수들도 이런 비슷한 것을 감안해야 할것이다.~cpp
template<class NumericalType>
class DataColletion {
public:
NumericalType min() const;
NumericalType max() const;
NumericalType avg() const;
...
}
min, max, avg에 함수는 현재의 해당 collection의 최소값, 최대값 평균을 반환하는 값이라고 생각해라, 여기에서 이들이 구현될수 있는 방법은 3가지 정도가 있다. eager evaluation(즉시연산)을 이용해서 min, max, avg가 호출될때마다 해당 연산을 하는 방법. lazy evaluation(게으른연산)을 해서 해당 함수값이 반환하는 값이, 실제로 연산에 필요할때 마지막에 계산에서 연산해서 값을 얻는 방법. 그리고 over-eager evaluation(미리연산)을 이용해서 아예 실행중에 최소값, 최대값, 평균값을 collection내부에 가지고 있어서 min, max, avg가 호출되면 즉시 값을 제공하는 방법-어떠한 계산도 필요 없이 그냥 즉시 돌리는거다. 만약 min, max, avg가 자주 호출된다면 collection의 최소값, 최대값, 평균값을 이용하는 함수들이 collection 전역에 걸쳐서 계산을 필요로 할수 있다. 그렇다면 이런 계산의 비용은 eager,lazy evaluaton(게으른연산, 즉시연산)에 비하여 저렴한 비용을 지출하게 될것이다.(필요값이 즉시 반환되니)~cpp
int findCubicleNumber(const string& employeesName)
{
// static으로 map을 선언하는 과정 이 맵이 local cashe이다.
typedef map<string, int> CubicleMap;
static CubicleMap cubes;
// 해당 직원 이름을 바탕으로 cache에서 찾는 과정
// STL interator "it"은 해당 entry를 찾는다.
CubicleMap::iterator it = cubes.find(employeeName);
// 만약 아무런 entry를 찾을수 없다면, "it"의 값은 cubes.end이다.
// 그런 경우에는 db에서 자료를 가지고 와야 한다.
if(it == cubes.end()){
int cubicle =
직원 이름의 개인방 번호를 데이터 베이스에서
얻어오는 과정
cubes[employeeName] = cubicle; // 추가
return cubicle;
}
else {
// "it" 포인터는 정확한 cache entry를 가리키며 cubicle번호는 두번째 인자라
// 이런 방법으로 얻는다.
return (*it).second;
}
}
STL코드를 자세히 알고 싶어서 촛점을 벗어나지 말아라. Item 35 보면 좀 확실히 알게 될것이다. 대신에 이 함수의 전체적인 기능에 촛점을 맞추어 보자.현재 이 방법은 비교적 비싼 데이터 베이스의 쿼리(query)문에대한 비용대신에 저렴한 메모리상의 데이터 베이스 구조에서 검색을 하는 것으로 교체하는걸로 볼수 있다. 개인 방번호에 대한 호출이 한벙 이상일때 findCubicleNumber는 개인방 번호에 대한 정보 반환의 평균 비용을 낮출수 있다. (한가지 조금 자세히 설명하자면, 마지막 구문에서 반환되는 값 (*it).second이 평범해 보이는 it->second 대신에 쓰였다. 왜? 대답은 STL에 의한 관행이라고 할수 있는데, 반환자(iterator)인 it은 객체이고 포인터가 아니라는 개념, 그래서 ->을 it에 적용할수 있다라는 보장이 없다. STL은 "."과 "*"를 interator상에서 원한다. 그래서 (*it).second라는 문법이 약간 어색해도 쓸수 있는 보장이 있다.)~cpp
template<class T> // 동적 배열 T에 관한 클래스 템플릿
class DynArray { ... };
DynArray<double> a; // 이런 관점에서 a[0]은 합법적인
// 배열 인자이다.
a[22] = 3.5; // a는 자동적으로 확장되었으며,
// 현재는 0-22까지의 영역을 가진다.
a[32] = 0; // 다시 확장되며 이제 0-32까지다.
어떻게 DynArray 객체가 필요할때 마다 스스로 확장되는 걸까? 곧장 생각할수 있는건 그것이 새로운 메모리가 필요될때만 할당되고는 것이다 이런것 처럼 말이다.~cpp
templace<class T>
T& DynArray<T>::operator[](int index)
{
if (index < 0) {
예외를 던진다.;
}
if (index > 현재 인덱스의 최대값){
new를 호출해서 충분한 메모리를 확보한다. 그렇게 해서 index를 활성화 시킨다.;
}
return 배열의 인덱스 인자를 반환한다.
이러한 접근은 new를 배열의 증가 때만 부르는 간단한 방법 이지만 new는 operator new(Item 8참고)를 부르고, operator new(그리고 operaotr delete)는 보통 이 명령어들은 비용이 비싸다. 그것의 이유는 일반적으로 OS, 시스템 기반의 호출(System call)이 in-process 함수호출 보다 느린게 되겠다. (DeleteMe OS를 배워야 확실히 알겠다 이건) 결과적으로 가능한 system 호출(system call)을 줄여 나가야 한다.~cpp
template<class T>
T& DynArray<T>::operator[](int index)
{
if (index < 0) 예외를 던진다.;
if (index > 현재 인덱스의 최대값){
int diff = index - 현재 인덱스의 최대값;
알맞은 메모리를 할당한다. 그래서 index+diff가 유효하게 한다.
}
return 현재 인덱스가 가리키는 인자;
}
이 함수는 두번 충분한 양의 배열을 각각 필요할때 할당한다. 만약 우리가 앞서 이야기한 쓰임의 시나리오대로 진행 된다면, 아마 DynArray는 그것이 두번의 논리적 크기의 확장을 할지라도 오직 메모리를 한번만 할당할 것이다.:~cpp
DynArray<double> a; // 오직 a[0]만이 유효하다.
a[22] = 3.5; // 현재 index 44정도의 저장 공간을
// 할당하며 a의 논리적 공간은 23이다.
a[32] = 0; // a의 논리적 공간은 이제 a[32]이다. 하지만
// 44의 저장공간이 있기에 확장하지 않는다.
만약 다시 확장이 필요하다면 44보다 크지 않는다면, new에 의한 높은 비용을 치루지 않는다.~cpp
template<class T>
void swap(T& object1, T& object2)
{
T temp = object;
object1 = object2;
object2 = temp;
}
자 여기서 temp를 보통 "temporary" 라고 부른다. 그렇지만 C++에 관점에서는 temp는 반드시 temporary라고 규정지을수 없다. 그것은 함수내에 존재하는 단순한 지역 객체일 뿐이다.~cpp
// 다음 함수는 str안에서 ch가 몇게 있는가 반환한다.
size_t countChar(const string& str, char ch);
char buffer[MAX_STRING_LEN];
char c;
// string과 char를 받아 들인다. setw 는 글자를 읽을때
// 오버 플로우(overflow)를 방지하는 목적으로 쓰인다.
cin >> c >> setw(MAX_STRING_LEC) >> buffer;
cout << "There are " << countChar(buffer, c)
<< " occurrences of the charcter " << c
<< " in " << buffer << endl;
countChar을 호출하는 곳을 보라. 처음에 구문에서 char 배열이 함수로 전달된다. 하지만 함수의 인자는 const string& 이다. 이런 호출은 오직 형(type)이 알맞지 않은것이 제거되거나 당신의 컴파일러는 훌륭히도 string 형의 임시 객체(temporary object)를 만들어서 그러한 맞지 않는 형문제를 제가하면 성공할수 있다. 그러한 임시 객체는 string 생성자가 buffer인자를 바탕으로 초기화 된다. 그러면 constChar의 str인자는 임시(temporary) string 객체를 받아들인다.(bind-bound) countChar이 반환될때 임시(temporary)객체는 자동 소멸된다. ~cpp
void uppercasify(string& str); // str의 모든 글자를 대문자료 바꾼다.
글자 세기(charter-counting)예제에서 char 배열은 성공적으로 countChar로 전달된다. 그렇지만 이것과 같은 함수에서는 에러가 발생한다. 다음과 같은 코드 말이다.~cpp
char subtleBookPlug[] = "Effective C++";
uppercasify(subtleBookPlug); // 에러다!
어떠한 임시인자(temporary)도 만들어 지지 않았다. 왜 만들어 지지 않은 걸까?~cpp const Number operator+(const Number& lhs, const Number &rhs);해당 함수의 반환 인자(const Number)는 임시물(temporary)이다. 왜냐하면 그것은 아무런 이름도 가지기 않기 때문이다.:단지 함수의 반환되는 값일 뿐이다. 당신은 반드시 operator+가 호출될때 해당 객체의 생성, 삭제에 관한 비용을 지불해야만 한다. (반환 값이 const인것에 관한 이유를 알고 싶다면 Item 6을 참고하라)
~cpp
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
int numerator() const;
int denominator() const;
};
// 반환 값이 왜 const 인지는 Item 6을 참고하라
const Rational operator* (const Rational& lhs, const Rational& rhs);
operator*를 위한 코드를 제외하고, 우리는 반드시 객체를 반환해야 한다는걸 알고 있다. 왜냐하면 유리수가 두개의 임의의 숫자로 표현되기 때문이다. 이것들은 임의의 숫자이다. 어떻게 operator*가 그것들의 곱을 수행하기위한 새로운 객체의 생성을 피할수 있을까? 할수 없다. 그래서 새로운 객체를 만들고 그것을 반환한다. 그럼에도 불구하고, C++프로그래머는 값으로 반환시(by-value)시 일어나는 비용의 제거를 위하여 Herculean 의 노력으로 시간을 소비한다.~cpp
// 이러한 객체의 반환은 피해야할 방법이다.
const Rational* operator* (const Rational& lhs, const Rational& rhs);
const Rational a = 10;
Rational b(1,2);
Rational c = *(a * b); // 이것이 "자연스러운" 것일까?
그것은 또한 의문을 자아 낸다. 호출자가 함수에 의해 반환된 포인터를 바탕으로 객체를 제거 해야 하는거? 이러한 대답에 관해서 보통은 "네" 이고 보통은 자원이 새어나가는(Resource leak)것을 발생 시킨다.~cpp
// 객체의 반환을 피하기에는 위험하고 옳바르지 않은 방법이다.
const Rational& operator* (const Rational& lhs, const Rational& rhs);
const Rational a = 10;
Rational b(1,2);
Rational c = a * b; // 보기에는 완전해 보인다.
그렇지만 이러한 함수는 결코 정확한 행동을 위해서 코드에 적용하지 못한다. 보통 이렇게 짜여 질텐데,~cpp
// 객체를 반환하는 또 다른 멍청하고 위험한 방법이다.
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational result(lhs.numerator() * rhs.numerator(), lhs.denominator(), rhs.denominator());
return result;
}
이 함수는 더이상 존재하지 않는 참조를 반환하는 함수이다. 그것이 반환하는 객체는 지역 객체인 result이며 이 result는 함수인 operator*가 종료되면 자동으로 삭제된다. 그래서 반환된 파괴되어진 해당 객체의 참조 값은 쓸수 없다.~cpp
// 객체를 반환하는 함수에 관하여 효율적이고 정확한 적용을 위해 다음과 같이 한다.
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
반환되는 표현식을 자세히 살펴 봐라. 그것은 Raional의 생성자이다. 당신은 임시 객체 Rational를 다음 표현에서 만들어 내는 것이다.~cpp
Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
그리고 이 임시 객체는 함수가 반환 값을 위하여 복사한다.~cpp
Rational a = 10;
Rational b(1,2);
Rational c = a * b;
당신의 컴파일러는 operator*내부의 임시 인자를 없애고 그 임시 인자는 operator*에 의하여 반환 된다. 그들은 객체 c를 메모리에 할당하는 코드에 대하여 return 표현에 의해 정의된 객체를 생성한다. 만약 당신의 컴파일러가 이렇게 한다면 operator*에 대한 임시 객체의 총 비용은 zero가 된다. 게다가 당신은 이것보다 더 좋은 어떠한것을 생각할수 없을꺼다. 왜냐하냐면 c가 이름 지어진 객체이고, 이름 지어진 객체는 사라지지 않기 때문이다.(Item 22참고). 거기에 당신은 inline함수의 선언으로 operator*를 부르는 부하 까지 없앨수 있다.~cpp
inline const Rational operator* (const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
"내~ 내" 하고서 당신은 궁시렁 거릴꺼다. "최적화라..바보 짓이지. 누가 컴파일러가 그렇게 할수 있다고 하는거지? 나는 정말 컴파일러가 저렇게 하는지 알고 싶은데. 진짜 컴파일러가 저런 일을 하는거 맞아?" 이렇게 말이다. 이러한 특별한 최적화-함수의 반환 값을 가능한한 지역 임시 객체가 사용되는 것을 제거해 버리는것-는 유명한 것이고, 일반적으로 구현되어 있다. 그것은 이렇게 이름 붙여진다.:thr return value optimization. 사실 이런 최적화에 대한 이름은 아마 많은 곳에서 설명되어 질꺼다. 프로그래머는 C++컴파일러가 "return value optimization"을 할수 있는지 벤더들에게 물어 볼수도 있을 정도다 만약 한 벤더가 "예"라고 하고 다른 곳은 "뭐요?" 라고 묻는다면 첫번째 벤더는 당근 경쟁력에서 앞서 가는거다. 아~ 자본주의 인가. 때론 당신은 그걸 좋아 할꺼다. ~cpp
class UPInt {
public:
UPInt();
UPInt(int value);
...
};
const UPInt operator+( const UPInt& lhs, const UPInt& rhs);
UPInt upi1, upi2;
...
UPInt upi3 = upi1 + upi2;
~cpp
upi3 = upi1 + 10;
upi3 = 10 + upi2;
~cpp
const UPInt operator+(const UPInt& lhs, const UPInt& rhs);
const UPInt operator+(const UPInt& lhs, const int rhs);
const UPInt operator+(const int lhs, const UPInt& rhs);
UPInt upi1, upi2;
...
UPInt upi3 = upi1 + upi2;
upi3 = upi1 + 10;
upi3 = 10 + upi1;
~cpp
const UPInt operator+(const int lhs, const int rhs);
~cpp
x = x + y; x = x - y;
~cpp
x += y; x -= y;
~cpp
class Rational{
public:
...
Rational& operator+=(const Rational& rhs);
Rational& operator-=(const Rational& rhs);
};
// 값으로의 반환의 이유는 Item 6참고, const의 이유는 p109참고
const Rational operator+(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs) += rhs;
}
const Rational operator-(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs) -= rhs;
}
~cpp
template<class T>
const T operator+(const T& lhs, const T&rhs)
{
return T(lhs) += rhs;
}
template<class T>
const T opreator-(const T& lhs, const T& rhs)
{
return T(lhs) -= rhs;
}
...
~cpp
Rational a, b, c, d, result;
...
result = a + b + c + d; // 아마 이 연산에서는 각 operaotr+를 호출할때 발생하는
// 3개의 임시 객체가 존재할 것이다.
~cpp
result = a; // 하지만 여기에서는 모두 임시 객체가 필요 없다.
result += b;
result += c;
result += d;
~cpp
template<T>
const T operator+(const T& lhs, const T&rhs)
{ return T(lhs) += rhs; }
~cpp
template<class T>
const T operator+(const T& lhs, const T& rhs)
{
T result(lhs);
return result += rhs;
}
~cpp
return T(lhs) += rhs;
~cpp
#ifdef STDIO
#include <stdio.h>
#else
#include <iostream>
#include <iomanip>
using namespace std;
#endif
const int VALUES = 30000;
int main()
{
double d;
for (int n = 1; n < VALUES; ++n){
#ifdef STDIO
scanf("%lf", &d);
printf("%10.5f", d);
#else
cin >> d;
cout << setw(10) // 표현을 10 필드로 고정
<< setprecision(5) // 소수점 자리수 결정
<< setiosflags(ios::showpoint) // 점 보이도록
<< setiosflags(ios::fixed) // 10자리 고정으로 부족한 자리 경우 빈자리
<< d;
#endif
if (n % 5 == 0){
#ifdef STDIO
printf("\n";
#else
cout << '\n';
#endif
}
return 0;
}
~cpp 0.00000 0.69315 1.09861 1.38629 1.60944 1.79176 1.94591 2.07944 2.19722 2.30259 2.77259 2.48491 2.56495 2.63906 2.70805 2.77289 2.83321 2.89037 2.94444 2.99573 3.04452 3.09104 3.13549 3.17805 3.21888
~cpp
cout << setw(10)
<< setprecision(5)
<< setiosflags(ios::showpoint)
<< setiosflags(ios::fixed)
<< d;
~cpp
printf("%10.5f", d);
~cpp
class C1{
public:
C1();
virtual ~C1();
virtual void f1();
virtual int f2(char c) const;
virtual void f3(const string&s);
void f4() const;
...
};