코딩/C와 C++

[C++ 기초 강좌] 8. 값 비교하기

드리프트 2021. 7. 3. 15:42
728x170

이 장에서 우리는 여러분의 C++ 프로그램에게 어떻게  의사 결정 기능을 추가하는지에 대해서 살펴볼 것이다. 또한, 여러분은 지정된 조건이 만족될 때까지 여러분의 프로그램이 일련의 행동을 반복하도록 만드는 방법에 대해서도 배우게 된다. 이러한 기능은 여러분이 다양한 분량의 입력을 다룰 수 있도록 할 뿐 만 아니라, 여러분이 읽어 들인 데이터가 유효한지를 검사한다. 또한, 여러분은 입력 대이터에 따라서 자기의 행동을 변경시킬 수 있는 프로그램을 작성할 수 있게 된다.

 

그리고 문제를 해결하는 데 있어서 반드시 논리가 필요한 상황을 처리할 수 있게 된다.

이 장의 끝부분 즈음까지 여러분은 다음과 같은 것들을  배우게 된다.

 

• 데이터 값을 비교하는 방법
• 결과에 따라서 프로그램 실행의 순서를 변경하는 방법
• 논리 연산자와 논리 표현이란 무엇인가, 그리고 그것들을 어떻게 사용할 수 있는가
• 여러 개 중에서 하나를 선택해야 하는 상황을 처리하는 방법

• 프로그램에서 루프를 작성하고 사용하는 방법

 

우리는 가장 강력한 프로그래밍 툴 중의 하나를 살펴볼 것이다. 즉, 변수와 표현을 다른 변수와 표현과 비교하는 능력과 출력되는 값에 따라서 한 집단의 문장 또는 다른 집단의 문장을 실행하는 기능이다.

 

 

1. 값 비교하기


우리가 남은 각자의 인생을 되는대로 살 생각이 아니라면 우리는 사물을 비교할 수 있는 메커니즘을 가지고 있어야 한다.

이 메커니즘은 관계 연산자라는 몇 가지 새로운 연산자를 사용한다. 여러분의 컴퓨터에 있는 모든 정보는 궁극적으로 수치 값으로 표현되기 때문에(우리는 마지막 장에서 문자 정보가 어떻게 수치 코드로 표현되는지에 대해서 살펴볼 것이다) 수치 값을 비교하는 것은 실질적으로 모든 의사 결정에서 필수적인 것이다. 우리가 두 개의 값을 비교하는 데 있어서 6가지의 기초적인 연산자를 사용한다.

 

< 보다 작다.

> 보다 크다.

== 똑같다.

<= 작거나 같다.

>= 크거나 같다.

!= 다르다.

 

'똑같다.' 비교 연산자는 두 개의 연속적인 '=' 기호를 가지고 있어야 한다. 이것은 단지 하나의 '=' 기호로 이루어진 할당 연산자와는 다른 것이다. 비교 연산자 대신에 할당 연산자를 쓰는 것이 가장 범하기 쉬운 실수이다. 그러므로 잠재적인 혼동의 염려가 있는 이 부분을 조심해야 한다.

이러한 각각의 연산자는 두 개의 값을 비교하여 두 가지의 가능한 불린 값 중 하나(즉, 그 비교가 맞다면 true를 그렇지 않은 경우에는 false를)를 리턴한다. 우리는 몇 가지의 간단한 샘플을 통하여 이것이 어떻게 작동하는지를 살펴볼 것이다. 우리가 각각 10과 -5라는 값을 가지는 i와 j라는 정수 변수를 가지고 있다고 생각해 보자. 그러면 다음과 같은 표현

i > j     i != j       j > -8     i <= j + 15

는 모두 true를 리턴한다.

 

우리가 다음과 같은 변수를 정의했다고 생각해 보자.

char first = 'A', last = 'Z';

이제, 우리는 문자 변수를 사용하여 몇 가지의 비교 샘플을 작성할 수 있다.

다음을 살펴보라.

first == 65    first < last       'E' <= first     first != last

이것 네 개 모두 ASCII 코드값을 비교하고 있다. 첫 번째 표현은 first가 십진수로 65에 해당하는 'A'라는 초기값을 가지고 있기 때문에 true를 리턴한다. 두 번째 표현은 'A'라는 값을 가지고 있는 first의 값이 'Z'라는 값을 가지고 있는 last의 값보다 작은 지를 검사한다. 만약 여러분이 이 문자들에 해당하는 ASCII 코드를 참조해 보았다면 대문자들은 수치 값인 65('A'를 나타낸다)에서 90('Z'를 나타낸다)까지 올림차순으로 표현된다는 것을 알 것이다. 그러므로 이 비교 작업도 true를 리턴한다. 세 번째 표현은 'E'가 first의 값보다 크기 때문에 false를 리턴한다. 마지막 표현은 분명히 'A'는 'Z'와 같지 않기 때문에 true를 리턴한다.

 

몇 가지 좀 더 복잡한 비교 작업을 살펴본다. 변수가 다음과 같이 정의되었을 때

int i = -10, j = 20;
double x = 1.5, y = -0.25E-10;

다음의 문장을 보라.

-1 < y     j < (10 - i)    2.0*x >= (3 + y)

여러분도 알 수 있듯이 우리는 비교 작업에서 결과가 수치 값으로 나오는 표현을 사용할 수 있다. 만약 여러분이 2장에 있는 우선순위 표를 검토해 보았다면, 엄격히 말해서 괄호는 필요하지 않다. 그러나 표현을 좀 더 분명하게 하기 위해서 괄호가 도움이 된다. 첫 번째 비교 문장은 올바르다. 그러므로 bool 값인 true틀 리턴한다. y 변수의 값은 가장 작은 음의 값인 -0.000000000025를 가지고 있다. 그러므로 -1보다는 크다. 두 번째 비교 문장은 false를 리턴한다. 10 - i라는 표현은 j와 같은 값인 20을 가진다. 세 번째 표현은 3 + y가 3보다 약간 크기 때문에 true를 리턴한다. 

우리는 관계 연산자를 사용하여 모든 기본적인 형식을 가지는 값을 비교할 수 있다. 그러므로 현재 우리가 필요로 하는 모든 것은 프로그램의 행동을 수정하기 위해서 비교의 결과를 사용하는 실제적인 방법이다.

 

 

1.1 if 문장

 

기본적인 if 문장은 설정된 조건이 true값을 리턴하면 여러분의 프로그램이 하나의 문장 또는 중괄호로 묶여 있는 문장들의 블록을 실행하도록 한다.

if 문장의 간단한 예는 다음과 같다.

if (letter == 'A')
    cout << "The first capital, alphabetically speaking.";

검사될 조건은 if 키워드 바로 뒤에 오는 괄호 안에 나타난다. 여기서 세미콜론의 위치를 보라. 세미콜론은 if 다음의 문장 뒤에 위치한다. 괄호 안에 있는 조건 뒤에는 세미콜론이 붙지 않는다. 또한, 여러분은 if 뒤에 문장이 어떻게 들여 써져 있는지를 볼 수 있을 것이다. 들여 써진 의미는 if 조건이 true값을 리턴할 때 그 들여 써진 문장만이 실행된다는 것을 의미한다. 들여 쓰기는 프로그램이 실행되는 데 있어서 꼭 필요한 것은 아니다. 그러나 여러분이 if 조건과 그것에 의존하고 있는 문장 사이의 관계를 인식하는 것을 돕는다. 

출력 문장은 letter 변수가 ‘A'라는 값을 가질 때만 실행된다. 우리는 이 샘플을 확장시켜서 letter가 ‘A’ 값을 가지고 있다면 그것의 값을 바꾸는 프로그램을 작성할 수 있다. 

if (letter == 'A')
{
    cout << "The first capital, aLphabetically speaking."; 
    Letter = 'a'; 
}


여기서 우리는 letter == ‘A’)라는 조건이 true를 리턴할 때만 블록 안에 있는 문장들을 실행시킨다. 중괄호를 사용하지 않는다면 단지 첫 번째 문장이 if의 당면 과제가 되기 때문에 ‘a'를 letter에게 할당하는 문장은 항상 실행될 것이 다. 블록의 끝에 있는 중괄호 다음이 아닌 블록 안에 있는 각각의 문장 다음에 세미콜론이 있다는 것을 명심하라. 이제, letter가 ‘A,라는 값을 가졌으므로 우리는 이전과 동일한 메시지를 출력한 다음, 그것의 값을 'a'로  바꾼다. 만약 조건이 false를 리턴하면 그 두 문장 모두 실행되지 않는다.

 

 

1.2 중첩된 if 문장

 

if 문장 안에 있는 조건이 true일 때 실행되는 문장은 if 문장이 될 수도 있다. 이러한 구조를 중첩된 if라고 한다. 안쪽의 if에 대한 조건은 바깥쪽에 있는 if 가 true일 경우에만 검사된다. 다른 if 문장 안에 중첩되어 있는 하나의 if 문장도 중첩된 if 문장을 포함할 수 있다. 일반적으로, 여러분은 이와 같이 계속해서 if 문장을 중첩시킬 수 있다. 단,  여러분이 만들고 있는 상황을 여러분 스스로가 잘 파악하고 있는 경우에 한해서.

 

연습 - 중첩된 if 사용하기

 

다음과 같은 실제적인 샘플을 통하여 중첩된 if를 설명할 수 있다.

// EX3_01.cpp
// 중첩된 if 문장

#include <iostream>

using namespace std;

int main()
{
  char letter = 0;
  
  cout << endl
       << "Enter a letter: ";
  cin >> letter;
  
  if (letter >= 'A')
    if (letter <= 'Z')
    {
      cout << endl
           << "You entered a capital letter."
           << endl;
      return 0;
    }


  if (letter >= 'a')
    if (letter <= 'z')
    {
      cout << endl
           << "You entered a small letter."
           << endl;
      return 0;
    }

  cout << endl << "You did not enter a letter." << endl;
  return 0;
}

 

작동하는 방식

 

이 프로그램은 평범한 주석으로부터 시작한다. 그런 다음, 입출력을 지원하는 헤더 파일에 대한 #include문장이 오고 나서, using 명령이 온다. 왜냐하면, 그러한 입출력 루틴은 std 네임 공간에 속하기 때문이다. main() 본문 안에 있는 첫 번째 동작은 문자를 입력받는 것이다 이 문자는 char 형식을 가지는 letter 변수 안에 저장된다. 

입력을 검사하는 문장이 그다음에 오는 if문은 입력된 문자가 ‘A'와 같거나 더 큰지를 검사한다. 소문자에 대한 Asal 코드(97에서 122까지)는 대문자의 그것(65에서 90까지)보다 크기 때문에 소문자를 입력하면 첫 번째 if 블록이 실행된다. 왜냐하면, (letter >= 'A')는 모든 문자에 대해서 true를 리턴하기 때문이다. 이 경우, 입력이 ‘Z'보다 작은 지를 검사하는 중첩된 if가 실행된다. 만약 입력된 문자가 ‘Z'이거나 그것보다 작다면 우리는 그것이 대문자라는 것을 알게 되어 메시지가 표시되고, 비교 작업은 끝나게 된다. 그러므로 우리는 프로그램의 끝부분에 있는 retum 문장을 실행한다. 그 두 문장은 괄호 안에 싸여 있다. 그러므로 중첩된 if 조건이 true를 리턴하면 그 두 문장은 모두 실행된다. 

그다음의 if문은 첫 번째의 if문과 본질적으로 동일한 메커니즘을 사용하여 입력된 문자가 소문자인지를 검사하여 메시지를 나타내고 리턴한다. 

만약 입력된 문자가 문자가 아니라면 마지막 if문 뒤에 오는 출력문이 실행된 다. 이 문장은 입력된 정보가 문자가 아니라는 사실을 알리는 메시지를 나타낸 다. 그런 다음,  return이 실행된다. 

각 문장에 대해서 들여 쓰기를 했기 때문에 여러분은 중첩된 if문들과 출력 문장 사이의 관계를 훨씬 쉽게 알 수 있을 것이다 

이 샘플의 전형적인 출력은 다음과 같다.

대문자를 검사하는 단지 하나의 추가의 문장을 if문에 포함시키기만 하면 여려 분은 대문자를 소문자로 바꾸는 프로그램을 쉽게 짤 수 있다.

if (letter >= 'A')
    if (letter <= 'Z')
    {
      cout << endl
           << "You entered a capital letter."
           << endl;
      letter += 'a' - 'A';   // 소문자로 변경
      return 0;
    }

위의 코드에는 하나의 추가된 문장을 가지고 있다. 이 문장은 letter 변수를 'a' - 'A'값만큼 증가시킴으로써 대문자를 소문자로 바꾼다. 이것은 'A'에서 'Z'까지 와 'a'에서 'z'까지에 대한 ASCII 코드가 계속되는 두 그룹의 수치 코드를 가지고 있기 때문이다. 그러므로 'a' - 'A' 표현은 대문자와 동일한 소문자를 얻기 위해서 대문자에게 추가되어야 할 값을 나타내게 된다.

 

 

1.3 확장된 if문

 

우리가 지금까지 사용해 왔던 if문은 지정된 조건이 true를 리턴할 때 하나의 문장을 실행한다. 그런 다음 프로그램 실행은 순서대로 그다음 문장으로 흘러간다. 우리는 또한 조건이 true를 리턴하면 어떤 하나의 문장을 실행시키고, false를 리턴하면 다른 문장을 리턴하는 if문도 살펴보았다. 그런 다음, 실행은 순서대로 그다음의 문장으로 가게 된다. 앞에서 보았듯이, 문장들로 된 블록은 항상 하나의 문장과 교체될 수 있다. 그러므로 if문에서도 이것이 적용된다.

 

연습 - if문을 확장시키기

 

다음은 확장된 if문에 관한 샘플이다.

// EX3_02.cpp
// 확장된 if 사용하기

#include <iostream>

using namespace std;

int main()
{
    long number =0;
    cout << endl
         << "Enteran integer number less than 2 billion: ";
    cin >> number;

    if (number % 2L)
        cout << endl
             << "Your number is odd." << endl;
    
    else 
       cout << endl
            << "Your number is even." << endl;

    return 0;
}

이 프로그램의 전형적인 출력은 다음과 같다.

 

작동하는 방식

 

입력을 number 안으로 받아들인 다음에 그 값을 2로 나눈 다음, 그 나머지를 취하여 검사한다. 그리고 그 검사 결과를 if문의 조건으로서 사용한다. 이 경우, if문의 조건은 불린 이 아닌 정수를 리턴한다. if 문은 조건이 0이 아닌 값을 리턴하면 그것을 true로 인식하고, 0을 false로 인식한다. 다시 말해서 if문

if (number % 2L != 0)

는 다음과 같은 방식으로 결과를 해석한다. 즉, 나머지가 1이면 조건은 true이고, 곧바로 if문 다음에 있는 문장이 실행된다. 만약 나머지가 0이면 조건은 false가 되고, else 키워드 다음에 있는 문장이 실행된다.

 

관계 연산자는 단지 true 또는 false값만을 리턴한다. 하나의 if문에서 조건은 우리가 앞장에서 보았던 모든 기본적인 데이터 형식을 취할 수 있다. 그러한 경우, if문은 항상 조건에 의해서 리턴되는 0이 아닌 값을 true로 해석하고, 0을 false로 해석한다. 그와 반대로, bool값인 true가 정수로 변환되면 1이라는 값을 가지고, false는 0 값을 가진다.

정수가 2로 나누어진 나머지는 1 또는 0이 되기 때문에 우리는 이 코드에게 그 사실을 나타내는 코멘트를 달았다. 그 두 가지 중 하나의 결과가 출력된 다음에 프로그램을 종료하는 return 문장이 실행된다.

else 키워드는 문장의 if 부분과 비슷하게 세미콜론이 없이 써졌다. 다시 말하지만, 들여 쓰기는 다양한 문장들 간의 관계를 시각적으로 나타낸다. 여러분은 true 또는 0이 아닌 값에 대해서 어떤 문장이 실행될 것인가, 그리고 false 또는 0인 값에 대해서는 어떤 문장이 실행될 것인가를 분명히 알 수 있을 것이다. 여러분은 문장의 논리적인 구조를 나타내기 위해서 항상 프로그램 안에서 문장을 들여 써야 한다.

if-else 조합은 두 가지의 옵션 중에서 하나를 선택할 수 있도록 한다. if-else의 일반적이 논리 구조가 다음에 나타나 있다.

그림 안에 있는 화살표는 if 조건이 true 또는 false를 리턴하는 것에 따라서 문장이 실행되는 순서를 나타낸다.

 

 

 

1.4 중첩된 if-else 문장

 

우리가 보았듯이 여러분은 if문 안에 if 문장들을 중첩시킬 수 있다. 또한, 여러분은 if문 안에 if-else 문을, if-else문 안에 if문을 중첩시킬 수도 있다. 이것은 우리에게 혼동을 줄 소지가 있지만, 몇 가지의 샘플을 살펴보도록 한다. 첫 번째 경우를 먼저 알아본다. if문 안에 중첩되어 있는 if-else 문의 경우는 다음과 같다.

 

if(coffee == 'y')
  if(donuts == 'y')
    cout << "We have coffee and donuts.";
  else 
    cout << "We have coffee, but not donuts"


 donuts에 대한 검사는 coffee에 대한 검사의 결과가 true를 리턴할 때만 실행된다. 그러므로 메시지는 각 경우에 대해서 올바른 상황을 반영하게 된다. 그러 나 이것은 쉽게 혼동될 염려가 있다. 만약 우리가 부정확하게 들여 쓰기를 하면 서 많은 동일한 사항을 작성한다면 우리는 잘못된 결론에 빠질 수 있다.

 

 if (coffee == 'y')
   if (donuts == 'y')
     cout << "He have coffee and donuts.";
 else // 이 else는 일부러 잘못되게 한 것임
  cout << "We have no coffee-"; // 들린 결과


 여기서는 이러한 실수를 쉽게 파악할 수 있지만, 좀 더 복잡한 if 구조에서 우리는 어떤 if가 어떤 else를 소유하는지에 대해서 잘 파악하고 있어야 한다.
 
 이것이 약간 복잡하게 보이지만, 여러분은 이 규칙을 정렬 작업에 적용할 수 있다. 여러분만의 프로그램을 작성할 때 여러분은 항상 괄호를 사용하여 상황을 명확하게 나타낼 수 있다. 간단한 경우에서는 괄호가 필요 없지만, 이전 샘 풀을 다음과 같이 쑬 수 있다.

 

if(coffee == 'y')
{
  if(donuts == 'y')
    cout << "We have coffee and donuts.";
  else 
    cout << "We have coffee, but not donuts"
}

 

프로그램의 구조가 확실히 명확하게 보인다. 이제, 우리가 그러한 규칙을 알고 있으므로 if-else 안에 중첩되는 if문도 쉽게 이해할 수 있을 것이다.

if(coffee == 'y')
{
  if(donuts == 'y')
    cout << "We have coffee and donuts.";
}
  else (tea == 'y') 
    cout << "We have coffee, but not donuts"

여기서 괄호는 필수적이다. 만약 우리가 괄호를 사용하지 않는다면 else는 donuts를 찾고 있는 if문에게 속할 것이다. 이러한 종류의 상황에서 괄호를 사용하는 것을 잊어버려서 찾아내기 어려운 에러를 발생시키기가 쉽다. 이러한 종류의 에러를 가지는 프로그램은 컴파일도 잘되고, 심지어 어떤 경우에는 올 바른 결과를 생성하기도 한다.

 

 만약 우리가 이 샘플에서 괄호를 없앤다면 coffee와 donuts가 둘 다 'y'를 가질 경우에만 올바른 결과가 출력된다. 그러로 if(tea == 'y') 검사 문장은 실행돼 지 않는다.


 다음은 if-else문 안에 중첩되어 있는 if-else문에 대해서 살펴본다. 이것은 단지 한 겹으로 중첩된 경우라 할지라도 매우 복잡할 수 있다. if문을 다시 사용하여 커피와 도너스를 분석하는 구문을 샅샅이 살펴보도록 한다.

 

 if(coffee == 'y')
   if(donuts == 'y')
     cout << "We have coffee and donuts.";
   else cout << "We have coffee, but not donuts";
else if(tea == 'y')
  cout << "We have no coffee, but we have tea, and maybe donuts...";
  else 
     cout << "NO tea or coffee, but maybe donuts...";


 여기서의 논리는 올바르게 들여 쓰기를 하더라도 그렇게 분명하게 파악되지는 않는다. 괄호는 필요하지 않다. 왜냐하면, 여러분이 이전에 살펴본 규칙에 의해 서 이것이 올바르다는 것이 확인되기 때문이다. 그래도 괄호를 쓰면 약간 더 명확하게 보일 것이다.

 if(coffee == 'y')
 {
   if(donuts == 'y')
     cout << "We have coffee and donuts.";
   else cout << "We have coffee, but not donuts";
}
else
{
  if(tea == 'y')
    cout << "We have no coffee, but we have tea, and maybe donuts...";
    else 
       cout << "NO tea or coffee, but maybe donuts...";
}

이러한 종류의 논리를 프로그램에서 다루는 데는 이것보다 훨씬 더 좋은 방법들이 있다. 만약 여러분이 if문을 여러 번 중첩시키면 십중팔구 어디선가 에러가 나기 마련이다. 다음 절은 그러한 상황을 단순화하는 방법에 대해서 다룬다.

 

 

 

그리드형