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

Erdos·2026년 4월 12일

서재

목록 보기
16/18
post-thumbnail

저자: Stephen Prata

ch04: 복합 데이터형

배열, 구조체, 포인터 → C++ 의 복합 데이터형

04.1 배열

초기화 형식은 배열을 정의하는 곳에서만 사용할 수 있다.

int yamcosts[3] = {20, 30, 5}; (o)

책 내용은 C++11 배열 초기화에 대해 다루는데, 내가 수행할 과제는 C++98에 맞춰야 해서 C++98허용 규칙 내용 위주로 정리.

  1. 선언 시점에만 배열 초기화 가능

  2. 크기 생략 가능

    int yamcost[] = {20, 30, 5};

    컴파일러가 배열 원소의 개수를 결정하도록 위임하는 건 좋지 않다고는 하지만, 상황에 따라 다르다고 함.

  3. 부분 초기화 가능

    int yamcost[5] = {20, 30}; //나머지는 0으로 자동 초기화

04.2 문자열

배열 중에서도 문자열을 구분하는 방법 → 끝에 꼭 ‘\0’

if char dog[8] = { ‘b’, ‘e’, ‘a’, ‘u’, ‘x’, ‘ ‘, ‘I’, ‘I’} → 문자열 아니다.
cout으로 출력한 경우, 우연히 널 문자를 만날 때까지 출력이 이어진다.

char fish[] = "Bubbles"; # 컴파일러가 알아서 '\0'을 넣는다.
char bird[11] = "Mr. Cheeps"; # 여기서도 '\0'이 저장된다.

문자열 입력

  • cin이 문자열 끝을 인식하는 방법: 빈칸, 탭과 같은 화이트 스페이스
  • 예컨대, cin>> hello world라고 입력하면 hello로 출력한다.
  • 그래서 화이트스페이스가 끼어 있는 형태를 입력하기 원한다면 getline(), get() 을 쓰면 된다.

getline()

  • cin.getline()은 Enter가 전달하는 개행 문자를 읽고 그것을 널 문자로 대체한다.

get()

  • getline()처럼 동작하지만, 개행 함수를 버리지 않고 입력 큐에 담아둔다. 그래서 get함수를 여러번 호출하면 문제가 발생한다.
// 이를 해결하려면 ..

cin.get(name, ArSize);
cin.get();
cin.get(dessert, Arsize); 

# 아니면 
cin.get(name, ArSize).get();
cin.get(dessert, Arsize).get();

getline vs get

  • getline 동작
    • 예시) apple\n
    • a,p,p,l,e를 차례로 읽어서 변수에 저장
    • 개행을 만나면 getline() 은 개행을 읽어서 버퍼에서 꺼냄
    • 개행을 \0으로 대신 저장
  • 이렇게 설계한 이유
    • 엔터는 입력을 끝냈다는 신호. 버퍼에서 치워주되, 실제 데이터에는 넣지 말자
  • get과의 차이

cin.getline(str) vs getline(cin, str)

  • 구식 vs 신식, iostream vs string

헤더 간의 의존성, 계층적 설계

  1. 역사적으로 cin.getline()이 먼저. 문제는 iostream 내에 설계되어 있었다. 나중에 str::string가 표준 라이브러리에 추가
    • 여기서 문제: iostream을 포함할 때마다 string(비교적 큰 라이브러리)를 불러와야 하나?????
  2. 해결책: 전역 함수를 만들자!기존의 cin은 건드리지 말고 둘다 아는 새로운 함수를 밖에다 만들어보자.
    2개의 차이점 체크.
    • cin.getline(arr, size): cin이라는 객체 내부에 있는 기능을 꺼내 쓰는 것 (멤버 함수)
    • getline(cin, str): 외부의 독립된 함수cinstr이라는 재료를 던져주고 "둘이 협력해서 한 줄 읽어와!"라고 시키는 것 (전역 함수)

04.4 구조체

struct inflatable   // structure declaration 
// inflatable은 태그라고 불리는 새로운 데이터형의 이름이 된다.
{
    char name[20];
    float volume;
    double price;
};

int main()
{
    using namespace std;
    inflatable guest = 
    {
        "Glorious Gloria",  // name value
        1.88,               // volume value
        29.99               // price value
    };  // guest is a structure variable of type inflatable

04.5 공용체

  • 어떤 데이터가 상황에 따라 int일 수도 있고 double일 수도 있지만, 동시에 두 가지일 수는 없을 때 → 메모리를 아끼기 위한 선택
  • 구조체 vs 공용체
    • 구조체: 모든 멤버가 자기만의 메모리를 가짐
    • 공용체: 모든 멤버가 똑같은 방 하나를 공유함 → 한 번에 하나의 멤버만 사용할 수 있다.

열거형 → 정수

04.7 포인터와 메모리 해제

  • 포인터 변수에 1을 더하면 그 포인터가 지시하는 데이터형의 바이트 수만큼 값이 증가한다.

포인터의 요약

  • 포인터의 간접 참조: 포인터가 지시하는 주소에 저장되어 있는 값을 참조한다.
    • 간접 값(indirect value) 연산자 또는 간접 참조(dereferencing) 연산자 ⇒ *
  • 말이 조금 이해가 안되서.. 코드는 이해가 된다.

cout 을 비롯한 대부분의 C++표현에서, char형의 배열 이름, char형을 지시하는 포인터, 큰따옴표로 둘러싸인 문자열 상수는 모두 문자열의 첫 번째 문자의 주소로 해석된다

#include <iostream>
#include <cstring>              // declare strlen(), strcpy()
int main()
{
    using namespace std;
    char animal[20] = "bear";   // animal holds bear
    const char * bird = "wren"; // bird holds address of string
    char * ps;                  // uninitialized

    cout << animal << " and ";  // display bear
    cout << bird << "\n";       // display wren
    // cout << ps << "\n";      //may display garbage, may cause a crash

    cout << "Enter a kind of animal: ";
    cin >> animal;              // ok if input < 20 chars
    // cin >> ps; Too horrible a blunder to try; ps doesn't
    //            point to allocated space

    ps = animal;                // set ps to point to string
    cout << ps << "!\n";       // ok, same as using animal
    cout << "Before using strcpy():\n";
    cout << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;

    ps = new char[strlen(animal) + 1];  // get new storage
    strcpy(ps, animal);         // copy string to new storage
    cout << "After using strcpy():\n";
    cout << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;
    delete [] ps;
    // cin.get();
    // cin.get();
    return 0; 
}

코드는 당연한 소리 하는 것 같은…데? 말이 어렵다.

어쨋든 배열에 문자열을 대입할 때에는 대입 연산자를 사용할 것이 아니라, strcpy() 또는 strncpy()를 사용하란다.

포인터와 배열과의 관계

→ 이건 c도 이러했다.

 ar[i] = *(ar + i)

nullptr vs NULL

  • nullptr(c++11이상부터) → nullptr_t : 절대로 int 타입으로 자동 형변환하지 않는다.
    • 실수로 포인터 자리에 정수를 넣거나 정수 자리에 포인터를 넣는 행위를 컴파일 단계에서 철저하게 차단
    • 그리고 포인터 함수를 좀 더 확실하게 찾아간다고 함.
  • 그러면 c++98기준으로는 어떻게 사용해야 하려나?
    • c++에서는 NULL보다 0을 좀 더 선호한다고…(! 아니 어느 장단에 맞춰야 해??)

const와 포인터

  • const int *ptr = 가리키는 내용물을 상수로 만듦(내용 변경 불가)
  • int * const ptr = 포인터 변수 자체를 상수로(다른 주소로 이사 불가)
  • const int* const ptr = 둘 다 못 바꿈

04.10 배열의 대안

vector 템플릿 클래스

#include <vector>

vector<int> vi;
vector<double> vd(n); // n개의 더블 배열을 만들어라

array 템플릿 클래스 → C++98에서는 안됨 C++11부터 가능.

일반 배열 vs std::vector vs std::array(C++11)

구분일반 배열 (int a[n])std::vectorstd::array (C++11)
메모리 위치스택(Stack)힙(Heap)스택(Stack)
크기 결정컴파일 타임런타임 (가변)컴파일 타임
속도최상보통 (동적 할당 비용)최상
안전성낮음 (경계 검사 없음)높음높음

ch05: 루프와 관계표현식

05.1 for 루프

접두어 방식과 접미어 방식

  • 프로그램의 행동에 영향을 주지는 않지만, 실행 속도는 조금 있다.
  • 접두어 방식 ← 미묘하게 이쪽이 조금 더 효율적이다.
for (n = lim; n > 0; --n)
  • 접미어 방식
for ( n = lim; n > 0; n--)

++i vs i++

숫자가 아닌 객체라면 후위연산자의 객체가 훨씬 무겁다!

  • ++i: ⭐⭐⭐ 값을 먼저 증가시키고 그 결과를 반환. 추가적인 메모리 복사 없음.
  • i++: 현재 값을 어딘가에 임시로 복사하고 값을 증가시킨 후, 아까 복사해둔 옛날 값을 반환.
    • 조금만 더 이해해보자.
    • 값을 증가시키기 전 상태를 저장하기 위해 복사 생성자 호출. 끝나면 소멸자 호출.

🔎 C++ 에서 성능 최적화 관점에서 객체를 다룰 때는 전위연산자를 선호한다.

→ 후위 연산자는 증가 전의 원래 값을 기억하기 위해 임시 복사본을 만드는 과정이 필요함.

→ 정수형(int)에서는 차이가 없지만 반복자나 복잡한 객체를 다룰 때 성능상 유리함

무한루프

while(1)
for (;;)

cin vs cin.get()

  • cin은 기본적으로 의미있는 데이터만 뽑아내도록 설계되었다. 그래서 whitespace를 건너뜀
  • cin.get() → 공백을 포함한 모든 문자를 읽으려면.

그런데 말입니다 cin은 EOF(end of file)을 어떻게 알 수 있을까?

  • cin.eof(): EOF 에 도달했는지 확인
  • cin.fail(): 입력 오류나 EOF 발생 시 true
cin.get(ch);
while(cin.fail() == false)
{
	cin.get(ch);
}

조금 더 깔끔하게 개선하면,

while (cin.get(ch)) // 입력이 성공했으면 루프를 실행해라
{

}

cin.get은 문자가 아닌 객체를 리턴하는데, 혹시 문자 출력을 하고 싶으면 cin.put(ch)를 사용하면 된다. → cin.put(ch)에서 EOF의 의미는 더이상 문자가 없어요~ 라는 신호

이전에 기록한 내용 >

cin >> ch # 의미 있는 데이터만. whitespace는 무시
cin.get(ch) # whitespace까지 포함하여 모든 것을 1바이트씩 읽음
  • cin >> ch와 cin.get()을 혼용해서 사용하면, 흔히 입력이 씹혔다고 표현하는 현상이 발생할 수 있다.

Ch06: 분기 구문과 논리 연산자

문자 함수를 위한 cctype 라이브러리

#undef isalnum
#undef isalpha
#undef iscntrl
#undef isdigit
#undef isgraph
#undef islower
#undef isprint
#undef ispunct
#undef isspace
#undef isupper
#undef isxdigit
#undef tolower
#undef toupper

switch문이 성능적으로 유리할까?

  • 3개 이상의 조건(조건이 많을수록 유리)
  • 그 조건 값들이 정수, 열거형이라면 → if else보다는 switch를 쓰면 가독성과 성능적인 유리함(해당 코드 위치로 즉시 점프 가능)
    • break 잘 활용하기: break가 없으면 아래쪽 case들을 멈추지 않고 다 실행.

while 문 안에 있는 cin 의 타입

// cinfish.cpp -- non-numeric input terminates loop
#include <iostream>
const int Max = 5;
int main()
{
    using namespace std;
// get data
    double fish[Max];
    cout << "Please enter the weights of your fish.\n";
    cout << "You may enter up to " << Max
            << " fish <q to terminate>.\n";
    cout << "fish #1: ";
    int i = 0;
    while (i < Max && cin >> fish[i]) 
	// 숫자가 아닌게 들어와서 실패하면 while을 빠져나감.
	// q가 아니라 다른 숫자가 아닌 것을 입력해도 cin이 종료된다.
	// cin 은 while문 안에서 bool 타입으로 자동 변환된다.
	{
        if (++i < Max)
            cout << "fish #" << i+1 << ": ";
    }
// calculate average
    double total = 0.0;
    for (int j = 0; j < i; j++)
        total += fish[j];
// report results
    if (i == 0)
        cout << "No fish\n";
    else
        cout << total / i << " = average weight of "
            << i << " fish\n";
    cout << "Done.\n";
// code to keep VC execution window open if q is entered
//	if (!cin)  // input terminated by non-numeric response
//	{
//	    cin.clear();  // reset input
//	    cin.get();    // read q
//	}
//	cin.get();    // read end of line after last input
//	cin.get();    // wait for user to press <Enter>
    return 0; 
}

주석 처리 된 부분이 입력 버퍼에 남아 있는 q에 대한 내용이다.

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

0개의 댓글