C++ Primer 05
학습목표
for
루프- 증가 연산자와 감소 연산자(
++
와--
) - 복합 구문(블록)
- 관계 연산자(
>
,>=
,==
,<=
,<
,!=
) typedef
기능- 문자 입력 메서드
get()
- 중첩 루프와 2차원 배열
- 표현식과 구문
- 조합 대입 연산자
- 콤마 연산자
while
루프do while
루프- 파일 끝 조건
5.1 for 루프
// forloop.cpp
#include <iostream>
int main()
{
using namespace std;
int i;
for (i = 0; i < 5; i++)
cout << "C가 루프를 사용합니다.\n";
cout << "루프를 언제 끝내야하는지 C++은 알고있습니다.\n";
return 0;
}
for
루프 : 반복적인 작업 처리
루프 초기화(loop initialization) / 루프 몸체(loop body) / 루프 갱신(loop update) 으로 이루어져있음
C++에서는 일반적으로 for
과 같은 제어 구문과 괄호 사이에는 공백을 넣고, 함수 이름과 괄호 사이에는 넣지 않음
++
: 증가 연산자(increment operator), 피연산자의 값을 1만큼 증가시킴
for 루프의 각 부분
for (initialization; test=expression; update-expression)
body
- 조건 검사에 사용할 카운터 값을 초기화
- 루프를 진행할 것인지 조건을 검사
- 루프 몸체를 수행
- 카운터 값을 갱신
몸체 안에 여러 구문이 사용되었을 때에도 for
루프 전체를 하나의 구문으로 간주
- 초기화는 처음에 한번만 수행, 일반적으로 변수에 시작값을 설정함
- 조건식은 일반적으로 두 값을 비교하는 관계식을 사용하며, 참 거짓 관계식 등 어떠한 표현식도 사용할 수 있음
- 조건식이 참이면 루프 진행, 거짓이면 루프 종료
// num_test.cpp
#include <iostream>
int main()
{
using namespace std;
cout << "카운트 시작값을 입력하십시오 : ";
int limit;
cin >> limit;
int i;
for (i = limit; i; i--)
cout << "i = " << i << endl;
cout << "i = " << i << "이므로 루프를 종료합니다.\n";
return 0;
}
C++에는 bool
형이 있기 때문에 조건식이 참이면 true
, 거짓이면 false
로 평가되며 이는 정수값 요구시 각각 1과 0으로 변환됨
반대로 bool
형 값이 요구될 때는 0이 false
로, 0이 아닌 수들은 true
로 변환됨
for
루프는 진입 조건(entry-condition) 루프이기 때문에 조건을 먼저 검사하고, 통과시에만 루프 몸체를 수행함
update-expression은 루프 몸체가 수행되고 난 후인 각 루프 주기의 끝에서 평가되며, 어떠한 표현식도 사용할 수 있음
// 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 << "관계 표현식 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;
}
x = 100
이라는 표현식을 평가하기 위해 x에는 100이 대입되어 메모리에 있는 데이터 값을 부수적으로 변경하는 부수 효과(side effect)가 발생함
모든 표현식에 세미콜론을 붙여 구문으로 만들 수 있음
단, 아무런 의미가 없을 수 있으며 역은 성립하지 않음
C++에서는 for (int i = 0; i < 5; i++)
과 같이 for
루프의 초기화 부분에서 변수를 선언하고 초기화할 수 있음
이 때의 변수는 for
구문 안에서만 존재하게되며, for
루프를 벗어날시 소멸됨
단, 시스템에 따라 구식 규칙을 따를 경우 해당 변수를 루프 앞에서 선언된 것으로 인식하여 삭제하지 않을 가능성이 있음
for 루프에 대한 보충
// formore.cpp
#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;
}
배열의 원소 개수를 const
값으로 정의하는 것이 좋은 습관임
외부 상수로 선언되면 프로그램이 실행되는 동안에 계속 존재하기 때문에 프로그램의 모든 함수가 해당 상수를 사용할 수 있음
갱신 크기 변경
// bigstep.cpp
#include <iostream>
int main()
{
using std::cout;
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;
}
갱신 표현식을 변경하면 루프 카운터가 갱신되는 크기를 바꿀 수 있음
갱신 표현식에는 어떠한 표현식도 사용 가능함
for 루프를 사용한 문자열 처리
// forstrl.cpp
#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;
}
string
클래스의 size()
메서드로 문자열을 구성하는 문자 수를 구하고, 이를 초기화 표현식에 사용함
인덱스에 해당하는 i가 NULL
문자 전을 가리키도록 하고, 인덱스를 하나식 줄여가며 출력
증가 연산자(++)와 감소 연산자(--)
// plus_one.cpp
#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;
}
접두어(prefix) 방식 : 피연산자 앞에 사용, 적용되는 시점이 표현식의 값을 평가하기 전임
접미어(postfix) 방식 : 피연산자 뒤에 사용, 적용되는 시점이 표현식의 값을 평가한 후임
내장 데이터형에 대해서는 두 방식의 효율 차이가 거의 없음
사용자 정의 데이터형에 대해서는 복사본을 만드는 접미어 방식보다 접두어 방식이 좀 더 효율적
부수 효과와 시퀀스 포인트
시퀀스 포인트 : 프로그램의 실행이 다음 단계로 넘어가기 전에 모든 부수 효과들이 확실하게 평가되는 포인트
- C++에서 구문에 있는 세미콜론이 시퀀스 포인트를 표시
- 따라서 연산자에 의해 일어나는 모든 변화는 다음 구문으로 넘어가기 전에 반드시 일어나야 함
- 몇몇 연산자들도 시퀀스 포인트를 가지며, 완전 수식(더 커다란 수식의 부분이 아닌 수식)의 끝도 시퀀스 포인트임
while (guests++ < 10)
에서guests++ < 10
은 완전수식으로 조건 확인 후 guests가 증가됨y = (4 + x++) + (6 + x++);
에서4 + x++
과6 + x++
은 완전 수식이 아니므로 해당 구문이 시퀀스 포인트를 만나기 전에는 x의 증가가 보장되지 않음
증가/감소 연산자와 포인터
증감 연산자는 포인터에도 사용 가능
포인터에 사용할 시 포인터가 지시하는 데이터형의 바이트 수만큼 값을 증가시킴
접두형 증감과 내용 참조 연산자는 우선순위가 같으며 오른쪽에서 왼쪽으로 결합
*++pt
의 경우++pt
가 먼저 적용된 후 pt의 새로운 값에*
가 적용됨++*pt
의 경우*pt
로 값을 먼저 참조한 후 해당 값이++
에 의해 증가됨
접미형 증감은 우선순위가 같으며 접미어보다 우선순위가 높음
*pt++
의 경우pt++
이 먼저 적용된 후 pt의 새로운 값에*
가 적용됨
조합 대입 연산자
조합 대입 연산자 : 산술 연산과 대입 기능이 하나로 결합된 연산자
두 피연산자를 산술 연산한 후, 그 결과를 왼쪽 피연산자에 대입함
이때 왼쪽 피연산자는 실제로 값을 대입할 수 있는 것이여야 함
+=
/ -=
/ *=
/ /=
/ %=
복합 구문 또는 블록
// block.cpp
#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;
}
한 쌍의 중괄호를 사용한 복합 구문(compound statement) 또는 블록(block)으로 루프 몸체 안에서 여러 개의 구문을 사용할 수 있음
블록은 중괄호와 그 안에 포함된 구문들로 구성되며, 모두 합해 하나의 구문으로 간주됨
블록 안에서 정의된 변수는 해당 블록의 구문들을 실행하는 동안에만 존재하여 그 블록 안에서만 사용할 수 있음
블록 안에서 블록 밖에있는 변수와 같은 이름의 변수를 선언했을 경우 해당 블록의 끝에 도달할때 까지만 변수가 덮어씌워짐
콤마 연산자
// forstr2.cpp
#include <iostream>
#include <string>
int main()
{
using namespace std;
cout << "단어를 하나 입력하십시오 : ";
string word;
cin >> word;
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;
}
콤마 연산자를 사용하여 여러 개의 표현식을 하나의 표현식으로 간주할 수 있음
- 콤마 연산자를 사용할 경우 첫 번째 표현식이 먼저 평가된 후 그 다음 표현식을 평가함
- 두 번째 표현식이 콤마 표현식 전체의 값이 됨
- 연산자 중에서 우선순위가 가장 낮음
관계 표현식
C++는 수를 비교할 수 있는 6개의 관계 연산자(relational operator)를 제공
<
/ <=
/ ==
/ >
/ >=
/!=
- 문자도
ASCII
코드로 구현되므로 관계 연산자를 적용할 수 있음
단, C 스타일의 문자열에는 사용할 수 없으며string
클래스 객체에는 사용할 수 있음 - 관계 표현식은 비교 결과에 따라 참이면
ture
, 거짓이면false
가 됨 - 관계 연산자는 산술 연산자보다 우선순위가 낮음
// 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;
}
위 코드의 실행 결과 segmentation fault
가 발생
==
와 =
는 다름에 유의해야함
C 스타일 문자열 비교
// 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;
}
strcmp()
: 매개변수로 두개 문자열의 주소를 취함
- 두 개 문자열이 같으면 0을 리턴
- 시스템 조회 순서(system collating sequenc)로 보았을 때 첫번째 문자열이 두번째 문자열보다 앞에 오면 음수를 리턴하고, 그 반대이면 양수를 리턴
NULL
문자까지만 비교하기 때문에 서로 크기가 다른 배열이라도 상관 없음
string 클래스 문자열 비교
// compstr2.cpp
#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;
}
클래스 설계는 문자열 비교에 관계 연산자를 사용하는 것을 허용
이는 연산자들을 오버로딩 또는 재정의하는 클래스 함수를 정의할 수 있기 때문
5.2 while 루프
while (test-expression)
body
for
루프에서 초기화 부분과 갱신 부분을 없애고 루프 몸체와 조건 검사 부분만 남겨놓으면 while
루프가 됨
루프가 끝나려면 조건 검사 표현식에 영향을 주는 것이 루프 몸체 안에 들어있어야 함
// while.cpp
#include <iostream>
const int ArSize = 20;
int main()
{
using namespace std;
char name[ArSize];
cout << "영문 이름을 입력하십시오 : ";
cin >> name;
cout << "귀하의 영문 이름을 한 줋에 한 자씩\n";
cout << "ASCII 코드와 함께 표시하면 이렇습니다.\n";
int i = 0;
while (name[i] != '\0')
{
cout << name[i] << " : " << int(name[i]) << endl;
i++;
}
return 0;
}
i++;
이 없을 시 계속 같은 배열 원소에 접근하며 루프하기때문에 무한 루프가 발생함
C 스타일 문자열과는 달리 string
클래스 객체는 문자열의 끝을 인식하기 위한 널 문자를 사용하지 않음
for과 while
C++에서의 for
루프와 while
루프는 사실상 같음
- 루프 실행을 종료시키는 조건 파악
- 첫번째 조건 검사를 하기 전 그 조건을 초기화
- 조건 검사를 다시하기 전 매 루프 주기마다 해당 조건을 갱신
따라서 어느 것을 선택하는지는 사용자의 프로그래밍 스타일에 달림
- 일반적으로
for
루프는 초기값, 종료값, 카운터 갱신을 한번에 할 수 있기 때문에 루프를 카운트해야할 때 주로 사용 while
루프는 루프의 반복 횟수를 미리 알 수 없을 때 주로 사용
시간 지연 루프
초창기에는 컴퓨터로 하여금 수를 카운트하게 하여 시간을 소모시켰음
그러나 이는 컴퓨터 프로세서의 성능에 따라 카운트해야하는 수의 한계가 달라짐
따라서 ANSI C와 C++ 라이브러리는 clock()
이라는 함수를 제공함
// waiting.cpp
#include <iostream>
int main()
{
using namespace std;
cout << "지연 시간을 초 단위로 입력하십시오 : ";
float secs;
cin >> secs;
clock_t delay = secs * CLOCKS_PER_SEC;
cout << "카운트를 시작합니다. \a\n";
clock_t start = clock();
while (clock() - start < delay)
;
cout << "종료\a\n";
return 0;
}
clock()
함수가 제공하는 시간은 초 단위 시간이 아님
시스템마다 clock()
함수가 리턴하는 데이터형이 다를 수 있음
이를 보완하기 위해 ctime
헤더 파일에서 초당 시스템 시간 단위 수를 뜻하는 CLOCKS_PER_SEC
이라는 기호 상수를 정의하고, 데이터형으로 clock_t
형을 정의함
데이터형의 대용 이름
전처리기 사용 : #define BYTE char *
- 단,
BYTE pa, pb;
와 같이 여러 개의 변수 리스트를 선언하는 경우 원하는 결과를 얻을 수 없음
키워드 사용 : typedef char byte;
typedef
를 사용하는 것이#define
보다 훨씬 좋은 선택임
5.3 do while 루프
do
body
while (test-expression);
for
루프나 while
루프와는 달리 탈출조건(exit-condition) 루프임
루프 몸체가 먼저 실행되고, 조건을 나중에 검사하여 참인경우 루프를 실행함
따라서 루프 몸체가 최소한 한번은 실행됨
// dowhile.cpp
#include <iostream>
int main()
{
using namespace std;
int n;
cout << "1부터 10까지의 수 중에서 ";
cout << "내가 좋아하는 수를 한번 맞추어 보십시오.\n";
do
{
cin >> n;
} while (n != 7);
cout << "맞았습니다. 내가 좋아하는 수는 럭키 세븐 7입니다.\n";
return 0;
}
일반적으로는 먼저 조건을 검사하는 루프가 더 나은 선택임
그러나 사용자로부터 입력을 받아야하는 등 do while
루프가 더 적당한 경우도 있음
5.4 Range 기반의 for 루프
C++11에는 Range 기반의 for
루프라고 불리는 새로운 형태가 추가됨
double prices[5] = { 4.99, 10.99, 6.87, 7.99, 8.49 };
for ( double x : prices )
cout << x << std::endl;
x는 prices 배열의 첫 번째 요소로 초기화되며, 배열의 range 안에 포함되는 모든 값들이 루프를 돌면서 출력됨
for (double &x : prices)
x = x * 0.8;
&x
로 배열의 값을 참조하여 변경할 수 있음
for ( int x : {3, 4, 2, 8, 6})
cout << x << " ";
Range 기반 for
루프를 초기화 리스트에도 사용할 수 있음
5.5 루프와 텍스트 입력
cin을 이용한 입력
// textin1.cpp
#include <iostream>
int main()
{
using namespace std;
char ch;
int count = 0;
cout << "문자들을 입력하시오; 끝내려면 #을 입력하시오 : \n";
cin >> ch;
while (ch != '#')
{
cout << ch;
++count;
cin >> ch;
}
cout << endl << count << " 문자를 읽었습니다.\n";
return 0;
}
표지 문자(sentinel character)라고 부르는 특수 문자를 입력 중지 신호로 사용
위 예제에서는 #
을 표지 문자로 사용함
cin
은 공백이나 개행을 무시하기 때문에 입력으로 받은 띄어쓰기들을 에코하지 않으며, 읽은 문자 수에도 포함되지 않음
cin
입력은 버퍼를 이용하므로 입력시에는 #
문자를 쓰더라도 뒤에 더 입력할 수 있음
cin.get(char)를 이용한 입력
// textin2.cpp
#include <iostream>
int main()
{
using namespace std;
char ch;
int count = 0;
cout << "문자들을 입력하시오; 끝내려면 #을 입력하시오 : \n";
cin.get(ch);
while (ch != '#')
{
cout << ch;
++count;
cin.get(ch);
}
cout << endl << count << " 문자를 읽었습니다.\n";
return 0;
}
cin.get(char)
는 공백 문자도 입력받음
C 문법에서라면 cin.get()
의 매개변수로 &ch
를 받아야했겠지만, C++에서는 함수의 매개변수를 참조형(reference)으로 선언할 경우 그냥 변수명으로 사용 가능함
C++에서는 함수 오버로딩(function overloading)이라는 OOP기능이 제공되어 사용하는 매개변수가 다른 동일한 이름의 함수들을 만들 수 있음
파일 끝(End-Of-File) 조건
#
와 같은 기호를 입력의 끝으로 나타낼 경우, 실제로 #
을 적법하게 사용해야할 경우 문제가 생김
Unix와 MS-DOS를 비롯한 많은 운영 체제들이 리디렉션(redirection) 기능을 통해 키보드 입력을 파일으로 대체하기 때문에 키보드 입력을 받는 경우에도 그 끝을 인식할 수 있음
또는 EOF
조건을 키보드 입력을 통해 시뮬레이션하는 것도 가능함
- Unix의 Ctrl+D
- DOS의 Ctrl+Z + Enter 등
- 일부 C++ 컴파일러에서도 유사한 기능을 지원
// textin3.cpp
#include <iostream>
int main()
{
using namespace std;
char ch;
int count = 0;
cin.get(ch);
while (cin.fail() == false)
{
cout << ch;
++count;
cin.get(ch);
}
cout << endl << count << " 문자를 읽었습니다.\n";
return 0;
}
cin
은 EOF
를 탐지할시 두개의 비트 eofbit
와 failbit
를 1로 설정함
cin.eof()
멤버 함수에서EOF
가 탐지되었으면true
, 아니면false
를 리턴함cin.fail()
멤버 함수에서는eofbit
또는failbit
가 1로 설정되었을시true
, 아니면false
를 리턴함
cin
메서드가 EOF
를 발견하면 cin
객체의 EOF
조건을 나타내는 플래그가 설정되고, cin
은 더이상의 입력을 받아들이지 않음
그러나 키보드 입력시 더 입력이 필요할 수 있고, 이 때 cin.clear()
메서드로 EOF
플래그를 지울 수 있음
일반적으로는 while(!cin.fail())
이나 while(!cin.eof())
대신 while(cin)
을 사용
또는 while(cin.get(ch))
를 사용해 검사 조건에서 한번만 호출할 수도 있음
cin.get()의 또다른 버전
매개변수가 없는 cin.get()
멤버 함수는 C의 getchar()
함수와 유사하게 동작하며 문자 코드를 int
형 값으로 리턴함
cout.put()
함수는 매개변수로 char
형을 받는다는 것을 제외하면 C의 putchar()
과 유사하게 동작함
// textin4.cpp
#include <iostream>
int main()
{
using namespace std;
int ch;
int count = 0;
while ((ch = cin.get()) != EOF)
{
cout.put(char(ch));
++count;
}
cout << endl << count << " 문자를 읽었습니다.\n";
return 0;
}
cin.get()
이 EOF
에 도달할 경우 기호 상수 EOF
로 나타나는 특별한 값을 리턴함
일반적으로는 ASCII
코드값과 겹치지 않는 -1로 정의됨
단, 이 때 시스템에 따라 char
형에 부호가 없을 수 잇기 때문에 int
형에 대입해야함
cin.get(char)
는 리턴값이 istream
객체이기 때문에 객체 지향적인 접근에 사용하는 것이 적당함
get()
은 getchar()
와 putchar()
함수를 iostream
의 cin.get()
과 cout.put()
메서드로 변환하는데 주로 사용함
중첩 루프와 2차원 배열
2차원 배열을 나타내는 데이터형이 따로 존재하지는 않으나, 배열 자체를 원소로 사용하는 배열로 2차원 배열을 구현함
// nested.cpp
#include <iostream>
const int Cities = 5;
const int Years = 4;
int main()
{
using namespace std;
const char * cities[Cities] =
{
"Seoul",
"Jeju",
"Busan",
"Gangneung",
"Daegu"
};
int maxtemps[Years][Cities] =
{
{35, 32, 33, 36, 35},
{33, 32, 34, 35, 31},
{33, 34, 32, 35, 34},
{36, 35, 34, 37, 35}
};
cout << "2009년부터 2012년까지의 연중 최고 온도\n\n";
for (int city = 0; city < Cities; ++city)
{
cout << cities[city] << " : \t";
for (int year = 0; year < Years; ++year)
cout << maxtemps[year][city] << "\t";
cout << endl;
}
return 0;
}
2차원 배열의 초기화시 1차원 배열의 초기화 값들이 나열된 중괄호들을 콤마로 분리하여 사용함
포인터 배열 대신 string
객체를 사용할 경우 자동 크기 조절 기능으로 인해 2차원 사용하기 훨씬 편리함
연습문제
-
진입 조건 루프는 루프 몸체에 들어가기 전 조건식을 평가, 탈출 조건 루프는 루프 몸체를 실행한 후 조건식을 평가
for
루프와while
루프는 진입 조건 루프,do while
루프는 탈출 조건 루프 -
01234
-
0369 12
-
6 8
-
k = 8
-
for (int i = 1; i < 65; i *= 2) cout << i << " ";
-
중괄호를 사용해 블록을 생성한다
-
a) 적법하다 - 1과 024의 두 표현식이 콤마 연산자로 결합되어 024가 전체 표현식의 값이 되고, 024는 십진수로 20이기 때문에 x에 20이 대입된다
b) 적법하다 - y에는 1이 대입되며, 전체 표현식의 값은 024가 되지만 사용되지는 않는다 cin >> ch
는 공백 문자를 입력받지 않고 건너뛴다