C ++ OOP 틱택 토

Nov 11 2020

이것은 여기 내 질문에 대한 후속 조치 입니다. 글쎄, 정확히 후속 작업은 아니지만 마지막 프로젝트 이후의 다음 프로젝트와 더 비슷합니다.

객체 지향 프로그래밍을 사용하여 tic tac toe 게임을 만들었습니다.

여러분은 이미 tic tac toe가 어떻게 작동하는지 알고 있으므로 어떻게 작동하는지 설명함으로써 시간을 낭비하지 않을 것입니다.

저는 저를 더 나은 프로그래머, 특히 더 나은 C ++ 프로그래머로 만들 수있는 모든 것에 대한 피드백을 찾고 있습니다. 또한 클래스 사용 방법, 더 나은 함수, OOP를 올바르게 사용하는 방법, 그리고 다음과 같습니다.

  • 최적화
  • 나쁜 습관과 좋은 습관
  • 코드 구조
  • 함수 및 변수 이름 지정
  • 버그
  • 클래스 및 함수 사용 개선
  • OOP를 올바르게 사용하는 방법
  • 아, 제대로 댓글을 추가하는 방법
  • 기타

대단히 감사합니다!

Visual Studio Community 2019 버전 16.7.7을 사용하고 있습니다.

Globals.h

#ifndef GUARD_GLOBALS_H
#define GUARD_GLOBALS_H

namespace
{
    enum class Players : char
    {
        PLAYER_X = 'X',
        PLAYER_O = 'O'
    };
}

#endif // !GUARD_GLOBALS_H

board.h

#ifndef GUARD_BOARD_H
#define GUARD_BOARD_H

#include "player.h"

class Board
{
private:
    char board[9];
    // This is suppose to be a place to put the score
    // But I don't know how to implement it yet
    int scoreX{};
    int scoreO{};
public:
    Board();

    void printBoard() const;
    void markBoard(const size_t& choseNum, const char& player, bool& inputPass);
    char checkWin(bool& isDone, int& countTurn);
    void printWinner(bool& isDone, int& countTurn);
};

#endif // !GUARD_BOARD_H

board.cpp

#include "board.h"

#include <iostream>

// To set the board with numbers
Board::Board()
{
    int j{ 1 };
    for (int i = 0; i < 9; i++)
    {
        board[i] = '0' + j++;
    }
}

void Board::printBoard() const
{
    system("cls");

    std::cout << "   |   |  " << "\n";
    std::cout << " " << board[0] << " | " << board[1] << " | " << board[2] <<   "\tPlayer X: " << scoreX << "\n";
    std::cout << "___|___|__" <<                                                "\tPlayer O: " << scoreO << "\n";
    std::cout << "   |   |  " << "\n";
    std::cout << " " << board[3] << " | " << board[4] << " | " << board[5] << "\n";
    std::cout << "___|___|__" << "\n";
    std::cout << "   |   |  " << "\n";
    std::cout << " " << board[6] << " | " << board[7] << " | " << board[8] << "\n";
    std::cout << "   |   |  " << "\n\n";

}

// To change the board to which the player choose the number
void Board::markBoard(const size_t& choseNum, const char& player, bool& inputPass)
{
    char checkNum = board[choseNum - 1];
    // To check if the number that the player choose is available or not 
    if (checkNum != (char)Players::PLAYER_X && checkNum != (char)Players::PLAYER_O)
    {
        // To check if the number that the player input
        if (choseNum >= 1 && choseNum <= 9)
        {
            board[choseNum - 1] = player;
            inputPass = true;
        }
        else
        {
            std::cout << "CHOOSE THE AVAILABLE NUMBER!\nTRY AGAIN: ";
        }
    }
    else
    {
        std::cout << "SPACE HAS ALREADY BEEN OCCUPIED\nTry again: ";
    }
}

/*
There is probably a better way to do this. But, I don't know how tho
Maybe someday I could improve the checking for win but right now 
this is good enough

Also, there are a lot of magic number here such as 8, 2, 6 and 7.
I've tried to remove the magic number but I don't know how.
*/

// Check the board if there is player with parallel set or not
char Board::checkWin(bool &isDone, int &countTurn)
{
    /*
    I use middleboard and initialize it to board[4] because in order 
    for a player to win diagonally they have to acquire the 
    middle board first. So, I initialize middleboard to board[4]
    hoping it could remove the magic number

    and I initialize i to 0 and j to 8 because the checking is 
    begin from the top left corner-middle-bottom right corner 
    if it false then I add add 2 to i and substract 2 from j
    because now the checking is top right corner-middle-bottom left corner
    */

    // Check diagonal win
    size_t middleBoard = board[4];
    for (size_t i = 0, j = 8; i <= 2 && j >= 6; i+=2, j-=2)
    {
        // If all the board is occupied by the same player then the same player win
        if (middleBoard == board[i] && board[i] == board[j])
        {
            //This is suppose to add score, but I don't know how to implement it yet
            board[middleBoard] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;
            isDone = true;
            return middleBoard; // To return the character of the player who won
        }
    }

    /*
    I initialize initialNum to 0 as a starting point for the checking. 
    Initilialized i to 1 and j to 2
    The checking is like this, top left corner-middle top-top right corner
    If it false then the I add 3 to initialNum to make middle left as the
    starting point, then add 3 to i and j so it the next checking is 
    middle left-middle-middle right, and so on
    */

    // Check horizontal win
    size_t initialNum = 0;
    for (size_t i = 1, j = 2; i <= 7 && j <= 8; i += 3, j += 3)
    {
        if (board[initialNum] == board[i] && board[i] == board[j])
        {
            board[initialNum] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;
            isDone = true;
            return board[initialNum];
        }
        else
        {
            initialNum += 3;
        }
        
    }
    
    /*
    I reset the initialNum to 0 and initialized i to 3 and j 6 so 
    the first check will be like this: top left corner-middle left-bottom left corner
    if it fails then i add 1 to initialNum, i, and j, so the next check will be
    middle top-middle-middle bottom and so on
    */

    // Check vertical win
    initialNum = 0;
    for (size_t i = 3, j = 6; i <= 5 && j <= 8; i++, j++)
    {
        if (board[initialNum] == board[i] && board[i] == board[j])
        {
            board[initialNum] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;
            isDone = true;
            return board[initialNum];
        }
        else
        {
            initialNum++;
        }
        
    }
    // If the countTurn is 8 then there're no place to occupy anymore, thus a draw
    if (countTurn == 8)
    {
        isDone = true;
        return 'D'; // As a check for printWinner() function
    }

    countTurn++;
}

// To print who's the winner or draw
void Board::printWinner(bool& isDone, int& countTurn)
{
    if (checkWin(isDone, countTurn) == 'D')
    {
        std::cout << "It's a Draw!\n";
    }
    else
    {
        std::cout << "Congratulations!\nPlayer " << checkWin(isDone, countTurn) << " won the game!\n";
    }
    
}

player.h

#ifndef GUARD_PLAYER_H
#define GUARD_PLAYER_H

#include "Globals.h"
#include "board.h"

class Board;

class Player
{
private:
    char mainPlayer;
    char secondPlayer;
    char turnPlayer = mainPlayer;

public:
    void choosePlayer(bool &choosePass);
    void movePlayer(Board& myBoard);
    void switchPlayer();
};

#endif // !GUARD_PLAYER_H

player.cpp

#include "player.h"
#include "board.h"

#include <iostream>
#include <random>

// To give a choice for the player if they want to be X or O
void Player::choosePlayer(bool& choosePass)
{
    char chosePlayer;
    std::cout << "Do you want to be player X or O? ";

    while (!choosePass)
    {
        std::cin >> chosePlayer;
        // If the player type X uppercase or lowercase then they will be
        // X and the computer will be O, vice versa
        if (chosePlayer == 'x' || chosePlayer == 'X')
        {
            mainPlayer = (char)Players::PLAYER_X;
            secondPlayer = (char)Players::PLAYER_O;
            choosePass = true;
        }
        else if (chosePlayer == 'o' || chosePlayer == 'O')
        {
            mainPlayer = (char)Players::PLAYER_O;
            secondPlayer = (char)Players::PLAYER_X;
            choosePass = true;
        }
        else
        {
            std::cout << "Invalid choice\n Try again: ";
        }
    }
}

// To make a player choose a number to which they want to occupy
void Player::movePlayer(Board &myBoard)
{
    size_t choseNum;
    bool inputPass = false;

    /*
    I make it turnPlayer != mainPlayer because if I make it
    turnPlayer == mainPlayer then the computer will make the first move
    I don't know why. Probably should find out the why. But it'll do for now
    */

    // If turnPlayer is not mainPlayer then it's the player's move
    if (turnPlayer != mainPlayer)
    {
        std::cout << "Player " << mainPlayer << " choose a number: ";

        while (!inputPass)
        {
            if (std::cin >> choseNum)
            {
                myBoard.markBoard(choseNum, mainPlayer, inputPass); //Go to markBoard function in board.cpp
            }
            else
            {
                std::cout << "Invalid input type (Type only number)\nTry again: ";
                std::cin.clear();                                                   // To clear the input so 
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // the player can input again
            }
        }
    }
    // If the turnPlayer is mainPlayer then it's the computer's move
    else
     {
         while (!inputPass)
         {
             // To make a random move for the computer
             std::random_device rd;
             std::mt19937 gen(rd());
             std::uniform_int_distribution<> distrib(1, 9);
             choseNum = distrib(gen);

             myBoard.markBoard(choseNum, secondPlayer, inputPass);
         }
     }
}

// To change turn, if the player finishes then the computer will make the move
void Player::switchPlayer()
{
    turnPlayer = (turnPlayer == mainPlayer) ? secondPlayer : mainPlayer;
}

main.cpp

#include "board.h"
#include "player.h"

int main()
{
    Board myBoard;
    Player mainPlayer;
    
    int countTurn{ 0 };
    bool choosePass = false;
    bool isDone = false;
    

    myBoard.printBoard(); // To print the initial board with numbered spaces
    
    while (!isDone)
    {
        if (!choosePass)
        {
            mainPlayer.choosePlayer(choosePass);
        }

        mainPlayer.movePlayer(myBoard);
        myBoard.printBoard();
        mainPlayer.switchPlayer();
        myBoard.checkWin(isDone, countTurn);
    }
    myBoard.printWinner(isDone, countTurn);
}

답변

6 AryanParekh Nov 12 2020 at 00:46

있어야합니까 Globals.h?

동의하지 않습니다. 수업 에만 의미있는 Globals.h싱글 enum이 있습니다 Player. 그렇다면 새 헤더를 만드는 이유는 무엇입니까? 왜 enum class Players그냥 들어갈 수 Player.cpp없습니까? 이 파일은의 내용에 액세스하는 유일한 파일입니다 Players. 여기서 할 수있는 가장 좋은 방법은 익명의 네임 스페이스Player.cpp 를 만들고 그대로 두는 것입니다.

// Player.cpp
namespace {
    enum class Players { ... };
}

또한 헤더 파일에서 이름이 지정되지 않은 네임 스페이스를 사용하는 동안주의하십시오.


사용 표준 : tolower를을

문자의 두 경우와 비교하는 대신 std::tolower문자를 소문자로 직접 변환하는 데 사용 합니다. 이것은 변환됩니다

std::cin >> chosePlayer;

if (chosePlayer == 'x' || chosePlayer == 'X') {...}
else if (chosePlayer == 'o' || chosePlayer == 'O') {...}
else {...}

으로

std::cin >> chosePlayer;
chosePlayer = std::tolower(chosePlayer, std::locale());

if (chosePlayer == 'x' ) {...}
else if (chosePlayer == 'o') {...}
else {...}

#include <locale>

  • 1 자 이상의 문자를 입력하면 코드가 첫 번째 문자를 허용합니다. 예를 들어, 사용자가를 입력 cplusplus하면 chosePlayer이제로 설정됩니다 c.

사용 enum class만든을

당신은이 만든 enum마법을 제거 x하고 o. 왜 아직도 여기에서 사용하고 있습니까?

if (chosePlayer == 'x' ) 
else if (chosePlayer == 'o')

enum class Players여기 의 값을 사용 하십시오.


enum여기에서 사용

일부는 동의하지 않을 수 있지만 여기에 enum비해 an 이 더 낫다고 생각합니다 enum class. 그 이유 charenumchar유형 을 비교할 때마다 값을 지속적으로 캐스팅 할 필요가 없기 때문 입니다. 앞서 언급 한 것처럼
단일 .cpp파일 에서만 볼 수 있다면 이름 충돌이 발생하지 않을 것입니다.

enum Player : char { PLAYER_1 = 'x', PLAYER_2 = 'o' };

에서 Player::chosePlayer()

void Player::choosePlayer(bool& choosePass)
{
    char chosePlayer;
    std::cout << "Do you want to be player X or O? ";

    while (!choosePass)
    {
        std::cin >> chosePlayer;
        // If the player type X uppercase or lowercase then they will be
        // X and the computer will be O, vice versa
        if (chosePlayer == 'x' || chosePlayer == 'X')
        {
            mainPlayer = (char)Players::PLAYER_X;
            secondPlayer = (char)Players::PLAYER_O;
            choosePass = true;
        }
        else if (chosePlayer == 'o' || chosePlayer == 'O')
        {
            mainPlayer = (char)Players::PLAYER_O;
            secondPlayer = (char)Players::PLAYER_X;
            choosePass = true;
        }
        else
        {
            std::cout << "Invalid choice\n Try again: ";
        }
    }
}

입력 된 값이 좋은지 나쁜지를 나타내려면 왜 참조를 bool변수에 전달 합니까? true입력이 양호하고 입력이 좋지 않으면 반환하지 않는 이유 false는 무엇입니까? 참조에 의한 전달은 암시 적으로 포인터를 전달하므로 실제로 함수의 bool 변수에 대한 포인터를 전달합니다. 당신은 것 가지고 는 현재의 논리로 가면 참조로 전달하는,하지만 것입니다

sizeof(bool) == 2
sizeof(bool*) == 8

그 이유와 단순함을 위해 단순히 돌아 True오거나 False더 나을 것이라고 믿습니다.


승자 확인

현재 승자를 확인하는 알고리즘은 매우 길고 읽기 어렵습니다. 더 나은 방법이 있습니다. 이 스레드는 그들에 대한 많은 유용한 정보를 제공합니다 . 가장 간단한 것

constexpr int NB_WIN_DIR = 8;
constexpr int N = 3; // please think of a better name 

constexpr int wins[NB_WIN_DIR][N] {
    {0, 1, 2}, // first row
    {3, 4, 5}, // second row
    {6, 7, 8}, // third row
    {0, 3, 6}, // first col
    {1, 4, 7}, // second col
    {2, 5, 8}, // third col
    {2, 4, 6}, // diagonal
    {0, 4, 8}, // antidiagonal
};

for (int i = 0; i < NB_WIN_DIR ;i++)
{
    if (board[wins[0]] == board[wins[1]] and board[wins[1]] == board[wins[2]]) 
        return board[wins[0]];
}

언제지나 가야 const&합니까?

내가보고 const bool&const size_t&기능 인수를.
당신은 언제 해야 일정한 기준으로 통과

  • 큰 물체에 대한 복사를 피하고 싶을 때

앞서 말했듯이 참조로 전달하면 암시 적으로 포인터가 전달됩니다. 하지만 문제는

sizeof(bool) == 2
sizeof(bool*) == 8

sizeof(size_t) == 8 // depending on your machine, sometimes 4
sizeof(size_t*) == 8 

따라서 기껏해야 전혀 좋지 않은 일이며 더 나쁜 일을 할 수도 있습니다 . 엄지 손가락의 간단한 규칙, 당신은 같은 기본 유형을 전달할 필요가 없습니다 int, char, double, float에 의해 const&당신 같은이있는 경우, 그러나, 참조로 통과 않습니다 std::vector.

날 오해하지 마세요, 당신이 해야 하는 함수가 객체의 원래 값을 수정해야하는지 참조로 전달합니다. 그러나 이것이 의도가 아니라면 큰 물체에만 사용하십시오.


코드 구조 재검토

이 수업이 정말 싫어요

class Player
{
private:
    char mainPlayer;
    char secondPlayer;
    char turnPlayer = mainPlayer;

public:
    void choosePlayer(bool &choosePass);
    void movePlayer(Board& myBoard);
    void switchPlayer();
};

당신의 Player수업은 한 명의 플레이어에 대한 정보를 가지고 있지 않습니다. 모든 멤버 함수는 board. 이 모든 것은 실제로 당신의 Board수업에 속합니다 . 플레이어는 실제로 char, 둘 중 하나 o또는 x. 말 그대로 그 외에는 다른 정보가 없습니다. 당신이해야 할 일은 단순히 당신이 이미하는 것과 같은 열거 형을 사용하여 플레이어를 나타내는 것입니다.

enum Player { ... };

class Board{ 
      Player human;
      Player bot;  
};

bot당신에 대하여 재생중인 컴퓨터가 될 것이고, human실제 사용자가 될 것입니다.

클래스를 사용하여 표현 해야 한다고 생각 하는 것은 간단한 동작입니다. 이동에는 두 가지가 있습니다.

  • 광장
  • 플레이어

프로그램의 모든 곳에서이 두 가지를 개별적으로 통과 struct했습니다. 그것을 담을 수있는 단순한 것을 만들어 보지 않겠습니까?

struct Move {
    int square;
    Player player;
}

나는이 게임이 어떻게 재구성 될 수 있는지에 대한 아주 기본적인 예를 썼습니다.

class Game
{
    private:
        struct Move {
            Player player;
            int square;

            Move(const int square, const Player player)
                : square(square), player(player)
            {}
        };

        enum Player {
            PLAYER_1, PLAYER_2, NONE 
        };

        template < typename T, size_t N > using array = std::array < T, N  >;


        array < char, NB_SQ > board;
        Player human;
        Player bot;

        short int turns; // number of total moves played
    

    
        void computer_move();
        Move input_move() const;
        void make_move(const Move& move);
        bool validate_move(const Move& move);

        Player check_win() const;
        bool check_draw() const;

        void print_board() const;
        void new_game(); // choose whether the player plays 'x' or 'o' here
        
    public:
        void mainloop(){
            for (;;) {
                const Move& move = input_move();
                make_move(move);
                computer_move();

                if (check_win()) // ...
                if (check_draw()) // ...

            }
        }
        
        Game() { new_game(); }

};
int main() {
    Game game;
    game.mainloop();
}

system("cls")

현재 프로그램은 창이 아닌 운영 체제에서 작동하지 않습니다. 대부분의 다른 시스템에서 단어는 clear입니다. 이식성을 높이기 위해 #ifdef 문을 사용 하여 운영 체제를 확인할 수 있습니다.

void clear_screen()
{
#ifdef _WIN32
    system("cls");
#else 
    system("clear");
#endif
}

더 읽어보기

3 pacmaninbw Nov 12 2020 at 05:47

전반적인 관찰

의 코드 main()는 크기가 좋고 멋지고 빡빡하며 매우 읽기 쉽습니다. 유일한 main()단점은 실제로 필요하지 않은 주석입니다.

Board와 Player 사이에 상호 의존성이있는 것 같습니다. 소프트웨어 설계에서 이것은 긴밀한 결합으로 알려져 있으며 일반적으로 잘못된 설계를 나타냅니다.

Player 클래스의 인스턴스가 하나만 표시되며 각 플레이어에 대해 하나씩 2 개의 인스턴스가 표시 될 것으로 예상합니다.

긴밀한 결합을 제거하기 위해 개체 설계 작업을 계속하고 SOLID 프로그래밍 원칙 을 따르십시오 . 구성과 같은 객체 지향 디자인 패턴을 배웁니다.

SOLID는 소프트웨어 설계를보다 이해하기 쉽고 유연하며 유지 보수하기 쉽게 만들기위한 5 가지 설계 원칙의 니모닉 약어입니다. 이렇게하면 개체와 클래스를 더 잘 디자인 할 수 있습니다.

  1. 단일 책임 원칙 - 클래스는 단지 클래스의 사양에 영향을 미칠 수 있어야 소프트웨어 사양의 일부를 변경, 단일 책임을 가져야한다.
  2. 오픈 폐쇄 원칙 - 확장을 위해 열려 있어야 소프트웨어 엔티티 (등 클래스, 모듈, 함수 등) 상태지만, 수정을 위해 마감했다.
  3. 리스 코프 치환 원칙은 - 프로그램의 객체는 그 프로그램의 정확성을 변경하지 않고 자신의 서브 타입의 인스턴스로 교체해야합니다.
  4. 인터페이스 분리의 원칙 - 어떤 클라이언트가 사용하지 않는 방법에 따라 강제되지 않는다는 것을 상태.
  5. 종속성 반전 원리 - 소프트웨어 모듈 디커플링의 특정 형태입니다. 이 원칙을 따를 때, 높은 수준의 정책 설정 모듈에서 낮은 수준의 종속성 모듈로 설정된 기존 종속성 관계가 역전되어 낮은 수준의 모듈 구현 세부 정보와 독립적으로 높은 수준의 모듈을 렌더링합니다.

높은 수준의 경고를 설정하고 경고를 무시하지 마십시오.

컴파일 할 때 두 가지 경고가 있으며 두 경고 모두 코드에서 가능한 논리 문제를 나타냅니다.

한 가지 경고는이 줄의 데이터 손실 가능성입니다.

            return middleBoard; // To return the character of the player who won  

에서 Board::checkwin(). 코드로 선언 된 변수에 반환되기 때문에이 경고는 size_tA와를 char.

두 번째 경고도 약 Board::checkwin()이며, 경고는 not all control paths return a value함수의 마지막 줄에 표시됩니다. 이것은 코드에서 가능한 논리 문제를 분명히 나타 내기 때문에 두 경고 중 더 심각한 것일 수 있습니다.

이전 C 스타일 캐스트보다 C ++ 스타일 캐스트 선호

다음 코드 줄은 이전 C 스타일 캐스트를 사용하고 있습니다.

            board[initialNum] == (char)Players::PLAYER_X ? scoreX++ : scoreO++;

C ++의 더 나은 경고 및 컴파일러 오류를 제공 그 자체 캐스트를 가지고,이은 static castsdynamic casts. 정적 캐스트는 컴파일 타임에 발생하며 캐스트가 형식에 안전하지 않은 경우 가능한 오류 또는 경고를 제공합니다. 위의 코드 줄에서 정적 캐스트가 더 적합합니다.

            board[initialNum] == (static_cast<char>(Players::PLAYER_X)) ? scoreX++ : scoreO++;

주석보다 자체 문서화 코드 선호

코드에 주석이 너무 많습니다. 초보 프로그래머가 알지 못하는 것 중 하나는 코드의 유지 관리입니다. 작성한 코드는 20 년 이상 사용 중일 수 있으며 그렇게 오랫동안 회사에서 일하지 않을 가능성이 높습니다. 코드에 주석이 많으면 코드 자체와 마찬가지로 주석을 유지해야하며 이로 인해 수행해야하는 작업량이 두 배가 될 수 있습니다. 명확한 변수, 클래스 및 함수 이름을 사용하여 자체 문서화 코드를 작성하는 것이 좋습니다. 디자인 결정이나 높은 수준의 추상화에 주석을 사용합니다. 함수에 특별한 흐름이 필요한 경우 함수 앞의 주석 블록에 있습니다.

드라이 코드

DRY 코드라고도 하는 Do n't Repeat Yourself Principle 이라는 프로그래밍 원칙이 있습니다. 동일한 코드를 여러 번 반복하는 경우이를 함수로 캡슐화하는 것이 좋습니다. 반복을 줄일 수있는 코드를 반복 할 수있는 경우. 이 함수 Board::checkWin()는 승리를 확인하는 3 개의 루프에 중복 코드를 포함합니다. 이 문제를 해결하는 방법에는 여러 가지가 있으며 다른 답변에서 좋은 방법이 제안되었습니다.

복잡성

함수 Board::checkWin()가 너무 복잡합니다 (너무 많이합니다). 문자 Board::checkWin()를 반환하는 대신 승리 여부를 나타내는 부울 값을 반환해야합니다. 다른 기능은 적절한 문자로 보드 업데이트를 구현해야합니다. 이 기능의 복잡성으로 인해 경고가 발생했습니다 not all control paths return a value.

매직 넘버

Board::checkWin()각 루프 의 함수에는 승리 여부를 확인하는 매직 넘버 가 있습니다. 코드를 더 읽기 쉽고 유지 관리하기 쉽게 만들기 위해 기호 상수를 만드는 것이 좋습니다. 이 번호는 여러 곳에서 사용될 수 있으며 한 줄만 편집하여 변경할 수 있으므로 유지 관리가 더 쉽습니다.

코드의 숫자 상수는 명백한 의미가 없기 때문에 매직 넘버 라고도 합니다. 이것에 대한 논의는 stackoverflow에 있습니다.