클라이언트/ 서버/ 엔지니어 " 게임 개발자"를 향한 매일의 공부일지

루프와 관계 표현식 2 : for 루프 2, 복합 구문과 콤마 연산자 본문

프로그래밍 언어/C++

루프와 관계 표현식 2 : for 루프 2, 복합 구문과 콤마 연산자

huenuri 2024. 8. 25. 13:24

이어서 for 루프 학습을 진행해본다. 복합 구문(중첩 for문과 다름)과 콤마 연산자, 관계 표현식 등이 남아있다.
그런 다음 while문과 do while문까지 학습해볼 예정이다. 난 for문 보다 while문이 훨씬 더 어렵게 느껴진다. 이 책은 무척 자세하게 서술되어 있고 예제가 많아서 좋은 것 같다.

바로 시작해보자!


 

 

1. for 루프 2

 

복합 구문 또는 블록

루프 몸체에 여러 개의 구문을 넣고 싶을 때는 어떻게 할 것인가? C++는 루프 몸체 안에 원하는 만큼의 구문을 넣을 수 있는 방법을 제공한다. 그것은 한 쌍의 중괄호를 사용하여 복합 구문 또는 블록을 만드는 것이다.
블록은 중괄호와 그 안에 포함된 구문들로 구성되며, 구문 규칙상 하나의 구문으로 간주된다.

// 복합 구문(블록) 사용

#include <iostream>
int main()
{
	using namespace std;
	cout << "값 5개의 합계와 평균을 구합니다.\n";
	cout << "값 5개를 입력하십시오.\n";
	double number;
	double sum = 0.0;

	for (int i = 1; i <= 5; i++)
	{								// 블록 시작
		cout << "값 " << i << ": ";
		cin >> number;
		sum += number;
	}								// 블록 끝
	cout << "값 5개가 모두 입력되었습니다.\n";
	cout << "입력한 값 5개의 합계는 " << sum << "입니다.\n";
	cout << "입력한 값 5개의 평균은 " << sum / 5 << "입니다.\n";
	cout << "감사합니다.\n";
	return 0;
}

 

앞의 프로그램에서 들여쓰기는 그대로 유지하고 중괄호를 생략해보자.

	for (int i = 1; i <= 5; i++)
									// 블록 시작
		cout << "값 " << i << ": ";
		cin >> number;
		sum += number;
									// 블록 끝
	cout << "값 5개가 모두 입력되었습니다.\n";

 

이렇게 하니 값을 입력하는 부분이 4개가 생략이 되고 5번째 값만 입력하는 문구가 뜬다. 컴파일러가 들여쓰기를 무시하므로 첫 번째 구문만 루프 몸체가 되기 때문이다.
복합 구문의 재미있는 특성이 또 하나 있다. 블록 안에서 새로운 변수를 정의하면, 그 변수는 그 블록 안에 속해 있는 구문들을 실행하는 동안에만 존재하고, 제어가 블록을 빠져나오면 그 변수는 사라진다.

#include <iostream>
int main()
{
	using namespace std;
	int x = 20;
	{						// 블록 시작
		int y = 100;
		cout << x << endl;
		cout << y << endl;
	}						// 블록 끝
	cout << x << endl;
	cout << y << endl;		// 컴파일 불가능
	return 0;
}

 

 

 

콤마 연산자

하나의 표현식을 허용하는 자리에 콤마 연산자를 사용하면 두 개의 표현식을 넣을 수 있다. 예를 들어, 매 루프 주기마다 하나의 변수는 1씩 증가하고, 다른 하나의 변수는 1씩 감소해야 하는 루프가 필요하다고 가정하다. for 루프의 갱신 부분에서 이 두 가지 일을 함께 처리힐 수 있으면 무척 편리할 것이다.
그러나 for 루프의 구문 규칙은 그 자리에 하나의 표현식만을 허용한다. 이 문제를 해결하는 방법은 콤마 연산자를 사용하여 두 개의 표현식을 하나로 결합하는 것이다.

++j, --i		// 두 개의 표현식이 하나의 표현식으로 간주된다

 

예제 9번 : forstr2.cpp

// 문자열 뒤집기

#include <iostream>
#include <string>
int main()
{
	using namespace std;
	cout << "단어를 하나 입력하십시오: ";
	string word;
	cin >> word;

	// string 객체를 실제로 변경한다
	char temp;
	int i, j;
	for (j = 0, i = word.size() - 1; j < i; --i, ++j)
	{
		temp = word[i];
		word[i] = word[j];
		word[j] = temp;
	}
	cout << word << "\n종료.\n";
	return 0;
}

 

string 클래스는 보다 간단하게 문자열을 뒤짚는 방법을 제공한다.


 

 

프로그램 분석

 

콤마 연산자는 두 개의 초기화 표현식을 하나의 표현식으로 결합한다. 루프 몸체에서 프로그램은 배열의 첫번째 원소와 마지막 원소를 서로 맞바꾼다.

 

이 예제에서 주목할 점은 변수 temp, i, j가 선언되어 있는 위치다. 변수 i와 j를 루프 앞에 선언한 이유는 루프의 초기화 부분에서 두 선언을 콤마로 결합할 수 없기 때문이다.
이들을 결합할 수 없는 이유는 변수 이름 i와 j의 분려 목적으로 콤마를 이미 사용하고 있기 때문이다. 여기서 사용된 콤마는 연산자가 아니라 분리자이다.


 

 

 

관계 표현식

 

컴퓨터는 단순히 수치 계산만 하는 기계가 아니라 값을 서로 비교할 수 있는 능력을 가지고 있다. 이 능력이 컴퓨터를 이용하여 의사 결정을 하는데 기초가 된다. C++는 수들을 비교할 수 있는 6개의 관계 연산자를 제공한다. 문자들은 아스키 코드로 표현되므로, 문자에도 관계 연산자를 적용할 수 있다.
C 스타일의 문자열에는 관계 연산자를 사용할 수 없다. 그러나 string 클래스에는 사용할 수 있다. 각각의 관계 표현식은 비교 결과가 참이면 bool형 값으로 true, 거짓이면 false가 된다.

 

6개의 관계 연산자는 오직 수를 비교하는 데에만 사용할 수 있다.


 

 

흔히 범하는 실수

 

'같다' 연산자(==)와 대입 연산자(=)를 혼동하지 않도록 주의해야 한다.

예제 10번 : equal.cpp

// 같다 연산자와 대입 연산자

#include <iostream>
int main()
{
	using namespace std;
	int quizscores[10] = { 20,20,20,20,20,19,20,18,20,20 };

	cout << "올바른 방법:\n";
	int i;
	for (i = 0; quizscores[i] == 20; i++)
		cout << i << "번 퀴즈의 점수는 20입니다.\n";
	cout << "잘못된 방법:\n";
	for (i = 0; quizscores[i] = 20; i++)
		cout << i << "번 퀴즈의 점수는 20입니다.\n";

	return 0;
}

 

첫 번째 루프는 처음 다섯 개의 퀴즈 점수를 출력한 후에 제대로 멈춘다. 그러나 두 번째 루프는 전체 배열을 출력하는데 그 값을 모두 20으로 출력한다. 이와 같은 사태가 일어난 것은 조건 검사 표현식이 잘못되었기 때문이다.

quizscores[i] = 20;
  1. 배열 원소에 0이 아닌 값(20)을 대입하기 때문에 이 표현식의 값은 항상 0이 아니고, 항상 true로 평가된다.
  2. 이 표현식은 배열 원소에 값을 대입하기 때문에 메모리에 있는 데이터를 실제로 변경한다.
  3. 조건 검사 표현식이 항상 ture로 유지되기 때문에, 배열의 끝을 벗어나 계속해서 데이터를 변경한다.

 

즉, 20이라는 값을 계속해서 메모리에 저장하는데 이는 매우 위험하다.


 

 

C 스타일 문자열 비교

 

문자 배열에 들어있는 문자열이 mate라는 단어인지 알고 싶다고 가정하자. 그 문자 배열의 이름이 word일 때, 다음과 같은 검사는 원하는 바를 수행하지 않는다.

word == "mate"

 

큰따옴표로 묶인 문자열 상수도 역시 주소이다. 그러므로 앞의 관계 표현식은 그 문자열이 같은지 검사하는 것이 아니라, 그들이 동일한 주소에 저장되어 있는지 검사하는 것이다. 따라서 두 문자열이 같은 문자들로 되어 있다고 할지라도 결과는 false이다.
C++는 C 스타일 문자열을 주소로 처리하기 때문에, 문자열 비교에 관계 연산자를 사용할 수 없다. 대신 C 스타일의 문자열 라이트러리 함수인 strcmp()를 사용할 수 있다. 이 함수는 매개변수로 두 개의 문자열 주소를 취한다.

C 스타일의 문자열은 문자열이 들어있는 배열의 크기가 아니라 널 문자에 의해서 정의된다. 따라서 크기가 다른 배열에 저장되어 있더라도 두 문자열이 동일할 수 있다.

char big[80] = "Daffy";		// 5 문자와 \0
char little[6] = "Daffy";	// 5 문자와 \0

 

다음 예제는 for 루프의 조건 검사 표현식에 strcmp() 함수를 사용하고 있다. 한 단어를 출력하고, 그 단어의 첫 문자를 바꾸어 다시 출력한다. 그 단어가 "mate"와 같다고 strcmp() 함수가 결정할 때까지 이 작업을 계속한다.

 

 

예제 11번 : compstr1.cpp

// 배열을 사용하여 문자열 비교

#include <iostream>
#include <cstring>
int main()
{
	using namespace std;
	char word[5] = "?ate";

	for (char ch = 'a'; strcmp(word, "mate"); ch++)
	{
		cout << word << endl;
		word[0] = ch;
	}
	cout << "루프가 끝난 후에 단어는 " << word << "입니다.\n";
	return 0;
}


 

프로그램 분석

 

우리는 word가 mate가 될 때까지 진행하는 루프를 원한다. 즉, strcmp()가 두 문자열이 같지 않다고 말하는 동안 루프를 진행시키는 조건 검사 표현식이 필요하다. 가장 명백한 조건 검사 표현식은 다음과 같다.

strcmp(word, "mate") != 0;		// 두 문자열이 같지 않다

이 표현식은 두 문자열이 같지 않으면 1, 같으면 0이 된다. 이렇게 하면 같은 효과를 내면서도 코드를 입력하는 수도를 덜 수 있다.

문자열을 비교하는 함수는 아직 완전히 이해가 되지 않는데, 다음에 더 학습해보려고 한다.


 

 

string 클래스 문자열 비교

 

클래스 설계가 문자열 비교에 관계 연산자를 사용하는 것을 허용하기 때문에, C 스타일 문자열 대신 string 클래스 문자열을 사용하는 것이 훨씬 편리하다. 이것이 가능한 것은 연산자들을 '오버로딩' 또는 재정의 하는 클래스 함수를 정의할 수 있기 때문이다.

예제 12번 : compstr2.cpp

// string 클래스를 사용하여 문자열 비교

#include <iostream>
#include <string>
int main()
{
	using namespace std;
	string word = "?ate";
	for (char ch = 'a'; word != "mate"; ch++)
	{
		cout << word << endl;
		word[0] = ch;
	}
	cout << "루프가 끝난 후에 단어는 " << word << "입니다.\n";
	return 0;
}

 

이 프로그램의 실행 결과는 앞의 예제와 같다.

word != "mate";

 

string 클래스가 != 연산자를 오버로딩하는 방법은, 피연산자 중에 적어도 하나가 string 객체이면 사용하는 것을 허용한다.


 

 

학습을 마치고

string 클래스는 내가 아직 어렵게 느껴져서 그런지 좀 많이 생소하다. 콤마 연산자에 대해서도 새로운 내용을 배울 수 있었다. 전에는 그냥 콤마란 두 점 사이를 분리하는 연산자라고 생각했는데, 이 연산자를 사용해서 성질이 다른 두 가지 표현을 만들어내기도 한다.
이제 단순 for문은 여기서 학습을 마치고 뒷장에 중첩 for문이 등장한다. 처음 배우는 단원이라 내가 예상했던 시간을 2배 이상 초월하고 있다.

다음 포스트에 while문에 대해 공부하며 정리해볼 것이다.