Programming Language/C++

Ch 03. 입출력 - 03. cout

hustle_D 2025. 3. 26. 15:01
반응형

cout은 C++의 표준 입출력 스트림으로, 콘솔에 데이터를 출력할 때 사용하는 도구이다.

기본적으로는 prinft보다는 객체지향적이며, 타입 안정성과 확정성이 뛰어난 방식이다.

이제 cout에 대해서 살펴 보도록 하자.

 

1. 타입의 구분 없이 사용이 가능한 cout

기존에 printf의 경우는 형식 지정자를 사용해서 어떤 형식인지를 지정하고 그에 맞는 데이터를 전달해줘야만 했었다.

그러나 cout의 경우는 어떤 데이터를 전달하냐에 따라서 알아서 그 타입을 확인하고 그 타입에 맞게 값을 출력해준다.

이렇게 정수를 입력해도, 문자를 입력해도 따로 설정하지 않더라도 해당 값을 출력해준다.

물론 해당 값은 순서대로 정수, 문자열의 형태로 출력이 되었을 것이다.

 

이렇게가 가능한 이유는 cout은 받는 타입에 대비해서 그 값을 받는 형태를 모두 오버로딩으로 만들어 뒀기에 가능한 기능이다.

 

std::cout << "Hello";

위 코드는 내부적으로 확인해보면 

namespace std {
    ostream cout;

    ostream& operator<<(ostream& os, const char* str);
    ostream& operator<<(ostream& os, int value);
    // ... 등등 오버로드 수십 개
}

이런식으로 선언이 되어 있다 

 

그리고 << 라는 연산자(원래는 비트 연산자로 쓰이던 연산자)를 

ostream& operator<<(ostream& os, const char* str);

이런식으로 대체할 수 있도록 오버로딩을 하면서 결과적으로 우리가 사용한 

std::cout << "Hello";

 

 위 코드는 컴파일러가 보고 아래와 같이 함수로 실행되어 

operator<<(std::cout, "Hello")

로 실행하게 된다

 

물론 이런것들은 내부적으로 들어가는 부분이라 이렇게 하는 경우 저렇게 하는 경우 에러가 발생할 수 있다.

더보기

시도 했는데 안되던 부분 

이렇게 operator에 정수 값을 전달할라 그러면 에러가 남.

봣드만 이거 std안에 있는 얜데 그냥 써서 전역으로 찾아서 그렇네~ 라고 설명해줌(gpt)

그래서 std::를 붙여봄

또 안된다함 이건 올려보니까 std에 비슷한게 많다 뭐가 원하는거야? 라고 하는듯 보임

그래서 더 찾아봤드니, 이거 cout안에 들어 있는 멤버 함수라서 쓸라면 cout에 점 표기법으로 불러와야해, 그리고 뒤에 첫번째 인자로 cout을 넘길 필요 없어 라기에 해봤더니 

이래하니까 됨

이래저래 아는바가 없으니 이런 기본적인 부분에서도 쓸데없이 시간을 낭비하는것 같아서 너무 시간이 아쉬운 생각이듦

우선은 이런 식으로 오버로딩 되어 설정되어 있고 <<라는 것은 컴파일러 입장에 함수와 동일하다고 본다 라는 부분을 이해하고 있자.

 

간단하게 저런 부분을 고려하지 않고 생각해면 << 은 이 방향으로 데이터를 전송한다, 흐른다 라고 생각하면 좀 더 쉽게 이해할 수 있다. << 는 cout 으로 "data"를 전달해서 출력한다! 라고 이해해두면 까먹지 않을것 같다.

 

cout에서는 <<말고도 데이터를 출력할 수 있는 함수들이 추가적으로 있다.

 

- std::cout.put('문자')

put() 메서드는 하나의 문자를 출력할 수 있는 기능을 가진다.

 

- std::cout.write("문자열", 출력할 갯수)

write() 함수는 문자열을 n개만큼 출력하는 기능을 가지고 있다.

첫번째 전달인자로 전달한 문자열을 두번째 전달인자로 전달한 갯수만큼 출력할 수 있게 한다.

 

 

- std::cout.width(N)

<<를 사용해서 출력하는 데이터의 폭을 지정해준다.

이렇게 폭을 먼저 설정해주고 <<연산자를 사용해서 출력해주면 

이렇게 앞에 4개의 폭이 추가된 5개의 폭의 데이터가 출력된다.

 

그러면 write()나 put() 메서드의 경우는 이 설정이 먹을까?

안먹는걸 볼 수 있다.

 

이는 << 와는 다르게 그냥 std::ostream 내부 버퍼에 직접 출력 문자나 바이트를 넣어주는 방식이기에 포멧팅을 전혀 사용하지 않고 그렇기에 이런 포멧 관련된 것은 설정이 되지 않는다.

 

여기서 공백으로 추가되는 공간을 다른 문자로 넣어주려면 어떻게 해야할까

 

- std::cout.fill("문자")

fill 함수 내부에 문자를 하나 넣어주면 해당 문자가 width에 설정된 공간의 공백 대신에 추가되게 된다.

이렇게 설정해두면 1의 왼쪽으로 *이 5폭을 포함해서 4개가 추가되어 출력 되게 된다.

 

여기서 width는 일회성으로 실행된 후 정상적으로 되돌아가지만 fill의 경우는 설정한 이후에 다시 width를 설정하면 그대로 그 설정이 추가된다.

 

precision() : 정밀도 주기

부동 소수점을 사용할때 사용하던 것인데, cout에서도 정밀도를 줄수가 있다.

이때 사용되는 함수는 precision() 함수이다.

 

- precision()

precision 함수는 전달인자 없이 전달하는 경우는 현재 정밀도를 조회한다

보면 3.1415926535에서 6숫자를 출력하고 이 값에 대한 현재 정밀도는 6이 되기에 6을 출력해준다.

 

- precision(n)

precision 함수에 전달인자로 정수를 전달하면 그 값만큼의 정밀도를 갖고 숫자를 출력해준다.

이렇게 출력하면 

이렇게 9자리 수가 나오는 것을 볼 수 있다.

 

여기서 double형이 아니라 float형의 경우는 상황에 따라서 정밀도의 최대 크기가 7인데 현재 3.1415926532를 넣으려고 한다면  정밀도를 벗어나기 때문에 손실 되어 일부 숫자가 변경 및 손실이 될 수 도 있다.

그래서 아래와 같이 float에 초과되는 크기의 정밀도를 넣으면

값의 결과가 

정확한 값이 나오진 않고 근사값이 나오는 상황이 생길 수 있다.

또한 float형의 경우는 표현할 수 없는 값이 들어오는 경우 근사치를 표출하기에 자동으로 반올림하는 경우도 있을 수 있다라는 점을 이해하고 있자.

 

- setf() : 스트림 형식 플래그를 설정하는 함수 - cout과 같은 출력 스트림에서 출력 형식을 제어하기 위해 사용

setf는 아래와 같은 형태를 갖고 있다

ios_base& setf (ios_base::fmtflags flags);
ios_base& setf (ios_base::fmtflags flags, ios_base::fmtflags mask);

여기서 매개변수에 대한 설명을 해보자면

 

- flags : 설정하고 싶은 출력 형식 플래그

- mask : 어떤 종류의 형식 플래그를 설정할 것인지를 지정한다 - 여러 카테고리(정수 표시, 부동 소수점 표시 ..등등)중 선택적으로 변경하고 싶을 때 사용한

 

형식 플래그의 종류는 아래와 같이 존재 한다.

카테고리 범주 플래그 설명
정수 표시 basefield ios_base/ios::dec 10진수(decimal)
정수 표시 basefield ios_base/ios::hex 16진수(hexa-)
정수 표시 basefield ios_base/ios::oct 8진수(octat-)
부동 소수점 floatfield ios_base/ios:fixed 고정 소수점 표시 (예: 120.00)
부동 소수점 floatfield ios_base/ios::scientific 지수 표기법 사용 (예: 1.2e+02)
정렬 adjustfield ios_base/ios::left 왼쪽 정렬
정렬 adjustfield ios_base/ios::right 오른쪽 정렬
정렬 adjustfield ios_base/ios::internal 기호(+/-)는 왼쪽 정렬, 숫자는 오른쪽 정렬 (예: - 42)
기타   ios_base/ios::showpoint 소수점 이하가 없어도 소수점 강제 출력
기타   ios_base/ios::showpos 양수 앞에 + 표시
기타   ios_base/ios::showbase 진수 기반 접두사 출력 (0x, 0, 등)
기타   ios_base/ios::uppercase 16진수, 지수 표기에 대문자 사용 (0X, E+02 등)
기타   ios_base/ios::boolalpha true/false로 출력
기타   ios_base/ios::unitbuf 매 출력마다 버퍼 비움 (flush)
기타   ios_base/ios::skipws 공백 무시 (기본값)
기타   ios_base/ios::noskipws 공백 무시 안 함

*ios나 ios_base나 대부분 비슷하게 기능을 하게 되니 어느것을 사용해도 상관은 없긴함

 

사용법

이렇게 setf의 전달인자로 위에서 말한 flags값을 던져주면 출력에 다음 출력 부터 형식에 영향을 준다.

그리고 이렇게 set된 flags는 한번 설정만 하면 지속적으로 영향을 추게 되는데

만약 이렇게 설정한 플래그 값을 변경해주고 싶거나 없에고 싶다면 unsetf함수를 사용하면된다.

 

-unsetf() : 지정한 플래그의 값을 해제하기 위해서 사용된다.

stream.unsetf(ios::fmtflags flags);

unsetf의 사용방법은 위와 같고 출력 스트림에 설정했던 형식 플래그를 해제하는 함수이다.

해제 방법은 그냥 해제하고자 하는 플래그를 전달인자로 전달하기만 하면 된다.

 

위에서 설정했던 showpos 플래그를 동일하게 넣어보면

이렇게 플래그가 해제 된것을 볼 수 있다.

 

여기서 범주가 있는 정수 표시, 부동소수점 표시, 정렬 flags에 경우는 덮어 씌워진다는 개념으로 사용되는게 아니기 때문에 한번 설정한 후에 해제를 하지 않고 다음걸 설정한다고 해서 다음 설정이 입혀지지 않음

 

예를 보여주자면 

만약 이런 코드가 존재할때 내가 코드를 작성할때 바란 부분은 첫번째 출력 부분은 폭이 10인 상태에서 왼쪽 공백을 두고 오른쪽으로 정렬이 되길 바라고 두번째 출력에서는 왼쪽으로 정렬되어 오른쪽에 공백이 생성되기를 바라면서 생성을 했다.

 

바라는 모습은 아래와 같은 모습인데

실제 위 코드를 출력해보면 

모두 오른쪽으로 숫자가 정렬되어 있는 것을 볼 수 있다.

 

이렇게 flags 값은 덮어 씌우진다는 개념보다는 추가된다는 개념이라서 추가된 플래그가 인식되기 위해서는 이전 플래그를 해제 한 후에 추가 시켜야만 한다.

그래서 

이렇게 left를 등록 한 이후에 unsetf를 right를 해준다면 

이렇게 원하는 대로 두번째 플래그인 left 정렬이 두번째 출력에 적용되는 것을 볼 수 있다.

 

이걸 한번에 하게 해주기 위해 사용하는 것이 mask라는 것으로 위에 표에서 범주라고 표현했던 

이 부분이다.

setf의 두번째 전달인자로 adjustfield를 전달하면 기존에 설정된 그 범주의 값을 제거해주면서 첫번째 전달인자로 전달한 값이 적용된다.

이렇게 적용하면 기존에 adjustfield의 범주에 해당하는 right 정렬값이 제거되고 첫번째 인자인 left가 적용되게 된다.

그러면서 우리가 바라는대로 두번째 출력은 왼쪽으로 정렬되게 된다.

 

추가로 이걸 << 연산자를 사용해서도 적용이 가능한데 이때는 

이런식으로 iso_base에 해당하는 값을 std를 사용해서 출력 이전에 설정해주기면 하면 

이렇게 출력을 시켜준다.

이때도 추가로 설정하지 않으면 설정한 값은 다음 출력에서도 그대로 유지된다.

이때는 flags처럼 추가하는 개념보다는 덮어 쓰는 개념으로 사용되는 듯 보인다

추가로 해제해주는 코드는 필요 없이 적용이 되는데 

그 이유는 std::right 같은게 내부적으로는

 // 실제 내부 동작 (의사 코드)
os.setf(std::ios_base::right, std::ios_base::adjustfield);

와 같은 방식으로 구현되어 있기 때문이다.

 

구체적으로 들어가면 또 진도가 나가지 않기 때문에 대략적으로 이렇다고만 알고 넘어가자.

 

추가적으로 비슷하게 width라던가 precision 같은 정밀도 같은거라던가 설정을 위와 같이 <<에서 사용할 수 있게 할 수 도 있는데 이땐 헤더를 하나 추가더해줘야 한다.

iomanip을 추가해주면 

width를 setw라는 기능과 동일하게 사용이 가능하다.

다 같이 써보면 

이런식으로 출력이 가능하다는 것을 알 수 있다.

 

이런 저런 사용이 가능한 함수의 명칭을 정리해보자면

조작자(iomanip) 방식 멤버 함수(std:ostream, cout.~ 으로 사용하는 함수) 설명
std::setw(n) width(n) 다음 출력 필드 너비 설정
std::setprecision(n) precision(n) 부동소수점 출력 자릿수 설정
std::setfill(c) fill(C) 채움 문자 설정

 

이렇게 알고 있으면 될것 같다.

반응형