C++ 기초 플러스 6판 (1)

Erdos·2026년 2월 11일

서재

목록 보기
12/16
post-thumbnail

저자: Stephen Prata

🤔어렵지 않게 설명하는 것을 지향하는 책인데, 너무 많은 내용을 담으려다 산만해진 느낌이다.
참고) 책의 내용을 내가 필요한 내용 위주로 정리함.


ch01: C++첫걸음

역사를 좀 배워보자.

C++로 전환하려면 기존의 C 프로그래밍 습관을 과감히 버려야 한단다.. 🥴

✏️ C++ 변수 선언 스타일은 가능하면 그 변수를 처음 사용하는 가장 가까운 위치에 그 변수를 선언하는 것이다.

C++의 간략한 역사

C++의 프로그래밍 철학

  • data: 프로그램이 사용하고 처리하는 정보 → 객제 치향 프로그래밍은 데이터 강조 → 하향식
  • algorithm:프로그램이 데이터를 처리하는 방법 → 절차 지향 프로그래밍은 알고리즘 강조 → 상향식
    • 해결해야 할 문제를 언어의 절차적 접근 방식에 억지로 끼워 맞추지 않고, 언어 자체를 해결해야 할 문제에 맞춘다. 즉, 해결해야 할 문제의 특성에 맞게 데이터형 자체를 설계한다.
    • 클래스가 이 같은 목적으로 설계되는 새로운 데이터형. → 객체는 클래스에 의해 만들어지는 특정한 데이터 구조
  • OOP는 재활용이 가능한 소스 코드를 쉽게 작성할 수 있다.
  • 정보를 은닉할 수 있어서 비인가된 접근으로부터 데이터를 안전하게 보호할 수 있다.
  • 다형성을 이용하여 이름이 같은 연산자와 함수를 여러 번 정의할 수 있기 때문에 상황에 따라 적당한 연산자나 함수를 프로그램이 스스로 선택하게 할 수 있다.
  • 상속을 이용하여 하나의 클래스로부터 새로운 클래스를 유도할 수 있다.

c++ 개발자 비야네 스트롭스트룹

이 책을 읽으면서 이 분의 이야기가 간간이 언급될 것이다.

리누스 토발즈만큼이나 프로그래밍에 한 획을 그은 개발자 중에 한 분인 것 같다.


ch02: C++시작하기

  • c++은 대소문자를 구별한다.

02.1 C++의 시작

#include <iostream>                           // a PREPROCESSOR directive 
// 전처리지시자
int main()                                    // function header
{                                             // start of function body
    using namespace std;                      // make definitions visible
    cout << "Come up and C++ me some time.";  // message
    cout << endl;                             // 개행
    cout << "You won't regret it!" << endl;   // more output                                               
    return 0;                                 // terminate main()
}                                             // end of function body

헤더파일 이름

  • C++ 에서는 .h 확장자를 생략하기로 했다.
    #include <iostream>     

헤더파일의 이름을 짓는 규칙

헤더파일의 종류규칙보기설명
C++ 구식.h 끝남iostream.hC++
C 구식.h 끝남math.hC
C++ 최신확장자 없음iostreamC++, namespace std
C 변환 스타일c접두어, 확장자 없음cmath

이름 공간

  • iostream.h → iostream을 사용할 때, 프로그램이 iostream의 정의를 사용할 수 있게 하려면 다음과 같은 이름 공간 지시자를 사용해야 한다.
  • C++의 새로운 기능. 프로그램을 작성할 때 여러 소프트웨어 개발업체들이 제공하는 코드들을 사용할 수 있도록 도와준다.
  • 자세한 내용은 “9장 메모리 모델과 이름 공간”에서 더 공부한다고 함
    using namespace std;    
    만약 위가 없었다면, 아래와 같이 써야 했을 것이다. 이 때 사용하는 게 using 지시다. std::접두어 를 붙이지 않고도 std이름 공간에 정의되어 있는 이름들을 사용할 수 있다.
    #include <iostream>                           // a PREPROCESSOR directive
    int main()                                    // function header
    {                                             // start of function body
        using namespace std;                      // 게으른 방식, 모든 이름 사용 가능
        std::cout << "Come up and C++ me some time.";  // message
        std::cout << std::endl;                             // start a new line
        std::cout << "You won't regret it!" << endl;   // more output                                                
        return 0;                                 // terminate main()
    }  

연산자 오버로딩

<<: 데이터를 출력 스트림에 집어넣는 삽입 연산자

cout 을 이용한 C++의 출력

cout: 문자열, 수, 문자 등을 포함한 여러가지 다양한 정보들을 출력하는 방법을 알고 있는 미리 정의된 객체.

  • printf와 비교했을 때 장점: 변환지정자를 특별히 신경쓰지 않아도 자동으로 출력된다.

조정자 endl

endl: cout은 문자열을 출력하고 나서 다음 행의 시작 위치로 커서를 자동으로 넘겨주지 않음. endl의 역할은 커서를 뒤로 넘겨준다. “\n”과도 비슷해보이는데, 이 책의 경우는 문자열을 출력할 때는 개행을 쓰고, 그 외의 것은 endl을 사용했다고 한다.

  • 개행과 endl의 차이점: endl은 출력버퍼를 즉시 비우도록 flush() 명령을 내림.
  • 성능적으로는 개행이 속도가 더 빠르다. 하지만, 프로그램이 갑자기 죽을 경우, endl은 죽기 직전까지 기록할 수 있어서 디버깅에 유리하다.
  • 고로, 성능이 중요한 루프에는 개행을 쓰고, 프로그램이 예기치 않게 종료되어도 로그를 남겨야 한다면 endl을 사용해라.
  • 조금만 더 풀어쓰자면, 사용자에게 즉각적인 피드백을 줘야 하거나 프로그램의 종료 상태를 확인할 때는 endl 사용.

cin:

cin >> carrots; # 입력 스트림에서 문자를 가져옴 
// scanf 였다면,
scanf("%d\n", &carrots); 

c vs c++ 데이터 전달 방식 비교

[ C 언어 (Pointer 방식) ]
scanf  <---  [ &carrots (주소값) ] 
"내가 이 주소(번지수)를 알려줄 테니, 거기 가서 값을 직접 넣어!"
(위험성: 잘못된 주소를 넘기면 프로그램이 죽음)

[ C++ (Reference 방식) ]
cin >> <---  [ carrots (변수 자체) ]
"carrots라는 변수 자체를 너에게 빌려줄게(별명). 알아서 채워줘!"
(안정성: 실체가 있는 변수만 넘길 수 있어 훨씬 안전함)

개발자들이 주소를 직접 다루다가 실수하는 것을 방지하기 위해서 '참조자'라는 개념이 등장했다.

References were introduced primarily to support operator overloading.
(..)
References are not pointers; they are aliases for objects. ... The main use of references in C++ is to provide a way of passing arguments to functions that is as efficient as passing pointers but provides the notation of passing by value.

-- The Design and Evolution of C++

참조자를 좀 더 파보자

// C언어: 포인터 (우편함) 
int* p = &a; 
*p = 20;  // "p라는 우편함에 적힌 주소로 가서(*), 그 집의 값을 바꿔라"

// C++: 참조자 (별명) 
int& r = a;
r = 20;   // "a의 별명인 r의 값을 20으로 바꿔라" (a 자체가 바뀌는 것과 동일)
  • 직접 주소 접근 제한
  • 포인터 연산 안 됨(c언어에서는 *p++와 같이 주소값을 변경해서 값들을 꺼낼 수 있었는데, c++에서는 값 자체가 1 증가한다. -> 메모리 헤집고 다니지 못하도록 제한)
  • 재할당 금지: 참조자는 태어날 때 정해진 본체를 절대 바꿀 수 없다. 포인터처럼 이 집, 저 집 가리키기 안 됨
  • NULL 참조 금지: 이 역시도 절대로 메모리를 함부로 건드릴 수 없다.

클래스 vs 객체 vs 인스턴스

  • 클래스는 데이터 형식의 모든 속성을 서술한 것(설계도)이고, 객체는 그 서술에 따라 실제로 생성한 구체물(메모리에 공간을 차지하고 있다)
    • 객체(object): 메모리 공간을 차지하고 있는 데이터 덩어리(물리적 존재). 존재 자체에 집중.
    • 인스턴스(instance) → 하나의 구체적인 사례: 그 사물이 어떤 설계도로부터 만들어졌는지 설명하는 관계 명칭. 누가 만들었는지에 집중

이게 무슨 소리야..?

#include <iostream>
#include <string>

class Fruit { // '과일'이라는 추상적 개념 (Concept)
public:
    std::string name; // 여기가 클래스 
};

int main() {
    // '과일'이라는 개념의 첫 번째 실제 사례(Instance)
    Fruit apple; 
    apple.name = "Apple";

    // '과일'이라는 개념의 두 번째 실제 사례(Instance)
    Fruit banana; 
    banana.name = "Banana";

    // "There are two instances of the Fruit class."
    // (Fruit 클래스의 인스턴스가 두 개 있습니다 = 과일의 사례가 두 개 있습니다.)
    
    return 0;
}

객체와 인스턴스의 구분은 여기서 말장난으로 느껴진다.

  • about 객체: 프로그램이 실행될 때, 스택 메모리 어딘가에 apple 공간이 할당됩니다. 이 메모리 덩어리는 name이라는 데이터를 가질 수 있는 객체다.
  • about 인스턴스 ← 이게 가장 헷갈리는 지점인데 클래스와의 혈통 관계로 맞춘다고. apple은 fruit 클래스에 정의된 속성을 본떠서 만들어졌다. 여기서 apple은 fruit 클래스의 인스턴스다.

ch02 요약(p65)

C++에서는 입력과 출력을 위해 cin과 cout이라는 두 개의 미리 정의된 객체를 사용한다.
이들은 각각 istream과 ostream 클래스의 속성으로 생성된 객체이다. istream과 ostream 클래스는 iostream파일에 정의되어 있다. 이 클래스들은 입력과 출력을 연속된 문자들의 스트림(stream) 이라고 간주한다. 삽입(insertion) 연산자(<<)는 ostream클래스에 정의되어 있으며, 데이터를 출력 스트림에 삽입한다. 추출(extraction) 연산자(>>)는 istream 클래스에 정의되어 있으며, 입력 스트림으로부터 정보를 추출한다.
cin과 cout은 잘 짜여진 영리한 객체이기 때문에, 프로그램의 문맥에 따라 한 형식을 다른 형식으로 자동으로 변환할 수 있다.


ch03: 데이터 처리

03.1 간단한 변수

// hexoct1.cpp -- shows hex and octal literals
#include <iostream>
int main()
{
    using namespace std;
    int chest = 42;     // decimal integer literal
    int waist = 0x42;   // hexadecimal integer literal
    int inseam = 042;   // octal integer literal

    cout << "Monsieur cuts a striking figure!\n";
    cout << "chest = " << chest << " (42 in decimal)\n";
    cout << "waist = " << waist << " (0x42 in hex)\n"; // 출력은 10진수로 됨 = 66
    cout << "inseam = " << inseam << " (042 in octal)\n"; // = 34
	// cin.get();
    return 0; 
}

위 경우에는 10진수로만 출력된다.

// hexoct2.cpp -- display values in hex and octal
#include <iostream>
using namespace std;
int main()
{
    using namespace std;
    int chest = 42;
    int waist = 42; 
    int inseam = 42;

    cout << "Monsieur cuts a striking figure!"  << endl;
    cout << "chest = " << chest << " (decimal for 42)" << endl;
    cout << hex;      // 진법을 바꾸는 조정자
    cout << "waist = " << waist << " (hexadecimal for 42)" << endl;
    cout << oct;      // 진법을 바꾸는 조정자
    cout << "inseam = " << inseam << " (octal for 42)" << endl;
    // cin.get();
    return 0; 
}

count << hex/oct 를 하니, 16진수와 8진수로 출력되었다.

sizeof operator

출처: cppreference
wow🤔 sizeof는 사실 함수가 아니라 연산자다. 그래서 expression인 경우 괄호를 생략할 수 있다.

    cout << "int is " << sizeof (int) << " bytes." << endl;
    cout << "short is " << sizeof n_short << " bytes." << endl;
    cout << "long is " << sizeof n_long << " bytes." << endl;
    cout << "long long is " << sizeof n_llong << " bytes." << endl;

변수 이름

  • 두 개의 밑줄 문자로 시작하는 이름이나, 밑줄 문자와 대문자로 시작하는 이름은, 그것을 사용하는 컴파일러와 리소스가 사용하기로 예약되어 있다.
  • 하나의 밑줄 문자로 시작하는 이름은, 그것을 사용하는 컴파일러와 리소스가 전역 식별자(global identifier)로 사용하기로 예약되어 있다.

03.2 const 제한자(qualifier)

💡 기호 상수를 define으로 정의하지 말고, const 제한자를 사용해서 정의하라. → 이것이 C++

왜 좋냐고?

  • 데이터형을 명시적으로 지정할 수 있음
  • C++ 활동 범위 규칙에 의해 그 정의를 특정 함수나 파일에서만 사용할 수 있도록 제한할 수 있음 → 9장 메모리 모델과 이름 공간에서 자세히 설명한다고?
// const 데이터형 상수 이름 = 값;
// 관행 중에 하나는 상수이름은 대문자로 한다.
const int MONTHS = 12;
구분#define (과거의 방식)const (현대적 방식)
동작 메커니즘전처리기가 단순히 텍스트를 찾아 치환함컴파일러가 변수로 인식하고 처리함
타입 안전성데이터 타입을 가지지 않음명시적인 데이터 타입을 가져 타입 체크 가능
디버깅 편의성기호 테이블에 이름이 남지 않아 디버깅 시 식별 불가기호 테이블에 이름과 값이 기록되어 디버깅이 용이함
유효 범위 (Scope)정의된 시점부터 파일 끝까지 유효 (전역적)특정 함수나 블록{} 내로 유효 범위를 제한 가능
메모리 활용치환될 때마다 코드의 복사본이 생길 수 있음실제 메모리 공간을 차지하여 효율적으로 관리됨

03.3 부동 소수점수

// floatnum.cpp -- floating-point types
#include <iostream>
int main()
{
    using namespace std; 
    cout.setf(ios_base::fixed, ios_base::floatfield); // fixed-point
    float tub = 10.0 / 3.0;     // good to about 6 places(3.333333)
    double mint = 10.0 / 3.0;   // good to about 15 places
    const float million = 1.0e6;

    cout << "tub = " << tub;
    cout << ", a million tubs = " << million * tub;
    cout << ",\nand ten million tubs = ";
    cout << 10 * million * tub << endl;

    cout << "mint = " << mint << " and a million mints = ";
    cout << million * mint << endl;
    // cin.get();
    return 0;
}

float형이 double형보다 정밀도가 얼마나 떨어지느냐

부동 소수점의 장단점

장점

  • 정수와 정수 사이에 있는 값을 나타낼 수 있다.
  • 스케일을 사용하여 매우 큰 범위의 값을 나타낼 수 있다.

단점

  • 수치 연산 보조 프로세서(math coprocessor)가 없는 컴퓨터에서는 정수 연산보다 속도가 느리다
    • 요새는 FPU(Floating Point Unit)와 SIMD(Single Instruction Multiple Data) 엔진이 포함되어 있어서 소수점 연산을 빛의 속도로 처리한다. 그래서 지금은 유효하지 않은 말이다. → 컴퓨터 구조 관련
  • 정밀도를 잃을 수 있다.
 cout.setf(ios_base::fixed, ios_base::floatfield); // 이거 뭐쥬?

실수를 출력할 때 소수점 자릿수를 고정하고, 지수 표기법을 쓰지 않겠다는 선언이다!

set flags 약자. 정밀도가 소수점 아래 자릿수를 의미하도록 강제한다.

비교연산자를 주의깊게 사용하자.

-> 적어도 이게 틀린 것일 수도 있다고 인지하고 있어야 한다.

double x = 0.1 + 0.2
if (x == 0.3) // 이 조건문은 참일 수도 있고, 거짓일 수도 있다. -> 근사치니까. 

정말 그럴까?

#include <iostream>
#include <iomanip>  // 소수점 출력 정밀도 조절을 위해 필요
#include <cmath>    // std::abs() 함수를 위해 필요

int main() {
    using namespace std;

    double x = 0.1;
    double y = 0.2;
    double sum = x + y;
    double target = 0.3;

    // 1. 일반적인 출력 (기본 설정으로는 0.3처럼 보임)
    cout << "일반 출력: " << sum << endl;

    // 2. 정밀도를 높여서 출력 (숨겨진 오차 발견)
    // setprecision(17)을 사용하는 이유는 double의 유효 숫자가 약 15~17자리이기 때문
    cout << fixed << setprecision(20); 
    cout << "0.1의 실체: " << x << endl;
    cout << "0.2의 실체: " << y << endl;
    cout << "합계(sum) : " << sum << endl;
    cout << "대상(target): " << target << endl;

    cout << "---------------------------------------" << endl;

    // 3. 직접 비교 테스트
    if (sum == target) {
        cout << "결과: sum과 0.3은 정확히 같습니다." << endl;
    } else {
        cout << "결과: sum과 0.3은 다릅니다!" << endl;
        cout << "두 값의 차이: " << sum - target << endl;
    }

    return 0;
}

profile
수학을 사랑하는 애독자📚 Stop dreaming. Start living. - 'The Secret Life of Walter Mitty'

0개의 댓글