알고리즘 사용, 프로그램 계획

STL은 이터레이터를 통해 컨테이너의 요소를 조작할 수 있는 일련의 알고리즘을 정의한다.

이 알고리즘들은 검색, 랜점, 정렬가 같은 일반적인 작업을 위해 존재한다.

제너릭한 방법으로 컨테이너 요소를 조작하는 단순한 작업을 STL에 맡길 수 있다.

이 알고리즘들의 강력한 점을 그들이 일반적, 제너릭하다는 점이다.

 

제너릭 프로그래밍은 다양한 유형의 데이터나 자료구조에 대해 일반적인 알고리즘을 작성하는 방법론이다.

데이터나 자료구조의 특정 유형에 의존하지 않고, 일반적으로 작동하는 코드를 작성할 수 있도록 해준다.

제네릭을 사용하면 코드의 재사용성이 향상되고, 유지보수가 쉬워지며, 더 깔끔하고 효율적인 코드를 작성할 수 있다.

 

// High Scores
// Demonsrates algorithms

#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
using namespace std;

int main() {
    vector<int>::const_iterator iter;

    cout << "Creating a list of scores.";
    vector<int> scores;
    scores.push_back(1500);
    scores.push_back(3500);
    scores.push_back(7500);

    cout << "\nHigh scores:\n";
    for (iter = scores.begin(); iter != scores.end(); ++iter) {
        cout << *iter << endl;
    }

    cout << "\nFinding a score.";
    int score;
    cout << "\nEnter a score to find: ";
    cin >> score;
    iter = find(scores.begin(), scores.end(), score);
    if (iter != scores.end()) {
        cout << "Score found.\n";
    }
    else {
        cout << "Socre not found.\n";
    }

    cout << "\nRandomizing scores.";
    srand(static_cast<unsigned int>(time(0)));
    random_shuffle(scores.begin(), scores.end());
    cout << "\nHigh Scores:\n";
    for (iter = scores.begin(); iter != scores.end(); ++iter) {
        cout << *iter << endl;
    }

    cout << "\nSorting scores.";
    sort(scores.begin(), scores.end());
    cout << "\nHigh Socres:\n";
    for (iter = scores.begin(); iter != scores.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;

}

 

STL 알고리즘을 사용하기 위해 알고리즘 정의가 들어 있는 파일을 포함시킨다. -> #include <algorithm>

모든 STL 구성 요소는 std 네임스페이스에 속해있다.

 

find() STL 알고리즘

컨테이너 요소의 지정된 범위에서 값을 검색한다.

일치하는 첫 번째 요소를 참조하는 이터레이터를 반환한다.

일치하는 값이 없으면 범위의 끝을 참조하는 이터레이터를 반환한다.

위의 코드에서는 시작 이터레이터, 끝 이터레이터, 찾을 값 3개의 인수를 전달했다.

전체 값을 검색하여 score 값이 발견되지 않은 경우, iter와 scores.end()가 동일해진다.

 

random_shuffle 알고리즘

점수를 무작위로 재배열 한다.

단일 난수를 생성할 때와 마찬가지로 난수 생성기에 시드를 제공하여 프로그램을 실행할 때마다 점수의 순서가 다르도록 한다.

이 알고리즘은 시퀀스의 요소를 무작위로 섞는다.

시퀀스의 시작점과 끝점에 대한 이터레이터를 인수로 제공해야 한다.

 

sort() 알고리즘

시퀀스의 요소를 오름차순으로 정렬한다.

시퀀스의 시작점과 끝점에 대한 이터레이터를 인수로 제공해야 한다.

 

+ STL 알고리즘의 매우 멋진 특성 중 하나는 STL 외부에 정의된 컨테이너와 함께 작동할 수 있다는 점이다.

이러한 컨테이너는 특정 요구 사항을 충족하기만 하면 된다.

예를 들어, string 객체는 STL의 일부가 아니다. 그렇지만 적절한 STL 알고리즘을 사용할 수 있다.

string word = "High Scores";
random_shuffle(word.begin(), word.end());

 

 

컨테이너에는 각각의 장단점이 있으며, 프로그래머는 작업에 적합한 올바른 컨테이너 유형을 선택할 수 있도록 각 컨테이너 유형의 성능 특성을 이해해야 한다.

 

벡터는 필요에 따라 동적으로 확장하지만, 모든 벡터는 특정 크기를 가지고 있다.

새로운 요소를 벡터에 추가하여 현재 크기를 넘어서면, 컴퓨터는 메모리를 재할당하고 심지어 모든 벡터 요소를 이 새롭게 할당한 메모리 공간으로 복사한다. 이는 성능에 영향을 줄 수 있다.

 

벡터 메모리 재할당이 프로그램의 성능에 중요하지 않는 경우 재할당 비용을 안전하게 무시할 수 있다.

작은 벡터의 경우 재할당 비용이 무시할 만큼 미미할 수 있다.

 

capacity() 벡터 멤버 함수

벡터의 용량을 반환한다.

즉, 프로그램이 더 많은 메모리를 할당하기 전에 벡터가 보유할 수 있는 요소의 수이다.

벡터의 용량은 현재 크기와 같지 않다.

cout << "10개의 요소를 보유할 벡터를 생성합니다.\n"; 
vector<int> scores(10, 0); // 모든 10개 요소를 0으로 초기화 
cout << "벡터 크기는 :" << scores.size() << endl;  //10
cout << "벡터 용량은:" << scores.capacity() << endl;   //10

cout << "점수를 추가합니다.\n";
scores.push_back(0); // 성장을 수용하기 위해 메모리가 재할당됨 
cout << "벡터 크기는 :" << scores.size() << endl;  //11
cout << "벡터 용량은:" << scores.capacity() << endl;   //20

 

요소가 추가된 후에 벡터의 크기는 11이고 용량은 20이다.

프로그램이 추가 메모리를 위해 메모리를 재할당할 때마다 벡터의 용량이 두 배로 증가하기 때문이다.

 

 

reserve() 멤버 함수

인수로 제공된 수의 요소를 수용할 수 있도록 벡터의 용량을 증가시킨다.

이 함수를 사용하면 추가 메모리 재할당이 말생하는 시점을 제어할 수 있다.

cout << "점수 목록을 생성합니다.\n";
vector<int> scores(10, 0); // 모든 10개 요소를 0으로 초기화 
cout << "벡터 크기는 :" << scores.size() << endl;
cout << "벡터 용량은:" << scores.capacity() << endl;
cout << "더 많은 메모리를 예약합니다.\n";
scores.reserve(20); // 10개의 추가 요소에 대한 메모리 예약 
cout << "벡터 크기는 :" << scores.size() << endl;
cout << "벡터 용량은:" << scores.capacity() << endl;

 

10개의 추가 요소에 대한 메모리를 예약한 후에도,

벡터의 크기는 여전히 10이고 용량은 20이 된다.

reserve() 함수를 사용하여 벡터의 용량을 충분히 크게 유지함으로써, 메모리 재할당을 원하는 시점으로 지연시킬 수 있다.

 

벡터에 대해 추가 또는 삭제를 수행할 때, push_back() 또는 pop_back() 함수를 사용하여 벡터 끝에서 요소를 추가하거나 제거하는 것이 극도록 효율적이다.

그러나 벡터의 다른 위치에 요소를 추가하거나 제거해야 하는 경우(insert() 또는 erase()), 여러 요소를 이동해야 할 수 있기 때문에 더 많은 작업이 필요할 수 있다.

작은 벡터의 경우에는 이러한 오버헤드가 무시할 수 있지만, 큰 벡터의 경우 중간에 요소를  삽입하거나 제거하는 것이 성능에 영향을 줄 수 있다.

다행히도, STL은 sequence container 유형 중 하나인 list를 제공하여 시퀀스 크기에 관계없이 효율적인 삽입 및 삭제가 가능하다.

 

STL 컨테이너

두 가지 기본 카테고리로 나뉜다.

1. 시퀀셜 컨테이너 : 시퀀스에서 값을 검색

2. 연관 컨테이너 :  키를 기반으로 값을 검색

 

게임을 예로 들면,

시퀀스를 순환하고 싶은 플레이어 그룹을 저장하는 데에는 벡터와 같은 시퀀셜 컨테이너를 사용한다.

고유한 식별자인 플레이어의 IP 주소를 찾아 랜덤한 방식으로 플레이어 정보를 검색하고 싶은 경우, 연관 컨테이너를 사용한다.

 

STL은 하나의 시퀀스 컨테이너를 적응시키는 컨테이너 어댑터를 정의한다.

컨테이너 어댑터는 STl에서 제공하는 컨테이너 유형 중 하나이다.

다른 컨테이너를 적응시켜서 새로운 인터페이스를 제공하는 방법으로 구현된다.

기존의 컨테이너를 다른 형태로 사용하거나 확장할 수 있도록 한다.

스택, 큐, 우선순위 큐 등이 해당된다.

 

 

 


프로그램 계획

프로그램을 계획하는 것은 프로그램이 효율적으로 잘 구조화되고 목표를 달성하게 되는 데에 중요하다.

계획하는 일반적인 방법 중 하나는 의사 코드(pseudocode)를 사용하는 것이다.

의사 코드는 프로그래밍 언어와 비슷한 일반 언어를 사용하여 프로그램 로직을 높은 수준에서 설명하는 것이다.

의사 코드는 프로그래밍 언어에 익숙하지 않은 사람도 이해할 수 있어야 한다.

새로운 유용한 제품을 생각할 수 있다면
    그것이 당신의 제품입니다
그렇지 않으면
    기존 제품을 재패키징하여 당신의 제품으로 만듭니다
    당신의 제품에 대한 인포머셜을 만듭니다
    TV에서 인포머셜을 방영합니다
    제품 당 $100을 청구합니다
    당신의 제품을 10,000개 판매합니다

 

의사 코드가 프로그램 코드와 유사하게 느껴져야 한다.

 

단계별 세분화를 사용하면 의사 코드를 프로그래밍 코드로 재작성할 수 있다.

단계별 세분화는 매우 간단한 과정이다.

기본적으로 "세부 사항을 더 구체화하라"는 의미이다.

의사 코드에서 설명된 각 단계를 가져와서 간단한 단계의 시리즈로 나누면 계획이 프로그래밍 코드에 더 가까워진다.

각 단계를 계속해서 세분화하다가 전체 계획이 프로그램으로 비교적 쉽게 번역될 수 있을 때까지 진행한다.

 

위 코드에서 '당신의 제품에 대한 인포머셜을 만듭니다'를 세분화하면 

당신의 제품에 대한 인포머셜에 대한 대본을 작성합니다
하루동안 TV 스튜디오를 빌립니다
제작 크루를 고용합니다
열정적인 관객을 고용합니다
인포머셜을 촬영합니다

이 다섯단계가 명확하고 달성 가능하다고 생각되면 해당 부분의 의사 코드가 충분히 세밀하게 세분화된 것이다.

 

 

행맨 프로그램 계획

행맨 프로그램에서 컴퓨터는 비밀 단어를 선택하고 플레이어는 한 번에 한 글자씩 추측한다.

플레이어는 8번의 잘못된 추측이 허용된다.

만약 플레이어가 제한 시간 내에 단어를 추측하지 못하면, 플레이어는 처형되고 게임이 종료된다.

 

1. 의사코드 작성

단어 그룹을 만든다.
그룹에서 무작위로 단어를 선택하여 비밀 단어로 정한다.
플레이어가 너무 많은 잘못된 추측을 하지 않았고 비밀 단어를 맞히지 않은 동안
	플레이어게 남은 잘못된 추측 횟수를 알려준다.
    플레이어가 이미 추측한 글자를 보여준다.
    플레이어가 지금까지 추측한 비밀 단어의 일부를 보여준다.
    플레이어의 다음 추측을 받는다.
    플레이어가 이미 추측한 글자를 입력하는 동안
    	플레이어의 추측을 받는다.
    새로운 추측을 이미 사용한 글자 그룹에 추가한다.
    만약 추측이 비밀 단어에 있다면
    	플레이어에게 추측이 맞다고 알려준다.
        지금까지 추측한 단어를 새로운 글자로 업데이트한다.
    그렇지 않다면
    	플레이어에게 추측이 잘못되었다고 알려준다.
        잘못된 추측 횟수를 증가시킨다.
플레이어가 너무 많은 잘못된 추측을 했다면
	플레이어에게 처형되었다고 알려준다.
그렇지 않다면
	플레이어가 비밀 단어를 맞힌 것을 축하해준다.

 

 

2. 프로그램 설정

일반적으로, 몇 가지 주석으로 시작하고 필요한 파일을 포함시킨다.

// Hangman
// The classic game of hangman
#include <iostream> 
#include <string> 
#include <vector> 
#include <algorithm> 
#include <ctime> 
#include <cctype>
using namespace std;

 

cctype : 표준 라이브러리 중 하나이며, 문자를 대문자로 변환하는 함수가 포함되어 있다. 개별 문자를 비교할 때 대문자와 대문자를 비교하려고 하기 때문에 사용한다.

 

3. 변수 및 상수 초기화

main() 함수 작성

int main() {
    //setup
    const int MAX_WRONG = 8;

    vector<string> words; // collection of possible words to guess
    words.push_back("GUESS");
    words.push_back("HANGMAN");
    words.push_back("DIFFICULT");

    srand(static_cast<unsigned int>(time(0)));
    random_shuffle(words.begin(), words.end());
    const string THE_WORD = words[0];
    int wrong = 0;
    string soFar(THE_WORD.size(), '-');
    string used = "";

    cout << "Welcome to Hangman. Good Luck!\n";
}

 

words는 문제로 낼 단어들의 벡터이다.

random_shuffle() 알고리즘을 사용하여 words를 무작위로 섞은 다음, THE_WORD에 벡터의 첫 번째 단어를 할당한다.

wrong은 플레이어가 틀린 횟수이다. soFar는 플레이어가 지금까지 추측한 단어이다.

soFar는 비밀 단어의 각 문자에 해당하는 대시로 싲갛나다.

플레이어가 비밀 단어에 있는 문자를 추측하여 해당 위치의 대시를 해당 문자로 바꾼다.

 

4. 메인 루프 진입

 //main loop
while ((wrong < MAX_WRONG) && (soFar != THE_WORD)) {
    cout << "\n\nYou have " << (MAX_WRONG - wrong);
    cout << " incorrect guesses left.\n";
    cout << "\nYou've used the following letters:\n" << used << endl;
    cout << "\nSo far, the word is :\n" << soFar << endl;
}

 

 

 

5. 플레이어에게 추측 입력 받기

    char guess;
    cout << "\n\nEnter your guess: ";
    cin >> guess;
    guess = toupper(guess);
    while (used.find(guess) != string::npos) {
        cout << "\nYou've already guessed "<< guess << endl;
        cout << "Enter your guess: ";
        cin >> guess;
        guess = toupper(guess);
    }

    used += guess;

    if (THE_WORD.find(guess) != string::npos) {
        cout << "That's right! " << guess << " is in the word.\n";

        //update soFar to include newly guessed letter
        for (int i=0; i < THE_WORD.length(); ++i) {
            if (THE_WORD[i] == guess) {
                soFar[i] = guess;
            }
        }
    }
    else {
        cout << "Sorry, " << guess << " isn't in the word.\n";
        ++wrong;
    }

 

만약 플레이어가 이미 추측한 문자를 다시 추측하면, 플레이어에게 다시 추측하라고 한다.

플레이어가 문자를 올바르게 추측하면, 지금까지 추측한 단어를 업데이트한다.

그렇지 않으면, 비밀 단어에 추측이 없다고 알리고, 틀린 횟수를 증가시킨다.

 

 

 

6. 게임 종료

 //shut down
    if (wrong == MAX_WRONG) {
        cout << "\nYou've been hanged!";
    }
    else {
        cout << "\nYou guessed it!";
    }

    cout << "\nThe word was " << THE_WORD << endl;

    return 0;

 

 

 

 


Q & A

Q : STL이 중요한 이유는 ?

A : 프로그래머에게 시간과 노력을 절약해준다. 일반적으로 사용되는 컨테이너 유형과 알고리즘을 제공한다.

 

Q : STL은 빠른가 ?

A : 그렇다. 여러 프로그래머에 의해 다양한 플랫폼에서 최대의 성능을 끌어낼 수 있도록 미세 조정되었다.

 

Q : 언제 배열 대신 벡터를 사용하는가 ?

A : 거의 항상. 벡터는 효율적이고 유연하다. 배열보다 조금 더 많은 메모리가 필요하긴 하지만 거의 항상 이득이다.

 

Q : 벡터에 대괄호 연산자를 사용할 수 있다면 반복자가 필요한 이유

A : 많은 벡터 함수가 반복자를 필요로 한다(insert(), erase()). 대부분의 STL 컨테이너에서 대괄호 연산자를 사용할 수 없으므로, 언젠가는 사용해야 함. 

 

Q : STL은 왜 여러 시퀀셜 컨테이너 유형을 정의하는가?

A : 서로 다른 시퀀셜 컨테이너는 서로 다른 성능 특성을 가지고 있다.

 

Q : 컨테이너 어댑터란 ?

A : STL 시퀀스 컨테이너 중 하나를 기반으로 하며 표준 컴퓨터 데이터 구조를 나타낸다.

 

Q : 점진적 세부화는 언제 ?

A : 의사 코드를 더 자세히 작성하려는 경우

 


 

Discussion Questions

  1. Why should a game programmer use the STL?
    • Standard Template Library는 게임 프로그래머가 노력과 시간을 절약할 수 있는 이유이다. 널리 사용되는 컨테이너 유형과 알고리즘을 제공하여 코드 작성과 유지 관리를 단순화한다. 이러한 컨테이너 및 알고리즘은 이미 검증되고 최적화되어 있으며, 다양한 프로그래밍 작업에 사용할 수 있다. 따라서 STL을 사용하면 개발자가 직접 구현할 필요 없이 표준화된 구성요소를 활용하여 빠르고 안정적인 코드를 작성할 수 있다.
  2. What are the advantages of a vector over an array?
    • 크기의 동적 조절 : 배열과 달리 실행 중에 요소를 추가하거나 제거할 수 있다.
    • 메모리 관리 :  벡터는 요소가 추가되거나 제거될 때 자동으로 메모리를 재할당하고 관리한다.
    • 반복자 지원 : 이터레이터를 통해 요소에 접근할 수 있다. 범위 기반 루프와 같은 반복 작업을 훨씬 간편하게 만든다.
    • 알고리즘과의 통합 :  STL 알고리즘과의 통합이 용이하다. 벡터는 STL 컨테이너이므로 STL 알고리즘과 함께 사용할 수 있다.
  3. What types of game objects might you store with a vector?
    • 플레이어 캐릭터나, 적 캐릭터
    • 아이템이나 강화템
    • 장애물 및 지형
  4. How do performance characteristics of a container type affect the decision to use it?
    • 속도 : 컨테이너의 삽입, 삭제, 검색 및 반복 속도는 알고리즘과 자료 구조에 따라 다르다. 특정 작업에 빠르고 효율적이지만 다른 작업에는 느릴 수 있다. 따라서 프로그램의 성능 요구 사항에 맞는 가장 효율적인 컨테이너를 선택해야 한다.
    • 메모리 사용량 :  컨테이너는 메모리를 다르게 사용한다.
    • 데이터 구조 : 컨테이너의 내부 데이터 구조는 작업을 수행하는 데 중요하다. 작업의 종류와 크기에 따라 가장 적합한 데이터 구조를 선택해야 한다.
    • 알고리즘 호환성 :  특정 알고리즘이나 작업에는 특정 컨테이너 타입이 필요할 수 있다.
  5. Why is program planning important?
    • 구조화된 개발 : 전체적인 구조를 파악하고 개발을 구조화할 수 있다. 프로그램의 각 부분이 어떻게 상호작용하고 연결되는 지를 이해하는 데 도움이 된다.
    • 문제 해결 : 계획을 세우면 문제를 분석하고 해결할 수 있는 체계적인 방법을 개발할 수 있다.
    • 시간과 비용 절감 : 개발 과정에서 발생할 수 있는 문제를 미리 파악하고 예방할 수 있다. 프로젝트의 시간과 비용을 절약하는 데 도움이 된다.
    • 의사 소통 : 계획은 팀 간의 의사소통을 촉진한다. 각 구성원이 프로그램의 목표와 요구사항을 이해하고 역할 및 책임을 명확하게 알 수 있다.

 

Exercises

1. Write a program using vectors and iterators that allows a user to main- tain a list of his or her favorite games. The program should allow the user to list all game titles, add a game title, and remove a game title.

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

int main() {
    vector<string> favoriteGames;

    while (true) {
        cout << "\nChoose an option:\n";
        cout << "1. List all game titles\n";
        cout << "2. Add a game title\n";
        cout << "3. Remove a game title\n";
        cout << "4. Quit\n";

        int choice;
        cin >> choice;

        switch (choice) {
        case 1:
            if (favoriteGames.empty()) {
                cout << "No game titles available.\n";
            }
            else {
                cout << "Your favorite game titles:\n";
                for (const string& game : favoriteGames) {
                    cout << game << endl;
                }
            }
            break;
        case 2: {
            cout << "Enter the name of the game to add: ";
            string newGame;
            cin >> newGame;
            favoriteGames.push_back(newGame);
            break;
        }
        case 3: {
            if (favoriteGames.empty()) {
                cout << "No game titles to remove.\n";
            }
            else {
                cout << "Enter the name of the game to remove: ";
                string gameToRemove;
                cin >> gameToRemove;
                auto it = find(favoriteGames.begin(), favoriteGames.end(), gameToRemove);
                if (it != favoriteGames.end()) {
                    favoriteGames.erase(it);
                }
                else {
                    cout << "Game title not found.\n";
                }
            }
            break;
        }
        case 4:
            cout << "Exiting program.\n";
            return 0;
        default:
            cout << "Invalid choice. Please try again.\n";
        }
    }
}

 

2. Assuming that scores is a vector that holds elements of type int, whats wrong with the following code snippet (meant to increment each element)?

vector<int>::iterator iter;
//increment each score
for (iter = scores.begin(); iter != scores.end(); ++iter) {

iter++; }

 

이 코드는 루프 본문에서 iter를 증가시키고 있다.

이미 for 루프에서 ++iter를 사용하여 증가시키고 있기 때문에 추가적으로 호출할 필요가 없다.

본문에서는 '*iter' 를 사용하여 iter 자체가 아니라 iter가 가리키는 요소를 수정해야 한다.

 

 

 

벡터 사용 및 반복

 

게임 프로그래머가 좋은 게임을 만들고자 할 때 사용하는 도구 중 하나가 STL(표준 템플릿 라이브러리)이다.

잘 개발된 프로그래밍 작업을 모아놓은 도구 모음이다.

STL은 컨테이너, 알고리즘, 반복자 등을 제공한다.

 

컨테이너란 ?

컨테이너는 동일한 유형의 값의 컬렉션을 저장하고 액세스할 수 있도록 해준다.

배열도 동일한 작업을 수행할 수 있지만, STL 컨테이너는 간단하지만 더 많은 유연성과 기능을 제공한다.

STL은 다양한 종류의 컨테이너 유형을 정의하며, 각각 다른 요구 사항을 충족하기 위해 다르게 작동한다.

 

STL에 정의된 알고리즘은 해당 컨테이너와 함께 작동한다. 이러한 알고리즘은 게임 프로그래머들이 반복적으로 값을 그룹으로 적용하는 일반적인 함수이다.

정렬, 검색, 복사, 병합, 삽입, 삭제 등의 알고리즘이 포함되어 있다.

여기서 재미있는 점은 동일한 알고리즘이 여러 개의 다른 컨테이너 유형에서도 작동한다는 것이다.

 

반복자는 컨테이너에서 요소를 식별하고 요소 사이를 이동할 수 있는 객체이다.

컨테이너를 반복하는 데 용이하며 STL 알고리즘에서 필수적으로 사용된다.

 


 

벡터

벡터 클래스는 STL에서 제공하는 한 종류의 컨테이너를 정의한다.

필요에 따라 크기를 조정할 수 있는 동적 배열이다.

벡터는 벡터 요소를 조작하기 위한 멤버 함수를 정의한다.

즉 배열의 모든 기능과 더불어 추가적인 기능을 제공한다는 것이다.

 

장점

- 배열은 필요에 따라 크기를 조정할 수 없지만 벡터는 가능하다. 게임에서 적들의 객체를 저장하기 위해 벡터를 사용할 경우, 벡터가 생성된 적의 수에 맞춰 크기가 조정될 것이다. 하지만 배열을 사용하면 최대한 많은 적을 저장할 수 있는 뱅뎔을 만들어야 하고, 플레이 중에 배열에 더 많은 공간이 필요해질 경우 곤란해진다.

- 배열은 STL 알고리즘과 함께 사용할 수 없지만 벡터는 가능하다. 벡터를 사용하면 검색 . 및정렬과 같은 복잡한 기능을 내장하여 편리하게 사용할 수 있다. 배열을 사용하면 동일한 기능을 얻기 위해 직접 코드를 작성해야 한다.

 

단점

- 벡터는 오버헤드로 약간의 추가 메모리가 필요하다.

- 벡터의 크기가 커질 때 성능 비용이 발생할 수 있다.

- 일부 게임 콘솔 시스템에서는 벡터 사용이 불가할 수도 있다.

 


 

// Hero's Inventory 2.0
// Demonstrates vectors

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main() {
    vector<string> inventory;
    inventory.push_back("sword");
    inventory.push_back("armor");
    inventory.push_back("shield");

    cout << "You have " << inventory.size() << " items.\n";

    cout << "\nYour items:\n";
    for (unsigned int i=0; i < inventory.size(); ++i) {
        cout << inventory[i] << endl;
    }

    cout << "\nYou trade your sword for a battle axe.";
    inventory[0] = "battle axe";
    cout << "\nYour items:\n";
    for (unsigned int i=0; i < inventory.size(); ++i) {
        cout << inventory[i] << endl;
    }

    cout << "\nThe item name '" << inventory[0] << "' has ";
    cout << inventory[0].size() << " letters in it.\n";

    cout << "\nYour shield is destroyed in a fierce battle.";
    inventory.pop_back();
    cout << "\nYour items:\n";
    for (unsigned int i=0; i < inventory.size(); ++i) {
        cout << inventory[i] << endl;
    }

    cout << "\nYou were robbed of all of your possessions by a thief.";
    inventory.clear();
    if (inventory.empty()) {
        cout << "\nYou have nothing.\n";
    }
    else {
        cout << "\nYou have at least one item.\n";
    }
    return 0;
}

 

[코드 분석]

벡터의 정의를 포함하는 포일을 포함해야 한다 -> #include <vector>

모든 STL 구성 요소는 std 네임스페이스에 속하므로 일반적으로

using namespace std;

를 사용함으로써 std::를 앞에 붙이지 않고도 벡터를 참조할 수 있다.

비어 있는 inventory라는 이름의 벡터를 선언했다. 이 벡터는 string 객체 요소를 포함할 수 있다.

새 요소를 추가할 때 크기가 자동으로 늘어날 수 있ㄷ으므로 비어 있는 벡터를 선언하는 것이 괜찮다.

inventory(10) 으로 설정하면 시작 크기를 10으로 지정한 벡터를 선언하게 된다.

vector<string> inventory(10, "nothing");

 

위 코드는 크기가 10이며, 모든 10개 요소를 "nothing"으로 초기화하는 벡터를 선언하는 코드이다.

 

vector<string> inventory(myStuff);

 

myStuff 벡터와 동일한 내용을 가지도록 초기화한 새로운 벡터를 생성한다.

 

push_back() 멤버 함수는 벡터의 끝에 새 요소를 추가한다.

inventory_size()를 사용하여 사용자가 소지한 아이템 수를 표시한다. 벡터의 크기를 반환한다.

 

루프 변수 i를 unsigned int로 만든 이유는 size()가 부호 없는 정수형을 반환하기 때문이다.

 

벡터는 동적이지만, 첨자 연산자를 사용하여 벡터의 크기를 증가시킬 수 없다.

vector<string> inventory; // 빈 벡터 생성
inventory[0] = "sword"; // 프로그램이 충돌할 수 있습니다!

 

배열과 마찬가지로 존재하지 않는 요소 위치에 접근을 시도할 수 있지만, 이는 잠재적으로 치명적인 결과를 초래할 수 있다.

위의 코드는 컴퓨터 메모리의 알 수 없는 부분을 변경하고 프로그램이 충돌할 수 있다.

 

pop_back() 멤버 함수는 벡터의 마지막 요소를 제거하고 벡터 크기를 하나 줄인다.

clear() 멤버 함수는 벡터의 모든 항목을 제거하고 크기를 0으로 설정한다.

벡터의 empty() 멤버 함수는 string의 empty() 멤버 함수와 동일하게 작동한다. 비어 있으면 true를 반환하고, 그렇지 않으면 false를 반환한다.

실행 결과


 

이터레이터는 컨테이너의 모든 기능을 최대한 활용하는 데 필수적이다.

컨테이너의 요소를 반복하여 접근하거나 조작할 수 있는 객체이다.

포인터와 유사한 기능을 하며, 컨테이너 내의 요소를 가리키고 다음 요소로 이동할 수 있는 기능을 제공한다.

여러 STL 알고리즘 및 컨테이너의 멤버 함수에서 사용된다.

 

// Hero's Inventory 3.0
// Demonstrates iterators

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main() {
    vector<string> inventory;
    inventory.push_back("sword");
    inventory.push_back("armor"); 
    inventory.push_back("shield");

    vector<string>::iterator myIterator;
    vector<string>::const_iterator iter;

    cout << "Your items:\n";
    for (iter = inventory.begin(); iter != inventory.end(); ++iter) {
        cout << *iter << endl;
    }
    
    cout << "\nYou trade your sword for a battle axe.";
    myIterator = inventory.begin();
    *myIterator = "battle axe";
    cout << "\nYour items:\n";
    for (iter = inventory.begin(); iter != inventory.end(); ++iter) {
        cout << *iter << endl;
    }

    cout << "\nThe item name '" << *myIterator << "' has ";
    cout << (*myIterator).size() << " letters in it.\n";

    cout << "\nThe item name '" << *myIterator << "' has ";
    cout << myIterator->size() << " letters in it.\n";

    cout << "\nYou recover a crossbow from a slain enemy.";
    inventory.insert(inventory.begin(), "crossbow");
    cout << "\nYour items:\n";
    for (iter = inventory.begin(); iter != inventory.end(); ++iter) {
        cout << *iter << endl;
    }

    cout << "\nYour armor is destroyed in a fierce battle.";
    inventory.erase((inventory.begin()+2));
    cout << "\nYour items:\n";
    for (iter = inventory.begin(); iter != inventory.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}

 

[코드 분석]

벡터를 위한 이터레이터를 선언하면, 해당 이터레이터를 사용하여 컨테이너의 특정 요소를 식별할 수 있다.

이터레이터를 통해 특정 요소의 $$$값을 접근할 수 있으며, 적절한 종류의 이터레이터를 사용하면 값을 변경할 수도 있다.

이터레이터를 특정 컨테이너 내의 요소에 붙일 수 있는 포스트잇처럼 생각하면 이해가 쉽다.

이터레이터는 요소 자체가 아니라 요소를 참조하는 방법이다.

myIterator를 사용하여 벡터 inventory의 특정 요소를 참조할 수 있다.

즉, myIterator를 inventory의 특정 요소에 붙일 수 있다.

이렇게 하면 myIterator를 통해 해당 요소에 액세스하거나 해당 요소의 값을 변경할 수 있다.

 

또 다른 이터레이터 const_iterator를 선언하였다.

문자열 객체를 포함하는 벡터에 대한 상수 이터레이터이다.

상수 이터레이터는 일반적인 이터레이터와 동일하지만, 참조하는 요소를 변경할 수 없다.

읽기 전용 액세스를 제공한다고 생각하면 된다.

하지만, 이터레이터 자체는 변경할 수 있다. 원하는 대로 벡터 inventory 주위에 이동할 수 있다.

 

+ push_back() 함수를 사용하면 벡터의 크기가 증가하고, 새로운 요소가 벡터의 끝에 추가된다. 벡터의 메모리를 재할당하고 이전 요소들을 새로운 메모리 공간으로 복사하게 된다. 이 과정에서 이전 메모리 위치와 새로운 메모리 위치 사이의 관계가 변경되므로, 이전에 저장된 포인터나 이터레이터는 더 이상 유효하지 않게 된다. push_back() 함수를 사용한 후에는 모든 이터레이터를 새로 다시 얻어야 된다.

 

for (iter = inventory.begin(); iter != inventory.end(); ++iter) {
    cout << *iter << endl;
}

 

for 루프를 사용하여 inventory의 첫번째 요소부터 마지막 요소까지 이동한다. 각 요소에 접근하기 위해 정수와 첨자 연산자를 사용하는 대신, 이터레이터를 사용한다.

루프의 초기화문에서는 inventory.begin()의 반환 값을 iter에 할당한다.

begin() 멤버 함수는 컨테이너의 첫번째 요소를 가리키는 이터레이터를 반환한다.

루프의 테스트문에서는 inventory.end()의 반환 값을 iter와 비교하여 같지 않은 지 확인한다.

end() 멤버 함수는 컨테이너의 마지막 요소를 넘어가는 위치를 가리키는 이터레이터를 반환한다.

따라서 end()로 반환된 이터레이터에서 값을 얻을 수는 없다.

 

루프의 동작문에서는 ++iter를 사용하여 iter를 증가시켜 다음 요소로 이동한다.

루프 본문에서는 iter를 cout에 보낸다. iter 앞에 역참조 연산자(*)를 붙임으로써, 이터레이터가 참조하는 요소의 값을 표시한다.(이터레이터 자체가 아님)

 

벡터 요소의 값 변경하기

1. myIterator를 inventory의 첫번째 요소를 참조하도록 설정한다.

2. 첫 번째 요소의 값을 변경한다.    ->      *myIterator = "battle axe";

myIterator를 변경하는 것이 아니다. 할당문 이후에도 myIterator는 여전히 벡터의 첫 번째 요소를 참조한다.

 

(*myIterator).size() 코드는 "myIterator를 역참조하고 그 객체의 size() 멤버 함수를 호출하라"라는 뜻이다.

myIterator가 "battle axe"를 참조하는 문자열 객체에 해당하므로, 이 코드는 10을 반환합니다.

+ 데이터 멤버나 멤버 함수에 접근하기 위해 이터레이터를 역참조할 때는 괄호로 둘러싸야 한다. dot 연산자가 이터레이터가 참조하는 객체에 적용되도록 보장한다.

 

(*myIterator).size() 코드를 조금 더 직관적으로 표현하면 myIterator->size() 이다.

-> 연산자를 사용하여 이터레이터가 참조하는 객체의 멤버함수나 데이터 멤버에 액세스할 수 있다.

 

inventory.insert(inventory.begin(), "crossbow");

insert() 멤버 함수의 한 가지 형태는 주어진 이터레이터가 참조하는 요소 바로 앞에 새로운 요소를 삽입한다.

이 형태에는 두 가지 인수가 필요하다.

첫번째는 이터레이터고 두번째는 삽입할 요소이다.

결과적으로 다른 요소들은 하나씩 아래로 이동한다.

insert() 함수는 새로 삽입된 요소를 참조하는 이터레이터를 반환한다.

 

erase() 멤버 함수는 벡터에서 요소를 제거한다.

제거할 요소를 참조하는 이터레이터를 인수로 가진다.

제거된 요소 다음을 참조한느 이터레이터를 반환한다.

 

Arrays

// Hero's Inventory
// Demonstrates arrays

#include <iostream>
#include <string>
using namespace std;

int main() {
    const int MAX_ITEMS = 10;
    string inventory[MAX_ITEMS];

    int numItems = 0;
    inventory[numItems++] = "sword";
    inventory[numItems++] = "armor";
    inventory[numItems++] = "shield";

    cout << "Your items:\n";
    for (int i = 0; i < numItems; ++i)
    {
        cout << inventory[i] << endl;
    }

    cout << "\nYou trade your sword for a battle axe.";
    inventory[0] = "battle axe";
    cout << "Your items:\n";
    for (int i = 0; i < numItems; ++i)
    {
        cout << inventory[i] << endl;
    }

    cout << "\nThe item name '" << inventory[0] << "' has ";
    cout << inventory[0].size() << " letters in it.\n";

    cout << "\nYou find a healing potion.";
    if (numItems < MAX_ITEMS) {
        inventory[numItems++] = "healing potion";
    }
    else {
        cout << "You have too many items and can't carry another.";
    }
    cout << "\nYour items:\n";
    for (int i=0; i < numItems; ++i){
        cout << inventory[i] << endl;
    }

    return 0;
}

 

배열

배열의 요소 수에 대한 상수를 정의

const int MAX_ITEMS = 10;

 

배열을 선언할 때는 배열의 크기를 지정해야 한다.

이는 컴파일러가 필요한 메모리 공간을 예약할 수 있도록 한다.

위에서는 MAX_ITEMS 개의 string 객체로 이루어진 배열 inventory 선언

 

string inventory[MAX_ITEMS] = {"sword", "armor", "shield"};

초기화 리스트를 사용하여 배열을 값으로 초기화할 수도 있다.

이때 요소의 수를 생략하면, 리스트 요소 수와 동일한 크기의 배열이 생성된다.

 

위 코드에서처럼 배열에 값을 추가할 때,

후위 증가 연산자를 사용하여 배열에 할당한 후에 numItems가 증가하도록 하였다.

 

inventory의 위치 0에 있는 요소를 문자열 객체 "battle axe"로 재할당한다.

 

배열 요소의 멤버 함수에 접근하기 위해서는 배열 요소를 작성한 다음 멤버 선택 연산자를 사용하여 멤버 함수 이름을 작성하면 된다.

inventory[0].size() 코드는 inventory[0] 요소의 size() 멤버함수를 호출해야 한다는 것을 의미

호출은 문자열 객체의 문자수인 10을 반환한다.

 

if문에서 먼저 numItems가 MAX_ITEMS보다 작은 지 확인한다.

배열의 크기가 고정되어 있으므로 새로운 아이템을 추가하기 전에 확인하여 오류를 방지한다.

 

인덱스 번호가 유효한 배열 위치인 지 확인한 다음 사용하기 전에 테스트하는 것을경계 검사(bound checking)라고 한다.사용하려는 인덱스가 유효하지 않을 가능성이 있는 경우에는 반드시 경계 검사를 수행해야 한다.

 

C-style Strings

문자열 객체가 등장하기 전에 C++ 프로그래먿르은 널 문자로 끝나는 문자의 배열로 문자열을 나타냈다.char phrase[] = "Game Over!!!";
C-스타일 문자열은 그 끝을 나타내기 위해 널 문자라는 문자로 종료된다.널 문자는 '\0'으로 작성할 수 있다.

 

다른 종류의 배열과 마찬가지로 배열 크기를 정의할 때 배열 크기를 지정할 수 있다.char phrase[81] = "Game Over!!!";
이 코드는 80 개의 인쇄 가능한 문자 (+ 종료되는 널 문자) 를 포함할 수 있는 C-스타일 문자열을 만든다.

 

C-스타일 문자열에는 멤버 함수가 없다.

하지만 표준 라이브러리의 cstring 파일에는 C-스타일 문자열과 작업하기 위한 다양한 함수가 포함되어 있다.

 


 

다차원 배열

// Tic-Tac-Toe Board
// Demonstrates multidimensional arrays

#include <iostream>
using namespace std;

int main() {
    const int ROWS = 3;
    const int COLUMNS = 3;
    char board[ROWS][COLUMNS] = {{'O','X','O'},{' ','X','O'},{'X','O','O'}};
    
    cout << "Here's the tic-tac-toe board:\n";
    for (int i=0; i < ROWS; ++i) {
        for (int j=0; j < COLUMNS; ++j) {
            cout << board[i][j];
        } cout << endl;
    }

    cout << "\n'X' moves to the empty location.\n\n ";
    board[1][0] = 'X';

    cout << "Now the tic-tac-toe board is:\n";
    for (int i=0; i < ROWS; ++i) {
        for (int j=0; j < COLUMNS; ++j) {
            cout << board[i][j];
        } cout << endl;
    }
    cout << "\n'X' wins!";
    return 0;
}

 

중첩된 두 개의 for 루프를 사용하여 2차원 배열을 이동하고 문자 요소를 표시하여 틱택토 보드를 출력한다.

 


 

// Word Jumble
// The classic word jumble game where the player can ask for a hint

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
    enum fields {WORD, HINT, NUM_FIELDS};
    const int NUM_WORDS = 5;
    const string WORDS[NUM_WORDS][NUM_FIELDS] = 
    {
        {"wall", "Do you feel you're banging your head against something?"},
        {"glasses", "These might help you see the answer."},
        {"labored", " Going slowly, is it?"},
        {"persistent", "Keep at it."},
        {"jumble", " It's what the game is all about."}
    };

    srand(static_cast<unsigned int>(time(0)));
    int choice = (rand()%NUM_WORDS);
    string theWord = WORDS[choice][WORD];  // word to guess
    string theHint = WORDS[choice][HINT];  // hint for word

    string jumble = theWord;
    int length = jumble.size();
    for (int i = 0; i < length; ++i) {
        int index1 = (rand() % length);
        int index2 = (rand() % length);
        char temp = jumble[index1];
        jumble[index1] = jumble[index2];
        jumble[index2] = temp;
    }

    cout << "\t\tWelcome to Word Jumble!\n\n";
    cout << "Unscramble the letters to make a word.\n";
    cout << "Enter 'hint' for a hint.\n";
    cout << "Enter 'quit' to quit the game.\n\n";
    cout << "The jumble is: " << jumble;

    string guess;
    cout << "\n\nYour guess: ";
    cin >> guess;

    while ((guess != theWord) && (guess != "quit")){
        if (guess == "hint") {
            cout << theHint;
        }
        else {
            cout << "Sorry, that's not it.";
        }
        cout << "\n\nYour guess: ";
        cin >> guess;
    }

    if (guess == theWord) {
        cout << "\nThat's it! You guessed it!\n";
    }
    cout << "\nThanks for playing.\n";

    return 0;
}

 

 


 

Summary

  • for 루프는 코드 섹션을 반복할 수 있게 해준다. for 루프에서 초기화문, 테스트할 표현식, 그리고 각 루프 반복 후에 수행할 작업을 제공한다.
  • for 루프는 주로 계산이나 순서를 반복하는 데에 사용한다.
  • 객체는 데이터와 함수를 결합한 캡슐화된, 일관된 엔티티이다.
  • string 객체를 사용하면 문자의 시퀀스를 저장할 수 있으며 멤버 함수도 사용할 수 있다.
  • string 객체는 연결 연산자나 관계 연산자와 같은 익숙한 연산자와 직관적으로 작동하도록 정의된다.
  • 배열은 모든 유형의 시퀀스를 저장하고 액세스하는 방법을 제공한다.
  • 배열의 제한점은 고정된 길이를 가지고 있다는 점
  • 다차원 배열을 사용하면 여러 하위 스크립트를 사용하여 배열 요소에 액세스할 수 있다.

 

Questions and Answers

Q : while vs for

A : 어느 것도 본질적으로 다른 것보다 더 나은 것이 아님. 사용하려는 목적에 가장 적합한 루프 사용하기

 

Q : 언제 while 대신 for ?

A : 카운팅이나 시퀀스를 반복하는 등의 경우네는 for이 더 적합하다.

 

Q : 루프에 break나 continue 문을 사용할 수 있나요?

A : 네. while 루프에서와 마찬가지로 동작한다. break는 루프를 종료하고 continue는 루프의 맨 위로 제어를 이동한다.

 

Q : int나 char 형식을 사용하려면 파일을 포함하지 않아도 되는데, 왜 문자열을 사용하려면 string 파일을 포함해야 하나요?

A : int나 char는 내장 형식이다. string 형식은 그렇지 않으며 string 파일에 표준 라이브러리의 일부로 정의되어 있다.

 

Q : C-style 문자열 대신에 string 객체를 사용해야 하는 이유 ?

A : string 객체는 C-style 문자열보다 장점을 가지고 있다. 동적으로 크기를 조절할 수 있으며 생성할 때 길이 제한을 지정할 필요가 없다.

 

Q : 연산자 오버로딩 ?

A : 연산자 오버로딩이란 익숙한 연산자를 다른 문맥에서 예측 가능한 결과로 정의할 수 있는 과정이다. 예를 들어, 숫자를 더하는 데 사용되는 + 연산자는 string 형식에 의해 문자열을 결합하는 데에도 사용된다.

 

Q : length() vs size()

A : 모두 동일한 값을 반환하므로 둘 중 어느 것을 사용해도 괜찮다.

 

Q : Predicate 함수란 ?

A : 참 또는 거짓을 반환하는 함수이다. string 객체의 empty() 멤버 함수가 predicate 함수의 예시이다.

 

Q : 배열의 경계를 넘어서 요소에 값을 할당하려고 하면 어떻게 되는 지 ?

A : C++에서는 할당을 허용한다. 하지만 결과는 예측할 수 없으며 프로그램이 충돌할 수 있다. 컴퓨터 메모리의 알 수 없는 부분을 변경하기 때문이다.

 

Q : 왜 다차원 배열을 사용하나요 ?

A : 다차원 배열은 요소 그룹을 다루는 데에 더 직관적이다.

 

Discussion Questions

  1. What are the advantages of using an array over a group of individual variables?
    • 데이터 그룹화 : 여러 데이터를 하나의 구조로 그룹화하여 관리할 수 있다. 데이터를 구조적으로 조직화하여 코드의 가독성을 향상시키고 유지 관리를 쉽게 만든다.
    • 반복 작업 :  배열을 사용하면 루프를 통해 데이터 그룹의 모든 요소에 대해 반복 작업을 수행할 수 있다. 코드를 간결하고 효율적으로 만들어준다.
    • 인덱싱 : 각 요소에 대한 인덱스를 사용하여 특정 요소에 직접 액세스할 수 있다.
  2. What are some limitations imposed by a fixed array size?
    • 배열은 선언할 때 크기가 정해지기 때문에, 사용할 수 있는 요소의 수가 제한된다. 크기가 동적으로 조정되지 않으므로 필요에 따라 요소를 추가하거나 제거할 수 없다.
    • 배열은 선언할 때 할당된 크기만큼의 메모리를 사용하므로, 실제로 사용되지 않는 공간이 메모리에 낭비될 수 있다.
    • 배열에 할당된 메모리 공간을 초과하여 데이터를 저장하려고 하면 오버플로우가 발생할 수 있다. 반대로 언더플로우 또한 발생할 수 있다.

 

Exercises

  1. Improve the Word Jumble game by adding a scoring system. Make the point value for a word based on its length. Deduct points if the player asks for a hint.
int score = 0;

    while ((guess != theWord) && (guess != "quit")){
        if (guess == "hint") {
            cout << theHint;
            score -= 1;
        }
        else {
            cout << "Sorry, that's not it.";
        }
        cout << "\n\nYour guess: ";
        cin >> guess;
    }

    if (guess == theWord) {
        cout << "\nThat's it! You guessed it!\n";
        score += theWord.size();
    }
    cout << "\nYour final score: " << score << " points.\n";
    cout << "\nThanks for playing.\n";

 

2. Whats wrong with the following code?

for (int i = 0; i <= phrase.size(); ++i) {
	cout << "Character at position " << i << "is: " << phrase[i] << endl;
}

 

       루프의 종료 조건이 잘못되었다.

       i 가 phrase.size()와 같아지는 경우에도 루프가 계속되지만 배열의 인덱스는 0부터 시작하기 때문에 i는 유효한 인덱스를 초과한다. 

 

 

3. Whats wrong with the following code?

const int ROWS = 2;
const int COLUMNS = 3;
char board[COLUMNS][ROWS] = {{'O', 'X', 'O'}, {' ', 'X', 'X'}};

 

        배열을 선언할 때 행과 열의 순서가 반대로 지정되었다.

        board 배열은 3개의 행과 2개의 열을 가지고 있어야 하지만 실제로 2개의 행과 3개의 열을 가지고 있다.

For Loops, Strings, and Arrays : Word Jumble

 

For 반복문

계수 및 시퀀스를 이동하는 데 적합하다.

 

for (초기화; 테스트; 동작) 문장;

초기화는 루프의 초기 조건을 설정하는 문장

표현식 테스트는 루프 몸체가 실행되기 전에 매번 테스트된다.

프로그램이 실행되고, 동작이 실행된다 ( 종종 카운터 변수가 증가함 )

테스트가 거짓이 될 때까지 이 사이클이 반복되고, 그 시점에서 루프가 종료된다.

 

// Counter
// Demonstrates for loops

#include <iostream>
using namespace std;

int main() {
    cout << "Counting forward:\n";
    for (int i=0; i<10; i++)
    {
        cout << i <<" ";
    }
    cout << "\n\nCounting backward:\n";
    for (int i=9; i>=0; i--)
    {
        cout << i << " ";
    }

    cout << "\n\nCounting by fives:\n";
    for (int i=1; i<=50; i+= 5)
    {
        cout << i << " ";
    }

    cout << "\n\nCounting with null statements:\n";
    int count = 0;
    for (; count<10;)
    {
        cout << count << " ";
        ++count;
    }

    cout << "\n\nCounting with nested for loops:\n";
    const int ROWS = 5;
    const int COLUMNS = 3;
    for (int i=0; i < ROWS; ++i)
    {
        for (int j=0; j < COLUMNS; ++j)
        {
            cout << i << "," << j << " ";
        }
        cout << endl;
    }
    return 0;
}

초기화문은 int i=0으로 i를 선언하고 0으로 초기화

표현식 i < 10은 i가 10보다 작은 동안 루프가 계속되도록 한다.

마지막 동작 문은 루프 본문이 완료될 때마다 i가 증가결과적으로 루프는 0~9까지의 값에 대해 10번 반복된다.

 

카운터 변수를 초기화하고 테스트 조건을 만든 후 카운터 변수를 원하는 값으로 업데이트할 수 있다.for 루프에서도 무한 루프에 대한 주의가 동일하게 적용된다,루프가 언젠가는 종료될 수 있는 루프를 만들어야 한다.for(;;)은 무한 루프를 만들 수 있다. 조건식이 비어있기 때문에 루프는 종료문을 만날 때까지 계속된다.

 

중첩된 for 루프

프로그램은 첫번째 외부 루프의 반복을 마지고 endl을 cout으로 보내어 첫번째 행을 종료한다.내부 루프가 외부 루프의 각 반복에 대해 전체적으로 실행된다.

 

객체 Object 이해

데이터와 함수를 결합하는 소프트웨어 object를 사용할 수 있다.오브젝트의 데이터 요소는 data member라고 하고, 함수는 member function이라고 한다.동일한 유형의 모든 객체는 동일한 기본 구조를 가지므로 각 개체는 동일한 데이터 멤버와 멤버 함수 세트를 가질 것이다.그러나 개별적으로 각 객체는 자체 데이터 멤버의 값을 가진다.객체의 좋은 점은 객체들을 사용하기 위해 구현 세부 정보를 알 필요가 없다는 것이다.예를 들어 자동차를 운전하기 위해서 자동차를 어떻게 만들 지 알 필요가 없다.객체의 데이터 멤버와 멤버 함수만 알면 된다.

 

내장 타입과 같이 객체들도 변수에 저장할 수 있다.멤버 선택 연산자 (.)를 사용하여 데이터 멤버와 멤버 함수에 액세스할 수 있다.

// ship is an object of Spacecraft type 
if (ship.energy > 10)
{
    ship.fireWeapons()
}

ship.energy는 객체의 에너지 데이터 멤버에 엑세스하고,

ship.fireWeapons()는 객체의 fireWeapons() 멤버 함수를 호출한다.

 

String Object

문자열 객체는 단어 퍼즐 게임을 작성하거나 단순히 플레이어의 이름을 저장하는 등 문자 시퀀스를 처리하는 방법이다.

문자열은 사실 객체이며, 문자열 객체를 사용하여 여러 작업을 수행할 수 있다.

// String Tester
// Demonstrates string objects

#include <iostream>
#include <string>
using namespace std;

int main() {
    string word1 = "Game";
    string word2("Over");
    string word3(3, '!');

    string phrase = word1 + " " + word2 + word3;
    cout << "The phrase is: " << phrase << "\n\n";

    cout << "The phrase has " << phrase.size() << "characters in it.\n\n";

    cout << "The character at position 0 is: " << phrase[0] << "\n\n";

    cout << "Changing the character at position 0.\n";
    phrase[0] = 'L';
    cout << "The phrase is now: " << phrase << "\n\n";

    for (unsigned int i=0; i <phrase.size(); ++i)
    {
        cout << "Character at position " << i << " is: "<< phrase[i] << endl;
    }

    cout << "\nThe sequence 'Over' begins at location ";
    cout << phrase.find("Over") << endl;

    if (phrase.find("eggplant") == string::npos)
    {
        cout << "'eggplant' is not in the phrase.\n\n";
    }

    phrase.erase(4,5);
    cout << "The phrase is now: " << phrase << endl;

    phrase.erase(4);
    cout << "The phrase is now: " << phrase << endl;

    phrase.erase();
    cout << "The phrase is now: " << phrase << endl;

    if (phrase.empty())
    {
        cout << "\nThe phrase is no more.\n";
    }
    return 0;
}

 

 

string word3(3, '!');   -> !!!

세 문자열 객체를 연결하여 새로운 문자열 객체 phrase 생성

phrase == "Game Over!!!"

+ 연산자를 오버로드하여 문자열과 함께 사용하면 문자열 객체 연결을 의미

연산자 오버로딩은 익숙한 연산자를 새롭게 정의하여 새로운 컨텍스트에서 다르게 작동하도록 만드는 것

 

문자열 객체 phrase의 size() 멤버 함수를 연산자(.) 을 통해 호출

문자열 객체의 크기 즉 문자 수를 반환 ( 공백 포함 모든 문자 카운트 ) // length() 동일

 

문자열 객체는 char 값의 시퀀스를 저장

[]에 인덱스 번호를 제공하여 개별 char 값을 액세스할 수 있다.

 

size() 반환 값이 unsigned int 이기에 i도 unsigned int로 초기화

 

 

find() 함수를 사용하여 두 개의 문자열 리터럴이 phrase에 포함되어 있는 지 확인

find() 함수는 호출하는 문자열 객체에서 인수로 제공된 문자열을 검색한다.

문자열 객체에서 찾고 있는 문자열이 시작하는 첫 번째 발생 위치 인덱스를 반환한다.

 

eggplant가 phrase에 존재하지 않기 때문에 find()는 string 헤더 파일에서 정의된 특수한 상수를 반환한다.

이를 string::npos로 액세스한다.

string::npos를 통해 액세스하는 상수는 문자열 객체의 가능한 가장 큰 크기를 나타내므로 문자열 객체의 가능한 모든 유효한 인덱스보다 크다. 즉 한 문자열이 다른 문자열에서 찾을 수 없음을 나타낸다.

find("eggplant", 5);  ->  phrase의 인덱스 5부터 eggplant를 찾기 시작한다.

 

erase() 함수는 문자열 객체에서 지정된 서브 문자열을 제거한다.

phrase.erase(4,5);  ->  인덱스 4에서 시작하는 5 글자의 서브 문자열을 제거한다.

phrase.erase(4);   ->   인덱스 4에서 시작하는 객체의 모든 문자를 제거한다.

phrase.erase();   ->   모든 문자가 제거

 

empty() 함수는 bool 값이다.

문자열 객체가 비어 있으면 true를 반환하고 그렇지 않으면 false를 반환한다.

( chapter 2 포스팅에 이어서 )

반복문

반복문에서 본 동작을 변경하는 것이 가능하다.

break 문을 사용하여 루프를 즉시 종료할 수 있고,

continue 문을 사용하여 루프의 맨 처음으로 이동할 수 있다.

 

// Finicky Counter
// Demonstrates break and continue statements
#include <iostream>
using namespace std;

int main()
{
    int count = 0;
    while (true)
    {
        count += 1;
        // end loop if count is greater than 10
        if (count > 10)
        {
            break;
        }
        // skip the number 5
        if (count == 5)
        {
            continue;
        }
        cout << count << endl;
    }
    return 0;
}

 

while(true) 구문을 사용하여 무한 루프를 설정하였지만 루프 본문에 종료 조건을 넣었기에 실제로 무한하지 않는다.

break문을 사용하면 루프를 빠져나옴을 의미하고 루프가 종료된다.

continue문을 사용하면 루프의 맨 처음으로 돌아가 while 표현식이 다시 평가되고, 값이 true면 루프에 다시 들어간다.

 

논리 연산자

 

 

 

 

 

 

 

// Designers Network
// Demonstrates logical operators

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string username;
    string password;
    bool success;

    cout << "\tGame Designer's Network\n";

    do
    {
        cout << "\nUsername: ";
        cin >> username;
        cout << "Password: ";
        cin >> password;

        if (username == "S.Meier" && password == "civilization")
        {
            cout << "\nHey, Sid.";
            success = true;
        }
        else if (username == "S.Miyamoto" && password == "mariobros")
        {
            cout << "\nWhat's up, Shigeru";
            success = true
        }
        else if (username == "W.Wright" && password == "thisims")
        {
            cout << "\nHow goes it, Will?";
            success << true;
        }
        else if (username == "guest" || password == "guest")
        {
            cout << "\nWelcome, guest.";
            success = true;
        }
        else
        {
            cout << "\nYour login failed.";
            success = false;
        }
    }while (!success);
    return 0;
}// Designers Network
// Demonstrates logical operators

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string username;
    string password;
    bool success;

    cout << "\tGame Designer's Network\n";

    do
    {
        cout << "\nUsername: ";
        cin >> username;
        cout << "Password: ";
        cin >> password;

        if (username == "S.Meier" && password == "civilization")
        {
            cout << "\nHey, Sid.";
            success = true;
        }
        else if (username == "S.Miyamoto" && password == "mariobros")
        {
            cout << "\nWhat's up, Shigeru";
            success = true
        }
        else if (username == "W.Wright" && password == "thisims")
        {
            cout << "\nHow goes it, Will?";
            success << true;
        }
        else if (username == "guest" || password == "guest")
        {
            cout << "\nWelcome, guest.";
            success = true;
        }
        else
        {
            cout << "\nYour login failed.";
            success = false;
        }
    }while (!success);
    return 0;
}

 

AND 연산자인 &&는 두 개의 표현식이 모두 참일 때만 참이 되고, 그렇지 않으면 거짓이 된다.

OR 연산자 ||는 두개의 표현식이 모두 거짓일 때만 거짓이 되고, 그렇지 않으면 참이 된다.

NOT 연산자인 !는 표현식의 참 또는 거짓을 전환하는 데 사용된다.

 

논리 연산자 우선순위

NOT > AND > OR

우선순위가 낮은 연산이 먼저 평가되길 원한다면 괄호를 사용하면 된다.

 

난수 생성

//DIe Roller
//Demonstrates generating random numbers

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main()
{
    srand(static_cast<unsigned int>(time (0)));   //seed random nubmer generator

    int randomNumber = rand();  //generate random number

    int die = (randomNumber % 6) + 1;  // get a number between 1 and 6
    cout << "You rolled a " << die << endl;

    return 0;
}

 

파일 <cstdlib>에는 난수 생성과 관련된 함수들이 포함되어 있다.

그 중 하나인 rand() 함수를 메인 함수에서 호출한다.

rand()가 생성할 수 있는 최대 난수 = RAND_MAX

rand()에서는 함수가 인수를 사용하지 않기 때문에 어떤 값을 전달하지 않았지만,

작업에 사용할 값들을 가져오기 위해서는 함수 이름 뒤의 괄호 사이에 쉼표로 구분하여 넣어서 제공하면 된다.

 

난수 생성기 초기화하기

컴퓨터는 공식에 기반한 의사난수(pseudorandom)를 생성한다.

이로 인해 컴퓨터는 프로그램에서 항상 같은 시리즈의 "랜덤"숫자를 생성한다.

이 문제에 대한 해결책은 프로그램이 시작될 때 컴퓨터에게 임의의 위치에서 읽기 시작하라고 지시하는 것이다.

이 프로세스를 난수 생성기를 시드하는(seed) 것이라고 한다.

시작 위치를 결정하기 위해 시드라고 불리는 숫자를 난수 생성기에 제공한다.

 

srand(static_cast<unsigned int>(time(0))); // 난수 생성기를 시드

 

이 코드는 현재 날짜와 시간을 기반으로 난수 생성기를 시드한다.

srand()함수는 난수 생성기를 시드한다.

unsigned int로 이루어진 시드를 전달하면 된다. 함수에 전달되는 것은 time(0)의 반환 값

static_cast는 이 값을 unsinged int로 변환(캐스트)한다.

 

범위 내의 숫자 계산하기

랜덤 숫자를 생성한 후에는 randomNumber가 0~32767 사이의 값을 가진다.

(randomNumber % 6) +1   ->   1~6 사이의 숫자로 나타내줌

 

게임 루프 이해하기

게임 루프는 게임의 이벤트 흐름을 일반화한 표현이다.

설정 -> 플레이어 입력 받기 -> 게임 내부 업데이트 -> 디스플레이 업데이트 -> 게임이 종료되었는지 확인 -> 종료

//Guess My Number
//The classic number guessing game
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
    srand(static_cast<unsigned int>(time(0)));

    int secretNumber = rand() % 100 +1; // random number between 1 and 100
    int tries = 0;
    int guess;

    cout << "\tWelcome to Guess My Number\n\n";
    do {
        cout << "Enter a guess: ";
        cin >> guess;
        ++tries;

        if (guess > secretNumber) {
            cout << "Too high!\n\n";
        }
        else if (guess < secretNumber) {
            cout << "Too low!\n\n";
        }
        else {
            cout << "\nThat's it! You got it in" << tries << " guesses!\n";
        }
    }while(guess != secretNumber);
    return 0;
}

 

 

 

 

 

 

 

 

정리

Q : switch 문에서 정수가 아닌 값을 테스트할 수 있나요?

A : 아니요. switch 문은 정수로 해석할 수 있는 값(예: char 값 포함)만 사용할 수 있습니다.

 

Q: 왜 무한 루프는 나쁜 것으로 간주되나요?

A: 무한 루프에 갇힌 프로그램은 스스로 종료되지 않습니다. 운영 체제에 의해 종료해야 합니다. 최악의 경우 사용자는 컴퓨터를 종료하여 무한 루프에 갇힌 프로그램을 종료해야 할 수 있습니다.

 

Q: 컴파일러가 무한 루프를 감지하고 오류로 표시하지 않나요?

A: 아니요. 무한 루프는 논리적 오류입니다 - 프로그래머가 추적해야 하는 종류의 오류입니다.

 

Q: 난수 생성기에 시드를 주는 것은 무엇을 의미하나요?

A: 난수 생성기에 정수와 같은 시드를 제공하여 생성기가 난수를 생성하는 방식에 영향을 미칩니다. 난수 생성기에 시드를 제공하지 않으면 프로그램이 시작될 때마다 동일한 일련의 숫자를 생성합니다.

 

Discussion Question

  1. What kinds of things would be difficult to program without loops?
    • 루프 없이 프로그래밍하기 어려운 것들은 반복 잡업이 필요한 경우이다.
    • 예를 들어, 배열의 모든 요소를 검색하거나 특정 조건이 충족될 때까지 작업을 반복하는 등의 작업은 루프 없이는 상당히 복잡해질 수 있다
  2. What are the advantages and disadvantages of the switch statement versus a series of if statements?
    • 장점 : 코드를 더 간결하게 만들고 가독성을 향상시킬 수 있다. 여러 경우에 따라 코드를 분기하기 편리하다.
    • 단점 : switch문은 정수 값에 대해서만 동작하기 때문에 다른 유형의 값에는 사용할 수 없다. 각 case 문의 끝에 break 문을 사용해야 하기 때문에 실수하기 쉽다.
  3. When might you omit a break statement from the end of a case in a switch statement?
    • 해당 case문에 해당하는 코드를 실행한 뒤 다음 case문으로 넘어가고자 할 때 break문을 생략해야 합니다.
    • 여러 case에 대해 동일한 동작을 수행하는 코드를 줄일 수 있다.
  4. When should you use a while loop over a do loop?
    • while 루프는 루프를 시작하기 전에 조건을 확인하는 데 사용되며, do 루프는 루프의 본문을 한 번 실행한 후에 조건을 확인하는 데 사용된다.
    • 루프를 최소 한 번 실행해야 할 때는 do 루프를 사용해야 한다.
  5. Describe your favorite game in terms of the game loop. Is the game loop a good fit?
  • 게임 루프는 플레이어의 입력을 받고 게임 세계를 업데이트하며, 새로운 이벤트를 처리하고, 게임이 종료될 때까지 반복된다.

 

Exercises

  1. Rewrite the Menu Chooser program from this chapter using an enumeration to represent difficulty levels. The variable choice will still be of type int.
// Menu Chooser
// Demonstrates the switch statement
#include <iostream> 
using namespace std;

enum DifficultyLevel {
    EASY = 1,
    NORMAL = 2,
    HARD = 3
};

int main() {
    cout << "Difficulty Levels\n\n"; 
    cout << "1 - Easy\n";
    cout << "2 - Normal\n";
    cout << "3 - Hard\n\n";

    int choice;
    cout << "Choice: "; 
    cin >> choice;
    switch (choice) {
        case EASY:
            cout << "You picked Easy.\n";
            break; 
        case NORMAL:
            cout << "You picked Normal.\n";
            break; 
        case HARD:
            cout << "You picked Hard.\n";
            break;
        default:
            cout << "You made an illegal choice.\n";
    }
    return 0; 
}
  1. Whats wrong with the following loop?
int x = 0;
while (x)
{
	++x;
    cout << x << endl;
}

 

0은 거짓으로 해석되고, 0이 아닌 수는 참으로 해석된다.

초기에 x 값이 0으로 설정되어 있으므로, 루프가 한 번도 실행되지 않고 종료된다.

 

3.  Write a new version of the Guess My Number program in which the player and the computer switch roles. That is, the player picks a number and the computer must guess what it is.

 

//Guess My Number
//The classic number guessing game
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
    srand(static_cast<unsigned int>(time(0)));

    int secretNumber;
    cout << "\tWelcome to Guess My Number\n\n";
    cout << "Pick a number between 1 and 100: ";
    cin >> secretNumber;

    int guess;
    int tries = 0;

    cout << "\n\nComputer's turn to guess!\n";

    do {
        guess = rand() % 100 + 1;
        cout << "Computer's guess: " << guess << endl;
        ++tries;

        if (guess > secretNumber) {
            cout << "Too high!\n\n";
        }
        else if (guess < secretNumber) {
            cout << "Too low!\n\n";
        }
        else {
            cout << "\nComputer guessed it! It took " << tries << " guesses!\n";
        }
    }while(guess != secretNumber);
    return 0;
}

Truth, Branching, and the Game Loop : Guess My Number

 

1장의 프로그램들은 모두 선형적이었다. 위->아래로 순서대로 실행된다.

흥미로운 게임을 만들려면 어떤 조건에 따라 코드 섹션을 실행하거나 건너뛰는 프로그램을 작성해야 한다.

if문을 사용하여 코드 섹션으로 분기 / switch문을 사용하여 실행할 코드 섹션 선택 / while 및 do 루프를 사용하여 코드 섹션 반복

 

bool 변수로 참과 거짓 부울값 저장

0이 아닌 모든 값은 true로 해석될 수 있고, 0은 false로 해석될 수 있다.

// Score Rater
// Demonstrates the if statement
#include <iostream> 
using namespace std;
int main() 
{
    if (true) 
    {
        cout << "This is always displayed.\n\n"; 
    }
    if (false) 
    {
        cout << "This is never displayed.\n\n"; 
    }

    int score = 1000;

    if (score) 
    {
        cout << "At least you didn’t score zero.\n\n"; 
    }
    if (score >= 250) 
    {
        cout << "You scored 250 or more. Decent.\n\n"; 
    }
    if (score >= 500) 
    {
        cout << "You scored 500 or more. Nice.\n\n";
        if (score >= 1000) 
        {
            cout << "You scored 1000 or more. Impressive!\n"; 
        }
    }
    return 0;
}

 

첫번째 if문은 true가 참이기 때문에 출력되고, 두번째 if문은 출력되지 않는다.

score이 0이 아니기 때문에 참으로 해석된다.

 

// Score Rater 2.0
// Demonstrates an else clause
#include <iostream> 
using namespace std;
int main() {
    int score;
    cout << "Enter your score: ";
    cin >> score;
    if (score >= 1000) 
    {
        cout << "You scored 1000 or more. Impressive!\n"; 
    }
    else 
    {
        cout << "You scored less than 1000.\n"; 
    }
    return 0; 
}

 

else절은 if절의 표현식이 거짓일 경우에 프로그램이 분기할 문장을 제공한다.

if / else if / else

 

// Score Rater 3.0
// Demonstrates if else-if else suite
#include <iostream> 
using namespace std;
int main() {
    int score;
    cout << "Enter your score: "; 
    cin >> score;
    if (score >= 1000) 
    {
    cout << "You scored 1000 or more. Impressive!\n"; 
    }
    else if (score >= 500) 
    {
        cout << "You scored 500 or more. Nice.\n"; 
    }
    else if (score >= 250) 
    {
        cout << "You scored 250 or more. Decent.\n"; 
    }
    else 
    {
        cout << "You scored less than 250. Nothing to brag about.\n"; 
    }
    return 0; 
}

 

만약 score가 500 이상이면, "You scored 500 or more. Nice."라는 메시지가 표시되고 컴퓨터가 return 문으로 분기한다. 그 표현식이 거짓이면, score가 500 미만임을 나타내며 컴퓨터는 시퀀스의 다음 표현식을 평가한다.

 

switch문

만약 choice가 값에 해당하는 경우, 프로그램은 해당하는 문장을 실행한다. 프로그램이 break문을 만나면 switch 구조를 종료한다. choice가 어떤 값과도 일치하지 않는 경우, 선택적인 default에 관련된 문장이 실행된다.

break와 default의 사용은 선택사항이다. 그러나 break를 생략하면 프로그램은 나머지 문장을 계속해서 진행한다.

switch문은 int(또는 char 또는 enumerator와 같은 int로 취급할 수 있는 값)를 테스트하는 데에만 사용할 수 있고 다른 유형과는 함께 작동하지 않는다. 

// Menu Chooser
// Demonstrates the switch statement
#include <iostream> 
using namespace std;
int main() {
    cout << "Difficulty Levels\n\n"; 
    cout << "1 - Easy\n";
    cout << "2 - Normal\n";
    cout << "3 - Hard\n\n";

    int choice;
    cout << "Choice: "; 
    cin >> choice;
    switch (choice) 
    {
        case 1:
            cout << "You picked Easy.\n";
            break; 
        case 2:
            cout << "You picked Normal.\n";
            break; 
        case 3:
            cout << "You picked Hard.\n";
            break;
        default:
            cout << "You made an illegal choice.\n";
    }
    return 0; 
}

 

거의 항상 각 case를 break문으로 끝내는 게 좋다.

 

while문

while 루프를 사용하면 표현식이 true인 동안 코드 섹션을 반복할 수 있다.

false이면 프로그램은 루프 이후의 문으로 이동한다. 

// Play Again
// Demonstrates while loops
#include <iostream> 
using namespace std;
int main() {
    char again = ’y’; 
    while (again == ’y’) {
        cout << "\n**Played an exciting game**";
        cout << "\nDo you want to play again? (y/n): "; 
        cin >> again;
    }
    cout << "\nOkay, bye.";
    return 0; 
}

 

'again'이라는 char 변수를 선언하고 'y'로 초기화

루프 식에서 사용되는 변수를 루프 이전에 초기화해야 한다.

while 루프는 루프 본문보다 식을 먼저 평가하기 때문에 루프가 시작되기 전에 표현식에 사용되는 모든 변수에 값이 있어야 한다.

 

while 루프와 마찬가지로, do 루프는 표현식을 기반으로 코드 섹션을 반복한다. 차이점은 do 루프가 각 루프 반복 후에 표현식을 테스트한다는 것이다. 즉 루프 본문이 항상 적어도 한 번 이상 실행된다는 것이다. 

// Play Again 2.0
// Demonstrates do loops
#include <iostream>
using namespace std;

int main() 
{
    char again; 
    do
    {
        cout << "\n**Played an exciting game**";
        cout << "\nDo you want to play again? (y/n): "; 
        cin >> again;
    } while (again == ’y’);

    cout << "\nOkay, bye.";
    return 0; 
}

 

do 루프가 시작되기 전에 char again을 선언하지만, 초기화할 필요는 없다. 이 변수가 루프의 첫 번째 반복 이후에만 테스트되기 때문이다.

대부분의 프로그래머는 while 루프를 사용한다. while 루프의 장점은 식이 루프의 맨 위에 바로 나타난다는 것이다.

+ Recent posts