C++ Stream I/O

이정빈·2023년 6월 2일
0

1. Stream I/O in C++

1.1 I/O Types, Standard Lib, void*

1.1.1 Streams

스트림은 프로그램의 안과 밖을 흐르게 하는 바이트열이다. 즉, 프로그램과 데이터의 흐름을 제어한다.

Input Source는 안에서 프로그램으로 이동한다. (키보드, 마우스 등)
입력 데이터는 바이트열 -> 자료형으로 변환

Output Sink는 프로그램에서 밖으로 이동한다. (모니터, 파일 등)
출력 데이터는 자료형 -> 바이트열로 변환된다.

데이터는 일시적으로 버퍼에 저장되고, 다 차거나 프로그램이 끝날 때 버퍼가 비워진다.

1.1.2 I/O Procedure

  1. 스트림 생성
  2. 소스, 싱크와 연결
  3. 입력,출력 연산자로 작업 수행
  4. 소스, 싱크와 연결 끊기
  5. 스트림 소멸

스트림 객체의 특징
참조값만 가진다.
1. 복사 생성자, 할당 연사자 x
2. 참조로 전달, 리턴
3. const 사용 x

1.1.3 I/O Format

  1. Formatted I/O
    사람이 편한 방식으로 처리
    정수,실수,문자열 등 원하는 형식으로 입출력
    <<,>> 사용

  2. Unformatted I/O
    주로 파일, 이진 데이터
    데이터를 바이너리 형태로 사용
    read,write

1.1.4 iostream Header

  1. cin : 키보드 장치와 연결된 istream 타입의 표준 입력 스트림 객체
  2. cout : 스크린 장치와 연결된 ostream 타입의 표준 출력 스트림 객체
  3. cerr, clog : 둘 다 표준 오류 출력 스트림 객체
    clog는 버퍼를 거치지만 cerr는 버퍼를 거치지 않고 스크린에 오류 출력

1.1.5 C-style String

단순히 널(종료)문자를 가지는 문자열이다.

char c[]="string";

6글자 밖에 없지만 C++은 자동적으로 마지막에 '\0' 문자를 넣어서 실제로 7이다.
그래서 실제로는 한 칸을 비워줘야한다.

void main() {
	char c[3] = { 'a','b','c' };
	cout << c;
}

c문자 스타일링이 오류는 체킹안되지만, 출력시 오류가 남.

오류가 안 나게 하려면 사이즈 그대로 출력해야함.

왜냐하면, cout<<c는 종료문자까지 출력하기 떄문임.

1.1.6 void*

const char 은 문자열 type
const void
로 형변환을 하면 주소가 출력됨 (16진수)
같은 문자열을 가르키는 주소를 보면 같게 되는데 동일한 문자열 리터럴을 가르켜서 그렇다.
a,b 둘다 같은 pby의 주소를 가르킴

문자열을 가르키려면 const char*이여야 한다.

#include <iostream>
using namespace std;

void main() {
	const char* const word = "again"; // 앞의 const는 again 수정 불가, 뒤는 주소 수정 불가 

	cout << word << endl;
	cout << static_cast<const void*>(word) << endl;
}

again
00007FF6286DAC1C

const char*: 이 부분은 word를 가리키는 포인터의 타입을 나타냅니다. const 한정자가 포인터를 가리키는 대상인 char에 적용됩니다. 이렇게 선언된 포인터는 가리키는 문자열을 변경할 수 없지만, 포인터 자체를 다른 주소로 변경하는 것은 가능합니다.

const: 이 부분은 포인터 자체를 가리키는 대상에 적용됩니다. const 한정자가 변수 word에 적용되므로, word의 값을 변경할 수 없습니다. 이는 word가 다른 문자열을 가리키거나 포인터 자체를 변경하는 것을 방지합니다.

따라서 const char* const word = "aaa";은 word가 가리키는 문자열을 변경할 수 없고, word 자체를 다른 주소로 변경할 수 없는 상수 포인터를 선언하는 것을 의미합니다.

#include <iostream>
using namespace std;

void main() {
	const char* c = "dlwlrma";
	const void* v = c;
	cout << v << endl;
	// const char* c2=v;
	const char* tmp = static_cast<const char*>(v);
	cout << tmp << endl;
}

00007FF6E43FAC10
dlwlrma

void*는 다른 타입 포인터로 변환이 불가능하기 때문에 const,static_cast 를 이용해서 변환해야 한다.
void에 특정한 주소를 넣고 그 주소에 맞게 형변환을 해주면 그 값이 출력이 됨.

그리고 반대로 const void 변수를 const char으로 바꿀 때, 암시적 x 무조건 static_cast 사용

1.2 Stream I/O Lib

1.2.1 Stream Input

istream은 >> 입력 연산자를 제공한다.
이것은 공백을 넘기고, istream 객체를 참조 리턴한다.

if, while(cin>>a) 할 때, c++은 묵시적으로 output을 void*로 형변환 한다.
만약 true 면 non-null-ptr이고, false면, null ptr이다.

void main() {
	int a = 0;
	if (cin >> a) {
		cout << a << endl;
	}
	else {
		cout << "fail" << endl;
	}
	
}

hi
fail

void*로 알아서 캐스팅이 되지만 타입이 맞지 않아서 failbit()이 true로 설정되고 fail이 출력된다.

1.2.2 Stream Input State

  1. eofbit
    추출할 추가적인 문자가 없음
    EOF를 만나면 true를 리턴한다.

  2. failbit
    유효하지 않은 입출력, 타입이 맞지 않음
    여전히 스트림에 문자들이 남아 있을 때

  3. badbit
    스트림에 문제가 발생 (드뭄), 메모리 부족
    심각한 오류이므로 회복 잘 안됨

  4. goodbit
    모든 것이 정상적으로 작동
    eof,fail,badbit이 설정되어 있지 않을 경우

이 네 상태들은 ios에 enum형태로 저장되어 있다.
goodbit = 0x00
eofbit = 0x01
failbit = 0x02
badbit = 0x04

rdstate()로 현재 상태를 알 수 있다. 오류가 여러 개이면 합의 형태로 나타나고 최대 7까지이다.
cin.clear()은 모든 상태들을 없애고 goodbit로 설정한다.
cin.clear(ios::failbit)은 다 없애지만 failbit으로 설정한다.
cin.setstate(ios::failbit)은 failbit으로 설정한다.

clear와 setstate의 차이점은 clear는 모든 스트림 오류 상태가 지워지고 특정한 것으로 세팅이지만, setstate는 초기화 하지않고 아직 설정되지 않을 것을 설정하는 것임, 즉 다른 오류 상태는 그대로이다.

void main() {
	cout << cin.rdstate() << endl;
	cin.setstate(ios::eofbit);
	cout << cin.rdstate() << endl;
	cin.setstate(ios::failbit);
	cout << cin.rdstate() << endl;
	cin.setstate(ios::badbit);
	cout << cin.rdstate() << endl;
	cin.clear(ios::badbit);
	cout << cin.rdstate() << endl;
}

0
1
3
7
4

중복 값 적용이 안되므로 최대치는 7이고 1,2,4순이다.

#include <iostream>
using namespace std;

void main() {
	int n;
	cout << cin.rdstate()
		<< cin.eof()
		<< cin.fail()
		<< cin.bad()
		<< cin.good() << endl;

	cin >> n;
	cout << cin.rdstate()
		<< cin.eof()
		<< cin.fail()
		<< cin.bad()
		<< cin.good() << endl;

	cin.clear();
	cout << cin.rdstate()
		<< cin.eof()
		<< cin.fail()
		<< cin.bad()
		<< cin.good();
}

00001
a
20100
00001

타입 매칭이 안되므로 failbit이 1로 바꼈다.

void main() {
	int a = 0;
	cin >> a;
	while (!cin.eof()) {
		if (cin.fail()) {
			cin.clear();
			cin.ignore(255, '\n');
		}
		cout << a << endl;
		cin >> a;
	}
}

a
0
b
0
c
0
1
1
2
2
3
3

끝을 만나면 종료인데 계속 반복하게끔 설정을 해뒀다. 단 ctrl+z를 하면 종료된다.
int형에 a,b,c를 넣으면 fail이므로 if문으로 들어가게 된다.
그러면 상태를 다 clear해주면 goodbit만 1로 설정된다.
그리고 버퍼를 255만큼 개행문자가 나오기까지 지워준다.

1.3 APIs

1.3.1 get()

입력 스트림에서 문자를 읽어 리턴, 오류 EOF를 만나면 -1을 리턴

  1. int get() // 인자가 없는 경우
    한 개의 문자를 읽어 리턴하며 공백문자도 읽는다.

int ch;
ch=cin.get();

 void main() {
	int c;
	while ((c = cin.get()) != EOF) {
		cout.put(c);
		if (c == '\n') {
			break;
		}
	}

여기서 put(int)인데 put은 int형으로 받아도 문자를 출력해준다.
아니면 cout<<(char)int; 형변환은 필수다.

  1. istream& get(char& ch) // 저장

char c;
cin.get(c);

위와 거의 비슷하지만 따로 저장이 가능하다.

void main() {
	char c;
	char pre = '\n';
	while (cin.get(c)) {
		if (pre == ' ' || pre == '\n') {
			cout.put(toupper(c));
		}
		else {
			cout.put(c);
		}
		pre = c;
	}
}

공백을 만나거나 개행문자를 만나면 다음 문자를 대문자로 만들어준다.

void main() {
	cout << "ASC : " << cin.get() << endl;
	cout << "tmp" << endl;
	char c;
	cout << "tmp" << endl;
	cin.get(c);
	cout << "char : " << c << endl;
}

ASC : ABC
65
tmp
tmp
char : B

위의 예제는 상당히 중요한 예제이다.
cin.get()으로 int 형식으로 받지만 입력은 ABC로 했다.
그러면 A는 65이므로 65를 출력한다.
그리고 cin.get(c)로 다음 문자인 B를 하나 받는다.
그리고 B 출력

  1. cin.get(str1,50,'\n')
    개행문자는 기본 값이라서 없어도 가능
    사이즈가 다 차거나 개행문자를 만나면 끝낸다. 하지만 개행문자 직전까지이므로 개행문자는 포함되지 않고 제거되지도 않는다. 계속 버퍼에 남아잇는다.
void main() {
	char str1[50];
	char str2[50];
	cin.get(str1, 50);
	cout << str1 << endl;

	cin.ignore(); 

	cin.get(str2, 50, ' ');
	cout << str2 << endl;
}

lee jkong
lee jkong
lee jkong
lee

이 예제에서 중요한 곳은 ignore이다.
cin.get(...)을 통해서 문자를 입력받았다. 사이즈 만큼 출력을 하는데, 50보다 적으면
개행문자가 무조건 있다. get은 개행문자를 따로 제거하지 않고 버퍼에 남겨둔다.
그러므로 ignore함수를 통해 개행문자를 제거한다.ㅣ

그리고 또 중요한 점은 마지막에 종료문자가 들어간다는 점이다.
hello입력시 hello+'\0'가 들어간다.

개행문자를 제거하지 않으면 오류가 나므로 cin.ignore에 대해 알아보자.

cin.ignore() // 1개 제거
cin.ignore(5) // 5개 제거
cin.ignore(5,' ') // 공백이 나오기 전까지 제거 최대5개까지 제거함

1.3.2 getline()

void main() {
	char str1[50];
	char str2[50];
	cin.getline(str1, 50);
	cout << str1 << endl;

	cin.getline(str2, 50, ' ');
	cout << str2 << endl;
}

getline은 마지막 개행문자를 제거해준다. 단, 49문자 + 종료문자는 동일하다.
그리고 개행문자는 포함되지 않는 것도 잊지말것

string str1;
getline(cin,str1,' ');
cout << str1 << endl;

cin을 받을 수도 있다, 하지만 string 헤더 파일, string 타입이어야함!

char line[80];
cin.getline(line,80);
int n=cin.gcount();

cin.gcount()는 실제 읽은 문자의 개수를 알려준다.

getline() hello+enter 는 6개
하지만 get() hello는 5개임

1.3.3 put()

한 개의 문자를 출력한다.

cout.put('A') // A
cout.put('A').put('B') // AB
cout.put(65) // A

1.3.4 Unformatted I/O

read, write, gcount() 바이너리 형식으로 입출력을 하는건데 주로 파일 입출력에서 많이 다룰 것이다. 원하는 바이트 크기만큼 다루기 수월하고 get,put과 비교하면 훨씬 빠르다.

read()는 특정 수보다 적게 입력 받으면 failbit이 설정된다.


void main() {
	char buf[80];
	cin.read(buf, 20);
	int n = cin.gcount();
	cout.write(buf, n);

}

20만큼 안 읽으면 출력이 안됨

int main() {
    char buf[80];
    cin.getline(buf, 20); // 최대 20개의 문자를 입력받음

    int n = cin.gcount();
    if (n <= 10) {
        cout.write(buf, n);
    }
    else {
        cout << "Input exceeds 10 characters." << endl;
    }

    return 0;
}

cin.read() 함수는 읽을 문자의 개수를 제한하는 기능을 가지고 있지 않습니다. 따라서 입력한 문자 개수에 상관없이 입력 버퍼에서 최대한 많은 문자를 읽으려고 시도하게 됩니다.

1.3.5 strcpy, strncpy

  1. strcpy
void main() {
	char c[10] = "dlwlrma";
	char d[10];
	strcpy(d, c);
	cout << d << endl;
}

strcpy는 위험해서 권장하지 않는 함수이다. 왜냐하면 받는 함수에서 종료문자 포함해서 공간이 부족하면 오류가 생기기 때문이다. 즉, 문자열+종료문자 만큼의 공간이 필요하다.

  1. strncpy
void main() {
	char c[10] = "dlwlrma";
	char d[10];
	strncpy(d, c,6);
	d[7] = '\0';
	cout << d << endl;
}

strcpy보다 조금 더 안전한 함수이다. 받을 문자의 개수를 정하기 때문에 문자열+종료문자 보다 적어도 괜찮다. 하지만 출력 시에는 종료문자가 꼭 필요하다. (10보다 더 받으려고 하면 오류남)

2. Stream Manipulators

2.1.1 precision, fixed, scientific

  1. precision
    소수점 자리가 표현되는 정도를 보여준다.
    음수 사용 x
    한번 세팅 시 고정

  2. fixed
    부동 소수점 자리를 고정 소수점자리 변환한다.
    ex)fixed<<setprecsion(4) 고정 소수점 4자리

void main() {
	double number = 123.456789;
	cout << setprecision(7) << number << endl;

	double f = 1.23456789;
	cout.precision(5);
	cout << f << endl;

	cout << fixed << f << endl;
}

123.4568
1.2346
1.23457

cout.precision() 은 기본값 6이 출력된다.

fixed가 없는 경우엔 정수부+소수부 기준으로 다섯자리를 반올림하여 출력
5뒤에 있는 6을 반올림한다.

fixed가 있으면 소수분만을 기준으로 다섯자리를 반올림하여 출력
6뒤 7을 반올림한다.

fixed를 하면 소수점 자리부터 5개
하지 않으면 1~9가 나온 수 부터 5개

void main() {
	double x = 0.001234567;
	double y = 1.946e9;

	cout << x << '\t' << y << endl;
	cout << scientific << x << '\t' << y << endl;
	cout << fixed << x << '\t' << y;
}

0.00123457 1.946e+09
1.234567e-03 1.946000e+09
0.001235 1946000000.000000

기본값이 6이므로 6뒤 7 반올림하였다.
scientific(unsticky,sticky) 는 소수점 뒤 6자리로 만들고 칸 이동에 따라 e 뒤에 +,-를 붙여서 사용한다.
fixed는 6자리인 4뒤 5를 반올림한다.
fixed는 소수점 6자리는 무조건 확보

fixed, scientific는 상관관계? 한 쪽이 써지면 다른 한쪽은 해제관계임
둘 다 sticky하지만, 같이 쓸 수 없는 관계임,,

void main() {
	double n1 = 0.00056789;
	double n2 = 12.34;

	cout << setprecision(4);
	cout << n1 << '\t' << n2 << endl;
	cout << n1 << '\t' << n2 << endl;
	cout << scientific;
	cout << n1 << '\t' << n2 << endl;
	cout << n1 << '\t' << n2 << endl;
	cout << fixed;
	cout << n1 << '\t' << n2 << endl;
	cout << n1 << '\t' << n2 << endl;
	cout.unsetf(ios::fixed);
	cout << n1 << '\t' << n2 << endl;
}

0.0005679 12.34
0.0005679 12.34
5.6789e-04 1.2340e+01
5.6789e-04 1.2340e+01
0.0006 12.3400
0.0006 12.3400
0.0005679 12.34

1~9가 나온 수 부터 4까지 출력함
scientific는 고정이지만 fixed를 설정,해제하면 해제된다.
fixed는 소수점 4자리를 무조건 확보한다.

2.1.2 width, setw

cout<<cout.width(); // 0출력

얼마만큼의 공간을 확보하는 조정자들이다.
고정적이지 않고 하나의 필드에만 적용된다.
cin.width() 의 경우에는, 종료문자를 읽을 때까지 n-1개의 문자열을 읽는다.
cout.width() 정해진 너비만큼 문자열을 출력한다.

cout << "Hi" << endl; // 1

cout.width(5);
cout << "Hi" << endl; // 2

cout.width(5);	
cout.fill('*');
cout << "Hi" << endl; // 3

cout.width(5);
cout.fill('*');
cout << "HiHiHiHi" << endl; // 4

Hi
Hi
***Hi
HiHiHiHi

cout << "BYE" << endl; // 1
cout << setw(5) << "BYE" << endl; // 2
cout << setw(5) << setfill('!') << "BYE" << endl; // 3
cout << setw(5) << setfill('!') << "BYEBYEBYE" << endl; // 4

BYE
BYE
!!BYE
BYEBYEBYE

void main() {
	int v = 4;
	char sen[10];

	cin.width(5);
	while (cin >> sen) {
		cout.width(v++);
		cout << sen << endl;
		cin.width(5);
	}	
}

This is a test of the width member function
This
is
a
test
of
the
widt
h
memb
er
func
tion

한 번에 읽을 때는 n-1개, 출력할 때는 n개이다.
1. 4개의 문자를 읽을거야.
2. while문 들어갈때 문자열 입력, EOF를 만날 때까지 반복
3. 4개만큼의 공간을 확보하지만 한 칸씩 늘릴거야
4. 읽은 4개의 문자를 출력할거야
5. 고정적이지 않기 때문에 한 번 더 호출할거야
6. 공백이면 끊김
계단식으로 나오는 이유가 cout.width(++) 해주고 있어서 그럼

void main() {
	cout.width(5);
	cout << "lee" << setw(7) << "wook" << endl;
	cout.width(5);
	cout << "dlwlrma" << setw(7) << "wook" << endl << endl;

	char str[10];
	cin >> setw(5) >> str;
	cout << str << endl;
	cin.width(5);
	cin >> str;
	cout << str << endl;
}

lee wook
dlwlrma wook

dlwlrmajkoong
dlwl
rmaj

setw(7)은 무조건 7개의 칸은 가진다는 의미, 이 이상보다 더 긴 문자열이 있으면 문자열 우선 출력
cin>>setw(5), cin.width(5)는 n-1개의 문자열을 읽는다.
대신 공백이 나올 때 까지임.

2.1.3 left, right, internal

internal은 부호 패딩 숫자
부호,진법은 왼쪽에 위치, 숫자는 오른쪽에 위치, 패딩은 가운데
left,right, internal은 계속 세팅값 유지한다.
showpos 부호 출력해주는 용도 sticky

void main() {
	cout << internal << showpos << setw(10) << 123 << endl;
	cout << 123 << endl;
	cout  <<left <<showpos<< setw(10) << 123 << endl;
	cout << 123<<endl;
	cout <<right<< showpos << setw(10) << 123 << endl;
	cout << 123<<endl;
}

   +   123

+123
+123
+123
+123
+123

setw()는 sticky하지 않아서 그럼

2.1.4 dec, oct, hex, setbase

setbase() 10,8,16을 넣어 설정가능함.
sticky

void main() {
	int n = 20;
	cout << n << " " << hex << n << endl;
	cout << n << " " << oct << n << endl;
	cout << setbase(10)<<n << " " << dec << n << endl;
}

20 14
14 24
20 20

2.1.5 showbase

sticky oct(8) 0, hex(16) 0x

void main() {
	int n = 20;
	cout << showbase;
	cout << n << " " << hex << n << endl;
	cout << n << " " << oct << n << endl;
	cout << setbase(10)<<n << " " << dec << n << endl;
}

20 0x14
0x14 024
20 20

2.1.6 fiil, setfill

비어있는 곳을 채움, sticky

void main() {
	int x = 10000;
	cout << x << endl;
	cout << setw(10) << x << endl;
	cout << left << setw(10) << x << endl;
	cout << showbase << internal << setw(10) << hex << x << endl;
	cout << endl;
	cout << right;
	cout.fill('*');
	cout << setw(10) << dec << x << endl;
	cout << left << setw(10) << setfill('&') << x << endl;
	cout << internal << setw(10) << setfill('^') << hex << x << endl;
}

10000
10000
10000
0x 2710

*10000
10000&&&&&
0x^^^^2710

2.1.7 showpoint

기본 설정값은 6이다. (숫자가 나타난 시점부터임)
만약 소수점 자리를 바꾸고 싶으면 setprescision으로 바꾸자

void main() {
	cout << 9.9900 << endl;
	cout << showpoint << 9.9900 << endl << 9.0000;
}

9.99
9.99000
9.00000

void main() {
	cout << showpoint << setprecision(10)<<0.00234;
}

0.002340000000

2.1.8 uppercase

hex에 있는 0x값도 대문자화한다. 기본은 당연히 소문자
sticky

void main() {
	cout << uppercase << 4.345e10 << endl;
	cout << hex << showbase << 123456789;
}

4.345E+10
0X75BCD15

Prac 1

int main() {
	cout << "Type any strings you want to convert.\n";
	string s;
	while (getline(cin, s)) {
		int len = s.length();
		int tmp;
		for (int i = 0; i < len; i++) {
			tmp = static_cast<int>(s[i]);
			cout << tmp << " ";
		}
		cout << "\n";
	}
	return 0;
}

Type any strings you want to convert.
Okay.
79 107 97 121 46
hi
104 105
^Z

Prac 2

class Date {
public:
	int day, month, year;
	Date() :year(2023), month(1), day(1) {};
	friend ostream& operator<<(ostream&, const Date&);
	friend istream& operator>>(istream&, Date&);
};

ostream& operator<<(ostream& out, const Date& d) {
	out << d.day << " " << d.month << " " << d.year << endl;
	return out;
}
istream& operator>>(istream& in, Date& d) {
	in >> d.day >> d.month >> d.year;
	return in;
}

int main() {
	Date d;
	Date d2;

	cout << d << endl;
	cout << "Enter Date ";
	cin >> d2;
	cout << d2;
	return 0;
}

1 1 2023

Enter Date 05 18 2023
5 18 2023

Prac 3

int main() {
	cout << "Type a character to count. " << endl;
	char c;
	cin.get(c);
	cin.ignore();
	cout << "Type any input string\n";
	string s;
	getline(cin, s);
	int len = s.size();
	int cnt = 0;
	for (int i = 0; i < len; i++) {
		if (c == s[i]) {
			cnt++;
		}
	}
	cout << "Count : " << cnt;
	return 0;
}
int main() {
	cout << "Type a character to count. " << endl;
	int c;
	c = cin.get();
	cin.ignore();
	cout << "Type any input string\n";
	string s;
	getline(cin, s);
	int len = s.size();
	int cnt = 0;
	for (int i = 0; i < len; i++) {
		if ((char)c == s[i]) {
			cnt++;
		}
	}
	cout << "Count : " << cnt;
	return 0;
}
int main() {
	char c[2]; // [2] = 단일문자만 받아야 프로그램이 작동하게끔
	string input;
	int count = 0;

	cout << "Type a character to count.\n";
	cin.get(c, 2);
	cin.ignore();

	cout << "Type any input string\n";
	getline(cin, input,'\n');
	int len = input.length();

	for (int i = 0; i < len; i++) {
		if (*c == input[i]) {
			count++;
		}
	}
	cout << "Count : " << count;
	return 0;
}

Type a character to count.
a
Type any input string
abc abc aabb
Count : 4

0개의 댓글