코딩/C와 C++

[C++ 기초 강좌] 4. C++ 계산 작업

드리프트 2021. 3. 13. 18:32
728x170

 

 

여기서부터 우리는 아까 입력한 데이터를 가지고 어떠한 작업을 시작하게 된다. 우리는 C++ 프로그램의 '처리'부분을 시작하려 하는 것이다. C++의 계산적인 측면의 거의 모든 부분은 매우 직관적이다. 그러므로 우리는 뜨거운 칼로 버터를 베듯 이것을 쉽게 배울 수 있다.

 

 

할당문

 

우리는 이미 할당문의 예를 보았다. 일반적인 할당문은 다음과 같다.

whole = part1 + part2 + part3;

할당문은 여러분이 등호의 오른쪽에 있는 표현식의 값(이 경우, part1, part2, 그리고 part3의 합)을 계산하여 그 결과를 왼쪽에 지정되어 있는 변수(이 경우, whole) 안에 저장할 수 있도록 해준다. 이 문장에서 whole은 단지 part들의 합이고, 그 이상의 어떤 것도 아니다.

 

문장이 세미콜론으로 끝난다는 사실을 명심하라.

 

또한, 여러분은 다음과 같은 반복된 문장을 작성할 수 있다.

A = B = 1;

위의 문장은 1이라는 값을 B에게 할당하고, B의 값을 A에게 할당하는 것과 동일하다.

 

 

lvalue란?

lvalue란 메모리 안에 있는 하나의 어드레스를 지칭하는 것으로서 이것이 lvalue라고 불리는 이유는 할당문에서 등호의 왼쪽에 나타날 수 있기 때문이다. 대부분의 변수는 lvalue이다. 왜냐하면, 변수들은 메모리 안에 있는 한 장소를 나타내기 때문이다. 그러나 앞으로 우리가 보게 되듯이 lvalue가 아닌 변수도 있다. 그 변수들의 값은 상수로 정의되어 있기 때문에 그것들은 할당문의 왼쪽에 나타나지 못한다. 위의 예에서 등장하는 A, B 변수들은 lvalue이다. 반면에, A+B라는 표현은 그렇지 않다. 왜냐하면, 그것의 결과는 변수가 저장될 수 있는 메모리 상의 어드레스를 결정하지 않기 때문이다.

 

 

산술 연산

 

우리가 마음대로 사용할 수 있는 산술 연산에는 각각 +, -, *, /의 기호로 표현되는 덧셈, 뺄셈, 곱셈, 나눗셈이 있다. 일반적으로, 이것들은 여러분이 예상한 방식대로 작동한다. 다만 나눗셈의 경우, 정수 변수와 상수값에 대해서는 약간 다른 방식으로 사용된다. 이것에 관해서는 나중에 살펴볼 것이다. 여러분은 문장을 다음과 같이 쓸 수 있다.

netPay = hours * rate - deductions;

여기서 hours와 rate의 곱이 계산된 다음, 그 생성된 값으로부터 deductions가 감하여진다. 곱셈 연산자와 나누기 연산자는 덧셈과 뺄셈 작업보다 먼저 실행된다. 우리는 이러한 실행 순서에 대해서 나중에 이 장에서 더 자세히 살펴볼 것이다. 표현식의 전체적인 결과는 netPay라는 변수에 저장된다.

 

마지막 문장에서 사용된 뺄셈 기호는 두 개의 피연산자에게 적용된다. 즉, 하나의 값에서 다른 값을 빼는 것이다. 이것을 이진(binary) 연산이라고 부르는데, 그것은 두 개의 값이 연관되기 때문이다. 뺄셈 기호는 하나의 피연산자에게 작용하는 그 값의 부호를 바꾸는 데 사용될 수도 있다. 이러한 경우, 그것은 1진 뺄셈 기호(unary minus)라고 부른다. 여러분은 그것을 다음과 같이 쓸 수 있다.

int A = 0;
int B = -5;
A = -B;

여기서 A에게는 5라는 값이 할당되는데, 이것은 1진 뺄셈 기호가 피연산 B의 부호를 변경시키기 때문이다.

 

이때 할당문이라는 것은 여러분이 고등학교 대수학 시간에 본 등식이라는 것과는 다른 것이라는 사실을 명심해야 한다. 할당문이라는 것은 어떠한 사실을 진술하는 것이 아니라 수행되어야 할 작업을 나타낸다. 다음과 같은 문장

A = A + 1;

에서 이 문장은 '1을 현재 A 안에 저장되어 있는 값에 더한 다음, 그 결과를 A에 저장하라.'는 뜻이다. 평범한 대수식 문장으로는 이해가 가지 않는 문장이다.

 


예제 - 기본적인 산술 연산

 

방 하나를 도배하는 데 얼마나 많은 벽지가 필요한지를 계산함으로써 C++에서의 기본적인 산술 연산을 연습할 수 있다. 다음의 샘플을 통해서 연습을 해본다.

 

// EX2_04.CPP
// 얼마나 많은 벽지가 필요한가를ㄹ 계산한다.

#include <iostream>

using namespace std;

int main()
{
	double height = 0.0, width = 0.0, length = 0.0; //방의 크기
    double perimiter = 0.0;   //방의 둘레
    
    const doubl rollwidth = 21.0;   // 표준 롤의 폭
    const double rolllength = 12.0*33.0; //표준 롤의 길이
    
    int strips_per_roll = 0;  // 한 롤에 있는 줄무늬의 개수
    int strips_reqd = 0;  // 필요한 줄무늬의 개수
    int nrolls = 0;    // 롤의 전체 개수
    
    cout << endl
    	<< "Enter the height of the room in inches: ";
    cin >> height;
    
    cout << endl;
    	<< "Now enter the length and width in inches: ";
    cin >> length >> width;
    
    strips_per_roll = rolllength / height;  // 롤 안에 있는 줄무늬의 개수를 계산한다.
    perimeter = 2.0*(length + width);    // 방의 둘레를 계산한다.
    strips_reqd = perimeter / rollwidth; //필요한 총 줄무늬를 얻는다.
    nrolls = strips_reqd / strips_per_roll // 롤의 개수를 계산한다.
    
    cout << endl
    	<< "For your room you need " << nrolls << " rolls of wallpaper."
        << endl;
    
    return 0;
}

 

작동하는 방식

처음에 확실하게 해두어야 할 것이 있다. 이 프로그램을 사용했다고 해서 여러분이 벽지를 다 써버릴 필요는 없다는 것이다. 우리가 앞으로 살펴볼 것이지만, 필요한 벽지의 개수를 계산하는 데 있어서 발생하는 모든 에러는 C++가 작동하는 방식 때문에 발생하는 것이고, 또한 여러분이 도배할 때 불가피하게 발생하는 벽지의 손실 때문이다. 그 양은 보통 50% 이상이다.

 

우리는 이 샘플에 있는 문장들을 순서대로 살펴보면서, 흥미로운 기법이나, 심지어는 흥분되는 기능에 대해서 알아볼 것이다. main()의 본문이 시작되는 곳에 있는 문장은 이제 여러분에게 익숙한 문장일 것이다. 그러므로 그것에 대해서는 그냥 넘어갈 것이다.

 

여기서 관심을 가지고 봐야 할 두 가지 일반적인 사항은 프로그램의 레이아웃이다. 먼저, main()의 본문에 있는 문장은 본문 전체를 보기 쉽도록 안쪽으로 들여 써져 있다. 두 번째로, 다양한 그룸의 문장들이 빈 줄에 의해서 분리되어 있어서 그것들이 기능적인 그룹이라는 것을 나타내고 있다. 문장을 들여 쓰는 것은 C++에서 프로그램 코드를 레이아웃하는 데 있어서 기본적인 기법이다. 여러분은 프로그램 안에서 다양한 논리적인 블록들을 시각적으로 파악할 수 있도록 하는 데 있어서 이러한 기법이 보편적으로 많이 사용된다는 것을 알게 될 것이다.

 

 

const 수정자

main()의 본문에 바로 프로그램에서 사용되는 변수에 대한 선언문 블록이 위치한다. 이러한 문장들은 매우 익숙한 것들이다. 그러나 그중 두 가지 문장은 몇 가지 새로운 기능을 포함하고 있다.

 

const double rollwidth = 21.0;
const double rolllength = 12.0*33.0;

위의 두 문장은 const라는 새로운 키워드로 시작되고 있다. 이것은 형식 수정자(type modifier)로서 변수들이 double 형식을 가지지 않는 상수라는 것을 나타낸다. 우리는 컴파일러에게 이것들이 상수라는 사실을 효과적으로 말하고 있기 때문에 컴파일러는 이러한 변수를 변경시키려고 시도하는 모든 문장을 검사하여 만약 그러한 문장이 발견되면 에러 메시지를 발생시킨다. 이것은 변수가 lvalue가 아닌 const로서 선언되었기 때문에 상대적으로 간단하다. 그러므로 이 변수는 할당 작업에서 왼쪽에 나타날 수 없다.

 

여러분은 rollwidth가 선언된 이후의 어떠한 곳에서도 다음과 같은 문장을 추가함으로써 이러한 사실을 확인할 수 있다.

rollwidth = 0;

여러분은 프로그램이 더 이상 컴파일되지 않고, 'error C2166: l-value specifies const object'라는 메시지를 리턴한다는 것을 알게 될 것이다.

 

상수를 const 변수 형식으로 선언하는 것이 매우 유용할 때가 있다. 특히, 여러분이 동일한 상수를 프로그램 안에서 몇 번씩이나 사용할 때가 그렇다. 첫 번째로, 여러분의 프로그램 여기저기에서 리터럴을 사용하는 것보다는 상수를 사용하는 것이 훨씬 낫다. 두 번째로, 여러분이 사용하고 있는 const 변수의 값을 변경해야 할 경우, 여러분은 단지 처음의 그것의 정의만을 변경시키면 그 뒤에 나타나는 상수도 모두 자동적으로 바뀌는 것이다. 우리는 이러한 기법을 많이 사용할 것이다.

 

 

상수 표현

const 변수인 rolllength도 역시 산술 표현인 (12.0*33.0)으로 초기화된다. 변수에 초기값을 할당하는 데 있어서 상수 표현을 사용할 수 있다는 것은 여러분이 직접 그 값을 계산해야 하는 수고를 피할 수 있고, 또한 더 의미가 있는 표현이라고 할 수 있다. 왜냐하면, 그냥 396이라고 쓰는 것보다는 33피트에 12인치를 곱하는 것으로 표현하는 것이 훨씬 더 명확하기 때문이다. 일반적으로, 컴파일러는 상수 표현을 정확하게 계산하는 반면에, 여러분이 그것을 직접 계산한다면 표현의 복잡성과 여러분의 숫자 처리 능력에 따라서 계산이 잘못될 수도 있다.

 

여러분은 이미 정의되어 있는 const 객체를 포함하여 컴파일할 때에 상수로서 계산될 수 있는 어떠한 표현도 사용할 수 있다. 그러므로 예를 들어, 그렇게 하는 것이 프로그램에서 유용하다면 우리는 다음과 같이 벽지 한 롤의 면적을 선언할 수 있다.

const double rollarea = rollwidth*rolllength;

당연히 이 문장은 rollarea를 초기화하는 데 사용된 두 개의 const 변수에 대한 선언 뒤에 위치해야 한다.

 


 

 

 

 

나머지 계산하기

 

우리는 마지막 샘플에서 하나의 정수를 다른 정수로 나누면 그 결과가 정수가 되며, 나머지는 무시된다. 그러므로 11을 4로 나누면 결과는 2가 된다. 나눗셈에서 발생하는 나머지는 큰 의미를 가지고 있다 예를 들어, 여러분이 쿠키를 애들에게 나눠줄 때 특히 그렇다. C++는 이러한 이유 때문에 특별한 연산자인 %를 제공한다. 그러므로 우리는 다음과 같은 문장을 쓸 수 있다.

int residue = 0, cookies = 19, children = 5;
residue = cookies % children;

residue라는 변수는 결과적으로 4(19를 5로 나눈 나머지)라는 값을 가지게 된다. 애들이 각각 몇 개의 쿠키를 받는지를 계산하려면 다음과 같이 평범한 나눗셈을 해야 한다.

each = cookies / children;

 

 

변수의 값 수정하기

 

변수의 값을 증가시키거나 두 배로 하는 것과 같이 변수의 기존의 값을 수정해야 할 경우가 종종 발생한다. 우리는 다음과 같은 문장을 사용하여 count 변수의 값을 증가시킬 수 있다.

count = count + 5;

이 문장은 현재 count 변수에 저장되어 있는 값에 단순히 5를 더한다. 그리고 그 결과를 또 count에 저장한다. 그러므로 만약 count가 10에서 시작했다면 그것은 15로 끝날 것이다. C++에서 여러분은 이와 똑같은 작업을 하는 문장을 다음과 같이 쓸 수 있다.

count += 5;

이것은 "count 안에 있는 값을 가져다가 그것에 5를 더하고, 그 결과를 count에 저장한다."는 뜻이다. 우리는 다른 연산자를 이러한 표기법으로 사용할 수도 있다. 예를 들어,

count *= 5;

위의 문장은 count 변수 안에 있는 현재의 값에 5를 곱하여 그 결과를 count에 저장한다. 일반적으로, 우리는 문장을 다음과 같은 형식으로 쓸 수 있다.

lhs op= rhs;

여기서 op는 다음과 같은 연산자 중 하나이다.

+      -      *      /      %
<<     >>     &      ^      |

처음에 나타나는 5개 연산자는 우리가 이미 보았던 것이고, 나머지는 시프트와 논리 연산자들이다. 이것에 대해서는 나중에 살펴볼 것이다. lhs는 문장의 왼쪽에 위치하는 표현을 나타내는 것으로, 보통(반드시 그런 것은 아니지만) 변수 이름이 된다. rhs는 문장의 오른쪽에 나타나는 표현을 나타낸다.

 

문장의 일반적인 형태는 다음의 것과 동일하다.

lhs = lhs op (rhs);

이것은 다음과 같은 문장

A /= B + C;

을  아래와 같이 쓸 수 있다는 것을 의미한다.

A = A / (B + C);

 

 

증가 연산자와 감소 연산자

 

이제, 우리는 증가 연산자와 감소 연산자라고 불리는 약간 평범하지 않은 산술 연산자에 대해서 알아보겠다. 우리가 본격적으로 C++ 안으로 깊이 들어감에 따라서 이 연산자들이 매우 유용한 자산이 된다는 것을 알게 될 것이다. 변수를 증가 또는 감소시키는 1진 연산자들이 있다. 예를 들어, count라는 변수가 int 형식을 가지고 있다면 다음의 세 개의 문장은 모두 동일한 결과를 낸다.

coutn = count + 1;
coutn += 1;
++count;

각각의 문장은 count 변수를 1씩 증가시킨다. 증가 연산자를 사용하고 있는 마지막 문장은 분명히 가장 간단한 문장이다. 만약 이러한 표현이 다른 표현 안에 포함된다면 연산자는 먼저 변수의 값을 증가시킨다. 그런 다음, 그 증가된 값을 표현 안에서 사용한다. 예를 들어, count가 5라는 값을 가지고 total 변수가 int 형식을 가진다면 다음의 문장

total = ++count + 6;

은 count를 6만큼 증가시키고, total에게는 12라는 값을 할당한다.

 

지금까지 우리는 ++라는 증가 연산자를 그것이 적용되는 변수 앞에 썼다. 이것을 전치(prefix) 형태라고 한다. 증가 연산자는 후치(postfix) 형태라는 것도 가지고 있다. 이때는 연산자가 그것이 적용되는 변수 뒤에 쓰인다. 이것의 결과는 약간 다르다. 예를 들어, count에 5라는 값을 할당하고, 위의 예를 다음과 같이 다시 쓰면,

total = count++ + 6;

total은 11이라는 값을 할당받는다. 왜냐하면, 1을 증가시키기 전에 표현의 값을 계산하려고 count의 초기값이 사용되기 때문이다. 위의 문장은 다음의 두 개의 문장과 동일하다.

total = count + 6;
++count;

위의 예에서 + 기호를 밀집하여 적어 놓은 것이 혼동을 일으킬 수 있다. 일반적으로, 증가 연산자를 이와 같은 방식으로 사용하는 것은 좋은 생각이 아니다. 다음과 같이 쓰면 좀 더 명확하다.

total = 6 + count++;

여기서 우리는 a++ + b, 또는 심지어 a+++b와 같은 표현을 보게 되는데, 이것은 그 표현이 의미하는 것이나, 컴파일러가 어떻게 해야 하는지에 대해서는 불명확하다. 그것들은 실제로는 동일하다. 그러나 두 번째의 경우, 여러분은 a + ++b라는 것으로 해석할 수도 있다. 이것은 전혀 다른 뜻이다. 이것은 두 개의 표현보다도 하나 더 많은 값을 계산한다.

 

증가 연산자에 대해 적용되었던 것과 정확하게 동일한 규칙이 -- 감소 연산자에게도 적용된다. 예를 들어, count가 5라는 초기값을 가지고 있었다면 다음의 문장

total = --count + 6;

은 total에게 10이라는 값을 할당한다. 반면에,

total = 6 + count--;

위의 문장은 total에게 11을 할당한다. 보통, 그 두 개의 연산자는 정수에게 적용된다. 특히, 우리가 앞으로 살펴볼 순환 루프에서 그렇다. 또한, 우리는 나중에 이 장에서 C++에서 그 연산자들이 다른 형식의 데이터에도 적용된다는 것을 살펴볼 것이다.

 


예제 - 콤마 연산자

콤마 연산자는 하나의 문장만을 써야 하는 곳에서 여러분이 여러 개의 문자를 쓸 수 있도록 한다. 이것에 대해서는 실제로 어떻게 작동되는지를 살펴보는 것이 가장 이해가 잘된다.

// EX2_05.CPP
// 콤마 연산자 연습하기

#include <iostream>

using namespace std;

int main()
{
	long num1 = 0, num2 = 0, num3 = 0, num4 = 0;
    
    num4 = (num1 = 10, num2 = 20, num3 = 30);
    
    cout << endl
    	<< "The value of a series of expressions "
        << "is the value of the right most: "
        << num4;
    cout << endl;
    
    return 0;
}

 

작동하는 방식

 

여러분이 위의 프로그램을 컴파일하여 실행시키면 여러분은 다음과 같은 결과를 얻게 된다.

이 결과는 설명하지 않아도 쉽게 알 수 있다. num4 변수는 세 개의 할당문에서 마지막에 있는 값을 받는다. 할당값은 왼쪽으로 할당된다. num4의 할당문에 있는 괄호는 꼭 필요한 것이다. 여러분은 괄호를 쓰지 않았을 때 그 결과가 어떠한지를 시험해 볼 수 있다. 괄호가 없으면 콤마에 의해서 분리되어 있는 첫 번째 표현은 다음과 같이 될 것이다.

num4 = num1 = 10;

그러면 num4는 10이라는 값을 가질 것이다.

 

물론, 콤마 연산자에 의해서 분리되어 있는 표현들이 반드시 할당문일 필요는 없다. 우리는 다음과 같이 작성할 수도 있다.

long num1 = 0, num2 = 10, num = 100, num4 = 0;
num4 = (++num1, ++num2, ++num3);

이 할당문의 결과는 num1, num2, 그리고 num3 변수를 1만큼 증가시키는 것이 될 것이다. 그리고 num4를 마지막 표현의 값인 101로 설정할 것이다. 이 예는 콤마 연산자의 효과를 나타내려고 사용한 것으로써 좋은 코드를 작성하는 방법을 설명하는 것이 아니다.


 

계산의 순서

 

지금까지 우리는 표현을 계산하는 데 있어서 계산의 순서가 어떻게 진행되는지에 대해서는 얘기하지 않았다. 일반적으로, 그 순서는 여러분이 학교에서 기본적인 산술 연산자를 다루는 방법을 배웠던 것과 동일하다. 그러나 C++ 에는 그 외의 다른 많은 연산자가 있다. 그러한 연산자의 경우에는 어떻게 되는가에 대해서 알아보려면 C++에서 순서를 결정하는 데 사용되는 메커니즘을 살펴보아햐 한다. 그러한 메커니즘을 연산자의 우선권이라고 한다.

 

연산자의 우선권

연산자의 우선권은 우선순위에 따라서 연산자의 순서를 정한다. 모든 표현에서 가장 높은 우선순위를 가진 연산자가 항상 먼저 실행된다. 그런 후 그다음으로 높은 우선순위를 가지고 있는 연산자가 실행된다. 등등.  그렇게 해서 가장 낮은 순위의 연산자에게까지 이른다. C++에서의 연산자의 우선순위가 다음의 표에 나타나 있다.

C++ 연산자 우선순위

여러분이 이제까지 보지 못했던 연산자도 많을 것이다. 그러나 나중에 그 모든 연산자를 다 알게 될 것이다. 그 모든 연산자들이 여기저기 산재하기보다는 이 우선순위 표에 다 모여 있기 때문에 여러분이 어느 연산자가 다른 연산자에 대해서 어떤 우선권을 가지는지에 대해서 잘 모를 때는 항상 이 표를 참조할 수 있다.

 

가장 높은 우선순위를 가지는 연산자가 표의 맨 위에 나타나 있다. 다른 연산자들까지 혼합된 연산자는 동일한 우선순위를 가지는 것으로 하였다. 만약 표현 안에 괄호가 없다면 동일한 우선순위의 연산자들은 그들의 연관성에 의해서 결정되는 순서로 실행된다. 그러므로 만약 연관성이 '왼쪽'이라면 표현 안에서 가장 왼쪽에 있는 연산자가 먼저 실행되고, 차츰 오른쪽으로 진행된다. 이것은 a + b + c + d와 같은 표현이 (((a + b) + c) + d)와 같이 쓰여진 것처럼 실행된다는 것을 의미한다. 왜냐하면, 이진 +(binary +)는 왼쪽으로 연관되기 때문이다.

 

하나의 연산자가 1진(하나의 피연산자에 대해서 작동한다)과 이진(두 개의 연산자에 대해서 작동한다) 형태를 가지는 곳에서는 1진 형태가 항상 더 높은 우선순위를 가지므로 먼저 실행된다.

 

여러분은 항상 괄호를 사용하여 연산자의 우선순위를 바꿀 수 있다. C++ 에는 많은 종류의 연산자가 있기 때문에 어떤 연산자가 다른 연산자에 대해서 우선권을 가지는지의 여부에 대해서 잘 파악하지 못할 때가 많다. 이때 확실하게 하려면 괄호를 사용하는 것이 좋다. 또한, 괄호를 사용하여 코드가 훨씬 읽기 편하게 된다.

 

그리드형