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

루프와 관계 표현식 1 : for 루프 1 - for문에서 증감/감소 연산자까지 본문

프로그래밍 언어/C++

루프와 관계 표현식 1 : for 루프 1 - for문에서 증감/감소 연산자까지

huenuri 2024. 8. 25. 13:22

이제 드디어 새로운 단원 학습에 들어간다. 이 책에는 for문부터 나와있어 이 부분부터 학습해야 할 것 같다. 다른 책에 보면 if문과 switch문에 가장 앞에 등장한다.
이번 포스트에서는 for문에 대해서만 알아보기로 하자. 이것 하나만 해도 분량이 정말 많다.

컴퓨터로 데이터를 처리하려면 반복적인 동작을 수행하거나, 조건을 판단하여 의사를 결정할 수 있는 도구가 필요하다. C++은 for, while, do while, if 구문, switch 구문과 같은 도구들을 제공한다.

프로그램을 제어하는 이러한 구문에서는 관계 표현식과 논리 표현식을 사용한다.


 

 

1. for 루프 1

프로그래밍을 하다 보면 배열의 원소들을 하나씩 모두 더하거나 문자열을 여러 번 출력하는 등의 반복적인 작업을 수행해야 할 때가 많다. for 루프를 사용하면 이러한 반복 작업을 쉽게 처리할 수 있다.

 

예제 1번 : forloop.cpp

// for 루프

#include <iostream>
int main()
{
	using namespace std;
	int i;				// 카운터를 선언한다
	// 초기화, 조건 검사, 갱신
	for (i = 0; i < 5; i++)
		cout << "C++가 루프를 사용합니다.\n";
	cout << "루프를 언제 끝내야 하는지 C++는 알고 있습니다.\n";
	return 0;
}

 

이 루프는 정수 i를 0으로 설정하는 거부터 시작한다. 그리고 나서 프로그램은 루프 조건 검사 부분에서 i가 5보다 작은지 검사한다.
이 조건을 만족하면 루프 몸체라고 부르는 다음과 같은 구문을 수행한다.

cout << "C++가 루프를 사용합니다.\n";

 

루프의 갱신 부분은 증가 연산자라고 부르는 ++ 연산자를 사용한다. 이 연산자는 피연산자의 값을 1만큼 증가시킨다.


 

 

for 루프의 각 부분

 

for 루프는 단계적인 처리를 통해 반복 작업을 수행한다. for 루프를 구성하는 각 부분은 순서대로 다음과 같은 단계를 처리한다.

  1. 조건 검사에 사용할 카운터 값을 초기화한다.
  2. 루프를 진행할 것인지 조건을 검사한다.
  3. 루프 몸체를 수행한다.
  4. 카운터 값을 갱신한다.

 

C++는 몸체 안에 여러 개의 구문이 사용되었을 경우에도 for 루프 전체를 하나의 구문으로 간주한다.
초기화는 처음에 단 한 번만 수행한다.

 

 

예제 2번 : num_test.cpp

// for 루프의 조건 검사에 수치를 직접 사용한다

#include <iostream>
int main()
{
	using namespace std;
	cout << "카운트 시작값을 입력하십시오: ";
	int limit;
	cin >> limit;
	int i;
	for (i = limit; i; i--)
		cout << "i = " << i << "\n";
	cout << "i = " << i << "이므로 루프를 종료합니다.\n";
	return 0;
}

 

i가 0에 도달했을 때 루프가 종료된 사실에 주목하라.
for 루프는 진입 조건 루프이다. 따라서 각 루프 주기에 진입할 때 조건 검사 표현식이 평가된다. 조건 검사 표현식이 거짓으로 평가되면 루프 몸체는 결코 수행되지 않는다.

 

 

표현식과 구문

 

C++는 표현력이 매우 풍부한 언어이다. 어떠한 값이나 값과 연산자들도 적절한 조합의 표현식이 될 수 있다.

예제 3번 : express.cpp

// 표현식의 값

#include <iostream>
int main()
{
	using namespace std;
	int x;

	cout << "대입 표현식 x = 100의 값은 ";
	cout << (x = 100) << endl;
	cout << "현재 x의 값은 " << x << endl;
	cout << "관계 표현식 x < 3의 정수값은 ";
	cout << (x > 3) << endl;
	cout.setf(ios_base::boolalpha);
	cout << "관계 표현식 x < 3의 bool값은 ";
	cout << (x < 3) << endl;
	cout << "관계 표현식 x > 3의 bool값은 ";
	cout << (x > 3) << endl;
	return 0;
}

cout.setf(ios_base::boolalpha);

 

함수 호출이 있으면 cout이 1이나 0 대신 true나 false를 출력하도록 플래그 설정을 한다.

 

 

 

표현식이 아닌 것과 구문

어떠한 표현식에도 세미콜론만 붙이면 구문이 된다. 그러나 그 역은 성립하지 않는다. 즉, 어떤 구문에서 세미콜론을 뺀다고 해서 모두 표현식이 되는 것은 아니다.

int toad;

 

이것은 표현식이 아니며 값도 가질 수 없다. 따라서 다음과 같은 코드는 잘못된 것이다.

eggs = int toad * 1000;
cin >> int toad;

 

 

for 루프에 대한 부충

 

이 프로그램은 첫 번째 for 루프를 사용하여 연속적인 계승의 값들을 계산하여 배열에 저장한다. 그리고 나서 두 번째 루프를 사용하여 그 결과를 출력한다.

예제 4번 : formore.cpp

// for 루프에 대한 보충

#include <iostream>
const int ArSize = 16;
int main()
{
	long long factorials[ArSize];
	factorials[1] = factorials[0] = 1LL;
	for (int i = 2; i < ArSize; i++)
		factorials[i] = i * factorials[i - 1];
	for (int i = 0; i < ArSize; i++)
		std::cout << i << "! = " << factorials[i] << std::endl;
	return 0;
}

 

이 프로그램은 for 루프와 배열을 결합하여 배열의 원소에 차례대로 접근할 수 있음을 보여준다. 또한 const라는 키워드를 사용하여 배열의 크기를 나타내는 기호 상수 ArSize를 생성한다.
배열의 인덱스는 ArSize보다 1만큼 작은 곳에서 끌나야 한다.


 

 

갱신 크기 변경

 

갱신 표현식을 변경하면 루프 카운터가 갱신되는 크기를 바꿀 수 있다.

// cout as directed

#include <iostream>
int main()
{
	using std::cout;		// using 선언
	using std::cin;		
	using std::endl;
	cout << "정수를 하나 입력하십시오: ";
	int by;
	cin >> by;
	cout << "갱신 크기 " << by << "s:\n";
	for (int i = 0; i < 100; i = i + by)
		cout << i << endl;
	return 0;
}

 

i 값이 102가 되면 루프를 벗어난다. 여기서 중요한 것은 어떠한 표현식도 갱신 표현식으로 사용할 수 있다는 사실이다.


 

 

for 루프를 사용한 문자열 처리

 

for 루프를 사용하여 문자열을 구성하는 문자들에 차례대로 접근할 수 있다.
아래 예제는 string 클래스 객체를 사용한다. string 클래스의 size() 메서드가 문자열을 구성하는 문자 수를 알아낸다. 그러고 나서 루프의 초기화 표현식에서 i를 그 문자열의 마지막 문자를 나타내는 인덱스로 초기화가기 위해 이 값을 사용한다.

예제 6번 : forstr1.cpp

// for 루프를 사용한 문자열 처리

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

	// 문자열을 거꾸로 출력한다
	for (int i = word.size() - 1; i >= 0; i--)
		cout << word[i];
	cout << "\n종료.\n";
	return 0;
}


 

증가 연산자(++)와 감소 연산자(--)

증가 연산자는 루프 카운터를 1씩 증가시키고, 감소 연산자는 루프 카운터를 1씩 감소시킨다. 두 연산자 모두 접두어 방식과 접미어 방식으로 사용할 수 있다.

// 증가 연산자

#include <iostream>
int main()
{
	using std::cout;
	int a = 20;
	int b = 20;

	cout << "a   = " << a << ":   b = " << b << "\n";
	cout << "a++ = " << a++ << ": ++b = " << ++b << "\n";
	cout << "a   = " << a << ":   b = " << b << "\n";
	return 0;
}

 

a++는 a가 현재 가지고 있는 값으로 펴현식의 값을 먼저 평가한 후, a의 값을 나중에 증가시키는 것이다. ++b는 b의 값을 먼저 증가시킨 후, 증가된 b의 값으로 표현식의 값을 나중에 평가하는 것이다. 그러므로 다음과 같은 것들이 성립한다.

int x = 5;
int y = ++x;		// x를 증가시킨 후 그 값을 y에 대입한다
					// 따라서 현재 z는 6이고, y도 6이다
int z = 5;
int y = z++;		// y에 5를 대입한 다음에 z를 증가시킨다
					// 따라서 현재 y는 5이고, z는 6이다

증가 연산자와 감소 연산자는 매력 있는 연산자이다. 그러나 이들을 남용하거나 하나의 구문에서 같은 값을 여러 번 증가시키거나 감소시키지 않도록 주의해야 한다.
값을 변경한 후 사용하는지 아니면 먼저 사용한 후에 변경하는지 애매모호한 상황이 발생할 수 있기 때문이다.


 

부수 효과와 시퀀스 포인트

 

부수 효과는 수식을 평가할 때 변수에 저장되어 있는 값과 같은 것이 변경될 때 일어나는 효과를 말한다. 시퀀스 포인트는 프로그럼의 실행이 다음 단계로 넘어가기 전에 모든 부수 효과들이 확실하게 평가되는 포인트이다. C++에서 구문에 있는 세미콜론은 시퀀스 포인트를 표시한다.
그것은 구문에서 대입 · 증가 · 감소 연산자에 의해 일어나는 모든 변화가 프로그램이 다음 구문으로 넘어가기 전에 반드시 일어나야 한다는 것을 의미한다.

솔직히 이 부분은 읽어도 무슨 말인지 잘 모르겠다. 아마도 다음에 학습하는 것이 나을 것 같아 넘어간다. 지금은 이런 것까지 자세히 알 필요는 없을 것 같다.
포인터 부분도 있는데 여기도 4장의 후반부 학습을 마친 후에 살펴볼 예정이다.

 

조합 대입 연산자

+= 연산자는 두 피연산자의 값을 더해서 그 결과를 왼쪽 피연산자에 대입한다. 여기서 왼쪽 피연산자는 변수, 배열의 원소, 구조체의 멤버, 포인터에 의해 참조되는 데이터와 같이 실제로 값을 대입할 수 있는 것이어야 한다.

int k = 5;
k += 3;					// k를 8로 설정한다
int* pa = new int[10];	// pa는 pa[0]을 지시한다
pa[4] = 12;
pa[4] += 6;				// pa[4]를 18로 설정한다
*(pa + 4) += 7;			// pa[4]를 25로 설정한다
pa += 2;				// pa는 pa[2]를 지시한다


 

 

학습을 마치고

 

분량이 너무 많아서 복합 구문부터는 다음 포스트에 이어서 정리하기로 했다. 조금만 더 학습하면 되는줄 알았는데 아직도 for 루프를 끝내려면 멀었다. 예제 문제를 직접 타이핑하고 예시로 든 문제들까지 다 코드로 쳐보는 중이다.
이제 내용을 정리하는 것도 점차 익숙해져서 나름의 노하우도 생기고 있다. 좀더 속도를 내면 좋을텐데 지금은 확실히 이해하고 넘어가는 게 더 좋을 듯하다.